|
|
- /**
- * 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:
- // unsupported
- }
- }
- } else if(e.name === 'subjectKeyIdentifier') {
- // value is an OCTETSTRING w/the hash of the key-type specific
- // public key structure (eg: RSAPublicKey)
- var ev = asn1.fromDer(e.value);
- e.subjectKeyIdentifier = forge.util.bytesToHex(ev.value);
- }
- }
- return e;
- };
-
- /**
- * Converts a PKCS#10 certification request (CSR) from an ASN.1 object.
- *
- * Note: If the certification request 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 CertificationRequestInfo part of the
- * ASN.1 object needs to be scanned before the csr object is created.
- *
- * @param obj the asn1 representation of a PKCS#10 certification request (CSR).
- * @param computeHash true to compute the hash for verification.
- *
- * @return the certification request (CSR).
- */
- pki.certificationRequestFromAsn1 = function(obj, computeHash) {
- // validate certification request and capture data
- var capture = {};
- var errors = [];
- if(!asn1.validate(obj, certificationRequestValidator, capture, errors)) {
- var error = new Error('Cannot read PKCS#10 certificate request. ' +
- 'ASN.1 object is not a PKCS#10 CertificationRequest.');
- 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 certification request
- var csr = pki.createCertificationRequest();
- csr.version = capture.csrVersion ? capture.csrVersion.charCodeAt(0) : 0;
- csr.signatureOid = forge.asn1.derToOid(capture.csrSignatureOid);
- csr.signatureParameters = _readSignatureParameters(
- csr.signatureOid, capture.csrSignatureParams, true);
- csr.siginfo.algorithmOid = forge.asn1.derToOid(capture.csrSignatureOid);
- csr.siginfo.parameters = _readSignatureParameters(
- csr.siginfo.algorithmOid, capture.csrSignatureParams, false);
- csr.signature = capture.csrSignature;
-
- // keep CertificationRequestInfo to preserve signature when exporting
- csr.certificationRequestInfo = capture.certificationRequestInfo;
-
- if(computeHash) {
- // check signature OID for supported signature types
- csr.md = null;
- if(csr.signatureOid in oids) {
- var oid = oids[csr.signatureOid];
- switch(oid) {
- case 'sha1WithRSAEncryption':
- csr.md = forge.md.sha1.create();
- break;
- case 'md5WithRSAEncryption':
- csr.md = forge.md.md5.create();
- break;
- case 'sha256WithRSAEncryption':
- csr.md = forge.md.sha256.create();
- break;
- case 'sha384WithRSAEncryption':
- csr.md = forge.md.sha384.create();
- break;
- case 'sha512WithRSAEncryption':
- csr.md = forge.md.sha512.create();
- break;
- case 'RSASSA-PSS':
- csr.md = forge.md.sha256.create();
- break;
- }
- }
- if(csr.md === null) {
- var error = new Error('Could not compute certification request digest. ' +
- 'Unknown signature OID.');
- error.signatureOid = csr.signatureOid;
- throw error;
- }
-
- // produce DER formatted CertificationRequestInfo and digest it
- var bytes = asn1.toDer(csr.certificationRequestInfo);
- csr.md.update(bytes.getBytes());
- }
-
- // handle subject, build subject message digest
- var smd = forge.md.sha1.create();
- csr.subject.getField = function(sn) {
- return _getAttribute(csr.subject, sn);
- };
- csr.subject.addField = function(attr) {
- _fillMissingFields([attr]);
- csr.subject.attributes.push(attr);
- };
- csr.subject.attributes = pki.RDNAttributesAsArray(
- capture.certificationRequestInfoSubject, smd);
- csr.subject.hash = smd.digest().toHex();
-
- // convert RSA public key from ASN.1
- csr.publicKey = pki.publicKeyFromAsn1(capture.subjectPublicKeyInfo);
-
- // convert attributes from ASN.1
- csr.getAttribute = function(sn) {
- return _getAttribute(csr, sn);
- };
- csr.addAttribute = function(attr) {
- _fillMissingFields([attr]);
- csr.attributes.push(attr);
- };
- csr.attributes = pki.CRIAttributesAsArray(
- capture.certificationRequestInfoAttributes || []);
-
- return csr;
- };
-
- /**
- * Creates an empty certification request (a CSR or certificate signing
- * request). Once created, its public key and attributes can be set and then
- * it can be signed.
- *
- * @return the empty certification request.
- */
- pki.createCertificationRequest = function() {
- var csr = {};
- csr.version = 0x00;
- csr.signatureOid = null;
- csr.signature = null;
- csr.siginfo = {};
- csr.siginfo.algorithmOid = null;
-
- csr.subject = {};
- csr.subject.getField = function(sn) {
- return _getAttribute(csr.subject, sn);
- };
- csr.subject.addField = function(attr) {
- _fillMissingFields([attr]);
- csr.subject.attributes.push(attr);
- };
- csr.subject.attributes = [];
- csr.subject.hash = null;
-
- csr.publicKey = null;
- csr.attributes = [];
- csr.getAttribute = function(sn) {
- return _getAttribute(csr, sn);
- };
- csr.addAttribute = function(attr) {
- _fillMissingFields([attr]);
- csr.attributes.push(attr);
- };
- csr.md = null;
-
- /**
- * Sets the subject of this certification request.
- *
- * @param attrs the array of subject attributes to use.
- */
- csr.setSubject = function(attrs) {
- // set new attributes
- _fillMissingFields(attrs);
- csr.subject.attributes = attrs;
- csr.subject.hash = null;
- };
-
- /**
- * Sets the attributes of this certification request.
- *
- * @param attrs the array of attributes to use.
- */
- csr.setAttributes = function(attrs) {
- // set new attributes
- _fillMissingFields(attrs);
- csr.attributes = attrs;
- };
-
- /**
- * Signs this certification request 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).
- */
- csr.sign = function(key, md) {
- // TODO: get signature OID from private key
- csr.md = md || forge.md.sha1.create();
- var algorithmOid = oids[csr.md.algorithm + 'WithRSAEncryption'];
- if(!algorithmOid) {
- var error = new Error('Could not compute certification request digest. ' +
- 'Unknown message digest algorithm OID.');
- error.algorithm = csr.md.algorithm;
- throw error;
- }
- csr.signatureOid = csr.siginfo.algorithmOid = algorithmOid;
-
- // get CertificationRequestInfo, convert to DER
- csr.certificationRequestInfo = pki.getCertificationRequestInfo(csr);
- var bytes = asn1.toDer(csr.certificationRequestInfo);
-
- // digest and sign
- csr.md.update(bytes.getBytes());
- csr.signature = key.sign(csr.md);
- };
-
- /**
- * Attempts verify the signature on the passed certification request using
- * its public key.
- *
- * A CSR that has been exported to a file in PEM format can be verified using
- * OpenSSL using this command:
- *
- * openssl req -in <the-csr-pem-file> -verify -noout -text
- *
- * @return true if verified, false if not.
- */
- csr.verify = function() {
- var rval = false;
-
- var md = csr.md;
- if(md === null) {
- // check signature OID for supported signature types
- if(csr.signatureOid in oids) {
- // TODO: create DRY `OID to md` function
- var oid = oids[csr.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 certification request digest. ' +
- 'Unknown signature OID.');
- error.signatureOid = csr.signatureOid;
- throw error;
- }
-
- // produce DER formatted CertificationRequestInfo and digest it
- var cri = csr.certificationRequestInfo ||
- pki.getCertificationRequestInfo(csr);
- var bytes = asn1.toDer(cri);
- md.update(bytes.getBytes());
- }
-
- if(md !== null) {
- var scheme;
-
- switch(csr.signatureOid) {
- case oids.sha1WithRSAEncryption:
- /* use PKCS#1 v1.5 padding scheme */
- break;
- case oids['RSASSA-PSS']:
- var hash, mgf;
-
- /* initialize mgf */
- hash = oids[csr.signatureParameters.mgf.hash.algorithmOid];
- if(hash === undefined || forge.md[hash] === undefined) {
- var error = new Error('Unsupported MGF hash function.');
- error.oid = csr.signatureParameters.mgf.hash.algorithmOid;
- error.name = hash;
- throw error;
- }
-
- mgf = oids[csr.signatureParameters.mgf.algorithmOid];
- if(mgf === undefined || forge.mgf[mgf] === undefined) {
- var error = new Error('Unsupported MGF function.');
- error.oid = csr.signatureParameters.mgf.algorithmOid;
- error.name = mgf;
- throw error;
- }
-
- mgf = forge.mgf[mgf].create(forge.md[hash].create());
-
- /* initialize hash function */
- hash = oids[csr.signatureParameters.hash.algorithmOid];
- if(hash === undefined || forge.md[hash] === undefined) {
- var error = new Error('Unsupported RSASSA-PSS hash function.');
- error.oid = csr.signatureParameters.hash.algorithmOid;
- error.name = hash;
- throw error;
- }
-
- scheme = forge.pss.create(forge.md[hash].create(), mgf,
- csr.signatureParameters.saltLength);
- break;
- }
-
- // verify signature on csr using its public key
- rval = csr.publicKey.verify(
- md.digest().getBytes(), csr.signature, scheme);
- }
-
- return rval;
- };
-
- return csr;
- };
-
- /**
- * Converts an X.509 subject or issuer to an ASN.1 RDNSequence.
- *
- * @param obj the subject or issuer (distinguished name).
- *
- * @return the ASN.1 RDNSequence.
- */
- function _dnToAsn1(obj) {
- // create an empty RDNSequence
- var rval = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
-
- // iterate over attributes
- var attr, set;
- var attrs = obj.attributes;
- for(var i = 0; i < attrs.length; ++i) {
- attr = attrs[i];
- var value = attr.value;
-
- // reuse tag class for attribute value if available
- var valueTagClass = asn1.Type.PRINTABLESTRING;
- if('valueTagClass' in attr) {
- valueTagClass = attr.valueTagClass;
-
- if(valueTagClass === asn1.Type.UTF8) {
- value = forge.util.encodeUtf8(value);
- }
- // FIXME: handle more encodings
- }
-
- // create a RelativeDistinguishedName set
- // each value in the set is an AttributeTypeAndValue first
- // containing the type (an OID) and second the value
- set = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // AttributeType
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(attr.type).getBytes()),
- // AttributeValue
- asn1.create(asn1.Class.UNIVERSAL, valueTagClass, false, value)
- ])
- ]);
- rval.value.push(set);
- }
-
- return rval;
- }
-
- /**
- * Gets all printable attributes (typically of an issuer or subject) in a
- * simplified JSON format for display.
- *
- * @param attrs the attributes.
- *
- * @return the JSON for display.
- */
- function _getAttributesAsJson(attrs) {
- var rval = {};
- for(var i = 0; i < attrs.length; ++i) {
- var attr = attrs[i];
- if(attr.shortName && (
- attr.valueTagClass === asn1.Type.UTF8 ||
- attr.valueTagClass === asn1.Type.PRINTABLESTRING ||
- attr.valueTagClass === asn1.Type.IA5STRING)) {
- var value = attr.value;
- if(attr.valueTagClass === asn1.Type.UTF8) {
- value = forge.util.encodeUtf8(attr.value);
- }
- if(!(attr.shortName in rval)) {
- rval[attr.shortName] = value;
- } else if(forge.util.isArray(rval[attr.shortName])) {
- rval[attr.shortName].push(value);
- } else {
- rval[attr.shortName] = [rval[attr.shortName], value];
- }
- }
- }
- return rval;
- }
-
- /**
- * Fills in missing fields in attributes.
- *
- * @param attrs the attributes to fill missing fields in.
- */
- function _fillMissingFields(attrs) {
- var attr;
- for(var i = 0; i < attrs.length; ++i) {
- attr = attrs[i];
-
- // populate missing name
- if(typeof attr.name === 'undefined') {
- if(attr.type && attr.type in pki.oids) {
- attr.name = pki.oids[attr.type];
- } else if(attr.shortName && attr.shortName in _shortNames) {
- attr.name = pki.oids[_shortNames[attr.shortName]];
- }
- }
-
- // populate missing type (OID)
- if(typeof attr.type === 'undefined') {
- if(attr.name && attr.name in pki.oids) {
- attr.type = pki.oids[attr.name];
- } else {
- var error = new Error('Attribute type not specified.');
- error.attribute = attr;
- throw error;
- }
- }
-
- // populate missing shortname
- if(typeof attr.shortName === 'undefined') {
- if(attr.name && attr.name in _shortNames) {
- attr.shortName = _shortNames[attr.name];
- }
- }
-
- // convert extensions to value
- if(attr.type === oids.extensionRequest) {
- attr.valueConstructed = true;
- attr.valueTagClass = asn1.Type.SEQUENCE;
- if(!attr.value && attr.extensions) {
- attr.value = [];
- for(var ei = 0; ei < attr.extensions.length; ++ei) {
- attr.value.push(pki.certificateExtensionToAsn1(
- _fillMissingExtensionFields(attr.extensions[ei])));
- }
- }
- }
-
- if(typeof attr.value === 'undefined') {
- var error = new Error('Attribute value not specified.');
- error.attribute = attr;
- throw error;
- }
- }
- }
-
- /**
- * Fills in missing fields in certificate extensions.
- *
- * @param e the extension.
- * @param [options] the options to use.
- * [cert] the certificate the extensions are for.
- *
- * @return the extension.
- */
- function _fillMissingExtensionFields(e, options) {
- options = options || {};
-
- // populate missing name
- if(typeof e.name === 'undefined') {
- if(e.id && e.id in pki.oids) {
- e.name = pki.oids[e.id];
- }
- }
-
- // populate missing id
- if(typeof e.id === 'undefined') {
- if(e.name && e.name in pki.oids) {
- e.id = pki.oids[e.name];
- } else {
- var error = new Error('Extension ID not specified.');
- error.extension = e;
- throw error;
- }
- }
-
- if(typeof e.value !== 'undefined') {
- return e;
- }
-
- // handle missing value:
-
- // value is a BIT STRING
- if(e.name === 'keyUsage') {
- // build flags
- var unused = 0;
- var b2 = 0x00;
- var b3 = 0x00;
- if(e.digitalSignature) {
- b2 |= 0x80;
- unused = 7;
- }
- if(e.nonRepudiation) {
- b2 |= 0x40;
- unused = 6;
- }
- if(e.keyEncipherment) {
- b2 |= 0x20;
- unused = 5;
- }
- if(e.dataEncipherment) {
- b2 |= 0x10;
- unused = 4;
- }
- if(e.keyAgreement) {
- b2 |= 0x08;
- unused = 3;
- }
- if(e.keyCertSign) {
- b2 |= 0x04;
- unused = 2;
- }
- if(e.cRLSign) {
- b2 |= 0x02;
- unused = 1;
- }
- if(e.encipherOnly) {
- b2 |= 0x01;
- unused = 0;
- }
- if(e.decipherOnly) {
- b3 |= 0x80;
- unused = 7;
- }
-
- // create bit string
- var value = String.fromCharCode(unused);
- if(b3 !== 0) {
- value += String.fromCharCode(b2) + String.fromCharCode(b3);
- } else if(b2 !== 0) {
- value += String.fromCharCode(b2);
- }
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
- } else if(e.name === 'basicConstraints') {
- // basicConstraints is a SEQUENCE
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
- // cA BOOLEAN flag defaults to false
- if(e.cA) {
- e.value.value.push(asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
- String.fromCharCode(0xFF)));
- }
- if('pathLenConstraint' in e) {
- e.value.value.push(asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
- asn1.integerToDer(e.pathLenConstraint).getBytes()));
- }
- } else if(e.name === 'extKeyUsage') {
- // extKeyUsage is a SEQUENCE of OIDs
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
- var seq = e.value.value;
- for(var key in e) {
- if(e[key] !== true) {
- continue;
- }
- // key is name in OID map
- if(key in oids) {
- seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
- false, asn1.oidToDer(oids[key]).getBytes()));
- } else if(key.indexOf('.') !== -1) {
- // assume key is an OID
- seq.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID,
- false, asn1.oidToDer(key).getBytes()));
- }
- }
- } else if(e.name === 'nsCertType') {
- // nsCertType is a BIT STRING
- // build flags
- var unused = 0;
- var b2 = 0x00;
-
- if(e.client) {
- b2 |= 0x80;
- unused = 7;
- }
- if(e.server) {
- b2 |= 0x40;
- unused = 6;
- }
- if(e.email) {
- b2 |= 0x20;
- unused = 5;
- }
- if(e.objsign) {
- b2 |= 0x10;
- unused = 4;
- }
- if(e.reserved) {
- b2 |= 0x08;
- unused = 3;
- }
- if(e.sslCA) {
- b2 |= 0x04;
- unused = 2;
- }
- if(e.emailCA) {
- b2 |= 0x02;
- unused = 1;
- }
- if(e.objCA) {
- b2 |= 0x01;
- unused = 0;
- }
-
- // create bit string
- var value = String.fromCharCode(unused);
- if(b2 !== 0) {
- value += String.fromCharCode(b2);
- }
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false, value);
- } else if(e.name === 'subjectAltName' || e.name === 'issuerAltName') {
- // SYNTAX SEQUENCE
- e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
-
- var altName;
- for(var n = 0; n < e.altNames.length; ++n) {
- altName = e.altNames[n];
- var value = altName.value;
- // handle IP
- if(altName.type === 7 && altName.ip) {
- value = forge.util.bytesFromIP(altName.ip);
- if(value === null) {
- var error = new Error(
- 'Extension "ip" value is not a valid IPv4 or IPv6 address.');
- error.extension = e;
- throw error;
- }
- } else if(altName.type === 8) {
- // handle OID
- if(altName.oid) {
- value = asn1.oidToDer(asn1.oidToDer(altName.oid));
- } else {
- // deprecated ... convert value to OID
- value = asn1.oidToDer(value);
- }
- }
- e.value.value.push(asn1.create(
- asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
- value));
- }
- } else if(e.name === 'nsComment' && options.cert) {
- // sanity check value is ASCII (req'd) and not too big
- if(!(/^[\x00-\x7F]*$/.test(e.comment)) ||
- (e.comment.length < 1) || (e.comment.length > 128)) {
- throw new Error('Invalid "nsComment" content.');
- }
- // IA5STRING opaque comment
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.IA5STRING, false, e.comment);
- } else if(e.name === 'subjectKeyIdentifier' && options.cert) {
- var ski = options.cert.generateSubjectKeyIdentifier();
- e.subjectKeyIdentifier = ski.toHex();
- // OCTETSTRING w/digest
- e.value = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ski.getBytes());
- } else if(e.name === 'authorityKeyIdentifier' && options.cert) {
- // SYNTAX SEQUENCE
- e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
- var seq = e.value.value;
-
- if(e.keyIdentifier) {
- var keyIdentifier = (e.keyIdentifier === true ?
- options.cert.generateSubjectKeyIdentifier().getBytes() :
- e.keyIdentifier);
- seq.push(
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, false, keyIdentifier));
- }
-
- if(e.authorityCertIssuer) {
- var authorityCertIssuer = [
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 4, true, [
- _dnToAsn1(e.authorityCertIssuer === true ?
- options.cert.issuer : e.authorityCertIssuer)
- ])
- ];
- seq.push(
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, authorityCertIssuer));
- }
-
- if(e.serialNumber) {
- var serialNumber = forge.util.hexToBytes(e.serialNumber === true ?
- options.cert.serialNumber : e.serialNumber);
- seq.push(
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, false, serialNumber));
- }
- } else if(e.name === 'cRLDistributionPoints') {
- e.value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
- var seq = e.value.value;
-
- // Create sub SEQUENCE of DistributionPointName
- var subSeq = asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
-
- // Create fullName CHOICE
- var fullNameGeneralNames = asn1.create(
- asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
- var altName;
- for(var n = 0; n < e.altNames.length; ++n) {
- altName = e.altNames[n];
- var value = altName.value;
- // handle IP
- if(altName.type === 7 && altName.ip) {
- value = forge.util.bytesFromIP(altName.ip);
- if(value === null) {
- var error = new Error(
- 'Extension "ip" value is not a valid IPv4 or IPv6 address.');
- error.extension = e;
- throw error;
- }
- } else if(altName.type === 8) {
- // handle OID
- if(altName.oid) {
- value = asn1.oidToDer(asn1.oidToDer(altName.oid));
- } else {
- // deprecated ... convert value to OID
- value = asn1.oidToDer(value);
- }
- }
- fullNameGeneralNames.value.push(asn1.create(
- asn1.Class.CONTEXT_SPECIFIC, altName.type, false,
- value));
- }
-
- // Add to the parent SEQUENCE
- subSeq.value.push(asn1.create(
- asn1.Class.CONTEXT_SPECIFIC, 0, true, [fullNameGeneralNames]));
- seq.push(subSeq);
- }
-
- // ensure value has been defined by now
- if(typeof e.value === 'undefined') {
- var error = new Error('Extension value not specified.');
- error.extension = e;
- throw error;
- }
-
- return e;
- }
-
- /**
- * Convert signature parameters object to ASN.1
- *
- * @param {String} oid Signature algorithm OID
- * @param params The signature parametrs object
- * @return ASN.1 object representing signature parameters
- */
- function _signatureParametersToAsn1(oid, params) {
- switch(oid) {
- case oids['RSASSA-PSS']:
- var parts = [];
-
- if(params.hash.algorithmOid !== undefined) {
- parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(params.hash.algorithmOid).getBytes()),
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
- ])
- ]));
- }
-
- if(params.mgf.algorithmOid !== undefined) {
- parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(params.mgf.algorithmOid).getBytes()),
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(params.mgf.hash.algorithmOid).getBytes()),
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
- ])
- ])
- ]));
- }
-
- if(params.saltLength !== undefined) {
- parts.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
- asn1.integerToDer(params.saltLength).getBytes())
- ]));
- }
-
- return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, parts);
-
- default:
- return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '');
- }
- }
-
- /**
- * Converts a certification request's attributes to an ASN.1 set of
- * CRIAttributes.
- *
- * @param csr certification request.
- *
- * @return the ASN.1 set of CRIAttributes.
- */
- function _CRIAttributesToAsn1(csr) {
- // create an empty context-specific container
- var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
-
- // no attributes, return empty container
- if(csr.attributes.length === 0) {
- return rval;
- }
-
- // each attribute has a sequence with a type and a set of values
- var attrs = csr.attributes;
- for(var i = 0; i < attrs.length; ++i) {
- var attr = attrs[i];
- var value = attr.value;
-
- // reuse tag class for attribute value if available
- var valueTagClass = asn1.Type.UTF8;
- if('valueTagClass' in attr) {
- valueTagClass = attr.valueTagClass;
- }
- if(valueTagClass === asn1.Type.UTF8) {
- value = forge.util.encodeUtf8(value);
- }
- var valueConstructed = false;
- if('valueConstructed' in attr) {
- valueConstructed = attr.valueConstructed;
- }
- // FIXME: handle more encodings
-
- // create a RelativeDistinguishedName set
- // each value in the set is an AttributeTypeAndValue first
- // containing the type (an OID) and second the value
- var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // AttributeType
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(attr.type).getBytes()),
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
- // AttributeValue
- asn1.create(
- asn1.Class.UNIVERSAL, valueTagClass, valueConstructed, value)
- ])
- ]);
- rval.value.push(seq);
- }
-
- return rval;
- }
-
- var jan_1_1950 = new Date('1950-01-01T00:00:00Z');
- var jan_1_2050 = new Date('2050-01-01T00:00:00Z');
-
- /**
- * Converts a Date object to ASN.1
- * Handles the different format before and after 1st January 2050
- *
- * @param date date object.
- *
- * @return the ASN.1 object representing the date.
- */
- function _dateToAsn1(date) {
- if(date >= jan_1_1950 && date < jan_1_2050) {
- return asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
- asn1.dateToUtcTime(date));
- } else {
- return asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.GENERALIZEDTIME, false,
- asn1.dateToGeneralizedTime(date));
- }
- }
-
- /**
- * Gets the ASN.1 TBSCertificate part of an X.509v3 certificate.
- *
- * @param cert the certificate.
- *
- * @return the asn1 TBSCertificate.
- */
- pki.getTBSCertificate = function(cert) {
- // TBSCertificate
- var notBefore = _dateToAsn1(cert.validity.notBefore);
- var notAfter = _dateToAsn1(cert.validity.notAfter);
- var tbs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // version
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
- // integer
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
- asn1.integerToDer(cert.version).getBytes())
- ]),
- // serialNumber
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
- forge.util.hexToBytes(cert.serialNumber)),
- // signature
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // algorithm
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(cert.siginfo.algorithmOid).getBytes()),
- // parameters
- _signatureParametersToAsn1(
- cert.siginfo.algorithmOid, cert.siginfo.parameters)
- ]),
- // issuer
- _dnToAsn1(cert.issuer),
- // validity
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- notBefore,
- notAfter
- ]),
- // subject
- _dnToAsn1(cert.subject),
- // SubjectPublicKeyInfo
- pki.publicKeyToAsn1(cert.publicKey)
- ]);
-
- if(cert.issuer.uniqueId) {
- // issuerUniqueID (optional)
- tbs.value.push(
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
- // TODO: support arbitrary bit length ids
- String.fromCharCode(0x00) +
- cert.issuer.uniqueId
- )
- ])
- );
- }
- if(cert.subject.uniqueId) {
- // subjectUniqueID (optional)
- tbs.value.push(
- asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
- // TODO: support arbitrary bit length ids
- String.fromCharCode(0x00) +
- cert.subject.uniqueId
- )
- ])
- );
- }
-
- if(cert.extensions.length > 0) {
- // extensions (optional)
- tbs.value.push(pki.certificateExtensionsToAsn1(cert.extensions));
- }
-
- return tbs;
- };
-
- /**
- * Gets the ASN.1 CertificationRequestInfo part of a
- * PKCS#10 CertificationRequest.
- *
- * @param csr the certification request.
- *
- * @return the asn1 CertificationRequestInfo.
- */
- pki.getCertificationRequestInfo = function(csr) {
- // CertificationRequestInfo
- var cri = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // version
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
- asn1.integerToDer(csr.version).getBytes()),
- // subject
- _dnToAsn1(csr.subject),
- // SubjectPublicKeyInfo
- pki.publicKeyToAsn1(csr.publicKey),
- // attributes
- _CRIAttributesToAsn1(csr)
- ]);
-
- return cri;
- };
-
- /**
- * Converts a DistinguishedName (subject or issuer) to an ASN.1 object.
- *
- * @param dn the DistinguishedName.
- *
- * @return the asn1 representation of a DistinguishedName.
- */
- pki.distinguishedNameToAsn1 = function(dn) {
- return _dnToAsn1(dn);
- };
-
- /**
- * Converts an X.509v3 RSA certificate to an ASN.1 object.
- *
- * @param cert the certificate.
- *
- * @return the asn1 representation of an X.509v3 RSA certificate.
- */
- pki.certificateToAsn1 = function(cert) {
- // prefer cached TBSCertificate over generating one
- var tbsCertificate = cert.tbsCertificate || pki.getTBSCertificate(cert);
-
- // Certificate
- return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // TBSCertificate
- tbsCertificate,
- // AlgorithmIdentifier (signature algorithm)
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // algorithm
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(cert.signatureOid).getBytes()),
- // parameters
- _signatureParametersToAsn1(cert.signatureOid, cert.signatureParameters)
- ]),
- // SignatureValue
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
- String.fromCharCode(0x00) + cert.signature)
- ]);
- };
-
- /**
- * Converts X.509v3 certificate extensions to ASN.1.
- *
- * @param exts the extensions to convert.
- *
- * @return the extensions in ASN.1 format.
- */
- pki.certificateExtensionsToAsn1 = function(exts) {
- // create top-level extension container
- var rval = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, []);
-
- // create extension sequence (stores a sequence for each extension)
- var seq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
- rval.value.push(seq);
-
- for(var i = 0; i < exts.length; ++i) {
- seq.value.push(pki.certificateExtensionToAsn1(exts[i]));
- }
-
- return rval;
- };
-
- /**
- * Converts a single certificate extension to ASN.1.
- *
- * @param ext the extension to convert.
- *
- * @return the extension in ASN.1 format.
- */
- pki.certificateExtensionToAsn1 = function(ext) {
- // create a sequence for each extension
- var extseq = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, []);
-
- // extnID (OID)
- extseq.value.push(asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(ext.id).getBytes()));
-
- // critical defaults to false
- if(ext.critical) {
- // critical BOOLEAN DEFAULT FALSE
- extseq.value.push(asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.BOOLEAN, false,
- String.fromCharCode(0xFF)));
- }
-
- var value = ext.value;
- if(typeof ext.value !== 'string') {
- // value is asn.1
- value = asn1.toDer(value).getBytes();
- }
-
- // extnValue (OCTET STRING)
- extseq.value.push(asn1.create(
- asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, value));
-
- return extseq;
- };
-
- /**
- * Converts a PKCS#10 certification request to an ASN.1 object.
- *
- * @param csr the certification request.
- *
- * @return the asn1 representation of a certification request.
- */
- pki.certificationRequestToAsn1 = function(csr) {
- // prefer cached CertificationRequestInfo over generating one
- var cri = csr.certificationRequestInfo ||
- pki.getCertificationRequestInfo(csr);
-
- // Certificate
- return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // CertificationRequestInfo
- cri,
- // AlgorithmIdentifier (signature algorithm)
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
- // algorithm
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
- asn1.oidToDer(csr.signatureOid).getBytes()),
- // parameters
- _signatureParametersToAsn1(csr.signatureOid, csr.signatureParameters)
- ]),
- // signature
- asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BITSTRING, false,
- String.fromCharCode(0x00) + csr.signature)
- ]);
- };
-
- /**
- * Creates a CA store.
- *
- * @param certs an optional array of certificate objects or PEM-formatted
- * certificate strings to add to the CA store.
- *
- * @return the CA store.
- */
- pki.createCaStore = function(certs) {
- // create CA store
- var caStore = {
- // stored certificates
- certs: {}
- };
-
- /**
- * Gets the certificate that issued the passed certificate or its
- * 'parent'.
- *
- * @param cert the certificate to get the parent for.
- *
- * @return the parent certificate or null if none was found.
- */
- caStore.getIssuer = function(cert) {
- var rval = getBySubject(cert.issuer);
-
- // see if there are multiple matches
- /*if(forge.util.isArray(rval)) {
- // TODO: resolve multiple matches by checking
- // authorityKey/subjectKey/issuerUniqueID/other identifiers, etc.
- // FIXME: or alternatively do authority key mapping
- // if possible (X.509v1 certs can't work?)
- throw new Error('Resolving multiple issuer matches not implemented yet.');
- }*/
-
- return rval;
- };
-
- /**
- * Adds a trusted certificate to the store.
- *
- * @param cert the certificate to add as a trusted certificate (either a
- * pki.certificate object or a PEM-formatted certificate).
- */
- caStore.addCertificate = function(cert) {
- // convert from pem if necessary
- if(typeof cert === 'string') {
- cert = forge.pki.certificateFromPem(cert);
- }
-
- ensureSubjectHasHash(cert.subject);
-
- if(!caStore.hasCertificate(cert)) { // avoid duplicate certificates in store
- if(cert.subject.hash in caStore.certs) {
- // subject hash already exists, append to array
- var tmp = caStore.certs[cert.subject.hash];
- if(!forge.util.isArray(tmp)) {
- tmp = [tmp];
- }
- tmp.push(cert);
- caStore.certs[cert.subject.hash] = tmp;
- } else {
- caStore.certs[cert.subject.hash] = cert;
- }
- }
- };
-
- /**
- * Checks to see if the given certificate is in the store.
- *
- * @param cert the certificate to check (either a pki.certificate or a
- * PEM-formatted certificate).
- *
- * @return true if the certificate is in the store, false if not.
- */
- caStore.hasCertificate = function(cert) {
- // convert from pem if necessary
- if(typeof cert === 'string') {
- cert = forge.pki.certificateFromPem(cert);
- }
-
- var match = getBySubject(cert.subject);
- if(!match) {
- return false;
- }
- if(!forge.util.isArray(match)) {
- match = [match];
- }
- // compare DER-encoding of certificates
- var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
- for(var i = 0; i < match.length; ++i) {
- var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
- if(der1 === der2) {
- return true;
- }
- }
- return false;
- };
-
- /**
- * Lists all of the certificates kept in the store.
- *
- * @return an array of all of the pki.certificate objects in the store.
- */
- caStore.listAllCertificates = function() {
- var certList = [];
-
- for(var hash in caStore.certs) {
- if(caStore.certs.hasOwnProperty(hash)) {
- var value = caStore.certs[hash];
- if(!forge.util.isArray(value)) {
- certList.push(value);
- } else {
- for(var i = 0; i < value.length; ++i) {
- certList.push(value[i]);
- }
- }
- }
- }
-
- return certList;
- };
-
- /**
- * Removes a certificate from the store.
- *
- * @param cert the certificate to remove (either a pki.certificate or a
- * PEM-formatted certificate).
- *
- * @return the certificate that was removed or null if the certificate
- * wasn't in store.
- */
- caStore.removeCertificate = function(cert) {
- var result;
-
- // convert from pem if necessary
- if(typeof cert === 'string') {
- cert = forge.pki.certificateFromPem(cert);
- }
- ensureSubjectHasHash(cert.subject);
- if(!caStore.hasCertificate(cert)) {
- return null;
- }
-
- var match = getBySubject(cert.subject);
-
- if(!forge.util.isArray(match)) {
- result = caStore.certs[cert.subject.hash];
- delete caStore.certs[cert.subject.hash];
- return result;
- }
-
- // compare DER-encoding of certificates
- var der1 = asn1.toDer(pki.certificateToAsn1(cert)).getBytes();
- for(var i = 0; i < match.length; ++i) {
- var der2 = asn1.toDer(pki.certificateToAsn1(match[i])).getBytes();
- if(der1 === der2) {
- result = match[i];
- match.splice(i, 1);
- }
- }
- if(match.length === 0) {
- delete caStore.certs[cert.subject.hash];
- }
-
- return result;
- };
-
- function getBySubject(subject) {
- ensureSubjectHasHash(subject);
- return caStore.certs[subject.hash] || null;
- }
-
- function ensureSubjectHasHash(subject) {
- // produce subject hash if it doesn't exist
- if(!subject.hash) {
- var md = forge.md.sha1.create();
- subject.attributes = pki.RDNAttributesAsArray(_dnToAsn1(subject), md);
- subject.hash = md.digest().toHex();
- }
- }
-
- // auto-add passed in certs
- if(certs) {
- // parse PEM-formatted certificates as necessary
- for(var i = 0; i < certs.length; ++i) {
- var cert = certs[i];
- caStore.addCertificate(cert);
- }
- }
-
- return caStore;
- };
-
- /**
- * Certificate verification errors, based on TLS.
- */
- pki.certificateError = {
- bad_certificate: 'forge.pki.BadCertificate',
- unsupported_certificate: 'forge.pki.UnsupportedCertificate',
- certificate_revoked: 'forge.pki.CertificateRevoked',
- certificate_expired: 'forge.pki.CertificateExpired',
- certificate_unknown: 'forge.pki.CertificateUnknown',
- unknown_ca: 'forge.pki.UnknownCertificateAuthority'
- };
-
- /**
- * Verifies a certificate chain against the given Certificate Authority store
- * with an optional custom verify callback.
- *
- * @param caStore a certificate store to verify against.
- * @param chain the certificate chain to verify, with the root or highest
- * authority at the end (an array of certificates).
- * @param options a callback to be called for every certificate in the chain or
- * an object with:
- * verify a callback to be called for every certificate in the
- * chain
- * validityCheckDate the date against which the certificate
- * validity period should be checked. Pass null to not check
- * the validity period. By default, the current date is used.
- *
- * The verify callback has the following signature:
- *
- * verified - Set to true if certificate was verified, otherwise the
- * pki.certificateError for why the certificate failed.
- * depth - The current index in the chain, where 0 is the end point's cert.
- * certs - The certificate chain, *NOTE* an empty chain indicates an anonymous
- * end point.
- *
- * The function returns true on success and on failure either the appropriate
- * pki.certificateError or an object with 'error' set to the appropriate
- * pki.certificateError and 'message' set to a custom error message.
- *
- * @return true if successful, error thrown if not.
- */
- pki.verifyCertificateChain = function(caStore, chain, options) {
- /* From: RFC3280 - Internet X.509 Public Key Infrastructure Certificate
- Section 6: Certification Path Validation
- See inline parentheticals related to this particular implementation.
-
- The primary goal of path validation is to verify the binding between
- a subject distinguished name or a subject alternative name and subject
- public key, as represented in the end entity certificate, based on the
- public key of the trust anchor. This requires obtaining a sequence of
- certificates that support that binding. That sequence should be provided
- in the passed 'chain'. The trust anchor should be in the given CA
- store. The 'end entity' certificate is the certificate provided by the
- end point (typically a server) and is the first in the chain.
-
- To meet this goal, the path validation process verifies, among other
- things, that a prospective certification path (a sequence of n
- certificates or a 'chain') satisfies the following conditions:
-
- (a) for all x in {1, ..., n-1}, the subject of certificate x is
- the issuer of certificate x+1;
-
- (b) certificate 1 is issued by the trust anchor;
-
- (c) certificate n is the certificate to be validated; and
-
- (d) for all x in {1, ..., n}, the certificate was valid at the
- time in question.
-
- Note that here 'n' is index 0 in the chain and 1 is the last certificate
- in the chain and it must be signed by a certificate in the connection's
- CA store.
-
- The path validation process also determines the set of certificate
- policies that are valid for this path, based on the certificate policies
- extension, policy mapping extension, policy constraints extension, and
- inhibit any-policy extension.
-
- Note: Policy mapping extension not supported (Not Required).
-
- Note: If the certificate has an unsupported critical extension, then it
- must be rejected.
-
- Note: A certificate is self-issued if the DNs that appear in the subject
- and issuer fields are identical and are not empty.
-
- The path validation algorithm assumes the following seven inputs are
- provided to the path processing logic. What this specific implementation
- will use is provided parenthetically:
-
- (a) a prospective certification path of length n (the 'chain')
- (b) the current date/time: ('now').
- (c) user-initial-policy-set: A set of certificate policy identifiers
- naming the policies that are acceptable to the certificate user.
- The user-initial-policy-set contains the special value any-policy
- if the user is not concerned about certificate policy
- (Not implemented. Any policy is accepted).
- (d) trust anchor information, describing a CA that serves as a trust
- anchor for the certification path. The trust anchor information
- includes:
-
- (1) the trusted issuer name,
- (2) the trusted public key algorithm,
- (3) the trusted public key, and
- (4) optionally, the trusted public key parameters associated
- with the public key.
-
- (Trust anchors are provided via certificates in the CA store).
-
- The trust anchor information may be provided to the path processing
- procedure in the form of a self-signed certificate. The trusted anchor
- information is trusted because it was delivered to the path processing
- procedure by some trustworthy out-of-band procedure. If the trusted
- public key algorithm requires parameters, then the parameters are
- provided along with the trusted public key (No parameters used in this
- implementation).
-
- (e) initial-policy-mapping-inhibit, which indicates if policy mapping is
- allowed in the certification path.
- (Not implemented, no policy checking)
-
- (f) initial-explicit-policy, which indicates if the path must be valid
- for at least one of the certificate policies in the user-initial-
- policy-set.
- (Not implemented, no policy checking)
-
- (g) initial-any-policy-inhibit, which indicates whether the
- anyPolicy OID should be processed if it is included in a
- certificate.
- (Not implemented, so any policy is valid provided that it is
- not marked as critical) */
-
- /* Basic Path Processing:
-
- For each certificate in the 'chain', the following is checked:
-
- 1. The certificate validity period includes the current time.
- 2. The certificate was signed by its parent (where the parent is either
- the next in the chain or from the CA store). Allow processing to
- continue to the next step if no parent is found but the certificate is
- in the CA store.
- 3. TODO: The certificate has not been revoked.
- 4. The certificate issuer name matches the parent's subject name.
- 5. TODO: If the certificate is self-issued and not the final certificate
- in the chain, skip this step, otherwise verify that the subject name
- is within one of the permitted subtrees of X.500 distinguished names
- and that each of the alternative names in the subjectAltName extension
- (critical or non-critical) is within one of the permitted subtrees for
- that name type.
- 6. TODO: If the certificate is self-issued and not the final certificate
- in the chain, skip this step, otherwise verify that the subject name
- is not within one of the excluded subtrees for X.500 distinguished
- names and none of the subjectAltName extension names are excluded for
- that name type.
- 7. The other steps in the algorithm for basic path processing involve
- handling the policy extension which is not presently supported in this
- implementation. Instead, if a critical policy extension is found, the
- certificate is rejected as not supported.
- 8. If the certificate is not the first or if its the only certificate in
- the chain (having no parent from the CA store or is self-signed) and it
- has a critical key usage extension, verify that the keyCertSign bit is
- set. If the key usage extension exists, verify that the basic
- constraints extension exists. If the basic constraints extension exists,
- verify that the cA flag is set. If pathLenConstraint is set, ensure that
- the number of certificates that precede in the chain (come earlier
- in the chain as implemented below), excluding the very first in the
- chain (typically the end-entity one), isn't greater than the
- pathLenConstraint. This constraint limits the number of intermediate
- CAs that may appear below a CA before only end-entity certificates
- may be issued. */
-
- // if a verify callback is passed as the third parameter, package it within
- // the options object. This is to support a legacy function signature that
- // expected the verify callback as the third parameter.
- if(typeof options === 'function') {
- options = {verify: options};
- }
- options = options || {};
-
- // copy cert chain references to another array to protect against changes
- // in verify callback
- chain = chain.slice(0);
- var certs = chain.slice(0);
-
- var validityCheckDate = options.validityCheckDate;
- // if no validityCheckDate is specified, default to the current date. Make
- // sure to maintain the value null because it indicates that the validity
- // period should not be checked.
- if(typeof validityCheckDate === 'undefined') {
- validityCheckDate = new Date();
- }
-
- // verify each cert in the chain using its parent, where the parent
- // is either the next in the chain or from the CA store
- var first = true;
- var error = null;
- var depth = 0;
- do {
- var cert = chain.shift();
- var parent = null;
- var selfSigned = false;
-
- if(validityCheckDate) {
- // 1. check valid time
- if(validityCheckDate < cert.validity.notBefore ||
- validityCheckDate > cert.validity.notAfter) {
- error = {
- message: 'Certificate is not valid yet or has expired.',
- error: pki.certificateError.certificate_expired,
- notBefore: cert.validity.notBefore,
- notAfter: cert.validity.notAfter,
- // TODO: we might want to reconsider renaming 'now' to
- // 'validityCheckDate' should this API be changed in the future.
- now: validityCheckDate
- };
- }
- }
-
- // 2. verify with parent from chain or CA store
- if(error === null) {
- parent = chain[0] || caStore.getIssuer(cert);
- if(parent === null) {
- // check for self-signed cert
- if(cert.isIssuer(cert)) {
- selfSigned = true;
- parent = cert;
- }
- }
-
- if(parent) {
- // FIXME: current CA store implementation might have multiple
- // certificates where the issuer can't be determined from the
- // certificate (happens rarely with, eg: old certificates) so normalize
- // by always putting parents into an array
- // TODO: there's may be an extreme degenerate case currently uncovered
- // where an old intermediate certificate seems to have a matching parent
- // but none of the parents actually verify ... but the intermediate
- // is in the CA and it should pass this check; needs investigation
- var parents = parent;
- if(!forge.util.isArray(parents)) {
- parents = [parents];
- }
-
- // try to verify with each possible parent (typically only one)
- var verified = false;
- while(!verified && parents.length > 0) {
- parent = parents.shift();
- try {
- verified = parent.verify(cert);
- } catch(ex) {
- // failure to verify, don't care why, try next one
- }
- }
-
- if(!verified) {
- error = {
- message: 'Certificate signature is invalid.',
- error: pki.certificateError.bad_certificate
- };
- }
- }
-
- if(error === null && (!parent || selfSigned) &&
- !caStore.hasCertificate(cert)) {
- // no parent issuer and certificate itself is not trusted
- error = {
- message: 'Certificate is not trusted.',
- error: pki.certificateError.unknown_ca
- };
- }
- }
-
- // TODO: 3. check revoked
-
- // 4. check for matching issuer/subject
- if(error === null && parent && !cert.isIssuer(parent)) {
- // parent is not issuer
- error = {
- message: 'Certificate issuer is invalid.',
- error: pki.certificateError.bad_certificate
- };
- }
-
- // 5. TODO: check names with permitted names tree
-
- // 6. TODO: check names against excluded names tree
-
- // 7. check for unsupported critical extensions
- if(error === null) {
- // supported extensions
- var se = {
- keyUsage: true,
- basicConstraints: true
- };
- for(var i = 0; error === null && i < cert.extensions.length; ++i) {
- var ext = cert.extensions[i];
- if(ext.critical && !(ext.name in se)) {
- error = {
- message:
- 'Certificate has an unsupported critical extension.',
- error: pki.certificateError.unsupported_certificate
- };
- }
- }
- }
-
- // 8. check for CA if cert is not first or is the only certificate
- // remaining in chain with no parent or is self-signed
- if(error === null &&
- (!first || (chain.length === 0 && (!parent || selfSigned)))) {
- // first check keyUsage extension and then basic constraints
- var bcExt = cert.getExtension('basicConstraints');
- var keyUsageExt = cert.getExtension('keyUsage');
- if(keyUsageExt !== null) {
- // keyCertSign must be true and there must be a basic
- // constraints extension
- if(!keyUsageExt.keyCertSign || bcExt === null) {
- // bad certificate
- error = {
- message:
- 'Certificate keyUsage or basicConstraints conflict ' +
- 'or indicate that the certificate is not a CA. ' +
- 'If the certificate is the only one in the chain or ' +
- 'isn\'t the first then the certificate must be a ' +
- 'valid CA.',
- error: pki.certificateError.bad_certificate
- };
- }
- }
- // basic constraints cA flag must be set
- if(error === null && bcExt !== null && !bcExt.cA) {
- // bad certificate
- error = {
- message:
- 'Certificate basicConstraints indicates the certificate ' +
- 'is not a CA.',
- error: pki.certificateError.bad_certificate
- };
- }
- // if error is not null and keyUsage is available, then we know it
- // has keyCertSign and there is a basic constraints extension too,
- // which means we can check pathLenConstraint (if it exists)
- if(error === null && keyUsageExt !== null &&
- 'pathLenConstraint' in bcExt) {
- // pathLen is the maximum # of intermediate CA certs that can be
- // found between the current certificate and the end-entity (depth 0)
- // certificate; this number does not include the end-entity (depth 0,
- // last in the chain) even if it happens to be a CA certificate itself
- var pathLen = depth - 1;
- if(pathLen > bcExt.pathLenConstraint) {
- // pathLenConstraint violated, bad certificate
- error = {
- message:
- 'Certificate basicConstraints pathLenConstraint violated.',
- error: pki.certificateError.bad_certificate
- };
- }
- }
- }
-
- // call application callback
- var vfd = (error === null) ? true : error.error;
- var ret = options.verify ? options.verify(vfd, depth, certs) : vfd;
- if(ret === true) {
- // clear any set error
- error = null;
- } else {
- // if passed basic tests, set default message and alert
- if(vfd === true) {
- error = {
- message: 'The application rejected the certificate.',
- error: pki.certificateError.bad_certificate
- };
- }
-
- // check for custom error info
- if(ret || ret === 0) {
- // set custom message and error
- if(typeof ret === 'object' && !forge.util.isArray(ret)) {
- if(ret.message) {
- error.message = ret.message;
- }
- if(ret.error) {
- error.error = ret.error;
- }
- } else if(typeof ret === 'string') {
- // set custom error
- error.error = ret;
- }
- }
-
- // throw error
- throw error;
- }
-
- // no longer first cert in chain
- first = false;
- ++depth;
- } while(chain.length > 0);
-
- return true;
- };
|