fido2-lib
Version:
A library for performing FIDO 2.0 / WebAuthn functionality
2,005 lines (1,717 loc) • 188 kB
JavaScript
'use strict';
var tldts = require('tldts');
var punycode = require('punycode.js');
var jose = require('jose');
var pkijs$1 = require('pkijs');
var asn1js = require('asn1js');
var cborX = require('cbor-x');
var base64 = require('@hexagon/base64');
var platformCrypto = require('crypto');
var peculiarCrypto = require('@peculiar/webcrypto');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var cborX__namespace = /*#__PURE__*/_interopNamespaceDefault(cborX);
var platformCrypto__namespace = /*#__PURE__*/_interopNamespaceDefault(platformCrypto);
var peculiarCrypto__namespace = /*#__PURE__*/_interopNamespaceDefault(peculiarCrypto);
class Certificate {
constructor(cert) {
let decoded;
// Clean up base64 string
if (typeof cert === "string" || cert instanceof String) {
cert = cert.replace(/\r/g, "").trim();
decoded = ab2str(coerceToArrayBuffer$1(cert, "certificate"));
}
if (isPem(cert)) {
cert = pemToBase64(cert);
} else if (decoded && isPem(decoded)) {
cert = pemToBase64(decoded);
}
// Clean up certificate
if (typeof cert === "string" || cert instanceof String) {
cert = cert.replace(/\n/g, "");
}
cert = coerceToArrayBuffer$1(cert, "certificate");
if (cert.byteLength === 0) {
throw new Error("cert was empty (0 bytes)");
}
const asn1 = asn1js.fromBER(cert);
if (asn1.offset === -1) {
throw new Error("error parsing ASN.1");
}
this._cert = new pkijs.Certificate({ schema: asn1.result });
this.warning = new Map();
this.info = new Map();
}
getCommonName() {
return this.searchForCommonName(this._cert.subject.typesAndValues);
}
searchForCommonName(attributes) {
const X509_COMMON_NAME_KEY = "2.5.4.3";
// Search the attributes for the common name of the certificate
for (const attr of attributes) {
if (attr.type === X509_COMMON_NAME_KEY) {
return attr.value.valueBlock.value;
}
}
// Return empty string if not found
return "";
}
verify() {
const issuerCommonName = this.getIssuer();
const issuerCert = CertManager.getCertByCommonName(issuerCommonName);
const _issuerCert = issuerCert ? issuerCert._cert : undefined;
return this._cert.verify(_issuerCert)
.catch((err) => {
// who the hell throws a string?
if (typeof err === "string") {
err = new Error(err);
}
return Promise.reject(err);
});
}
async getPublicKey() {
const k = await this._cert.getPublicKey();
return k;
}
async getPublicKeyJwk() {
const publicKey = await this.getPublicKey();
// Covert CryptoKey to JWK
const publicKeyJwk = await webcrypto.subtle.exportKey("jwk", publicKey);
return publicKeyJwk;
}
getIssuer() {
return this.searchForCommonName(this._cert.issuer.typesAndValues);
}
getSerial(compatibility) {
if (compatibility === undefined) {
console.warn("[DEPRECATION WARNING] Please use getSerial(\"v2\").");
} else if (compatibility === "v1") {
console.warn("[DEPRECATION WARNING] Please migrate to getSerial(\"v2\") which will return just the serial number.");
}
return (compatibility === "v2")
? this._cert.serialNumber.valueBlock.toString()
: this.getCommonName();
}
getVersion() {
// x.509 versions:
// 0 = v1
// 1 = v2
// 2 = v3
return (this._cert.version + 1);
}
getSubject() {
const ret = new Map();
const subjectItems = this._cert.subject.typesAndValues;
for (const subject of subjectItems) {
const kv = resolveOid(subject.type,decodeValue(subject.value.valueBlock));
ret.set(kv.id, kv.value);
}
return ret;
}
getExtensions() {
const ret = new Map();
if (this._cert.extensions === undefined) return ret;
for (const ext of this._cert.extensions) {
let kv;
let v = ext.parsedValue || ext.extnValue;
try {
if (v.valueBlock) {
v = decodeValue(v.valueBlock);
}
kv = resolveOid(ext.extnID, v);
} catch (err) {
if (ext.critical === false) {
this.warning.set("x509-extension-error", ext.extnID + ": " + err.message);
continue;
} else {
throw err;
}
}
ret.set(kv.id, kv.value);
}
return ret;
}
}
function resolveOid(id, value) {
/* eslint complexity: ["off"] */
const ret = {
id,
value,
};
if (value && value.valueHex) value = value.valueHex;
let retMap;
switch (id) {
// FIDO
case "1.3.6.1.4.1.45724.2.1.1":
ret.id = "fido-u2f-transports";
ret.value = decodeU2FTransportType(value);
return ret;
case "1.3.6.1.4.1.45724.1.1.4":
ret.id = "fido-aaguid";
return ret;
// Subject
case "2.5.4.6":
ret.id = "country-name";
return ret;
case "2.5.4.10":
ret.id = "organization-name";
return ret;
case "2.5.4.11":
ret.id = "organizational-unit-name";
return ret;
case "2.5.4.3":
ret.id = "common-name";
return ret;
// cert attributes
case "2.5.29.14":
ret.id = "subject-key-identifier";
return ret;
case "2.5.29.15":
ret.id = "key-usage";
ret.value = decodeKeyUsage(value);
return ret;
case "2.5.29.19":
ret.id = "basic-constraints";
return ret;
case "2.5.29.35":
retMap = new Map();
ret.id = "authority-key-identifier";
retMap.set("key-identifier", decodeValue(value.keyIdentifier));
// TODO: other values
ret.value = retMap;
return ret;
case "2.5.29.32":
ret.id = "certificate-policies";
ret.value = decodeCertificatePolicies(value);
return ret;
case "1.3.6.1.4.1.311.21.31":
ret.id = "policy-qualifiers";
ret.value = decodePolicyQualifiers(value);
return ret;
case "2.5.29.37":
ret.id = "ext-key-usage";
ret.value = decodeExtKeyUsage(value);
return ret;
case "2.5.29.17":
ret.id = "subject-alt-name";
ret.value = decodeAltNames(value);
return ret;
case "1.3.6.1.5.5.7.1.1":
ret.id = "authority-info-access";
ret.value = decodeAuthorityInfoAccess(value);
return ret;
case "1.3.6.1.5.5.7.48.2":
ret.id = "cert-authority-issuers";
if (typeof value !== "object") {
throw new Error("expect cert-authority-issues to have Object as value");
}
ret.value = decodeGeneralName(value.type, value.value);
return ret;
case "1.3.6.1.5.5.7.2.2":
ret.id = "policy-qualifier";
ret.value = decodeValue(value.valueBlock);
return ret;
// TPM
case "2.23.133.8.3":
ret.id = "tcg-kp-aik-certificate";
return ret;
case "2.23.133.2.1":
ret.id = "tcg-at-tpm-manufacturer";
return ret;
case "2.23.133.2.2":
ret.id = "tcg-at-tpm-model";
return ret;
case "2.23.133.2.3":
ret.id = "tcg-at-tpm-version";
return ret;
// Yubico
case "1.3.6.1.4.1.41482.2":
ret.id = "yubico-device-id";
ret.value = resolveOid(ab2str(value)).id;
return ret;
case "1.3.6.1.4.1.41482.1.1":
ret.id = "Security Key by Yubico";
return ret;
case "1.3.6.1.4.1.41482.1.2":
ret.id = "YubiKey NEO/NEO-n";
return ret;
case "1.3.6.1.4.1.41482.1.3":
ret.id = "YubiKey Plus";
return ret;
case "1.3.6.1.4.1.41482.1.4":
ret.id = "YubiKey Edge";
return ret;
case "1.3.6.1.4.1.41482.1.5":
ret.id = "YubiKey 4/YubiKey 4 Nano";
return ret;
// TODO
// 1.3.6.1.4.1.45724.1.1.4 FIDO AAGUID
// basic-constraints Yubico FIDO2, ST Micro
// 2.5.29.35 ST Micro
// subject-key-identifier ST Micro
// 1.3.6.1.4.1.41482.3.3 Yubico Firmware version, encoded as 3 bytes, like: 040300 for 4.3.0
// 1.3.6.1.4.1.41482.3.7 Yubico serial number of the YubiKey, encoded as an integer
// 1.3.6.1.4.1.41482.3.8 Yubico two bytes, the first encoding pin policy and the second touch policy
// Pin policy: 01 - never, 02 - once per session, 03 - always
// Touch policy: 01 - never, 02 - always, 03 - cached for 15s
default:
return ret;
}
}
function decodeValue(valueBlock) {
// Use property-based detection instead of constructor.name to avoid issues
// with bundlers that rename classes (e.g., Deno v2 bundler)
// Check if it's a wrapper object with valueBlock property (OctetString, BmpString, Utf8String, Constructed)
// These are ASN.1 objects that wrap a valueBlock
if (valueBlock && typeof valueBlock === "object" && "valueBlock" in valueBlock && valueBlock.valueBlock) {
const innerBlock = valueBlock.valueBlock;
// Constructed wrapper: only decode the first element of the array
// (matches original case "Constructed")
if (Array.isArray(innerBlock.value) && innerBlock.value.length > 0) {
return decodeValue(innerBlock.value[0]);
}
// String wrapper types (BmpString, Utf8String): return the string value
// This must come before valueHex check because string types also have valueHex
// (matches original case "BmpString" and "Utf8String")
if ("value" in innerBlock && typeof innerBlock.value === "string") {
return innerBlock.value;
}
// OctetString wrapper: return the hex value
// (matches original case "OctetString")
if ("valueHex" in innerBlock && innerBlock.valueHex instanceof ArrayBuffer) {
return innerBlock.valueHex;
}
// Fallback to recursively decode the inner block
return decodeValue(innerBlock);
}
// Now handle value blocks (objects without valueBlock property)
// LocalIntegerValueBlock: has valueDec getter
// (matches original case "LocalIntegerValueBlock")
if ("valueDec" in valueBlock) {
return valueBlock.valueDec;
}
// LocalBitStringValueBlock: has unusedBits and valueHex
// (matches original case "LocalBitStringValueBlock")
if ("unusedBits" in valueBlock && "valueHex" in valueBlock && valueBlock.valueHex instanceof ArrayBuffer) {
return new Uint8Array(valueBlock.valueHex)[0];
}
// LocalOctetStringValueBlock: has isConstructed and valueHex but no unusedBits
// (matches original case "LocalOctetStringValueBlock")
if ("isConstructed" in valueBlock && "valueHex" in valueBlock && !("unusedBits" in valueBlock) && valueBlock.valueHex instanceof ArrayBuffer) {
return valueBlock.valueHex;
}
// LocalConstructedValueBlock: has value array, map over ALL elements
// (matches original case "LocalConstructedValueBlock")
// Must check for array specifically and NOT be a wrapper (no valueBlock property)
if (typeof valueBlock === "object" && !("valueBlock" in valueBlock) && "value" in valueBlock && Array.isArray(valueBlock.value)) {
return valueBlock.value.map((v) => decodeValue(v));
}
// String value blocks (LocalSimpleStringValueBlock, LocalUtf8StringValueBlock, LocalBmpStringValueBlock)
// These are value blocks (not wrappers) with a string value property
// (matches original case "LocalUtf8StringValueBlock", "LocalSimpleStringValueBlock", "LocalBmpStringValueBlock")
if ("value" in valueBlock && typeof valueBlock.value === "string") {
return valueBlock.value;
}
// If we can't determine the type, throw an error with helpful debug info
const blockType = Object.getPrototypeOf(valueBlock).constructor.name;
const availableProps = Object.keys(valueBlock).join(", ");
throw new TypeError(`unknown value type when decoding certificate: ${blockType} (properties: ${availableProps})`);
}
function decodeU2FTransportType(u2fRawTransports) {
const bitLen = 3;
const bitCount = 8 - bitLen - 1;
let type = (u2fRawTransports >> bitLen);
const ret = new Set();
for (let i = bitCount; i >= 0; i--) {
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-authenticator-transports-extension-v1.2-ps-20170411.html
if (type & 0x1) switch (i) {
case 0:
ret.add("bluetooth-classic");
break;
case 1:
ret.add("bluetooth-low-energy");
break;
case 2:
ret.add("usb");
break;
case 3:
ret.add("nfc");
break;
case 4:
ret.add("usb-internal");
break;
default:
throw new Error("unknown U2F transport type: " + type);
}
type >>= 1;
}
return ret;
}
function decodeKeyUsage(value) {
if (typeof value !== "number") {
throw new Error("certificate: expected 'keyUsage' value to be number");
}
const retSet = new Set();
if (value & 0x80) retSet.add("digitalSignature");
if (value & 0x40) retSet.add("contentCommitment");
if (value & 0x20) retSet.add("keyEncipherment");
if (value & 0x10) retSet.add("dataEncipherment");
if (value & 0x08) retSet.add("keyAgreement");
if (value & 0x04) retSet.add("keyCertSign");
if (value & 0x02) retSet.add("cRLSign");
if (value & 0x01) retSet.add("encipherOnly");
if (value & 0x01) retSet.add("decipherOnly");
return retSet;
}
function decodeExtKeyUsage(value) {
let keyPurposes = value.keyPurposes;
if (typeof value !== "object" || !Array.isArray(keyPurposes)) {
throw new Error("expected extended key purposes to be an Array");
}
keyPurposes = keyPurposes.map((oid) => resolveOid(oid).id);
return keyPurposes;
}
function decodeCertificatePolicies(value) {
if (value && Array.isArray(value.certificatePolicies)) {
value = value.certificatePolicies.map((_policy) => resolveOid(value.certificatePolicies[0].policyIdentifier, value.certificatePolicies[0].policyQualifiers));
}
return value;
}
function decodePolicyQualifiers(value) {
if (value && Array.isArray(value)) {
value = value.map((qual) => resolveOid(qual.policyQualifierId, qual.qualifier));
}
return value;
}
function decodeAltNames(value) {
if (typeof value !== "object" || !Array.isArray(value.altNames)) {
throw new Error("expected alternate names to be an Array");
}
let altNames = value.altNames;
altNames = altNames.map((name) => {
if (typeof name !== "object") {
throw new Error("expected alternate name to be an object");
}
if (name.type !== 4) {
throw new Error("expected all alternate names to be of general type");
}
if (typeof name.value !== "object" || !Array.isArray(name.value.typesAndValues)) {
throw new Error("malformatted alternate name");
}
return decodeGeneralName(name.type, name.value.typesAndValues);
});
return altNames;
}
function decodeAuthorityInfoAccess(v) {
if (typeof v !== "object" || !Array.isArray(v.accessDescriptions)) {
throw new Error("expected authority info access descriptions to be Array");
}
const retMap = new Map();
v.accessDescriptions.forEach((desc) => {
const { id, value } = resolveOid(desc.accessMethod, desc.accessLocation);
retMap.set(id, value);
});
return retMap;
}
function decodeGeneralName(type, v) {
if (typeof type !== "number") {
throw new Error("malformed general name in x509 certificate");
}
let nameList;
switch (type) {
case 0: // other name
throw new Error("general name 'other name' not supported");
case 1: // rfc822Name
throw new Error("general name 'rfc822Name' not supported");
case 2: // dNSName
throw new Error("general name 'dNSName' not supported");
case 3: // x400Address
throw new Error("general name 'x400Address' not supported");
case 4: // directoryName
if (!Array.isArray(v)) {
throw new Error("expected general name 'directory name' to be Array");
}
nameList = new Map();
v.forEach((val) => {
const { id, value } = resolveOid(val.type, decodeValue(val.value));
nameList.set(id, value);
});
return { directoryName: nameList };
case 5: // ediPartyName
throw new Error("general name 'ediPartyName' not supported");
case 6: // uniformResourceIdentifier
return { uniformResourceIdentifier: v };
case 7: // iPAddress
throw new Error("general name 'iPAddress' not supported");
case 8: // registeredID
throw new Error("general name 'registeredID' not supported");
default:
throw new Error("unknown general name type: " + type);
}
}
class CRL {
constructor(crl) {
// Clean up base64 string
if (typeof crl === "string" || crl instanceof String) {
crl = crl.replace(/\r/g, "");
}
if (isPem(crl)) {
crl = pemToBase64(crl);
}
crl = coerceToArrayBuffer$1(crl, "crl");
const asn1 = asn1js.fromBER(crl);
this._crl = new pkijs.CertificateRevocationList({
schema: asn1.result,
});
}
}
const certMap = new Map();
class CertManager {
static addCert(certBuf) {
const cert = new Certificate(certBuf);
const commonName = cert.getCommonName();
certMap.set(commonName, cert);
return true;
}
static getCerts() {
return new Map([...certMap]);
}
static getCertBySerial(serial) {
console.warn("[DEPRECATION WARNING] Please use CertManager.getCertByCommonName(commonName).");
return certMap.get(serial);
}
static getCertByCommonName(commonName) {
return certMap.get(commonName);
}
static removeAll() {
certMap.clear();
}
static async verifyCertChain(certs, roots, crls) {
if (!Array.isArray(certs) ||
certs.length < 1) {
throw new Error("expected 'certs' to be non-empty Array, got: " + certs);
}
certs = certs.map((cert) => {
if (!(cert instanceof Certificate)) {
// throw new Error("expected 'cert' to be an instance of Certificate");
cert = new Certificate(cert);
}
return cert._cert;
});
if (!Array.isArray(roots) ||
roots.length < 1) {
throw new Error("expected 'roots' to be non-empty Array, got: " + roots);
}
roots = roots.map((r) => {
if (!(r instanceof Certificate)) {
// throw new Error("expected 'root' to be an instance of Certificate");
r = new Certificate(r);
}
return r._cert;
});
crls = crls || [];
if (!Array.isArray(crls)) {
throw new Error("expected 'crls' to be undefined or Array, got: " + crls);
}
crls = crls.map((crl) => {
if (!(crl instanceof CRL)) {
// throw new Error("expected 'crl' to be an instance of Certificate");
crl = new CRL(crl);
}
return crl._crl;
});
const chain = new pkijs.CertificateChainValidationEngine({
trustedCerts: roots,
certs,
crls,
});
const res = await chain.verify();
if (!res.result) {
throw new Error(res.resultMessage);
} else {
return res;
}
}
}
const helpers = {
resolveOid,
};
/**
* Main COSE labels
* defined here: https://tools.ietf.org/html/rfc8152#section-7.1
* used by {@link fromCose}
*
* @private
*/
const coseLabels = {
1: {
name: "kty",
values: {
1: "OKP",
2: "EC",
3: "RSA",
},
},
2: {
name: "kid",
values: {},
},
3: {
name: "alg",
values: {
"-7": "ECDSA_w_SHA256",
/* "-8": "EdDSA", */
"-35": "ECDSA_w_SHA384",
"-36": "ECDSA_w_SHA512",
/*"-37": "RSASSA-PSS_w_SHA-256",
"-38": "RSASSA-PSS_w_SHA-384",
"-39": "RSASSA-PSS_w_SHA-512",*/
"-257": "RSASSA-PKCS1-v1_5_w_SHA256",
"-258": "RSASSA-PKCS1-v1_5_w_SHA384",
"-259": "RSASSA-PKCS1-v1_5_w_SHA512",
"-65535": "RSASSA-PKCS1-v1_5_w_SHA1",
},
},
4: {
name: "key_ops",
values: {},
},
5: {
name: "base_iv",
values: {},
},
};
/**
* Key specific COSE parameters
* used by {@link fromCose}
*
* @private
*/
const coseKeyParamList = {
// ECDSA key parameters
// defined here: https://tools.ietf.org/html/rfc8152#section-13.1.1
EC: {
"-1": {
name: "crv",
values: {
1: "P-256",
2: "P-384",
3: "P-521",
},
},
// value = Buffer
"-2": { name: "x" },
"-3": { name: "y" },
"-4": { name: "d" },
},
// Octet Key Pair key parameters
// defined here: https://datatracker.ietf.org/doc/html/rfc8152#section-13.2
OKP: {
"-1": {
name: "crv",
values: {
4: "X25519",
5: "X448",
6: "Ed25519",
7: "Ed448",
},
},
// value = Buffer
"-2": { name: "x" },
"-4": { name: "d" },
},
// RSA key parameters
// defined here: https://tools.ietf.org/html/rfc8230#section-4
RSA: {
// value = Buffer
"-1": { name: "n" },
"-2": { name: "e" },
"-3": { name: "d" },
"-4": { name: "p" },
"-5": { name: "q" },
"-6": { name: "dP" },
"-7": { name: "dQ" },
"-8": { name: "qInv" },
"-9": { name: "other" },
"-10": { name: "r_i" },
"-11": { name: "d_i" },
"-12": { name: "t_i" },
},
};
/**
* Maps COSE algorithm identifier to JWK alg
* used by {@link fromCose}
*
* @private
*/
const algToJWKAlg = {
"RSASSA-PKCS1-v1_5_w_SHA256": "RS256",
"RSASSA-PKCS1-v1_5_w_SHA384": "RS384",
"RSASSA-PKCS1-v1_5_w_SHA512": "RS512",
"RSASSA-PKCS1-v1_5_w_SHA1": "RS256",
/*
PS256-512 is untested
"RSASSA-PSS_w_SHA-256": "PS256",
"RSASSA-PSS_w_SHA-384": "PS384",
"RSASSA-PSS_w_SHA-512": "PS512",*/
"ECDSA_w_SHA256": "ES256",
"ECDSA_w_SHA384": "ES384",
"ECDSA_w_SHA512": "ES512",
/*
EdDSA is untested and unfinished
"EdDSA": "EdDSA" */
};
/**
* Maps Cose algorithm identifier or JWK.alg to webcrypto algorithm identifier
* used by {@link setAlgorithm}
*
* @private
*/
const algorithmInputMap = {
/* Cose Algorithm identifier to Webcrypto algorithm name */
"RSASSA-PKCS1-v1_5_w_SHA256": "RSASSA-PKCS1-v1_5",
"RSASSA-PKCS1-v1_5_w_SHA384": "RSASSA-PKCS1-v1_5",
"RSASSA-PKCS1-v1_5_w_SHA512": "RSASSA-PKCS1-v1_5",
"RSASSA-PKCS1-v1_5_w_SHA1": "RSASSA-PKCS1-v1_5",
/*"RSASSA-PSS_w_SHA-256": "RSASSA-PSS",
"RSASSA-PSS_w_SHA-384": "RSASSA-PSS",
"RSASSA-PSS_w_SHA-512": "RSASSA-PSS",*/
"ECDSA_w_SHA256": "ECDSA",
"ECDSA_w_SHA384": "ECDSA",
"ECDSA_w_SHA512": "ECDSA",
/*"EdDSA": "EdDSA",*/
/* JWK alg to Webcrypto algorithm name */
"RS256": "RSASSA-PKCS1-v1_5",
"RS384": "RSASSA-PKCS1-v1_5",
"RS512": "RSASSA-PKCS1-v1_5",
/*"PS256": "RSASSA-PSS",
"PS384": "RSASSA-PSS",
"PS512": "RSASSA-PSS",*/
"ES384": "ECDSA",
"ES256": "ECDSA",
"ES512": "ECDSA",
/*"EdDSA": "EdDSA",*/
};
/**
* Maps Cose algorithm identifier webcrypto hash name
* used by {@link setAlgorithm}
*
* @private
*/
const inputHashMap = {
/* Cose Algorithm identifier to Webcrypto hash name */
"RSASSA-PKCS1-v1_5_w_SHA256": "SHA-256",
"RSASSA-PKCS1-v1_5_w_SHA384": "SHA-384",
"RSASSA-PKCS1-v1_5_w_SHA512": "SHA-512",
"RSASSA-PKCS1-v1_5_w_SHA1": "SHA-1",
/*"RSASSA-PSS_w_SHA256": "SHA-256",
"RSASSA-PSS_w_SHA384": "SHA-384",
"RSASSA-PSS_w_SHA512": "SHA-512",*/
"ECDSA_w_SHA256": "SHA-256",
"ECDSA_w_SHA384": "SHA-384",
"ECDSA_w_SHA512": "SHA-512",
/* "EdDSA": "EdDSA", */
};
/**
* Class representing a generic public key,
* with utility functions to convert between different formats
* using Webcrypto
*
* @package
*
*/
class PublicKey {
/**
* Create a empty public key
*
* @returns {CryptoKey}
*/
constructor() {
/**
* Internal reference to imported PEM string
* @type {string}
* @private
*/
this._original_pem = undefined;
/**
* Internal reference to imported JWK object
* @type {object}
* @private
*/
this._original_jwk = undefined;
/**
* Internal reference to imported Cose data
* @type {object}
* @private
*/
this._original_cose = undefined;
/**
* Internal reference to algorithm, should be of RsaHashedImportParams or EcKeyImportParams format
* @type {object}
* @private
*/
this._alg = undefined;
/**
* Internal reference to a CryptoKey object
* @type {object}
* @private
*/
this._key = undefined;
}
/**
* Import a CryptoKey, makes basic checks and throws on failure
*
* @public
* @param {CryptoKey} key - CryptoKey to import
* @param {object} [alg] - Algorithm override
*
* @returns {CryptoKey} - Returns this for chaining
*/
fromCryptoKey(key, alg) {
// Throw on missing key
if (!key) {
throw new TypeError("No key passed");
}
// Allow a CryptoKey to be passed through the constructor
if (key && (!key.type || key.type !== "public")) {
throw new TypeError("Invalid argument passed to fromCryptoKey, should be instance of CryptoKey with type public");
}
// Store key
this._key = key;
// Store internal representation of algorithm
this.setAlgorithm(key.algorithm);
// Update algorithm if passed
if (alg) {
this.setAlgorithm(alg);
}
return this;
}
/**
* Import public key from SPKI PEM. Throws on any type of failure.
*
* @async
* @public
* @param {string} pem - PEM formatted string
* @return {Promise<PublicKey>} - Returns itself for chaining
*/
async fromPem(pem, hashName) {
// Convert PEM to Base64
let base64ber,
ber;
// Clean up base64 string
if (typeof pem === "string" || pem instanceof String) {
pem = pem.replace(/\r/g, "");
}
if (isPem(pem)) {
base64ber = pemToBase64(pem);
ber = coerceToArrayBuffer$1(base64ber, "base64ber");
} else {
throw new Error("Supplied key is not in PEM format");
}
if (ber.byteLength === 0) {
throw new Error("Supplied key ber was empty (0 bytes)");
}
// Extract x509 information
const asn1 = asn1js.fromBER(ber);
if (asn1.offset === -1) {
throw new Error("error parsing ASN.1");
}
let keyInfo = new pkijs.PublicKeyInfo({ schema: asn1.result });
const algorithm = {};
// Extract algorithm from key info
if (keyInfo.algorithm.algorithmId === "1.2.840.10045.2.1") {
algorithm.name = "ECDSA";
// Use parsedKey to extract namedCurve if present, else default to P-256
const parsedKey = keyInfo.parsedKey;
if (parsedKey && parsedKey.namedCurve === "1.2.840.10045.3.1.7") { // NIST P-256, secp256r1
algorithm.namedCurve = "P-256";
} else if (parsedKey && parsedKey.namedCurve === "1.3.132.0.34") { // NIST P-384, secp384r1
algorithm.namedCurve = "P-384";
} else if (parsedKey && parsedKey.namedCurve === "1.3.132.0.35") { // NIST P-512, secp521r1
algorithm.namedCurve = "P-512";
} else {
algorithm.namedCurve = "P-256";
}
// Handle RSA
} else if (keyInfo.algorithm.algorithmId === "1.2.840.113549.1.1.1") {
algorithm.name = "RSASSA-PKCS1-v1_5";
// Default hash to SHA-256
algorithm.hash = hashName || "SHA-256";
}
this.setAlgorithm(algorithm);
// Import key using webcrypto
let importSPKIResult;
try {
importSPKIResult = await webcrypto.subtle.importKey("spki", ber, algorithm, true, ["verify"]);
} catch (_e1) {
throw new Error("Unsupported key format", _e1);
}
// Store references
this._original_pem = pem;
this._key = importSPKIResult;
return this;
}
/**
* Import public key from JWK. Throws on any type of failure.
*
* @async
* @public
* @param {object} jwk - JWK object
* @return {Promise<PublicKey>} - Returns itself for chaining
*/
async fromJWK(jwk, extractable) {
// Copy JWK
const jwkCopy = JSON.parse(JSON.stringify(jwk));
// Force extractable flag if specified
if (
typeof extractable !== "undefined" &&
typeof extractable === "boolean"
) {
jwkCopy.ext = extractable;
}
// Store alg
this.setAlgorithm(jwkCopy);
// Import jwk with Jose
this._original_jwk = jwk;
const generatedKey = await webcrypto.subtle.importKey(
"jwk",
jwkCopy,
this.getAlgorithm(),
true,
["verify"]
);
this._key = generatedKey;
return this;
}
/**
* Import public key from COSE data. Throws on any type of failure.
*
* Internally this function converts COSE to a JWK, then calls .fromJwk() to import key to CryptoKey
*
* @async
* @public
* @param {object} cose - COSE data
* @return {Promise<PublicKey>} - Returns itself for chaining
*/
async fromCose(cose) {
if (typeof cose !== "object") {
throw new TypeError(
"'cose' argument must be an object, probably an Buffer conatining valid COSE",
);
}
this._cose = coerceToArrayBuffer$1(cose, "coseToJwk");
let parsedCose;
try {
// In the current state, the "cose" parameter can contain not only the actual cose (= public key) but also extensions.
// Both are CBOR encoded entries, so you can treat and evaluate the "cose" parameter accordingly.
// "fromCose" is called from a context that contains an active AT flag (attestation), so the first CBOR entry is the actual cose.
// "tools.cbor.decode" will fail when multiple entries are provided (e.g. cose + at least one extension), so "decodeMultiple" is the sollution.
cborX__namespace.decodeMultiple(
new Uint8Array(cose),
cborObject => {
parsedCose = cborObject;
return false;
}
);
} catch (err) {
throw new Error(
"couldn't parse authenticator.authData.attestationData CBOR: " +
err,
);
}
if (typeof parsedCose !== "object") {
throw new Error(
"invalid parsing of authenticator.authData.attestationData CBOR",
);
}
const coseMap = new Map(Object.entries(parsedCose));
const extraMap = new Map();
const retKey = {};
// parse main COSE labels
for (const kv of coseMap) {
const key = kv[0].toString();
let value = kv[1].toString();
if (!coseLabels[key]) {
extraMap.set(kv[0], kv[1]);
continue;
}
const name = coseLabels[key].name;
if (coseLabels[key].values[value]) {
value = coseLabels[key].values[value];
}
retKey[name] = value;
}
const keyParams = coseKeyParamList[retKey.kty];
// parse key-specific parameters
for (const kv of extraMap) {
const key = kv[0].toString();
let value = kv[1];
if (!keyParams[key]) {
throw new Error(
"unknown COSE key label: " + retKey.kty + " " + key,
);
}
const name = keyParams[key].name;
if (keyParams[key].values) {
value = keyParams[key].values[value.toString()];
}
value = coerceToBase64Url(value, "coseToJwk");
retKey[name] = value;
}
// Store reference to original cose object
this._original_cose = cose;
// Set algorithm from cose JWK-like
this.setAlgorithm(retKey);
// Convert cose algorithm identifier to jwk algorithm name
retKey.alg = algToJWKAlg[retKey.alg];
await this.fromJWK(retKey, true);
return this;
}
/**
* Exports public key to PEM.
* - Reuses original PEM string if present.
* - Possible to force regeneration of PEM string by setting 'forcedExport' parameter to true
* - Throws on any kind of failure
*
* @async
* @public
* @param {boolean} [forcedExport] - Force regeneration of PEM string even if original PEM-string is available
* @return {Promise<string>} - Returns PEM string
*/
async toPem(forcedExport) {
if (this._original_pem && !forcedExport) {
return this._original_pem;
} else if (this.getKey()) {
let pemResult = abToPem("PUBLIC KEY",await webcrypto.subtle.exportKey("spki", this.getKey()));
return pemResult;
} else {
throw new Error("No key information available");
}
}
/**
* Exports public key to JWK.
* - Only works if original jwk from 'fromJwk()' is available
* - Throws on any kind of failure
*
* @public
* @return {object} - Returns JWK object
*/
toJwk() {
if (this._original_jwk) {
return this._original_jwk;
} else {
throw new Error("No usable key information available");
}
}
/**
* Exports public key to COSE data
* - Only works if original cose data from 'fromCose()' is available
* - Throws on any kind of failure
*
* @public
* @return {object} - Returns COSE data object
*/
toCose() {
if (this._original_cose) {
return this._original_cose;
} else {
throw new Error("No usable key information available");
}
}
/**
* Returns internal key in CryptoKey format
* - Mainly intended for internal use
* - Throws if internal CryptoKey does not exist
*
* @public
* @return {CryptoKey} - Internal CryptoKey instance, or undefined
*/
getKey() {
if (this._key) {
return this._key;
} else {
throw new Error("Key data not available");
}
}
/**
* Returns internal algorithm, which should be of one of the following formats
* - RsaHashedImportParams
* - EcKeyImportParams
* - undefined
*
* @public
* @return {object|undefined} - Internal algorithm representation, or undefined
*/
getAlgorithm() {
return this._alg;
}
/**
* Sets internal algorithm identifier in format used by webcrypto, should be one of
* - Allows adding missing properties
* - Makes sure `alg.hash` is is `{ hash: { name: 'foo'} }` format
* - Syncs back updated algorithm to this._key
*
* @public
* @param {object} - RsaHashedImportParams, EcKeyImportParams, JWK or JWK-like
* @return {object|undefined} - Internal algorithm representation, or undefined
*/
setAlgorithm(algorithmInput) {
let algorithmOutput = this._alg || {};
// Check for name if not already present
// From Algorithm object
if (algorithmInput.name) {
algorithmOutput.name = algorithmInput.name;
// JWK or JWK-like
} else if (algorithmInput.alg) {
const algMapResult = algorithmInputMap[algorithmInput.alg];
if (algMapResult) {
algorithmOutput.name = algMapResult;
}
}
// Check for hash if not already present
// From Algorithm object
if (algorithmInput.hash) {
if (algorithmInput.hash.name) {
algorithmOutput.hash = algorithmInput.hash;
} else {
algorithmOutput.hash = { name: algorithmInput.hash }; }
// Try to extract hash from JWK-like .alg
} else if (algorithmInput.alg) {
let hashMapResult = inputHashMap[algorithmInput.alg];
if (hashMapResult) {
algorithmOutput.hash = { name: hashMapResult };
}
}
// Try to extract namedCurve if not already present
if (algorithmInput.namedCurve) {
algorithmOutput.namedCurve = algorithmInput.namedCurve;
} else if (algorithmInput.crv) {
algorithmOutput.namedCurve = algorithmInput.crv;
}
// Set this._alg if any algorithm properties existed, or were added
if (Object.keys(algorithmOutput).length > 0) {
this._alg = algorithmOutput;
// Sync algorithm hash to CryptoKey
if (this._alg.hash && this._key) {
this._key.algorithm.hash = this._alg.hash;
}
}
}
}
/**
* Utility function to convert a cose algorithm to string
*
* @package
*
* @param {string|number} - Cose algorithm
*/
function coseAlgToStr(alg) {
if (typeof alg !== "number") {
throw new TypeError("expected 'alg' to be a number, got: " + alg);
}
const algValues = coseLabels["3"].values;
const mapResult = algValues[alg];
if (!mapResult) {
throw new Error("'alg' is not a valid COSE algorithm number");
}
return algValues[alg];
}
/**
* Utility function to convert a cose hashing algorithm to string
*
* @package
*
* @param {string|number} - Cose algorithm
*/
function coseAlgToHashStr(alg) {
if (typeof alg === "number") alg = coseAlgToStr(alg);
if (typeof alg !== "string") {
throw new Error("'alg' is not a string or a valid COSE algorithm number");
}
const mapResult = inputHashMap[alg];
if (!mapResult) {
throw new Error("'alg' is not a valid COSE algorithm");
}
return inputHashMap[alg];
}
// External dependencies
let webcrypto;
if (typeof self !== "undefined" && "crypto" in self) {
// Always use crypto if available natively (browser / Deno)
webcrypto = self.crypto;
} else {
// Always use node webcrypto if available ( >= 16.0 )
if (platformCrypto__namespace && platformCrypto__namespace.webcrypto) {
webcrypto = platformCrypto__namespace.webcrypto;
} else {
// Fallback to @peculiar/webcrypto
webcrypto = new peculiarCrypto__namespace.Crypto();
}
}
// Set up pkijs
const pkijs = {
setEngine: pkijs$1.setEngine,
CryptoEngine: pkijs$1.CryptoEngine,
Certificate: pkijs$1.Certificate,
CertificateRevocationList: pkijs$1.CertificateRevocationList,
CertificateChainValidationEngine: pkijs$1.CertificateChainValidationEngine,
PublicKeyInfo: pkijs$1.PublicKeyInfo,
};
pkijs.setEngine(
"newEngine",
webcrypto,
new pkijs.CryptoEngine({
name: "",
crypto: webcrypto,
subtle: webcrypto.subtle,
})
);
function extractBigNum(fullArray, start, end, expectedLength) {
let num = fullArray.slice(start, end);
if (num.length !== expectedLength) {
num = Array(expectedLength)
.fill(0)
.concat(...num)
.slice(num.length);
}
return num;
}
/*
Convert signature from DER to raw
Expects Uint8Array
*/
function derToRaw(signature) {
const rStart = 4;
const rEnd = rStart + signature[3];
const sStart = rEnd + 2;
return new Uint8Array([
...extractBigNum(signature, rStart, rEnd, 32),
...extractBigNum(signature, sStart, signature.length, 32),
]);
}
function isAndroidFacetId(str) {
return str.startsWith("android:apk-key-hash:");
}
function isIOSFacetId(str) {
return str.startsWith("ios:bundle-id:");
}
function checkOrigin(str) {
if (!str) throw new Error("Empty Origin");
if (isAndroidFacetId(str) || isIOSFacetId(str)) {
return str;
}
const originUrl = new URL(str);
const origin = originUrl.origin;
if (origin !== str) {
throw new Error("origin was malformatted");
}
const isLocalhost =
originUrl.hostname == "localhost" ||
originUrl.hostname.endsWith(".localhost");
if (originUrl.protocol !== "https:" && !isLocalhost) {
throw new Error("origin should be https");
}
if (
(!validDomainName(originUrl.hostname) ||
!validEtldPlusOne(originUrl.hostname)) &&
!isLocalhost
) {
throw new Error("origin is not a valid eTLD+1");
}
return origin;
}
function checkUrl(value, name, rules = {}) {
if (!name) {
throw new TypeError("name not specified in checkUrl");
}
if (typeof value !== "string") {
throw new Error(`${name} must be a string`);
}
let urlValue = null;
try {
urlValue = new URL(value);
} catch (_err) {
throw new Error(`${name} is not a valid eTLD+1/url`);
}
if (!value.startsWith("http")) {
throw new Error(`${name} must be http protocol`);
}
if (!rules.allowHttp && urlValue.protocol !== "https:") {
throw new Error(`${name} should be https`);
}
// origin: base url without path including /
if (!rules.allowPath && (value.endsWith("/") || urlValue.pathname !== "/")) {
// urlValue adds / in path always
throw new Error(`${name} should not include path in url`);
}
if (!rules.allowHash && urlValue.hash) {
throw new Error(`${name} should not include hash in url`);
}
if (!rules.allowCred && (urlValue.username || urlValue.password)) {
throw new Error(`${name} should not include credentials in url`);
}
if (!rules.allowQuery && urlValue.search) {
throw new Error(`${name} should not include query string in url`);
}
return value;
}
function validEtldPlusOne(value) {
// Parse domain name
const result = tldts.parse(value, { allowPrivateDomains: true });
// Require valid public suffix
if (result.publicSuffix === null) {
return false;
}
// Require valid hostname
if (result.domainWithoutSuffix === null) {
return false;
}
return true;
}
function validDomainName(value) {
// Before we can validate we need to take care of IDNs with unicode chars.
const ascii = punycode.toASCII(value);
if (ascii.length < 1) {
// return 'DOMAIN_TOO_SHORT';
return false;
}
if (ascii.length > 255) {
// return 'DOMAIN_TOO_LONG';
return false;
}
// Check each part's length and allowed chars.
const labels = ascii.split(".");
let label;
for (let i = 0; i < labels.length; ++i) {
label = labels[i];
if (!label.length) {
// LABEL_TOO_SHORT
return false;
}
if (label.length > 63) {
// LABEL_TOO_LONG
return false;
}
if (label.charAt(0) === "-") {
// LABEL_STARTS_WITH_DASH
return false;
}
/* if (label.charAt(label.length - 1) === '-') {
// LABEL_ENDS_WITH_DASH
return false;
} */
if (!/^[a-z0-9-]+$/.test(label)) {
// LABEL_INVALID_CHARS
return false;
}
}
return true;
}
function checkDomainOrUrl(value, name, rules = {}) {
if (!name) {
throw new TypeError("name not specified in checkDomainOrUrl");
}
if (typeof value !== "string") {
throw new Error(`${name} must be a string`);
}
if (validEtldPlusOne(value) && validDomainName(value)) {
return value; // if valid domain no need for futher checks
}
return checkUrl(value, name, rules);
}
function checkRpId(rpId) {
if (typeof rpId !== "string") {
throw new Error("rpId must be a string");
}
const isLocalhost = rpId === "localhost" || rpId.endsWith(".localhost");
if (isLocalhost) return rpId;
return checkDomainOrUrl(rpId, "rpId");
}
async function verifySignature(publicKey, expectedSignature, data, hashName) {
let publicKeyInst;
if (publicKey instanceof PublicKey) {
publicKeyInst = publicKey;
// Check for Public CryptoKey
} else if (publicKey && publicKey.type === "public") {
publicKeyInst = new PublicKey();
publicKeyInst.fromCryptoKey(publicKey);
// Try importing from PEM
} else {
publicKeyInst = new PublicKey();
await publicKeyInst.fromPem(publicKey);
}
// Check for valid algorithm
const alg = publicKeyInst.getAlgorithm();
if (typeof alg === "undefined") {
throw new Error("verifySignature: Algoritm missing.");
}
// Use supplied hashName
if (hashName) {
alg.hash = {
name: hashName,
};
}
if (!alg.hash) {
throw new Error("verifySignature: Hash name missing.");
}
// Sync (possible updated) algorithm back to key
publicKeyInst.setAlgorithm(alg);
try {
let uSignature = new Uint8Array(expectedSignature);
if (alg.name === "ECDSA") {
uSignature = await derToRaw(uSignature);
}
const result = await webcrypto.subtle.verify(
publicKeyInst.getAlgorithm(),
publicKeyInst.getKey(),
uSignature,
new Uint8Array(data)
);
// If verification fails with SHA-1, try Node.js native crypto as fallback
// Node.js 24+ disables SHA-1 in WebCrypto, so we use the native crypto module
if (
!result &&
hashName === "SHA-1" &&
platformCrypto__namespace &&
platformCrypto__namespace.createVerify
) {
try {
const pem = await publicKeyInst.toPem();
const verify = platformCrypto__namespace.createVerify("RSA-SHA1");
verify.update(Buffer.from(data));
verify.end();
return verify.verify(pem, Buffer.from(expectedSignature));
} catch (fallbackError) {
console.error("SHA-1 fallback failed:", fallbackError);
return result;
}
}
return result;
} catch (e) {
console.error(e);
throw e;
}
}
async function hashDigest(o, alg) {
if (typeof o === "string") {
o = new TextEncoder().encode(o);
}
const result = await webcrypto.subtle.digest(alg || "SHA-256", o);
return result;
}
function randomValues(n) {
const byteArray = new Uint8Array(n);
webcrypto.getRandomValues(byteArray);
return byteArray;
}
function getHostname(urlIn) {
return new URL(urlIn).hostname;
}
async function getEmbeddedJwk(jwsHeader, alg) {
let publicKeyJwk;
// Use JWK from header
if (jwsHeader.jwk) {
publicKeyJwk = jwsHeader.jwk;
// Extract JWK from first x509 certificate in header
} else if (jwsHeader.x5c) {
const x5c0 = jwsHeader.x5c[0];
const cert = new Certificate(x5c0);
publicKeyJwk = await cert.getPublicKeyJwk();
// Use common name as kid if missing
publicKeyJwk.kid = publicKeyJwk.kid || cert.getCommonName();
}
if (!publicKeyJwk) {
throw new Error("getEmbeddedJwk: JWK not found in JWS.");
}
// Use alg from header if not present, use passed alg as default
publicKeyJwk.alg = publicKeyJwk.alg || jwsHeader.alg || alg;
return publicKeyJwk;
}
var toolbox = /*#__PURE__*/Object.freeze({
__proto__: null,
base64: base64,
cbor: cborX__namespace,
checkDomainOrUrl: checkDomainOrUrl,
checkOrigin: checkOrigin,
checkRpId: checkRpId,
checkUrl: checkUrl,
decodeProtectedHeader: jose.decodeProtectedHeader,
fromBER: asn1js.fromBER,
getEmbeddedJwk: getEmbeddedJwk,
getHostname: getHostname,
hashDigest: hashDigest,
importJWK: jose.importJWK,
jwtVerify: jose.jwtVerify,
pkijs: pkijs,
randomValues: randomValues,
verifySignature: verifySignature,
get webcrypto () { return webcrypto; }
});
function ab2str(buf) {
let str = "";
new Uint8Array(buf).forEach((ch) => {
str += String.fromCharCode(ch);
});
return str;
}
function isBase64Url(str) {
return !!str.match(/^[A-Za-z0-9\-_]+={0,2}$/);
}
function isPem(pem) {
if (typeof pem !== "string") {
return false;
}
const pemRegex = /^-----BEGIN .+-----$\n([A-Za-z0-9+/=]|\n)*^-----END .+-----$/m;
return !!pem.match(pemRegex);
}
function isPositiveInteger(n) {
return n >>> 0 === parseFloat(n);
}
function abToBuf$1(ab) {
return new Uint8Array(ab).buffer;
}
function abToInt(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw new Error("abToInt: expected ArrayBuffer");
}
const buf = new Uint8Array(ab);
let cnt = ab.byteLength - 1;
let ret = 0;
buf.forEach((byte) => {
ret |= byte << (cnt * 8);
cnt--;
});
return ret;
}
function abToPem(type, ab) {
if (typeof type !== "string") {
throw new Error(
"abToPem expected 'type' to be string like 'CERTIFICATE', got: " +
type,
);
}
const str = coerceToBase64(ab, "pem buffer");
return [
`-----BEGIN ${type}-----\n`,
...str.match(/.{1,64}/g).map((s) => s + "\n"),
`-----END ${type}-----\n`,
].join("");
}
/**
* Creates a new Uint8Array based on two different ArrayBuffers
*
* @private
* @param {ArrayBuffers} buffer1 The first buffer.
* @param {ArrayBuffers} buffer2 The second buffer.
* @return {ArrayBuffers} The new ArrayBuffer created out of the two.
*/
const appendBuffer$1 = function(buffer1, buffer2) {
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
};
function coerceToArrayBuffer$1(buf, name) {
if (!name) {
throw new TypeError("name not specified in coerceToArrayBuffer");
}
// Handle empty strings
if (typeof buf === "string" && buf === "") {
buf = new Uint8Array(0);
// Handle base64url and base64 strings
} else if (typeof buf === "string") {
// base64 to base64url
buf = buf.replace(/\+/g, "-").replace(/\//g, "_").replace("=", "");
// base64 to Buffer
buf = base64.toArrayBuffer(buf, true);
}
// Extract typed array from Array
if (Array.isArray(buf)) {
buf = new Uint8Array(buf);
}
// Extract ArrayBuffer from Node buffer
if (typeof Buffer !== "undefined" && buf instanceof Buffer) {
buf = new Uint8Array(buf);
buf = buf.buffer;
}
// Extract arraybuffer from TypedArray
if (buf instanceof Uint8Array) {
buf = buf.slice(0, buf.byteLength, buf.buffer.byteOffset).buffer;
}
// error if none of the above worked
if (!(buf instanceof ArrayBuffer)) {
throw new TypeError(`could not coerce '${name}' to ArrayBuffer`);
}
return buf;
}
function coerceToBase64(thing, name) {
if (!name) {
throw new TypeError("name not specified in coerceToBase64");
}
if (typeof thing !== "string") {
try {
thing = base64.fromArrayBuffer(
coerceToArrayBuffer$1(thing, name),
);
} catch (_err) {
throw new Error(`could not coerce '${name}' to string`);
}
}
return thing;
}
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function coerceToBase64Url(thing, name) {
if (!name) {
throw new TypeError("name not specified in coerceToBase64");
}
if (typeof thing === "string") {
// Convert from base64 to base64url
thing = thing.replace(/\+/g, "-").replace(/\//g, "_").replace(/={0,2}$/g, "");
}
if (typeof thing !== "string") {
try {
thing = base64.fromArrayBuffer(
coerceToArrayBuffer$1(thing, name),
true,
);
} catch (_err) {
throw new Error(`could not coerce '${name}' to string`);
}
}
return thing;
}
// Merged with previous arrayBufferEquals
function arrayBufferEquals(b1, b2) {
if (
!(b1 instanceof ArrayBuffer) ||
!(b2 instanceof ArrayBuffer)
) {
return false;
}
if (b1.byteLength !== b2.byteLength) {
return false;
}
b1 = new Uint8Array(b1);
b2 = new Uint8Array(b2);
for (let i = 0; i < b1.byteLength; i++) {
if (b1[i] !== b2[i]) return false;
}
return true;
}
function abToHex(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw new TypeError("Invalid argument passed to abToHex");
}
const result = Array.prototype.map.call(
new Uint8Array(ab),
(x) => ("00" + x.toString(16)).slice(-2),
).join("");
return result;
}
function b64ToJsObject(b64, desc) {
return JSON.parse(ab2str(coerceToArrayBuffer$1(b64, desc)));
}
function jsObjectToB64(obj) {
return base64.fromString(
JSON.stringify(obj).replace(/[\u{0080}-\u{FFFF}]/gu, ""),
);
}
function pemToBase64(pem) {
// Clean up base64 string
if (typeof pem === "string" || pem instanceof String) {
pem = pem.replace(/\r/g, "");
}
if (!isPem(pem)) {
throw new Error("expected PEM string as input");
}
// Remove trailing \n
pem = pem.replace(/\n$/, "");
// Split on \n
let pemArr = pem.split("\n");
// remove first and last lines
pemArr = pemArr.slice(1, pemArr.length - 1);
return pemArr.join("");
}
var utils = /*#__PURE__*/Object.freeze({
__proto__: null,
ab2str: ab2str,
abToBuf: abToBuf$1,
abToHex: abToHex,
abToInt: abToInt,
abToPem: abToPem,
appendBuffer: appendBuffer$1,
arrayBufferEquals: arrayBufferEquals,
b64ToJsObject: b64ToJsObject,
coerceToArrayBuffer: coerceToArrayBuffer$1,
coerceToBase64: coerceToBase64,
coerceToBase64Url: coerceToBase64Url,
isBase64Url: isBase64Url,
isPem: isPem,
isPositiveInteger: isPositiveInteger,
jsObjectToB64: jsObjectToB64,
pemToBase64: pemToBase64,
str2ab: str2ab,
tools: toolbox
});
// deno-lint-ignore-file
async function validateExpectations() {
/* eslint complexity: ["off"] */
let req = this.requiredExpectations;
let opt = this.optionalExpectations;
let exp = this.expectations;
if (!(exp instanceof Map)) {
throw new Error("expectations should be of type Map");
}
if (Array.isArray(req)) {
req = new Set([req]);
}
if (!(req instanceof Set)) {
throw new Error("requiredExpectaions should be of type Set");
}
if (Array.isArray(opt)) {
opt = new Set([opt]);
}
if (!(opt instanceof Set)) {
throw new Error("optionalExpectations should be of type Set");
}
for (let field of req) {
if (!exp.has(field)) {
throw new Error(`expectation did not contain value for '${field}'`);
}
}
let optCount = 0;
for (const [field] of exp) {
if (opt.has(field)) {
optCount++;
}
}
if (req.size !== exp.size - optCount) {
throw new Error(
`wrong number of expectations: should have ${req.size} but got ${exp.size - optCount}`,
);
}
// origin - isValid
if (req.has("origin")) {
let expectedOrigin = exp.get("origin");
checkOr