UNPKG

kuara-forge

Version:

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

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