CcmBlockCipher.cs 21 KB

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