mirror of
https://github.com/gchq/CyberChef.git
synced 2026-03-17 12:21:40 -07:00
Merge ac2def74d0 into d195a51e2e
This commit is contained in:
commit
5228f526d5
2 changed files with 175 additions and 0 deletions
|
|
@ -196,6 +196,7 @@
|
|||
"RSA Verify",
|
||||
"RSA Encrypt",
|
||||
"RSA Decrypt",
|
||||
"RSA Key Modulus",
|
||||
"Generate ECDSA Key Pair",
|
||||
"ECDSA Signature Conversion",
|
||||
"ECDSA Sign",
|
||||
|
|
|
|||
174
src/core/operations/RSAKeyModulus.mjs
Normal file
174
src/core/operations/RSAKeyModulus.mjs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/**
|
||||
* @author rayane-ara []
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import forge from "node-forge";
|
||||
|
||||
/**
|
||||
* RSA Key Modulus operation
|
||||
*/
|
||||
class RSAKeyModulus extends Operation {
|
||||
|
||||
/**
|
||||
* RSAKeyModulus constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "RSA Key Modulus";
|
||||
this.module = "Crypto";
|
||||
this.description = "Extracts the modulus from an RSA private key, public key, or X.509 certificate. This is commonly used to verify if a private key corresponds to a specific X.509 certificate.<br><br>This operation provides the same functionality as the <code>openssl rsa -noout -modulus</code> and <code>openssl x509 -noout -modulus</code> commands.<br><br>Accepts PEM-encoded input. Supported formats:<br><ul><li><code>-----BEGIN CERTIFICATE-----</code> (X.509 certificate)</li><li><code>-----BEGIN PRIVATE KEY-----</code> (PKCS#8 unencrypted)</li><li><code>-----BEGIN RSA PRIVATE KEY-----</code> (PKCS#1)</li><li><code>-----BEGIN PUBLIC KEY-----</code> (PKCS#8 / SubjectPublicKeyInfo)</li><li><code>-----BEGIN RSA PUBLIC KEY-----</code> (PKCS#1)</li></ul>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
name: "Output format",
|
||||
type: "option",
|
||||
value: ["Hex", "Decimal", "Base64"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the RSA modulus from a PEM-encoded RSA private key, public key,
|
||||
* or X.509 certificate.
|
||||
*
|
||||
* Supports the following PEM headers:
|
||||
* - -----BEGIN CERTIFICATE----- (X.509 certificate)
|
||||
* - -----BEGIN RSA PRIVATE KEY----- (PKCS#1 private key)
|
||||
* - -----BEGIN PRIVATE KEY----- (PKCS#8 unencrypted private key)
|
||||
* - -----BEGIN RSA PUBLIC KEY----- (PKCS#1 public key)
|
||||
* - -----BEGIN PUBLIC KEY----- (PKCS#8 / SubjectPublicKeyInfo public key)
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [outputFormat] = args;
|
||||
|
||||
if (!input || input.trim() === "") {
|
||||
throw new OperationError("No input provided. Please provide a PEM-encoded RSA key.");
|
||||
}
|
||||
|
||||
const pem = input.trim();
|
||||
let key = null;
|
||||
|
||||
// Detect key type from PEM header
|
||||
if (pem.includes("-----BEGIN CERTIFICATE-----")) {
|
||||
// X.509 certificate - extract the embedded RSA public key
|
||||
try {
|
||||
const cert = forge.pki.certificateFromPem(pem);
|
||||
key = cert.publicKey;
|
||||
if (!key || !key.n) {
|
||||
throw new OperationError("The certificate does not contain an RSA public key.");
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof OperationError) throw err;
|
||||
throw new OperationError(`Failed to parse X.509 certificate: ${err.message}`);
|
||||
}
|
||||
} else if (pem.includes("-----BEGIN RSA PRIVATE KEY-----")) {
|
||||
// PKCS#1 RSA private key
|
||||
try {
|
||||
key = forge.pki.privateKeyFromPem(pem);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Failed to parse PKCS#1 RSA private key: ${err.message}`);
|
||||
}
|
||||
} else if (pem.includes("-----BEGIN PRIVATE KEY-----")) {
|
||||
// PKCS#8 unencrypted private key
|
||||
try {
|
||||
key = forge.pki.privateKeyFromPem(pem);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Failed to parse PKCS#8 private key: ${err.message}. Note: encrypted private keys (-----BEGIN ENCRYPTED PRIVATE KEY-----) are not supported.`);
|
||||
}
|
||||
} else if (pem.includes("-----BEGIN RSA PUBLIC KEY-----")) {
|
||||
// PKCS#1 RSA public key - forge does not support this header natively.
|
||||
// We parse the ASN.1 DER directly to extract n and e, then build a
|
||||
// forge public key object from those two integers.
|
||||
// PKCS#1 RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
|
||||
try {
|
||||
const der = forge.util.decode64(
|
||||
pem
|
||||
.replace("-----BEGIN RSA PUBLIC KEY-----", "")
|
||||
.replace("-----END RSA PUBLIC KEY-----", "")
|
||||
.replace(/\s+/g, "")
|
||||
);
|
||||
const asn1 = forge.asn1.fromDer(der);
|
||||
if (
|
||||
asn1.type !== forge.asn1.Type.SEQUENCE ||
|
||||
!asn1.value ||
|
||||
asn1.value.length < 2
|
||||
) {
|
||||
throw new Error("Unexpected ASN.1 structure in PKCS#1 public key.");
|
||||
}
|
||||
// Each INTEGER value in the ASN.1 tree is a raw DER byte string
|
||||
const nDer = asn1.value[0].value;
|
||||
const eDer = asn1.value[1].value;
|
||||
|
||||
// forge.jsbn.BigInteger can be constructed directly from a hex string
|
||||
const n = new forge.jsbn.BigInteger(forge.util.bytesToHex(nDer), 16);
|
||||
const e = new forge.jsbn.BigInteger(forge.util.bytesToHex(eDer), 16);
|
||||
|
||||
key = forge.pki.rsa.setPublicKey(n, e);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Failed to parse PKCS#1 RSA public key: ${err.message}`);
|
||||
}
|
||||
} else if (pem.includes("-----BEGIN PUBLIC KEY-----")) {
|
||||
// PKCS#8 / SubjectPublicKeyInfo public key
|
||||
try {
|
||||
key = forge.pki.publicKeyFromPem(pem);
|
||||
} catch (err) {
|
||||
throw new OperationError(`Failed to parse PKCS#8 public key: ${err.message}`);
|
||||
}
|
||||
} else if (pem.includes("-----BEGIN ENCRYPTED PRIVATE KEY-----")) {
|
||||
throw new OperationError("Encrypted private keys are not supported. Please decrypt your key first (e.g. with openssl pkcs8 -in key.pem -out decrypted.pem).");
|
||||
} else {
|
||||
throw new OperationError("Unrecognised PEM header. Supported types:\n -----BEGIN CERTIFICATE-----\n -----BEGIN RSA PRIVATE KEY-----\n -----BEGIN PRIVATE KEY-----\n -----BEGIN RSA PUBLIC KEY-----\n -----BEGIN PUBLIC KEY-----");
|
||||
}
|
||||
|
||||
// Extract the modulus BigInteger from the parsed key
|
||||
// For both public and private forge keys the modulus is exposed as key.n
|
||||
const n = key.n;
|
||||
if (!n) {
|
||||
throw new OperationError("Could not extract modulus from key. The key may not be an RSA key.");
|
||||
}
|
||||
|
||||
// Convert the forge BigInteger to a hex string (unsigned, big-endian)
|
||||
// n.toString(16) returns lowercase hex without leading zeros
|
||||
let hexModulus = n.toString(16);
|
||||
|
||||
// Ensure even number of hex characters (full bytes)
|
||||
if (hexModulus.length % 2 !== 0) {
|
||||
hexModulus = "0" + hexModulus;
|
||||
}
|
||||
|
||||
// Format output according to user preference
|
||||
switch (outputFormat) {
|
||||
case "Hex":
|
||||
// Match openssl output style: uppercase hex
|
||||
return "Modulus=" + hexModulus.toUpperCase();
|
||||
|
||||
case "Decimal":
|
||||
return n.toString(10);
|
||||
|
||||
case "Base64": {
|
||||
// Convert hex string to binary then base64
|
||||
const bytes = hexModulus.match(/.{2}/g).map(b => parseInt(b, 16));
|
||||
const binary = bytes.map(b => String.fromCharCode(b)).join("");
|
||||
return forge.util.encode64(binary);
|
||||
}
|
||||
|
||||
default:
|
||||
return hexModulus.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default RSAKeyModulus;
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue