Last commit july 5th

This commit is contained in:
2024-07-05 13:46:23 +02:00
parent dad0d86e8c
commit b0e4dfbb76
24982 changed files with 2621219 additions and 413 deletions

48
spa/node_modules/sigstore/dist/x509/cert.d.ts generated vendored Normal file
View File

@@ -0,0 +1,48 @@
/// <reference types="node" />
import * as sigstore from '../types/sigstore';
import { ASN1Obj } from '../util/asn1';
import { x509AuthorityKeyIDExtension, x509BasicConstraintsExtension, x509Extension, x509KeyUsageExtension, x509SCTExtension, x509SubjectAlternativeNameExtension, x509SubjectKeyIDExtension } from './ext';
interface SCTVerificationResult {
verified: boolean;
logID: Buffer;
}
export declare class x509Certificate {
root: ASN1Obj;
constructor(asn1: ASN1Obj);
static parse(cert: Buffer | string): x509Certificate;
get tbsCertificate(): ASN1Obj;
get version(): string;
get notBefore(): Date;
get notAfter(): Date;
get issuer(): Buffer;
get subject(): Buffer;
get publicKey(): Buffer;
get signatureAlgorithm(): string;
get signatureValue(): Buffer;
get extensions(): ASN1Obj[];
get extKeyUsage(): x509KeyUsageExtension | undefined;
get extBasicConstraints(): x509BasicConstraintsExtension | undefined;
get extSubjectAltName(): x509SubjectAlternativeNameExtension | undefined;
get extAuthorityKeyID(): x509AuthorityKeyIDExtension | undefined;
get extSubjectKeyID(): x509SubjectKeyIDExtension | undefined;
get extSCT(): x509SCTExtension | undefined;
get isCA(): boolean;
extension(oid: string): x509Extension | undefined;
verify(issuerCertificate?: x509Certificate): boolean;
validForDate(date: Date): boolean;
equals(other: x509Certificate): boolean;
verifySCTs(issuer: x509Certificate, logs: sigstore.TransparencyLogInstance[]): SCTVerificationResult[];
private clone;
private findExtension;
private checkRecognizedExtensions;
private get tbsCertificateObj();
private get signatureAlgorithmObj();
private get signatureValueObj();
private get versionObj();
private get issuerObj();
private get validityObj();
private get subjectObj();
private get subjectPublicKeyInfoObj();
private get extensionsObj();
}
export {};

242
spa/node_modules/sigstore/dist/x509/cert.js generated vendored Normal file
View File

