@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
166 lines (165 loc) • 7.52 kB
JavaScript
import { CredoError } from "../../error/CredoError.mjs";
import "../../error/index.mjs";
import { TypedArrayEncoder } from "../../utils/TypedArrayEncoder.mjs";
import "../../utils/index.mjs";
import "../../agent/index.mjs";
import { isKnownJwaSignatureAlgorithm } from "../kms/jwk/jwa.mjs";
import { PublicJwk } from "../kms/jwk/PublicJwk.mjs";
import { KeyManagementApi } from "../kms/KeyManagementApi.mjs";
import "../kms/index.mjs";
import { X509Certificate } from "../x509/X509Certificate.mjs";
import { parseDid } from "../dids/domain/parse.mjs";
import { getPublicJwkFromVerificationMethod } from "../dids/domain/key-type/keyDidMapping.mjs";
import { DidResolverService } from "../dids/services/DidResolverService.mjs";
import { DidsApi } from "../dids/DidsApi.mjs";
import "../dids/index.mjs";
import { SdJwtVcError } from "./SdJwtVcError.mjs";
//#region src/modules/sd-jwt-vc/utils.ts
async function resolveSigningPublicJwkFromDidUrl(agentContext, didUrl) {
const { publicJwk } = await agentContext.dependencyManager.resolve(DidsApi).resolveVerificationMethodFromCreatedDidRecord(didUrl);
return publicJwk;
}
async function resolveDidUrl(agentContext, didUrl) {
const didDocument = await agentContext.dependencyManager.resolve(DidResolverService).resolveDidDocument(agentContext, didUrl);
return {
verificationMethod: didDocument.dereferenceKey(didUrl, ["assertionMethod"]),
didDocument
};
}
async function extractKeyFromHolderBinding(agentContext, holder, { forSigning = false, jwkKeyId } = {}) {
if (holder.method === "did") {
if (!parseDid(holder.didUrl).fragment) throw new CredoError(`didUrl '${holder.didUrl}' does not contain a '#'. Unable to derive key from did document`);
let publicJwk;
if (forSigning) publicJwk = await resolveSigningPublicJwkFromDidUrl(agentContext, holder.didUrl);
else {
const { verificationMethod } = await resolveDidUrl(agentContext, holder.didUrl);
publicJwk = getPublicJwkFromVerificationMethod(verificationMethod);
}
const supportedSignatureAlgorithms = publicJwk.supportedSignatureAlgorithms;
if (supportedSignatureAlgorithms.length === 0) throw new CredoError(`No supported JWA signature algorithms found for key ${publicJwk.jwkTypeHumanDescription}`);
return {
alg: supportedSignatureAlgorithms[0],
publicJwk,
cnf: { kid: holder.didUrl }
};
}
if (holder.method === "jwk") {
const publicJwk = holder.jwk;
const alg = publicJwk.supportedSignatureAlgorithms[0];
if (forSigning) publicJwk.keyId = jwkKeyId ?? publicJwk.legacyKeyId;
return {
alg,
publicJwk,
cnf: { jwk: publicJwk.toJson() }
};
}
throw new CredoError("Unsupported credential holder binding. Only 'did' and 'jwk' are supported at the moment.");
}
/**
* Set the `alg` from a JWT header on the public jwk, so signing and verification use the
* algorithm of the JWT rather than the first supported signature algorithm of the key.
*
* Throws an error if the alg is not a known JWA signature algorithm, or if the alg is not
* supported by the jwk.
*/
function setJwkAlgFromJwtHeader(publicJwk, jwtHeaderAlg) {
if (typeof jwtHeaderAlg !== "string" || !isKnownJwaSignatureAlgorithm(jwtHeaderAlg)) throw new CredoError(`Expected JWT header 'alg' to be a known JWA signature algorithm, found '${jwtHeaderAlg}'`);
publicJwk.alg = jwtHeaderAlg;
}
/**
* Hashing algorithms that can be used for the disclosure digests of an SD-JWT, and are placed
* in the `_sd_alg` claim.
*
* `sha-1` is omitted, as SD-JWT requires a hash algorithm that is considered secure at the
* time of issuance.
*
* @see https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-22.html#name-hash-function-claim
*/
const sdJwtHashingAlgorithms = [
"sha-256",
"sha-384",
"sha-512"
];
/**
* Get the hashing algorithm to use for the disclosure digests of an SD-JWT, defaulting to `sha-256`.
*
* The sign options already exclude insecure hashing algorithms on a type level, but this is
* enforced at runtime as well, as the value can originate from JavaScript callers or dynamic input.
*/
function getSdJwtHashingAlgorithm(hashingAlgorithm) {
const hashAlg = hashingAlgorithm ?? "sha-256";
if (!sdJwtHashingAlgorithms.includes(hashAlg)) throw new CredoError(`Unsupported hashing algorithm '${hashAlg}' for the disclosure digests of an SD-JWT. Supported hashing algorithms are ${sdJwtHashingAlgorithms.join(", ")}`);
return hashAlg;
}
function getSdJwtSigner(agentContext, key) {
const kms = agentContext.resolve(KeyManagementApi);
return async (input) => {
const result = await kms.sign({
keyId: key.keyId,
data: TypedArrayEncoder.fromUtf8String(input),
algorithm: key.signatureAlgorithm
});
return TypedArrayEncoder.toBase64Url(result.signature);
};
}
function getSdJwtVerifier(agentContext, key) {
const kms = agentContext.resolve(KeyManagementApi);
return async (message, signatureBase64Url) => {
return (await kms.verify({
signature: TypedArrayEncoder.fromBase64Url(signatureBase64Url),
key: { publicJwk: key.toJson() },
data: TypedArrayEncoder.fromUtf8String(message),
algorithm: key.signatureAlgorithm
})).verified;
};
}
function parseHolderBindingFromCredential(payload) {
if (!payload) throw new CredoError("Unable to extract payload from SD-JWT VC");
if (!payload.cnf) return null;
const cnf = payload.cnf;
if (cnf.jwk) return {
method: "jwk",
jwk: PublicJwk.fromUnknown(cnf.jwk)
};
if (cnf.kid) {
if (!cnf.kid.startsWith("did:") || !cnf.kid.includes("#")) throw new CredoError("Invalid holder kid for did. Only absolute KIDs for cnf are supported");
return {
method: "did",
didUrl: cnf.kid
};
}
throw new CredoError("Unsupported credential holder binding. Only 'did' and 'jwk' are supported at the moment.");
}
function parseIssuerFromCredential(header, payload) {
if (!payload || !header) throw new SdJwtVcError("SD-JWT is missing payload");
const iss = payload.iss;
if (header?.x5c) {
if (!Array.isArray(header.x5c)) throw new SdJwtVcError("Invalid x5c header in credential. Not an array.");
if (header.x5c.length === 0) throw new SdJwtVcError("Invalid x5c header in credential. Empty array.");
if (header.x5c.some((x5c) => typeof x5c !== "string")) throw new SdJwtVcError("Invalid x5c header in credential. Not an array of strings.");
return {
method: "x5c",
x5c: header.x5c.map((cert) => X509Certificate.fromEncodedCertificate(cert)),
issuer: iss
};
}
if (iss?.startsWith("did:")) {
if (!header.kid) throw new SdJwtVcError("Credential does not contain a kid in the header");
const issuerKid = header.kid;
let didUrl;
if (issuerKid.startsWith("#")) didUrl = `${iss}${issuerKid}`;
else if (issuerKid.startsWith("did:")) {
const didFromKid = parseDid(issuerKid);
if (didFromKid.did !== iss) throw new SdJwtVcError(`kid in header is an absolute DID URL, but the did (${didFromKid.did}) does not match with the 'iss' did (${iss})`);
didUrl = issuerKid;
} else throw new SdJwtVcError("Invalid issuer kid for did. Only absolute or relative (starting with #) did urls are supported.");
return {
method: "did",
didUrl
};
}
throw new SdJwtVcError("Unsupported signing method for SD-JWT VC. Only did and x5c are supported at the moment.");
}
//#endregion
export { extractKeyFromHolderBinding, getSdJwtHashingAlgorithm, getSdJwtSigner, getSdJwtVerifier, parseHolderBindingFromCredential, parseIssuerFromCredential, resolveDidUrl, resolveSigningPublicJwkFromDidUrl, sdJwtHashingAlgorithms, setJwkAlgFromJwtHeader };
//# sourceMappingURL=utils.mjs.map