Merge branch 'master' into parse-ethernet-frame

This commit is contained in:
Ted Kruijff 2024-05-01 10:47:12 +02:00 committed by GitHub
commit 2cd47f7103
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 3049 additions and 328 deletions

View File

@ -13,6 +13,24 @@ All major and minor version changes will be documented in this file. Details of
## Details
### [10.18.0] - 2024-04-24
- Added 'XXTEA Encrypt' and 'XXTEA Decrypt' operations [@n1474335] | [0a353ee]
### [10.17.0] - 2024-04-13
- Fix unit test 'expectOutput' implementation [@zb3] | [#1783]
- Add accessibility labels for icons [@e218736] | [#1743]
- Add focus styling for keyboard navigation [@e218736] | [#1739]
- Add support for operation option hiding [@TheZ3ro] | [#541]
- Improve efficiency of RAKE implementation [@sw5678] | [#1751]
- Require (a, 26) to be coprime in 'Affine Encode' [@EvieHarv] | [#1788]
- Added 'JWK to PEM' operation [@cplussharp] | [#1277]
- Added 'PEM to JWK' operation [@cplussharp] | [#1277]
- Added 'Public Key from Certificate' operation [@cplussharp] | [#1642]
- Added 'Public Key from Private Key' operation [@cplussharp] | [#1642]
### [10.16.0] - 2024-04-12
- Added 'JA4Server Fingerprint' operation [@n1474335] | [#1789]
### [10.15.0] - 2024-04-02
- Fix Ciphersaber2 key concatenation [@zb3] | [#1765]
- Fix DeriveEVPKey's array parsing [@zb3] | [#1767]
@ -418,6 +436,9 @@ All major and minor version changes will be documented in this file. Details of
## [4.0.0] - 2016-11-28
- Initial open source commit [@n1474335] | [b1d73a72](https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306)
[10.18.0]: https://github.com/gchq/CyberChef/releases/tag/v10.18.0
[10.17.0]: https://github.com/gchq/CyberChef/releases/tag/v10.17.0
[10.16.0]: https://github.com/gchq/CyberChef/releases/tag/v10.16.0
[10.15.0]: https://github.com/gchq/CyberChef/releases/tag/v10.15.0
[10.14.0]: https://github.com/gchq/CyberChef/releases/tag/v10.14.0
[10.13.0]: https://github.com/gchq/CyberChef/releases/tag/v10.13.0
@ -598,6 +619,10 @@ All major and minor version changes will be documented in this file. Details of
[@zb3]: https://github.com/zb3
[@jkataja]: https://github.com/jkataja
[@tomgond]: https://github.com/tomgond
[@e218736]: https://github.com/e218736
[@TheZ3ro]: https://github.com/TheZ3ro
[@EvieHarv]: https://github.com/EvieHarv
[@cplussharp]: https://github.com/cplussharp
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
@ -609,6 +634,7 @@ All major and minor version changes will be documented in this file. Details of
[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff
[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1
[65ffd8d]: https://github.com/gchq/CyberChef/commit/65ffd8d65d88eb369f6f61a5d1d0f807179bffb7
[0a353ee]: https://github.com/gchq/CyberChef/commit/0a353eeb378b9ca5d49e23c7dfc175ae07107b08
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@ -744,3 +770,5 @@ All major and minor version changes will be documented in this file. Details of
[#1504]: https://github.com/gchq/CyberChef/issues/1504
[#512]: https://github.com/gchq/CyberChef/issues/512
[#1732]: https://github.com/gchq/CyberChef/issues/1732
[#1789]: https://github.com/gchq/CyberChef/issues/1789

12
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "cyberchef",
"version": "10.15.1",
"version": "10.18.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cyberchef",
"version": "10.15.1",
"version": "10.18.3",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@ -115,7 +115,7 @@
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-transform-builtin-extend": "1.1.2",
"base64-loader": "^1.0.0",
"chromedriver": "^122.0.0",
"chromedriver": "^123.0.4",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"copy-webpack-plugin": "^12.0.2",
@ -4744,9 +4744,9 @@
}
},
"node_modules/chromedriver": {
"version": "122.0.6",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.6.tgz",
"integrity": "sha512-Q0r+QlUtiJWMQ5HdYaFa0CtBmLFq3n5JWfmq9mOC00UMBvWxku09gUkvBt457QnYfTM/XHqY/HTFOxHvATnTmA==",
"version": "123.0.4",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.4.tgz",
"integrity": "sha512-3Yi7y7q35kkSAOTbRisiww/SL2w+DqafDPAaUShpSuLMmPaOvHQR0i3bm2/33QBiQ8fUb1J/MzppzVL6IDqvhA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "10.15.1",
"version": "10.18.3",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@ -55,7 +55,7 @@
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-transform-builtin-extend": "1.1.2",
"base64-loader": "^1.0.0",
"chromedriver": "^122.0.0",
"chromedriver": "^123.0.4",
"cli-progress": "^3.12.0",
"colors": "^1.4.0",
"copy-webpack-plugin": "^12.0.2",
@ -96,6 +96,7 @@
"@babel/polyfill": "^7.12.1",
"@blu3r4y/lzma": "^2.3.3",
"@wavesenterprise/crypto-gost-js": "^2.1.0-RC1",
"@xmldom/xmldom": "^0.8.0",
"argon2-browser": "^1.18.0",
"arrive": "^2.4.1",
"avsc": "^5.7.7",
@ -122,12 +123,12 @@
"escodegen": "^2.1.0",
"esprima": "^4.0.1",
"exif-parser": "^0.1.12",
"ieee754": "^1.1.13",
"fernet": "^0.3.2",
"file-saver": "^2.0.5",
"flat": "^6.0.1",
"geodesy": "1.1.3",
"highlight.js": "^11.9.0",
"ieee754": "^1.1.13",
"jimp": "^0.16.13",
"jquery": "3.7.1",
"js-crc": "^0.2.0",
@ -175,7 +176,6 @@
"unorm": "^1.6.0",
"utf8": "^3.0.0",
"vkbeautify": "^0.99.3",
"@xmldom/xmldom": "^0.8.0",
"xpath": "0.0.34",
"xregexp": "^5.1.1",
"zlibjs": "^0.3.1"

View File

@ -117,6 +117,8 @@
"XOR Brute Force",
"Vigenère Encode",
"Vigenère Decode",
"XXTEA Encrypt",
"XXTEA Decrypt",
"To Morse Code",
"From Morse Code",
"Bacon Cipher Encode",
@ -155,8 +157,7 @@
"Typex",
"Lorenz",
"Colossus",
"SIGABA",
"XXTEA"
"SIGABA"
]
},
{
@ -168,6 +169,8 @@
"Hex to PEM",
"Hex to Object Identifier",
"Object Identifier to Hex",
"PEM to JWK",
"JWK to PEM",
"Generate PGP Key Pair",
"PGP Encrypt",
"PGP Decrypt",
@ -179,8 +182,14 @@
"RSA Verify",
"RSA Encrypt",
"RSA Decrypt",
"Generate ECDSA Key Pair",
"ECDSA Signature Conversion",
"ECDSA Sign",
"ECDSA Verify",
"Parse SSH Host Key",
"Parse CSR"
"Parse CSR",
"Public Key from Certificate",
"Public Key from Private Key"
]
},
{
@ -239,6 +248,7 @@
"JA3 Fingerprint",
"JA3S Fingerprint",
"JA4 Fingerprint",
"JA4Server Fingerprint",
"HASSH Client Fingerprint",
"HASSH Server Fingerprint",
"Format MAC addresses",

View File

@ -3,6 +3,7 @@
*
* @author Matt C [matt@artemisbot.uk]
* @author n1474335 [n1474335@gmail.com]
* @author Evie H [evie@evie.sh]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
@ -10,6 +11,7 @@
*/
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import CryptoJS from "crypto-js";
/**
@ -30,6 +32,10 @@ export function affineEncode(input, args) {
throw new OperationError("The values of a and b can only be integers.");
}
if (Utils.gcd(a, 26) !== 1) {
throw new OperationError("The value of `a` must be coprime to 26.");
}
for (let i = 0; i < input.length; i++) {
if (alphabet.indexOf(input[i]) >= 0) {
// Uses the affine function ax+b % m = y (where m is length of the alphabet)

View File

@ -25,6 +25,9 @@ export function toJA4(bytes) {
let tlsr = {};
try {
tlsr = parseTLSRecord(bytes);
if (tlsr.handshake.value.handshakeType.value !== 0x01) {
throw new Error();
}
} catch (err) {
throw new OperationError("Data is not a valid TLS Client Hello. QUIC is not yet supported.\n" + err);
}
@ -48,16 +51,7 @@ export function toJA4(bytes) {
break;
}
}
switch (version) {
case 0x0304: version = "13"; break; // TLS 1.3
case 0x0303: version = "12"; break; // TLS 1.2
case 0x0302: version = "11"; break; // TLS 1.1
case 0x0301: version = "10"; break; // TLS 1.0
case 0x0300: version = "s3"; break; // SSL 3.0
case 0x0200: version = "s2"; break; // SSL 2.0
case 0x0100: version = "s1"; break; // SSL 1.0
default: version = "00"; // Unknown
}
version = tlsVersionMapper(version);
/* SNI
If the SNI extension (0x0000) exists, then the destination of the connection is a domain, or d in the fingerprint.
@ -99,6 +93,7 @@ export function toJA4(bytes) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
break;
}
}
@ -164,3 +159,106 @@ export function toJA4(bytes) {
"JA4_ro": `${ptype}${version}${sni}${cipherLen}${extLen}${alpn}_${originalCiphersRaw}_${originalExtensionsRaw}`,
};
}
/**
* Calculate the JA4Server from a given TLS Server Hello Stream
* @param {Uint8Array} bytes
* @returns {string}
*/
export function toJA4S(bytes) {
let tlsr = {};
try {
tlsr = parseTLSRecord(bytes);
if (tlsr.handshake.value.handshakeType.value !== 0x02) {
throw new Error();
}
} catch (err) {
throw new OperationError("Data is not a valid TLS Server Hello. QUIC is not yet supported.\n" + err);
}
/* QUIC
q or t, which denotes whether the hello packet is for QUIC or TCP.
TODO: Implement QUIC
*/
const ptype = "t";
/* TLS Version
TLS version is shown in 3 different places. If extension 0x002b exists (supported_versions), then the version
is the highest value in the extension. Remember to ignore GREASE values. If the extension doesnt exist, then
the TLS version is the value of the Protocol Version. Handshake version (located at the top of the packet)
should be ignored.
*/
let version = tlsr.version.value;
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "supported_versions") {
version = parseHighestSupportedVersion(ext.value.data);
break;
}
}
version = tlsVersionMapper(version);
/* Number of Extensions
2 character number of cipher suites, so if theres 6 cipher suites in the hello packet, then the value should be 06.
If theres > 99, which there should never be, then output 99.
*/
let extLen = tlsr.handshake.value.extensions.value.length;
extLen = extLen > 99 ? "99" : extLen.toString().padStart(2, "0");
/* ALPN Extension Chosen Value
The first and last characters of the ALPN (Application-Layer Protocol Negotiation) first value.
If there are no ALPN values or no ALPN extension then we print 00 as the value in the fingerprint.
*/
let alpn = "00";
for (const ext of tlsr.handshake.value.extensions.value) {
if (ext.type.value === "application_layer_protocol_negotiation") {
alpn = parseFirstALPNValue(ext.value.data);
alpn = alpn.charAt(0) + alpn.charAt(alpn.length - 1);
if (alpn.charCodeAt(0) > 127) alpn = "99";
break;
}
}
/* Chosen Cipher
The hex value of the chosen cipher suite
*/
const cipher = toHexFast(tlsr.handshake.value.cipherSuite.data);
/* Extension hash
A 12 character truncated sha256 hash of the list of extensions.
The extension list is created using the 4 character hex values of the extensions, lower case, comma delimited.
*/
const extensionsList = [];
for (const ext of tlsr.handshake.value.extensions.value) {
extensionsList.push(toHexFast(ext.type.data));
}
const extensionsRaw = extensionsList.join(",");
const extensionsHash = runHash(
"sha256",
Utils.strToArrayBuffer(extensionsRaw)
).substring(0, 12);
return {
"JA4S": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsHash}`,
"JA4S_r": `${ptype}${version}${extLen}${alpn}_${cipher}_${extensionsRaw}`,
};
}
/**
* Takes a TLS version value and returns a JA4 TLS version string
* @param {Uint8Array} version - Two byte array of version number
* @returns {string}
*/
function tlsVersionMapper(version) {
switch (version) {
case 0x0304: return "13"; // TLS 1.3
case 0x0303: return "12"; // TLS 1.2
case 0x0302: return "11"; // TLS 1.1
case 0x0301: return "10"; // TLS 1.0
case 0x0300: return "s3"; // SSL 3.0
case 0x0200: return "s2"; // SSL 2.0
case 0x0100: return "s1"; // SSL 1.0
default: return "00"; // Unknown
}
}

View File

@ -70,13 +70,11 @@ function parseHandshake(bytes) {
// Handshake type
h.handshakeType = {
description: "Client Hello",
description: "Handshake Type",
length: 1,
data: b.getBytes(1),
value: s.readInt(1)
};
if (h.handshakeType.value !== 0x01)
throw new OperationError("Not a Client Hello.");
// Handshake length
h.handshakeLength = {
@ -86,8 +84,33 @@ function parseHandshake(bytes) {
value: s.readInt(3)
};
if (s.length !== h.handshakeLength.value + 4)
throw new OperationError("Not enough data in Client Hello.");
throw new OperationError("Not enough data in Handshake message.");
switch (h.handshakeType.value) {
case 0x01:
h.handshakeType.description = "Client Hello";
parseClientHello(s, b, h);
break;
case 0x02:
h.handshakeType.description = "Server Hello";
parseServerHello(s, b, h);
break;
default:
throw new OperationError("Not a known handshake message.");
}
return h;
}
/**
* Parse a TLS Client Hello
* @param {Stream} s
* @param {Stream} b
* @param {Object} h
* @returns {JSON}
*/
function parseClientHello(s, b, h) {
// Hello version
h.helloVersion = {
description: "Client Hello Version",
@ -171,6 +194,79 @@ function parseHandshake(bytes) {
return h;
}
/**
* Parse a TLS Server Hello
* @param {Stream} s
* @param {Stream} b
* @param {Object} h
* @returns {JSON}
*/
function parseServerHello(s, b, h) {
// Hello version
h.helloVersion = {
description: "Server Hello Version",
length: 2,
data: b.getBytes(2),
value: s.readInt(2)
};
// Random
h.random = {
description: "Server Random",
length: 32,
data: b.getBytes(32),
value: s.getBytes(32)
};
// Session ID Length
h.sessionIDLength = {
description: "Session ID Length",
length: 1,
data: b.getBytes(1),
value: s.readInt(1)
};
// Session ID
h.sessionID = {
description: "Session ID",
length: h.sessionIDLength.value,
data: b.getBytes(h.sessionIDLength.value),
value: s.getBytes(h.sessionIDLength.value)
};
// Cipher Suite
h.cipherSuite = {
description: "Selected Cipher Suite",
length: 2,
data: b.getBytes(2),
value: CIPHER_SUITES_LOOKUP[s.readInt(2)] || "Unknown"
};
// Compression Method
h.compressionMethod = {
description: "Selected Compression Method",
length: 1,
data: b.getBytes(1),
value: s.readInt(1) // TODO: Compression method name here
};
// Extensions Length
h.extensionsLength = {
description: "Extensions Length",
length: 2,
data: b.getBytes(2),
value: s.readInt(2)
};
// Extensions
h.extensions = {
description: "Extensions",
length: h.extensionsLength.value,
data: b.getBytes(h.extensionsLength.value),
value: parseExtensions(s.getBytes(h.extensionsLength.value))
};
}
/**
* Parse Cipher Suites
* @param {Uint8Array} bytes
@ -748,6 +844,11 @@ export const GREASE_VALUES = [
export function parseHighestSupportedVersion(bytes) {
const s = new Stream(bytes);
// The Server Hello supported_versions extension simply contains the chosen version
if (s.length === 2) {
return s.readInt(2);
}
// Length
let i = s.readInt(1);

174
src/core/lib/XXTEA.mjs Normal file
View File

@ -0,0 +1,174 @@
/**
* XXTEA library
*
* Encryption Algorithm Authors:
* David J. Wheeler
* Roger M. Needham
*
* @author Ma Bingyao [mabingyao@gmail.com]
* @author n1474335 [n1474335@gmail.com]
* @license MIT
*/
const DELTA = 0x9E3779B9;
/**
* Convert a buffer to a Uint8Array
* @param {Uint32Array} v
* @param {boolean} includeLength
* @returns {Uint8Array}
*/
function toUint8Array(v, includeLength) {
const length = v.length;
let n = length << 2;
if (includeLength) {
const m = v[length - 1];
n -= 4;
if ((m < n - 3) || (m > n)) {
return null;
}
n = m;
}
const bytes = new Uint8Array(n);
for (let i = 0; i < n; i++) {
bytes[i] = v[i >> 2] >> ((i & 3) << 3);
}
return bytes;
}
/**
* Convert a buffer to a Uint32Array
* @param {TypedArray} bs
* @param {boolean} includeLength
* @returns {Uint32Array}
*/
function toUint32Array(bs, includeLength) {
const length = bs.length;
let n = length >> 2;
if ((length & 3) !== 0) {
++n;
}
let v;
if (includeLength) {
v = new Uint32Array(n + 1);
v[n] = length;
} else {
v = new Uint32Array(n);
}
for (let i = 0; i < length; ++i) {
v[i >> 2] |= bs[i] << ((i & 3) << 3);
}
return v;
}
/**
* Mask an int to 32 bits
* @param {number} i
* @returns {number}
*/
function int32(i) {
return i & 0xFFFFFFFF;
}
/**
* MX function for data randomisation
* @param {number} sum
* @param {number} y
* @param {number} z
* @param {number} p
* @param {number} e
* @param {number} k
* @returns {number}
*/
function mx(sum, y, z, p, e, k) {
return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
}
/**
* Ensure an array is a multiple of 16 bits
* @param {TypedArray} k
* @returns {TypedArray}
*/
function fixk(k) {
if (k.length < 16) {
const key = new Uint8Array(16);
key.set(k);
return key;
}
return k;
}
/**
* Performs XXTEA encryption on a Uint32Array
* @param {Uint32Array} v
* @param {Uint32Array} k
* @returns {Uint32Array}
*/
function encryptUint32Array(v, k) {
const length = v.length;
const n = length - 1;
let y, z, sum, e, p, q;
z = v[n];
sum = 0;
for (q = Math.floor(6 + 52 / length) | 0; q > 0; --q) {
sum = int32(sum + DELTA);
e = sum >>> 2 & 3;
for (p = 0; p < n; ++p) {
y = v[p + 1];
z = v[p] = int32(v[p] + mx(sum, y, z, p, e, k));
}
y = v[0];
z = v[n] = int32(v[n] + mx(sum, y, z, n, e, k));
}
return v;
}
/**
* Performs XXTEA decryption on a Uint32Array
* @param {Uint32Array} v
* @param {Uint32Array} k
* @returns {Uint32Array}
*/
function decryptUint32Array(v, k) {
const length = v.length;
const n = length - 1;
let y, z, sum, e, p;
y = v[0];
const q = Math.floor(6 + 52 / length);
for (sum = int32(q * DELTA); sum !== 0; sum = int32(sum - DELTA)) {
e = sum >>> 2 & 3;
for (p = n; p > 0; --p) {
z = v[p - 1];
y = v[p] = int32(v[p] - mx(sum, y, z, p, e, k));
}
z = v[n];
y = v[0] = int32(v[0] - mx(sum, y, z, 0, e, k));
}
return v;
}
/**
* Encrypt function
* @param {TypedArray} data
* @param {TypedArray} key
* @returns {Uint8Array}
*/
export function encrypt(data, key) {
if (data === undefined || data === null || data.length === 0) {
return data;
}
return toUint8Array(encryptUint32Array(toUint32Array(data, true), toUint32Array(fixk(key), false)), false);
}
/**
* Decrypt function
* @param {TypedArray} data
* @param {TypedArray} key
* @returns {Uint8Array}
*/
export function decrypt(data, key) {
if (data === undefined || data === null || data.length === 0) {
return data;
}
return toUint8Array(decryptUint32Array(toUint32Array(data, false), toUint32Array(fixk(key), false)), true);
}

View File

@ -0,0 +1,107 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { fromHex } from "../lib/Hex.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import r from "jsrsasign";
/**
* ECDSA Sign operation
*/
class ECDSASign extends Operation {
/**
* ECDSASign constructor
*/
constructor() {
super();
this.name = "ECDSA Sign";
this.module = "Ciphers";
this.description = "Sign a plaintext message with a PEM encoded EC key.";
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "ECDSA Private Key (PEM)",
type: "text",
value: "-----BEGIN EC PRIVATE KEY-----"
},
{
name: "Message Digest Algorithm",
type: "option",
value: [
"SHA-256",
"SHA-384",
"SHA-512",
"SHA-1",
"MD5"
]
},
{
name: "Output Format",
type: "option",
value: [
"ASN.1 HEX",
"P1363 HEX",
"JSON Web Signature",
"Raw JSON"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [keyPem, mdAlgo, outputFormat] = args;
if (keyPem.replace("-----BEGIN EC PRIVATE KEY-----", "").length === 0) {
throw new OperationError("Please enter a private key.");
}
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
const key = r.KEYUTIL.getKey(keyPem);
if (key.type !== "EC") {
throw new OperationError("Provided key is not an EC key.");
}
if (!key.isPrivate) {
throw new OperationError("Provided key is not a private key.");
}
sig.init(key);
const signatureASN1Hex = sig.signString(input);
let result;
switch (outputFormat) {
case "ASN.1 HEX":
result = signatureASN1Hex;
break;
case "P1363 HEX":
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
break;
case "JSON Web Signature":
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
break;
case "Raw JSON": {
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
result = JSON.stringify(signatureRS);
break;
}
}
return result;
}
}
export default ECDSASign;

View File

@ -0,0 +1,146 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { fromBase64, toBase64 } from "../lib/Base64.mjs";
import { fromHex, toHexFast } from "../lib/Hex.mjs";
import r from "jsrsasign";
/**
* ECDSA Sign operation
*/
class ECDSASignatureConversion extends Operation {
/**
* ECDSASignatureConversion constructor
*/
constructor() {
super();
this.name = "ECDSA Signature Conversion";
this.module = "Ciphers";
this.description = "Convert an ECDSA signature between hex, asn1 and json.";
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input Format",
type: "option",
value: [
"Auto",
"ASN.1 HEX",
"P1363 HEX",
"JSON Web Signature",
"Raw JSON"
]
},
{
name: "Output Format",
type: "option",
value: [
"ASN.1 HEX",
"P1363 HEX",
"JSON Web Signature",
"Raw JSON"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let inputFormat = args[0];
const outputFormat = args[1];
// detect input format
let inputJson;
if (inputFormat === "Auto") {
try {
inputJson = JSON.parse(input);
if (typeof(inputJson) === "object") {
inputFormat = "Raw JSON";
}
} catch {}
}
if (inputFormat === "Auto") {
const hexRegex = /^[a-f\d]{2,}$/gi;
if (hexRegex.test(input)) {
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
inputFormat = "ASN.1 HEX";
} else {
inputFormat = "P1363 HEX";
}
}
}
let inputBase64;
if (inputFormat === "Auto") {
try {
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
inputFormat = "JSON Web Signature";
} catch {}
}
// convert input to ASN.1 hex
let signatureASN1Hex;
switch (inputFormat) {
case "Auto":
throw new OperationError("Signature format could not be detected");
case "ASN.1 HEX":
signatureASN1Hex = input;
break;
case "P1363 HEX":
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
break;
case "JSON Web Signature":
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
break;
case "Raw JSON": {
if (!inputJson) inputJson = JSON.parse(input);
if (!inputJson.r) {
throw new OperationError('No "r" value in the signature JSON');
}
if (!inputJson.s) {
throw new OperationError('No "s" value in the signature JSON');
}
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
break;
}
}
// convert ASN.1 hex to output format
let result;
switch (outputFormat) {
case "ASN.1 HEX":
result = signatureASN1Hex;
break;
case "P1363 HEX":
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
break;
case "JSON Web Signature":
result = r.KJUR.crypto.ECDSA.asn1SigToConcatSig(signatureASN1Hex);
result = toBase64(fromHex(result), "A-Za-z0-9-_"); // base64url
break;
case "Raw JSON": {
const signatureRS = r.KJUR.crypto.ECDSA.parseSigHexInHexRS(signatureASN1Hex);
result = JSON.stringify(signatureRS);
break;
}
}
return result;
}
}
export default ECDSASignatureConversion;

View File

@ -0,0 +1,154 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { fromBase64 } from "../lib/Base64.mjs";
import { toHexFast } from "../lib/Hex.mjs";
import r from "jsrsasign";
/**
* ECDSA Verify operation
*/
class ECDSAVerify extends Operation {
/**
* ECDSAVerify constructor
*/
constructor() {
super();
this.name = "ECDSA Verify";
this.module = "Ciphers";
this.description = "Verify a message against a signature and a public PEM encoded EC key.";
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input Format",
type: "option",
value: [
"Auto",
"ASN.1 HEX",
"P1363 HEX",
"JSON Web Signature",
"Raw JSON"
]
},
{
name: "Message Digest Algorithm",
type: "option",
value: [
"SHA-256",
"SHA-384",
"SHA-512",
"SHA-1",
"MD5"
]
},
{
name: "ECDSA Public Key (PEM)",
type: "text",
value: "-----BEGIN PUBLIC KEY-----"
},
{
name: "Message",
type: "text",
value: ""
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let inputFormat = args[0];
const [, mdAlgo, keyPem, msg] = args;
if (keyPem.replace("-----BEGIN PUBLIC KEY-----", "").length === 0) {
throw new OperationError("Please enter a public key.");
}
// detect input format
let inputJson;
if (inputFormat === "Auto") {
try {
inputJson = JSON.parse(input);
if (typeof(inputJson) === "object") {
inputFormat = "Raw JSON";
}
} catch {}
}
if (inputFormat === "Auto") {
const hexRegex = /^[a-f\d]{2,}$/gi;
if (hexRegex.test(input)) {
if (input.substring(0, 2) === "30" && r.ASN1HEX.isASN1HEX(input)) {
inputFormat = "ASN.1 HEX";
} else {
inputFormat = "P1363 HEX";
}
}
}
let inputBase64;
if (inputFormat === "Auto") {
try {
inputBase64 = fromBase64(input, "A-Za-z0-9-_", false);
inputFormat = "JSON Web Signature";
} catch {}
}
// convert to ASN.1 signature
let signatureASN1Hex;
switch (inputFormat) {
case "Auto":
throw new OperationError("Signature format could not be detected");
case "ASN.1 HEX":
signatureASN1Hex = input;
break;
case "P1363 HEX":
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(input);
break;
case "JSON Web Signature":
if (!inputBase64) inputBase64 = fromBase64(input, "A-Za-z0-9-_");
signatureASN1Hex = r.KJUR.crypto.ECDSA.concatSigToASN1Sig(toHexFast(inputBase64));
break;
case "Raw JSON": {
if (!inputJson) inputJson = JSON.parse(input);
if (!inputJson.r) {
throw new OperationError('No "r" value in the signature JSON');
}
if (!inputJson.s) {
throw new OperationError('No "s" value in the signature JSON');
}
signatureASN1Hex = r.KJUR.crypto.ECDSA.hexRSSigToASN1Sig(inputJson.r, inputJson.s);
break;
}
}
// verify signature
const internalAlgorithmName = mdAlgo.replace("-", "") + "withECDSA";
const sig = new r.KJUR.crypto.Signature({ alg: internalAlgorithmName });
const key = r.KEYUTIL.getKey(keyPem);
if (key.type !== "EC") {
throw new OperationError("Provided key is not an EC key.");
}
if (!key.isPublic) {
throw new OperationError("Provided key is not a public key.");
}
sig.init(key);
sig.updateString(msg);
const result = sig.verify(signatureASN1Hex);
return result ? "Verified OK" : "Verification Failure";
}
}
export default ECDSAVerify;

View File

@ -0,0 +1,102 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import { cryptNotice } from "../lib/Crypt.mjs";
import r from "jsrsasign";
/**
* Generate ECDSA Key Pair operation
*/
class GenerateECDSAKeyPair extends Operation {
/**
* GenerateECDSAKeyPair constructor
*/
constructor() {
super();
this.name = "Generate ECDSA Key Pair";
this.module = "Ciphers";
this.description = `Generate an ECDSA key pair with a given Curve.<br><br>${cryptNotice}`;
this.infoURL = "https://wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Elliptic Curve",
type: "option",
value: [
"P-256",
"P-384",
"P-521"
]
},
{
name: "Output Format",
type: "option",
value: [
"PEM",
"DER",
"JWK"
]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
const [curveName, outputFormat] = args;
return new Promise((resolve, reject) => {
let internalCurveName;
switch (curveName) {
case "P-256":
internalCurveName = "secp256r1";
break;
case "P-384":
internalCurveName = "secp384r1";
break;
case "P-521":
internalCurveName = "secp521r1";
break;
}
const keyPair = r.KEYUTIL.generateKeypair("EC", internalCurveName);
let pubKey;
let privKey;
let result;
switch (outputFormat) {
case "PEM":
pubKey = r.KEYUTIL.getPEM(keyPair.pubKeyObj).replace(/\r/g, "");
privKey = r.KEYUTIL.getPEM(keyPair.prvKeyObj, "PKCS8PRV").replace(/\r/g, "");
result = pubKey + "\n" + privKey;
break;
case "DER":
result = keyPair.prvKeyObj.prvKeyHex;
break;
case "JWK":
pubKey = r.KEYUTIL.getJWKFromKey(keyPair.pubKeyObj);
pubKey.key_ops = ["verify"]; // eslint-disable-line camelcase
pubKey.kid = "PublicKey";
privKey = r.KEYUTIL.getJWKFromKey(keyPair.prvKeyObj);
privKey.key_ops = ["sign"]; // eslint-disable-line camelcase
privKey.kid = "PrivateKey";
result = JSON.stringify({keys: [privKey, pubKey]}, null, 4);
break;
}
resolve(result);
});
}
}
export default GenerateECDSAKeyPair;

View File

@ -0,0 +1,66 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {toJA4S} from "../lib/JA4.mjs";
/**
* JA4Server Fingerprint operation
*/
class JA4ServerFingerprint extends Operation {
/**
* JA4ServerFingerprint constructor
*/
constructor() {
super();
this.name = "JA4Server Fingerprint";
this.module = "Crypto";
this.description = "Generates a JA4Server Fingerprint (JA4S) to help identify TLS servers or sessions based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS or QUIC Server Hello packet application layer.";
this.infoURL = "https://medium.com/foxio/ja4-network-fingerprinting-9376fe9ca637";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["JA4S", "JA4S Raw", "Both"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const ja4s = toJA4S(new Uint8Array(input));
// Output
switch (outputFormat) {
case "JA4S":
return ja4s.JA4S;
case "JA4S Raw":
return ja4s.JA4S_r;
case "Both":
default:
return `JA4S: ${ja4s.JA4S}\nJA4S_r: ${ja4s.JA4S_r}`;
}
}
}
export default JA4ServerFingerprint;

View File

@ -0,0 +1,80 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import r from "jsrsasign";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* PEM to JWK operation
*/
class PEMToJWK extends Operation {
/**
* PEMToJWK constructor
*/
constructor() {
super();
this.name = "JWK to PEM";
this.module = "PublicKey";
this.description = "Converts Keys in JSON Web Key format to PEM format (PKCS#8).";
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [
{
"pattern": "\"kty\":\\s*\"(EC|RSA)\"",
"flags": "gm",
"args": []
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const inputJson = JSON.parse(input);
let keys = [];
if (Array.isArray(inputJson)) {
// list of keys => transform all keys
keys = inputJson;
} else if (Array.isArray(inputJson.keys)) {
// JSON Web Key Set => transform all keys
keys = inputJson.keys;
} else if (typeof inputJson === "object") {
// single key
keys.push(inputJson);
} else {
throw new OperationError("Input is not a JSON Web Key");
}
let output = "";
for (let i=0; i<keys.length; i++) {
const jwk = keys[i];
if (typeof jwk.kty !== "string") {
throw new OperationError("Invalid JWK format");
} else if ("|RSA|EC|".indexOf(jwk.kty) === -1) {
throw new OperationError(`Unsupported JWK key type '${inputJson.kty}'`);
}
const key = r.KEYUTIL.getKey(jwk);
const pem = key.isPrivate ? r.KEYUTIL.getPEM(key, "PKCS8PRV") : r.KEYUTIL.getPEM(key);
// PEM ends with '\n', so a new key always starts on a new line
output += pem;
}
return output;
}
}
export default PEMToJWK;

View File

@ -0,0 +1,88 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import r from "jsrsasign";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* PEM to JWK operation
*/
class PEMToJWK extends Operation {
/**
* PEMToJWK constructor
*/
constructor() {
super();
this.name = "PEM to JWK";
this.module = "PublicKey";
this.description = "Converts Keys in PEM format to a JSON Web Key format.";
this.infoURL = "https://datatracker.ietf.org/doc/html/rfc7517";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [
{
"pattern": "-----BEGIN ((RSA |EC )?(PRIVATE|PUBLIC) KEY|CERTIFICATE)-----",
"args": []
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let output = "";
let match;
const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g;
while ((match = regex.exec(input)) !== null) {
// find corresponding end tag
const indexBase64 = match.index + match[0].length;
const header = input.substring(match.index, indexBase64);
const footer = `-----END ${match[1]}-----`;
const indexFooter = input.indexOf(footer, indexBase64);
if (indexFooter === -1) {
throw new OperationError(`PEM footer '${footer}' not found`);
}
const pem = input.substring(match.index, indexFooter + footer.length);
if (match[1].indexOf("KEY") !== -1) {
if (header === "-----BEGIN RSA PUBLIC KEY-----") {
throw new OperationError("Unsupported RSA public key format. Only PKCS#8 is supported.");
}
const key = r.KEYUTIL.getKey(pem);
if (key.type === "DSA") {
throw new OperationError("DSA keys are not supported for JWK");
}
const jwk = r.KEYUTIL.getJWKFromKey(key);
if (output.length > 0) {
output += "\n";
}
output += JSON.stringify(jwk);
} else if (match[1] === "CERTIFICATE") {
const cert = new r.X509();
cert.readCertPEM(pem);
const key = cert.getPublicKey();
const jwk = r.KEYUTIL.getJWKFromKey(key);
if (output.length > 0) {
output += "\n";
}
output += JSON.stringify(jwk);
} else {
throw new OperationError(`Unsupported PEM type '${match[1]}'`);
}
}
return output;
}
}
export default PEMToJWK;

View File

@ -0,0 +1,68 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import r from "jsrsasign";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Public Key from Certificate operation
*/
class PubKeyFromCert extends Operation {
/**
* PubKeyFromCert constructor
*/
constructor() {
super();
this.name = "Public Key from Certificate";
this.module = "PublicKey";
this.description = "Extracts the Public Key from a Certificate.";
this.infoURL = "https://en.wikipedia.org/wiki/X.509";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let output = "";
let match;
const regex = /-----BEGIN CERTIFICATE-----/g;
while ((match = regex.exec(input)) !== null) {
// find corresponding end tag
const indexBase64 = match.index + match[0].length;
const footer = "-----END CERTIFICATE-----";
const indexFooter = input.indexOf(footer, indexBase64);
if (indexFooter === -1) {
throw new OperationError(`PEM footer '${footer}' not found`);
}
const certPem = input.substring(match.index, indexFooter + footer.length);
const cert = new r.X509();
cert.readCertPEM(certPem);
let pubKey;
try {
pubKey = cert.getPublicKey();
} catch {
throw new OperationError("Unsupported public key type");
}
const pubKeyPem = r.KEYUTIL.getPEM(pubKey);
// PEM ends with '\n', so a new key always starts on a new line
output += pubKeyPem;
}
return output;
}
}
export default PubKeyFromCert;

View File

@ -0,0 +1,82 @@
/**
* @author cplussharp
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import r from "jsrsasign";
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Public Key from Private Key operation
*/
class PubKeyFromPrivKey extends Operation {
/**
* PubKeyFromPrivKey constructor
*/
constructor() {
super();
this.name = "Public Key from Private Key";
this.module = "PublicKey";
this.description = "Extracts the Public Key from a Private Key.";
this.infoURL = "https://en.wikipedia.org/wiki/PKCS_8";
this.inputType = "string";
this.outputType = "string";
this.args = [];
this.checks = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let output = "";
let match;
const regex = /-----BEGIN ((RSA |EC |DSA )?PRIVATE KEY)-----/g;
while ((match = regex.exec(input)) !== null) {
// find corresponding end tag
const indexBase64 = match.index + match[0].length;
const footer = `-----END ${match[1]}-----`;
const indexFooter = input.indexOf(footer, indexBase64);
if (indexFooter === -1) {
throw new OperationError(`PEM footer '${footer}' not found`);
}
const privKeyPem = input.substring(match.index, indexFooter + footer.length);
let privKey;
try {
privKey = r.KEYUTIL.getKey(privKeyPem);
} catch (err) {
throw new OperationError(`Unsupported key type: ${err}`);
}
let pubKey;
if (privKey.type && privKey.type === "EC") {
pubKey = new r.KJUR.crypto.ECDSA({ curve: privKey.curve });
pubKey.setPublicKeyHex(privKey.generatePublicKeyHex());
} else if (privKey.type && privKey.type === "DSA") {
if (!privKey.y) {
throw new OperationError(`DSA Private Key in PKCS#8 is not supported`);
}
pubKey = new r.KJUR.crypto.DSA();
pubKey.setPublic(privKey.p, privKey.q, privKey.g, privKey.y);
} else if (privKey.n && privKey.e) {
pubKey = new r.RSAKey();
pubKey.setPublic(privKey.n, privKey.e);
} else {
throw new OperationError(`Unsupported key type`);
}
const pubKeyPem = r.KEYUTIL.getPEM(pubKey);
// PEM ends with '\n', so a new key always starts on a new line
output += pubKeyPem;
}
return output;
}
}
export default PubKeyFromPrivKey;

View File

@ -101,22 +101,17 @@ class RAKE extends Operation {
phrases = phrases.filter(subArray => subArray.length > 0);
// Remove duplicate phrases
const uniquePhrases = [...new Set(phrases.map(function (phrase) {
return phrase.join(" ");
}))];
phrases = uniquePhrases.map(function (phrase) {
return phrase.split(" ");
});
phrases = phrases.unique();
// Generate word_degree_matrix and populate
const wordDegreeMatrix = Array.from(Array(tokens.length), _ => Array(tokens.length).fill(0));
phrases.forEach(function (phrase) {
phrase.forEach(function (word1) {
phrase.forEach(function (word2) {
const wordDegreeMatrix = Array(tokens.length).fill().map(() => Array(tokens.length).fill(0));
for (const phrase of phrases) {
for (const word1 of phrase) {
for (const word2 of phrase) {
wordDegreeMatrix[tokens.indexOf(word1)][tokens.indexOf(word2)]++;
});
});
});
}
}
}
// Calculate degree score for each token
const degreeScores = Array(tokens.length).fill(0);

View File

@ -1,182 +0,0 @@
/**
* @author devcydo [devcydo@gmail.com]
* @author Ma Bingyao [mabingyao@gmail.com]
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import {toBase64} from "../lib/Base64.mjs";
import Utils from "../Utils.mjs";
/**
* XXTEA Encrypt operation
*/
class XXTEAEncrypt extends Operation {
/**
* XXTEAEncrypt constructor
*/
constructor() {
super();
this.name = "XXTEA";
this.module = "Default";
this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block.";
this.infoURL = "https://wikipedia.org/wiki/XXTEA";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Key",
"type": "string",
"value": "",
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
let key = args[0];
if (input === undefined || input === null || input.length === 0) {
throw new OperationError("Invalid input length (0)");
}
if (key === undefined || key === null || key.length === 0) {
throw new OperationError("Invalid key length (0)");
}
input = Utils.convertToByteString(input, "utf8");
key = Utils.convertToByteString(key, "utf8");
input = this.convertToUint32Array(input, true);
key = this.fixLength(this.convertToUint32Array(key, false));
let encrypted = this.encryptUint32Array(input, key);
encrypted = toBase64(this.toBinaryString(encrypted, false));
return encrypted;
}
/**
* Convert Uint32Array to binary string
*
* @param {Uint32Array} v
* @param {Boolean} includeLength
* @returns {string}
*/
toBinaryString(v, includeLENGTH) {
const LENGTH = v.length;
let n = LENGTH << 2;
if (includeLENGTH) {
const M = v[LENGTH - 1];
n -= 4;
if ((M < n - 3) || (M > n)) {
return null;
}
n = M;
}
for (let i = 0; i < LENGTH; i++) {
v[i] = String.fromCharCode(
v[i] & 0xFF,
v[i] >>> 8 & 0xFF,
v[i] >>> 16 & 0xFF,
v[i] >>> 24 & 0xFF
);
}
const RESULT = v.join("");
if (includeLENGTH) {
return RESULT.substring(0, n);
}
return RESULT;
}
/**
* @param {number} sum
* @param {number} y
* @param {number} z
* @param {number} p
* @param {number} e
* @param {number} k
* @returns {number}
*/
mx(sum, y, z, p, e, k) {
return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z));
}
/**
* Encrypt Uint32Array
*
* @param {Uint32Array} v
* @param {number} k
* @returns {Uint32Array}
*/
encryptUint32Array(v, k) {
const LENGTH = v.length;
const N = LENGTH - 1;
let y, z, sum, e, p, q;
z = v[N];
sum = 0;
for (q = Math.floor(6 + 52 / LENGTH) | 0; q > 0; --q) {
sum = (sum + 0x9E3779B9) & 0xFFFFFFFF;
e = sum >>> 2 & 3;
for (p = 0; p < N; ++p) {
y = v[p + 1];
z = v[p] = (v[p] + this.mx(sum, y, z, p, e, k)) & 0xFFFFFFFF;
}
y = v[0];
z = v[N] = (v[N] + this.mx(sum, y, z, N, e, k)) & 0xFFFFFFFF;
}
return v;
}
/**
* Fixes the Uint32Array lenght to 4
*
* @param {Uint32Array} k
* @returns {Uint32Array}
*/
fixLength(k) {
if (k.length < 4) {
k.length = 4;
}
return k;
}
/**
* Convert string to Uint32Array
*
* @param {string} bs
* @param {Boolean} includeLength
* @returns {Uint32Array}
*/
convertToUint32Array(bs, includeLength) {
const LENGTH = bs.length;
let n = LENGTH >> 2;
if ((LENGTH & 3) !== 0) {
++n;
}
let v;
if (includeLength) {
v = new Array(n + 1);
v[n] = LENGTH;
} else {
v = new Array(n);
}
for (let i = 0; i < LENGTH; ++i) {
v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3);
}
return v;
}
}
export default XXTEAEncrypt;

View File

@ -0,0 +1,57 @@
/**
* @author devcydo [devcydo@gmail.com]
* @author Ma Bingyao [mabingyao@gmail.com]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import OperationError from "../errors/OperationError.mjs";
import {decrypt} from "../lib/XXTEA.mjs";
/**
* XXTEA Decrypt operation
*/
class XXTEADecrypt extends Operation {
/**
* XXTEADecrypt constructor
*/
constructor() {
super();
this.name = "XXTEA Decrypt";
this.module = "Ciphers";
this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block.";
this.infoURL = "https://wikipedia.org/wiki/XXTEA";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option));
try {
return decrypt(new Uint8Array(input), key).buffer;
} catch (err) {
throw new OperationError("Unable to decrypt using this key");
}
}
}
export default XXTEADecrypt;

View File

@ -0,0 +1,52 @@
/**
* @author devcydo [devcydo@gmail.com]
* @author Ma Bingyao [mabingyao@gmail.com]
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import {encrypt} from "../lib/XXTEA.mjs";
/**
* XXTEA Encrypt operation
*/
class XXTEAEncrypt extends Operation {
/**
* XXTEAEncrypt constructor
*/
constructor() {
super();
this.name = "XXTEA Encrypt";
this.module = "Ciphers";
this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block.";
this.infoURL = "https://wikipedia.org/wiki/XXTEA";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
},
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const key = new Uint8Array(Utils.convertToByteArray(args[0].string, args[0].option));
return encrypt(new Uint8Array(input), key).buffer;
}
}
export default XXTEAEncrypt;

View File

@ -39,7 +39,6 @@ class App {
this.baking = false;
this.autoBake_ = false;
this.autoBakePause = false;
this.progress = 0;
this.ingId = 0;
@ -155,12 +154,12 @@ class App {
* Runs Auto Bake if it is set.
*/
autoBake() {
// If autoBakePause is set, we are loading a full recipe (and potentially input), so there is no
// need to set the staleness indicator. Just exit and wait until auto bake is called after loading
// has completed.
if (this.autoBakePause) return false;
if (this.baking) {
this.manager.worker.cancelBakeForAutoBake();
this.baking = false;
}
if (this.autoBake_ && !this.baking) {
if (this.autoBake_) {
log.debug("Auto-baking");
this.manager.worker.bakeInputs({
nums: [this.manager.tabs.getActiveTab("input")],
@ -473,7 +472,6 @@ class App {
* @fires Manager#statechange
*/
loadURIParams(params=this.getURIParams()) {
this.autoBakePause = true;
this.uriParams = params;
// Read in recipe from URI params
@ -502,7 +500,7 @@ class App {
// Input Character Encoding
// Must be set before the input is loaded
if (this.uriParams.ienc) {
this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true);
this.manager.input.chrEncChange(parseInt(this.uriParams.ienc, 10), true, true);
}
// Output Character Encoding
@ -540,7 +538,6 @@ class App {
this.manager.options.changeTheme(Utils.escapeHtml(this.uriParams.theme));
}
this.autoBakePause = false;
window.dispatchEvent(this.manager.statechange);
}
@ -574,10 +571,6 @@ class App {
setRecipeConfig(recipeConfig) {
document.getElementById("rec-list").innerHTML = null;
// Pause auto-bake while loading but don't modify `this.autoBake_`
// otherwise `manualBake` cannot trigger.
this.autoBakePause = true;
for (let i = 0; i < recipeConfig.length; i++) {
const item = this.manager.recipe.addOperation(recipeConfig[i].op);
@ -612,9 +605,6 @@ class App {
this.progress = 0;
}
// Unpause auto bake
this.autoBakePause = false;
}

View File

@ -85,6 +85,7 @@ class HTMLOperation {
<div class="recip-icons">
<i class="material-icons breakpoint" title="Set breakpoint" break="false" data-help-title="Setting breakpoints" data-help="Setting a breakpoint on an operation will cause execution of the Recipe to pause when it reaches that operation.">pause</i>
<i class="material-icons disable-icon" title="Disable operation" disabled="false" data-help-title="Disabling operations" data-help="Disabling an operation will prevent it from being executed when the Recipe is baked. Execution will skip over the disabled operation and continue with subsequent operations.">not_interested</i>
<i class="material-icons hide-args-icon" title="Hide operation's arguments" hide-args="false" data-help-title="Hide operation's arguments" data-help="Hiding an operation's argument will save space in the Recipe window. Execution will still take place with the selected argument options.">keyboard_arrow_up</i>
</div>
<div class="clearfix">&nbsp;</div>`;

View File

@ -139,6 +139,7 @@ class Manager {
document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls));
document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls));
document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls));
document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeArgsClick.bind(this.recipe));
document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls));
this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls);
@ -154,6 +155,7 @@ class Manager {
// Recipe
this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe);
this.addDynamicListener(".hide-args-icon", "click", this.recipe.hideArgsClick, this.recipe);
this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe);
this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe);
this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe);

View File

@ -142,8 +142,8 @@
<div id="preloader-error" class="loading-error"></div>
</div>
<!-- End preloader overlay -->
<button type="button" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<i class="material-icons">star</i>
<button type="button" aria-label="Edit Favourites" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<i class="material-icons" aria-hidden="true">star</i>
</button>
<div id="content-wrapper">
<div id="banner" class="row">
@ -181,14 +181,17 @@
<div class="title no-select">
Recipe
<span class="pane-controls hide-on-maximised-output">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe" data-help-title="Saving a recipe" data-help="<p>Recipes can be represented in a few different formats and saved for use at a later date. You can either copy the Recipe configuration and save it somewhere offline for later use, or use your browser's local storage.</p><ul><li><b>Deep link:</b> The easiest way to share a CyberChef Recipe is to copy the deep link, either from the address bar (which is updated as the Recipe or Input changes), or from the 'Save recipe' pane. When you visit this link, the Recipe and Input should be populated from where you left off.</li><li><b>Chef format:</b> This custom format is designed to be compact and easily readable. It is the format used in CyberChef's URL, so it largely uses characters that do not have to be escaped in URL encoding, making it a little easier to understand what a CyberChef URL contains.</li><li><b>Clean JSON:</b> This JSON format uses whitespace and indentation in a way that makes the Recipe easy to read.</li><li><b>Compact JSON:</b> This is the most compact way that the Recipe can be represented in JSON.</li><li><b>Local storage:</b> Alternatively, you can enter a name into the 'Recipe name' field and save to your browser's local storage. The Recipe will then be available to load from the 'Load Recipe' pane as long as you are using the same browser profile. Be aware that if your browser profile is cleaned, you may lose this data.</li></ul>">
<i class="material-icons">save</i>
<button type="button" aria-label="Hide arguments" class="btn btn-primary bmd-btn-icon" id="hide-icon" data-toggle="tooltip" title="Hide arguments" hide-args="false" data-help-title="Hiding every Operation's argument view in a Recipe" data-help="Clicking 'Hide arguments' will hide all the argument views for every Operation in the Recipe, to save space when you have too many Operation in your Recipe">
<i class="material-icons">keyboard_arrow_up</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="load" data-toggle="tooltip" title="Load recipe" data-help-title="Loading a recipe" data-help="<p>Saved recipes can be loaded using one of the following methods:</p><ul><li>If you have a CyberChef deep link, simply visit that link and the Recipe and Input should be populated automatically.</li><li>If you have a Recipe string in any of the accepted formats, paste it into the 'Load recipe' pane textbox and click 'Load'.</li><li>If you have saved a Recipe to your browser's local storage, it should be available in the dropdown menu in the 'Load recipe' pane. If it is not there, you may not be using the same browser profile, or your profile may have been cleared.</li></ul>">
<i class="material-icons">folder</i>
<button type="button" aria-label="Save recipe" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe" data-help-title="Saving a recipe" data-help="<p>Recipes can be represented in a few different formats and saved for use at a later date. You can either copy the Recipe configuration and save it somewhere offline for later use, or use your browser's local storage.</p><ul><li><b>Deep link:</b> The easiest way to share a CyberChef Recipe is to copy the deep link, either from the address bar (which is updated as the Recipe or Input changes), or from the 'Save recipe' pane. When you visit this link, the Recipe and Input will be populated from where you left off.</li><li><b>Chef format:</b> This custom format is designed to be compact and easily readable. It is the format used in CyberChef's URL, so it largely uses characters that do not have to be escaped in URL encoding, making it a little easier to understand what a CyberChef URL contains.</li><li><b>Clean JSON:</b> This JSON format uses whitespace and indentation in a way that makes the Recipe easy to read.</li><li><b>Compact JSON:</b> This is the most compact way that the Recipe can be represented in JSON.</li><li><b>Local storage:</b> Alternatively, you can enter a name into the 'Recipe name' field and save to your browser's local storage. The Recipe will then be available to load from the 'Load Recipe' pane as long as you are using the same browser profile. Be aware that if your browser profile is cleaned, you may lose this data.</li></ul>">
<i class="material-icons" aria-hidden="true">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-recipe" data-toggle="tooltip" title="Clear recipe" data-help-title="Clearing a recipe" data-help="Clicking the 'Clear recipe' button will remove all operations from the Recipe. It will not clear the Input, but it will trigger a Bake if Auto-bake is turned on, which will change the value of the Output.">
<i class="material-icons">delete</i>
<button type="button" aria-label="Load recipe" class="btn btn-primary bmd-btn-icon" id="load" data-toggle="tooltip" title="Load recipe" data-help-title="Loading a recipe" data-help="<p>Saved recipes can be loaded using one of the following methods:</p><ul><li>If you have a CyberChef deep link, simply visit that link and the Recipe and Input will be populated automatically.</li><li>If you have a Recipe string in any of the accepted formats, paste it into the 'Load recipe' pane textbox and click 'Load'.</li><li>If you have saved a Recipe to your browser's local storage, it should be available in the dropdown menu in the 'Load recipe' pane. If it is not there, you may not be using the same browser profile, or your profile may have been cleared.</li></ul>">
<i class="material-icons" aria-hidden="true">folder</i>
</button>
<button type="button" aria-label="Clear recipe" class="btn btn-primary bmd-btn-icon" id="clr-recipe" data-toggle="tooltip" title="Clear recipe" data-help-title="Clearing a recipe" data-help="Clicking the 'Clear recipe' button will remove all operations from the Recipe. It will not clear the Input, but it will trigger a Bake if Auto-bake is turned on, which will change the value of the Output.">
<i class="material-icons" aria-hidden="true">delete</i>
</button>
</span>
</div>
@ -223,22 +226,22 @@
<label for="input-text">Input</label>
<span class="pane-controls">
<div class="io-info" id="input-files-info"></div>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab" data-help-title="Tabs" data-help="<p>New tabs can be created to support multiple Inputs. These tabs have their own associated character encodings and EOL separators, as defined in their status bars.</p><p>The deep link in the URL bar only contains information about the currently active tab.</p>">
<i class="material-icons">add</i>
<button type="button" aria-label="Add new input tab" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab" data-help-title="Tabs" data-help="<p>New tabs can be created to support multiple Inputs. These tabs have their own associated character encodings and EOL separators, as defined in their status bars.</p><p>The deep link in the URL bar only contains information about the currently active tab.</p>">
<i class="material-icons" aria-hidden="true">add</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-folder" data-toggle="tooltip" title="Open folder as input" data-help-title="Opening a folder" data-help="<p>You can open a whole folder into CyberChef, which will result in each file being loaded into a separate Input tab.</p><p>CyberChef can handle lots of Input files, but be aware that performance may suffer, especially if the files are large in size.</p><p>Folders can also be loaded by dragging them over the Input pane and dropping them.</p>">
<i class="material-icons">folder_open</i>
<button type="button" aria-label="Open folder as input" class="btn btn-primary bmd-btn-icon" id="btn-open-folder" data-toggle="tooltip" title="Open folder as input" data-help-title="Opening a folder" data-help="<p>You can open a whole folder into CyberChef, which will result in each file being loaded into a separate Input tab.</p><p>CyberChef can handle lots of Input files, but be aware that performance may suffer, especially if the files are large in size.</p><p>Folders can also be loaded by dragging them over the Input pane and dropping them.</p>">
<i class="material-icons" aria-hidden="true">folder_open</i>
<input type="file" id="open-folder" style="display: none" multiple directory webkitdirectory>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-open-file" data-toggle="tooltip" title="Open file as input" data-help-title="Opening a file" data-help="<p>Files can be loaded into CyberChef individually or in groups, either using the 'Open file as input' button, or by dragging and dropping them over the Input pane.</p><p>CyberChef can handle reasonably large files (at least 500MB, depending on hardware), but performance may be impacted and some Operations will run very slowly over large Inputs.</p>">
<i class="material-icons">input</i>
<button type="button" aria-label="Open file as input" class="btn btn-primary bmd-btn-icon" id="btn-open-file" data-toggle="tooltip" title="Open file as input" data-help-title="Opening a file" data-help="<p>Files can be loaded into CyberChef individually or in groups, either using the 'Open file as input' button, or by dragging and dropping them over the Input pane.</p><p>CyberChef can handle reasonably large files (at least 500MB, depending on hardware), but performance may be impacted and some Operations will run very slowly over large Inputs.</p>">
<i class="material-icons" aria-hidden="true">input</i>
<input type="file" id="open-file" style="display: none" multiple>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output" data-help-title="Clearing the Input and Output" data-help="Clicking the 'Clear input and output' button will remove all Inputs and Outputs. It will not clear the Recipe.">
<i class="material-icons">delete</i>
<button type="button" aria-label="Clear input and output" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output" data-help-title="Clearing the Input and Output" data-help="Clicking the 'Clear input and output' button will remove all Inputs and Outputs. It will not clear the Recipe.">
<i class="material-icons" aria-hidden="true">delete</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout" data-help-title="Resetting the pane layout" data-help="CyberChef's panes can be resized to suit your area of focus. This button will reset the pane sizes to their default configuration.">
<i class="material-icons">view_compact</i>
<button type="button" aria-label="Reset pane layout" class="btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout" data-help-title="Resetting the pane layout" data-help="CyberChef's panes can be resized to suit your area of focus. This button will reset the pane sizes to their default configuration.">
<i class="material-icons" aria-hidden="true">view_compact</i>
</button>
</span>
</div>
@ -272,7 +275,7 @@
</div>
</div>
<div id="output" class="split" data-help-title="Output pane" data-help="<p>This pane displays the results of the Recipe after it has processed your Input.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p><p>When copying these characters from the Output, the original byte value should be copied into your clipboard, rather than the control character picture itself.</p>">
<div id="output" class="split" data-help-title="Output pane" data-help="<p>This pane displays the results of the Recipe after it has processed your Input.</p><p>CyberChef does its best to represent data as accurately as possible to ensure you know exactly what you are working with. Non-printable characters are represented using control character pictures, for example a null byte (0x00) is displayed like this: <span title='Control character null' aria-label='Control character null' class='cm-specialChar'>␀</span>.</p><p>When copying these characters from the Output, the original byte value will be copied into your clipboard, rather than the control character picture itself.</p>">
<div class="title no-select">
<label for="output-text">Output</label>
<span class="pane-controls">
@ -280,17 +283,17 @@
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none" data-help-title="Saving all outputs to a zip file" data-help="<p>When operating with multiple tabbed Inputs and Outputs, you can use this button to save off all the Outputs at once in a ZIP file.</p><p>Use the 'Bake' button to bake all Inputs at once.</p><p>You will be given the choice to specify the file extension for the Outputs, or you can let CyberChef attempt to detect the filetype of each one. If an Output's type is not clear, CyberChef will use the '.dat' extension.</p>">
<i class="material-icons">archive</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file" data-help-title="Saving output to a file" data-help="The currently active Output can be saved to a file. You will be asked to specify a filename. CyberChef will attempt to guess the correct file extension based on the data. If a file type cannot be detected, the extension defaults to '.dat' but can be changed manually.">
<i class="material-icons">save</i>
<button type="button" aria-label="save" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file" data-help-title="Saving output to a file" data-help="The currently active Output can be saved to a file. You will be asked to specify a filename. CyberChef will attempt to guess the correct file extension based on the data. If a file type cannot be detected, the extension defaults to '.dat' but can be changed manually.">
<i class="material-icons" aria-hidden="true">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard" data-help-title="Copying raw output to the clipboard" data-help="<p>Data can be copied from the Output in the normal way by selecting text and copying it. This button provides a quick way of copying the entire output to the clipboard without having to select it. It directly copies the raw data rather than selecting text in the Output editor. Each method should have the same result, but the button may be more efficient for large Outputs as it does not require any DOM interaction.</p>">
<i class="material-icons">content_copy</i>
<button type="button" aria-label="copy content" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard" data-help-title="Copying raw output to the clipboard" data-help="<p>Data can be copied from the Output in the normal way by selecting text and copying it. This button provides a quick way of copying the entire output to the clipboard without having to select it. It directly copies the raw data rather than selecting text in the Output editor. Each method will have the same result, but the button may be more efficient for large Outputs as it does not require any DOM interaction.</p>">
<i class="material-icons" aria-hidden="true">content_copy</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Replace input with output" data-help-title="Replacing input with output" data-help="<p>This button moves the currently active Output data into the currently active Input tab, overwriting whatever data was already there.</p><p>The Input character encoding and EOL sequence will be changed to match the current Output values, so that the data is interpreted correctly.</p>">
<i class="material-icons">open_in_browser</i>
<button type="button" aria-label="replace input with output" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Replace input with output" data-help-title="Replacing input with output" data-help="<p>This button moves the currently active Output data into the currently active Input tab, overwriting whatever data was already there.</p><p>The Input character encoding and EOL sequence will be changed to match the current Output values, so that the data is interpreted correctly.</p>">
<i class="material-icons" aria-hidden="true">open_in_browser</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane" data-help-title="Maximising the Output pane" data-help="This button allows you to view the Output pane at maximum size, hiding the Operations, Recipe and Input panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons">fullscreen</i>
<button type="button" aria-label="maximise output pane" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane" data-help-title="Maximising the Output pane" data-help="This button allows you to view the Output pane at maximum size, hiding the Operations, Recipe and Input panes. You can restore the pane to its normal size by clicking the same button again.">
<i class="material-icons" aria-hidden="true">fullscreen</i>
</button>
</span>
@ -608,7 +611,7 @@
What sort of things can I do with CyberChef?
</a>
<div class="collapse" id="faq-examples">
<p>There are around 300 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
<p>There are <span class="num-ops">hundreds of</span> operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
<ul>
<li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
<li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
@ -679,7 +682,7 @@
<br>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>There are <span class="num-ops">hundreds of</span> useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
@ -860,7 +863,7 @@
<h6>CyberChef v<%= htmlWebpackPlugin.options.version %></h6>
<ul>
<li>Build time: <%= htmlWebpackPlugin.options.compileTime %></li>
<li>The changelog for this version can be viewed <a href="https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md">here</a></li>
<li>The changelog for this version can be viewed <a href="https://github.com/gchq/CyberChef/blob/v<%= htmlWebpackPlugin.options.version %>/CHANGELOG.md">here</a></li>
<li>&copy; Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %></li>
<li>Released under the Apache Licence, Version 2.0</li>
<li>SHA256 hash: DOWNLOAD_HASH_PLACEHOLDER</li>

View File

@ -99,10 +99,12 @@
.bmd-form-group.is-focused [class^='bmd-label'],
.bmd-form-group.is-focused [class*=' bmd-label'],
.bmd-form-group.is-focused label,
.checkbox label:hover {
.checkbox label:hover,
.bmd-form-group.is-filled:focus-within .checkbox.option-item label {
color: var(--input-highlight-colour);
}
.bmd-form-group.option-item label+.form-control{
background-image:
linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px),

View File

@ -36,6 +36,11 @@ class ControlsWaiter {
boundary: "viewport",
trigger: "hover"
});
// Set number of operations in various places in the DOM
document.querySelectorAll(".num-ops").forEach(el => {
el.innerHTML = Object.keys(this.app.operations).length;
});
}
@ -345,6 +350,36 @@ class ControlsWaiter {
}
/**
* Hides the arguments for all the operations in the current recipe.
*/
hideRecipeArgsClick() {
const icon = document.getElementById("hide-icon");
if (icon.getAttribute("hide-args") === "false") {
icon.setAttribute("hide-args", "true");
icon.setAttribute("data-original-title", "Show arguments");
icon.children[0].innerText = "keyboard_arrow_down";
Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) {
item.setAttribute("hide-args", "true");
item.innerText = "keyboard_arrow_down";
item.classList.add("hide-args-selected");
item.parentNode.previousElementSibling.style.display = "none";
});
} else {
icon.setAttribute("hide-args", "false");
icon.setAttribute("data-original-title", "Hide arguments");
icon.children[0].innerText = "keyboard_arrow_up";
Array.from(document.getElementsByClassName("hide-args-icon")).forEach(function(item) {
item.setAttribute("hide-args", "false");
item.innerText = "keyboard_arrow_up";
item.classList.remove("hide-args-selected");
item.parentNode.previousElementSibling.style.display = "grid";
});
}
}
/**
* Populates the bug report information box with useful technical info.
*

View File

@ -215,13 +215,16 @@ class InputWaiter {
* Handler for Chr Enc change events
* Sets the input character encoding
* @param {number} chrEncVal
* @param {boolean} [manual=false]
* @param {boolean} [manual=false] - Flag to indicate the encoding was set by the user
* @param {boolean} [internal=false] - Flag to indicate this was set internally, i.e. by loading from URI
*/
chrEncChange(chrEncVal, manual=false) {
chrEncChange(chrEncVal, manual=false, internal=false) {
if (typeof chrEncVal !== "number") return;
this.inputChrEnc = chrEncVal;
this.encodingState = manual ? 2 : this.encodingState;
this.inputChange();
if (!internal) {
this.inputChange();
}
}
/**
@ -639,10 +642,6 @@ class InputWaiter {
const inputStr = toBase64(inputVal, "A-Za-z0-9+/");
this.app.updateURL(true, inputStr);
}
// Trigger a state change
if (!silent) window.dispatchEvent(this.manager.statechange);
}.bind(this));
}

View File

@ -1541,10 +1541,12 @@ class OutputWaiter {
this.app.ioSplitter.collapse(0);
$(el).attr("data-original-title", "Restore output pane");
$(el).attr("aria-label", "Restore output pane");
el.querySelector("i").innerHTML = "fullscreen_exit";
} else {
document.body.classList.remove("output-maximised");
$(el).attr("data-original-title", "Maximise output pane");
$(el).attr("aria-label", "Maximise output pane");
el.querySelector("i").innerHTML = "fullscreen";
this.app.initialiseSplitter(false);
this.app.resetLayout();

View File

@ -215,6 +215,45 @@ class RecipeWaiter {
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for hide-args click events.
* Updates the icon status.
*
* @fires Manager#statechange
* @param {event} e
*/
hideArgsClick(e) {
const icon = e.target;
if (icon.getAttribute("hide-args") === "false") {
icon.setAttribute("hide-args", "true");
icon.innerText = "keyboard_arrow_down";
icon.classList.add("hide-args-selected");
icon.parentNode.previousElementSibling.style.display = "none";
} else {
icon.setAttribute("hide-args", "false");
icon.innerText = "keyboard_arrow_up";
icon.classList.remove("hide-args-selected");
icon.parentNode.previousElementSibling.style.display = "grid";
}
const icons = Array.from(document.getElementsByClassName("hide-args-icon"));
if (icons.length > 1) {
// Check if ALL the icons are hidden/shown
const uniqueIcons = icons.map(function(item) {
return item.getAttribute("hide-args");
}).unique();
const controlsIconStatus = document.getElementById("hide-icon").getAttribute("hide-args");
// If all icons are in the same state and the global icon isn't, fix it
if (uniqueIcons.length === 1 && icon.getAttribute("hide-args") !== controlsIconStatus) {
this.manager.controls.hideRecipeArgsClick();
}
}
window.dispatchEvent(this.manager.statechange);
}
/**
* Handler for disable click events.

View File

@ -322,6 +322,28 @@ class WorkerWaiter {
};
}
/**
* Cancels the current bake making it possible to autobake again
*/
cancelBakeForAutoBake() {
if (this.totalOutputs > 1) {
this.cancelBake();
} else {
// In this case the UI changes can be skipped
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
if (this.chefWorkers[i].active) {
this.removeChefWorker(this.chefWorkers[i]);
}
}
this.inputs = [];
this.inputNums = [];
this.totalOutputs = 0;
this.loadingOutputs = 0;
}
}
/**
* Cancels the current bake by terminating and removing all ChefWorkers
*

View File

@ -167,6 +167,37 @@ module.exports = {
browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF");
},
"Autobaking the latest input": browser => {
// Use the sleep recipe to simulate a long running task
utils.loadRecipe(browser, "Sleep", "input", [2000]);
browser.waitForElementVisible("#stale-indicator");
// Enable previously disabled autobake
browser.expect.element("#auto-bake").to.not.be.selected;
browser.click("#auto-bake-label");
browser.expect.element("#auto-bake").to.be.selected.before(1000);
// Add content to the input
browser.pause(100);
browser.sendKeys("#input-text .cm-content", "1");
browser.waitForElementVisible("#output-loader");
browser.pause(500);
// Make another change while the previous input is being baked
browser
.sendKeys("#input-text .cm-content", "2")
.waitForElementNotVisible("#stale-indicator")
.waitForElementNotVisible("#output-loader");
// Ensure we got the latest input baked
utils.expectOutput(browser, "input12");
// Turn autobake off again
browser.click("#auto-bake-label");
browser.expect.element("#auto-bake").to.not.be.selected.before(1000);
},
"Special content": browser => {
/* Special characters are rendered correctly */
utils.setInput(browser, SPECIAL_CHARS, false);
@ -383,13 +414,17 @@ module.exports = {
utils.setInput(browser, CHINESE_CHARS, false);
utils.setChrEnc(browser, "input", "UTF-8");
utils.bake(browser);
utils.expectOutput(browser, "\u00E4\u00B8\u008D\u00E8\u00A6\u0081\u00E6\u0081\u0090\u00E6\u0085\u008C\u00E3\u0080\u0082");
/* Changing output to match input works as expected */
utils.setChrEnc(browser, "output", "UTF-8");
utils.bake(browser);
/* Output encoding should be autodetected */
browser
.waitForElementVisible("#snackbar-container .snackbar-content", 5000)
.expect.element("#snackbar-container .snackbar-content").text.to.equal("Output character encoding has been detected and changed to UTF-8");
utils.expectOutput(browser, CHINESE_CHARS);
/* Change the output encoding manually to test for URL presence */
utils.setChrEnc(browser, "output", "UTF-8");
/* Encodings appear in the URL */
browser.assert.urlContains("ienc=65001");
browser.assert.urlContains("oenc=65001");
@ -641,6 +676,20 @@ module.exports = {
},
"Loading from URL": browser => {
utils.clear(browser);
/* Side panel displays correct info */
utils.uploadFile(browser, "files/TowelDay.jpeg");
browser
.waitForElementVisible("#input-text .cm-file-details")
.waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
.waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail")
.waitForElementVisible("#input-text .cm-file-details .file-details-name")
.waitForElementVisible("#input-text .cm-file-details .file-details-size")
.waitForElementVisible("#input-text .cm-file-details .file-details-type")
.waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
/* Complex deep link populates the input correctly (encoding, eol, input) */
browser
.urlHash("recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=FF&oeol=PS")

View File

@ -37,7 +37,7 @@ module.exports = {
testOp(browser, ["From Hex", "Add Text To Image", "To Base64"], Images.PNG_HEX, Images.PNG_CHEF_B64, [[], ["Chef", "Center", "Middle", 0, 0, 16], []]);
testOp(browser, "Adler-32 Checksum", "test input", "16160411");
testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]);
testOp(browser, "Affine Cipher Encode", "test input", "njln rbfpn", [2, 1]);
testOp(browser, "Affine Cipher Encode", "test input", "gndg zoujg", [3, 1]);
testOp(browser, "AMF Decode", "\u000A\u0013\u0001\u0003a\u0006\u0009test", /"\$value": "test"/);
testOp(browser, "AMF Encode", '{"a": "test"}', "\u000A\u0013\u0001\u0003a\u0006\u0009test");
testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/);

View File

@ -39,6 +39,7 @@ function setInput(browser, input, type=true) {
browser.execute(text => {
window.app.setInput(text);
}, [input]);
browser.pause(100);
}
}
@ -176,13 +177,14 @@ function loadRecipe(browser, opName, input, args) {
*/
function expectOutput(browser, expected) {
browser.execute(expected => {
const output = window.app.manager.output.outputEditorView.state.doc.toString();
return window.app.manager.output.outputEditorView.state.doc.toString();
}, [expected], function({value}) {
if (expected instanceof RegExp) {
return expected.test(output);
browser.expect(value).match(expected);
} else {
return expected === output;
browser.expect(value).to.be.equal(expected);
}
}, [expected]);
});
}
/** @function

View File

@ -59,6 +59,7 @@ import "./tests/Crypt.mjs";
import "./tests/CSV.mjs";
import "./tests/DateTime.mjs";
import "./tests/DefangIP.mjs";
import "./tests/ECDSA.mjs";
import "./tests/ELFInfo.mjs";
import "./tests/Enigma.mjs";
import "./tests/ExtractEmailAddresses.mjs";
@ -83,12 +84,13 @@ import "./tests/HKDF.mjs";
import "./tests/Image.mjs";
import "./tests/IndexOfCoincidence.mjs";
import "./tests/JA3Fingerprint.mjs";
import "./tests/JA4Fingerprint.mjs";
import "./tests/JA4.mjs";
import "./tests/JA3SFingerprint.mjs";
import "./tests/JSONBeautify.mjs";
import "./tests/JSONMinify.mjs";
import "./tests/JSONtoCSV.mjs";
import "./tests/Jump.mjs";
import "./tests/JWK.mjs";
import "./tests/JWTDecode.mjs";
import "./tests/JWTSign.mjs";
import "./tests/JWTVerify.mjs";
@ -121,6 +123,8 @@ import "./tests/PGP.mjs";
import "./tests/PHP.mjs";
import "./tests/PowerSet.mjs";
import "./tests/Protobuf.mjs";
import "./tests/PubKeyFromCert.mjs";
import "./tests/PubKeyFromPrivKey.mjs";
import "./tests/Rabbit.mjs";
import "./tests/RAKE.mjs";
import "./tests/Regex.mjs";
@ -150,6 +154,7 @@ import "./tests/UnescapeString.mjs";
import "./tests/Unicode.mjs";
import "./tests/YARA.mjs";
import "./tests/ParseCSR.mjs";
import "./tests/XXTEA.mjs";
const testStatus = {
allTestsPassing: true,

View File

@ -0,0 +1,464 @@
/**
* ECDSA tests.
*
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
import { ASCII_TEXT } from "../../samples/Ciphers.mjs";
const P256 = {
// openssl ecparam -name prime256v1 -genkey -noout -out p256.priv.key
privateKeyPkcs1: `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49
AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC
a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END EC PRIVATE KEY-----`,
privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw
YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC
6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p
-----END PRIVATE KEY-----`,
// openssl ec -in p256.priv.key -pubout -out p256.pub.key
publicKey: `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ
gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END PUBLIC KEY-----`,
signature: {
sha256: {
asn1: "3046022100e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127022100b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d",
p1363: "e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d",
jws: "4GkFYIovp9vanihMKnlZ37aPtSel8AOy15df8TUUUSe2uqJTeTM0-Lk-od1iK8YAEk2AkLq9gH7-P3e4syQ4jQ",
json: `{"r":"00e06905608a2fa7dbda9e284c2a7959dfb68fb527a5f003b2d7975ff135145127","s":"00b6baa253793334f8b93ea1dd622bc600124d8090babd807efe3f77b8b324388d"}`
}
}
};
// openssl pkcs8 -topk8 -in p256.priv.key -out p256.enc-priv.key -v2 des3 -v2prf hmacWithSHA1 -passout pass:Test1234
/* const PEM_PRIV_P256_ENCRYPTED_PASS = "Test1234";
const PEM_PRIV_P256_ENCRYPTED = `-----BEGIN ENCRYPTED PRIVATE KEY-----
MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAg+4ckqI9Q9ZAICCAAw
DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEOnMUW15Hn/ub0OcCCj9lksEgZCk
kxaK4d430lZHovcA4ZeKTt94QcfjnIHRk65aZt93l17l52pv6n/srs3aRo/n5RV+
wZ5sTLF0925ZQWJB5cIhzc8KQIvguGCX1znLQJJaRHyYOUXIN77AKEfALKAinBit
25paDnbXAqGn1CR3UwFWUZZW+c3UEhWhmpghQpS1tIl0KI6IAvnrGIdw2kKIouo=
-----END ENCRYPTED PRIVATE KEY-----`;*/
const P384 = {
privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAYo22xn2kZjN8MInom
NDsgD/zhpUwnCYch634jUgO59fN9m2lR5ekaI1XABHz39rihZANiAAQwXoCsPOLv
Nn2STUs/hpL41CQveSL3WUmJ4QdtD7UFCl1mBO6ME0xSUgIQTUNkHt5k9CpOq3x9
r+LG5+GcisoLn7R54R+bRoGp/p1ZBeuBXoCgthvs+RFoT3OewUmA8oQ=
-----END PRIVATE KEY-----`,
publicKey: `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMF6ArDzi7zZ9kk1LP4aS+NQkL3ki91lJ
ieEHbQ+1BQpdZgTujBNMUlICEE1DZB7eZPQqTqt8fa/ixufhnIrKC5+0eeEfm0aB
qf6dWQXrgV6AoLYb7PkRaE9znsFJgPKE
-----END PUBLIC KEY-----`
};
const P521 = {
privateKeyPkcs8: `-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAifBaJDqNwOtKgThc
FU34GzPQ73ubOQg9dnighpVGwA3b/KwCifimCNKDmKnXJaE04mEcxg8yzcFKausF
5I8o206hgYkDgYYABAGwpkwrBBlZOdx4u9mxqYxJvtzAHaFFAzl21WQVbAjyrqXe
nFPMkhbFpEEWr1ualPYKQkHe14AX33iU3fQ9MlBkgAAripsPbiKggAaog74cUERo
qbrUFZwMbptGgovpE6pU93h7A1wb3Vtw9DZQCgiNbwzMbdsft+p2RJ8iSxWEC6Gd
mw==
-----END PRIVATE KEY-----`,
publicKey: `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBsKZMKwQZWTnceLvZsamMSb7cwB2h
RQM5dtVkFWwI8q6l3pxTzJIWxaRBFq9bmpT2CkJB3teAF994lN30PTJQZIAAK4qb
D24ioIAGqIO+HFBEaKm61BWcDG6bRoKL6ROqVPd4ewNcG91bcPQ2UAoIjW8MzG3b
H7fqdkSfIksVhAuhnZs=
-----END PUBLIC KEY-----`
};
const PEM_PPRIV_RSA512 = `-----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 PEM_PUB_RSA512 = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`;
TestRegister.addTests([
{
name: "ECDSA Sign/Verify: P-256 with MD5",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs1, "MD5", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "MD5", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-256 with SHA1",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs1, "SHA-1", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-1", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-256 with SHA256",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs1, "SHA-256", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-256 with SHA384",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs1, "SHA-384", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-384", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-256 with SHA512",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs1, "SHA-512", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-512", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify:: Using a private key in PKCS#8 format works",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.privateKeyPkcs8, "SHA-256", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-384 with SHA384",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P384.privateKeyPkcs8, "SHA-384", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-384", P384.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Sign/Verify: P-521 with SHA512",
input: ASCII_TEXT,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P521.privateKeyPkcs8, "SHA-512", "ASN.1 HEX"]
},
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-512", P521.publicKey, ASCII_TEXT]
}
]
},
// ECDSA Sign
{
name: "ECDSA Sign: Using public key fails",
input: ASCII_TEXT,
expectedOutput: "Provided key is not a private key.",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [P256.publicKey, "SHA-256", "ASN.1 HEX"]
}
]
},
{
name: "ECDSA Sign: Using an RSA key fails",
input: ASCII_TEXT,
expectedOutput: "Provided key is not an EC key.",
recipeConfig: [
{
"op": "ECDSA Sign",
"args": [PEM_PPRIV_RSA512, "SHA-256", "ASN.1 HEX"]
}
]
},
// ECDSA Verify
{
name: "ECDSA Verify: P-256 with SHA256 (ASN.1 signature)",
input: P256.signature.sha256.asn1,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: P-256 with SHA256 (P1363 signature)",
input: P256.signature.sha256.p1363,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: P-256 with SHA256 (JWS signature)",
input: P256.signature.sha256.jws,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: P-256 with SHA256 (JSON signature)",
input: P256.signature.sha256.json,
expectedOutput: "Verified OK",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: JSON signature missing r",
input: JSON.stringify({s: JSON.parse(P256.signature.sha256.json).s}),
expectedOutput: 'No "r" value in the signature JSON',
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: JSON signature missing s",
input: JSON.stringify({r: JSON.parse(P256.signature.sha256.json).r}),
expectedOutput: 'No "s" value in the signature JSON',
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["Auto", "SHA-256", P256.publicKey, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: Using private key fails",
input: P256.signature.sha256.asn1,
expectedOutput: "Provided key is not a public key.",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-256", P256.privateKeyPkcs1, ASCII_TEXT]
}
]
},
{
name: "ECDSA Verify: Using an RSA key fails",
input: P256.signature.sha256.asn1,
expectedOutput: "Provided key is not an EC key.",
recipeConfig: [
{
"op": "ECDSA Verify",
"args": ["ASN.1 HEX", "SHA-256", PEM_PUB_RSA512, ASCII_TEXT]
}
]
},
// ECDSA Signatur Conversion
{
name: "ECDSA Signature Conversion: ASN.1 To ASN.1",
input: P256.signature.sha256.asn1,
expectedOutput: P256.signature.sha256.asn1,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "ASN.1 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: ASN.1 To P1363",
input: P256.signature.sha256.asn1,
expectedOutput: P256.signature.sha256.p1363,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "P1363 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: ASN.1 To JWS",
input: P256.signature.sha256.asn1,
expectedOutput: P256.signature.sha256.jws,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "JSON Web Signature"]
}
]
},
{
name: "ECDSA Signature Conversion: ASN.1 To JSON",
input: P256.signature.sha256.asn1,
expectedOutput: P256.signature.sha256.json,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "Raw JSON"]
}
]
},
{
name: "ECDSA Signature Conversion: P1363 To ASN.1",
input: P256.signature.sha256.p1363,
expectedOutput: P256.signature.sha256.asn1,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "ASN.1 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: P1363 To P1363",
input: P256.signature.sha256.p1363,
expectedOutput: P256.signature.sha256.p1363,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "P1363 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: P1363 To JWS",
input: P256.signature.sha256.p1363,
expectedOutput: P256.signature.sha256.jws,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "JSON Web Signature"]
}
]
},
{
name: "ECDSA Signature Conversion: P1363 To JSON",
input: P256.signature.sha256.p1363,
expectedOutput: P256.signature.sha256.json,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "Raw JSON"]
}
]
},
{
name: "ECDSA Signature Conversion: JSON To ASN.1",
input: P256.signature.sha256.json,
expectedOutput: P256.signature.sha256.asn1,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "ASN.1 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: JSON To P1363",
input: P256.signature.sha256.json,
expectedOutput: P256.signature.sha256.p1363,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "P1363 HEX"]
}
]
},
{
name: "ECDSA Signature Conversion: JSON To JWS",
input: P256.signature.sha256.json,
expectedOutput: P256.signature.sha256.jws,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "JSON Web Signature"]
}
]
},
{
name: "ECDSA Signature Conversion: JSON To JSON",
input: P256.signature.sha256.json,
expectedOutput: P256.signature.sha256.json,
recipeConfig: [
{
"op": "ECDSA Signature Conversion",
"args": ["Auto", "Raw JSON"]
}
]
}
]);

View File

@ -1,5 +1,5 @@
/**
* JA4Fingerprint tests.
* JA4 tests.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2024
@ -52,4 +52,70 @@ TestRegister.addTests([
}
],
},
{
name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN",
input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832",
expectedOutput: "t1204h2_cca9_1428ce7b4018",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S"]
}
]
},
{
name: "JA4Server Fingerprint: TLS 1.2 h2 ALPN Raw",
input: "16030300640200006003035f0236c07f47bfb12dc2da706ecb3fe7f9eeac9968cc2ddf444f574e4752440120b89ff1ab695278c69b8a73f76242ef755e0b13dc6d459aaaa784fec9c2dfce34cca900001800000000ff01000100000b00020100001000050003026832",
expectedOutput: "t1204h2_cca9_0000,ff01,000b,0010",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S Raw"]
}
]
},
{
name: "JA4Server Fingerprint: TLS 1.3",
input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304",
expectedOutput: "t130200_1301_234ea6891581",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S"]
}
]
},
{
name: "JA4Server Fingerprint: TLS 1.3 Raw",
input: "160303007a020000760303236d214556452c55a0754487e64b1a8b0262c50ba23004c9d504166a6de3439920d0b0099243c9296a0c84153ea4ada7d87ad017f4211c2ea1350b0b3cc5514d5f130100002e00330024001d002099e3cc43a2c9941ae75af1b2c7a629bee3ee7031973cad85c82f2f23677fb244002b00020304",
expectedOutput: "t130200_1301_0033,002b",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S Raw"]
}
]
},
{
name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN",
input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304",
expectedOutput: "t130200_1301_234ea6891581",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S"]
}
]
},
{
name: "JA4Server Fingerprint: TLS 1.3 non-ascii ALPN Raw",
input: "160303007a020000760303897c232e3ee313314f2b662307ff4f7e2cf1caeec1b27711bca77f469519168520bc58b92f865e6b9aa4a6371cadcb0afe1da1c0f705209a11d52357f56d5dd962130100002e00330024001d002076b8b7ed0f96b63a773d85ab6f3a87a151c130529785b41a4defb53184055957002b00020304",
expectedOutput: "t130200_1301_0033,002b",
recipeConfig: [
{
"op": "JA4Server Fingerprint",
"args": ["Hex", "JA4S Raw"]
}
]
},
]);

View File

@ -0,0 +1,359 @@
/**
* JWK conversion
*
* @author cplussharp
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
// test data for RSA key pair
const RSA_512 = {
private: {
pem1: `-----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-----`,
pem8: `-----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-----`,
jwk: {
"kty": "RSA",
"n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w",
"e": "AQAB",
"d": "OJUpM0lv36MAQR3WAwsFF7DOy-LnigteCvaNWiNVxZ6jByB5Qb7sall_Qlu9sFI0ZwrlVcKS0kldee7JTYlLWQ",
"p": "_dQoR862lOBPW1iZ2FqqPGox_EGibRGL6u31qOXOzq8",
"q": "9L85B8_Gnb7p6Af7_wpmafL277OV4X4xBfzMR-TUzHU",
"dp": "Gr5UtCQidpMfqVcvdm0vDIh_1b0wmN9FZ65EuNPlsz8",
"dq": "ReysOpMeR8tXwLcnRAKQqAyGiI1icP5Au1kydAfo1FE",
"qi": "V6dwNSyj0feakeD890gmId-lvl_w_3oUXiczqvl_N9o"
}
},
public: {
pem1: `-----BEGIN RSA PUBLIC KEY-----
MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB
757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAE=
-----END RSA PUBLIC KEY-----`,
pem8: `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`,
cert: `-----BEGIN CERTIFICATE-----
MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL
BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3
MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA
MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB
757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p
pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx
gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z
IA==
-----END CERTIFICATE-----`,
jwk: {
"kty": "RSA",
"n": "8qvQOnph0i3M5-TpruZrsvgEXgud6Uxgq1ugYuuTqKG2oU9kVEs1wmLrwe-e3yy0ys_nS3qOrBZDYSMx2SPp-w",
"e": "AQAB"
}
}
};
// test data for EC key pair
const EC_P256 = {
private: {
pem1: `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49
AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC
a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END EC PRIVATE KEY-----`,
pem8: `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw
YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC
6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p
-----END PRIVATE KEY-----`,
jwk: {
"kty": "EC",
"crv": "P-256",
"x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk",
"y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk",
"d": "21OPBSSB8CJLCqBwYBdbITS54hbqfaTf3l2ZBne8avg"
}
},
public: {
pem8: `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ
gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END PUBLIC KEY-----`,
cert: `-----BEGIN CERTIFICATE-----
MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw
FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx
NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr
qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM
gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN
ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80
ylxt
-----END CERTIFICATE-----`,
jwk: {
"kty": "EC",
"crv": "P-256",
"x": "DUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fk",
"y": "CfGZkzYggmurC4Edrw9VTYdnYoq1oCjx-D1TCmr-Xuk"
}
}
};
const PEM_PRIV_DSA1024 = `-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQCkFEttBrPHEJRgcvaT8HbZs9h1pVQLHhn2F452izusRox1czMM
IC8Z7YQiM1pt6bgEmf0h8ldx6UFT0YL9JWSbyBy1U5pHKfnz/xjeg7ZMReL4F0/T
Gwmu4ercqfM//TmEg9nL3nDxb4WmF2al/SmHN3qlzYmYaIDEFfEuu8vWbwIVAMOq
7pqQiMGUu6uJY/nQTWW0c3IfAoGARWryStp2AElj538qN9tWRuyobRA93Q1ujrdM
EqsqVpMZd1a8qtRyMaZVVdB7N3EweNUuFOoSAp10s/SQEH9qhVo6NwvzhB7lEtm4
5FjWW9+9WCuuFOGZpTy8PSFAvQcfUqunP/DeaDliNmgKci+n0nfIBakuQn10Zmqk
vGu8NZICgYBUsoQeXSJ19e6XZenk6G8wVI3yXFqnRAwb6s7sAVoPwfDCsOXTxC7W
Mlfz0HcYMiifFKEd28NnuAZ2e0ngyPHsb9s5phzTgRfO3GFzOjsjwgx3DmQI2Ck2
yOWHSAtaNhH4DoBZEyNsb1akiB50vx9b09EHN4weqbgAu743NMDHRQIVAIG5uiiO
OnWUYieHAiVIPkBCrYUd
-----END DSA PRIVATE KEY-----`;
// https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.2
const JWK_PUB_ED25591 = {
"kty": "OKP",
"crv": "Ed25519",
"x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
};
TestRegister.addTests([
{
name: "PEM to JWK: Missing footer",
input: RSA_512.private.pem1.substring(0, RSA_512.private.pem1.length / 2),
expectedOutput: "PEM footer '-----END RSA PRIVATE KEY-----' not found",
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: DSA not supported",
input: PEM_PRIV_DSA1024,
expectedOutput: "DSA keys are not supported for JWK",
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
// test RSA key convertion
{
name: "PEM to JWK: RSA Private Key PKCS1",
input: RSA_512.private.pem1,
expectedOutput: JSON.stringify(RSA_512.private.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: RSA Private Key PKCS8",
input: RSA_512.private.pem8,
expectedOutput: JSON.stringify(RSA_512.private.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: RSA Public Key PKCS1",
input: RSA_512.public.pem1,
expectedOutput: "Unsupported RSA public key format. Only PKCS#8 is supported.",
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: RSA Public Key PKCS8",
input: RSA_512.public.pem8,
expectedOutput: JSON.stringify(RSA_512.public.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: Certificate with RSA Public Key",
input: RSA_512.public.cert,
expectedOutput: JSON.stringify(RSA_512.public.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
// test EC key conversion
{
name: "PEM to JWK: EC Private Key PKCS1",
input: EC_P256.private.pem1,
expectedOutput: JSON.stringify(EC_P256.private.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: EC Private Key PKCS8",
input: EC_P256.private.pem8,
expectedOutput: JSON.stringify(EC_P256.private.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: EC Public Key",
input: EC_P256.public.pem8,
expectedOutput: JSON.stringify(EC_P256.public.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "PEM to JWK: Certificate with EC Public Key",
input: EC_P256.public.cert,
expectedOutput: JSON.stringify(EC_P256.public.jwk),
recipeConfig: [
{
op: "PEM to JWK",
args: [],
}
],
},
{
name: "JWK to PEM: not a JWK",
input: "\"foobar\"",
expectedOutput: "Input is not a JSON Web Key",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
{
name: "JWK to PEM: unsupported key type",
input: JSON.stringify(JWK_PUB_ED25591),
expectedOutput: "Unsupported JWK key type 'OKP'",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
// test RSA key conversion
{
name: "JWK to PEM: RSA Private Key",
input: JSON.stringify(RSA_512.private.jwk),
expectedOutput: RSA_512.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
{
name: "JWK to PEM: RSA Public Key",
input: JSON.stringify(RSA_512.public.jwk),
expectedOutput: RSA_512.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
// test EC key conversion
{
name: "JWK to PEM: EC Private Key",
input: JSON.stringify(EC_P256.private.jwk),
expectedOutput: EC_P256.private.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
{
name: "JWK to PEM: EC Public Key",
input: JSON.stringify(EC_P256.public.jwk),
expectedOutput: EC_P256.public.pem8.replace(/\r/g, "").replace(/\n/g, "\r\n")+"\r\n",
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
{
name: "JWK to PEM: Array of keys",
input: JSON.stringify([RSA_512.public.jwk, EC_P256.public.jwk]),
expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
},
{
name: "JWK to PEM: JSON Web Key Set",
input: JSON.stringify({"keys": [RSA_512.public.jwk, EC_P256.public.jwk]}),
expectedOutput: (RSA_512.public.pem8 + "\n" + EC_P256.public.pem8 + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "JWK to PEM",
args: [],
}
],
}
]);

View File

@ -0,0 +1,215 @@
/**
* Public Key from Certificate
*
* @author cplussharp
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const RSA_CERT = `-----BEGIN CERTIFICATE-----
MIIBfTCCASegAwIBAgIUeisK5Nwss2DGg5PCs4uSxxXyyNkwDQYJKoZIhvcNAQEL
BQAwEzERMA8GA1UEAwwIUlNBIHRlc3QwHhcNMjExMTE5MTcyMDI2WhcNMzExMTE3
MTcyMDI2WjATMREwDwYDVQQDDAhSU0EgdGVzdDBcMA0GCSqGSIb3DQEBAQUAA0sA
MEgCQQDyq9A6emHSLczn5Omu5muy+AReC53pTGCrW6Bi65OoobahT2RUSzXCYuvB
757fLLTKz+dLeo6sFkNhIzHZI+n7AgMBAAGjUzBRMB0GA1UdDgQWBBRO+jvkqq5p
pnQgwMMnRoun6e7eiTAfBgNVHSMEGDAWgBRO+jvkqq5ppnQgwMMnRoun6e7eiTAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA0EAR/5HAZM5qBhU/ezDUIFx
gmUGoFbIb5kJD41YCnaSdrgWglh4He4melSs42G/oxBBjuCJ0bUpqWnLl+lJkv1z
IA==
-----END CERTIFICATE-----`;
const RSA_PUBKEY = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`;
const EC_P256_CERT = `-----BEGIN CERTIFICATE-----
MIIBfzCCASWgAwIBAgIUK4H8J3Hr7NpRLPrACj8Pje4JJJ0wCgYIKoZIzj0EAwIw
FTETMBEGA1UEAwwKUC0yNTYgdGVzdDAeFw0yMTExMTkxNzE5NDVaFw0zMTExMTcx
NzE5NDVaMBUxEzARBgNVBAMMClAtMjU2IHRlc3QwWTATBgcqhkjOPQIBBggqhkjO
PQMBBwNCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC6yBwATwfrzXR+QnxmZM2IIJr
qwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7po1MwUTAdBgNVHQ4EFgQU/SxodXrpkybM
gcIgkxnRKd7HMzowHwYDVR0jBBgwFoAU/SxodXrpkybMgcIgkxnRKd7HMzowDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiBU9PrOa/kXCpTTBInRf/sN
ac2iDHmbdpWzcXI+xLKNYAIhAIRR1LRSHVwOTLQ/iBXd+8LCkm5aTB27RW46LN80
ylxt
-----END CERTIFICATE-----`;
const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ
gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END PUBLIC KEY-----`;
const DSA_CERT = `-----BEGIN CERTIFICATE-----
MIIEXzCCBA2gAwIBAgIUYYcPJB8UQLzUnqkGJvs3J4RI0OgwCwYJYIZIAWUDBAMC
MBMxETAPBgNVBAMMCERTQSBUZXN0MB4XDTIzMTAxNTAwMjEzNVoXDTMzMTAxMjAw
MjEzNVowEzERMA8GA1UEAwwIRFNBIFRlc3QwggNCMIICNQYHKoZIzjgEATCCAigC
ggEBALoLV+uz7vMYZCIuwXNkgZawvDgZAG1T7IiG030WgqesRNncuoUQOmAJCiuN
zkjVNSY08rabex/RIkWILvxP91SlzhA9t9+dp87p238ecxGa1sD2re+y35RP7IxN
T33633NtwGItZ3BqqAhoMmuwwwxau0E8zwYodTTlwTRp4QVPpMH1eJCUBeEzcWP5
ZZ1lRNhR5M2TqzSU3ya5/4c3a9rI86h9VIVgw8yVvw3y6yclzjALm2ntD5riskdM
Z6mMkfYQwEbIGRTELX6A7LZ0lX1CislenF9ASb2E4g2nGcMQ0uSGzA4W9mf6wwmP
S6iwX5+Qu/i6jCm5i37fQ1H5HHUCHQDA+UnPHM6PZEgfFen8djZpl/cl05MpWk+d
nikFAoIBADXOTpBw0WA+UihxDG+6qqM05kxVMYmz6IRZ/06ffZSGVFN6Bx1i0s3v
kzM5V8GsKpkKkSk7V8fTQnAIIlMmt1Y7ff+ng7+TfYotMrvvEYlolYK06J2WWoUA
8iKp8+n58vdoky+xZmuGmcvCAojVDbEeU2wEqYE1PzrHCSOoOiKB2P4fOhyuF+qx
E8nkzURIg2RmSSkqWOkXiWyKyfpUaB+4cEisp4ThENEPmdntE1vLh2r7EOIxpE5D
0NAy2wFKqe3ljfgE6XsPZKgVAguRDVpzdmL6WDY7DM/BcS726vx+kX55QDkszvec
raNirnir2QrB/a0JQjF6Y62yGmG7GF8DggEFAAKCAQBpN+w0N0b5IIAspXnlJ9yu
B6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb0rba
L5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyrHudV
Xu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp02e8H
zvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFMMeKN
K/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9LjKj
o1MwUTAdBgNVHQ4EFgQUE+xZdvgiDIFWKQskMYnNaZ3iPHAwHwYDVR0jBBgwFoAU
E+xZdvgiDIFWKQskMYnNaZ3iPHAwDwYDVR0TAQH/BAUwAwEB/zALBglghkgBZQME
AwIDPwAwPAIcZbtf4+bjXEGQqNs6IglLrOgIjYF46q7qCNfXmQIcMKUtH3S6sDJE
3ds9eL+oC+HPFlfUNfUiU30aDA==
-----END CERTIFICATE-----`;
const DSA_PUBKEY = `-----BEGIN PUBLIC KEY-----
MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt
U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff
nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G
KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF
YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf
QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ
zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM
VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/
p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs
BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI
rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi
+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB
BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa
VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0
Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC
8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ
N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1
ZJMV1HnACFLOjJNdJEn5LohpfS4yow==
-----END PUBLIC KEY-----`;
const ED25519_CERT = `-----BEGIN CERTIFICATE-----
MIIBQjCB9aADAgECAhRjPJhrdNco5LzpsIs0vSLLaZaZ0DAFBgMrZXAwFzEVMBMG
A1UEAwwMRWQyNTUxOSBUZXN0MB4XDTIzMTAxNTAwMjMwOFoXDTMzMTAxMjAwMjMw
OFowFzEVMBMGA1UEAwwMRWQyNTUxOSBUZXN0MCowBQYDK2VwAyEAELP6AflXwsuZ
5q4NDIO0LP2iCdKRvds4nwsUmRhOw3ijUzBRMB0GA1UdDgQWBBRfxS9q0IemWxkH
4mwAwzr9dQx2xzAfBgNVHSMEGDAWgBRfxS9q0IemWxkH4mwAwzr9dQx2xzAPBgNV
HRMBAf8EBTADAQH/MAUGAytlcANBAI/+03iVq4yJ+DaLVs61w41cVX2UxKvquSzv
lllkpkclM9LH5dLrw4ArdTjS9zAjzY/02WkphHhICHXt3KqZTwI=
-----END CERTIFICATE-----`;
/*
const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g=
-----END PUBLIC KEY-----`;
*/
const ED448_CERT = `-----BEGIN CERTIFICATE-----
MIIBijCCAQqgAwIBAgIUZaCS7zEjOnQ7O4KUFym6fJF5vl8wBQYDK2VxMBUxEzAR
BgNVBAMMCkVkNDQ4IFRlc3QwHhcNMjMxMDE1MDAyMzI1WhcNMzMxMDEyMDAyMzI1
WjAVMRMwEQYDVQQDDApFZDQ0OCBUZXN0MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/Ov
BTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRlEHQwXsNYLZTtY2Jra6AWhbVYYaEAo1Mw
UTAdBgNVHQ4EFgQUJFrepAf9YXrmDMSAzrMeYQmosd0wHwYDVR0jBBgwFoAUJFre
pAf9YXrmDMSAzrMeYQmosd0wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXEDcwA+YiZj
puFr2aogfV1qg/ixk7qLi25BbKVNR6+7PEUjo7+4yBn9qnLbAHUGnHn7E96pSey9
VkLqpoDNMRcM3Eb6h3AJpQM0oxGj8q9arjDXqJkXgaO2e0tVn8KKVfy7S8qO72Kd
rWzZowcOjnWKhXm7JgA=
-----END CERTIFICATE-----`;
/*
const ED448_PUBKEY = `-----BEGIN PUBLIC KEY-----
MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl
EHQwXsNYLZTtY2Jra6AWhbVYYaEA
-----END PUBLIC KEY-----`
*/
TestRegister.addTests([
{
name: "Public Key from Certificate: Missing footer",
input: RSA_CERT.substring(0, RSA_CERT.length / 2),
expectedOutput: "PEM footer '-----END CERTIFICATE-----' not found",
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
// test RSA certificate
{
name: "Public Key from Certificate: RSA",
input: RSA_CERT,
expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
// test EC certificate
{
name: "Public Key from Certificate: EC",
input: EC_P256_CERT,
expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
// test DSA certificate
{
name: "Public Key from Certificate: DSA",
input: DSA_CERT,
expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
// test EdDSA certificates
{
name: "Public Key from Certificate: Ed25519",
input: ED25519_CERT,
expectedOutput: "Unsupported public key type",
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
{
name: "Public Key from Certificate: Ed448",
input: ED448_CERT,
expectedOutput: "Unsupported public key type",
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
},
// test multi-input
{
name: "Public Key from Certificate: Multiple certificates",
input: RSA_CERT + "\n" + EC_P256_CERT,
expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Certificate",
args: [],
}
],
}
]);

View File

@ -0,0 +1,254 @@
/**
* Public Key from Private Key
*
* @author cplussharp
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const RSA_PRIVKEY_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 RSA_PRIVKEY_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 RSA_PUBKEY = `-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPKr0Dp6YdItzOfk6a7ma7L4BF4LnelM
YKtboGLrk6ihtqFPZFRLNcJi68Hvnt8stMrP50t6jqwWQ2EjMdkj6fsCAwEAAQ==
-----END PUBLIC KEY-----`;
const EC_P256_PRIVKEY_SEC1 = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtTjwUkgfAiSwqgcGAXWyE0ueIW6n2k395dmQZ3vGr4oAoGCCqGSM49
AwEHoUQDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJgusgcAE8H6810fkJ8ZmTNiCC
a6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END EC PRIVATE KEY-----`;
const EC_P256_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg21OPBSSB8CJLCqBw
YBdbITS54hbqfaTf3l2ZBne8avihRANCAAQNRzwDQQM0qgJgg9YwfPXJTOoTmYmC
6yBwATwfrzXR+QnxmZM2IIJrqwuBHa8PVU2HZ2KKtaAo8fg9Uwpq/l7p
-----END PRIVATE KEY-----`;
const EC_P256_PUBKEY = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDUc8A0EDNKoCYIPWMHz1yUzqE5mJ
gusgcAE8H6810fkJ8ZmTNiCCa6sLgR2vD1VNh2diirWgKPH4PVMKav5e6Q==
-----END PUBLIC KEY-----`;
const DSA_PRIVKEY_TRAD = `-----BEGIN DSA PRIVATE KEY-----
MIIDTQIBAAKCAQEAugtX67Pu8xhkIi7Bc2SBlrC8OBkAbVPsiIbTfRaCp6xE2dy6
hRA6YAkKK43OSNU1JjTytpt7H9EiRYgu/E/3VKXOED23352nzunbfx5zEZrWwPat
77LflE/sjE1Pffrfc23AYi1ncGqoCGgya7DDDFq7QTzPBih1NOXBNGnhBU+kwfV4
kJQF4TNxY/llnWVE2FHkzZOrNJTfJrn/hzdr2sjzqH1UhWDDzJW/DfLrJyXOMAub
ae0PmuKyR0xnqYyR9hDARsgZFMQtfoDstnSVfUKKyV6cX0BJvYTiDacZwxDS5IbM
Dhb2Z/rDCY9LqLBfn5C7+LqMKbmLft9DUfkcdQIdAMD5Sc8czo9kSB8V6fx2NmmX
9yXTkylaT52eKQUCggEANc5OkHDRYD5SKHEMb7qqozTmTFUxibPohFn/Tp99lIZU
U3oHHWLSze+TMzlXwawqmQqRKTtXx9NCcAgiUya3Vjt9/6eDv5N9ii0yu+8RiWiV
grTonZZahQDyIqnz6fny92iTL7Fma4aZy8ICiNUNsR5TbASpgTU/OscJI6g6IoHY
/h86HK4X6rETyeTNREiDZGZJKSpY6ReJbIrJ+lRoH7hwSKynhOEQ0Q+Z2e0TW8uH
avsQ4jGkTkPQ0DLbAUqp7eWN+ATpew9kqBUCC5ENWnN2YvpYNjsMz8FxLvbq/H6R
fnlAOSzO95yto2KueKvZCsH9rQlCMXpjrbIaYbsYXwKCAQBpN+w0N0b5IIAspXnl
J9yuB6ORk3j/5rZ+DUtTzW1YAJI6xjTcFQvN7FpVLkmLtXKUXF04R+sdGJ7VFwOb
0rbaL5vQzrqNkBrbgSzuzeloiG+7OLA6VeQtNbQh6OurrZFi9gY+qA5ciT9kQXyr
HudVXu956NDrooRxmv6JIVFvToaNiwe2vcgdkALw8HUbLFYof4SAE9jgU8EpxTp0
2e8HzvVSVa6yj1nnGhpzLPlEqF8TZvs9pTg2kIk3/zvWojMJoPyTALfbTjbAeiFM
MeKNK/CKOOJj23AVAZxpMSR6cUbrIcRdKDnhCTVkkxXUecAIUs6Mk10kSfkuiGl9
LjKjAhwpK4MOpkKEu+y308fZ+yZXypZW2m9Y/wOT0L4g
-----END DSA PRIVATE KEY-----`;
const DSA_PRIVKEY_PKCS8 = `-----BEGIN PRIVATE KEY-----
MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4
GQBtU+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4Q
PbffnafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtB
PM8GKHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOo
fVSFYMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJ
XpxfQEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0A
wPlJzxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqj
NOZMVTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdW
O33/p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2x
HlNsBKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgf
uHBIrKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1a
c3Zi+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhf
BB4CHCkrgw6mQoS77LfTx9n7JlfKllbab1j/A5PQviA=
-----END PRIVATE KEY-----`;
const DSA_PUBKEY = `-----BEGIN PUBLIC KEY-----
MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQC6C1frs+7zGGQiLsFzZIGWsLw4GQBt
U+yIhtN9FoKnrETZ3LqFEDpgCQorjc5I1TUmNPK2m3sf0SJFiC78T/dUpc4QPbff
nafO6dt/HnMRmtbA9q3vst+UT+yMTU99+t9zbcBiLWdwaqgIaDJrsMMMWrtBPM8G
KHU05cE0aeEFT6TB9XiQlAXhM3Fj+WWdZUTYUeTNk6s0lN8muf+HN2vayPOofVSF
YMPMlb8N8usnJc4wC5tp7Q+a4rJHTGepjJH2EMBGyBkUxC1+gOy2dJV9QorJXpxf
QEm9hOINpxnDENLkhswOFvZn+sMJj0uosF+fkLv4uowpuYt+30NR+Rx1Ah0AwPlJ
zxzOj2RIHxXp/HY2aZf3JdOTKVpPnZ4pBQKCAQA1zk6QcNFgPlIocQxvuqqjNOZM
VTGJs+iEWf9On32UhlRTegcdYtLN75MzOVfBrCqZCpEpO1fH00JwCCJTJrdWO33/
p4O/k32KLTK77xGJaJWCtOidllqFAPIiqfPp+fL3aJMvsWZrhpnLwgKI1Q2xHlNs
BKmBNT86xwkjqDoigdj+HzocrhfqsRPJ5M1ESINkZkkpKljpF4lsisn6VGgfuHBI
rKeE4RDRD5nZ7RNby4dq+xDiMaROQ9DQMtsBSqnt5Y34BOl7D2SoFQILkQ1ac3Zi
+lg2OwzPwXEu9ur8fpF+eUA5LM73nK2jYq54q9kKwf2tCUIxemOtshphuxhfA4IB
BQACggEAaTfsNDdG+SCALKV55SfcrgejkZN4/+a2fg1LU81tWACSOsY03BULzexa
VS5Ji7VylFxdOEfrHRie1RcDm9K22i+b0M66jZAa24Es7s3paIhvuziwOlXkLTW0
Iejrq62RYvYGPqgOXIk/ZEF8qx7nVV7veejQ66KEcZr+iSFRb06GjYsHtr3IHZAC
8PB1GyxWKH+EgBPY4FPBKcU6dNnvB871UlWuso9Z5xoacyz5RKhfE2b7PaU4NpCJ
N/871qIzCaD8kwC32042wHohTDHijSvwijjiY9twFQGcaTEkenFG6yHEXSg54Qk1
ZJMV1HnACFLOjJNdJEn5LohpfS4yow==
-----END PUBLIC KEY-----`;
const ED25519_PRIVKEY = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIC18vtoHINC8Mo9dTIqOrBs3J28ZvHrwzRq57g2kpV98
-----END PRIVATE KEY-----`;
/*
const ED25519_PUBKEY = `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAELP6AflXwsuZ5q4NDIO0LP2iCdKRvds4nwsUmRhOw3g=
-----END PUBLIC KEY-----`;
*/
const ED448_PRIVKEY = `-----BEGIN PRIVATE KEY-----
MEcCAQAwBQYDK2VxBDsEOWdGJ06bDcWznJhBoQqPeTfsCe+AvBv1n7KfIGYzR4tv
1kcwHnbxlemnCMgqvbrRXaLuFUBysUZThA==
-----END PRIVATE KEY-----`;
/*
const ED448_PUBKEY = `-----BEGIN PUBLIC KEY-----
MEMwBQYDK2VxAzoAVN8kG0TMVyGOu/OvBTe8H0Wi4HJrQAlSv4XLwJbkuoi4EeRl
EHQwXsNYLZTtY2Jra6AWhbVYYaEA
-----END PUBLIC KEY-----`;
*/
TestRegister.addTests([
{
name: "Public Key from Private Key: Missing footer",
input: RSA_PRIVKEY_PKCS1.substring(0, RSA_PRIVKEY_PKCS1.length / 2),
expectedOutput: "PEM footer '-----END RSA PRIVATE KEY-----' not found",
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
// test RSA
{
name: "Public Key from Private Key: RSA PKCS#1",
input: RSA_PRIVKEY_PKCS1,
expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
{
name: "Public Key from Private Key: RSA PKCS#8",
input: RSA_PRIVKEY_PKCS8,
expectedOutput: (RSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
// test EC certificate
{
name: "Public Key from Private Key: EC SEC1",
input: EC_P256_PRIVKEY_SEC1,
expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
{
name: "Public Key from Private Key: EC PKCS#8",
input: EC_P256_PRIVKEY_PKCS8,
expectedOutput: (EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
// test DSA
{
name: "Public Key from Private Key: DSA Traditional",
input: DSA_PRIVKEY_TRAD,
expectedOutput: (DSA_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
{
name: "Public Key from Private Key: DSA PKCS#8",
input: DSA_PRIVKEY_PKCS8,
expectedOutput: "DSA Private Key in PKCS#8 is not supported",
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
// test EdDSA
{
name: "Public Key from Private Key: Ed25519",
input: ED25519_PRIVKEY,
expectedOutput: "Unsupported key type: Error: malformed PKCS8 private key(code:004)",
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
{
name: "Public Key from Private Key: Ed448",
input: ED448_PRIVKEY,
expectedOutput: "Unsupported key type: Error: malformed PKCS8 private key(code:004)",
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
},
// test multi-input
{
name: "Public Key from Private Key: Multiple keys",
input: RSA_PRIVKEY_PKCS8 + "\n" + EC_P256_PRIVKEY_PKCS8,
expectedOutput: (RSA_PUBKEY + "\n" + EC_P256_PUBKEY + "\n").replace(/\r/g, "").replace(/\n/g, "\r\n"),
recipeConfig: [
{
op: "Public Key from Private Key",
args: [],
}
],
}
]);

View File

@ -1,62 +1,42 @@
/**
* Base64 tests.
* XXTEA tests.
*
* @author devcydo [devcydo@gmail.com]
*
* @copyright Crown Copyright 2022
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2024
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "XXTEA",
name: "XXTEA Encrypt and Decrypt",
input: "Hello World! 你好,中国!",
expectedOutput: "QncB1C0rHQoZ1eRiPM4dsZtRi9pNrp7sqvX76cFXvrrIHXL6",
reecipeConfig: [
expectedOutput: "Hello World! 你好,中国!",
recipeConfig: [
{
args: "1234567890"
"op": "XXTEA Encrypt",
"args": [{ "option": "UTF8", "string": "1234567890" }]
},
{
"op": "XXTEA Decrypt",
"args": [{ "option": "UTF8", "string": "1234567890" }]
}
],
},
{
name: "XXTEA",
name: "XXTEA Encrypt",
input: "ნუ პანიკას",
expectedOutput: "PbWjnbFmP8Apu2MKOGNbjeW/72IZLlLMS/g82ozLxwE=",
reecipeConfig: [
expectedOutput: "3db5a39db1663fc029bb630a38635b8de5bfef62192e52cc4bf83cda8ccbc701",
recipeConfig: [
{
args: "1234567890"
"op": "XXTEA Encrypt",
"args": [{ "option": "UTF8", "string": "1234567890" }]
},
],
},
{
name: "XXTEA",
input: "ნუ პანიკას",
expectedOutput: "dHrOJ4ClIx6gH33NPSafYR2GG7UqsazY6Xfb0iekBY4=",
reecipeConfig: [
{
args: "ll3kj209d2"
},
"op": "To Hex",
"args": ["None", 0]
}
],
},
{
name: "XXTEA",
input: "",
expectedOutput: "Invalid input length (0)",
reecipeConfig: [
{
args: "1234567890"
},
],
},
{
name: "XXTEA",
input: "",
expectedOutput: "Invalid input length (0)",
reecipeConfig: [
{
args: ""
},
],
},
}
]);