@@ -0,0 +1,242 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.x509Certificate = void 0;
const util_1 = require("../util");
const asn1_1 = require("../util/asn1");
const stream_1 = require("../util/stream");
const ext_1 = require("./ext");
const EXTENSION_OID_SUBJECT_KEY_ID = '2.5.29.14';
const EXTENSION_OID_KEY_USAGE = '2.5.29.15';
const EXTENSION_OID_SUBJECT_ALT_NAME = '2.5.29.17';
const EXTENSION_OID_BASIC_CONSTRAINTS = '2.5.29.19';
const EXTENSION_OID_AUTHORITY_KEY_ID = '2.5.29.35';
const EXTENSION_OID_SCT = '1.3.6.1.4.1.11129.2.4.2';
// List of recognized critical extensions
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2
const RECOGNIZED_EXTENSIONS = [
EXTENSION_OID_KEY_USAGE,
EXTENSION_OID_BASIC_CONSTRAINTS,
EXTENSION_OID_SUBJECT_ALT_NAME,
];
const ECDSA_SIGNATURE_ALGOS = {
'1.2.840.10045.4.3.1': 'sha224',
'1.2.840.10045.4.3.2': 'sha256',
'1.2.840.10045.4.3.3': 'sha384',
'1.2.840.10045.4.3.4': 'sha512',
};
class x509Certificate {
constructor(asn1) {
this.root = asn1;
if (!this.checkRecognizedExtensions()) {
throw new Error('Certificate contains unrecognized critical extensions');
}
}
static parse(cert) {
const der = typeof cert === 'string' ? util_1.pem.toDER(cert) : cert;
const asn1 = asn1_1.ASN1Obj.parseBuffer(der);
return new x509Certificate(asn1);
}
get tbsCertificate() {
return this.tbsCertificateObj;
}
get version() {
// version number is the first element of the version context specific tag
const ver = this.versionObj.subs[0].toInteger();
return `v${(ver + BigInt(1)).toString()}`;
}
get notBefore() {
// notBefore is the first element of the validity sequence
return this.validityObj.subs[0].toDate();
}
get notAfter() {
// notAfter is the second element of the validity sequence
return this.validityObj.subs[1].toDate();
}
get issuer() {
return this.issuerObj.value;
}
get subject() {
return this.subjectObj.value;
}
get publicKey() {
return this.subjectPublicKeyInfoObj.toDER();
}
get signatureAlgorithm() {
const oid = this.signatureAlgorithmObj.subs[0].toOID();
return ECDSA_SIGNATURE_ALGOS[oid];
}
get signatureValue() {
// Signature value is a bit string, so we need to skip the first byte
return this.signatureValueObj.value.subarray(1);
}
get extensions() {
// The extension list is the first (and only) element of the extensions
// context specific tag
const extSeq = this.extensionsObj?.subs[0];
return extSeq?.subs || [];
}
get extKeyUsage() {
const ext = this.findExtension(EXTENSION_OID_KEY_USAGE);
return ext ? new ext_1.x509KeyUsageExtension(ext) : undefined;
}
get extBasicConstraints() {
const ext = this.findExtension(EXTENSION_OID_BASIC_CONSTRAINTS);
return ext ? new ext_1.x509BasicConstraintsExtension(ext) : undefined;
}
get extSubjectAltName() {
const ext = this.findExtension(EXTENSION_OID_SUBJECT_ALT_NAME);
return ext ? new ext_1.x509SubjectAlternativeNameExtension(ext) : undefined;
}
get extAuthorityKeyID() {
const ext = this.findExtension(EXTENSION_OID_AUTHORITY_KEY_ID);
return ext ? new ext_1.x509AuthorityKeyIDExtension(ext) : undefined;
}
get extSubjectKeyID() {
const ext = this.findExtension(EXTENSION_OID_SUBJECT_KEY_ID);
return ext ? new ext_1.x509SubjectKeyIDExtension(ext) : undefined;
}
get extSCT() {
const ext = this.findExtension(EXTENSION_OID_SCT);
return ext ? new ext_1.x509SCTExtension(ext) : undefined;
}
get isCA() {
const ca = this.extBasicConstraints?.isCA || false;
// If the KeyUsage extension is present, keyCertSign must be set
if (this.extKeyUsage) {
ca && this.extKeyUsage.keyCertSign;
}
return ca;
}
extension(oid) {
const ext = this.findExtension(oid);
return ext ? new ext_1.x509Extension(ext) : undefined;
}
verify(issuerCertificate) {
// Use the issuer's public key if provided, otherwise use the subject's
const publicKey = issuerCertificate?.publicKey || this.publicKey;
const key = util_1.crypto.createPublicKey(publicKey);
return util_1.crypto.verifyBlob(this.tbsCertificate.toDER(), key, this.signatureValue, this.signatureAlgorithm);
}
validForDate(date) {
return this.notBefore <= date && date <= this.notAfter;
}
equals(other) {
return this.root.toDER().equals(other.root.toDER());
}
verifySCTs(issuer, logs) {
let extSCT;
// Verifying the SCT requires that we remove the SCT extension and
// re-encode the TBS structure to DER -- this value is part of the data
// over which the signature is calculated. Since this is a destructive action
// we create a copy of the certificate so we can remove the SCT extension
// without affecting the original certificate.
const clone = this.clone();
// Intentionally not using the findExtension method here because we want to
// remove the the SCT extension from the certificate before calculating the
// PreCert structure
for (let i = 0; i < clone.extensions.length; i++) {
const ext = clone.extensions[i];
if (ext.subs[0].toOID() === EXTENSION_OID_SCT) {
extSCT = new ext_1.x509SCTExtension(ext);
// Remove the extension from the certificate
clone.extensions.splice(i, 1);
break;
}
}
if (!extSCT) {
throw new Error('Certificate does not contain SCT extension');
}
if (extSCT?.signedCertificateTimestamps?.length === 0) {
throw new Error('Certificate does not contain any SCTs');
}
// Construct the PreCert structure
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
const preCert = new stream_1.ByteStream();
// Calculate hash of the issuer's public key
const issuerId = util_1.crypto.hash(issuer.publicKey);
preCert.appendView(issuerId);
// Re-encodes the certificate to DER after removing the SCT extension
const tbs = clone.tbsCertificate.toDER();
preCert.appendUint24(tbs.length);
preCert.appendView(tbs);
// Calculate and return the verification results for each SCT
return extSCT.signedCertificateTimestamps.map((sct) => ({
logID: sct.logID,
verified: sct.verify(preCert.buffer, logs),
}));
}
// Creates a copy of the certificate with a new buffer
clone() {
const der = this.root.toDER();
const clone = Buffer.alloc(der.length);
der.copy(clone);
return x509Certificate.parse(clone);
}
findExtension(oid) {
// Find the extension with the given OID. The OID will always be the first
// element of the extension sequence
return this.extensions.find((ext) => ext.subs[0].toOID() === oid);
}
// A certificate should be considered invalid if it contains critical
// extensions that are not recognized
checkRecognizedExtensions() {
// The extension list is the first (and only) element of the extensions
// context specific tag
const extSeq = this.extensionsObj?.subs[0];
const exts = extSeq?.subs.map((ext) => new ext_1.x509Extension(ext));
// Check for unrecognized critical extensions
return (!exts ||
exts.every((ext) => !ext.critical || RECOGNIZED_EXTENSIONS.includes(ext.oid)));
}
/////////////////////////////////////////////////////////////////////////////
// The following properties use the documented x509 structure to locate the
// desired ASN.1 object
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.1
get tbsCertificateObj() {
// tbsCertificate is the first element of the certificate sequence
return this.root.subs[0];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.2
get signatureAlgorithmObj() {
// signatureAlgorithm is the second element of the certificate sequence
return this.root.subs[1];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.1.3
get signatureValueObj() {
// signatureValue is the third element of the certificate sequence
return this.root.subs[2];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.1
get versionObj() {
// version is the first element of the tbsCertificate sequence
return this.tbsCertificateObj.subs[0];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.4
get issuerObj() {
// issuer is the fourth element of the tbsCertificate sequence
return this.tbsCertificateObj.subs[3];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5
get validityObj() {
// version is the fifth element of the tbsCertificate sequence
return this.tbsCertificateObj.subs[4];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.6
get subjectObj() {
// subject is the sixth element of the tbsCertificate sequence
return this.tbsCertificateObj.subs[5];
}
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7
get subjectPublicKeyInfoObj() {
// subjectPublicKeyInfo is the seventh element of the tbsCertificate sequence
return this.tbsCertificateObj.subs[6];
}
// Extensions can't be located by index because their position varies. Instead,
// we need to find the extensions context specific tag
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.9
get extensionsObj() {
return this.tbsCertificateObj.subs.find((sub) => sub.tag.isContextSpecific(0x03));
}
}
exports.x509Certificate = x509Certificate;

42
spa/node_modules/sigstore/dist/x509/ext.d.ts generated vendored Normal file
View File

@@ -0,0 +1,42 @@
/// <reference types="node" />
import { ASN1Obj } from '../util/asn1';
import { SignedCertificateTimestamp } from './sct';
export declare class x509Extension {
protected root: ASN1Obj;
constructor(asn1: ASN1Obj);
get oid(): string;
get critical(): boolean;
get value(): Buffer;
get valueObj(): ASN1Obj;
protected get extnValueObj(): ASN1Obj;
}
export declare class x509BasicConstraintsExtension extends x509Extension {
get isCA(): boolean;
get pathLenConstraint(): bigint | undefined;
private get sequence();
}
export declare class x509KeyUsageExtension extends x509Extension {
get digitalSignature(): boolean;
get keyCertSign(): boolean;
get crlSign(): boolean;
private get bitString();
}
export declare class x509SubjectAlternativeNameExtension extends x509Extension {
get rfc822Name(): string | undefined;
get uri(): string | undefined;
otherName(oid: string): string | undefined;
private findGeneralName;
private get generalNames();
}
export declare class x509AuthorityKeyIDExtension extends x509Extension {
get keyIdentifier(): Buffer | undefined;
private findSequenceMember;
private get sequence();
}
export declare class x509SubjectKeyIDExtension extends x509Extension {
get keyIdentifier(): Buffer;
}
export declare class x509SCTExtension extends x509Extension {
constructor(asn1: ASN1Obj);
get signedCertificateTimestamps(): SignedCertificateTimestamp[];
}

145
spa/node_modules/sigstore/dist/x509/ext.js generated vendored Normal file
View File

@@ -0,0 +1,145 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.x509SCTExtension = exports.x509SubjectKeyIDExtension = exports.x509AuthorityKeyIDExtension = exports.x509SubjectAlternativeNameExtension = exports.x509KeyUsageExtension = exports.x509BasicConstraintsExtension = exports.x509Extension = void 0;
const stream_1 = require("../util/stream");
const sct_1 = require("./sct");
// https://www.rfc-editor.org/rfc/rfc5280#section-4.1
class x509Extension {
constructor(asn1) {
this.root = asn1;
}
get oid() {
return this.root.subs[0].toOID();
}
get critical() {
// The critical field is optional and will be the second element of the
// extension sequence if present. Default to false if not present.
return this.root.subs.length === 3 ? this.root.subs[1].toBoolean() : false;
}
get value() {
return this.extnValueObj.value;
}
get valueObj() {
return this.extnValueObj;
}
get extnValueObj() {
// The extnValue field will be the last element of the extension sequence
return this.root.subs[this.root.subs.length - 1];
}
}
exports.x509Extension = x509Extension;
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9
class x509BasicConstraintsExtension extends x509Extension {
get isCA() {
return this.sequence.subs[0].toBoolean();
}
get pathLenConstraint() {
return this.sequence.subs.length > 1
? this.sequence.subs[1].toInteger()
: undefined;
}
// The extnValue field contains a single sequence wrapping the isCA and
// pathLenConstraint.
get sequence() {
return this.extnValueObj.subs[0];
}
}
exports.x509BasicConstraintsExtension = x509BasicConstraintsExtension;
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
class x509KeyUsageExtension extends x509Extension {
get digitalSignature() {
return this.bitString[0] === 1;
}
get keyCertSign() {
return this.bitString[5] === 1;
}
get crlSign() {
return this.bitString[6] === 1;
}
// The extnValue field contains a single bit string which is a bit mask
// indicating which key usages are enabled.
get bitString() {
return this.extnValueObj.subs[0].toBitString();
}
}
exports.x509KeyUsageExtension = x509KeyUsageExtension;
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6
class x509SubjectAlternativeNameExtension extends x509Extension {
get rfc822Name() {
return this.findGeneralName(0x01)?.value.toString('ascii');
}
get uri() {
return this.findGeneralName(0x06)?.value.toString('ascii');
}
// Retrieve the value of an otherName with the given OID.
otherName(oid) {
const otherName = this.findGeneralName(0x00);
if (otherName === undefined) {
return undefined;
}
// The otherName is a sequence containing an OID and a value.
// Need to check that the OID matches the one we're looking for.
const otherNameOID = otherName.subs[0].toOID();
if (otherNameOID !== oid) {
return undefined;
}
// The otherNameValue is a sequence containing the actual value.
const otherNameValue = otherName.subs[1];
return otherNameValue.subs[0].value.toString('ascii');
}
findGeneralName(tag) {
return this.generalNames.find((gn) => gn.tag.isContextSpecific(tag));
}
// The extnValue field contains a sequence of GeneralNames.
get generalNames() {
return this.extnValueObj.subs[0].subs;
}
}
exports.x509SubjectAlternativeNameExtension = x509SubjectAlternativeNameExtension;
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.1
class x509AuthorityKeyIDExtension extends x509Extension {
get keyIdentifier() {
return this.findSequenceMember(0x00)?.value;
}
findSequenceMember(tag) {
return this.sequence.subs.find((el) => el.tag.isContextSpecific(tag));
}
// The extnValue field contains a single sequence wrapping the keyIdentifier
get sequence() {
return this.extnValueObj.subs[0];
}
}
exports.x509AuthorityKeyIDExtension = x509AuthorityKeyIDExtension;
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.2
class x509SubjectKeyIDExtension extends x509Extension {
get keyIdentifier() {
return this.extnValueObj.subs[0].value;
}
}
exports.x509SubjectKeyIDExtension = x509SubjectKeyIDExtension;
// https://www.rfc-editor.org/rfc/rfc6962#section-3.3
class x509SCTExtension extends x509Extension {
constructor(asn1) {
super(asn1);
}
get signedCertificateTimestamps() {
const buf = this.extnValueObj.subs[0].value;
const stream = new stream_1.ByteStream(buf);
// The overall list length is encoded in the first two bytes -- note this
// is the length of the list in bytes, NOT the number of SCTs in the list
const end = stream.getUint16() + 2;
const sctList = [];
while (stream.position < end) {
// Read the length of the next SCT
const sctLength = stream.getUint16();
// Slice out the bytes for the next SCT and parse it
const sct = stream.getBlock(sctLength);
sctList.push(sct_1.SignedCertificateTimestamp.parse(sct));
}
if (stream.position !== end) {
throw new Error('SCT list length does not match actual length');
}
return sctList;
}
}
exports.x509SCTExtension = x509SCTExtension;

26
spa/node_modules/sigstore/dist/x509/sct.d.ts generated vendored Normal file
View File

@@ -0,0 +1,26 @@
/// <reference types="node" />
import * as sigstore from '../types/sigstore';
interface SCTOptions {
version: number;
logID: Buffer;
timestamp: Buffer;
extensions: Buffer;
hashAlgorithm: number;
signatureAlgorithm: number;
signature: Buffer;
}
export declare class SignedCertificateTimestamp {
readonly version: number;
readonly logID: Buffer;
readonly timestamp: Buffer;
readonly extensions: Buffer;
readonly hashAlgorithm: number;
readonly signatureAlgorithm: number;
readonly signature: Buffer;
constructor(options: SCTOptions);
get datetime(): Date;
get algorithm(): string;
verify(preCert: Buffer, logs: sigstore.TransparencyLogInstance[]): boolean;
static parse(buf: Buffer): SignedCertificateTimestamp;
}
export {};

101
spa/node_modules/sigstore/dist/x509/sct.js generated vendored Normal file
View File

@@ -0,0 +1,101 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignedCertificateTimestamp = void 0;
const util_1 = require("../util");
const stream_1 = require("../util/stream");
class SignedCertificateTimestamp {
constructor(options) {
this.version = options.version;
this.logID = options.logID;
this.timestamp = options.timestamp;
this.extensions = options.extensions;
this.hashAlgorithm = options.hashAlgorithm;
this.signatureAlgorithm = options.signatureAlgorithm;
this.signature = options.signature;
}
get datetime() {
return new Date(Number(this.timestamp.readBigInt64BE()));
}
// Returns the hash algorithm used to generate the SCT's signature.
// https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4.1
get algorithm() {
switch (this.hashAlgorithm) {
case 0:
return 'none';
case 1:
return 'md5';
case 2:
return 'sha1';
case 3:
return 'sha224';
case 4:
return 'sha256';
case 5:
return 'sha384';
case 6:
return 'sha512';
default:
return 'unknown';
}
}
verify(preCert, logs) {
// Find key for the log reponsible for this signature
const log = logs.find((log) => log.logId?.keyId.equals(this.logID));
if (!log?.publicKey?.rawBytes) {
throw new Error(`No key found for log: ${this.logID.toString('base64')}`);
}
const publicKey = util_1.crypto.createPublicKey(log.publicKey.rawBytes);
// Assemble the digitally-signed struct (the data over which the signature
// was generated).
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
const stream = new stream_1.ByteStream();
stream.appendChar(this.version);
stream.appendChar(0x00); // SignatureType = certificate_timestamp(0)
stream.appendView(this.timestamp);
stream.appendUint16(0x01); // LogEntryType = precert_entry(1)
stream.appendView(preCert);
stream.appendUint16(this.extensions.byteLength);
if (this.extensions.byteLength > 0) {
stream.appendView(this.extensions);
}
return util_1.crypto.verifyBlob(stream.buffer, publicKey, this.signature, this.algorithm);
}
// Parses a SignedCertificateTimestamp from a buffer. SCTs are encoded using
// TLS encoding which means the fields and lengths of most fields are
// specified as part of the SCT and TLS specs.
// https://www.rfc-editor.org/rfc/rfc6962#section-3.2
// https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.4.1
static parse(buf) {
const stream = new stream_1.ByteStream(buf);
// Version - enum { v1(0), (255) }
const version = stream.getUint8();
// Log ID - struct { opaque key_id[32]; }
const logID = stream.getBlock(32);
// Timestamp - uint64
const timestamp = stream.getBlock(8);
// Extensions - opaque extensions<0..2^16-1>;
const extenstionLength = stream.getUint16();
const extensions = stream.getBlock(extenstionLength);
// Hash algo - enum { sha256(4), . . . (255) }
const hashAlgorithm = stream.getUint8();
// Signature algo - enum { anonymous(0), rsa(1), dsa(2), ecdsa(3), (255) }
const signatureAlgorithm = stream.getUint8();
// Signature - opaque signature<0..2^16-1>;
const sigLength = stream.getUint16();
const signature = stream.getBlock(sigLength);
// Check that we read the entire buffer
if (stream.position !== buf.length) {
throw new Error('SCT buffer length mismatch');
}
return new SignedCertificateTimestamp({
version,
logID,
timestamp,
extensions,
hashAlgorithm,
signatureAlgorithm,
signature,
});
}
}
exports.SignedCertificateTimestamp = SignedCertificateTimestamp;

8
spa/node_modules/sigstore/dist/x509/verify.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import { x509Certificate } from './cert';
interface VerifyCertificateChainOptions {
trustedCerts: x509Certificate[];
untrustedCert: x509Certificate;
validAt?: Date;
}
export declare function verifyCertificateChain(opts: VerifyCertificateChainOptions): x509Certificate[];
export {};

177
spa/node_modules/sigstore/dist/x509/verify.js generated vendored Normal file
View File

@@ -0,0 +1,177 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyCertificateChain = void 0;
/*
Copyright 2023 The Sigstore Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const error_1 = require("../error");
function verifyCertificateChain(opts) {
const verifier = new CertificateChainVerifier(opts);
return verifier.verify();
}
exports.verifyCertificateChain = verifyCertificateChain;
class CertificateChainVerifier {
constructor(opts) {
this.untrustedCert = opts.untrustedCert;
this.trustedCerts = opts.trustedCerts;
this.localCerts = dedupeCertificates([
...opts.trustedCerts,
opts.untrustedCert,
]);
this.validAt = opts.validAt || new Date();
}
verify() {
// Construct certificate path from leaf to root
const certificatePath = this.sort();
// Perform validation checks on each certificate in the path
this.checkPath(certificatePath);
// Return verified certificate path
return certificatePath;
}
sort() {
const leafCert = this.untrustedCert;
// Construct all possible paths from the leaf
let paths = this.buildPaths(leafCert);
// Filter for paths which contain a trusted certificate
paths = paths.filter((path) => path.some((cert) => this.trustedCerts.includes(cert)));
if (paths.length === 0) {
throw new error_1.VerificationError('No trusted certificate path found');
}
// Find the shortest of possible paths
const path = paths.reduce((prev, curr) => prev.length < curr.length ? prev : curr);
// Construct chain from shortest path
// Removes the last certificate in the path, which will be a second copy
// of the root certificate given that the root is self-signed.
return [leafCert, ...path].slice(0, -1);
}
// Recursively build all possible paths from the leaf to the root
buildPaths(certificate) {
const paths = [];
const issuers = this.findIssuer(certificate);
if (issuers.length === 0) {
throw new error_1.VerificationError('No valid certificate path found');
}
for (let i = 0; i < issuers.length; i++) {
const issuer = issuers[i];
// Base case - issuer is self
if (issuer.equals(certificate)) {
paths.push([certificate]);
continue;
}
// Recursively build path for the issuer
const subPaths = this.buildPaths(issuer);
// Construct paths by appending the issuer to each subpath
for (let j = 0; j < subPaths.length; j++) {
paths.push([issuer, ...subPaths[j]]);
}
}
return paths;
}
// Return all possible issuers for the given certificate
findIssuer(certificate) {
let issuers = [];
let keyIdentifier;
// Exit early if the certificate is self-signed
if (certificate.subject.equals(certificate.issuer)) {
if (certificate.verify()) {
return [certificate];
}
}
// If the certificate has an authority key identifier, use that
// to find the issuer
if (certificate.extAuthorityKeyID) {
keyIdentifier = certificate.extAuthorityKeyID.keyIdentifier;
// TODO: Add support for authorityCertIssuer/authorityCertSerialNumber
// though Fulcio doesn't appear to use these
}
// Find possible issuers by comparing the authorityKeyID/subjectKeyID
// or issuer/subject. Potential issuers are added to the result array.
this.localCerts.forEach((possibleIssuer) => {
if (keyIdentifier) {
if (possibleIssuer.extSubjectKeyID) {
if (possibleIssuer.extSubjectKeyID.keyIdentifier.equals(keyIdentifier)) {
issuers.push(possibleIssuer);
}
return;
}
}
// Fallback to comparing certificate issuer and subject if
// subjectKey/authorityKey extensions are not present
if (possibleIssuer.subject.equals(certificate.issuer)) {
issuers.push(possibleIssuer);
}
});
// Remove any issuers which fail to verify the certificate
issuers = issuers.filter((issuer) => {
try {
return certificate.verify(issuer);
}
catch (ex) {
return false;
}
});
return issuers;
}
checkPath(path) {
if (path.length < 1) {
throw new error_1.VerificationError('Certificate chain must contain at least one certificate');
}
// Check that all certificates are valid at the check date
const validForDate = path.every((cert) => cert.validForDate(this.validAt));
if (!validForDate) {
throw new error_1.VerificationError('Certificate is not valid or expired at the specified date');
}
// Ensure that all certificates beyond the leaf are CAs
const validCAs = path.slice(1).every((cert) => cert.isCA);
if (!validCAs) {
throw new error_1.VerificationError('Intermediate certificate is not a CA');
}
// Certificate's issuer must match the subject of the next certificate
// in the chain
for (let i = path.length - 2; i >= 0; i--) {
if (!path[i].issuer.equals(path[i + 1].subject)) {
throw new error_1.VerificationError('Incorrect certificate name chaining');
}
}
// Check pathlength constraints
for (let i = 0; i < path.length; i++) {
const cert = path[i];
// If the certificate is a CA, check the path length
if (cert.extBasicConstraints?.isCA) {
const pathLength = cert.extBasicConstraints.pathLenConstraint;
// The path length, if set, indicates how many intermediate
// certificates (NOT including the leaf) are allowed to follow. The
// pathLength constraint of any intermediate CA certificate MUST be
// greater than or equal to it's own depth in the chain (with an
// adjustment for the leaf certificate)
if (pathLength !== undefined && pathLength < i - 1) {
throw new error_1.VerificationError('Path length constraint exceeded');
}
}
}
}
}
// Remove duplicate certificates from the array
function dedupeCertificates(certs) {
for (let i = 0; i < certs.length; i++) {
for (let j = i + 1; j < certs.length; j++) {
if (certs[i].equals(certs[j])) {
certs.splice(j, 1);
j--;
}
}
}
return certs;
}