diff --git a/lib/pkcs12.js b/lib/pkcs12.js index cd06c494a..7c0eb3434 100644 --- a/lib/pkcs12.js +++ b/lib/pkcs12.js @@ -268,6 +268,14 @@ var certBagValidator = { }] }; +var algoToOidMap = { + sha1: pki.oids.sha1, + sha256: pki.oids.sha256, + sha384: pki.oids.sha384, + sha512: pki.oids.sha512, + md5: pki.oids.md5, +}; + /** * Search SafeContents structure for bags with matching attributes. * @@ -304,6 +312,27 @@ function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) { return result; } +/** + * Converts a PKCS#12 PFX in ASN.1 notation and returns the captured data + * + * If invalid, then it throws error + * + * @param obj The PKCS#12 PFX in ASN.1 notation. + * @returns capture captured data as specified in pfxValidator + */ +p12.validatePfx = function(obj) { + var capture = {}; + var errors = []; + if(!asn1.validate(obj, pfxValidator, capture, errors)) { + var error = new Error('Cannot read PKCS#12 PFX. ' + + 'ASN.1 object is not an PKCS#12 PFX.'); + error.errors = errors; + throw error; + } + + return capture; +}; + /** * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object. * @@ -323,14 +352,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { } // validate PFX and capture data - var capture = {}; - var errors = []; - if(!asn1.validate(obj, pfxValidator, capture, errors)) { - var error = new Error('Cannot read PKCS#12 PFX. ' + - 'ASN.1 object is not an PKCS#12 PFX.'); - error.errors = error; - throw error; - } + var capture = p12.validatePfx(obj); var pfx = { version: capture.version.charCodeAt(0), @@ -433,32 +455,26 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { // check for MAC if(capture.mac) { var md = null; - var macKeyBytes = 0; - var macAlgorithm = asn1.derToOid(capture.macAlgorithm); - switch(macAlgorithm) { - case pki.oids.sha1: - md = forge.md.sha1.create(); - macKeyBytes = 20; - break; - case pki.oids.sha256: - md = forge.md.sha256.create(); - macKeyBytes = 32; - break; - case pki.oids.sha384: - md = forge.md.sha384.create(); - macKeyBytes = 48; - break; - case pki.oids.sha512: - md = forge.md.sha512.create(); - macKeyBytes = 64; - break; - case pki.oids.md5: - md = forge.md.md5.create(); - macKeyBytes = 16; - break; + var macAlgorithmOid = asn1.derToOid(capture.macAlgorithm); + switch(macAlgorithmOid) { + case pki.oids.sha1: + md = forge.md.sha1.create(); + break; + case pki.oids.sha256: + md = forge.md.sha256.create(); + break; + case pki.oids.sha384: + md = forge.md.sha384.create(); + break; + case pki.oids.sha512: + md = forge.md.sha512.create(); + break; + case pki.oids.md5: + md = forge.md.md5.create(); + break; } if(md === null) { - throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm); + throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithmOid); } // verify MAC (iterations default to 1) @@ -466,7 +482,7 @@ p12.pkcs12FromAsn1 = function(obj, strict, password) { var macIterations = (('macIterations' in capture) ? parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1); var macKey = p12.generateKey( - password, macSalt, 3, macIterations, macKeyBytes, md); + password, macSalt, 3, macIterations, md.digestLength, md); var mac = forge.hmac.create(); mac.start(md, macKey); mac.update(data.value); @@ -799,6 +815,15 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) { options.saltSize = options.saltSize || 8; options.count = options.count || 2048; options.algorithm = options.algorithm || options.encAlgorithm || 'aes128'; + + // process macAlgorithm option + options.macAlgorithm = options.macAlgorithm || 'sha1'; + var macAlgorithm = forge.md.algorithms[options.macAlgorithm] || ''; + var macAlgoirthmOid = algoToOidMap[options.macAlgorithm] || ''; + if(!macAlgorithm || !macAlgoirthmOid) { + throw new Error('PKCS#12 unsupported MAC algorithm: ' + options.macAlgorithm); + } + if(!('useMac' in options)) { options.useMac = true; } @@ -820,9 +845,10 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) { if(typeof pairedCert === 'string') { pairedCert = pki.certificateFromPem(pairedCert); } - var sha1 = forge.md.sha1.create(); - sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes()); - localKeyId = sha1.digest().getBytes(); + + var md = macAlgorithm.create(); + md.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes()); + localKeyId = md.digest().getBytes(); } else { // FIXME: consider using SHA-1 of public key (which can be generated // from private key components), see: cert.generateSubjectKeyIdentifier @@ -1000,14 +1026,14 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) { var macData; if(options.useMac) { // MacData - var sha1 = forge.md.sha1.create(); + var md = macAlgorithm.create(); var macSalt = new forge.util.ByteBuffer( forge.random.getBytes(options.saltSize)); var count = options.count; // 160-bit key - var key = p12.generateKey(password, macSalt, 3, count, 20); + var key = p12.generateKey(password, macSalt, 3, count, md.digestLength, md); var mac = forge.hmac.create(); - mac.start(sha1, key); + mac.start(md, key); mac.update(asn1.toDer(safe).getBytes()); var macValue = mac.getMac(); macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ @@ -1017,7 +1043,7 @@ p12.toPkcs12Asn1 = function(key, cert, password, options) { asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ // algorithm = SHA-1 asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, - asn1.oidToDer(pki.oids.sha1).getBytes()), + asn1.oidToDer(macAlgoirthmOid).getBytes()), // parameters = Null asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') ]), diff --git a/tests/unit/pkcs12.js b/tests/unit/pkcs12.js index 746a8c5b1..de1b6a325 100644 --- a/tests/unit/pkcs12.js +++ b/tests/unit/pkcs12.js @@ -9,6 +9,65 @@ var UTIL = require('../../lib/util'); (function() { var _data; describe('pkcs12', function() { + it('should allow macAlgorithm option', function() { + var algoOptions = { + sha1: PKI.oids.sha1, + sha256: PKI.oids.sha256, + sha384: PKI.oids.sha384, + sha512: PKI.oids.sha512, + md5: PKI.oids.md5, + }; + var privateKey = PKI.privateKeyFromPem(_data.privateKey); + + for(var algoName in algoOptions) { + var algoOid = algoOptions[algoName]; + + var algorithm = forge.md.algorithms[algoName] || null; + ASSERT.notEqual(algorithm, null); + + var md = algorithm.create(); + + // generate pkcs12 + var options = { + macAlgorithm: md.algorithm, + saltSize: 20, + count: 10000, + }; + + var p12Asn = PKCS12.toPkcs12Asn1(privateKey, _data.certificate, _data.nopass, options); + + // validate and capture different parts of pkcs12 + var capture = PKCS12.validatePfx(p12Asn); + ASSERT.equal(capture.version.charCodeAt(0), 3); + ASSERT.equal(ASN1.derToOid(capture.contentType), PKI.oids.data); + + // verify mac algorithm related parameters + var macAlgorithmOid = ASN1.derToOid(capture.macAlgorithm); + var macSalt = new forge.util.ByteBuffer(capture.macSalt); + var macIterations = (('macIterations' in capture) ? + parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1); + ASSERT.equal(macAlgorithmOid, algoOid); + ASSERT.equal(macSalt.length(), options.saltSize); + ASSERT.equal(macIterations, options.count); + + // verify mac digest + var data = capture.content.value[0]; + ASSERT.equal(data.tagClass, ASN1.Class.UNIVERSAL); + ASSERT.equal(data.type, ASN1.Type.OCTETSTRING); + var macKey = PKCS12.generateKey(_data.nopass, macSalt, 3, macIterations, md.digestLength, md); + var mac = forge.hmac.create(); + mac.start(md, macKey); + mac.update(data.value); + var macValue = mac.getMac(); + ASSERT.equal(macValue.getBytes(), capture.macDigest); + + // finally verify that PFX file can be parsed successfully + var p12 = PKCS12.pkcs12FromAsn1(p12Asn, true, _data.nopass); + ASSERT.equal(p12.version, 3); + ASSERT.equal(p12.safeContents.length, 2); + } + }); + it('should create certificate-only p12', function() { var p12Asn = PKCS12.toPkcs12Asn1(null, _data.certificate, null, { useMac: false, @@ -290,6 +349,7 @@ var UTIL = require('../../lib/util'); }); _data = { + nopass: 'nopass', certificate: '-----BEGIN CERTIFICATE-----\r\n' + 'MIIDtDCCApwCCQDUVBxA2DXi8zANBgkqhkiG9w0BAQUFADCBmzELMAkGA1UEBhMC\r\n' + 'REUxEjAQBgNVBAgMCUZyYW5jb25pYTEQMA4GA1UEBwwHQW5zYmFjaDEVMBMGA1UE\r\n' +