mirror of
https://github.com/gchq/CyberChef.git
synced 2024-11-16 08:58:30 +01:00
269 lines
8.5 KiB
JavaScript
269 lines
8.5 KiB
JavaScript
|
/**
|
||
|
* @author jkataja
|
||
|
* @copyright Crown Copyright 2023
|
||
|
* @license Apache-2.0
|
||
|
*/
|
||
|
|
||
|
import Operation from "../Operation.mjs";
|
||
|
import forge from "node-forge";
|
||
|
import Utils from "../Utils.mjs";
|
||
|
|
||
|
/**
|
||
|
* Parse CSR operation
|
||
|
*/
|
||
|
class ParseCSR extends Operation {
|
||
|
|
||
|
/**
|
||
|
* ParseCSR constructor
|
||
|
*/
|
||
|
constructor() {
|
||
|
super();
|
||
|
|
||
|
this.name = "Parse CSR";
|
||
|
this.module = "PublicKey";
|
||
|
this.description = "Parse Certificate Signing Request (CSR) for an X.509 certificate";
|
||
|
this.infoURL = "https://en.wikipedia.org/wiki/Certificate_signing_request";
|
||
|
this.inputType = "string";
|
||
|
this.outputType = "string";
|
||
|
this.args = [
|
||
|
{
|
||
|
"name": "Input format",
|
||
|
"type": "option",
|
||
|
"value": ["PEM"]
|
||
|
},
|
||
|
{
|
||
|
"name": "Strict ASN.1 value lengths",
|
||
|
"type": "boolean",
|
||
|
"value": true
|
||
|
}
|
||
|
];
|
||
|
this.checks = [
|
||
|
{
|
||
|
"pattern": "^-+BEGIN CERTIFICATE REQUEST-+\\r?\\n[\\da-z+/\\n\\r]+-+END CERTIFICATE REQUEST-+\\r?\\n?$",
|
||
|
"flags": "i",
|
||
|
"args": ["PEM"]
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param {string} input
|
||
|
* @param {Object[]} args
|
||
|
* @returns {string} Human-readable description of a Certificate Signing Request (CSR).
|
||
|
*/
|
||
|
run(input, args) {
|
||
|
if (!input.length) {
|
||
|
return "No input";
|
||
|
}
|
||
|
|
||
|
const csr = forge.pki.certificationRequestFromPem(input, args[1]);
|
||
|
|
||
|
// RSA algorithm is the only one supported for CSR in node-forge as of 1.3.1
|
||
|
return `Version: ${1 + csr.version} (0x${Utils.hex(csr.version)})
|
||
|
Subject${formatSubject(csr.subject)}
|
||
|
Subject Alternative Names${formatSubjectAlternativeNames(csr)}
|
||
|
Public Key
|
||
|
Algorithm: RSA
|
||
|
Length: ${csr.publicKey.n.bitLength()} bits
|
||
|
Modulus: ${formatMultiLine(chop(csr.publicKey.n.toString(16).replace(/(..)/g, "$&:")))}
|
||
|
Exponent: ${csr.publicKey.e} (0x${Utils.hex(csr.publicKey.e)})
|
||
|
Signature
|
||
|
Algorithm: ${forge.pki.oids[csr.signatureOid]}
|
||
|
Signature: ${formatMultiLine(Utils.strToByteArray(csr.signature).map(b => Utils.hex(b)).join(":"))}
|
||
|
Extensions${formatExtensions(csr)}`;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format Subject of the request as a multi-line string
|
||
|
* @param {*} subject CSR Subject
|
||
|
* @returns Multi-line string describing Subject
|
||
|
*/
|
||
|
function formatSubject(subject) {
|
||
|
let out = "\n";
|
||
|
|
||
|
for (const attribute of subject.attributes) {
|
||
|
out += ` ${attribute.shortName} = ${attribute.value}\n`;
|
||
|
}
|
||
|
|
||
|
return chop(out);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Format Subject Alternative Names from the name `subjectAltName` extension
|
||
|
* @param {*} extension CSR object
|
||
|
* @returns Multi-line string describing Subject Alternative Names
|
||
|
*/
|
||
|
function formatSubjectAlternativeNames(csr) {
|
||
|
let out = "\n";
|
||
|
|
||
|
for (const attribute of csr.attributes) {
|
||
|
for (const extension of attribute.extensions) {
|
||
|
if (extension.name === "subjectAltName") {
|
||
|
const names = [];
|
||
|
for (const altName of extension.altNames) {
|
||
|
switch (altName.type) {
|
||
|
case 1:
|
||
|
names.push(`EMAIL: ${altName.value}`);
|
||
|
break;
|
||
|
case 2:
|
||
|
names.push(`DNS: ${altName.value}`);
|
||
|
break;
|
||
|
case 6:
|
||
|
names.push(`URI: ${altName.value}`);
|
||
|
break;
|
||
|
case 7:
|
||
|
names.push(`IP: ${altName.ip}`);
|
||
|
break;
|
||
|
default:
|
||
|
names.push(`(unable to format type ${altName.type} name)\n`);
|
||
|
}
|
||
|
}
|
||
|
out += indent(2, names);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return chop(out);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format known extensions of a CSR
|
||
|
* @param {*} csr CSR object
|
||
|
* @returns Multi-line string describing attributes
|
||
|
*/
|
||
|
function formatExtensions(csr) {
|
||
|
let out = "\n";
|
||
|
|
||
|
for (const attribute of csr.attributes) {
|
||
|
for (const extension of attribute.extensions) {
|
||
|
// formatted separately
|
||
|
if (extension.name === "subjectAltName") {
|
||
|
continue;
|
||
|
}
|
||
|
out += ` ${extension.name}${(extension.critical ? " CRITICAL" : "")}:\n`;
|
||
|
let parts = [];
|
||
|
switch (extension.name) {
|
||
|
case "basicConstraints" :
|
||
|
parts = describeBasicConstraints(extension);
|
||
|
break;
|
||
|
case "keyUsage" :
|
||
|
parts = describeKeyUsage(extension);
|
||
|
break;
|
||
|
case "extKeyUsage" :
|
||
|
parts = describeExtendedKeyUsage(extension);
|
||
|
break;
|
||
|
default :
|
||
|
parts = ["(unable to format extension)"];
|
||
|
}
|
||
|
out += indent(4, parts);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return chop(out);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Format hex string onto multiple lines
|
||
|
* @param {*} longStr
|
||
|
* @returns Hex string as a multi-line hex string
|
||
|
*/
|
||
|
function formatMultiLine(longStr) {
|
||
|
const lines = [];
|
||
|
|
||
|
for (let remain = longStr ; remain !== "" ; remain = remain.substring(48)) {
|
||
|
lines.push(remain.substring(0, 48));
|
||
|
}
|
||
|
|
||
|
return lines.join("\n ");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Describe Basic Constraints
|
||
|
* @see RFC 5280 4.2.1.9. Basic Constraints https://www.ietf.org/rfc/rfc5280.txt
|
||
|
* @param {*} extension CSR extension with the name `basicConstraints`
|
||
|
* @returns Array of strings describing Basic Constraints
|
||
|
*/
|
||
|
function describeBasicConstraints(extension) {
|
||
|
const constraints = [];
|
||
|
|
||
|
constraints.push(`CA = ${extension.cA}`);
|
||
|
if (extension.pathLenConstraint !== undefined) constraints.push(`PathLenConstraint = ${extension.pathLenConstraint}`);
|
||
|
|
||
|
return constraints;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Describe Key Usage extension permitted use cases
|
||
|
* @see RFC 5280 4.2.1.3. Key Usage https://www.ietf.org/rfc/rfc5280.txt
|
||
|
* @param {*} extension CSR extension with the name `keyUsage`
|
||
|
* @returns Array of strings describing Key Usage extension permitted use cases
|
||
|
*/
|
||
|
function describeKeyUsage(extension) {
|
||
|
const usage = [];
|
||
|
|
||
|
if (extension.digitalSignature) usage.push("Digital signature");
|
||
|
if (extension.nonRepudiation) usage.push("Non-repudiation");
|
||
|
if (extension.keyEncipherment) usage.push("Key encipherment");
|
||
|
if (extension.dataEncipherment) usage.push("Data encipherment");
|
||
|
if (extension.keyAgreement) usage.push("Key agreement");
|
||
|
if (extension.keyCertSign) usage.push("Key certificate signing");
|
||
|
if (extension.cRLSign) usage.push("CRL signing");
|
||
|
if (extension.encipherOnly) usage.push("Encipher only");
|
||
|
if (extension.decipherOnly) usage.push("Decipher only");
|
||
|
|
||
|
if (usage.length === 0) usage.push("(none)");
|
||
|
|
||
|
return usage;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Describe Extended Key Usage extension permitted use cases
|
||
|
* @see RFC 5280 4.2.1.12. Extended Key Usage https://www.ietf.org/rfc/rfc5280.txt
|
||
|
* @param {*} extension CSR extension with the name `extendedKeyUsage`
|
||
|
* @returns Array of strings describing Extended Key Usage extension permitted use cases
|
||
|
*/
|
||
|
function describeExtendedKeyUsage(extension) {
|
||
|
const usage = [];
|
||
|
|
||
|
if (extension.serverAuth) usage.push("TLS Web Server Authentication");
|
||
|
if (extension.clientAuth) usage.push("TLS Web Client Authentication");
|
||
|
if (extension.codeSigning) usage.push("Code signing");
|
||
|
if (extension.emailProtection) usage.push("E-mail Protection (S/MIME)");
|
||
|
if (extension.timeStamping) usage.push("Trusted Timestamping");
|
||
|
if (extension.msCodeInd) usage.push("Microsoft Individual Code Signing");
|
||
|
if (extension.msCodeCom) usage.push("Microsoft Commercial Code Signing");
|
||
|
if (extension.msCTLSign) usage.push("Microsoft Trust List Signing");
|
||
|
if (extension.msSGC) usage.push("Microsoft Server Gated Crypto");
|
||
|
if (extension.msEFS) usage.push("Microsoft Encrypted File System");
|
||
|
if (extension.nsSGC) usage.push("Netscape Server Gated Crypto");
|
||
|
|
||
|
if (usage.length === 0) usage.push("(none)");
|
||
|
|
||
|
return usage;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Join an array of strings and add leading spaces to each line.
|
||
|
* @param {*} n How many leading spaces
|
||
|
* @param {*} parts Array of strings
|
||
|
* @returns Joined and indented string.
|
||
|
*/
|
||
|
function indent(n, parts) {
|
||
|
const fluff = " ".repeat(n);
|
||
|
return fluff + parts.join("\n" + fluff) + "\n";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove last character from a string.
|
||
|
* @param {*} s String
|
||
|
* @returns Chopped string.
|
||
|
*/
|
||
|
function chop(s) {
|
||
|
return s.substring(0, s.length - 1);
|
||
|
}
|
||
|
|
||
|
export default ParseCSR;
|