463 字
2 分鐘
[範例] 使用 node-forge 解析 PKCS7 | 自然人憑證開發筆記
2024-01-15
無標籤

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

PKCS7 簽章值中有包含憑證(含公鑰、身分證末四碼、姓名)、TBS、卡號、簽章時間、Hash 演算法、Nonce 等資訊。本篇說明如何取出這些資訊。


Demo#


程式碼#

讀取 PKCS7 簽章值的資訊,透過 OID 找到對應欄位,可以先到 ASN.1 JavaScript decoder 解碼了解欄位的 OID,再到程式中抓取。

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/forge.min.js"></script>
<h1>自然人憑證 node-forge 解析 PKCS7</h1>
PKCS7<br>
<textarea id="pkcs7" rows="8" cols="65"></textarea><br>
<button onclick="submit()">解析</button>
</form>
<hr />
<pre id="result"></pre>
function submit() {
let result = "";
// 從 certb64 提取公鑰
const begin = "-----BEGIN PKCS7-----";
const end = "-----END PKCS7-----";
const certPem = begin + document.getElementById("pkcs7").value + end;
const pkcs7 = forge.pkcs7.messageFromPem(certPem);
const data = toUtf8(pkcs7);
result += "簽章資訊\n===\n";
result += `tbs: ${data.rawCapture.content.value[0].value}\n`;
result += `hashAlgorithm: ${data.rawCapture.digestAlgorithm}\n`;
const attrs = data.rawCapture.authenticatedAttributes;
const nonce = data.rawCapture.authenticatedAttributes.find(
(i) => i.value[0].value == "1.2.840.113549.1.9.25.3"
);
result += `nonce: ${nonce ? nonce.value[1].value[0].value : "null"}\n`;
const cardNumber = data.rawCapture.authenticatedAttributes.find(
(i) => i.value[0].value == "2.16.886.1.100.2.204"
);
result += `cardNumber: ${
cardNumber ? cardNumber.value[1].value[0].value : "null"
}\n`;
const signingTime = data.rawCapture.authenticatedAttributes.find(
(i) => i.value[0].value == "1.2.840.113549.1.9.5"
);
result += `signingTime: ${
signingTime ? signingTime.value[1].value[0].value : "null"
}\n`;
result += "\n\n憑證資訊\n===\n";
const issuer = data.certificates[0].issuer.attributes;
result += `issuerDN: C=${issuer[0].value}, O=${issuer[1].value}, OU=${issuer[2].value}\n`;
const subject = data.certificates[0].subject.attributes;
result += `subjectDN: C=${subject[0].value}, O=${subject[1].value}, serialNumber=${subject[2].value}\n`;
result += `serialNumber: ${data.certificates[0].serialNumber}\n`;
result += `notAfter: ${data.certificates[0].validity.notAfter}\n`;
result += `notBefore: ${data.certificates[0].validity.notBefore}\n`;
result += JSON.stringify(data, null, 2);
document.getElementById("result").innerHTML = result;
}
// 將object中所有value轉為utf8
function toUtf8(obj) {
if (typeof obj === "object" && obj !== null) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj[key] = toUtf8(obj[key]);
}
}
return obj;
} else if (typeof obj === "string") {
if (/[\u0000-\u001F]/.test(obj)) {
// 解決OID亂碼問題
return toOid(obj);
} else {
// 解決中文亂碼問題
const textDecoder = new TextDecoder("utf-8");
const byteArray = new Uint8Array([...obj].map((c) => c.charCodeAt(0)));
return textDecoder.decode(byteArray);
}
} else {
return obj;
}
}
// 將HEX轉為OID
function toOid(str) {
const bytes = Array.from(str, c => c.charCodeAt(0));
const oid = [Math.floor(bytes[0] / 40), bytes[0] % 40];
let t = 0;
for (let i = 1; i < bytes.length; i++) {
if (bytes[i] >= 128) {
t = (t << 7) + (bytes[i] - 128);
} else {
oid.push((t << 7) + bytes[i]);
t = 0;
}
}
return oid.join(".");
}