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