420 字
2 分鐘
[範例] Web Crypto API 透過公鑰驗證 PKCS1 簽章值 | 自然人憑證開發筆記

前文範例有說明過使用 node-forge 進行驗簽 PKCS1,也可以透過瀏覽器、Node.js 原生的 Web Crypto API 來驗簽。不過 Web Crypto 無法從憑證匯出公鑰,因此無法從憑證驗簽。
Demo
前端程式碼
<h1>自然人憑證 公鑰驗簽PKCS1 (後端用 Web Crypto)</h1><input type="button" value="讀取簽章公鑰" onclick="getCert()" /><form action="https://cert.modatw.workers.dev/api/verifyPkcs1WithKeyUseWebCrypto" method="post"> tbs: <input type="text" name="tbs" /><br> hashAlgorithm: <select name="hashAlgorithm"> <option value="SHA-1">SHA1</option> <option value="SHA-256" selected>SHA256</option> <option value="SHA-384">SHA384</option> <option value="SHA-512">SHA512</option> </select><br> signature (簽章值)<br> <textarea name="signature" rows="8" cols="65"></textarea><br> keyb64 (公鑰)<br> <textarea name="keyb64" rows="8" cols="65"></textarea><br> <input type="submit"></form><script> let popupForm; let timeout;
function getCert() { popupForm = window.open( "http://localhost:61161/popupForm", "popupForm", "height=200, width=200, left=100, top=20" ); timeout = setTimeout(() => { popupForm.close(); alert("尚未安裝元件"); }, 5000); }
function receiveMessage(event) { if (event.origin !== "http://localhost:61161") return;
let ret = JSON.parse(event.data); if (ret.func === "getTbs") { clearTimeout(timeout); const tbsData = { func: "GetUserCert" }; popupForm.postMessage(JSON.stringify(tbsData), "*"); } else { const data = JSON.parse(event.data); const cert = data.slots[0].token.keys[0].keyb64; document.getElementsByName("keyb64")[0].value = cert; } }
window.addEventListener("message", receiveMessage, false);</script>後端程式碼
以 Cloudflare Workers 實作,本段程式碼也可以在前端實作。
export default { async fetch(request, env, ctx) { try { const { pathname } = new URL(request.url); if (pathname == "/api/verifyPkcs1WithKeyUseWebCrypto" && request.method == "POST") { return await verifyPkcs1WithKeyUseWebCrypto(request); } return new Response(""); } catch (e) { return new Response(e); } },};
async function verifyPkcs1WithKeyUseWebCrypto(request) { const formData = await request.formData(); const tbs = formData.get("tbs"); const hashAlgorithm = formData.get("hashAlgorithm"); const signature = formData.get("signature") let keyb64 = formData.get("keyb64");
// 自然人憑證讀取的公鑰少了此前綴,會導致讀取失敗 if (!keyb64.startsWith("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A")) { keyb64 = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A" + keyb64; }
const publicKeyBuffer = Uint8Array.from(atob(keyb64), c => c.charCodeAt(0)); const signatureBuffer = Uint8Array.from(atob(signature), c => c.charCodeAt(0)); const tbsDataBuffer = new TextEncoder().encode(tbs);
const publicKey = await crypto.subtle.importKey( "spki", publicKeyBuffer, { name: 'RSASSA-PKCS1-v1_5', hash: { name: hashAlgorithm } }, false, ['verify'] );
const verified = await crypto.subtle.verify( { name: 'RSASSA-PKCS1-v1_5' }, publicKey, signatureBuffer, tbsDataBuffer );
if (verified) { return new Response("PKCS1 驗簽成功"); } else { return new Response("PKCS1 驗簽失敗"); }}