Merge d5593a1815fd5a8cdb38b24e497ef418a00a0f3d into 4290ea753912378913b1f3f54e0fc5720afeda5d

This commit is contained in:
Fra3zz 2026-08-10 03:38:08 -07:00 committed by GitHub
commit 2655eb8b6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 392 additions and 0 deletions

View File

@ -212,6 +212,8 @@
"RSA Verify",
"RSA Encrypt",
"RSA Decrypt",
"RSA Encrypt Private Key",
"RSA Decrypt Public Key",
"Generate ECDSA Key Pair",
"ECDSA Signature Conversion",
"ECDSA Sign",

View File

@ -0,0 +1,79 @@
/**
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
/**
* RSA Decrypt Public Key operation
*/
class RSADecryptPublic extends Operation {
/**
* RSADecryptPublic constructor
*/
constructor() {
super();
this.name = "RSA Decrypt Public Key";
this.module = "Ciphers";
this.description = "Decrypt a message that was encrypted with an RSA private key, using the corresponding public key. Supports PKCS#1 (<code>BEGIN RSA PUBLIC KEY</code>) and X.509 SubjectPublicKeyInfo (<code>BEGIN PUBLIC KEY</code>) formats.<br><br>This is the complement of the <b>RSA Encrypt Private Key</b> operation. Standard RSA decryption uses a private key; this operation uses the public key to reverse a private-key-encrypted ciphertext.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Public Key (PEM)",
type: "text",
value: "-----BEGIN PUBLIC KEY-----"
},
{
name: "Encryption Scheme",
type: "option",
value: ["PKCS1 v1.5", "RAW"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, scheme] = args;
if (!pemKey.startsWith("-----BEGIN")) {
throw new OperationError("Please enter a public key.");
}
let pubKey;
try {
pubKey = forge.pki.publicKeyFromPem(pemKey);
} catch (err) {
throw new OperationError(`Unable to load public key: ${err.message}`);
}
try {
if (scheme === "PKCS1 v1.5") {
const decrypted = forge.pki.rsa.decrypt(input, pubKey, true, true);
return forge.util.decodeUtf8(decrypted);
} else {
return forge.pki.rsa.decrypt(input, pubKey, true, false);
}
} catch (err) {
if (err.message === "Encrypted message length is invalid.") {
throw new OperationError(`Input length (${err.length} bytes) does not match key size (${err.expected} bytes). Ensure the input is raw ciphertext, not base64 or hex encoded.`);
}
throw new OperationError(err);
}
}
}
export default RSADecryptPublic;

View File

