pkcs12-keystore.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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 validator = require('./input-validator');
  11. var codec = require('scytl-codec');
  12. var forge = require('node-forge');
  13. module.exports = Pkcs12KeyStore;
  14. /**
  15. * @class Pkcs12KeyStore
  16. * @classdesc Encapsulates a PKCS #12 key store. To instantiate this object, use
  17. * the method {@link KeyStoreService.newPkcs12KeyStore}.
  18. * @hideconstructor
  19. * @param {Uint8Array|string}
  20. * keyStore The provided PKCS #12 key store as a DER encoded ASN.1
  21. * structure <b>OR</b> such a structure Base64 encoded.
  22. * @param {string}
  23. * password The password to load the PKCS #12 key store.
  24. * @throws {Error}
  25. * If the input data validation fails or the underlying key store
  26. * could not be loaded.
  27. */
  28. function Pkcs12KeyStore(keyStore, password) {
  29. var pkcs12Der;
  30. if (typeof keyStore !== 'string') {
  31. pkcs12Der = keyStore;
  32. } else {
  33. validator.checkIsNonEmptyString(
  34. keyStore, 'PKCS12 key store, Base64 encoded, to load');
  35. pkcs12Der = codec.base64Decode(keyStore);
  36. }
  37. validator.checkIsInstanceOf(
  38. pkcs12Der, Uint8Array, 'Uint8Array',
  39. 'PKCS12 key store, DER encoded, to load');
  40. validator.checkIsType(
  41. password, 'string', 'Password to load PKCS12 key store');
  42. var forgePkcs12Asn1_;
  43. var forgePkcs12_;
  44. try {
  45. forgePkcs12Asn1_ = forge.asn1.fromDer(codec.binaryEncode(pkcs12Der), false);
  46. forgePkcs12_ =
  47. forge.pkcs12.pkcs12FromAsn1(forgePkcs12Asn1_, false, password);
  48. } catch (error) {
  49. throw new Error('Could not load PKCS12 key store; ' + error);
  50. }
  51. /**
  52. * Retrieves a private key stored inside the PKCS #12 key store, given the
  53. * key's storage alias name and password
  54. *
  55. * @function getPrivateKey
  56. * @memberof Pkcs12KeyStore
  57. * @param {string}
  58. * alias The storage alias name of the private key to retrieve.
  59. * @param {string}
  60. * password The password used to store the private key.
  61. * @returns {string} The private key, in PEM format.
  62. * @throws {Error}
  63. * If the input data validation fails or the private key could
  64. * not be retrieved.
  65. */
  66. this.getPrivateKey = function(alias, password) {
  67. validator.checkIsNonEmptyString(
  68. alias,
  69. 'Storage alias name of private key to retrieve from PKCS12 key store');
  70. checkPassword(
  71. password, forgePkcs12Asn1_,
  72. 'Password to retrieve private key from PKCS12 key store');
  73. try {
  74. var privateKeyPem;
  75. var privateKeyFound = false;
  76. for (var i = 0; i < forgePkcs12_.safeContents.length; i++) {
  77. var safeContents = forgePkcs12_.safeContents[i];
  78. for (var j = 0; j < safeContents.safeBags.length; j++) {
  79. var safeBag = safeContents.safeBags[j];
  80. if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag &&
  81. safeBag.attributes.friendlyName[0] === alias) {
  82. privateKeyPem = forge.pki.privateKeyToPem(safeBag.key);
  83. privateKeyFound = true;
  84. break;
  85. }
  86. }
  87. if (privateKeyFound) {
  88. break;
  89. }
  90. }
  91. if (!privateKeyPem) {
  92. throw new Error(
  93. 'Could not find any private key with alias \'' + alias +
  94. '\' in PKCS12 key store.');
  95. }
  96. return privateKeyPem;
  97. } catch (error) {
  98. throw new Error(
  99. 'Could not retrieve private key with alias \'' + alias +
  100. '\' from PKCS12 key store; ' + error);
  101. }
  102. };
  103. /**
  104. * Retrieves a certificate stored inside the PKCS #12 key store, given the
  105. * storage alias name of the certificate or that of its associated private
  106. * key entry.
  107. *
  108. * @function getCertificate
  109. * @memberof Pkcs12KeyStore
  110. * @param {string}
  111. * alias The storage alias name of the certificate or that of its
  112. * associated private key entry.
  113. * @returns {string} The certificate, in PEM format.
  114. * @throws {Error}
  115. * If the input data validation fails or the certificate could
  116. * not be retrieved.
  117. */
  118. this.getCertificate = function(alias) {
  119. validator.checkIsNonEmptyString(
  120. alias,
  121. 'Storage alias name of certificate or that of its associated private key entry, to retrieve from PKCS12 key store');
  122. try {
  123. var certificatePem;
  124. var certificateFound = false;
  125. for (var i = 0; i < forgePkcs12_.safeContents.length; i++) {
  126. var safeContents = forgePkcs12_.safeContents[i];
  127. for (var j = 0; j < safeContents.safeBags.length; j++) {
  128. var safeBag = safeContents.safeBags[j];
  129. if (safeBag.type === forge.pki.oids.certBag &&
  130. safeBag.attributes && safeBag.attributes.friendlyName &&
  131. safeBag.attributes.friendlyName[0] === alias) {
  132. certificatePem = forge.pki.certificateToPem(safeBag.cert);
  133. certificateFound = true;
  134. break;
  135. }
  136. }
  137. if (certificateFound) {
  138. break;
  139. }
  140. }
  141. if (!certificatePem) {
  142. throw new Error(
  143. 'Could not find any certificate with alias \'' + alias +
  144. '\' in PKCS12 key store.');
  145. }
  146. return certificatePem;
  147. } catch (error) {
  148. throw new Error(
  149. 'Could not retrieve certificate with alias \'' + alias +
  150. '\' from PKCS12 key store; ' + error);
  151. }
  152. };
  153. /**
  154. * Retrieves a certificate stored inside the PKCS #12 key store, given the
  155. * certificate's subject common name.
  156. *
  157. * @function getCertificateBySubject
  158. * @memberof Pkcs12KeyStore
  159. * @param {string}
  160. * subjectCn The subject common name of the certificate.
  161. * @returns {string} The certificate, in PEM format.
  162. * @throws {Error}
  163. * If the input data validation fails or the certificate could
  164. * not be retrieved.
  165. */
  166. this.getCertificateBySubject = function(subjectCN) {
  167. validator.checkIsNonEmptyString(
  168. subjectCN,
  169. 'Subject common name of certificate to retrieve from PKCS12 key store');
  170. try {
  171. var certificatePem;
  172. var certificateFound = false;
  173. for (var i = 0; i < forgePkcs12_.safeContents.length; i++) {
  174. var safeContents = forgePkcs12_.safeContents[i];
  175. for (var j = 0; j < safeContents.safeBags.length; j++) {
  176. var safeBag = safeContents.safeBags[j];
  177. if (safeBag.type === forge.pki.oids.certBag &&
  178. safeBag.cert.subject.getField('CN').value === subjectCN) {
  179. certificatePem = forge.pki.certificateToPem(safeBag.cert);
  180. certificateFound = true;
  181. break;
  182. }
  183. }
  184. if (certificateFound) {
  185. break;
  186. }
  187. }
  188. if (!certificatePem) {
  189. throw new Error(
  190. 'Could not find any certificate with subject common name \'' +
  191. subjectCN + '\' in PKCS12 key store.');
  192. }
  193. return certificatePem;
  194. } catch (error) {
  195. throw Error(
  196. 'Could not retrieve certificate with subject common name \'' +
  197. subjectCN + '\' from PKCS12 key store; ' + error);
  198. }
  199. };
  200. /**
  201. * Retrieves a certificate chain stored inside the PKCS #12 key store, given
  202. * the storage alias name of the chain's associated private key entry.
  203. *
  204. * @function getCertificateChain
  205. * @memberof Pkcs12KeyStore
  206. * @param {string}
  207. * alias The storage alias name of the chain's associated private
  208. * key entry.
  209. * @returns {string[]} The certificate chain, as an array of strings in PEM
  210. * format.
  211. * @throws {Error}
  212. * If the input data validation fails or the certificate chain
  213. * could not be retrieved.
  214. */
  215. this.getCertificateChain = function(alias) {
  216. validator.checkIsNonEmptyString(
  217. alias,
  218. 'Storage alias name of private key associated with certificate chain to retrieve from PKCS12 key store');
  219. try {
  220. // Loop through certificate safe bags of PKCS12.
  221. var certificatePemChain = [];
  222. var savedBags = {};
  223. var nextBagId;
  224. for (var i = 0; i < forgePkcs12_.safeContents.length; i++) {
  225. var safeContents = forgePkcs12_.safeContents[i];
  226. for (var j = 0; j < safeContents.safeBags.length; j++) {
  227. var safeBag = safeContents.safeBags[j];
  228. if (safeBag.type === forge.pki.oids.certBag) {
  229. var nextBag;
  230. // If no bags have been found yet and this is the safe bag
  231. // associated with the alias (i.e. the leaf safe bag).
  232. // Or if this happens to be the next bag in the chain.
  233. if ((!nextBagId && safeBag.attributes &&
  234. safeBag.attributes.friendlyName &&
  235. safeBag.attributes.friendlyName[0] === alias) ||
  236. (nextBagId && nextBagId === safeBag.cert.subject.hash)) {
  237. nextBag = safeBag;
  238. }
  239. // If this is a bag higher up in the chain, and so will need to be
  240. // added later.
  241. else {
  242. savedBags[safeBag.cert.subject.hash] = safeBag;
  243. }
  244. // Add this bag and any saved bags to the chain, and in the proper
  245. // order.
  246. while (nextBag) {
  247. var certificatePem = forge.pki.certificateToPem(nextBag.cert);
  248. certificatePemChain.push(certificatePem);
  249. // If this bag is the root certificate, then the process is
  250. // finished.
  251. if (nextBag.cert.subject.hash === nextBag.cert.issuer.hash) {
  252. return certificatePemChain;
  253. }
  254. // Save the issuer of this bag as the ID of the next bag to add to
  255. // the chain.
  256. nextBagId = nextBag.cert.issuer.hash;
  257. // Add the saved bag corresponding to the ID of the next bag to
  258. // the chain.
  259. nextBag = savedBags[nextBagId];
  260. }
  261. }
  262. }
  263. }
  264. // Check whether any certificate safe bags were found.
  265. if (certificatePemChain.length === 0) {
  266. throw new Error(
  267. 'Could not find any certificate chains with storage alias name \'' +
  268. alias + '\' in PKCS12 key store');
  269. }
  270. return certificatePemChain;
  271. } catch (error) {
  272. throw new Error(
  273. 'Could not retrieve certificate chain with storage alias name \'' +
  274. alias + '\' from PKCS12 key store; ' + error);
  275. }
  276. };
  277. function checkPassword(password, pkcs12Asn1, label) {
  278. validator.checkIsType(password, 'string', label);
  279. try {
  280. forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
  281. } catch (error) {
  282. throw new Error(label + ' is not valid');
  283. }
  284. }
  285. }