Merge 26ee41b2afa5c58d26a85ff96a741d002f4f3280 into 976c8b2d56f8e344c459f26f6b2fc891303b6d17

This commit is contained in:
みけCAT 2026-02-21 20:46:11 +00:00 committed by GitHub
commit 11b4872bc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 237 additions and 2 deletions

28
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@astronautlabs/amf": "^0.0.6", "@astronautlabs/amf": "^0.0.6",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@blu3r4y/lzma": "^2.3.3", "@blu3r4y/lzma": "^2.3.3",
"@noble/ed25519": "^2.3.0",
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
"@xmldom/xmldom": "^0.8.10", "@xmldom/xmldom": "^0.8.10",
"argon2-browser": "^1.18.0", "argon2-browser": "^1.18.0",
@ -38,6 +39,7 @@
"d3-hexbin": "^0.2.2", "d3-hexbin": "^0.2.2",
"diff": "^5.2.0", "diff": "^5.2.0",
"dompurify": "^3.2.5", "dompurify": "^3.2.5",
"ed448-js": "^2.0.0",
"es6-promisify": "^7.0.0", "es6-promisify": "^7.0.0",
"escodegen": "^2.1.0", "escodegen": "^2.1.0",
"esprima": "^4.0.1", "esprima": "^4.0.1",
@ -3913,6 +3915,14 @@
"archiver": "^5.3.1" "archiver": "^5.3.1"
} }
}, },
"node_modules/@noble/ed25519": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz",
"integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": { "node_modules/@noble/hashes": {
"version": "1.6.1", "version": "1.6.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz",
@ -8504,6 +8514,15 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/ed448-js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ed448-js/-/ed448-js-2.0.0.tgz",
"integrity": "sha512-5xft1V4bJ0ji4SB3eQu4J7REsBGdv0dgLXMXu7t0ifrRBycM1fKDP+1gI0Se88cakX/VE6HEz6P23rWNBzEV6w==",
"dependencies": {
"jsbn": "^1.1.0",
"jssha": "^3.2.0"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -12404,7 +12423,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/jsdoc-type-pratt-parser": { "node_modules/jsdoc-type-pratt-parser": {
@ -12591,6 +12609,14 @@
"url": "https://github.com/kjur/jsrsasign#donations" "url": "https://github.com/kjur/jsrsasign#donations"
} }
}, },
"node_modules/jssha": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
"integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
"engines": {
"node": "*"
}
},
"node_modules/jszip": { "node_modules/jszip": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",

View File

@ -99,6 +99,7 @@
"@astronautlabs/amf": "^0.0.6", "@astronautlabs/amf": "^0.0.6",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.12.1",
"@blu3r4y/lzma": "^2.3.3", "@blu3r4y/lzma": "^2.3.3",
"@noble/ed25519": "^2.3.0",
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
"@xmldom/xmldom": "^0.8.10", "@xmldom/xmldom": "^0.8.10",
"argon2-browser": "^1.18.0", "argon2-browser": "^1.18.0",
@ -124,6 +125,7 @@
"d3-hexbin": "^0.2.2", "d3-hexbin": "^0.2.2",
"diff": "^5.2.0", "diff": "^5.2.0",
"dompurify": "^3.2.5", "dompurify": "^3.2.5",
"ed448-js": "^2.0.0",
"es6-promisify": "^7.0.0", "es6-promisify": "^7.0.0",
"escodegen": "^2.1.0", "escodegen": "^2.1.0",
"esprima": "^4.0.1", "esprima": "^4.0.1",

View File

@ -199,7 +199,8 @@
"Public Key from Certificate", "Public Key from Certificate",
"Public Key from Private Key", "Public Key from Private Key",
"SM2 Encrypt", "SM2 Encrypt",
"SM2 Decrypt" "SM2 Decrypt",
"Generate EdDSA Key Pair"
] ]
}, },
{ {

View File

@ -0,0 +1,206 @@
/**
* @author mikecat
* @copyright Crown Copyright 2025
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { cryptNotice } from "../lib/Crypt.mjs";
import forge from "node-forge";
import { isWorkerEnvironment } from "../Utils.mjs";
import * as Ed25519 from "@noble/ed25519";
import createEd448 from "ed448-js";
/**
* Generate EdDSA Key Pair operation
*/
class GenerateEdDSAKeyPair extends Operation {
/**
* GenerateEdDSAKeyPair constructor
*/
constructor() {
super();
this.name = "Generate EdDSA Key Pair";
this.module = "Ciphers";
this.description = `Generate an EdDSA (Ed25519 and Ed448) key pair.<br><br>${cryptNotice}`;
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc8032";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Instance",
"type": "option",
"value": ["Ed25519", "Ed448"]
},
{
"name": "Output Format",
"type": "option",
"value": ["PEM", "JWK", "OpenSSH", "Raw"]
}
];
// create Ed448 instance later (creating here resulted in errors in bundling)
this.Ed448 = null;
this.getRandomBytes = (length) => {
if (isWorkerEnvironment() && self.crypto) {
const result = new Uint8Array(length);
self.crypto.getRandomValues(result);
return Array.from(result);
} else {
const randomStr = forge.random.getBytesSync(length);
return Array.from(randomStr).map((e) => e.charCodeAt(0));
}
};
this.bytesToHex = (byteArray) => Ed25519.etc.bytesToHex(new Uint8Array(byteArray));
this.bytesToBase64 = (byteArray) => btoa(byteArray.map((c) => String.fromCharCode(c)).join(""));
this.bytesToBase64url = (byteArray) => (
this.bytesToBase64(byteArray).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
);
this.insertNewlines = (str, length) => {
let result = "";
for (;;) {
result += str.substring(0, length);
str = str.substring(length);
if (str.length > 0) {
result += "\n";
} else {
return result;
}
}
};
this.textEncoder = new TextEncoder();
this.strToBytes = (str) => Array.from(this.textEncoder.encode(str));
this.uint32ToBytes = (value) => {
const arrayBuffer = new ArrayBuffer(4);
const dataView = new DataView(arrayBuffer);
dataView.setUint32(0, value);
return Array.from(new Uint8Array(arrayBuffer));
};
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const instance = args[0], outputFormat = args[1];
let privateKey, publicKey, crv, objectIdentifier, opensshKeyType;
switch (instance) {
case "Ed448":
if (!this.Ed448) this.Ed448 = createEd448();
privateKey = this.getRandomBytes(57);
publicKey = Array.from(this.Ed448.getPublicKey(new Uint8Array(privateKey)));
crv = "Ed448";
objectIdentifier = [1 * 40 + 3, 101, 113];
opensshKeyType = "ssh-ed448";
break;
default: // Ed25519
privateKey = this.getRandomBytes(32);
publicKey = Array.from(await Ed25519.getPublicKeyAsync(new Uint8Array(privateKey)));
crv = "Ed25519";
objectIdentifier = [1 * 40 + 3, 101, 112];
opensshKeyType = "ssh-ed25519";
break;
}
switch (outputFormat) {
case "PEM":
{
// assuming data to deal with here is short enough
const objectIdentifierSequence = [0x30, objectIdentifier.length + 2, 6, objectIdentifier.length].concat(objectIdentifier);
const privateKeyOctetString = [4, privateKey.length + 2, 4, privateKey.length].concat(privateKey);
const privateKeySequenceData = [2, 1, 0].concat(objectIdentifierSequence, privateKeyOctetString);
const privateKeyBytes = [0x30, privateKeySequenceData.length].concat(privateKeySequenceData);
const publicKeyBitString = [3, publicKey.length + 1, 0].concat(publicKey);
const publicKeySequenceData = objectIdentifierSequence.concat(publicKeyBitString);
const publicKeyBytes = [0x30, publicKeySequenceData.length].concat(publicKeySequenceData);
return (
"-----BEGIN PUBLIC KEY-----\n" +
this.insertNewlines(this.bytesToBase64(publicKeyBytes), 64) +
"\n-----END PUBLIC KEY-----\n\n-----BEGIN PRIVATE KEY-----\n" +
this.insertNewlines(this.bytesToBase64(privateKeyBytes), 64) +
"\n-----END PRIVATE KEY-----\n"
);
}
case "JWK":
{
const publicKeyJWK = {
kty: "OKP",
crv,
x: this.bytesToBase64url(publicKey)
};
return JSON.stringify({
keys: [
{
...publicKeyJWK,
d: this.bytesToBase64url(privateKey),
key_ops: ["sign"], // eslint-disable-line camelcase
kid: "PrivateKey"
},
{
...publicKeyJWK,
key_ops: ["verify"], // eslint-disable-line camelcase
kid: "PublicKey"
}
]
}, null, 4);
}
case "OpenSSH":
{
const comment = "cyberchef";
const commentBytes = this.strToBytes(comment);
const encryptMethodBytes = this.strToBytes("none");
const kdfMethodBytes = this.strToBytes("none");
const kdfParameterBytes = [];
const checkValue = this.getRandomBytes(4);
const keyTypeBytes = this.strToBytes(opensshKeyType);
const publicKeyBytes = [].concat(
this.uint32ToBytes(keyTypeBytes.length),
keyTypeBytes,
this.uint32ToBytes(publicKey.length),
publicKey
);
const privateKeyDataBytes = [].concat(
checkValue,
checkValue,
publicKeyBytes,
this.uint32ToBytes(privateKey.length + publicKey.length),
privateKey,
publicKey,
this.uint32ToBytes(commentBytes.length),
commentBytes
);
for (let i = 1; privateKeyDataBytes.length % 8 !== 0; i++) {
privateKeyDataBytes.push(i % 256);
}
const privateKeyBytes = [].concat(
this.strToBytes("openssh-key-v1"),
[0],
this.uint32ToBytes(encryptMethodBytes.length),
encryptMethodBytes,
this.uint32ToBytes(kdfMethodBytes.length),
kdfMethodBytes,
this.uint32ToBytes(kdfParameterBytes.length),
kdfParameterBytes,
this.uint32ToBytes(1),
this.uint32ToBytes(publicKeyBytes.length),
publicKeyBytes,
this.uint32ToBytes(privateKeyDataBytes.length),
privateKeyDataBytes
);
return (
opensshKeyType + " " + this.bytesToBase64(publicKeyBytes) + " " + comment +
"\n\n-----BEGIN OPENSSH PRIVATE KEY-----\n" +
this.insertNewlines(this.bytesToBase64(privateKeyBytes), 70) +
"\n-----END OPENSSH PRIVATE KEY-----\n"
);
}
default: // Raw
return this.bytesToHex(new Uint8Array(privateKey.concat(publicKey)));
}
}
}
export default GenerateEdDSAKeyPair;