diff --git a/src/core/lib/PublicKey.mjs b/src/core/lib/PublicKey.mjs new file mode 100644 index 00000000..2fabc89d --- /dev/null +++ b/src/core/lib/PublicKey.mjs @@ -0,0 +1,72 @@ +/** + * Public key functions. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import { toHex, fromHex } from "./Hex"; + +/** + * Formats Distinguished Name (DN) strings. + * + * @param {string} dnStr + * @param {number} indent + * @returns {string} + */ +export function formatDnStr (dnStr, indent) { + const fields = dnStr.substr(1).replace(/([^\\])\//g, "$1$1/").split(/[^\\]\//); + let output = "", + 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 = key.padEnd(maxKeyLen, " ") + " = " + value + "\n"; + + output += str.padStart(indent + str.length, " "); + } + + return output.slice(0, -1); +} + + +/** + * Formats byte strings by adding line breaks and delimiters. + * + * @param {string} byteStr + * @param {number} length - Line width + * @param {number} indent + * @returns {string} + */ +export function formatByteStr (byteStr, length, indent) { + byteStr = toHex(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 += str.padStart(indent + str.length, " "); + } + } + + return output.slice(0, output.length-1); +} diff --git a/src/core/operations/HexToObjectIdentifier.mjs b/src/core/operations/HexToObjectIdentifier.mjs new file mode 100644 index 00000000..00512e1a --- /dev/null +++ b/src/core/operations/HexToObjectIdentifier.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to Object Identifier operation + */ +class HexToObjectIdentifier extends Operation { + + /** + * HexToObjectIdentifier constructor + */ + constructor() { + super(); + + this.name = "Hex to Object Identifier"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal string into an object identifier (OID)."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidHexToInt(input.replace(/\s/g, "")); + } + +} + +export default HexToObjectIdentifier; diff --git a/src/core/operations/HexToPEM.mjs b/src/core/operations/HexToPEM.mjs new file mode 100644 index 00000000..c08888d4 --- /dev/null +++ b/src/core/operations/HexToPEM.mjs @@ -0,0 +1,46 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Hex to PEM operation + */ +class HexToPEM extends Operation { + + /** + * HexToPEM constructor + */ + constructor() { + super(); + + this.name = "Hex to PEM"; + this.module = "PublicKey"; + this.description = "Converts a hexadecimal DER (Distinguished Encoding Rules) string into PEM (Privacy Enhanced Mail) format."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Header string", + "type": "string", + "value": "CERTIFICATE" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.getPEMStringFromHex(input.replace(/\s/g, ""), args[0]); + } + +} + +export default HexToPEM; diff --git a/src/core/operations/ObjectIdentifierToHex.mjs b/src/core/operations/ObjectIdentifierToHex.mjs new file mode 100644 index 00000000..9be7108c --- /dev/null +++ b/src/core/operations/ObjectIdentifierToHex.mjs @@ -0,0 +1,40 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Object Identifier to Hex operation + */ +class ObjectIdentifierToHex extends Operation { + + /** + * ObjectIdentifierToHex constructor + */ + constructor() { + super(); + + this.name = "Object Identifier to Hex"; + this.module = "PublicKey"; + this.description = "Converts an object identifier (OID) into a hexadecimal string."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + return r.KJUR.asn1.ASN1Util.oidIntToHex(input); + } + +} + +export default ObjectIdentifierToHex; diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs new file mode 100644 index 00000000..36ac4d71 --- /dev/null +++ b/src/core/operations/PEMToHex.mjs @@ -0,0 +1,50 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * PEM to Hex operation + */ +class PEMToHex extends Operation { + + /** + * PEMToHex constructor + */ + constructor() { + super(); + + this.name = "PEM to Hex"; + this.module = "PublicKey"; + this.description = "Converts PEM (Privacy Enhanced Mail) format to a hexadecimal DER (Distinguished Encoding Rules) string."; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(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-----"; + } + const cert = new r.X509(); + cert.readCertPEM(input); + return cert.hex; + } + +} + +export default PEMToHex; diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs new file mode 100644 index 00000000..9f192156 --- /dev/null +++ b/src/core/operations/ParseASN1HexString.mjs @@ -0,0 +1,54 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import Operation from "../Operation"; + +/** + * Parse ASN.1 hex string operation + */ +class ParseASN1HexString extends Operation { + + /** + * ParseASN1HexString constructor + */ + constructor() { + super(); + + this.name = "Parse ASN.1 hex string"; + this.module = "PublicKey"; + this.description = "Abstract Syntax Notation One (ASN.1) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking.

This operation parses arbitrary ASN.1 data and presents the resulting tree."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Starting index", + "type": "number", + "value": 0 + }, + { + "name": "Truncate octet strings longer than", + "type": "number", + "value": 32 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [index, truncateLen] = args; + return r.ASN1HEX.dump(input.replace(/\s/g, ""), { + "ommitLongOctet": truncateLen + }, index); + } + +} + +export default ParseASN1HexString; diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs new file mode 100644 index 00000000..99040cdf --- /dev/null +++ b/src/core/operations/ParseX509Certificate.mjs @@ -0,0 +1,216 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + */ + +import r from "jsrsasign"; +import { fromBase64 } from "../lib/Base64"; +import { toHex } from "../lib/Hex"; +import { formatByteStr, formatDnStr } from "../lib/PublicKey"; +import Operation from "../Operation"; +import Utils from "../Utils"; + +/** + * Parse X.509 certificate operation + */ +class ParseX509Certificate extends Operation { + + /** + * ParseX509Certificate constructor + */ + constructor() { + super(); + + this.name = "Parse X.509 certificate"; + this.module = "PublicKey"; + this.description = "X.509 is an ITU-T standard for a public key infrastructure (PKI) and Privilege Management Infrastructure (PMI). It is commonly involved with SSL/TLS security.

This operation displays the contents of a certificate in a human readable format, similar to the openssl command line tool.

Tags: X509, server hello, handshake"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Input format", + "type": "option", + "value": ["PEM", "DER Hex", "Base64", "Raw"] + } + ]; + this.patterns = [ + { + "match": "^-+BEGIN CERTIFICATE-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE-+\\r?\\n?$", + "flags": "i", + "args": [ + "PEM" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + if (!input.length) { + return "No input"; + } + + const 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(toHex(fromBase64(input, null, "byteArray"), "")); + break; + case "Raw": + cert.readCertHex(toHex(Utils.strToByteArray(input), "")); + break; + default: + throw "Undefined input format"; + } + + const sn = cert.getSerialNumberHex(), + issuer = cert.getIssuerString(), + subject = cert.getSubjectString(), + pk = cert.getPublicKey(), + pkFields = [], + sig = cert.getSignatureValueHex(); + + let pkStr = "", + sigStr = "", + extensions = ""; + + // 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: formatByteStr(pk.pubKeyHex, 16, 18) + }); + } else if (pk.type === "DSA") { // DSA + pkFields.push({ + key: "pub", + value: formatByteStr(pk.y.toString(16), 16, 18) + }); + pkFields.push({ + key: "P", + value: formatByteStr(pk.p.toString(16), 16, 18) + }); + pkFields.push({ + key: "Q", + value: formatByteStr(pk.q.toString(16), 16, 18) + }); + pkFields.push({ + key: "G", + value: 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: 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}:${(pkFields[i].value + "\n").padStart( + 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: ${formatByteStr(r.ASN1HEX.getV(sig, 4), 16, 18)} + s: ${formatByteStr(r.ASN1HEX.getV(sig, 48), 16, 18)}`; + } else { // RSA or unknown + sigStr = ` Signature: ${formatByteStr(sig, 16, 18)}`; + } + + // Extensions + try { + extensions = cert.getInfo().split("X509v3 Extensions:\n")[1].split("signature")[0]; + } catch (err) {} + + const issuerStr = formatDnStr(issuer, 2), + nbDate = formatDate(cert.getNotBefore()), + naDate = formatDate(cert.getNotAfter()), + subjectStr = 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}`; + } + +} + +export default ParseX509Certificate; + +/** + * Formats dates. + * + * @param {string} dateStr + * @returns {string} + */ +function formatDate (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]; +}