FastCcmBlockCipher.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using System.IO;
  5. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto;
  6. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Macs;
  7. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Modes;
  8. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Crypto.Parameters;
  9. using Best.HTTP.SecureProtocol.Org.BouncyCastle.Utilities;
  10. namespace Best.HTTP.Shared.TLS.Crypto.Impl
  11. {
  12. /**
  13. * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
  14. * NIST Special Publication 800-38C.
  15. * <p>
  16. * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
  17. * </p>
  18. */
  19. public class FastCcmBlockCipher
  20. : IAeadBlockCipher
  21. {
  22. private static readonly int BlockSize = 16;
  23. private readonly IBlockCipher cipher;
  24. private readonly byte[] macBlock;
  25. private bool forEncryption;
  26. private byte[] nonce;
  27. private byte[] initialAssociatedText;
  28. private int macSize;
  29. private ICipherParameters keyParam;
  30. private readonly MemoryStream associatedText = new MemoryStream();
  31. private readonly MemoryStream data = new MemoryStream();
  32. /**
  33. * Basic constructor.
  34. *
  35. * @param cipher the block cipher to be used.
  36. */
  37. public FastCcmBlockCipher(
  38. IBlockCipher cipher)
  39. {
  40. this.cipher = cipher;
  41. this.macBlock = new byte[BlockSize];
  42. if (cipher.GetBlockSize() != BlockSize)
  43. throw new ArgumentException("cipher required with a block size of " + BlockSize + ".");
  44. }
  45. /**
  46. * return the underlying block cipher that we are wrapping.
  47. *
  48. * @return the underlying block cipher that we are wrapping.
  49. */
  50. public virtual IBlockCipher UnderlyingCipher => cipher;
  51. public virtual void Init(bool forEncryption, ICipherParameters parameters)
  52. {
  53. this.forEncryption = forEncryption;
  54. ICipherParameters cipherParameters;
  55. if (parameters is AeadParameters aeadParameters)
  56. {
  57. nonce = aeadParameters.GetNonce();
  58. initialAssociatedText = aeadParameters.GetAssociatedText();
  59. macSize = GetMacSize(forEncryption, aeadParameters.MacSize);
  60. cipherParameters = aeadParameters.Key;
  61. }
  62. else if (parameters is ParametersWithIV parametersWithIV)
  63. {
  64. nonce = parametersWithIV.GetIV();
  65. initialAssociatedText = null;
  66. macSize = GetMacSize(forEncryption, 64);
  67. cipherParameters = parametersWithIV.Parameters;
  68. }
  69. else
  70. {
  71. throw new ArgumentException("invalid parameters passed to CCM");
  72. }
  73. // NOTE: Very basic support for key re-use, but no performance gain from it
  74. if (cipherParameters != null)
  75. {
  76. keyParam = cipherParameters;
  77. }
  78. if (nonce == null || nonce.Length < 7 || nonce.Length > 13)
  79. throw new ArgumentException("nonce must have length from 7 to 13 octets");
  80. Reset();
  81. }
  82. public virtual string AlgorithmName => cipher.AlgorithmName + "/CCM";
  83. public virtual int GetBlockSize()
  84. {
  85. return cipher.GetBlockSize();
  86. }
  87. public virtual void ProcessAadByte(byte input)
  88. {
  89. associatedText.WriteByte(input);
  90. }
  91. public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
  92. {
  93. // TODO: Process AAD online
  94. associatedText.Write(inBytes, inOff, len);
  95. }
  96. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  97. public virtual void ProcessAadBytes(ReadOnlySpan<byte> input)
  98. {
  99. // TODO: Process AAD online
  100. associatedText.Write(input);
  101. }
  102. #endif
  103. public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
  104. {
  105. data.WriteByte(input);
  106. return 0;
  107. }
  108. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  109. public virtual int ProcessByte(byte input, Span<byte> output)
  110. {
  111. data.WriteByte(input);
  112. return 0;
  113. }
  114. #endif
  115. public virtual int ProcessBytes(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
  116. {
  117. Check.DataLength(inBytes, inOff, inLen, "input buffer too short");
  118. data.Write(inBytes, inOff, inLen);
  119. return 0;
  120. }
  121. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  122. public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
  123. {
  124. data.Write(input);
  125. return 0;
  126. }
  127. #endif
  128. public virtual int DoFinal(byte[] outBytes, int outOff)
  129. {
  130. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  131. return DoFinal(outBytes.AsSpan(outOff));
  132. #else
  133. byte[] input = data.GetBuffer();
  134. int inLen = Convert.ToInt32(data.Length);
  135. int len = ProcessPacket(input, 0, inLen, outBytes, outOff);
  136. Reset();
  137. return len;
  138. #endif
  139. }
  140. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  141. public virtual int DoFinal(Span<byte> output)
  142. {
  143. byte[] input = data.GetBuffer();
  144. int inLen = Convert.ToInt32(data.Length);
  145. int len = ProcessPacket(input.AsSpan(0, inLen), output);
  146. Reset();
  147. return len;
  148. }
  149. #endif
  150. public virtual void Reset()
  151. {
  152. associatedText.SetLength(0);
  153. data.SetLength(0);
  154. }
  155. /**
  156. * Returns a byte array containing the mac calculated as part of the
  157. * last encrypt or decrypt operation.
  158. *
  159. * @return the last mac calculated.
  160. */
  161. public virtual byte[] GetMac()
  162. {
  163. return Arrays.CopyOfRange(macBlock, 0, macSize);
  164. }
  165. public virtual int GetUpdateOutputSize(int len)
  166. {
  167. return 0;
  168. }
  169. public virtual int GetOutputSize(int len)
  170. {
  171. int totalData = Convert.ToInt32(data.Length) + len;
  172. if (forEncryption)
  173. {
  174. return totalData + macSize;
  175. }
  176. return totalData < macSize ? 0 : totalData - macSize;
  177. }
  178. /**
  179. * Process a packet of data for either CCM decryption or encryption.
  180. *
  181. * @param in data for processing.
  182. * @param inOff offset at which data starts in the input array.
  183. * @param inLen length of the data in the input array.
  184. * @return a byte array containing the processed input..
  185. * @throws IllegalStateException if the cipher is not appropriately set up.
  186. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
  187. */
  188. public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen)
  189. {
  190. byte[] output;
  191. if (forEncryption)
  192. {
  193. output = new byte[inLen + macSize];
  194. }
  195. else
  196. {
  197. if (inLen < macSize)
  198. throw new InvalidCipherTextException("data too short");
  199. output = new byte[inLen - macSize];
  200. }
  201. ProcessPacket(input, inOff, inLen, output, 0);
  202. return output;
  203. }
  204. /**
  205. * Process a packet of data for either CCM decryption or encryption.
  206. *
  207. * @param in data for processing.
  208. * @param inOff offset at which data starts in the input array.
  209. * @param inLen length of the data in the input array.
  210. * @param output output array.
  211. * @param outOff offset into output array to start putting processed bytes.
  212. * @return the number of bytes added to output.
  213. * @throws IllegalStateException if the cipher is not appropriately set up.
  214. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
  215. * @throws DataLengthException if output buffer too short.
  216. */
  217. public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff)
  218. {
  219. // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
  220. // Need to keep the CTR and CBC Mac parts around and reset
  221. if (keyParam == null)
  222. throw new InvalidOperationException("CCM cipher unitialized.");
  223. int n = nonce.Length;
  224. int q = 15 - n;
  225. if (q < 4)
  226. {
  227. int limitLen = 1 << (8 * q);
  228. if (inLen >= limitLen)
  229. throw new InvalidOperationException("CCM packet too large for choice of q.");
  230. }
  231. byte[] iv = new byte[BlockSize];
  232. iv[0] = (byte)((q - 1) & 0x7);
  233. nonce.CopyTo(iv, 1);
  234. IBlockCipher ctrCipher = new FastSicBlockCipher(cipher);
  235. ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
  236. int outputLen;
  237. int inIndex = inOff;
  238. int outIndex = outOff;
  239. if (forEncryption)
  240. {
  241. outputLen = inLen + macSize;
  242. Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
  243. CalculateMac(input, inOff, inLen, macBlock);
  244. byte[] encMac = new byte[BlockSize];
  245. ctrCipher.ProcessBlock(macBlock, 0, encMac, 0); // S0
  246. while (inIndex < (inOff + inLen - BlockSize)) // S1...
  247. {
  248. ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
  249. outIndex += BlockSize;
  250. inIndex += BlockSize;
  251. }
  252. byte[] block = new byte[BlockSize];
  253. Array.Copy(input, inIndex, block, 0, inLen + inOff - inIndex);
  254. ctrCipher.ProcessBlock(block, 0, block, 0);
  255. Array.Copy(block, 0, output, outIndex, inLen + inOff - inIndex);
  256. Array.Copy(encMac, 0, output, outOff + inLen, macSize);
  257. }
  258. else
  259. {
  260. if (inLen < macSize)
  261. throw new InvalidCipherTextException("data too short");
  262. outputLen = inLen - macSize;
  263. Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
  264. Array.Copy(input, inOff + outputLen, macBlock, 0, macSize);
  265. ctrCipher.ProcessBlock(macBlock, 0, macBlock, 0);
  266. for (int i = macSize; i != macBlock.Length; i++)
  267. {
  268. macBlock[i] = 0;
  269. }
  270. while (inIndex < (inOff + outputLen - BlockSize))
  271. {
  272. ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
  273. outIndex += BlockSize;
  274. inIndex += BlockSize;
  275. }
  276. byte[] block = new byte[BlockSize];
  277. Array.Copy(input, inIndex, block, 0, outputLen - (inIndex - inOff));
  278. ctrCipher.ProcessBlock(block, 0, block, 0);
  279. Array.Copy(block, 0, output, outIndex, outputLen - (inIndex - inOff));
  280. byte[] calculatedMacBlock = new byte[BlockSize];
  281. CalculateMac(output, outOff, outputLen, calculatedMacBlock);
  282. if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
  283. throw new InvalidCipherTextException("mac check in CCM failed");
  284. }
  285. return outputLen;
  286. }
  287. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  288. public virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
  289. {
  290. int inLen = input.Length;
  291. // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
  292. // Need to keep the CTR and CBC Mac parts around and reset
  293. if (keyParam == null)
  294. throw new InvalidOperationException("CCM cipher unitialized.");
  295. int n = nonce.Length;
  296. int q = 15 - n;
  297. if (q < 4)
  298. {
  299. int limitLen = 1 << (8 * q);
  300. if (inLen >= limitLen)
  301. throw new InvalidOperationException("CCM packet too large for choice of q.");
  302. }
  303. byte[] iv = new byte[BlockSize];
  304. iv[0] = (byte)((q - 1) & 0x7);
  305. nonce.CopyTo(iv, 1);
  306. IBlockCipher ctrCipher = new SicBlockCipher(cipher);
  307. ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
  308. int outputLen;
  309. int index = 0;
  310. Span<byte> block = stackalloc byte[BlockSize];
  311. if (forEncryption)
  312. {
  313. outputLen = inLen + macSize;
  314. Check.OutputLength(output, outputLen, "output buffer too short");
  315. CalculateMac(input, macBlock);
  316. byte[] encMac = new byte[BlockSize];
  317. ctrCipher.ProcessBlock(macBlock, encMac); // S0
  318. while (index < (inLen - BlockSize)) // S1...
  319. {
  320. ctrCipher.ProcessBlock(input[index..], output[index..]);
  321. index += BlockSize;
  322. }
  323. input[index..].CopyTo(block);
  324. ctrCipher.ProcessBlock(block, block);
  325. block[..(inLen - index)].CopyTo(output[index..]);
  326. encMac.AsSpan(0, macSize).CopyTo(output[inLen..]);
  327. }
  328. else
  329. {
  330. if (inLen < macSize)
  331. throw new InvalidCipherTextException("data too short");
  332. outputLen = inLen - macSize;
  333. Check.OutputLength(output, outputLen, "output buffer too short");
  334. input[outputLen..].CopyTo(macBlock);
  335. ctrCipher.ProcessBlock(macBlock, macBlock);
  336. for (int i = macSize; i != macBlock.Length; i++)
  337. {
  338. macBlock[i] = 0;
  339. }
  340. while (index < (outputLen - BlockSize))
  341. {
  342. ctrCipher.ProcessBlock(input[index..], output[index..]);
  343. index += BlockSize;
  344. }
  345. input[index..outputLen].CopyTo(block);
  346. ctrCipher.ProcessBlock(block, block);
  347. block[..(outputLen - index)].CopyTo(output[index..]);
  348. Span<byte> calculatedMacBlock = stackalloc byte[BlockSize];
  349. CalculateMac(output[..outputLen], calculatedMacBlock);
  350. if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
  351. throw new InvalidCipherTextException("mac check in CCM failed");
  352. }
  353. return outputLen;
  354. }
  355. #endif
  356. private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
  357. {
  358. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  359. return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock);
  360. #else
  361. IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
  362. cMac.Init(keyParam);
  363. //
  364. // build b0
  365. //
  366. byte[] b0 = new byte[16];
  367. if (HasAssociatedText())
  368. {
  369. b0[0] |= 0x40;
  370. }
  371. b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
  372. b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
  373. Array.Copy(nonce, 0, b0, 1, nonce.Length);
  374. int q = dataLen;
  375. int count = 1;
  376. while (q > 0)
  377. {
  378. b0[b0.Length - count] = (byte)(q & 0xff);
  379. q >>= 8;
  380. count++;
  381. }
  382. cMac.BlockUpdate(b0, 0, b0.Length);
  383. //
  384. // process associated text
  385. //
  386. if (HasAssociatedText())
  387. {
  388. int extra;
  389. int textLength = GetAssociatedTextLength();
  390. if (textLength < ((1 << 16) - (1 << 8)))
  391. {
  392. cMac.Update((byte)(textLength >> 8));
  393. cMac.Update((byte)textLength);
  394. extra = 2;
  395. }
  396. else // can't go any higher than 2^32
  397. {
  398. cMac.Update((byte)0xff);
  399. cMac.Update((byte)0xfe);
  400. cMac.Update((byte)(textLength >> 24));
  401. cMac.Update((byte)(textLength >> 16));
  402. cMac.Update((byte)(textLength >> 8));
  403. cMac.Update((byte)textLength);
  404. extra = 6;
  405. }
  406. if (initialAssociatedText != null)
  407. {
  408. cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
  409. }
  410. if (associatedText.Length > 0)
  411. {
  412. byte[] input = associatedText.GetBuffer();
  413. int len = Convert.ToInt32(associatedText.Length);
  414. cMac.BlockUpdate(input, 0, len);
  415. }
  416. extra = (extra + textLength) % 16;
  417. if (extra != 0)
  418. {
  419. for (int i = extra; i < 16; ++i)
  420. {
  421. cMac.Update((byte)0x00);
  422. }
  423. }
  424. }
  425. //
  426. // add the text
  427. //
  428. cMac.BlockUpdate(data, dataOff, dataLen);
  429. return cMac.DoFinal(macBlock, 0);
  430. #endif
  431. }
  432. #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || UNITY_2021_2_OR_NEWER
  433. private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock)
  434. {
  435. IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
  436. cMac.Init(keyParam);
  437. //
  438. // build b0
  439. //
  440. byte[] b0 = new byte[16];
  441. if (HasAssociatedText())
  442. {
  443. b0[0] |= 0x40;
  444. }
  445. b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
  446. b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
  447. Array.Copy(nonce, 0, b0, 1, nonce.Length);
  448. int q = data.Length;
  449. int count = 1;
  450. while (q > 0)
  451. {
  452. b0[b0.Length - count] = (byte)(q & 0xff);
  453. q >>= 8;
  454. count++;
  455. }
  456. cMac.BlockUpdate(b0, 0, b0.Length);
  457. //
  458. // process associated text
  459. //
  460. if (HasAssociatedText())
  461. {
  462. int extra;
  463. int textLength = GetAssociatedTextLength();
  464. if (textLength < ((1 << 16) - (1 << 8)))
  465. {
  466. cMac.Update((byte)(textLength >> 8));
  467. cMac.Update((byte)textLength);
  468. extra = 2;
  469. }
  470. else // can't go any higher than 2^32
  471. {
  472. cMac.Update((byte)0xff);
  473. cMac.Update((byte)0xfe);
  474. cMac.Update((byte)(textLength >> 24));
  475. cMac.Update((byte)(textLength >> 16));
  476. cMac.Update((byte)(textLength >> 8));
  477. cMac.Update((byte)textLength);
  478. extra = 6;
  479. }
  480. if (initialAssociatedText != null)
  481. {
  482. cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
  483. }
  484. if (associatedText.Length > 0)
  485. {
  486. byte[] input = associatedText.GetBuffer();
  487. int len = Convert.ToInt32(associatedText.Length);
  488. cMac.BlockUpdate(input, 0, len);
  489. }
  490. extra = (extra + textLength) % 16;
  491. if (extra != 0)
  492. {
  493. for (int i = extra; i < 16; ++i)
  494. {
  495. cMac.Update((byte)0x00);
  496. }
  497. }
  498. }
  499. //
  500. // add the text
  501. //
  502. cMac.BlockUpdate(data);
  503. return cMac.DoFinal(macBlock);
  504. }
  505. #endif
  506. private int GetMacSize(bool forEncryption, int requestedMacBits)
  507. {
  508. if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15)))
  509. throw new ArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}");
  510. return requestedMacBits >> 3;
  511. }
  512. private int GetAssociatedTextLength()
  513. {
  514. return Convert.ToInt32(associatedText.Length) +
  515. (initialAssociatedText == null ? 0 : initialAssociatedText.Length);
  516. }
  517. private bool HasAssociatedText()
  518. {
  519. return GetAssociatedTextLength() > 0;
  520. }
  521. }
  522. }
  523. #pragma warning restore
  524. #endif