DesEdeWrapEngine.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using System;
  3. using Org.BouncyCastle.Crypto.Digests;
  4. using Org.BouncyCastle.Crypto.Modes;
  5. using Org.BouncyCastle.Crypto.Parameters;
  6. using Org.BouncyCastle.Security;
  7. using Org.BouncyCastle.Utilities;
  8. namespace Org.BouncyCastle.Crypto.Engines
  9. {
  10. /**
  11. * Wrap keys according to
  12. * <a href="http://www.ietf.org/internet-drafts/draft-ietf-smime-key-wrap-01.txt">
  13. * draft-ietf-smime-key-wrap-01.txt</a>.
  14. * <p>
  15. * Note:
  16. * <ul>
  17. * <li>this is based on a draft, and as such is subject to change - don't use this class for anything requiring long term storage.</li>
  18. * <li>if you are using this to wrap triple-des keys you need to set the
  19. * parity bits on the key and, if it's a two-key triple-des key, pad it
  20. * yourself.</li>
  21. * </ul>
  22. * </p>
  23. */
  24. public class DesEdeWrapEngine
  25. : IWrapper
  26. {
  27. /** Field engine */
  28. private CbcBlockCipher engine;
  29. /** Field param */
  30. private KeyParameter param;
  31. /** Field paramPlusIV */
  32. private ParametersWithIV paramPlusIV;
  33. /** Field iv */
  34. private byte[] iv;
  35. /** Field forWrapping */
  36. private bool forWrapping;
  37. /** Field IV2 */
  38. private static readonly byte[] IV2 = { (byte) 0x4a, (byte) 0xdd, (byte) 0xa2,
  39. (byte) 0x2c, (byte) 0x79, (byte) 0xe8,
  40. (byte) 0x21, (byte) 0x05 };
  41. //
  42. // checksum digest
  43. //
  44. private readonly IDigest sha1 = new Sha1Digest();
  45. private readonly byte[] digest = new byte[20];
  46. /**
  47. * Method init
  48. *
  49. * @param forWrapping
  50. * @param param
  51. */
  52. public virtual void Init(
  53. bool forWrapping,
  54. ICipherParameters parameters)
  55. {
  56. this.forWrapping = forWrapping;
  57. this.engine = new CbcBlockCipher(new DesEdeEngine());
  58. SecureRandom sr;
  59. if (parameters is ParametersWithRandom)
  60. {
  61. ParametersWithRandom pr = (ParametersWithRandom) parameters;
  62. parameters = pr.Parameters;
  63. sr = pr.Random;
  64. }
  65. else
  66. {
  67. sr = new SecureRandom();
  68. }
  69. if (parameters is KeyParameter)
  70. {
  71. this.param = (KeyParameter) parameters;
  72. if (this.forWrapping)
  73. {
  74. // Hm, we have no IV but we want to wrap ?!?
  75. // well, then we have to create our own IV.
  76. this.iv = new byte[8];
  77. sr.NextBytes(iv);
  78. this.paramPlusIV = new ParametersWithIV(this.param, this.iv);
  79. }
  80. }
  81. else if (parameters is ParametersWithIV)
  82. {
  83. if (!forWrapping)
  84. throw new ArgumentException("You should not supply an IV for unwrapping");
  85. this.paramPlusIV = (ParametersWithIV) parameters;
  86. this.iv = this.paramPlusIV.GetIV();
  87. this.param = (KeyParameter) this.paramPlusIV.Parameters;
  88. if (this.iv.Length != 8)
  89. throw new ArgumentException("IV is not 8 octets", "parameters");
  90. }
  91. }
  92. /**
  93. * Method GetAlgorithmName
  94. *
  95. * @return
  96. */
  97. public virtual string AlgorithmName
  98. {
  99. get { return "DESede"; }
  100. }
  101. /**
  102. * Method wrap
  103. *
  104. * @param in
  105. * @param inOff
  106. * @param inLen
  107. * @return
  108. */
  109. public virtual byte[] Wrap(
  110. byte[] input,
  111. int inOff,
  112. int length)
  113. {
  114. if (!forWrapping)
  115. {
  116. throw new InvalidOperationException("Not initialized for wrapping");
  117. }
  118. byte[] keyToBeWrapped = new byte[length];
  119. Array.Copy(input, inOff, keyToBeWrapped, 0, length);
  120. // Compute the CMS Key Checksum, (section 5.6.1), call this CKS.
  121. byte[] CKS = CalculateCmsKeyChecksum(keyToBeWrapped);
  122. // Let WKCKS = WK || CKS where || is concatenation.
  123. byte[] WKCKS = new byte[keyToBeWrapped.Length + CKS.Length];
  124. Array.Copy(keyToBeWrapped, 0, WKCKS, 0, keyToBeWrapped.Length);
  125. Array.Copy(CKS, 0, WKCKS, keyToBeWrapped.Length, CKS.Length);
  126. // Encrypt WKCKS in CBC mode using KEK as the key and IV as the
  127. // initialization vector. Call the results TEMP1.
  128. int blockSize = engine.GetBlockSize();
  129. if (WKCKS.Length % blockSize != 0)
  130. throw new InvalidOperationException("Not multiple of block length");
  131. engine.Init(true, paramPlusIV);
  132. byte [] TEMP1 = new byte[WKCKS.Length];
  133. for (int currentBytePos = 0; currentBytePos != WKCKS.Length; currentBytePos += blockSize)
  134. {
  135. engine.ProcessBlock(WKCKS, currentBytePos, TEMP1, currentBytePos);
  136. }
  137. // Let TEMP2 = IV || TEMP1.
  138. byte[] TEMP2 = new byte[this.iv.Length + TEMP1.Length];
  139. Array.Copy(this.iv, 0, TEMP2, 0, this.iv.Length);
  140. Array.Copy(TEMP1, 0, TEMP2, this.iv.Length, TEMP1.Length);
  141. // Reverse the order of the octets in TEMP2 and call the result TEMP3.
  142. byte[] TEMP3 = reverse(TEMP2);
  143. // Encrypt TEMP3 in CBC mode using the KEK and an initialization vector
  144. // of 0x 4a dd a2 2c 79 e8 21 05. The resulting cipher text is the desired
  145. // result. It is 40 octets long if a 168 bit key is being wrapped.
  146. ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);
  147. this.engine.Init(true, param2);
  148. for (int currentBytePos = 0; currentBytePos != TEMP3.Length; currentBytePos += blockSize)
  149. {
  150. engine.ProcessBlock(TEMP3, currentBytePos, TEMP3, currentBytePos);
  151. }
  152. return TEMP3;
  153. }
  154. /**
  155. * Method unwrap
  156. *
  157. * @param in
  158. * @param inOff
  159. * @param inLen
  160. * @return
  161. * @throws InvalidCipherTextException
  162. */
  163. public virtual byte[] Unwrap(
  164. byte[] input,
  165. int inOff,
  166. int length)
  167. {
  168. if (forWrapping)
  169. {
  170. throw new InvalidOperationException("Not set for unwrapping");
  171. }
  172. if (input == null)
  173. {
  174. throw new InvalidCipherTextException("Null pointer as ciphertext");
  175. }
  176. int blockSize = engine.GetBlockSize();
  177. if (length % blockSize != 0)
  178. {
  179. throw new InvalidCipherTextException("Ciphertext not multiple of " + blockSize);
  180. }
  181. /*
  182. // Check if the length of the cipher text is reasonable given the key
  183. // type. It must be 40 bytes for a 168 bit key and either 32, 40, or
  184. // 48 bytes for a 128, 192, or 256 bit key. If the length is not supported
  185. // or inconsistent with the algorithm for which the key is intended,
  186. // return error.
  187. //
  188. // we do not accept 168 bit keys. it has to be 192 bit.
  189. int lengthA = (estimatedKeyLengthInBit / 8) + 16;
  190. int lengthB = estimatedKeyLengthInBit % 8;
  191. if ((lengthA != keyToBeUnwrapped.Length) || (lengthB != 0)) {
  192. throw new XMLSecurityException("empty");
  193. }
  194. */
  195. // Decrypt the cipher text with TRIPLedeS in CBC mode using the KEK
  196. // and an initialization vector (IV) of 0x4adda22c79e82105. Call the output TEMP3.
  197. ParametersWithIV param2 = new ParametersWithIV(this.param, IV2);
  198. this.engine.Init(false, param2);
  199. byte [] TEMP3 = new byte[length];
  200. for (int currentBytePos = 0; currentBytePos != TEMP3.Length; currentBytePos += blockSize)
  201. {
  202. engine.ProcessBlock(input, inOff + currentBytePos, TEMP3, currentBytePos);
  203. }
  204. // Reverse the order of the octets in TEMP3 and call the result TEMP2.
  205. byte[] TEMP2 = reverse(TEMP3);
  206. // Decompose TEMP2 into IV, the first 8 octets, and TEMP1, the remaining octets.
  207. this.iv = new byte[8];
  208. byte[] TEMP1 = new byte[TEMP2.Length - 8];
  209. Array.Copy(TEMP2, 0, this.iv, 0, 8);
  210. Array.Copy(TEMP2, 8, TEMP1, 0, TEMP2.Length - 8);
  211. // Decrypt TEMP1 using TRIPLedeS in CBC mode using the KEK and the IV
  212. // found in the previous step. Call the result WKCKS.
  213. this.paramPlusIV = new ParametersWithIV(this.param, this.iv);
  214. this.engine.Init(false, this.paramPlusIV);
  215. byte[] WKCKS = new byte[TEMP1.Length];
  216. for (int currentBytePos = 0; currentBytePos != WKCKS.Length; currentBytePos += blockSize)
  217. {
  218. engine.ProcessBlock(TEMP1, currentBytePos, WKCKS, currentBytePos);
  219. }
  220. // Decompose WKCKS. CKS is the last 8 octets and WK, the wrapped key, are
  221. // those octets before the CKS.
  222. byte[] result = new byte[WKCKS.Length - 8];
  223. byte[] CKStoBeVerified = new byte[8];
  224. Array.Copy(WKCKS, 0, result, 0, WKCKS.Length - 8);
  225. Array.Copy(WKCKS, WKCKS.Length - 8, CKStoBeVerified, 0, 8);
  226. // Calculate a CMS Key Checksum, (section 5.6.1), over the WK and compare
  227. // with the CKS extracted in the above step. If they are not equal, return error.
  228. if (!CheckCmsKeyChecksum(result, CKStoBeVerified)) {
  229. throw new InvalidCipherTextException(
  230. "Checksum inside ciphertext is corrupted");
  231. }
  232. // WK is the wrapped key, now extracted for use in data decryption.
  233. return result;
  234. }
  235. /**
  236. * Some key wrap algorithms make use of the Key Checksum defined
  237. * in CMS [CMS-Algorithms]. This is used to provide an integrity
  238. * check value for the key being wrapped. The algorithm is
  239. *
  240. * - Compute the 20 octet SHA-1 hash on the key being wrapped.
  241. * - Use the first 8 octets of this hash as the checksum value.
  242. *
  243. * @param key
  244. * @return
  245. * @throws Exception
  246. * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
  247. */
  248. private byte[] CalculateCmsKeyChecksum(
  249. byte[] key)
  250. {
  251. sha1.BlockUpdate(key, 0, key.Length);
  252. sha1.DoFinal(digest, 0);
  253. byte[] result = new byte[8];
  254. Array.Copy(digest, 0, result, 0, 8);
  255. return result;
  256. }
  257. /**
  258. * @param key
  259. * @param checksum
  260. * @return
  261. * @see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
  262. */
  263. private bool CheckCmsKeyChecksum(
  264. byte[] key,
  265. byte[] checksum)
  266. {
  267. return Arrays.ConstantTimeAreEqual(CalculateCmsKeyChecksum(key), checksum);
  268. }
  269. private static byte[] reverse(byte[] bs)
  270. {
  271. byte[] result = new byte[bs.Length];
  272. for (int i = 0; i < bs.Length; i++)
  273. {
  274. result[i] = bs[bs.Length - (i + 1)];
  275. }
  276. return result;
  277. }
  278. }
  279. }
  280. #endif