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

首頁 > 系列文 > 自然人憑證開發筆記

前文範例有說明過使用 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 驗簽失敗");
}
}