• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.x509Certificate = void 0;
4const util_1 = require("../util");
5const asn1_1 = require("../util/asn1");
6const stream_1 = require("../util/stream");
7const ext_1 = require("./ext");
8const EXTENSION_OID_SUBJECT_KEY_ID = '2.5.29.14';
9const EXTENSION_OID_KEY_USAGE = '2.5.29.15';
10const EXTENSION_OID_SUBJECT_ALT_NAME = '2.5.29.17';
11const EXTENSION_OID_BASIC_CONSTRAINTS = '2.5.29.19';
12const EXTENSION_OID_AUTHORITY_KEY_ID = '2.5.29.35';
13const EXTENSION_OID_SCT = '1.3.6.1.4.1.11129.2.4.2';
14// List of recognized critical extensions
15// https://www.rfc-editor.org/rfc/rfc5280#section-4.2
16const RECOGNIZED_EXTENSIONS = [
17    EXTENSION_OID_KEY_USAGE,
18    EXTENSION_OID_BASIC_CONSTRAINTS,
19    EXTENSION_OID_SUBJECT_ALT_NAME,
20];
21const ECDSA_SIGNATURE_ALGOS = {
22    '1.2.840.10045.4.3.1': 'sha224',
23    '1.2.840.10045.4.3.2': 'sha256',
24    '1.2.840.10045.4.3.3': 'sha384',
25    '1.2.840.10045.4.3.4': 'sha512',
26};
27class x509Certificate {
28    constructor(asn1) {
29        this.root = asn1;
30        if (!this.checkRecognizedExtensions()) {
31            throw new Error('Certificate contains unrecognized critical extensions');
32        }
33    }
34    static parse(cert) {
35        const der = typeof cert === 'string' ? util_1.pem.toDER(cert) : cert;
36        const asn1 = asn1_1.ASN1Obj.parseBuffer(der);
37        return new x509Certificate(asn1);
38    }
39    get tbsCertificate() {
40        return this.tbsCertificateObj;
41    }
42    get version() {
43        // version number is the first element of the version context specific tag
44        const ver = this.versionObj.subs[0].toInteger();
45        return `v${(ver + BigInt(1)).toString()}`;
46    }
47    get notBefore() {
48        // notBefore is the first element of the validity sequence
49        return this.validityObj.subs[0].toDate();
50    }
51    get notAfter() {
52        // notAfter is the second element of the validity sequence
53        return this.validityObj.subs[1].toDate();
54    }
55    get issuer() {
56        return this.issuerObj.value;
57    }
58    get subject() {
59        return this.subjectObj.value;
60    }
61    get publicKey() {
62        return this.subjectPublicKeyInfoObj.toDER();
63    }
64    get signatureAlgorithm() {
65        const oid = this.signatureAlgorithmObj.subs[0].toOID();
66        return ECDSA_SIGNATURE_ALGOS[oid];
67    }
68    get signatureValue() {
69        // Signature value is a bit string, so we need to skip the first byte
70        return this.signatureValueObj.value.subarray(1);
71    }
72    get extensions() {
73        // The extension list is the first (and only) element of the extensions
74        // context specific tag
75        const extSeq = this.extensionsObj?.subs[0];
76        return extSeq?.subs || [];
77    }
78    get extKeyUsage() {
79        const ext = this.findExtension(EXTENSION_OID_KEY_USAGE);
80        return ext ? new ext_1.x509KeyUsageExtension(ext) : undefined;
81    }
82    get extBasicConstraints() {
83        const ext = this.findExtension(EXTENSION_OID_BASIC_CONSTRAINTS);
84        return ext ? new ext_1.x509BasicConstraintsExtension(ext) : undefined;
85    }
86    get extSubjectAltName() {
87        const ext = this.findExtension(EXTENSION_OID_SUBJECT_ALT_NAME);
88        return ext ? new ext_1.x509SubjectAlternativeNameExtension(ext) : undefined;
89    }
90    get extAuthorityKeyID() {
91        const ext = this.findExtension(EXTENSION_OID_AUTHORITY_KEY_ID);
92        return ext ? new ext_1.x509AuthorityKeyIDExtension(ext) : undefined;
93    }
94    get extSubjectKeyID() {
95        const ext = this.findExtension(EXTENSION_OID_SUBJECT_KEY_ID);
96        return ext ? new ext_1.x509SubjectKeyIDExtension(ext) : undefined;
97    }
98    get extSCT() {
99        const ext = this.findExtension(EXTENSION_OID_SCT);
100        return ext ? new ext_1.x509SCTExtension(ext) : undefined;
101    }
102    get isCA() {
103        const ca = this.extBasicConstraints?.isCA || false;
104        // If the KeyUsage extension is present, keyCertSign must be set
105        if (this.extKeyUsage) {
106            ca && this.extKeyUsage.keyCertSign;
107        }
108        return ca;
109    }
110    extension(oid) {
111        const ext = this.findExtension(oid);
112        return ext ? new ext_1.x509Extension(ext) : undefined;
113    }
114    verify(issuerCertificate) {
115        // Use the issuer's public key if provided, otherwise use the subject's
116        const publicKey = issuerCertificate?.publicKey || this.publicKey;
117        const key = util_1.crypto.createPublicKey(publicKey);
118        return util_1.crypto.verifyBlob(this.tbsCertificate.toDER(), key, this.signatureValue, this.signatureAlgorithm);
119    }
120    validForDate(date) {
121        return this.notBefore <= date && date <= this.notAfter;
122    }
123    equals(other) {
124        return this.root.toDER().equals(other.root.toDER());
125    }
126    verifySCTs(issuer, logs) {
127        let extSCT;
128        // Verifying the SCT requires that we remove the SCT extension and
129        // re-encode the TBS structure to DER -- this value is part of the data
130        // over which the signature is calculated. Since this is a destructive action
131        // we create a copy of the certificate so we can remove the SCT extension
132        // without affecting the original certificate.
133        const clone = this.clone();
134        // Intentionally not using the findExtension method here because we want to
135        // remove the the SCT extension from the certificate before calculating the
136        // PreCert structure
137        for (let i = 0; i < clone.extensions.length; i++) {
138            const ext = clone.extensions[i];
139            if (ext.subs[0].toOID() === EXTENSION_OID_SCT) {
140                extSCT = new ext_1.x509SCTExtension(ext);
141                // Remove the extension from the certificate
142                clone.extensions.splice(i, 1);
143                break;
144            }
145        }
146        if (!extSCT) {
147            throw new Error('Certificate does not contain SCT extension');
148        }
149        if (extSCT?.signedCertificateTimestamps?.length === 0) {
150            throw new Error('Certificate does not contain any SCTs');
151        }
152        // Construct the PreCert structure
153        // https://www.rfc-editor.org/rfc/rfc6962#section-3.2
154        const preCert = new stream_1.ByteStream();
155        // Calculate hash of the issuer's public key
156        const issuerId = util_1.crypto.hash(issuer.publicKey);
157        preCert.appendView(issuerId);
158        // Re-encodes the certificate to DER after removing the SCT extension
159        const tbs = clone.tbsCertificate.toDER();
160        preCert.appendUint24(tbs.length);
161        preCert.appendView(tbs);
162        // Calculate and return the verification results for each SCT
163        return extSCT.signedCertificateTimestamps.map((sct) => ({
164            logID: sct.logID,
165            verified: sct.verify(preCert.buffer, logs),
166        }));
167    }
168    // Creates a copy of the certificate with a new buffer
169    clone() {
170        const der = this.root.toDER();
171        const clone = Buffer.alloc(der.length);
172        der.copy(clone);
173        return x509Certificate.parse(clone);
174    }
175    findExtension(oid) {
176        // Find the extension with the given OID. The OID will always be the first
177        // element of the extension sequence
178        return this.extensions.find((ext) => ext.subs[0].toOID() === oid);
179    }
180    // A certificate should be considered invalid if it contains critical
181    // extensions that are not recognized
182    checkRecognizedExtensions() {
183        // The extension list is the first (and only) element of the extensions
184        // context specific tag
185        const extSeq = this.extensionsObj?.subs[0];
186        const exts = extSeq?.subs.map((ext) => new ext_1.x509Extension(ext));
187        // Check for unrecognized critical extensions
188        return (!exts ||
189            exts.every((ext) => !ext.critical || RECOGNIZED_EXTENSIONS.includes(ext.oid)));
190    }
191    /////////////////////////////////////////////////////////////////////////////
192    // The following properties use the documented x509 structure to locate the
193    // desired ASN.1 object
194    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1
195    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.1
196    get tbsCertificateObj() {
197        // tbsCertificate is the first element of the certificate sequence
198        return this.root.subs[0];
199    }
200    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.2
201    get signatureAlgorithmObj() {
202        // signatureAlgorithm is the second element of the certificate sequence
203        return this.root.subs[1];
204    }
205    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.3
206    get signatureValueObj() {
207        // signatureValue is the third element of the certificate sequence
208        return this.root.subs[2];
209    }
210    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.1
211    get versionObj() {
212        // version is the first element of the tbsCertificate sequence
213        return this.tbsCertificateObj.subs[0];
214    }
215    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.4
216    get issuerObj() {
217        // issuer is the fourth element of the tbsCertificate sequence
218        return this.tbsCertificateObj.subs[3];
219    }
220    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5
221    get validityObj() {
222        // version is the fifth element of the tbsCertificate sequence
223        return this.tbsCertificateObj.subs[4];
224    }
225    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.6
226    get subjectObj() {
227        // subject is the sixth element of the tbsCertificate sequence
228        return this.tbsCertificateObj.subs[5];
229    }
230    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7
231    get subjectPublicKeyInfoObj() {
232        // subjectPublicKeyInfo is the seventh element of the tbsCertificate sequence
233        return this.tbsCertificateObj.subs[6];
234    }
235    // Extensions can't be located by index because their position varies. Instead,
236    // we need to find the extensions context specific tag
237    // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.9
238    get extensionsObj() {
239        return this.tbsCertificateObj.subs.find((sub) => sub.tag.isContextSpecific(0x03));
240    }
241}
242exports.x509Certificate = x509Certificate;
243