/* * 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 */ cryptolib.modules.stores = cryptolib.modules.stores || {}; /** * Defines a pkcs12 store. */ cryptolib.modules.stores.pkcs12 = function(box) { 'use strict'; box.stores = box.stores || {}; box.stores.pkcs12 = {}; var converters; var exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * Defines a PKCS12. * * @param pkcs12DerB64 * PKCS12, as string in Base64 encoded DER format. */ box.stores.pkcs12.Pkcs12 = function(pkcs12DerB64) { this.pkcs12Der = converters.base64Decode(pkcs12DerB64); this.pkcs12Asn1 = forge.asn1.fromDer(this.pkcs12Der, false); }; box.stores.pkcs12.Pkcs12.prototype = { /** * Retrieves the private key of the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key of PKCS12, as string in PEM format. */ getPrivateKey: function(passwordB64) { var aliases = Array.prototype.slice.call(arguments, 1); function isAliasInList(alias) { var returnValue = false; if (aliases.indexOf(alias) >= 0) { returnValue = true; } return returnValue; } var privateKeyAlias = ''; try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve private key safe bag from PKCS12. var privateKeys = {}; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { privateKeyAlias = safeBag.attributes.friendlyName[0]; if (aliases.length === 0 || isAliasInList(privateKeyAlias)) { privateKeys[privateKeyAlias] = forge.pki.privateKeyToPem(safeBag.key); } } } } // Check whether any private key safe bags were found. if (Object.getOwnPropertyNames(privateKeys).length === 0) { throw new exceptions.CryptoLibException( 'Could not find any private key safe bags in PKCS12.'); } return privateKeys; } catch (error) { var errorMsg = 'Could not retrieve private key from PKCS12.'; if (privateKeyAlias.length > 0) { errorMsg = 'Could not retrieve private key with alias ' + privateKeyAlias + ' from PKCS12.'; } throw new exceptions.CryptoLibException(errorMsg); } }, /** * Retrieves the private key chain from the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key chain of PKCS12, as PrivateKeyChain object. */ getPrivateKeyChain: function(passwordB64) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve private key safe bags from PKCS12. var privateKeyChain = new box.stores.pkcs12.PrivateKeyChain(); for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { var alias = safeBag.attributes.friendlyName[0]; var privateKeyPem = forge.pki.privateKeyToPem(safeBag.key); privateKeyChain.add(alias, privateKeyPem); } } } // Check whether any private key safe bags were found. if (privateKeyChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any private key safe bags in PKCS12.'); } return privateKeyChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve private key chain from PKCS12.', error); } }, /** * Retrieves the certificate chain of the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Certificate chain of PKCS12, as array of strings in PEM * format. */ getCertificateChain: function(passwordB64) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve certificate safe bags from PKCS12. var certificatePemChain = []; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag) { var certificatePem = forge.pki.certificateToPem(safeBag.cert); certificatePemChain.push(certificatePem); } } } // Check whether any certificate safe bags were found. if (certificatePemChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any certificate safe bags in PKCS12.'); } return certificatePemChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate chain from PKCS12.', error); } }, /** * Retrieves the certificate chain of the PKCS12 filtered by alias. * * @param passwordB64 * Password as string in Base64 encoded format. * @param alias * Alias to filter * @return Certificate chain of PKCS12, as array of strings in PEM * format. */ getCertificateChainByAlias: function(passwordB64, alias) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); var discardedBags = {}; var nextBagId = null; // Retrieve certificate safe bags from PKCS12. var certificatePemChain = []; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag) { var bagFound = null; // if not initializated the chain, find the one with alias // or if is the one that goes next into the list take it if ((!nextBagId && safeBag.attributes && safeBag.attributes.friendlyName && safeBag.attributes.friendlyName[0] === alias) || (nextBagId && nextBagId === safeBag.cert.subject.hash)) { bagFound = safeBag; } // otherwise, if not initializated and it's not the alias, save it // for later use else { discardedBags[safeBag.cert.subject.hash] = safeBag; } // add the bag and check if we have the next one in the list we // had while (bagFound) { // transform the current to PEM and save it var certificatePem = forge.pki.certificateToPem(bagFound.cert); certificatePemChain.push(certificatePem); // we arrived to the root so we are done! if (bagFound.cert.subject.hash === bagFound.cert.issuer.hash) { return certificatePemChain; } // save which is the next bag we need nextBagId = bagFound.cert.issuer.hash; // go for the next in the list of discarded ones bagFound = discardedBags[nextBagId]; } } } } // Check whether any certificate safe bags were found. if (certificatePemChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any certificates for that alias.'); } return certificatePemChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate chain from PKCS12.', error); } }, /** * Retrieves an issuer certificate from the certificate chain of the * PKCS12, given the issuer common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param issuerCn * Issuer common name of certificate to retrieve, as string. * @return Certificate with given issuer common name, as string in PEM * format. */ getCertificateByIssuer: function(passwordB64, issuerCn) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve certificate from PKCS12. var certificate = null; var certificateFound = false; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.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.issuer.getField('CN').value === issuerCn) { certificate = safeBag.cert; certificateFound = true; break; } } if (certificateFound) { break; } } // Check whether certificate was found. if (certificate === null) { throw new exceptions.CryptoLibException( 'Could not find certificate in PKCS12 with issuer common name ' + issuerCn); } return forge.pki.certificateToPem(certificate); } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate with issuer common name ' + issuerCn + ' from PKCS12.', error); } }, /** * Retrieves the subject certificate from the certificate chain of the * PKCS12, given the subject common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param subjectCn * Subject common name of certificate to retrieve, as string. * @return Certificate with given issuer common name, as string in PEM * format. */ getCertificateBySubject: function(passwordB64, subjectCn) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve subject certificate from PKCS12. var certificate = null; var certificateFound = false; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.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) { certificate = safeBag.cert; certificateFound = true; break; } } if (certificateFound) { break; } } // Check whether certificate was found. if (certificate === null) { throw new exceptions.CryptoLibException( 'Could not find certificate in PKCS12 with subject common name ' + subjectCn); } return forge.pki.certificateToPem(certificate); } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate with subject common name ' + subjectCn + ' from PKCS12.', error); } }, /** * Retrieves the PKCS12 as a Base64 encoded DER string. * * @return PKCS12, as Base64 encoded DER string. */ getBase64Encoded: function() { return converters.base64Encode(this.pkcs12Der); } }; /** * Container class for a chain of private keys to be inserted in a key * store. */ box.stores.pkcs12.PrivateKeyChain = function() { this.map = {}; }; box.stores.pkcs12.PrivateKeyChain.prototype = { /** * Adds a private key to the chain. * * @param alias * Alias of private key, as string. * @param privateKeyPem * Private key to add, as string in PEM format. */ add: function(alias, privateKeyPem) { this.map[alias] = privateKeyPem; }, /** * Retrieves a private key from the chain. * * @return Private key chain, as array of strings in PEM format. */ get: function(alias) { return this.map[alias]; } }; };