You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					3243 lines
				
				99 KiB
			
		
		
			
		
	
	
					3243 lines
				
				99 KiB
			| 
											2 years ago
										 | /** | ||
|  |  * 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; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Create signature digest for OID. | ||
|  |  * | ||
|  |  * @param options | ||
|  |  *   signatureOid: the OID specifying the signature algorithm. | ||
|  |  *   type: a human readable type for error messages | ||
|  |  * @return a created md instance. throws if unknown oid. | ||
|  |  */ | ||
|  | var _createSignatureDigest = function(options) { | ||
|  |   switch(oids[options.signatureOid]) { | ||
|  |     case 'sha1WithRSAEncryption': | ||
|  |     // deprecated alias
 | ||
|  |     case 'sha1WithRSASignature': | ||
|  |       return forge.md.sha1.create(); | ||
|  |     case 'md5WithRSAEncryption': | ||
|  |       return forge.md.md5.create(); | ||
|  |     case 'sha256WithRSAEncryption': | ||
|  |       return forge.md.sha256.create(); | ||
|  |     case 'sha384WithRSAEncryption': | ||
|  |       return forge.md.sha384.create(); | ||
|  |     case 'sha512WithRSAEncryption': | ||
|  |       return forge.md.sha512.create(); | ||
|  |     case 'RSASSA-PSS': | ||
|  |       return forge.md.sha256.create(); | ||
|  |     default: | ||
|  |       var error = new Error( | ||
|  |         'Could not compute ' + options.type + ' digest. ' + | ||
|  |         'Unknown signature OID.'); | ||
|  |       error.signatureOid = options.signatureOid; | ||
|  |       throw error; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Verify signature on certificate or CSR. | ||
|  |  * | ||
|  |  * @param options: | ||
|  |  *   certificate the certificate or CSR to verify. | ||
|  |  *   md the signature digest. | ||
|  |  *   signature the signature | ||
|  |  * @return a created md instance. throws if unknown oid. | ||
|  |  */ | ||
|  | var _verifySignature = function(options) { | ||
|  |   var cert = options.certificate; | ||
|  |   var scheme; | ||
|  | 
 | ||
|  |   switch(cert.signatureOid) { | ||
|  |     case oids.sha1WithRSAEncryption: | ||
|  |     // deprecated alias
 | ||
|  |     case oids.sha1WithRSASignature: | ||
|  |       /* use PKCS#1 v1.5 padding scheme */ | ||
|  |       break; | ||
|  |     case oids['RSASSA-PSS']: | ||
|  |       var hash, mgf; | ||
|  | 
 | ||
|  |       /* initialize mgf */ | ||
|  |       hash = oids[cert.signatureParameters.mgf.hash.algorithmOid]; | ||
|  |       if(hash === undefined || forge.md[hash] === undefined) { | ||
|  |         var error = new Error('Unsupported MGF hash function.'); | ||
|  |         error.oid = cert.signatureParameters.mgf.hash.algorithmOid; | ||
|  |         error.name = hash; | ||
|  |         throw error; | ||
|  |       } | ||
|  | 
 | ||
|  |       mgf = oids[cert.signatureParameters.mgf.algorithmOid]; | ||
|  |       if(mgf === undefined || forge.mgf[mgf] === undefined) { | ||
|  |         var error = new Error('Unsupported MGF function.'); | ||
|  |         error.oid = cert.signatureParameters.mgf.algorithmOid; | ||
|  |         error.name = mgf; | ||
|  |         throw error; | ||
|  |       } | ||
|  | 
 | ||
|  |       mgf = forge.mgf[mgf].create(forge.md[hash].create()); | ||
|  | 
 | ||
|  |       /* initialize hash function */ | ||
|  |       hash = oids[cert.signatureParameters.hash.algorithmOid]; | ||
|  |       if(hash === undefined || forge.md[hash] === undefined) { | ||
|  |         var error = new Error('Unsupported RSASSA-PSS hash function.'); | ||
|  |         error.oid = cert.signatureParameters.hash.algorithmOid; | ||
|  |         error.name = hash; | ||
|  |         throw error; | ||
|  |       } | ||
|  | 
 | ||
|  |       scheme = forge.pss.create( | ||
|  |         forge.md[hash].create(), mgf, cert.signatureParameters.saltLength | ||
|  |       ); | ||
|  |       break; | ||
|  |   } | ||
|  | 
 | ||
|  |   // verify signature on cert using public key
 | ||
|  |   return cert.publicKey.verify( | ||
|  |     options.md.digest().getBytes(), options.signature, scheme | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * 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 = subject.attributes; | ||
|  |       error.actualIssuer = issuer.attributes; | ||
|  |       throw error; | ||
|  |     } | ||
|  | 
 | ||
|  |     var md = child.md; | ||
|  |     if(md === null) { | ||
|  |       // create digest for OID signature types
 | ||
|  |       md = _createSignatureDigest({ | ||
|  |         signatureOid: child.signatureOid, | ||
|  |         type: 'certificate' | ||
|  |       }); | ||
|  | 
 | ||
|  |       // 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) { | ||
|  |       rval = _verifySignature({ | ||
|  |         certificate: cert, md: md, signature: child.signature | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     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) { | ||
|  |     // create digest for OID signature type
 | ||
|  |     cert.md = _createSignatureDigest({ | ||
|  |       signatureOid: cert.signatureOid, | ||
|  |       type: 'certificate' | ||
|  |     }); | ||
|  | 
 | ||
|  |     // 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(); | ||
|  |   var ibytes = asn1.toDer(capture.certIssuer); | ||
|  |   imd.update(ibytes.getBytes()); | ||
|  |   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); | ||
|  |   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(); | ||
|  |   var sbytes = asn1.toDer(capture.certSubject); | ||
|  |   smd.update(sbytes.getBytes()); | ||
|  |   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); | ||
|  |   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) { | ||
|  |     // create digest for OID signature type
 | ||
|  |     csr.md = _createSignatureDigest({ | ||
|  |       signatureOid: csr.signatureOid, | ||
|  |       type: 'certification request' | ||
|  |     }); | ||
|  | 
 | ||
|  |     // 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) { | ||
|  |       md = _createSignatureDigest({ | ||
|  |         signatureOid: csr.signatureOid, | ||
|  |         type: 'certification request' | ||
|  |       }); | ||
|  | 
 | ||
|  |       // 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) { | ||
|  |       rval = _verifySignature({ | ||
|  |         certificate: csr, md: md, signature: csr.signature | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     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; | ||
|  | }; |