asymmetric.cipher.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};
  9. /**
  10. * @namespace asymmetric/cipher
  11. */
  12. cryptolib.modules.asymmetric.cipher = function(box) {
  13. 'use strict';
  14. box.asymmetric = box.asymmetric || {};
  15. box.asymmetric.cipher = {};
  16. /**
  17. * A module that holds asymmetric cipher functionalities.
  18. *
  19. * @exports asymmetric/cipher/factory
  20. */
  21. box.asymmetric.cipher.factory = {};
  22. var policies = {
  23. cipher: {
  24. algorithm: box.policies.asymmetric.cipher.algorithm,
  25. secretKeyLengthBytes: box.policies.asymmetric.cipher.secretKeyLengthBytes,
  26. ivLengthBytes: box.policies.asymmetric.cipher.ivLengthBytes,
  27. tagLengthBytes: box.policies.asymmetric.cipher.tagLengthBytes,
  28. deriver: box.policies.asymmetric.cipher.deriver,
  29. hash: box.policies.asymmetric.cipher.hash,
  30. symmetricCipher: box.policies.asymmetric.cipher.symmetricCipher,
  31. provider: box.policies.asymmetric.cipher.provider
  32. }
  33. };
  34. var utils, converters, exceptions, randomFactory;
  35. var f = function(box) {
  36. utils = box.commons.utils;
  37. converters = new box.commons.utils.Converters();
  38. exceptions = box.commons.exceptions;
  39. randomFactory =
  40. new box.primitives.securerandom.factory.SecureRandomFactory();
  41. };
  42. f.policies = {
  43. primitives: {
  44. secureRandom:
  45. {provider: box.policies.asymmetric.cipher.secureRandom.provider}
  46. }
  47. };
  48. cryptolib('commons', 'primitives.securerandom', f);
  49. /** @class */
  50. box.asymmetric.cipher.factory.AsymmetricCipherFactory = function() {};
  51. box.asymmetric.cipher.factory.AsymmetricCipherFactory.prototype = {
  52. /**
  53. * @function Gets an asymmetric cipher.
  54. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
  55. */
  56. getCryptoAsymmetricCipher: function() {
  57. try {
  58. if (policies.cipher.provider ===
  59. Config.asymmetric.cipher.provider.FORGE) {
  60. var secureRandomBytes = randomFactory.getCryptoRandomBytes();
  61. return this.getCryptoForgeAsymmetricCipher(secureRandomBytes);
  62. } else {
  63. throw new exceptions.CryptoLibException(
  64. 'No suitable provider for the asymmetric cipher was provided.');
  65. }
  66. } catch (error) {
  67. throw new exceptions.CryptoLibException(
  68. 'A CryptoAsymmetricCipher could not be obtained.', error);
  69. }
  70. },
  71. /**
  72. * @function Gets a Forge asymmetric cipher.
  73. * @param secureRandom
  74. * {CryptoScytlRandomBytes} a source of random bytes.
  75. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher}
  76. */
  77. getCryptoForgeAsymmetricCipher: function(secureRandom) {
  78. return new CryptoForgeAsymmetricCipher(secureRandom);
  79. }
  80. };
  81. /**
  82. * Defines a Forge asymmetric cipher
  83. *
  84. * @class
  85. * @param secureRandomBytes
  86. * {CryptoScytlRandomBytes} a source of random bytes.
  87. * @memberof asymmetric/cipher
  88. */
  89. function CryptoForgeAsymmetricCipher(secureRandomBytes) {
  90. if (!secureRandomBytes) {
  91. throw new exceptions.CryptoLibException(
  92. 'The received PRNG was not valid');
  93. }
  94. this.bitOperators = new utils.BitOperators();
  95. this.secureRandomBytes = secureRandomBytes;
  96. // We would like to use the SCYTL PRNG directly in the ciphers that are
  97. // created. However it is currently not possible to pass a PRNG to the
  98. // Forge library when creating or using a cipher. However, in some cases,
  99. // it is possible to pass a seed (created using the SCYTL PRNG) to FORGE
  100. // that is then used to seed the FORGE internal PRNG.
  101. this.encodingSeedLength = 32;
  102. try {
  103. if ((policies.cipher.algorithm.name !==
  104. Config.asymmetric.cipher.algorithm.RSA_OAEP.name) &&
  105. (policies.cipher.algorithm.name !==
  106. Config.asymmetric.cipher.algorithm.RSA_KEM.name)) {
  107. throw new exceptions.CryptoLibException(
  108. 'The specified algorithm is not supported.');
  109. }
  110. } catch (error) {
  111. throw new exceptions.CryptoLibException(
  112. 'CryptoForgeAsymmetricCipher could not be created.', error);
  113. }
  114. /**
  115. * Parses the four parts of the data that is produced by the encrypt
  116. * function, and that is received by the decrypt function.
  117. * <p>
  118. * We know the length of three of these parts, and we know the total
  119. * length of the data, therefore we can parse out all four of the parts.
  120. */
  121. this._parseParts = function(privateKey, encryptedData) {
  122. var ivLengthBytes = policies.cipher.ivLengthBytes;
  123. var tagLengthBytes = policies.cipher.tagLengthBytes;
  124. var encapsulationLengthBytes = privateKey.n.bitLength() / 8;
  125. var totalLength = encryptedData.length;
  126. var totalKnownLength =
  127. encapsulationLengthBytes + ivLengthBytes + tagLengthBytes;
  128. var encryptedDataLength = totalLength - totalKnownLength;
  129. var startIndexOfSecondPart = encapsulationLengthBytes;
  130. var startIndexOfThirdPart = startIndexOfSecondPart + ivLengthBytes;
  131. var startIndexOfFourthPart = startIndexOfThirdPart + encryptedDataLength;
  132. var encapsulation =
  133. this.bitOperators.extract(encryptedData, 0, startIndexOfSecondPart);
  134. var iv = this.bitOperators.extract(
  135. encryptedData, startIndexOfSecondPart, startIndexOfThirdPart);
  136. var data = this.bitOperators.extract(
  137. encryptedData, startIndexOfThirdPart, startIndexOfFourthPart);
  138. var tag =
  139. this.bitOperators.extract(encryptedData, startIndexOfFourthPart);
  140. return {encapsulation: encapsulation, iv: iv, data: data, tag: tag};
  141. };
  142. this._validateInputs = function(keyPem, data) {
  143. if (!keyPem) {
  144. throw new exceptions.CryptoLibException(
  145. 'The received key was not initialized.');
  146. }
  147. if (!data) {
  148. throw new exceptions.CryptoLibException(
  149. 'The received data was not initialized.');
  150. }
  151. };
  152. // Note: at the moment, the options that are being used with the RSA_OAEP
  153. // algorithm are hardcoded (for encrypting and decrypting). This could be
  154. // improved so that these are read from the properties file. Doing this will
  155. // mean any that existing properties files (used by consumers of the
  156. // library) will become invalid (if the consumer uses RSA_OAEP) as they wont
  157. // have the mandatory new properties.
  158. this._getRsaOaepHash = function() {
  159. return box.forge.md.sha256.create();
  160. };
  161. //
  162. this._getRsaOaepMaskHash = function() {
  163. // For interoperability purposes, the MGF1 hash function must
  164. // remain as SHA-1.
  165. return box.forge.md.sha1.create();
  166. };
  167. this._getRsaOaepEncodingOptions = function() {
  168. var encodingOptions = {
  169. md: this._getRsaOaepHash(),
  170. mgf1: {md: this._getRsaOaepMaskHash()},
  171. seed: this.secureRandomBytes.nextRandom(this.encodingSeedLength)
  172. };
  173. return encodingOptions;
  174. };
  175. this._getRsaOaepDecodingOptions = function() {
  176. var decodingOptions = {
  177. md: this._getRsaOaepHash(),
  178. mgf1: {md: this._getRsaOaepMaskHash()}
  179. };
  180. return decodingOptions;
  181. };
  182. this._determineAndCreateHash = function(requestedHash) {
  183. if (requestedHash ===
  184. Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.messagedigest
  185. .algorithm.SHA256) {
  186. return forge.md.sha256.create();
  187. } else {
  188. throw new exceptions.CryptoLibException(
  189. 'Unsupported hash function specified.');
  190. }
  191. };
  192. this._determineAndCreateDeriver = function(requestedDeriver) {
  193. var hash = this._determineAndCreateHash(policies.cipher.hash);
  194. if (requestedDeriver ===
  195. Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF1) {
  196. return new forge.kem.kdf1(hash);
  197. } else if (
  198. requestedDeriver ===
  199. Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF2) {
  200. return new forge.kem.kdf2(hash);
  201. } else if (
  202. requestedDeriver ===
  203. Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.MGF1) {
  204. return new forge.mgf.mgf1.create(hash);
  205. } else {
  206. throw new exceptions.CryptoLibException(
  207. 'Unsupported deriver function specified.');
  208. }
  209. };
  210. }
  211. CryptoForgeAsymmetricCipher.prototype = {
  212. /**
  213. * Encrypts some data.
  214. * <p>
  215. * If the algorithm is RSA-KEM, then the output from this function will
  216. * be the base64 encoding of the following data:
  217. * <p>
  218. * [Encapsulation][IV][Encrypted Data][Tag]
  219. *
  220. * @function
  221. * @param publicKeyPem
  222. * {string} public key, as string in PEM format.
  223. * @param dataBase64
  224. * {string} data to be encrypted, as string in Base64 encoded
  225. * format.
  226. * @returns encrypted data, as string in Base 64 encoded format..
  227. */
  228. encrypt: function(publicKeyPem, dataBase64) {
  229. this._validateInputs(publicKeyPem, dataBase64);
  230. try {
  231. var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem);
  232. var data = converters.base64Decode(dataBase64);
  233. var output;
  234. if (policies.cipher.algorithm.name ===
  235. Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
  236. var encodingOptions = this._getRsaOaepEncodingOptions();
  237. output = publicKey.encrypt(
  238. data, policies.cipher.algorithm.name, encodingOptions);
  239. } else if (
  240. policies.cipher.algorithm.name ===
  241. Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
  242. var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;
  243. var ivLengthBytes = policies.cipher.ivLengthBytes;
  244. var symmetricCipher = policies.cipher.symmetricCipher;
  245. var deriver =
  246. this._determineAndCreateDeriver(policies.cipher.deriver);
  247. // generate and encapsulate secret key
  248. var kem = forge.kem.rsa.create(deriver);
  249. var result = kem.encrypt(publicKey, secretKeyLengthBytes);
  250. var iv = forge.random.getBytesSync(ivLengthBytes);
  251. var cipher = forge.cipher.createCipher(symmetricCipher, result.key);
  252. cipher.start({iv: iv});
  253. cipher.update(forge.util.createBuffer(data));
  254. cipher.finish();
  255. var encryptedData = cipher.output.getBytes();
  256. var tag = cipher.mode.tag.getBytes();
  257. output = result.encapsulation.toString() + iv.toString() +
  258. encryptedData.toString() + tag.toString();
  259. } else {
  260. throw new exceptions.CryptoLibException(
  261. 'The specified algorithm is not supported.');
  262. }
  263. return converters.base64Encode(output);
  264. } catch (error) {
  265. throw new exceptions.CryptoLibException(
  266. 'CryptoForgeAsymmetricCipher, data could not be encrypted.', error);
  267. }
  268. },
  269. /**
  270. * Decrypts some encrypted data.
  271. *
  272. * @function
  273. * @param privateKeyPem
  274. * {string} private key, as string in PEM format.
  275. * @param encryptedDataB64
  276. * {string} encrypted data, as string in Base64 encoded
  277. * format.
  278. * @returns decrypted data, as string in Base64 encoded format.
  279. */
  280. decrypt: function(privateKeyPem, encryptedDataBase64) {
  281. this._validateInputs(privateKeyPem, encryptedDataBase64);
  282. try {
  283. var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem);
  284. var encryptedData = converters.base64Decode(encryptedDataBase64);
  285. var decryptedData;
  286. if (policies.cipher.algorithm.name ===
  287. Config.asymmetric.cipher.algorithm.RSA_OAEP.name) {
  288. var decodingOptions = this._getRsaOaepDecodingOptions();
  289. decryptedData = privateKey.decrypt(
  290. encryptedData, policies.cipher.algorithm.name, decodingOptions);
  291. } else if (
  292. policies.cipher.algorithm.name ===
  293. Config.asymmetric.cipher.algorithm.RSA_KEM.name) {
  294. var symmetricCipher = policies.cipher.symmetricCipher;
  295. var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes;
  296. var encryptedDataParts = this._parseParts(privateKey, encryptedData);
  297. var deriver =
  298. this._determineAndCreateDeriver(policies.cipher.deriver);
  299. // decrypt encapsulated secret key
  300. var kem = forge.kem.rsa.create(deriver);
  301. var key = kem.decrypt(
  302. privateKey, encryptedDataParts.encapsulation,
  303. secretKeyLengthBytes);
  304. // decrypt some bytes
  305. var decipher = forge.cipher.createDecipher(symmetricCipher, key);
  306. decipher.start(
  307. {iv: encryptedDataParts.iv, tag: encryptedDataParts.tag});
  308. decipher.update(forge.util.createBuffer(encryptedDataParts.data));
  309. var result = decipher.finish();
  310. // result will be false if there was a failure
  311. if (result) {
  312. decryptedData = decipher.output.getBytes();
  313. } else {
  314. throw new exceptions.CryptoLibException(
  315. 'Error while decrypting data.');
  316. }
  317. } else {
  318. throw new exceptions.CryptoLibException(
  319. 'The specified algorithm is not supported.');
  320. }
  321. return converters.base64Encode(decryptedData);
  322. } catch (error) {
  323. throw new exceptions.CryptoLibException(
  324. 'CryptoForgeAsymmetricCipher, data could not be decrypted.', error);
  325. }
  326. }
  327. };
  328. };