import { COSEALG, COSEKEYS, COSEPublicKeyRSA, isCOSEAlg } from '../../cose.ts'; import { mapCoseAlgToWebCryptoAlg } from './mapCoseAlgToWebCryptoAlg.ts'; import { importKey } from './importKey.ts'; import { isoBase64URL } from '../index.ts'; import { mapCoseAlgToWebCryptoKeyAlgName } from './mapCoseAlgToWebCryptoKeyAlgName.ts'; import { getWebCrypto } from './getWebCrypto.ts'; /** * Verify a signature using an RSA public key */ export async function verifyRSA(opts: { cosePublicKey: COSEPublicKeyRSA; signature: Uint8Array; data: Uint8Array; shaHashOverride?: COSEALG; }): Promise { const { cosePublicKey, signature, data, shaHashOverride } = opts; const WebCrypto = await getWebCrypto(); const alg = cosePublicKey.get(COSEKEYS.alg); const n = cosePublicKey.get(COSEKEYS.n); const e = cosePublicKey.get(COSEKEYS.e); if (!alg) { throw new Error('Public key was missing alg (RSA)'); } if (!isCOSEAlg(alg)) { throw new Error(`Public key had invalid alg ${alg} (RSA)`); } if (!n) { throw new Error('Public key was missing n (RSA)'); } if (!e) { throw new Error('Public key was missing e (RSA)'); } const keyData: JsonWebKey = { kty: 'RSA', alg: '', n: isoBase64URL.fromBuffer(n), e: isoBase64URL.fromBuffer(e), ext: false, }; const keyAlgorithm = { name: mapCoseAlgToWebCryptoKeyAlgName(alg), hash: { name: mapCoseAlgToWebCryptoAlg(alg) }, }; const verifyAlgorithm: AlgorithmIdentifier | RsaPssParams = { name: mapCoseAlgToWebCryptoKeyAlgName(alg), }; if (shaHashOverride) { keyAlgorithm.hash.name = mapCoseAlgToWebCryptoAlg(shaHashOverride); } if (keyAlgorithm.name === 'RSASSA-PKCS1-v1_5') { if (keyAlgorithm.hash.name === 'SHA-256') { keyData.alg = 'RS256'; } else if (keyAlgorithm.hash.name === 'SHA-384') { keyData.alg = 'RS384'; } else if (keyAlgorithm.hash.name === 'SHA-512') { keyData.alg = 'RS512'; } else if (keyAlgorithm.hash.name === 'SHA-1') { keyData.alg = 'RS1'; } } else if (keyAlgorithm.name === 'RSA-PSS') { /** * salt length. The default value is 20 but the convention is to use hLen, the length of the * output of the hash function in bytes. A salt length of zero is permitted and will result in * a deterministic signature value. The actual salt length used can be determined from the * signature value. * * From https://www.cryptosys.net/pki/manpki/pki_rsaschemes.html */ let saltLength = 0; if (keyAlgorithm.hash.name === 'SHA-256') { keyData.alg = 'PS256'; saltLength = 32; // 256 bits => 32 bytes } else if (keyAlgorithm.hash.name === 'SHA-384') { keyData.alg = 'PS384'; saltLength = 48; // 384 bits => 48 bytes } else if (keyAlgorithm.hash.name === 'SHA-512') { keyData.alg = 'PS512'; saltLength = 64; // 512 bits => 64 bytes } (verifyAlgorithm as RsaPssParams).saltLength = saltLength; } else { throw new Error( `Unexpected RSA key algorithm ${alg} (${keyAlgorithm.name})`, ); } const key = await importKey({ keyData, algorithm: keyAlgorithm, }); return WebCrypto.subtle.verify(verifyAlgorithm, key, signature, data); }