scytl-keystore.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. /* jshint node:true */
  9. 'use strict';
  10. var Pkcs12KeyStore = require('./pkcs12-keystore');
  11. var cryptoPolicy = require('scytl-cryptopolicy');
  12. var elGamal = require('scytl-elgamal');
  13. var symmetric = require('scytl-symmetric');
  14. var pbkdf = require('scytl-pbkdf');
  15. var constants = require('./constants');
  16. var validator = require('./input-validator');
  17. var codec = require('scytl-codec');
  18. var forge = require('node-forge');
  19. module.exports = ScytlKeyStore;
  20. var BigInteger = forge.jsbn.BigInteger;
  21. /**
  22. * @class ScytlKeyStore
  23. * @classdesc Encapsulates a Scytl key store. To instantiate this object, use
  24. * the method {@link KeyStoreService.newScytlKeyStore}.
  25. * @hideconstructor
  26. * @param {Object|string}
  27. * keyStore The provided Scytl key store, as an object with expected
  28. * properties <b>OR</b> its JSON string representation.
  29. * @param {string}
  30. * password The password to load the Scytl key store.
  31. * @param {Object}
  32. * [options] An object containing optional arguments.
  33. * @param {Policy}
  34. * [options.policy=Default policy] The cryptographic policy to use.
  35. * @param {PbkdfRandomService}
  36. * [options.pbkdfRandomService=Created internally] The PBKDF service
  37. * to use.
  38. * @param {SymmetricCryptographyService}
  39. * [options.symmetricCryptographyService=Created internally] The
  40. * symmetric cryptography service to use.
  41. * @param {ElGamalCryptographyService}
  42. * [options.elGamalCryptographyService=Created internally] The
  43. * ElGamal cryptography service to use.
  44. * @throws {Error}
  45. * If the input data validation fails or the underlying key store
  46. * could not be loaded.
  47. */
  48. function ScytlKeyStore(keyStore, password, options) {
  49. var scytlKeyStore = keyStore;
  50. if (typeof keyStore === 'string') {
  51. validator.checkIsJsonString(
  52. keyStore, 'Scytl key store (in JSON format) to load');
  53. scytlKeyStore = JSON.parse(keyStore);
  54. }
  55. checkScytlKeyStore(scytlKeyStore);
  56. validator.checkIsType(password, 'string', 'Password to load Scytl key store');
  57. options = options || {};
  58. var policy_;
  59. if (options.policy) {
  60. policy_ = options.policy;
  61. } else {
  62. policy_ = cryptoPolicy.newIntance();
  63. }
  64. var pbkdfService;
  65. if (options.pbkdfService) {
  66. pbkdfService = options.pbkdfService;
  67. } else {
  68. pbkdfService = pbkdf.newService({policy: policy_});
  69. }
  70. var symmetricService_;
  71. if (options.symmetricCryptographyService) {
  72. symmetricService_ = options.symmetricCryptographyService;
  73. } else {
  74. symmetricService_ = symmetric.newService({policy: policy_});
  75. }
  76. var elGamalService_;
  77. if (options.elGamalCryptographyService) {
  78. elGamalService_ = options.elGamalCryptographyService;
  79. } else {
  80. elGamalService_ = elGamal.newService();
  81. }
  82. var pbkdfDeriver_ = pbkdfService.newDeriver();
  83. var symmetricCipher_ = symmetricService_.newCipher();
  84. var derivedKeys_ = {};
  85. var derivedPasswords_ = {};
  86. var pkcs12KeyStore_;
  87. try {
  88. pkcs12KeyStore_ =
  89. new Pkcs12KeyStore(scytlKeyStore.store, getDerivedPassword(password));
  90. } catch (error) {
  91. throw new Error('Could not load underlying PKCS12 key store; ' + error);
  92. }
  93. /**
  94. * Retrieves a private key stored inside the Scytl key store, given the
  95. * key's storage alias name and password.
  96. *
  97. * @function getPrivateKey
  98. * @memberof ScytlKeyStore
  99. * @param {string}
  100. * alias The storage alias name of the private key to retrieve.
  101. * @param {string}
  102. * password The storage password of the private key.
  103. * @returns {string} The private key, in PEM format.
  104. * @throws {Error}
  105. * If the input data validation fails or the private key could
  106. * not be retrieved.
  107. */
  108. this.getPrivateKey = function(alias, password) {
  109. validator.checkIsNonEmptyString(
  110. alias,
  111. 'Storage alias name of private key to retrieve from Scytl key store');
  112. validator.checkIsType(
  113. password, 'string',
  114. 'Password of private key to retrieve from Scytl key store');
  115. try {
  116. return pkcs12KeyStore_.getPrivateKey(alias, getDerivedPassword(password));
  117. } catch (error) {
  118. throw new Error(
  119. 'Could not retrieve private key with alias \'' + alias +
  120. '\' from Scytl key store; ' + error);
  121. }
  122. };
  123. /**
  124. * Retrieves a certificate stored inside the Scytl key store, given the
  125. * storage alias name of the certificate or that of its associated private
  126. * key entry.
  127. *
  128. * @function getCertificate
  129. * @memberof ScytlKeyStore
  130. * @param {string}
  131. * alias The storage alias name of the certificate or that of its
  132. * associated private key entry.
  133. * @returns {string} The certificate, in PEM format.
  134. * @throws {Error}
  135. * If the input data validation fails or the certificate could
  136. * not be retrieved.
  137. */
  138. this.getCertificate = function(alias) {
  139. validator.checkIsNonEmptyString(
  140. alias,
  141. 'Storage alias name of certificate to retrieve from Scytl key store,');
  142. try {
  143. return pkcs12KeyStore_.getCertificate(alias);
  144. } catch (error) {
  145. throw new Error(
  146. 'Could not retrieve certificate with alias \'' + alias +
  147. '\' from Scytl key store; ' + error);
  148. }
  149. };
  150. /**
  151. * Retrieves a certificate stored inside the Scytl key store, given the
  152. * certificate's subject common name.
  153. *
  154. * @function getCertificateBySubject
  155. * @memberof ScytlKeyStore
  156. * @param {string}
  157. * subjectCn The subject common name of the certificate.
  158. * @returns {string} The certificate, in PEM format.
  159. * @throws {Error}
  160. * If the input data validation fails or the certificate could
  161. * not be retrieved.
  162. */
  163. this.getCertificateBySubject = function(subjectCn) {
  164. validator.checkIsNonEmptyString(
  165. subjectCn,
  166. 'Subject common name of certificate to retrieve from Scytl key store');
  167. try {
  168. return pkcs12KeyStore_.getCertificateBySubject(subjectCn);
  169. } catch (error) {
  170. throw new Error(
  171. 'Could not retrieve certificate with subject common name \'' +
  172. subjectCn + '\' from Scytl key store; ' + error);
  173. }
  174. };
  175. /**
  176. * Retrieves a certificate chain stored inside the Scytl key store, given
  177. * the storage alias name of the chain's associated private key entry.
  178. *
  179. * @function getCertificateChain
  180. * @memberof ScytlKeyStore
  181. * @param {string}
  182. * alias The storage alias name of the chain's associated private
  183. * key entry.
  184. * @returns {string[]} The certificate chain, as an array of strings in PEM
  185. * format.
  186. * @throws {Error}
  187. * If the input data validation fails or the certificate chain
  188. * could not be retrieved.
  189. */
  190. this.getCertificateChain = function(alias) {
  191. validator.checkIsNonEmptyString(
  192. alias,
  193. 'Storage alias name of certificate chain to retrieve from Scytl key store');
  194. try {
  195. return pkcs12KeyStore_.getCertificateChain(alias);
  196. } catch (error) {
  197. throw new Error(
  198. 'Could not retrieve certificate chain with alias \'' + alias +
  199. '\' from Scytl key store; ' + error);
  200. }
  201. };
  202. /**
  203. * Retrieves a secret key stored inside the Scytl key store, given the key's
  204. * storage alias name and password.
  205. *
  206. * @function getSecretKey
  207. * @memberof ScytlKeyStore
  208. * @param {string}
  209. * alias The storage alias name of the secret key to retrieve.
  210. * @param {string}
  211. * password The storage password of the secret key.
  212. * @returns {Uint8Array} The secret key.
  213. * @throws {Error}
  214. * If the input data validation fails or the secret key could
  215. * not be retrieved.
  216. */
  217. this.getSecretKey = function(alias, password) {
  218. validator.checkIsNonEmptyString(
  219. alias,
  220. 'Storage alias name of secret key to retrieve from Scytl key store');
  221. validator.checkIsType(
  222. password, 'string',
  223. 'Password of secret key to retrieve from key store');
  224. var encryptedKey = scytlKeyStore.secrets[alias];
  225. checkEncryptedKey(encryptedKey, 'secret', alias);
  226. var secretKey = getKey(password, encryptedKey, alias, 'secret');
  227. return codec.binaryDecode(secretKey);
  228. };
  229. /**
  230. * Retrieves an ElGamal private key stored inside the Scytl key store, given
  231. * the key's storage alias name and password.
  232. *
  233. * @function getElGamalPrivateKey
  234. * @memberof ScytlKeyStore
  235. * @param {string}
  236. * alias The storage alias name of the ElGamal private key to
  237. * retrieve.
  238. * @param {string}
  239. * password The storage password of the ElGamal private key.
  240. * @returns {ElGamalPrivateKey} The ElGamal private key.
  241. * @throws {Error}
  242. * If the input data validation fails or the ElGamal private key
  243. * could not be retrieved.
  244. */
  245. this.getElGamalPrivateKey = function(alias, password) {
  246. validator.checkIsNonEmptyString(
  247. alias,
  248. 'Storage alias name of ElGamal private key to retrieve from Scytl key store');
  249. validator.checkIsType(
  250. password, 'string',
  251. 'Password of ElGamal private key to retrieve from Scytl key store');
  252. var encryptedKey = scytlKeyStore.egPrivKeys[alias];
  253. checkEncryptedKey(encryptedKey, 'ElGamal private', alias);
  254. var elGamalPrivateKeyJson =
  255. getKey(password, encryptedKey, alias, 'ElGamal private');
  256. return elGamalService_.newPrivateKey(elGamalPrivateKeyJson);
  257. };
  258. function getDerivedPassword(password) {
  259. if (!derivedPasswords_.hasOwnProperty(password)) {
  260. var derivedKeyBytes = getDerivedKey(password);
  261. var derivedKeyBigInteger = new BigInteger(derivedKeyBytes);
  262. var derivedPassword =
  263. derivedKeyBigInteger.toString(constants.CHARACTER_MAX_RADIX);
  264. derivedPasswords_[password] = derivedPassword;
  265. }
  266. return derivedPasswords_[password];
  267. }
  268. function getDerivedKey(password) {
  269. if (!derivedKeys_.hasOwnProperty(password)) {
  270. derivedKeys_[password] = pbkdfDeriver_.derive(
  271. password, codec.base64Decode(scytlKeyStore.salt));
  272. }
  273. return derivedKeys_[password];
  274. }
  275. function checkEncryptedKey(key, keyType, alias) {
  276. if (!key) {
  277. throw new Error(
  278. 'Could not find ' + keyType + ' key with storge alias name \'' +
  279. alias + '\'');
  280. }
  281. }
  282. function getKey(password, encryptedKey, alias, keyType) {
  283. var aliasAndKey = symmetricCipher_.init(getDerivedKey(password))
  284. .decrypt(codec.base64Decode(encryptedKey));
  285. var aliasAndKeyBinaryEncoded = codec.binaryEncode(aliasAndKey);
  286. var decryptedAlias = aliasAndKeyBinaryEncoded.slice(0, alias.length);
  287. if (decryptedAlias !== alias) {
  288. throw new Error(
  289. 'Expected decrypted alias for ' + keyType + ' key to be \'' + alias +
  290. '\'; Found \'' + decryptedAlias +
  291. '\' Check password provided to retrieve key from store.');
  292. }
  293. return aliasAndKeyBinaryEncoded.slice(alias.length, aliasAndKey.length);
  294. }
  295. function checkScytlKeyStore(scytlKeyStore) {
  296. validator.checkIsObjectWithProperties(
  297. scytlKeyStore, 'Scytl key store to load');
  298. if (typeof scytlKeyStore.salt === 'undefined') {
  299. throw new Error('Field \'salt\' is undefined in Scytl key store to load');
  300. }
  301. if (typeof scytlKeyStore.store === 'undefined') {
  302. throw new Error(
  303. 'Field \'store\' is undefined in Scytl key store to load');
  304. }
  305. }
  306. }