@ -0,0 +1,90 @@
/**
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import forge from "node-forge";
/**
* RSA Encrypt Private Key operation
*/
class RSAEncryptPrivate extends Operation {
/**
* RSAEncryptPrivate constructor
*/
constructor() {
super();
this.name = "RSA Encrypt Private Key";
this.module = "Ciphers";
this.description = "Encrypt a message with a PEM encoded RSA private key using the private key transform. Supports PKCS#1 and PKCS#8 key formats, including password-protected keys. The resulting ciphertext can be decrypted by anyone holding the corresponding public key.<br><br>Note: standard RSA encryption uses a public key. This operation applies the private key transform (equivalent to signing without hashing) and is sometimes required for compatibility with specific systems.";
this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "RSA Private Key (PEM)",
type: "text",
value: "-----BEGIN RSA PRIVATE KEY-----"
},
{
name: "Key Password",
type: "text",
value: ""
},
{
name: "Encryption Scheme",
type: "option",
value: ["PKCS1 v1.5", "RAW"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [pemKey, password, scheme] = args;
if (!pemKey.startsWith("-----BEGIN")) {
throw new OperationError("Please enter a private key.");
}
let privKey;
try {
privKey = forge.pki.decryptRsaPrivateKey(pemKey, password);
} catch (err) {}
if (!privKey) {
try {
privKey = forge.pki.privateKeyFromPem(pemKey);
} catch (err) {
throw new OperationError(`Unable to load private key: ${err.message}`);
}
}
if (!privKey) {
throw new OperationError("Unable to load private key. Check the key format and password.");
}
try {
const plaintextBytes = forge.util.encodeUtf8(input);
const bt = scheme === "PKCS1 v1.5" ? 0x01 : false;
return forge.pki.rsa.encrypt(plaintextBytes, privKey, bt);
} catch (err) {
if (err.message && err.message.includes("too long")) {
throw new OperationError(`Message is too long for this key size.`);
}
throw new OperationError(err);
}
}
}
export default RSAEncryptPrivate;

View File

@ -0,0 +1,102 @@
/**
* RSA Decrypt Public Key tests.
*
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const PRIV_512_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL
NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF
F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL
WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p
6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf
RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw
NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=
-----END RSA PRIVATE KEY-----`;
const PUB_512_SPKI = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`;
const PUB_512_PKCS1 = `-----BEGIN RSA PUBLIC KEY-----
MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB
757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE=
-----END RSA PUBLIC KEY-----`;
TestRegister.addTests([
{
name: "RSA Decrypt Public Key: missing key",
input: "anything",
expectedOutput: "Please enter a public key.",
recipeConfig: [
{
op: "RSA Decrypt Public Key",
args: ["", "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, SPKI public key, round-trip Hello World",
input: "Hello World",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, PKCS#1 RSA public key, round-trip Hello World",
input: "Hello World",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_PKCS1, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, SPKI public key, round-trip empty",
input: "",
expectedOutput: "",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
},
{
name: "RSA Decrypt Public Key: PKCS1 v1.5, decrypt from base64 ciphertext",
input: "2EoJzsG9F9el3Dwxm+7Ua1Ykdpnh9WeS2prrkAYGfnkN9irgRvNIUkOSUJAgy4yQ5RjqAHkKgAdwHvjWqLuLhA==",
expectedOutput: "Hello World",
recipeConfig: [
{
op: "From Base64",
args: ["A-Za-z0-9+/=", true, false]
},
{
op: "RSA Decrypt Public Key",
args: [PUB_512_SPKI, "PKCS1 v1.5"]
}
]
}
]);

View File

@ -0,0 +1,119 @@
/**
* RSA Encrypt Private Key tests.
*
* @author Fra3zz
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const PRIV_512_PKCS1 = `-----BEGIN RSA PRIVATE KEY-----
MIIBOQIBAAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelMYKtboGLrk6ihtqFPZFRL
NcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQJAOJUpM0lv36MAQR3WAwsF
F7DOy+LnigteCvaNWiNVxZ6jByB5Qb7sall/Qlu9sFI0ZwrlVcKS0kldee7JTYlL
WQIhAP3UKEfOtpTgT1tYmdhaqjxqMfxBom0Ri+rt9ajlzs6vAiEA9L85B8/Gnb7p
6Af7/wpmafL277OV4X4xBfzMR+TUzHUCIBq+VLQkInaTH6lXL3ZtLwyIf9W9MJjf
RWeuRLjT5bM/AiBF7Kw6kx5Hy1fAtydEApCoDIaIjWJw/kC7WTJ0B+jUUQIgV6dw
NSyj0feakeD890gmId+lvl/w/3oUXiczqvl/N9o=
-----END RSA PRIVATE KEY-----`;
const PRIV_512_PKCS8 = `-----BEGIN PRIVATE KEY-----
MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA8qvQOnph0i3M5+Tp
ruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe+e3yy0ys/nS3qOrBZDYSMx
2SPp+wIDAQABAkA4lSkzSW/fowBBHdYDCwUXsM7L4ueKC14K9o1aI1XFnqMHIHlB
vuxqWX9CW72wUjRnCuVVwpLSSV157slNiUtZAiEA/dQoR862lOBPW1iZ2FqqPGox
/EGibRGL6u31qOXOzq8CIQD0vzkHz8advunoB/v/CmZp8vbvs5XhfjEF/MxH5NTM
dQIgGr5UtCQidpMfqVcvdm0vDIh/1b0wmN9FZ65EuNPlsz8CIEXsrDqTHkfLV8C3
J0QCkKgMhoiNYnD+QLtZMnQH6NRRAiBXp3A1LKPR95qR4Pz3SCYh36W+X/D/ehRe
JzOq+X832g==
-----END PRIVATE KEY-----`;
const PRIV_512_PKCS8_ENC = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBrzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQILP5/Mw4jnpUCAggA
MB0GCWCGSAFlAwQBAgQQ18VGFlqTKWNfEro31Ev+UgSCAWAwara+xKGKPSNGOakz
DLST6Xhnxv6PcL1+Ei4RR65ns94ofPJl/2lai8R5itkL9gRbqiGi1L7rdwsP8Awb
O5d4KUnVMSEFTBDyIBU5w5Di4WJFU04PuchVHDRBiqYNHVEu0y5JmQu0cPpaTd/m
eNuLaymoIduUFLj/aMpda0BTEOwdCJ+G4P5IOwAXkGQ4JUX3NPp3S/bwmS6HYnct
W3q65mhEIuJb1CK7O1k97L9ArmFQ+p74jELvwfJTaP9FNnee3Rs1hpbQ3AVwtEtq
3w0P2XXEDDVcwNvNh+sLKhp7WYWvQM9NXnWPQFu1FiJqIfUk6/SMdplH0KEAdexb
XvERV5TRWqb/ilQPYYiJVgQpe0evZblvERMS3Ul3hSrsEYcr4MBII+RvMZHss/hQ
2yp5XcQNzP9uceT4hIGAlvTAG4BGaH2tbU4x/T9ae0mx52/8I8RS9gSgPqK2SdSf
tMER
-----END ENCRYPTED PRIVATE KEY-----`;
const HELLO_WORLD_PKCS1_B64 = "2EoJzsG9F9el3Dwxm+7Ua1Ykdpnh9WeS2prrkAYGfnkN9irgRvNIUkOSUJAgy4yQ5RjqAHkKgAdwHvjWqLuLhA==";
const EMPTY_PKCS1_B64 = "rfhNp4omqGuP+7J6Z2MuMeDwBzGcRm9kFIPPAn/S6HPXfXN/4Kasd1FUdr9n0+7egspZNSPg5TWq4GKGXuhBhA==";
TestRegister.addTests([
{
name: "RSA Encrypt Private Key: missing key",
input: "Hello World",
expectedOutput: "Please enter a private key.",
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: ["", "", "PKCS1 v1.5"]
}
]
},
{
name: "RSA Encrypt Private Key: PKCS#1 key, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: PKCS#8 key, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS8, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: encrypted PKCS#8 key with password, Hello World",
input: "Hello World",
expectedOutput: HELLO_WORLD_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS8_ENC, "testpassword", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
},
{
name: "RSA Encrypt Private Key: empty input",
input: "",
expectedOutput: EMPTY_PKCS1_B64,
recipeConfig: [
{
op: "RSA Encrypt Private Key",
args: [PRIV_512_PKCS1, "", "PKCS1 v1.5"]
},
{
op: "To Base64",
args: ["A-Za-z0-9+/="]
}
]
}
]);