did-jwt
Version:
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
151 lines • 6.2 kB
JavaScript
import { sha256, toEthereumAddress } from './Digest.js';
import { base64ToBytes, bytesToHex, extractPublicKeyBytes, stringToBytes, } from './util.js';
import { verifyBlockchainAccountId } from './blockchains/index.js';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { p256 } from '@noble/curves/nist.js';
import { ed25519 } from '@noble/curves/ed25519.js';
// converts a JOSE signature to it's components
export function toSignatureObject(signature, recoverable = false) {
const rawSig = base64ToBytes(signature);
if (rawSig.length !== (recoverable ? 65 : 64)) {
throw new Error('wrong signature length');
}
const r = bytesToHex(rawSig.slice(0, 32));
const s = bytesToHex(rawSig.slice(32, 64));
const sigObj = { r, s };
if (recoverable) {
sigObj.recoveryParam = rawSig[64];
}
return sigObj;
}
function toSignatureObject2(signature, recoverable = false) {
const bytes = 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 = sha256(data);
const sig = 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 } = extractPublicKeyBytes(pk);
return 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 = sha256(data);
const signatureBytes = 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 } = extractPublicKeyBytes(pk);
return 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 = sha256(data);
const checkSignatureAgainstSigner = (sigObj) => {
const signature = secp256k1.Signature.fromBytes(sigObj.compact, 'compact').addRecoveryBit(sigObj.recovery || 0);
const recoveredPublicKey = signature.recoverPublicKey(hash);
const recoveredAddress = toEthereumAddress(recoveredPublicKey.toHex(false)).toLowerCase();
const recoveredPublicKeyHex = recoveredPublicKey.toHex(false);
const recoveredCompressedPublicKeyHex = recoveredPublicKey.toHex(true);
return authenticators.find((a) => {
const { keyBytes } = extractPublicKeyBytes(a);
const keyHex = bytesToHex(keyBytes);
return (keyHex === recoveredPublicKeyHex ||
keyHex === recoveredCompressedPublicKeyHex ||
a.ethereumAddress?.toLowerCase() === recoveredAddress ||
a.blockchainAccountId?.split('@eip155')?.[0].toLowerCase() === recoveredAddress || // CAIP-2
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 = stringToBytes(data);
const signatureBytes = base64ToBytes(signature);
const signer = authenticators.find((a) => {
const { keyBytes, keyType } = extractPublicKeyBytes(a);
if (keyType === 'Ed25519') {
return 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;
export default VerifierAlgorithm;
//# sourceMappingURL=VerifierAlgorithm.js.map