UNPKG

trusted-issuer-registry

Version:
159 lines (139 loc) 6.24 kB
import * as asn1js from 'asn1js'; import { Certificate } from 'pkijs'; export const verifySignatureWithPem = async (pemKey, signature, data) => { try { const pemContent = pemKey .replace(/-----BEGIN [^-]+-----/, '') .replace(/-----END [^-]+-----/, '') .replace(/\s+/g, ''); // Convert base64 to binary const bytes = base64ToUint8Array(pemContent); const asn1 = asn1js.fromBER(bytes.buffer); const cert = new Certificate({ schema: asn1.result }); const publicKeyInfo = cert.subjectPublicKeyInfo; if (!publicKeyInfo || !publicKeyInfo.algorithm || !publicKeyInfo.algorithm.algorithmId) { console.error('Parsed publicKeyInfo:', publicKeyInfo); throw new Error('Could not extract algorithm information from public key'); } const webCryptoAlg = getWebCryptoAlgorithmFromOid(publicKeyInfo); // Convert to SPKI format for Web Crypto const spkiBytes = publicKeyInfo.toSchema().toBER(); const spkiKey = await crypto.subtle.importKey( 'spki', spkiBytes, webCryptoAlg, false, ['verify'] ); // Convert signature from base64 to ArrayBuffer let signatureBuffer; if (webCryptoAlg.name === 'ECDSA') { let rsLen = 32; // Default P-256 if (webCryptoAlg.namedCurve === 'P-384') rsLen = 48; if (webCryptoAlg.namedCurve === 'P-521') rsLen = 66; // For ECDSA, convert DER signature to raw format signatureBuffer = convertDerSignatureToRaw(signature, rsLen); } else { // For RSA, use as-is signatureBuffer = base64ToUint8Array(signature).buffer; } const verified = await crypto.subtle.verify(webCryptoAlg, spkiKey, signatureBuffer, data); return verified; } catch (error) { console.error('Error converting PEM to SPKI key:', error); throw error; } }; function base64ToUint8Array(base64) { if(typeof Buffer == 'function') { return new Uint8Array(Buffer.from(base64, 'base64')); } else { const raw = atob(base64); const bytes = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) { bytes[i] = raw.charCodeAt(i); } return bytes; } } // Helper to pad or trim a Uint8Array to a specific length function padOrTrimUint8Array(buf, length) { if (buf.length === length) return buf; if (buf.length > length) return buf.slice(buf.length - length); // pad with zeros at the start const out = new Uint8Array(length); out.set(buf, length - buf.length); return out; } // Function to convert DER signature to raw format for ECDSA function convertDerSignatureToRaw(base64Signature, rsLen) { try { // Decode base64 to binary const derBytes = base64ToUint8Array(base64Signature); // Parse DER structure const asn1 = asn1js.fromBER(derBytes.buffer); // DER signature should be SEQUENCE { INTEGER r, INTEGER s } if (asn1.result.valueBlock.value.length !== 2) { throw new Error('Invalid DER signature structure'); } const r = asn1.result.valueBlock.value[0]; const s = asn1.result.valueBlock.value[1]; // Extract r and s values as byte arrays const rBytes = new Uint8Array(r.valueBlock.valueHex); const sBytes = new Uint8Array(s.valueBlock.valueHex); // For P-256, each value should be 32 bytes const rPadded = padOrTrimUint8Array(rBytes, rsLen); const sPadded = padOrTrimUint8Array(sBytes, rsLen); // Concatenate r and s const rawSignature = new Uint8Array(rsLen * 2); rawSignature.set(rPadded, 0); rawSignature.set(sPadded, rsLen); return rawSignature.buffer; } catch (error) { console.error('Error converting DER signature to raw:', error); throw error; } } function getWebCryptoAlgorithmFromOid(publicKeyInfo) { const algorithmOid = publicKeyInfo.algorithm.algorithmId; const algorithmParams = publicKeyInfo.algorithm.algorithmParams; let oidString; if (algorithmOid && typeof algorithmOid === 'object' && algorithmOid.valueBlock && typeof algorithmOid.valueBlock.toString === 'function') { oidString = algorithmOid.valueBlock.toString(); } else if (typeof algorithmOid === 'string') { oidString = algorithmOid; } else { throw new Error('Unsupported algorithmOid format'); } switch (oidString) { case '1.2.840.10045.2.1': // ecPublicKey // Parse the curve parameters to determine the specific curve let curveOid; if (algorithmParams && typeof algorithmParams === 'object' && algorithmParams.valueBlock && typeof algorithmParams.valueBlock.toString === 'function') { curveOid = algorithmParams.valueBlock.toString(); } else if (typeof algorithmParams === 'string') { curveOid = algorithmParams; } else { curveOid = undefined; } switch (curveOid) { case '1.2.840.10045.3.1.7': // P-256 return { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }; case '1.3.132.0.34': // P-384 return { name: 'ECDSA', namedCurve: 'P-384', hash: { name: 'SHA-384' } }; case '1.3.132.0.35': // P-521 return { name: 'ECDSA', namedCurve: 'P-521', hash: { name: 'SHA-512' } }; case undefined: // Default to P-256 if no parameters provided return { name: 'ECDSA', namedCurve: 'P-256', hash: { name: 'SHA-256' } }; default: throw new Error(`Unsupported EC curve: ${curveOid}`); } case '1.2.840.113549.1.1.1': // rsaEncryption return { name: 'RSASSA-PKCS1-v1_5' }; case '1.2.840.113549.1.1.10': // rsassaPss return { name: 'RSA-PSS' }; default: throw new Error(`Unsupported algorithm OID: ${oidString}`); } }