diff --git a/CHANGELOG.md b/CHANGELOG.md index abceeda3..e9fbb154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 + diff --git a/package-lock.json b/package-lock.json index 20935e27..b5836052 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index c623c647..81009c3a 100644 --- a/package.json +++ b/package.json @@ -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 ", "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" diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 0f5e0412..c76e50ef 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -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", diff --git a/src/core/lib/Ciphers.mjs b/src/core/lib/Ciphers.mjs index a4d174b5..6266a8e1 100644 --- a/src/core/lib/Ciphers.mjs +++ b/src/core/lib/Ciphers.mjs @@ -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) diff --git a/src/core/lib/JA4.mjs b/src/core/lib/JA4.mjs index b0b423a2..5e606f46 100644 --- a/src/core/lib/JA4.mjs +++ b/src/core/lib/JA4.mjs @@ -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 doesn’t 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 there’s 6 cipher suites in the hello packet, then the value should be “06”. + If there’s > 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 + } +} diff --git a/src/core/lib/TLS.mjs b/src/core/lib/TLS.mjs index e3f18eb3..6373bfa2 100644 --- a/src/core/lib/TLS.mjs +++ b/src/core/lib/TLS.mjs @@ -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); diff --git a/src/core/lib/XXTEA.mjs b/src/core/lib/XXTEA.mjs new file mode 100644 index 00000000..6d556fc5 --- /dev/null +++ b/src/core/lib/XXTEA.mjs @@ -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); +} diff --git a/src/core/operations/ECDSASign.mjs b/src/core/operations/ECDSASign.mjs new file mode 100644 index 00000000..7b8f57f1 --- /dev/null +++ b/src/core/operations/ECDSASign.mjs @@ -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; diff --git a/src/core/operations/ECDSASignatureConversion.mjs b/src/core/operations/ECDSASignatureConversion.mjs new file mode 100644 index 00000000..3f6c6bfb --- /dev/null +++ b/src/core/operations/ECDSASignatureConversion.mjs @@ -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; diff --git a/src/core/operations/ECDSAVerify.mjs b/src/core/operations/ECDSAVerify.mjs new file mode 100644 index 00000000..7e46e867 --- /dev/null +++ b/src/core/operations/ECDSAVerify.mjs @@ -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; diff --git a/src/core/operations/GenerateECDSAKeyPair.mjs b/src/core/operations/GenerateECDSAKeyPair.mjs new file mode 100644 index 00000000..14714a02 --- /dev/null +++ b/src/core/operations/GenerateECDSAKeyPair.mjs @@ -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.

