UNPKG

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
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