import Utils from "../Utils.js"; import * as r from "jsrsasign"; /** * Public Key operations. * * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2016 * @license Apache-2.0 * * @namespace */ const PublicKey = { /** * @constant * @default */ X509_INPUT_FORMAT: ["PEM", "DER Hex", "Base64", "Raw"], /** * Parse X.509 certificate operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runParseX509: function (input, args) { if (!input.length) { return "No input"; } let cert = new r.X509(), inputFormat = args[0]; switch (inputFormat) { case "DER Hex": input = input.replace(/\s/g, ""); cert.readCertHex(input); break; case "PEM": cert.readCertPEM(input); break; case "Base64": cert.readCertHex(Utils.toHex(Utils.fromBase64(input, null, "byteArray"), "")); break; case "Raw": cert.readCertHex(Utils.toHex(Utils.strToByteArray(input), "")); break; default: throw "Undefined input format"; } let sn = cert.getSerialNumberHex(), issuer = cert.getIssuerString(), subject = cert.getSubjectString(), pk = cert.getPublicKey(), pkFields = [], pkStr = "", sig = cert.getSignatureValueHex(), sigStr = "", extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; // Public Key fields pkFields.push({ key: "Algorithm", value: pk.type }); if (pk.type === "EC") { // ECDSA pkFields.push({ key: "Curve Name", value: pk.curveName }); pkFields.push({ key: "Length", value: (((new r.BigInteger(pk.pubKeyHex, 16)).bitLength()-3) /2) + " bits" }); pkFields.push({ key: "pub", value: PublicKey._formatByteStr(pk.pubKeyHex, 16, 18) }); } else if (pk.type === "DSA") { // DSA pkFields.push({ key: "pub", value: PublicKey._formatByteStr(pk.y.toString(16), 16, 18) }); pkFields.push({ key: "P", value: PublicKey._formatByteStr(pk.p.toString(16), 16, 18) }); pkFields.push({ key: "Q", value: PublicKey._formatByteStr(pk.q.toString(16), 16, 18) }); pkFields.push({ key: "G", value: PublicKey._formatByteStr(pk.g.toString(16), 16, 18) }); } else if (pk.e) { // RSA pkFields.push({ key: "Length", value: pk.n.bitLength() + " bits" }); pkFields.push({ key: "Modulus", value: PublicKey._formatByteStr(pk.n.toString(16), 16, 18) }); pkFields.push({ key: "Exponent", value: pk.e + " (0x" + pk.e.toString(16) + ")" }); } else { pkFields.push({ key: "Error", value: "Unknown Public Key type" }); } // Format Public Key fields for (let i = 0; i < pkFields.length; i++) { pkStr += " " + pkFields[i].key + ":" + Utils.padLeft( pkFields[i].value + "\n", 18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1, " " ); } // Signature fields let breakoutSig = false; try { breakoutSig = r.ASN1HEX.dump(sig).indexOf("SEQUENCE") === 0; } catch (err) { // Error processing signature, output without further breakout } if (breakoutSig) { // DSA or ECDSA sigStr = " r: " + PublicKey._formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18) + "\n" + " s: " + PublicKey._formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18); } else { // RSA or unknown sigStr = " Signature: " + PublicKey._formatByteStr(sig, 16, 18); } let issuerStr = PublicKey._formatDnStr(issuer, 2), nbDate = PublicKey._formatDate(cert.getNotBefore()), naDate = PublicKey._formatDate(cert.getNotAfter()), subjectStr = PublicKey._formatDnStr(subject, 2); return `Version: ${cert.version} (0x${Utils.hex(cert.version - 1)}) Serial number: ${new r.BigInteger(sn, 16).toString()} (0x${sn}) Algorithm ID: ${cert.getSignatureAlgorithmField()} Validity Not Before: ${nbDate} (dd-mm-yy hh:mm:ss) (${cert.getNotBefore()}) Not After: ${naDate} (dd-mm-yy hh:mm:ss) (${cert.getNotAfter()}) Issuer ${issuerStr} Subject ${subjectStr} Public Key ${pkStr.slice(0, -1)} Certificate Signature Algorithm: ${cert.getSignatureAlgorithmName()} ${sigStr} Extensions ${extensions}`; }, /** * PEM to Hex operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runPemToHex: function(input, args) { if (input.indexOf("-----BEGIN") < 0) { // Add header so that the KEYUTIL function works input = "-----BEGIN CERTIFICATE-----" + input; } if (input.indexOf("-----END") < 0) { // Add footer so that the KEYUTIL function works input = input + "-----END CERTIFICATE-----"; } let cert = new r.X509(); cert.readCertPEM(input); return cert.hex; }, /** * @constant * @default */ PEM_HEADER_STRING: "CERTIFICATE", /** * Hex to PEM operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runHexToPem: function(input, args) { return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); }, /** * Hex to Object Identifier operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runHexToObjectIdentifier: function(input, args) { return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); }, /** * Object Identifier to Hex operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runObjectIdentifierToHex: function(input, args) { return r.KJUR.asn1.ASN1Util.oidIntToHex(input); }, /** * @constant * @default */ ASN1_TRUNCATE_LENGTH: 32, /** * Parse ASN.1 hex string operation. * * @param {string} input * @param {Object[]} args * @returns {string} */ runParseAsn1HexString: function(input, args) { let truncateLen = args[1], index = args[0]; return r.ASN1HEX.dump(input.replace(/\s/g, ""), { "ommitLongOctet": truncateLen }, index); }, /** * Formats Distinguished Name (DN) strings. * * @private * @param {string} dnStr * @param {number} indent * @returns {string} */ _formatDnStr: function(dnStr, indent) { let output = "", fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//), maxKeyLen = 0, key, value, i, str; for (i = 0; i < fields.length; i++) { if (!fields[i].length) continue; key = fields[i].split("=")[0]; maxKeyLen = key.length > maxKeyLen ? key.length : maxKeyLen; } for (i = 0; i < fields.length; i++) { if (!fields[i].length) continue; key = fields[i].split("=")[0]; value = fields[i].split("=")[1]; str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n"; output += Utils.padLeft(str, indent + str.length, " "); } return output.slice(0, -1); }, /** * Formats byte strings by adding line breaks and delimiters. * * @private * @param {string} byteStr * @param {number} length - Line width * @param {number} indent * @returns {string} */ _formatByteStr: function(byteStr, length, indent) { byteStr = Utils.toHex(Utils.fromHex(byteStr), ":"); length = length * 3; let output = ""; for (let i = 0; i < byteStr.length; i += length) { const str = byteStr.slice(i, i + length) + "\n"; if (i === 0) { output += str; } else { output += Utils.padLeft(str, indent + str.length, " "); } } return output.slice(0, output.length-1); }, /** * Formats dates. * * @private * @param {string} dateStr * @returns {string} */ _formatDate: function(dateStr) { return dateStr[4] + dateStr[5] + "/" + dateStr[2] + dateStr[3] + "/" + dateStr[0] + dateStr[1] + " " + dateStr[6] + dateStr[7] + ":" + dateStr[8] + dateStr[9] + ":" + dateStr[10] + dateStr[11]; }, }; export default PublicKey;