did-jwt
Version:
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
154 lines • 6.59 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toSignatureObject = toSignatureObject;
const Digest_js_1 = require("./Digest.js");
const util_js_1 = require("./util.js");
const index_js_1 = require("./blockchains/index.js");
const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
const nist_js_1 = require("@noble/curves/nist.js");
const ed25519_js_1 = require("@noble/curves/ed25519.js");
// converts a JOSE signature to it's components
function toSignatureObject(signature, recoverable = false) {
const rawSig = (0, util_js_1.base64ToBytes)(signature);
if (rawSig.length !== (recoverable ? 65 : 64)) {
throw new Error('wrong signature length');
}
const r = (0, util_js_1.bytesToHex)(rawSig.slice(0, 32));
const s = (0, util_js_1.bytesToHex)(rawSig.slice(32, 64));
const sigObj = { r, s };
if (recoverable) {
sigObj.recoveryParam = rawSig[64];
}
return sigObj;
}
function toSignatureObject2(signature, recoverable = false) {
const bytes = (0, util_js_1.base64ToBytes)(signature);
if (bytes.length !== (recoverable ? 65 : 64)) {
throw new Error('wrong signature length');
}
return {
compact: bytes.slice(0, 64),
recovery: bytes[64],
};
}
function verifyES256(data, signature, authenticators) {
const hash = (0, Digest_js_1.sha256)(data);
const sig = (0, util_js_1.base64ToBytes)(signature);
if (sig.length !== 64) {
throw new Error(`"compact signature" expected Uint8Array of length 64, got length=${sig.length}`);
}
const fullPublicKeys = authenticators.filter((a) => !a.ethereumAddress && !a.blockchainAccountId);
const signer = fullPublicKeys.find((pk) => {
try {
const { keyBytes } = (0, util_js_1.extractPublicKeyBytes)(pk);
return nist_js_1.p256.verify(sig, hash, keyBytes, { prehash: false, lowS: false });
}
catch {
return false;
}
});
if (!signer)
throw new Error('invalid_signature: Signature invalid for JWT');
return signer;
}
function verifyES256K(data, signature, authenticators) {
const hash = (0, Digest_js_1.sha256)(data);
const signatureBytes = (0, util_js_1.base64ToBytes)(signature);
if (signatureBytes.length !== 64) {
throw new Error(`"compact signature" expected Uint8Array of length 64, got length=${signatureBytes.length}`);
}
const fullPublicKeys = authenticators.filter((a) => {
return !a.ethereumAddress && !a.blockchainAccountId;
});
const blockchainAddressKeys = authenticators.filter((a) => {
return a.ethereumAddress || a.blockchainAccountId;
});
let signer = fullPublicKeys.find((pk) => {
try {
const { keyBytes } = (0, util_js_1.extractPublicKeyBytes)(pk);
return secp256k1_js_1.secp256k1.verify(signatureBytes, hash, keyBytes, { prehash: false, lowS: false, format: 'compact' });
}
catch {
return false;
}
});
if (!signer && blockchainAddressKeys.length > 0) {
signer = verifyRecoverableES256K(data, signature, blockchainAddressKeys);
}
if (!signer)
throw new Error('invalid_signature: Signature invalid for JWT');
return signer;
}
function verifyRecoverableES256K(data, signature, authenticators) {
const signatures = [];
if (signature.length > 86) {
signatures.push(toSignatureObject2(signature, true));
}
else {
const so = toSignatureObject2(signature, false);
signatures.push({ ...so, recovery: 0 });
signatures.push({ ...so, recovery: 1 });
}
const hash = (0, Digest_js_1.sha256)(data);
const checkSignatureAgainstSigner = (sigObj) => {
const signature = secp256k1_js_1.secp256k1.Signature.fromBytes(sigObj.compact, 'compact').addRecoveryBit(sigObj.recovery || 0);
const recoveredPublicKey = signature.recoverPublicKey(hash);
const recoveredAddress = (0, Digest_js_1.toEthereumAddress)(recoveredPublicKey.toHex(false)).toLowerCase();
const recoveredPublicKeyHex = recoveredPublicKey.toHex(false);
const recoveredCompressedPublicKeyHex = recoveredPublicKey.toHex(true);
return authenticators.find((a) => {
const { keyBytes } = (0, util_js_1.extractPublicKeyBytes)(a);
const keyHex = (0, util_js_1.bytesToHex)(keyBytes);
return (keyHex === recoveredPublicKeyHex ||
keyHex === recoveredCompressedPublicKeyHex ||
a.ethereumAddress?.toLowerCase() === recoveredAddress ||
a.blockchainAccountId?.split('@eip155')?.[0].toLowerCase() === recoveredAddress || // CAIP-2
(0, index_js_1.verifyBlockchainAccountId)(recoveredPublicKeyHex, a.blockchainAccountId) // CAIP-10
);
});
};
// Find first verification method
for (const signature of signatures) {
const verificationMethod = checkSignatureAgainstSigner(signature);
if (verificationMethod)
return verificationMethod;
}
// If no one found matching
throw new Error('invalid_signature: Signature invalid for JWT');
}
function verifyEd25519(data, signature, authenticators) {
const clear = (0, util_js_1.stringToBytes)(data);
const signatureBytes = (0, util_js_1.base64ToBytes)(signature);
const signer = authenticators.find((a) => {
const { keyBytes, keyType } = (0, util_js_1.extractPublicKeyBytes)(a);
if (keyType === 'Ed25519') {
return ed25519_js_1.ed25519.verify(signatureBytes, clear, keyBytes);
}
else {
return false;
}
});
if (!signer)
throw new Error('invalid_signature: Signature invalid for JWT');
return signer;
}
const algorithms = {
ES256: verifyES256,
ES256K: verifyES256K,
// This is a non-standard algorithm but retained for backwards compatibility
// see https://github.com/decentralized-identity/did-jwt/issues/146
'ES256K-R': verifyRecoverableES256K,
// This is actually incorrect but retained for backwards compatibility
// see https://github.com/decentralized-identity/did-jwt/issues/130
Ed25519: verifyEd25519,
EdDSA: verifyEd25519,
};
function VerifierAlgorithm(alg) {
const impl = algorithms[alg];
if (!impl)
throw new Error(`not_supported: Unsupported algorithm ${alg}`);
return impl;
}
VerifierAlgorithm.toSignatureObject = toSignatureObject;
exports.default = VerifierAlgorithm;
//# sourceMappingURL=VerifierAlgorithm.js.map