/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ /* jshint node:true */ 'use strict'; var validator = require('./input-validator'); var codec = require('scytl-codec'); var forge = require('node-forge'); module.exports = Pkcs12KeyStore; /** * @class Pkcs12KeyStore * @classdesc Encapsulates a PKCS #12 key store. To instantiate this object, use * the method {@link KeyStoreService.newPkcs12KeyStore}. * @hideconstructor * @param {Uint8Array|string} * keyStore The provided PKCS #12 key store as a DER encoded ASN.1 * structure OR such a structure Base64 encoded. * @param {string} * password The password to load the PKCS #12 key store. * @throws {Error} * If the input data validation fails or the underlying key store * could not be loaded. */ function Pkcs12KeyStore(keyStore, password) { var pkcs12Der; if (typeof keyStore !== 'string') { pkcs12Der = keyStore; } else { validator.checkIsNonEmptyString( keyStore, 'PKCS12 key store, Base64 encoded, to load'); pkcs12Der = codec.base64Decode(keyStore); } validator.checkIsInstanceOf( pkcs12Der, Uint8Array, 'Uint8Array', 'PKCS12 key store, DER encoded, to load'); validator.checkIsType( password, 'string', 'Password to load PKCS12 key store'); var forgePkcs12Asn1_; var forgePkcs12_; try { forgePkcs12Asn1_ = forge.asn1.fromDer(codec.binaryEncode(pkcs12Der), false); forgePkcs12_ = forge.pkcs12.pkcs12FromAsn1(forgePkcs12Asn1_, false, password); } catch (error) { throw new Error('Could not load PKCS12 key store; ' + error); } /** * Retrieves a private key stored inside the PKCS #12 key store, given the * key's storage alias name and password * * @function getPrivateKey * @memberof Pkcs12KeyStore * @param {string} * alias The storage alias name of the private key to retrieve. * @param {string} * password The password used to store the private key. * @returns {string} The private key, in PEM format. * @throws {Error} * If the input data validation fails or the private key could * not be retrieved. */ this.getPrivateKey = function(alias, password) { validator.checkIsNonEmptyString( alias, 'Storage alias name of private key to retrieve from PKCS12 key store'); checkPassword( password, forgePkcs12Asn1_, 'Password to retrieve private key from PKCS12 key store'); try { var privateKeyPem; var privateKeyFound = false; for (var i = 0; i < forgePkcs12_.safeContents.length; i++) { var safeContents = forgePkcs12_.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag && safeBag.attributes.friendlyName[0] === alias) { privateKeyPem = forge.pki.privateKeyToPem(safeBag.key); privateKeyFound = true; break; } } if (privateKeyFound) { break; } } if (!privateKeyPem) { throw new Error( 'Could not find any private key with alias \'' + alias + '\' in PKCS12 key store.'); } return privateKeyPem; } catch (error) { throw new Error( 'Could not retrieve private key with alias \'' + alias + '\' from PKCS12 key store; ' + error); } }; /** * Retrieves a certificate stored inside the PKCS #12 key store, given the * storage alias name of the certificate or that of its associated private * key entry. * * @function getCertificate * @memberof Pkcs12KeyStore * @param {string} * alias The storage alias name of the certificate or that of its * associated private key entry. * @returns {string} The certificate, in PEM format. * @throws {Error} * If the input data validation fails or the certificate could * not be retrieved. */ this.getCertificate = function(alias) { validator.checkIsNonEmptyString( alias, 'Storage alias name of certificate or that of its associated private key entry, to retrieve from PKCS12 key store'); try { var certificatePem; var certificateFound = false; for (var i = 0; i < forgePkcs12_.safeContents.length; i++) { var safeContents = forgePkcs12_.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag && safeBag.attributes && safeBag.attributes.friendlyName && safeBag.attributes.friendlyName[0] === alias) { certificatePem = forge.pki.certificateToPem(safeBag.cert); certificateFound = true; break; } } if (certificateFound) { break; } } if (!certificatePem) { throw new Error( 'Could not find any certificate with alias \'' + alias + '\' in PKCS12 key store.'); } return certificatePem; } catch (error) { throw new Error( 'Could not retrieve certificate with alias \'' + alias + '\' from PKCS12 key store; ' + error); } }; /** * Retrieves a certificate stored inside the PKCS #12 key store, given the * certificate's subject common name. * * @function getCertificateBySubject * @memberof Pkcs12KeyStore * @param {string} * subjectCn The subject common name of the certificate. * @returns {string} The certificate, in PEM format. * @throws {Error} * If the input data validation fails or the certificate could * not be retrieved. */ this.getCertificateBySubject = function(subjectCN) { validator.checkIsNonEmptyString( subjectCN, 'Subject common name of certificate to retrieve from PKCS12 key store'); try { var certificatePem; var certificateFound = false; for (var i = 0; i < forgePkcs12_.safeContents.length; i++) { var safeContents = forgePkcs12_.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag && safeBag.cert.subject.getField('CN').value === subjectCN) { certificatePem = forge.pki.certificateToPem(safeBag.cert); certificateFound = true; break; } } if (certificateFound) { break; } } if (!certificatePem) { throw new Error( 'Could not find any certificate with subject common name \'' + subjectCN + '\' in PKCS12 key store.'); } return certificatePem; } catch (error) { throw Error( 'Could not retrieve certificate with subject common name \'' + subjectCN + '\' from PKCS12 key store; ' + error); } }; /** * Retrieves a certificate chain stored inside the PKCS #12 key store, given * the storage alias name of the chain's associated private key entry. * * @function getCertificateChain * @memberof Pkcs12KeyStore * @param {string} * alias The storage alias name of the chain's associated private * key entry. * @returns {string[]} The certificate chain, as an array of strings in PEM * format. * @throws {Error} * If the input data validation fails or the certificate chain * could not be retrieved. */ this.getCertificateChain = function(alias) { validator.checkIsNonEmptyString( alias, 'Storage alias name of private key associated with certificate chain to retrieve from PKCS12 key store'); try { // Loop through certificate safe bags of PKCS12. var certificatePemChain = []; var savedBags = {}; var nextBagId; for (var i = 0; i < forgePkcs12_.safeContents.length; i++) { var safeContents = forgePkcs12_.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag) { var nextBag; // If no bags have been found yet and this is the safe bag // associated with the alias (i.e. the leaf safe bag). // Or if this happens to be the next bag in the chain. if ((!nextBagId && safeBag.attributes && safeBag.attributes.friendlyName && safeBag.attributes.friendlyName[0] === alias) || (nextBagId && nextBagId === safeBag.cert.subject.hash)) { nextBag = safeBag; } // If this is a bag higher up in the chain, and so will need to be // added later. else { savedBags[safeBag.cert.subject.hash] = safeBag; } // Add this bag and any saved bags to the chain, and in the proper // order. while (nextBag) { var certificatePem = forge.pki.certificateToPem(nextBag.cert); certificatePemChain.push(certificatePem); // If this bag is the root certificate, then the process is // finished. if (nextBag.cert.subject.hash === nextBag.cert.issuer.hash) { return certificatePemChain; } // Save the issuer of this bag as the ID of the next bag to add to // the chain. nextBagId = nextBag.cert.issuer.hash; // Add the saved bag corresponding to the ID of the next bag to // the chain. nextBag = savedBags[nextBagId]; } } } } // Check whether any certificate safe bags were found. if (certificatePemChain.length === 0) { throw new Error( 'Could not find any certificate chains with storage alias name \'' + alias + '\' in PKCS12 key store'); } return certificatePemChain; } catch (error) { throw new Error( 'Could not retrieve certificate chain with storage alias name \'' + alias + '\' from PKCS12 key store; ' + error); } }; function checkPassword(password, pkcs12Asn1, label) { validator.checkIsType(password, 'string', label); try { forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password); } catch (error) { throw new Error(label + ' is not valid'); } } }