Zum Hauptinhalt springen
claw
am
tools
Werkzeuge
Dokumente
Daoark
EN
Startseite
Startseite
/
Verschlüsselung & Codierung
›
Startseite
manifest.json
"']/g, function(c) { return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]; }); } function pemToBuf(pem) { var lines = pem.split('\n'); var b64 = ''; var inKey = false; for (var i = 0; i < lines.length; i++) { var l = lines[i].trim(); if (l.indexOf('-----BEGIN ') === 0) { inKey = true; continue; } if (l.indexOf('-----END ') === 0) { inKey = false; continue; } if (inKey) b64 += l; } b64 = b64.replace(/\s+/g, ''); if (!b64) throw new Error('PEM 内容为空'); var bin = atob(b64); var bytes = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); return bytes; } // ===== ASN.1 DER 解析 ===== function derRead(buf, off) { var tag = buf[off++]; var len = buf[off++]; if (len & 0x80) { var n = len & 0x7F; len = 0; for (var k = 0; k < n; k++) len = (len << 8) | buf[off++]; } var val = buf.slice(off, off + len); return { tag: tag, len: len, value: val, headerLen: off - (off - len) + 0 /* 简化:调用者再加 */ }; } function derReadTLV(buf, off) { var start = off; var tag = buf[off++]; var len = buf[off++]; if (len & 0x80) { var n = len & 0x7F; len = 0; for (var k = 0; k < n; k++) len = (len << 8) | buf[off++]; } return { tag: tag, len: len, value: buf.slice(off, off + len), start: start, end: off + len }; } // OID 解码 function oidDecode(bytes) { if (bytes.length === 0) return ''; var first = bytes[0]; var a = Math.floor(first / 40); var b = first % 40; var oid = [a, b]; var i = 1, v = 0; while (i < bytes.length) { v = (v << 7) | (bytes[i] & 0x7F); if (!(bytes[i] & 0x80)) { oid.push(v); v = 0; } i++; } return oid.join('.'); } function oidToName(oid) { var common = { '2.5.4.3': 'CN', '2.5.4.6': 'C', '2.5.4.7': 'L', '2.5.4.8': 'ST', '2.5.4.10': 'O', '2.5.4.11': 'OU', '2.5.4.5': 'serialNumber', '2.5.4.9': 'street', '2.5.4.17': 'postalCode', '1.2.840.113549.1.9.1': 'emailAddress', '0.9.2342.19200300.100.1.25': 'DC', '1.2.840.113549.1.1.1': 'rsaEncryption', '1.2.840.113549.1.1.5': 'sha1WithRSA', '1.2.840.113549.1.1.11': 'sha256WithRSA', '1.2.840.113549.1.1.12': 'sha384WithRSA', '1.2.840.113549.1.1.13': 'sha512WithRSA', '1.2.840.10045.2.1': 'ecPublicKey', '1.2.840.10045.4.3.2': 'ecdsaWithSHA256', '2.5.29.17': 'subjectAltName', '2.5.29.19': 'basicConstraints', '2.5.29.31': 'cRLDistributionPoints', '2.5.29.32': 'certificatePolicies', '2.5.29.35': 'authorityKeyIdentifier', '2.5.29.37': 'extKeyUsage', '1.3.6.1.5.5.7.1.1': 'authorityInfoAccess', }; return common[oid] || oid; } // 解析 Name (RDNSequence) function parseName(buf) { var out = {}; var seq = derReadTLV(buf, 0); var seqVal = seq.value; var pos = 0; while (pos < seqVal.length) { var setTLV = derReadTLV(seqVal, pos); var setVal = setTLV.value; var seqTLV = derReadTLV(setVal, 0); var oidTLV = derReadTLV(seqTLV.value, 0); var valTLV = derReadTLV(seqTLV.value, oidTLV.end); var oid = oidDecode(oidTLV.value); var name = oidToName(oid); var val = ''; if (valTLV.tag === 0x0C || valTLV.tag === 0x13 || valTLV.tag === 0x14) { // UTF8/Printable/T61 val = new TextDecoder().decode(valTLV.value); } else if (valTLV.tag === 0x86) { // IA5 val = new TextDecoder().decode(valTLV.value); } out[name] = val; pos = setTLV.end; } return out; } // 解析时间 (UTCTime / GeneralizedTime) function parseTime(bytes) { var s = new TextDecoder().decode(bytes); if (s.length === 13) { // GeneralizedTime YYYYMMDDHHMMSSZ return s.substring(0, 4) + '-' + s.substring(4, 6) + '-' + s.substring(6, 8) + ' ' + s.substring(8, 10) + ':' + s.substring(10, 12) + ':' + s.substring(12, 14) + ' UTC'; } if (s.length === 15) { // UTCTime YYMMDDHHMMSSZ var yy = parseInt(s.substring(0, 2), 10); yy += yy < 50 ? 2000 : 1900; return yy + '-' + s.substring(2, 4) + '-' + s.substring(4, 6) + ' ' + s.substring(6, 8) + ':' + s.substring(8, 10) + ':' + s.substring(10, 12) + ' UTC'; } return s; } // 解析 SAN extension function parseSAN(bytes) { var out = []; var seq = derReadTLV(bytes, 0); var pos = 0; while (pos < seq.value.length) { var tlv = derReadTLV(seq.value, pos); var type = tlv.tag; if (type === 2) out.push('DNS:' + new TextDecoder().decode(tlv.value)); // dNSName else if (type === 6) out.push('URI:' + new TextDecoder().decode(tlv.value)); else if (type === 7) out.push('IP:' + Array.from(tlv.value).join('.')); else if (type === 1) out.push('email:' + new TextDecoder().decode(tlv.value)); pos = tlv.end; } return out; } // 解析 RSA 公钥 function parseRSAPubKey(bytes) { var seq = derReadTLV(bytes, 0); var nTLV = derReadTLV(seq.value, 0); var eTLV = derReadTLV(seq.value, nTLV.end); var n = nTLV.value; // 去掉前导 0 if (n[0] === 0) n = n.slice(1); var nHex = Array.from(n).map(function(b) { return b.toString(16).padStart(2, '0'); }).join(''); return { bits: n.length * 8, e: eTLV.value[0] || 65537, n: nHex, nB64: btoa(String.fromCharCode.apply(null, n)) }; } // 主解析 function parseCert(buf) { var certTLV = derReadTLV(buf, 0); // SEQUENCE var tbsTLV = derReadTLV(certTLV.value, 0); // tbsCertificate // 简化:只看 tbs 内的关键字段 var tbsVal = tbsTLV.value; // [0] EXPLICIT version (optional) var pos = 0; var version = 'v1'; var cur = derReadTLV(tbsVal, pos); if (cur.tag === 0xA0) { // [0] EXPLICIT version = 'v' + (cur.value[0] + 1); pos = cur.end; cur = derReadTLV(tbsVal, pos); } // serialNumber INTEGER var serial = cur.value; if (serial[0] === 0) serial = serial.slice(1); var serialHex = Array.from(serial).map(function(b) { return b.toString(16).padStart(2, '0'); }).join('').toUpperCase(); pos = cur.end; // signature AlgorithmIdentifier cur = derReadTLV(tbsVal, pos); pos = cur.end; // issuer Name cur = derReadTLV(tbsVal, pos); var issuer = parseName(cur.value); pos = cur.end; // validity SEQUENCE { notBefore, notAfter } cur = derReadTLV(tbsVal, pos); var nbTLV = derReadTLV(cur.value, 0); var naTLV = derReadTLV(cur.value, nbTLV.end); var notBefore = parseTime(nbTLV.value); var notAfter = parseTime(naTLV.value); pos = cur.end; // subject Name cur = derReadTLV(tbsVal, pos); var subject = parseName(cur.value); pos = cur.end; // subjectPublicKeyInfo SEQUENCE cur = derReadTLV(tbsVal, pos); var spkiVal = cur.value; var spkiInner = derReadTLV(spkiVal, 0); var algoTLV = derReadTLV(spkiInner.value, 0); var algoOid = oidDecode(algoTLV.value); var pubKeyTLV = derReadTLV(spkiInner.value, algoTLV.end); var pubKeyBits = pubKeyTLV.value; pos = cur.end; // extensions [3] EXPLICIT var extensions = []; if (pos < tbsVal.length) { cur = derReadTLV(tbsVal, pos); if (cur.tag === 0xA3) { var extSeq = derReadTLV(cur.value, 0); var extPos = 0; while (extPos < extSeq.value.length) { var extTLV = derReadTLV(extSeq.value, extPos); var extOidTLV = derReadTLV(extTLV.value, 0); var extOid = oidDecode(extOidTLV.value); if (extOid === '2.5.29.17') { var extValTLV = derReadTLV(extTLV.value, extOidTLV.end); var sanSeqTLV = derReadTLV(extValTLV.value, 0); extensions.push({ oid: 'SAN', values: parseSAN(sanSeqTLV.value) }); } else { extensions.push({ oid: oidToName(extOid), oidRaw: extOid }); } extPos = extTLV.end; } } } return { version: version, serial: serialHex, issuer: issuer, subject: subject, notBefore: notBefore, notAfter: notAfter, algoOid: oidToName(algoOid), pubKey: { algo: oidToName(algoOid), raw: pubKeyBits, oid: algoOid } }; } function parseRSAPubKeyBytes(bytes) { try { return parseRSAPubKey(bytes); } catch (e) { return null; } } function doProcess() { try { var pem = getVal('text-a'); if (!pem) { showError('请粘贴 PEM 证书'); return; } var buf = pemToBuf(pem); var c = parseCert(buf); var pub = c.pubKey.algo === 'rsaEncryption' ? parseRSAPubKeyBytes(c.pubKey.raw) : null; var now = new Date(); var nb = new Date(c.notBefore); var na = new Date(c.notAfter); var status = (now < nb) ? ' 尚未生效' : (now > na) ? ' 已过期' : ' 有效'; var out = '=== X.509 证书 ===\n\n' + '版本: ' + c.version + '\n' + '序列号: ' + c.serial + '\n' + '签名算法: ' + c.algoOid + '\n' + '状态: ' + status + '\n\n' + '--- Subject(主体) ---\n' + formatName(c.subject) + '\n' + '--- Issuer(签发者) ---\n' + formatName(c.issuer) + '\n' + '--- Validity(有效期) ---\n' + 'Not Before: ' + c.notBefore + '\n' + 'Not After: ' + c.notAfter + '\n\n' + '--- Public Key(公钥) ---\n' + '算法: ' + c.pubKey.algo + '\n' + (pub ? '位数: ' + pub.bits + ' bit\n' + '指数: ' + pub.e + ' (0x' + pub.e.toString(16) + ')\n' + 'Modulus (前 64 字符):\n ' + pub.n.substring(0, 64) + (pub.n.length > 64 ? '...' : '') + '\n' + 'Modulus 长度: ' + pub.n.length + ' hex chars = ' + (pub.n.length / 2) + ' bytes' : ''); if (c.pubKey.algo === 'ecPublicKey') { out += '类型: EC (省略详情)\n'; } out += '\n--- Extensions(扩展) ---\n'; if (c._extensions && c._extensions.length) { for (var i = 0; i < c._extensions.length; i++) out += ' ' + c._extensions[i] + '\n'; } else { out += ' (无或未解析)\n'; } out += '\n--- 原始 SHA-256 指纹 ---\n' + ' ' + crypto.subtle.digest('SHA-256', buf).then(function(h) { var ha = Array.from(new Uint8Array(h)).map(function(b) { return b.toString(16).padStart(2, '0'); }).join(':').toUpperCase(); out2 = out.replace(' ', ' ' + ha.match(/.{2}/g).join(':') + '\n (fingerprint)'); }); // 同步算 SHA-256 crypto.subtle.digest('SHA-256', buf).then(function(h) { var ha = Array.from(new Uint8Array(h)).map(function(b) { return b.toString(16).padStart(2, '0'); }).join(':').toUpperCase(); $('result-content').textContent = out + '\nSHA-256 指纹: ' + ha + '\n (' + (ha.match(/.{2}/g) || []).length + ' 字节)\n'; }); $('result-content').textContent = out + '\nSHA-256 指纹: (计算中...)'; $('result-meta').textContent = c.subject.CN || c.subject.O || '证书'; var ra = $('result-area'); ra.hidden = false; ra.style.display = 'block'; var em = $('error-msg'); em.hidden = true; em.style.display = 'none'; } catch (e) { showError('解析失败: ' + (e.message || e) + '\n\n提示: 本工具支持标准 X.509 证书(DER 编码)。若是 OpenSSL 自签名证书,可直接用 openssl x509 -in cert.pem -text -noout 验证。'); } } function formatName(n) { var keys = Object.keys(n); if (keys.length === 0) return ' (空)\n'; var out = ''; for (var i = 0; i < keys.length; i++) out += ' ' + keys[i] + ' = ' + n[keys[i]] + '\n'; return out; } $('btn-main').addEventListener('click', function(e) { e.preventDefault(); doProcess(); }); $('btn-clear').addEventListener('click', function() { $('text-a').value = ''; var ra = $('result-area'); ra.hidden = true; ra.style.display = 'none'; var em = $('error-msg'); em.hidden = true; em.style.display = 'none'; }); })();