UNPKG

@credo-ts/core

Version:

<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1

240 lines (236 loc) • 11.7 kB
import { TypedArrayEncoder } from "../../utils/TypedArrayEncoder.mjs"; import "../../utils/index.mjs"; import { asymmetricPublicJwkMatches } from "../kms/jwk/equals.mjs"; import "../kms/index.mjs"; import { CredoWebCryptoKey } from "../../crypto/webcrypto/CredoWebCryptoKey.mjs"; import { publicJwkToCryptoKeyAlgorithm, spkiToPublicJwk } from "../../crypto/webcrypto/utils/keyAlgorithmConversion.mjs"; import "../../crypto/webcrypto/utils/index.mjs"; import { CredoWebCrypto } from "../../crypto/webcrypto/CredoWebCrypto.mjs"; import "../../crypto/webcrypto/index.mjs"; import { createAuthorityKeyIdentifierExtension, createBasicConstraintsExtension, createCrlDistributionPointsExtension, createExtendedKeyUsagesExtension, createIssuerAlternativeNameExtension, createKeyUsagesExtension, createSubjectAlternativeNameExtension, createSubjectKeyIdentifierExtension } from "./utils/extensions.mjs"; import { X509Error } from "./X509Error.mjs"; import { convertName } from "./utils/nameConversion.mjs"; import { x509SignatureAlgorithmToJwa } from "./utils/signatureAlgorithm.mjs"; import "./utils/index.mjs"; import { AsnParser } from "@peculiar/asn1-schema"; import { SubjectPublicKeyInfo, id_ce_authorityKeyIdentifier, id_ce_extKeyUsage, id_ce_issuerAltName, id_ce_keyUsage, id_ce_subjectAltName, id_ce_subjectKeyIdentifier } from "@peculiar/asn1-x509"; import * as x509 from "@peculiar/x509"; //#region src/modules/x509/X509Certificate.ts let X509KeyUsage = /* @__PURE__ */ function(X509KeyUsage) { X509KeyUsage[X509KeyUsage["DigitalSignature"] = 1] = "DigitalSignature"; X509KeyUsage[X509KeyUsage["NonRepudiation"] = 2] = "NonRepudiation"; X509KeyUsage[X509KeyUsage["KeyEncipherment"] = 4] = "KeyEncipherment"; X509KeyUsage[X509KeyUsage["DataEncipherment"] = 8] = "DataEncipherment"; X509KeyUsage[X509KeyUsage["KeyAgreement"] = 16] = "KeyAgreement"; X509KeyUsage[X509KeyUsage["KeyCertSign"] = 32] = "KeyCertSign"; X509KeyUsage[X509KeyUsage["CrlSign"] = 64] = "CrlSign"; X509KeyUsage[X509KeyUsage["EncipherOnly"] = 128] = "EncipherOnly"; X509KeyUsage[X509KeyUsage["DecipherOnly"] = 256] = "DecipherOnly"; return X509KeyUsage; }({}); let X509ExtendedKeyUsage = /* @__PURE__ */ function(X509ExtendedKeyUsage) { X509ExtendedKeyUsage["ServerAuth"] = "1.3.6.1.5.5.7.3.1"; X509ExtendedKeyUsage["ClientAuth"] = "1.3.6.1.5.5.7.3.2"; X509ExtendedKeyUsage["CodeSigning"] = "1.3.6.1.5.5.7.3.3"; X509ExtendedKeyUsage["EmailProtection"] = "1.3.6.1.5.5.7.3.4"; X509ExtendedKeyUsage["TimeStamping"] = "1.3.6.1.5.5.7.3.8"; X509ExtendedKeyUsage["OcspSigning"] = "1.3.6.1.5.5.7.3.9"; X509ExtendedKeyUsage["MdlDs"] = "1.0.18013.5.1.2"; return X509ExtendedKeyUsage; }({}); var X509Certificate = class X509Certificate { constructor(options) { this.publicJwk = options.publicJwk; this.privateKey = options.privateKey; this.x509Certificate = options.x509Certificate; } set keyId(keyId) { this.publicJwk.keyId = keyId; } get keyId() { return this.publicJwk.keyId; } get hasKeyId() { return this.publicJwk.hasKeyId; } static fromRawCertificate(rawCertificate) { const certificate = new x509.X509Certificate(rawCertificate); return X509Certificate.parseCertificate(certificate); } static fromEncodedCertificate(encodedCertificate) { const certificate = new x509.X509Certificate(encodedCertificate); return X509Certificate.parseCertificate(certificate); } static parseCertificate(certificate) { const spki = AsnParser.parse(certificate.publicKey.rawData, SubjectPublicKeyInfo); const privateKey = certificate.privateKey ? new Uint8Array(certificate.privateKey.rawData) : void 0; return new X509Certificate({ publicJwk: spkiToPublicJwk(spki), privateKey, x509Certificate: certificate }); } getMatchingExtensions(objectIdentifier) { return this.x509Certificate.extensions.filter((e) => e.type === objectIdentifier); } get rawCertificate() { return new Uint8Array(this.x509Certificate.rawData); } get subjectAlternativeNames() { return this.getMatchingExtensions(id_ce_subjectAltName)?.flatMap((s) => s.names.items).map((i) => ({ type: i.type, value: i.value })) ?? []; } get issuerAlternativeNames() { return this.getMatchingExtensions(id_ce_issuerAltName)?.flatMap((i) => i.names.items).map((i) => ({ type: i.type, value: i.value })) ?? []; } get sanDnsNames() { return this.subjectAlternativeNames.filter((san) => san.type === "dns").map((san) => san.value); } get sanUriNames() { return this.subjectAlternativeNames.filter((ian) => ian.type === "url").map((ian) => ian.value); } get ianDnsNames() { return this.issuerAlternativeNames.filter((san) => san.type === "dns").map((san) => san.value); } get ianUriNames() { return this.issuerAlternativeNames.filter((ian) => ian.type === "url").map((ian) => ian.value); } get authorityKeyIdentifier() { const keyIds = this.getMatchingExtensions(id_ce_authorityKeyIdentifier)?.map((e) => e.keyId); if (keyIds && keyIds.length > 1) throw new X509Error("Multiple Authority Key Identifiers are not allowed"); return keyIds?.[0]; } get subjectKeyIdentifier() { const keyIds = this.getMatchingExtensions(id_ce_subjectKeyIdentifier)?.map((e) => e.keyId); if (keyIds && keyIds.length > 1) throw new X509Error("Multiple Subject Key Identifiers are not allowed"); return keyIds?.[0]; } get keyUsage() { const keyUsages = this.getMatchingExtensions(id_ce_keyUsage)?.map((e) => e.usages); if (keyUsages && keyUsages.length > 1) throw new X509Error("Multiple Key Usages are not allowed"); if (keyUsages) return Object.values(X509KeyUsage).filter((key) => typeof key === "number").filter((flagValue) => (keyUsages[0] & flagValue) === flagValue).map((flagValue) => flagValue); } get extendedKeyUsage() { const extendedKeyUsages = this.getMatchingExtensions(id_ce_extKeyUsage)?.map((e) => e.usages); if (extendedKeyUsages && extendedKeyUsages.length > 1) throw new X509Error("Multiple Key Usages are not allowed"); return extendedKeyUsages?.[0] ?? []; } isExtensionCritical(id) { const extension = this.getMatchingExtensions(id); if (!extension) throw new X509Error(`extension with id '${id}' is not found`); return !!extension[0].critical; } static async create(options, webCrypto) { const subjectPublicKey = options.subjectPublicKey ?? options.authorityKey; const isSelfSignedCertificate = asymmetricPublicJwkMatches(options.authorityKey.toJson(), subjectPublicKey.toJson()); const signingKey = new CredoWebCryptoKey(options.authorityKey, publicJwkToCryptoKeyAlgorithm(options.authorityKey), false, "private", ["sign"]); const publicKey = new CredoWebCryptoKey(subjectPublicKey, publicJwkToCryptoKeyAlgorithm(options.authorityKey), true, "public", ["verify"]); const issuerName = convertName(options.issuer); const extensions = []; extensions.push(createSubjectKeyIdentifierExtension(options.extensions?.subjectKeyIdentifier, { publicJwk: subjectPublicKey })); extensions.push(createKeyUsagesExtension(options.extensions?.keyUsage)); extensions.push(createExtendedKeyUsagesExtension(options.extensions?.extendedKeyUsage)); extensions.push(createAuthorityKeyIdentifierExtension(options.extensions?.authorityKeyIdentifier, { publicJwk: options.authorityKey })); extensions.push(createIssuerAlternativeNameExtension(options.extensions?.issuerAlternativeName)); extensions.push(createSubjectAlternativeNameExtension(options.extensions?.subjectAlternativeName)); extensions.push(createBasicConstraintsExtension(options.extensions?.basicConstraints)); extensions.push(createCrlDistributionPointsExtension(options.extensions?.crlDistributionPoints)); if (isSelfSignedCertificate) { if (options.subject) throw new X509Error("Do not provide a subject name when the certificate is supposed to be self signed"); const certificate = await x509.X509CertificateGenerator.createSelfSigned({ keys: { publicKey, privateKey: signingKey }, name: issuerName, notBefore: options.validity?.notBefore, notAfter: options.validity?.notAfter, extensions: extensions.filter((e) => e !== void 0), serialNumber: options.serialNumber }, webCrypto); const certificateInstance = X509Certificate.parseCertificate(certificate); if (subjectPublicKey.hasKeyId) certificateInstance.publicJwk.keyId = subjectPublicKey.keyId; return certificateInstance; } if (!options.subject) throw new X509Error("Provide a subject name when the certificate is not supposed to be self signed"); const subjectName = convertName(options.subject); const certificate = await x509.X509CertificateGenerator.create({ signingKey, publicKey, issuer: issuerName, subject: subjectName, notBefore: options.validity?.notBefore, notAfter: options.validity?.notAfter, extensions: extensions.filter((e) => e !== void 0) }, webCrypto); const certificateInstance = X509Certificate.parseCertificate(certificate); if (subjectPublicKey.hasKeyId) certificateInstance.publicJwk.keyId = subjectPublicKey.keyId; return certificateInstance; } get subject() { return this.x509Certificate.subject; } get issuer() { return this.x509Certificate.issuer; } async verify({ verificationDate = /* @__PURE__ */ new Date(), publicJwk, skipSignatureVerification = false }, webCrypto) { let publicCryptoKey; if (publicJwk) publicCryptoKey = new CredoWebCryptoKey(publicJwk, publicJwkToCryptoKeyAlgorithm(publicJwk, { alg: publicJwk.kty === "RSA" ? x509SignatureAlgorithmToJwa(this.x509Certificate.signatureAlgorithm) : void 0 }), true, "public", ["verify"]); const isSignatureValid = skipSignatureVerification ? true : await this.x509Certificate.verify({ signatureOnly: true, publicKey: publicCryptoKey }, webCrypto); const time = verificationDate.getTime(); const isNotBeforeValid = this.x509Certificate.notBefore.getTime() <= time; const isNotAfterValid = time <= this.x509Certificate.notAfter.getTime(); if (!isSignatureValid) throw new X509Error(`Certificate: '${this.x509Certificate.subject}' has an invalid signature`); if (!isNotBeforeValid) throw new X509Error(`Certificate: '${this.x509Certificate.subject}' used before it is allowed`); if (!isNotAfterValid) throw new X509Error(`Certificate: '${this.x509Certificate.subject}' used after it is allowed`); } /** * Get the thumbprint of the X509 certificate in hex format. */ async getThumbprintInHex(agentContext) { const thumbprint = await this.x509Certificate.getThumbprint(new CredoWebCrypto(agentContext)); return TypedArrayEncoder.toHex(new Uint8Array(thumbprint)); } /** * Get the data elements of the x509 certificate */ get data() { return { issuerName: this.x509Certificate.issuerName.toString(), issuer: this.x509Certificate.issuer, subjectName: this.x509Certificate.subjectName.toString(), subject: this.x509Certificate.subject, serialNumber: this.x509Certificate.serialNumber, pem: this.x509Certificate.toString(), notBefore: this.x509Certificate.notBefore, notAfter: this.x509Certificate.notAfter }; } getIssuerNameField(field) { return this.x509Certificate.issuerName.getField(field); } getSubjectNameField(field) { return this.x509Certificate.subjectName.getField(field); } /** * @param format the format to export to, defaults to `pem` */ toString(format) { return this.x509Certificate.toString(format ?? "pem"); } equal(certificate) { const parsedOther = new x509.X509Certificate(certificate.rawCertificate); return this.x509Certificate.equal(parsedOther); } }; //#endregion export { X509Certificate, X509ExtendedKeyUsage, X509KeyUsage }; //# sourceMappingURL=X509Certificate.mjs.map