CcmBlockCipher.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using System;
  3. using System.IO;
  4. using Org.BouncyCastle.Crypto;
  5. using Org.BouncyCastle.Crypto.Macs;
  6. using Org.BouncyCastle.Crypto.Parameters;
  7. using Org.BouncyCastle.Utilities;
  8. namespace 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 GetUnderlyingCipher()
  49. {
  50. return cipher;
  51. }
  52. public virtual void Init(
  53. bool forEncryption,
  54. ICipherParameters parameters)
  55. {
  56. this.forEncryption = forEncryption;
  57. ICipherParameters cipherParameters;
  58. if (parameters is AeadParameters)
  59. {
  60. AeadParameters param = (AeadParameters) parameters;
  61. nonce = param.GetNonce();
  62. initialAssociatedText = param.GetAssociatedText();
  63. macSize = param.MacSize / 8;
  64. cipherParameters = param.Key;
  65. }
  66. else if (parameters is ParametersWithIV)
  67. {
  68. ParametersWithIV param = (ParametersWithIV) parameters;
  69. nonce = param.GetIV();
  70. initialAssociatedText = null;
  71. macSize = macBlock.Length / 2;
  72. cipherParameters = param.Parameters;
  73. }
  74. else
  75. {
  76. throw new ArgumentException("invalid parameters passed to CCM");
  77. }
  78. // NOTE: Very basic support for key re-use, but no performance gain from it
  79. if (cipherParameters != null)
  80. {
  81. keyParam = cipherParameters;
  82. }
  83. if (nonce == null || nonce.Length < 7 || nonce.Length > 13)
  84. {
  85. throw new ArgumentException("nonce must have length from 7 to 13 octets");
  86. }
  87. Reset();
  88. }
  89. public virtual string AlgorithmName
  90. {
  91. get { return cipher.AlgorithmName + "/CCM"; }
  92. }
  93. public virtual int GetBlockSize()
  94. {
  95. return cipher.GetBlockSize();
  96. }
  97. public virtual void ProcessAadByte(byte input)
  98. {
  99. associatedText.WriteByte(input);
  100. }
  101. public virtual void ProcessAadBytes(byte[] inBytes, int inOff, int len)
  102. {
  103. // TODO: Process AAD online
  104. associatedText.Write(inBytes, inOff, len);
  105. }
  106. public virtual int ProcessByte(
  107. byte input,
  108. byte[] outBytes,
  109. int outOff)
  110. {
  111. data.WriteByte(input);
  112. return 0;
  113. }
  114. public virtual int ProcessBytes(
  115. byte[] inBytes,
  116. int inOff,
  117. int inLen,
  118. byte[] outBytes,
  119. int outOff)
  120. {
  121. Check.DataLength(inBytes, inOff, inLen, "Input buffer too short");
  122. data.Write(inBytes, inOff, inLen);
  123. return 0;
  124. }
  125. public virtual int DoFinal(
  126. byte[] outBytes,
  127. int outOff)
  128. {
  129. #if PORTABLE || NETFX_CORE
  130. byte[] input = data.ToArray();
  131. int inLen = input.Length;
  132. #else
  133. byte[] input = data.GetBuffer();
  134. int inLen = (int)data.Position;
  135. #endif
  136. int len = ProcessPacket(input, 0, inLen, outBytes, outOff);
  137. Reset();
  138. return len;
  139. }
  140. public virtual void Reset()
  141. {
  142. cipher.Reset();
  143. associatedText.SetLength(0);
  144. data.SetLength(0);
  145. }
  146. /**
  147. * Returns a byte array containing the mac calculated as part of the
  148. * last encrypt or decrypt operation.
  149. *
  150. * @return the last mac calculated.
  151. */
  152. public virtual byte[] GetMac()
  153. {
  154. return Arrays.CopyOfRange(macBlock, 0, macSize);
  155. }
  156. public virtual int GetUpdateOutputSize(
  157. int len)
  158. {
  159. return 0;
  160. }
  161. public virtual int GetOutputSize(
  162. int len)
  163. {
  164. int totalData = (int)data.Length + len;
  165. if (forEncryption)
  166. {
  167. return totalData + macSize;
  168. }
  169. return totalData < macSize ? 0 : totalData - macSize;
  170. }
  171. /**
  172. * Process a packet of data for either CCM decryption or encryption.
  173. *
  174. * @param in data for processing.
  175. * @param inOff offset at which data starts in the input array.
  176. * @param inLen length of the data in the input array.
  177. * @return a byte array containing the processed input..
  178. * @throws IllegalStateException if the cipher is not appropriately set up.
  179. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
  180. */
  181. public virtual byte[] ProcessPacket(byte[] input, int inOff, int inLen)
  182. {
  183. byte[] output;
  184. if (forEncryption)
  185. {
  186. output = new byte[inLen + macSize];
  187. }
  188. else
  189. {
  190. if (inLen < macSize)
  191. throw new InvalidCipherTextException("data too short");
  192. output = new byte[inLen - macSize];
  193. }
  194. ProcessPacket(input, inOff, inLen, output, 0);
  195. return output;
  196. }
  197. /**
  198. * Process a packet of data for either CCM decryption or encryption.
  199. *
  200. * @param in data for processing.
  201. * @param inOff offset at which data starts in the input array.
  202. * @param inLen length of the data in the input array.
  203. * @param output output array.
  204. * @param outOff offset into output array to start putting processed bytes.
  205. * @return the number of bytes added to output.
  206. * @throws IllegalStateException if the cipher is not appropriately set up.
  207. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails.
  208. * @throws DataLengthException if output buffer too short.
  209. */
  210. public virtual int ProcessPacket(byte[] input, int inOff, int inLen, byte[] output, int outOff)
  211. {
  212. // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
  213. // Need to keep the CTR and CBC Mac parts around and reset
  214. if (keyParam == null)
  215. throw new InvalidOperationException("CCM cipher unitialized.");
  216. int n = nonce.Length;
  217. int q = 15 - n;
  218. if (q < 4)
  219. {
  220. int limitLen = 1 << (8 * q);
  221. if (inLen >= limitLen)
  222. throw new InvalidOperationException("CCM packet too large for choice of q.");
  223. }
  224. byte[] iv = new byte[BlockSize];
  225. iv[0] = (byte)((q - 1) & 0x7);
  226. nonce.CopyTo(iv, 1);
  227. IBlockCipher ctrCipher = new SicBlockCipher(cipher);
  228. ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
  229. int outputLen;
  230. int inIndex = inOff;
  231. int outIndex = outOff;
  232. if (forEncryption)
  233. {
  234. outputLen = inLen + macSize;
  235. Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
  236. CalculateMac(input, inOff, inLen, macBlock);
  237. byte[] encMac = new byte[BlockSize];
  238. ctrCipher.ProcessBlock(macBlock, 0, encMac, 0); // S0
  239. while (inIndex < (inOff + inLen - BlockSize)) // S1...
  240. {
  241. ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
  242. outIndex += BlockSize;
  243. inIndex += BlockSize;
  244. }
  245. byte[] block = new byte[BlockSize];
  246. Array.Copy(input, inIndex, block, 0, inLen + inOff - inIndex);
  247. ctrCipher.ProcessBlock(block, 0, block, 0);
  248. Array.Copy(block, 0, output, outIndex, inLen + inOff - inIndex);
  249. Array.Copy(encMac, 0, output, outOff + inLen, macSize);
  250. }
  251. else
  252. {
  253. if (inLen < macSize)
  254. throw new InvalidCipherTextException("data too short");
  255. outputLen = inLen - macSize;
  256. Check.OutputLength(output, outOff, outputLen, "Output buffer too short.");
  257. Array.Copy(input, inOff + outputLen, macBlock, 0, macSize);
  258. ctrCipher.ProcessBlock(macBlock, 0, macBlock, 0);
  259. for (int i = macSize; i != macBlock.Length; i++)
  260. {
  261. macBlock[i] = 0;
  262. }
  263. while (inIndex < (inOff + outputLen - BlockSize))
  264. {
  265. ctrCipher.ProcessBlock(input, inIndex, output, outIndex);
  266. outIndex += BlockSize;
  267. inIndex += BlockSize;
  268. }
  269. byte[] block = new byte[BlockSize];
  270. Array.Copy(input, inIndex, block, 0, outputLen - (inIndex - inOff));
  271. ctrCipher.ProcessBlock(block, 0, block, 0);
  272. Array.Copy(block, 0, output, outIndex, outputLen - (inIndex - inOff));
  273. byte[] calculatedMacBlock = new byte[BlockSize];
  274. CalculateMac(output, outOff, outputLen, calculatedMacBlock);
  275. if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
  276. throw new InvalidCipherTextException("mac check in CCM failed");
  277. }
  278. return outputLen;
  279. }
  280. private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
  281. {
  282. IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
  283. cMac.Init(keyParam);
  284. //
  285. // build b0
  286. //
  287. byte[] b0 = new byte[16];
  288. if (HasAssociatedText())
  289. {
  290. b0[0] |= 0x40;
  291. }
  292. b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
  293. b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
  294. Array.Copy(nonce, 0, b0, 1, nonce.Length);
  295. int q = dataLen;
  296. int count = 1;
  297. while (q > 0)
  298. {
  299. b0[b0.Length - count] = (byte)(q & 0xff);
  300. q >>= 8;
  301. count++;
  302. }
  303. cMac.BlockUpdate(b0, 0, b0.Length);
  304. //
  305. // process associated text
  306. //
  307. if (HasAssociatedText())
  308. {
  309. int extra;
  310. int textLength = GetAssociatedTextLength();
  311. if (textLength < ((1 << 16) - (1 << 8)))
  312. {
  313. cMac.Update((byte)(textLength >> 8));
  314. cMac.Update((byte)textLength);
  315. extra = 2;
  316. }
  317. else // can't go any higher than 2^32
  318. {
  319. cMac.Update((byte)0xff);
  320. cMac.Update((byte)0xfe);
  321. cMac.Update((byte)(textLength >> 24));
  322. cMac.Update((byte)(textLength >> 16));
  323. cMac.Update((byte)(textLength >> 8));
  324. cMac.Update((byte)textLength);
  325. extra = 6;
  326. }
  327. if (initialAssociatedText != null)
  328. {
  329. cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
  330. }
  331. if (associatedText.Position > 0)
  332. {
  333. #if PORTABLE || NETFX_CORE
  334. byte[] input = associatedText.ToArray();
  335. int len = input.Length;
  336. #else
  337. byte[] input = associatedText.GetBuffer();
  338. int len = (int)associatedText.Position;
  339. #endif
  340. cMac.BlockUpdate(input, 0, len);
  341. }
  342. extra = (extra + textLength) % 16;
  343. if (extra != 0)
  344. {
  345. for (int i = extra; i < 16; ++i)
  346. {
  347. cMac.Update((byte)0x00);
  348. }
  349. }
  350. }
  351. //
  352. // add the text
  353. //
  354. cMac.BlockUpdate(data, dataOff, dataLen);
  355. return cMac.DoFinal(macBlock, 0);
  356. }
  357. private int GetAssociatedTextLength()
  358. {
  359. return (int)associatedText.Length + ((initialAssociatedText == null) ? 0 : initialAssociatedText.Length);
  360. }
  361. private bool HasAssociatedText()
  362. {
  363. return GetAssociatedTextLength() > 0;
  364. }
  365. }
  366. }
  367. #endif