UNPKG

node-tls

Version:

JavaScript implementations of network transports, cryptography, ciphers, PKI, message digests, and various utilities.

1,622 lines (1,521 loc) 105 kB
/** * Javascript implementation of X.509 and related components (such as * Certification Signing Requests) of a Public Key Infrastructure. * * @author Dave Longley * * Copyright (c) 2010-2014 Digital Bazaar, Inc. * * The ASN.1 representation of an X.509v3 certificate is as follows * (see RFC 2459): * * Certificate ::= SEQUENCE { * tbsCertificate TBSCertificate, * signatureAlgorithm AlgorithmIdentifier, * signatureValue BIT STRING * } * * TBSCertificate ::= SEQUENCE { * version [0] EXPLICIT Version DEFAULT v1, * serialNumber CertificateSerialNumber, * signature AlgorithmIdentifier, * issuer Name, * validity Validity, * subject Name, * subjectPublicKeyInfo SubjectPublicKeyInfo, * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, * -- If present, version shall be v2 or v3 * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, * -- If present, version shall be v2 or v3 * extensions [3] EXPLICIT Extensions OPTIONAL * -- If present, version shall be v3 * } * * Version ::= INTEGER { v1(0), v2(1), v3(2) } * * CertificateSerialNumber ::= INTEGER * * Name ::= CHOICE { * // only one possible choice for now * RDNSequence * } * * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName * * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue * * AttributeTypeAndValue ::= SEQUENCE { * type AttributeType, * value AttributeValue * } * AttributeType ::= OBJECT IDENTIFIER * AttributeValue ::= ANY DEFINED BY AttributeType * * Validity ::= SEQUENCE { * notBefore Time, * notAfter Time * } * * Time ::= CHOICE { * utcTime UTCTime, * generalTime GeneralizedTime * } * * UniqueIdentifier ::= BIT STRING * * SubjectPublicKeyInfo ::= SEQUENCE { * algorithm AlgorithmIdentifier, * subjectPublicKey BIT STRING * } * * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension * * Extension ::= SEQUENCE { * extnID OBJECT IDENTIFIER, * critical BOOLEAN DEFAULT FALSE, * extnValue OCTET STRING * } * * The only key algorithm currently supported for PKI is RSA. * * RSASSA-PSS signatures are described in RFC 3447 and RFC 4055. * * PKCS#10 v1.7 describes certificate signing requests: * * CertificationRequestInfo: * * CertificationRequestInfo ::= SEQUENCE { * version INTEGER { v1(0) } (v1,...), * subject Name, * subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, * attributes [0] Attributes{{ CRIAttributes }} * } * * Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }} * * CRIAttributes ATTRIBUTE ::= { * ... -- add any locally defined attributes here -- } * * Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { * type ATTRIBUTE.&id({IOSet}), * values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) * } * * CertificationRequest ::= SEQUENCE { * certificationRequestInfo CertificationRequestInfo, * signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, * signature BIT STRING * } */ var forge = require('./forge'); require('./aes'); require('./asn1'); require('./des'); require('./md'); require('./mgf'); require('./oids'); require('./pem'); require('./pss'); require('./rsa'); require('./util'); // shortcut for asn.1 API var asn1 = forge.asn1; /* Public Key Infrastructure (PKI) implementation. */ var pki = module.exports = forge.pki = forge.pki || {}; var oids = pki.oids; // short name OID mappings var _shortNames = {}; _shortNames['CN'] = oids['commonName']; _shortNames['commonName'] = 'CN'; _shortNames['C'] = oids['countryName']; _shortNames['countryName'] = 'C'; _shortNames['L'] = oids['localityName']; _shortNames['localityName'] = 'L'; _shortNames['ST'] = oids['stateOrProvinceName']; _shortNames['stateOrProvinceName'] = 'ST'; _shortNames['O'] = oids['organizationName']; _shortNames['organizationName'] = 'O'; _shortNames['OU'] = oids['organizationalUnitName']; _shortNames['organizationalUnitName'] = 'OU'; _shortNames['E'] = oids['emailAddress']; _shortNames['emailAddress'] = 'E'; // validator for an SubjectPublicKeyInfo structure // Note: Currently only works with an RSA public key var publicKeyValidator = forge.pki.rsa.publicKeyValidator; // validator for an X.509v3 certificate var x509CertificateValidator = { name: 'Certificate', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'Certificate.TBSCertificate', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'tbsCertificate', value: [{ name: 'Certificate.TBSCertificate.version', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 0, constructed: true, optional: true, value: [{ name: 'Certificate.TBSCertificate.version.integer', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.INTEGER, constructed: false, capture: 'certVersion' }] }, { name: 'Certificate.TBSCertificate.serialNumber', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.INTEGER, constructed: false, capture: 'certSerialNumber' }, { name: 'Certificate.TBSCertificate.signature', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'Certificate.TBSCertificate.signature.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'certinfoSignatureOid' }, { name: 'Certificate.TBSCertificate.signature.parameters', tagClass: asn1.Class.UNIVERSAL, optional: true, captureAsn1: 'certinfoSignatureParams' }] }, { name: 'Certificate.TBSCertificate.issuer', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'certIssuer' }, { name: 'Certificate.TBSCertificate.validity', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, // Note: UTC and generalized times may both appear so the capture // names are based on their detected order, the names used below // are only for the common case, which validity time really means // "notBefore" and which means "notAfter" will be determined by order value: [{ // notBefore (Time) (UTC time case) name: 'Certificate.TBSCertificate.validity.notBefore (utc)', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.UTCTIME, constructed: false, optional: true, capture: 'certValidity1UTCTime' }, { // notBefore (Time) (generalized time case) name: 'Certificate.TBSCertificate.validity.notBefore (generalized)', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.GENERALIZEDTIME, constructed: false, optional: true, capture: 'certValidity2GeneralizedTime' }, { // notAfter (Time) (only UTC time is supported) name: 'Certificate.TBSCertificate.validity.notAfter (utc)', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.UTCTIME, constructed: false, optional: true, capture: 'certValidity3UTCTime' }, { // notAfter (Time) (only UTC time is supported) name: 'Certificate.TBSCertificate.validity.notAfter (generalized)', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.GENERALIZEDTIME, constructed: false, optional: true, capture: 'certValidity4GeneralizedTime' }] }, { // Name (subject) (RDNSequence) name: 'Certificate.TBSCertificate.subject', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'certSubject' }, // SubjectPublicKeyInfo publicKeyValidator, { // issuerUniqueID (optional) name: 'Certificate.TBSCertificate.issuerUniqueID', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 1, constructed: true, optional: true, value: [{ name: 'Certificate.TBSCertificate.issuerUniqueID.id', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.BITSTRING, constructed: false, // TODO: support arbitrary bit length ids captureBitStringValue: 'certIssuerUniqueId' }] }, { // subjectUniqueID (optional) name: 'Certificate.TBSCertificate.subjectUniqueID', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 2, constructed: true, optional: true, value: [{ name: 'Certificate.TBSCertificate.subjectUniqueID.id', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.BITSTRING, constructed: false, // TODO: support arbitrary bit length ids captureBitStringValue: 'certSubjectUniqueId' }] }, { // Extensions (optional) name: 'Certificate.TBSCertificate.extensions', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 3, constructed: true, captureAsn1: 'certExtensions', optional: true }] }, { // AlgorithmIdentifier (signature algorithm) name: 'Certificate.signatureAlgorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ // algorithm name: 'Certificate.signatureAlgorithm.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'certSignatureOid' }, { name: 'Certificate.TBSCertificate.signature.parameters', tagClass: asn1.Class.UNIVERSAL, optional: true, captureAsn1: 'certSignatureParams' }] }, { // SignatureValue name: 'Certificate.signatureValue', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.BITSTRING, constructed: false, captureBitStringValue: 'certSignature' }] }; var rsassaPssParameterValidator = { name: 'rsapss', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'rsapss.hashAlgorithm', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 0, constructed: true, value: [{ name: 'rsapss.hashAlgorithm.AlgorithmIdentifier', tagClass: asn1.Class.UNIVERSAL, type: asn1.Class.SEQUENCE, constructed: true, optional: true, value: [{ name: 'rsapss.hashAlgorithm.AlgorithmIdentifier.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'hashOid' /* parameter block omitted, for SHA1 NULL anyhow. */ }] }] }, { name: 'rsapss.maskGenAlgorithm', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 1, constructed: true, value: [{ name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier', tagClass: asn1.Class.UNIVERSAL, type: asn1.Class.SEQUENCE, constructed: true, optional: true, value: [{ name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'maskGenOid' }, { name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'rsapss.maskGenAlgorithm.AlgorithmIdentifier.params.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'maskGenHashOid' /* parameter block omitted, for SHA1 NULL anyhow. */ }] }] }] }, { name: 'rsapss.saltLength', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 2, optional: true, value: [{ name: 'rsapss.saltLength.saltLength', tagClass: asn1.Class.UNIVERSAL, type: asn1.Class.INTEGER, constructed: false, capture: 'saltLength' }] }, { name: 'rsapss.trailerField', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 3, optional: true, value: [{ name: 'rsapss.trailer.trailer', tagClass: asn1.Class.UNIVERSAL, type: asn1.Class.INTEGER, constructed: false, capture: 'trailer' }] }] }; // validator for a CertificationRequestInfo structure var certificationRequestInfoValidator = { name: 'CertificationRequestInfo', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'certificationRequestInfo', value: [{ name: 'CertificationRequestInfo.integer', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.INTEGER, constructed: false, capture: 'certificationRequestInfoVersion' }, { // Name (subject) (RDNSequence) name: 'CertificationRequestInfo.subject', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'certificationRequestInfoSubject' }, // SubjectPublicKeyInfo publicKeyValidator, { name: 'CertificationRequestInfo.attributes', tagClass: asn1.Class.CONTEXT_SPECIFIC, type: 0, constructed: true, optional: true, capture: 'certificationRequestInfoAttributes', value: [{ name: 'CertificationRequestInfo.attributes', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ name: 'CertificationRequestInfo.attributes.type', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false }, { name: 'CertificationRequestInfo.attributes.value', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SET, constructed: true }] }] }] }; // validator for a CertificationRequest structure var certificationRequestValidator = { name: 'CertificationRequest', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, captureAsn1: 'csr', value: [ certificationRequestInfoValidator, { // AlgorithmIdentifier (signature algorithm) name: 'CertificationRequest.signatureAlgorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.SEQUENCE, constructed: true, value: [{ // algorithm name: 'CertificationRequest.signatureAlgorithm.algorithm', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.OID, constructed: false, capture: 'csrSignatureOid' }, { name: 'CertificationRequest.signatureAlgorithm.parameters', tagClass: asn1.Class.UNIVERSAL, optional: true, captureAsn1: 'csrSignatureParams' }] }, { // signature name: 'CertificationRequest.signature', tagClass: asn1.Class.UNIVERSAL, type: asn1.Type.BITSTRING, constructed: false, captureBitStringValue: 'csrSignature' } ] }; /** * Converts an RDNSequence of ASN.1 DER-encoded RelativeDistinguishedName * sets into an array with objects that have type and value properties. * * @param rdn the RDNSequence to convert. * @param md a message digest to append type and value to if provided. */ pki.RDNAttributesAsArray = function(rdn, md) { var rval = []; // each value in 'rdn' in is a SET of RelativeDistinguishedName var set, attr, obj; for(var si = 0; si < rdn.value.length; ++si) { // get the RelativeDistinguishedName set set = rdn.value[si]; // each value in the SET is an AttributeTypeAndValue sequence // containing first a type (an OID) and second a value (defined by // the OID) for(var i = 0; i < set.value.length; ++i) { obj = {}; attr = set.value[i]; obj.type = asn1.derToOid(attr.value[0].value); obj.value = attr.value[1].value; obj.valueTagClass = attr.value[1].type; // if the OID is known, get its name and short name if(obj.type in oids) { obj.name = oids[obj.type]; if(obj.name in _shortNames) { obj.shortName = _shortNames[obj.name]; } } if(md) { md.update(obj.type); md.update(obj.value); } rval.push(obj); } } return rval; }; /** * Converts ASN.1 CRIAttributes into an array with objects that have type and * value properties. * * @param attributes the CRIAttributes to convert. */ pki.CRIAttributesAsArray = function(attributes) { var rval = []; // each value in 'attributes' in is a SEQUENCE with an OID and a SET for(var si = 0; si < attributes.length; ++si) { // get the attribute sequence var seq = attributes[si]; // each value in the SEQUENCE containing first a type (an OID) and // second a set of values (defined by the OID) var type = asn1.derToOid(seq.value[0].value); var values = seq.value[1].value; for(var vi = 0; vi < values.length; ++vi) { var obj = {}; obj.type = type; obj.value = values[vi].value; obj.valueTagClass = values[vi].type; // if the OID is known, get its name and short name if(obj.type in oids) { obj.name = oids[obj.type]; if(obj.name in _shortNames) { obj.shortName = _shortNames[obj.name]; } } // parse extensions if(obj.type === oids.extensionRequest) { obj.extensions = []; for(var ei = 0; ei < obj.value.length; ++ei) { obj.extensions.push(pki.certificateExtensionFromAsn1(obj.value[ei])); } } rval.push(obj); } } return rval; }; /** * Gets an issuer or subject attribute from its name, type, or short name. * * @param obj the issuer or subject object. * @param options a short name string or an object with: * shortName the short name for the attribute. * name the name for the attribute. * type the type for the attribute. * * @return the attribute. */ function _getAttribute(obj, options) { if(typeof options === 'string') { options = {shortName: options}; } var rval = null; var attr; for(var i = 0; rval === null && i < obj.attributes.length; ++i) { attr = obj.attributes[i]; if(options.type && options.type === attr.type) { rval = attr; } else if(options.name && options.name === attr.name) { rval = attr; } else if(options.shortName && options.shortName === attr.shortName) { rval = attr; } } return rval; } /** * Converts signature parameters from ASN.1 structure. * * Currently only RSASSA-PSS supported. The PKCS#1 v1.5 signature scheme had * no parameters. * * RSASSA-PSS-params ::= SEQUENCE { * hashAlgorithm [0] HashAlgorithm DEFAULT * sha1Identifier, * maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT * mgf1SHA1Identifier, * saltLength [2] INTEGER DEFAULT 20, * trailerField [3] INTEGER DEFAULT 1 * } * * HashAlgorithm ::= AlgorithmIdentifier * * MaskGenAlgorithm ::= AlgorithmIdentifier * * AlgorithmIdentifer ::= SEQUENCE { * algorithm OBJECT IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL * } * * @param oid The OID specifying the signature algorithm * @param obj The ASN.1 structure holding the parameters * @param fillDefaults Whether to use return default values where omitted * @return signature parameter object */ var _readSignatureParameters = function(oid, obj, fillDefaults) { var params = {}; if(oid !== oids['RSASSA-PSS']) { return params; } if(fillDefaults) { params = { hash: { algorithmOid: oids['sha1'] }, mgf: { algorithmOid: oids['mgf1'], hash: { algorithmOid: oids['sha1'] } }, saltLength: 20 }; } var capture = {}; var errors = []; if(!asn1.validate(obj, rsassaPssParameterValidator, capture, errors)) { var error = new Error('Cannot read RSASSA-PSS parameter block.'); error.errors = errors; throw error; } if(capture.hashOid !== undefined) { params.hash = params.hash || {}; params.hash.algorithmOid = asn1.derToOid(capture.hashOid); } if(capture.maskGenOid !== undefined) { params.mgf = params.mgf || {}; params.mgf.algorithmOid = asn1.derToOid(capture.maskGenOid); params.mgf.hash = params.mgf.hash || {}; params.mgf.hash.algorithmOid = asn1.derToOid(capture.maskGenHashOid); } if(capture.saltLength !== undefined) { params.saltLength = capture.saltLength.charCodeAt(0); } return params; }; /** * Converts an X.509 certificate from PEM format. * * Note: If the certificate is to be verified then compute hash should * be set to true. This will scan the TBSCertificate part of the ASN.1 * object while it is converted so it doesn't need to be converted back * to ASN.1-DER-encoding later. * * @param pem the PEM-formatted certificate. * @param computeHash true to compute the hash for verification. * @param strict true to be strict when checking ASN.1 value lengths, false to * allow truncated values (default: true). * * @return the certificate. */ pki.certificateFromPem = function(pem, computeHash, strict) { var msg = forge.pem.decode(pem)[0]; if(msg.type !== 'CERTIFICATE' && msg.type !== 'X509 CERTIFICATE' && msg.type !== 'TRUSTED CERTIFICATE') { var error = new Error( 'Could not convert certificate from PEM; PEM header type ' + 'is not "CERTIFICATE", "X509 CERTIFICATE", or "TRUSTED CERTIFICATE".'); error.headerType = msg.type; throw error; } if(msg.procType && msg.procType.type === 'ENCRYPTED') { throw new Error( 'Could not convert certificate from PEM; PEM is encrypted.'); } // convert DER to ASN.1 object var obj = asn1.fromDer(msg.body, strict); return pki.certificateFromAsn1(obj, computeHash); }; /** * Converts an X.509 certificate to PEM format. * * @param cert the certificate. * @param maxline the maximum characters per line, defaults to 64. * * @return the PEM-formatted certificate. */ pki.certificateToPem = function(cert, maxline) { // convert to ASN.1, then DER, then PEM-encode var msg = { type: 'CERTIFICATE', body: asn1.toDer(pki.certificateToAsn1(cert)).getBytes() }; return forge.pem.encode(msg, {maxline: maxline}); }; /** * Converts an RSA public key from PEM format. * * @param pem the PEM-formatted public key. * * @return the public key. */ pki.publicKeyFromPem = function(pem) { var msg = forge.pem.decode(pem)[0]; if(msg.type !== 'PUBLIC KEY' && msg.type !== 'RSA PUBLIC KEY') { var error = new Error('Could not convert public key from PEM; PEM header ' + 'type is not "PUBLIC KEY" or "RSA PUBLIC KEY".'); error.headerType = msg.type; throw error; } if(msg.procType && msg.procType.type === 'ENCRYPTED') { throw new Error('Could not convert public key from PEM; PEM is encrypted.'); } // convert DER to ASN.1 object var obj = asn1.fromDer(msg.body); return pki.publicKeyFromAsn1(obj); }; /** * Converts an RSA public key to PEM format (using a SubjectPublicKeyInfo). * * @param key the public key. * @param maxline the maximum characters per line, defaults to 64. * * @return the PEM-formatted public key. */ pki.publicKeyToPem = function(key, maxline) { // convert to ASN.1, then DER, then PEM-encode var msg = { type: 'PUBLIC KEY', body: asn1.toDer(pki.publicKeyToAsn1(key)).getBytes() }; return forge.pem.encode(msg, {maxline: maxline}); }; /** * Converts an RSA public key to PEM format (using an RSAPublicKey). * * @param key the public key. * @param maxline the maximum characters per line, defaults to 64. * * @return the PEM-formatted public key. */ pki.publicKeyToRSAPublicKeyPem = function(key, maxline) { // convert to ASN.1, then DER, then PEM-encode var msg = { type: 'RSA PUBLIC KEY', body: asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes() }; return forge.pem.encode(msg, {maxline: maxline}); }; /** * Gets a fingerprint for the given public key. * * @param options the options to use. * [md] the message digest object to use (defaults to forge.md.sha1). * [type] the type of fingerprint, such as 'RSAPublicKey', * 'SubjectPublicKeyInfo' (defaults to 'RSAPublicKey'). * [encoding] an alternative output encoding, such as 'hex' * (defaults to none, outputs a byte buffer). * [delimiter] the delimiter to use between bytes for 'hex' encoded * output, eg: ':' (defaults to none). * * @return the fingerprint as a byte buffer or other encoding based on options. */ pki.getPublicKeyFingerprint = function(key, options) { options = options || {}; var md = options.md || forge.md.sha1.create(); var type = options.type || 'RSAPublicKey'; var bytes; switch(type) { case 'RSAPublicKey': bytes = asn1.toDer(pki.publicKeyToRSAPublicKey(key)).getBytes(); break; case 'SubjectPublicKeyInfo': bytes = asn1.toDer(pki.publicKeyToAsn1(key)).getBytes(); break; default: throw new Error('Unknown fingerprint type "' + options.type + '".'); } // hash public key bytes md.start(); md.update(bytes); var digest = md.digest(); if(options.encoding === 'hex') { var hex = digest.toHex(); if(options.delimiter) { return hex.match(/.{2}/g).join(options.delimiter); } return hex; } else if(options.encoding === 'binary') { return digest.getBytes(); } else if(options.encoding) { throw new Error('Unknown encoding "' + options.encoding + '".'); } return digest; }; /** * Converts a PKCS#10 certification request (CSR) from PEM format. * * Note: If the certification request is to be verified then compute hash * should be set to true. This will scan the CertificationRequestInfo part of * the ASN.1 object while it is converted so it doesn't need to be converted * back to ASN.1-DER-encoding later. * * @param pem the PEM-formatted certificate. * @param computeHash true to compute the hash for verification. * @param strict true to be strict when checking ASN.1 value lengths, false to * allow truncated values (default: true). * * @return the certification request (CSR). */ pki.certificationRequestFromPem = function(pem, computeHash, strict) { var msg = forge.pem.decode(pem)[0]; if(msg.type !== 'CERTIFICATE REQUEST') { var error = new Error('Could not convert certification request from PEM; ' + 'PEM header type is not "CERTIFICATE REQUEST".'); error.headerType = msg.type; throw error; } if(msg.procType && msg.procType.type === 'ENCRYPTED') { throw new Error('Could not convert certification request from PEM; ' + 'PEM is encrypted.'); } // convert DER to ASN.1 object var obj = asn1.fromDer(msg.body, strict); return pki.certificationRequestFromAsn1(obj, computeHash); }; /** * Converts a PKCS#10 certification request (CSR) to PEM format. * * @param csr the certification request. * @param maxline the maximum characters per line, defaults to 64. * * @return the PEM-formatted certification request. */ pki.certificationRequestToPem = function(csr, maxline) { // convert to ASN.1, then DER, then PEM-encode var msg = { type: 'CERTIFICATE REQUEST', body: asn1.toDer(pki.certificationRequestToAsn1(csr)).getBytes() }; return forge.pem.encode(msg, {maxline: maxline}); }; /** * Creates an empty X.509v3 RSA certificate. * * @return the certificate. */ pki.createCertificate = function() { var cert = {}; cert.version = 0x02; cert.serialNumber = '00'; cert.signatureOid = null; cert.signature = null; cert.siginfo = {}; cert.siginfo.algorithmOid = null; cert.validity = {}; cert.validity.notBefore = new Date(); cert.validity.notAfter = new Date(); cert.issuer = {}; cert.issuer.getField = function(sn) { return _getAttribute(cert.issuer, sn); }; cert.issuer.addField = function(attr) { _fillMissingFields([attr]); cert.issuer.attributes.push(attr); }; cert.issuer.attributes = []; cert.issuer.hash = null; cert.subject = {}; cert.subject.getField = function(sn) { return _getAttribute(cert.subject, sn); }; cert.subject.addField = function(attr) { _fillMissingFields([attr]); cert.subject.attributes.push(attr); }; cert.subject.attributes = []; cert.subject.hash = null; cert.extensions = []; cert.publicKey = null; cert.md = null; /** * Sets the subject of this certificate. * * @param attrs the array of subject attributes to use. * @param uniqueId an optional a unique ID to use. */ cert.setSubject = function(attrs, uniqueId) { // set new attributes, clear hash _fillMissingFields(attrs); cert.subject.attributes = attrs; delete cert.subject.uniqueId; if(uniqueId) { // TODO: support arbitrary bit length ids cert.subject.uniqueId = uniqueId; } cert.subject.hash = null; }; /** * Sets the issuer of this certificate. * * @param attrs the array of issuer attributes to use. * @param uniqueId an optional a unique ID to use. */ cert.setIssuer = function(attrs, uniqueId) { // set new attributes, clear hash _fillMissingFields(attrs); cert.issuer.attributes = attrs; delete cert.issuer.uniqueId; if(uniqueId) { // TODO: support arbitrary bit length ids cert.issuer.uniqueId = uniqueId; } cert.issuer.hash = null; }; /** * Sets the extensions of this certificate. * * @param exts the array of extensions to use. */ cert.setExtensions = function(exts) { for(var i = 0; i < exts.length; ++i) { _fillMissingExtensionFields(exts[i], {cert: cert}); } // set new extensions cert.extensions = exts; }; /** * Gets an extension by its name or id. * * @param options the name to use or an object with: * name the name to use. * id the id to use. * * @return the extension or null if not found. */ cert.getExtension = function(options) { if(typeof options === 'string') { options = {name: options}; } var rval = null; var ext; for(var i = 0; rval === null && i < cert.extensions.length; ++i) { ext = cert.extensions[i]; if(options.id && ext.id === options.id) { rval = ext; } else if(options.name && ext.name === options.name) { rval = ext; } } return rval; }; /** * Signs this certificate using the given private key. * * @param key the private key to sign with. * @param md the message digest object to use (defaults to forge.md.sha1). */ cert.sign = function(key, md) { // TODO: get signature OID from private key cert.md = md || forge.md.sha1.create(); var algorithmOid = oids[cert.md.algorithm + 'WithRSAEncryption']; if(!algorithmOid) { var error = new Error('Could not compute certificate digest. ' + 'Unknown message digest algorithm OID.'); error.algorithm = cert.md.algorithm; throw error; } cert.signatureOid = cert.siginfo.algorithmOid = algorithmOid; // get TBSCertificate, convert to DER cert.tbsCertificate = pki.getTBSCertificate(cert); var bytes = asn1.toDer(cert.tbsCertificate); // digest and sign cert.md.update(bytes.getBytes()); cert.signature = key.sign(cert.md); }; /** * Attempts verify the signature on the passed certificate using this * certificate's public key. * * @param child the certificate to verify. * * @return true if verified, false if not. */ cert.verify = function(child) { var rval = false; if(!cert.issued(child)) { var issuer = child.issuer; var subject = cert.subject; var error = new Error( 'The parent certificate did not issue the given child ' + 'certificate; the child certificate\'s issuer does not match the ' + 'parent\'s subject.'); error.expectedIssuer = issuer.attributes; error.actualIssuer = subject.attributes; throw error; } var md = child.md; if(md === null) { // check signature OID for supported signature types if(child.signatureOid in oids) { var oid = oids[child.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': md = forge.md.md5.create(); break; case 'sha256WithRSAEncryption': md = forge.md.sha256.create(); break; case 'sha384WithRSAEncryption': md = forge.md.sha384.create(); break; case 'sha512WithRSAEncryption': md = forge.md.sha512.create(); break; case 'RSASSA-PSS': md = forge.md.sha256.create(); break; } } if(md === null) { var error = new Error('Could not compute certificate digest. ' + 'Unknown signature OID.'); error.signatureOid = child.signatureOid; throw error; } // produce DER formatted TBSCertificate and digest it var tbsCertificate = child.tbsCertificate || pki.getTBSCertificate(child); var bytes = asn1.toDer(tbsCertificate); md.update(bytes.getBytes()); } if(md !== null) { var scheme; switch(child.signatureOid) { case oids.sha1WithRSAEncryption: scheme = undefined; /* use PKCS#1 v1.5 padding scheme */ break; case oids['RSASSA-PSS']: var hash, mgf; /* initialize mgf */ hash = oids[child.signatureParameters.mgf.hash.algorithmOid]; if(hash === undefined || forge.md[hash] === undefined) { var error = new Error('Unsupported MGF hash function.'); error.oid = child.signatureParameters.mgf.hash.algorithmOid; error.name = hash; throw error; } mgf = oids[child.signatureParameters.mgf.algorithmOid]; if(mgf === undefined || forge.mgf[mgf] === undefined) { var error = new Error('Unsupported MGF function.'); error.oid = child.signatureParameters.mgf.algorithmOid; error.name = mgf; throw error; } mgf = forge.mgf[mgf].create(forge.md[hash].create()); /* initialize hash function */ hash = oids[child.signatureParameters.hash.algorithmOid]; if(hash === undefined || forge.md[hash] === undefined) { throw { message: 'Unsupported RSASSA-PSS hash function.', oid: child.signatureParameters.hash.algorithmOid, name: hash }; } scheme = forge.pss.create(forge.md[hash].create(), mgf, child.signatureParameters.saltLength); break; } // verify signature on cert using public key rval = cert.publicKey.verify( md.digest().getBytes(), child.signature, scheme); } return rval; }; /** * Returns true if this certificate's issuer matches the passed * certificate's subject. Note that no signature check is performed. * * @param parent the certificate to check. * * @return true if this certificate's issuer matches the passed certificate's * subject. */ cert.isIssuer = function(parent) { var rval = false; var i = cert.issuer; var s = parent.subject; // compare hashes if present if(i.hash && s.hash) { rval = (i.hash === s.hash); } else if(i.attributes.length === s.attributes.length) { // all attributes are the same so issuer matches subject rval = true; var iattr, sattr; for(var n = 0; rval && n < i.attributes.length; ++n) { iattr = i.attributes[n]; sattr = s.attributes[n]; if(iattr.type !== sattr.type || iattr.value !== sattr.value) { // attribute mismatch rval = false; } } } return rval; }; /** * Returns true if this certificate's subject matches the issuer of the * given certificate). Note that not signature check is performed. * * @param child the certificate to check. * * @return true if this certificate's subject matches the passed * certificate's issuer. */ cert.issued = function(child) { return child.isIssuer(cert); }; /** * Generates the subjectKeyIdentifier for this certificate as byte buffer. * * @return the subjectKeyIdentifier for this certificate as byte buffer. */ cert.generateSubjectKeyIdentifier = function() { /* See: 4.2.1.2 section of the the RFC3280, keyIdentifier is either: (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey (excluding the tag, length, and number of unused bits). (2) The keyIdentifier is composed of a four bit type field with the value 0100 followed by the least significant 60 bits of the SHA-1 hash of the value of the BIT STRING subjectPublicKey (excluding the tag, length, and number of unused bit string bits). */ // skipping the tag, length, and number of unused bits is the same // as just using the RSAPublicKey (for RSA keys, which are the // only ones supported) return pki.getPublicKeyFingerprint(cert.publicKey, {type: 'RSAPublicKey'}); }; /** * Verifies the subjectKeyIdentifier extension value for this certificate * against its public key. If no extension is found, false will be * returned. * * @return true if verified, false if not. */ cert.verifySubjectKeyIdentifier = function() { var oid = oids['subjectKeyIdentifier']; for(var i = 0; i < cert.extensions.length; ++i) { var ext = cert.extensions[i]; if(ext.id === oid) { var ski = cert.generateSubjectKeyIdentifier().getBytes(); return (forge.util.hexToBytes(ext.subjectKeyIdentifier) === ski); } } return false; }; return cert; }; /** * Converts an X.509v3 RSA certificate from an ASN.1 object. * * Note: If the certificate is to be verified then compute hash should * be set to true. There is currently no implementation for converting * a certificate back to ASN.1 so the TBSCertificate part of the ASN.1 * object needs to be scanned before the cert object is created. * * @param obj the asn1 representation of an X.509v3 RSA certificate. * @param computeHash true to compute the hash for verification. * * @return the certificate. */ pki.certificateFromAsn1 = function(obj, computeHash) { // validate certificate and capture data var capture = {}; var errors = []; if(!asn1.validate(obj, x509CertificateValidator, capture, errors)) { var error = new Error('Cannot read X.509 certificate. ' + 'ASN.1 object is not an X509v3 Certificate.'); error.errors = errors; throw error; } // get oid var oid = asn1.derToOid(capture.publicKeyOid); if(oid !== pki.oids.rsaEncryption) { throw new Error('Cannot read public key. OID is not RSA.'); } // create certificate var cert = pki.createCertificate(); cert.version = capture.certVersion ? capture.certVersion.charCodeAt(0) : 0; var serial = forge.util.createBuffer(capture.certSerialNumber); cert.serialNumber = serial.toHex(); cert.signatureOid = forge.asn1.derToOid(capture.certSignatureOid); cert.signatureParameters = _readSignatureParameters( cert.signatureOid, capture.certSignatureParams, true); cert.siginfo.algorithmOid = forge.asn1.derToOid(capture.certinfoSignatureOid); cert.siginfo.parameters = _readSignatureParameters(cert.siginfo.algorithmOid, capture.certinfoSignatureParams, false); cert.signature = capture.certSignature; var validity = []; if(capture.certValidity1UTCTime !== undefined) { validity.push(asn1.utcTimeToDate(capture.certValidity1UTCTime)); } if(capture.certValidity2GeneralizedTime !== undefined) { validity.push(asn1.generalizedTimeToDate( capture.certValidity2GeneralizedTime)); } if(capture.certValidity3UTCTime !== undefined) { validity.push(asn1.utcTimeToDate(capture.certValidity3UTCTime)); } if(capture.certValidity4GeneralizedTime !== undefined) { validity.push(asn1.generalizedTimeToDate( capture.certValidity4GeneralizedTime)); } if(validity.length > 2) { throw new Error('Cannot read notBefore/notAfter validity times; more ' + 'than two times were provided in the certificate.'); } if(validity.length < 2) { throw new Error('Cannot read notBefore/notAfter validity times; they ' + 'were not provided as either UTCTime or GeneralizedTime.'); } cert.validity.notBefore = validity[0]; cert.validity.notAfter = validity[1]; // keep TBSCertificate to preserve signature when exporting cert.tbsCertificate = capture.tbsCertificate; if(computeHash) { // check signature OID for supported signature types cert.md = null; if(cert.signatureOid in oids) { var oid = oids[cert.signatureOid]; switch(oid) { case 'sha1WithRSAEncryption': cert.md = forge.md.sha1.create(); break; case 'md5WithRSAEncryption': cert.md = forge.md.md5.create(); break; case 'sha256WithRSAEncryption': cert.md = forge.md.sha256.create(); break; case 'sha384WithRSAEncryption': cert.md = forge.md.sha384.create(); break; case 'sha512WithRSAEncryption': cert.md = forge.md.sha512.create(); break; case 'RSASSA-PSS': cert.md = forge.md.sha256.create(); break; } } if(cert.md === null) { var error = new Error('Could not compute certificate digest. ' + 'Unknown signature OID.'); error.signatureOid = cert.signatureOid; throw error; } // produce DER formatted TBSCertificate and digest it var bytes = asn1.toDer(cert.tbsCertificate); cert.md.update(bytes.getBytes()); } // handle issuer, build issuer message digest var imd = forge.md.sha1.create(); cert.issuer.getField = function(sn) { return _getAttribute(cert.issuer, sn); }; cert.issuer.addField = function(attr) { _fillMissingFields([attr]); cert.issuer.attributes.push(attr); }; cert.issuer.attributes = pki.RDNAttributesAsArray(capture.certIssuer, imd); if(capture.certIssuerUniqueId) { cert.issuer.uniqueId = capture.certIssuerUniqueId; } cert.issuer.hash = imd.digest().toHex(); // handle subject, build subject message digest var smd = forge.md.sha1.create(); cert.subject.getField = function(sn) { return _getAttribute(cert.subject, sn); }; cert.subject.addField = function(attr) { _fillMissingFields([attr]); cert.subject.attributes.push(attr); }; cert.subject.attributes = pki.RDNAttributesAsArray(capture.certSubject, smd); if(capture.certSubjectUniqueId) { cert.subject.uniqueId = capture.certSubjectUniqueId; } cert.subject.hash = smd.digest().toHex(); // handle extensions if(capture.certExtensions) { cert.extensions = pki.certificateExtensionsFromAsn1(capture.certExtensions); } else { cert.extensions = []; } // convert RSA public key from ASN.1 cert.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo); return cert; }; /** * Converts an ASN.1 extensions object (with extension sequences as its * values) into an array of extension objects with types and values. * * Supported extensions: * * id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 } * KeyUsage ::= BIT STRING { * digitalSignature (0), * nonRepudiation (1), * keyEncipherment (2), * dataEncipherment (3), * keyAgreement (4), * keyCertSign (5), * cRLSign (6), * encipherOnly (7), * decipherOnly (8) * } * * id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 } * BasicConstraints ::= SEQUENCE { * cA BOOLEAN DEFAULT FALSE, * pathLenConstraint INTEGER (0..MAX) OPTIONAL * } * * subjectAltName EXTENSION ::= { * SYNTAX GeneralNames * IDENTIFIED BY id-ce-subjectAltName * } * * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName * * GeneralName ::= CHOICE { * otherName [0] INSTANCE OF OTHER-NAME, * rfc822Name [1] IA5String, * dNSName [2] IA5String, * x400Address [3] ORAddress, * directoryName [4] Name, * ediPartyName [5] EDIPartyName, * uniformResourceIdentifier [6] IA5String, * IPAddress [7] OCTET STRING, * registeredID [8] OBJECT IDENTIFIER * } * * OTHER-NAME ::= TYPE-IDENTIFIER * * EDIPartyName ::= SEQUENCE { * nameAssigner [0] DirectoryString {ub-name} OPTIONAL, * partyName [1] DirectoryString {ub-name} * } * * @param exts the extensions ASN.1 with extension sequences to parse. * * @return the array. */ pki.certificateExtensionsFromAsn1 = function(exts) { var rval = []; for(var i = 0; i < exts.value.length; ++i) { // get extension sequence var extseq = exts.value[i]; for(var ei = 0; ei < extseq.value.length; ++ei) { rval.push(pki.certificateExtensionFromAsn1(extseq.value[ei])); } } return rval; }; /** * Parses a single certificate extension from ASN.1. * * @param ext the extension in ASN.1 format. * * @return the parsed extension as an object. */ pki.certificateExtensionFromAsn1 = function(ext) { // an extension has: // [0] extnID OBJECT IDENTIFIER // [1] critical BOOLEAN DEFAULT FALSE // [2] extnValue OCTET STRING var e = {}; e.id = asn1.derToOid(ext.value[0].value); e.critical = false; if(ext.value[1].type === asn1.Type.BOOLEAN) { e.critical = (ext.value[1].value.charCodeAt(0) !== 0x00); e.value = ext.value[2].value; } else { e.value = ext.value[1].value; } // if the oid is known, get its name if(e.id in oids) { e.name = oids[e.id]; // handle key usage if(e.name === 'keyUsage') { // get value as BIT STRING var ev = asn1.fromDer(e.value); var b2 = 0x00; var b3 = 0x00; if(ev.value.length > 1) { // skip first byte, just indicates unused bits which // will be padded with 0s anyway // get bytes with flag bits b2 = ev.value.charCodeAt(1); b3 = ev.value.length > 2 ? ev.value.charCodeAt(2) : 0; } // set flags e.digitalSignature = (b2 & 0x80) === 0x80; e.nonRepudiation = (b2 & 0x40) === 0x40; e.keyEncipherment = (b2 & 0x20) === 0x20; e.dataEncipherment = (b2 & 0x10) === 0x10; e.keyAgreement = (b2 & 0x08) === 0x08; e.keyCertSign = (b2 & 0x04) === 0x04; e.cRLSign = (b2 & 0x02) === 0x02; e.encipherOnly = (b2 & 0x01) === 0x01; e.decipherOnly = (b3 & 0x80) === 0x80; } else if(e.name === 'basicConstraints') { // handle basic constraints // get value as SEQUENCE var ev = asn1.fromDer(e.value); // get cA BOOLEAN flag (defaults to false) if(ev.value.length > 0 && ev.value[0].type === asn1.Type.BOOLEAN) { e.cA = (ev.value[0].value.charCodeAt(0) !== 0x00); } else { e.cA = false; } // get path length constraint var value = null; if(ev.value.length > 0 && ev.value[0].type === asn1.Type.INTEGER) { value = ev.value[0].value; } else if(ev.value.length > 1) { value = ev.value[1].value; } if(value !== null) { e.pathLenConstraint = asn1.derToInteger(value); } } else if(e.name === 'extKeyUsage') { // handle extKeyUsage // value is a SEQUENCE of OIDs var ev = asn1.fromDer(e.value); for(var vi = 0; vi < ev.value.length; ++vi) { var oid = asn1.derToOid(ev.value[vi].value); if(oid in oids) { e[oids[oid]] = true; } else { e[oid] = true; } } } else if(e.name === 'nsCertType') { // handle nsCertType // get value as BIT STRING var ev = asn1.fromDer(e.value); var b2 = 0x00; if(ev.value.length > 1) { // skip first byte, just indicates unused bits which // will be padded with 0s anyway // get bytes with flag bits b2 = ev.value.charCodeAt(1); } // set flags e.client = (b2 & 0x80) === 0x80; e.server = (b2 & 0x40) === 0x40; e.email = (b2 & 0x20) === 0x20; e.objsign = (b2 & 0x10) === 0x10; e.reserved = (b2 & 0x08) === 0x08; e.sslCA = (b2 & 0x04) === 0x04; e.emailCA = (b2 & 0x02) === 0x02; e.objCA = (b2 & 0x01) === 0x01; } else if( e.name === 'subjectAltName' || e.name === 'issuerAltName') { // handle subjectAltName/issuerAltName e.altNames = []; // ev is a SYNTAX SEQUENCE var gn; var ev = asn1.fromDer(e.value); for(var n = 0; n < ev.value.length; ++n) { // get GeneralName gn = ev.value[n]; var altName = { type: gn.type, value: gn.value }; e.altNames.push(altName); // Note: Support for types 1,2,6,7,8 switch(gn.type) { // rfc822Name case 1: // dNSName case 2: // uniformResourceIdentifier (URI) case 6: break; // IPAddress case 7: // convert to IPv4/IPv6 string representation altName.ip = forge.util.bytesToIP(gn.value); break; // registeredID case 8: altName.oid = asn1.derToOid(gn.value); break; default: