CcmBlockCipher.cs 14 KB

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