${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; diff --git a/src/core/operations/JA4ServerFingerprint.mjs b/src/core/operations/JA4ServerFingerprint.mjs new file mode 100644 index 00000000..662285a8 --- /dev/null +++ b/src/core/operations/JA4ServerFingerprint.mjs @@ -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.

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; diff --git a/src/core/operations/JWKToPem.mjs b/src/core/operations/JWKToPem.mjs new file mode 100644 index 00000000..c8c00270 --- /dev/null +++ b/src/core/operations/JWKToPem.mjs @@ -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 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; diff --git a/src/core/operations/PubKeyFromCert.mjs b/src/core/operations/PubKeyFromCert.mjs new file mode 100644 index 00000000..0233b04a --- /dev/null +++ b/src/core/operations/PubKeyFromCert.mjs @@ -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; diff --git a/src/core/operations/PubKeyFromPrivKey.mjs b/src/core/operations/PubKeyFromPrivKey.mjs new file mode 100644 index 00000000..5a08882b --- /dev/null +++ b/src/core/operations/PubKeyFromPrivKey.mjs @@ -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; diff --git a/src/core/operations/RAKE.mjs b/src/core/operations/RAKE.mjs index d1165b51..1470f5f0 100644 --- a/src/core/operations/RAKE.mjs +++ b/src/core/operations/RAKE.mjs @@ -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); diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs deleted file mode 100644 index 1a0a4368..00000000 --- a/src/core/operations/XXTEA.mjs +++ /dev/null @@ -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; diff --git a/src/core/operations/XXTEADecrypt.mjs b/src/core/operations/XXTEADecrypt.mjs new file mode 100644 index 00000000..496e5409 --- /dev/null +++ b/src/core/operations/XXTEADecrypt.mjs @@ -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; diff --git a/src/core/operations/XXTEAEncrypt.mjs b/src/core/operations/XXTEAEncrypt.mjs new file mode 100644 index 00000000..85379de0 --- /dev/null +++ b/src/core/operations/XXTEAEncrypt.mjs @@ -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; diff --git a/src/web/App.mjs b/src/web/App.mjs index 53070e26..3ebfe1be 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -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; } diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index ae61b58d..30cfd1d9 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -85,6 +85,7 @@ class HTMLOperation {
pause not_interested + keyboard_arrow_up
 
`; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index b02a7eee..b188b3b9 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -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); diff --git a/src/web/html/index.html b/src/web/html/index.html index 02005796..13e7b8e3 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -142,8 +142,8 @@
-
@@ -272,7 +275,7 @@
-
+
@@ -280,17 +283,17 @@ - - - - @@ -608,7 +611,7 @@ What sort of things can I do with CyberChef?
-

There are around 300 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:

+

There are hundreds of operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:

  • Decode a Base64-encoded string
  • Convert a date and time to a different time zone
  • @@ -679,7 +682,7 @@
    -

    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.

    +

    There are hundreds of 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.

    It’s the Cyber Swiss Army Knife.

@@ -860,7 +863,7 @@
CyberChef v<%= htmlWebpackPlugin.options.version %>
  • Build time: <%= htmlWebpackPlugin.options.compileTime %>
  • -
  • The changelog for this version can be viewed here
  • +
  • The changelog for this version can be viewed here
  • © Crown Copyright 2016-<%= htmlWebpackPlugin.options.compileYear %>
  • Released under the Apache Licence, Version 2.0
  • SHA256 hash: DOWNLOAD_HASH_PLACEHOLDER
  • diff --git a/src/web/stylesheets/layout/_modals.css b/src/web/stylesheets/layout/_modals.css index affc372d..3cd0ce09 100755 --- a/src/web/stylesheets/layout/_modals.css +++ b/src/web/stylesheets/layout/_modals.css @@ -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), diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 660a7ec5..b57940a3 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -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. * diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index bffca98c..3999fd2f 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -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)); } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 190d2ad9..2b2cb096 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -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(); diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index 42e763b0..3f5aa302 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -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. diff --git a/src/web/waiters/WorkerWaiter.mjs b/src/web/waiters/WorkerWaiter.mjs index 3ad9695e..296aa956 100644 --- a/src/web/waiters/WorkerWaiter.mjs +++ b/src/web/waiters/WorkerWaiter.mjs @@ -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 * diff --git a/tests/browser/01_io.js b/tests/browser/01_io.js index 6791c88e..f8f7f9e1 100644 --- a/tests/browser/01_io.js +++ b/tests/browser/01_io.js @@ -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") diff --git a/tests/browser/02_ops.js b/tests/browser/02_ops.js index c7a862ee..782600f2 100644 --- a/tests/browser/02_ops.js +++ b/tests/browser/02_ops.js @@ -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/); diff --git a/tests/browser/browserUtils.js b/tests/browser/browserUtils.js index 4a559758..f8fc71cb 100644 --- a/tests/browser/browserUtils.js +++ b/tests/browser/browserUtils.js @@ -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 diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 3a387d00..1b9c8dbe 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -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, diff --git a/tests/operations/tests/ECDSA.mjs b/tests/operations/tests/ECDSA.mjs new file mode 100644 index 00000000..560afc5c --- /dev/null +++ b/tests/operations/tests/ECDSA.mjs @@ -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"] + } + ] + } +]); diff --git a/tests/operations/tests/JA4Fingerprint.mjs b/tests/operations/tests/JA4.mjs similarity index 63% rename from tests/operations/tests/JA4Fingerprint.mjs rename to tests/operations/tests/JA4.mjs index dba84a38..0fb4624e 100644 --- a/tests/operations/tests/JA4Fingerprint.mjs +++ b/tests/operations/tests/JA4.mjs @@ -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"] + } + ] + }, ]); diff --git a/tests/operations/tests/JWK.mjs b/tests/operations/tests/JWK.mjs new file mode 100644 index 00000000..c77c983e --- /dev/null +++ b/tests/operations/tests/JWK.mjs @@ -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: [], + } + ], + } +]); diff --git a/tests/operations/tests/PubKeyFromCert.mjs b/tests/operations/tests/PubKeyFromCert.mjs new file mode 100644 index 00000000..ae5609aa --- /dev/null +++ b/tests/operations/tests/PubKeyFromCert.mjs @@ -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: [], + } + ], + } +]); diff --git a/tests/operations/tests/PubKeyFromPrivKey.mjs b/tests/operations/tests/PubKeyFromPrivKey.mjs new file mode 100644 index 00000000..25f14a69 --- /dev/null +++ b/tests/operations/tests/PubKeyFromPrivKey.mjs @@ -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: [], + } + ], + } +]); diff --git a/tests/operations/tests/XXTEA.mjs b/tests/operations/tests/XXTEA.mjs index 4787f086..61e2c188 100644 --- a/tests/operations/tests/XXTEA.mjs +++ b/tests/operations/tests/XXTEA.mjs @@ -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: "" - }, - ], - }, + } ]);