Merge branch 'master' into drop-nth-bytes
This commit is contained in:
commit
912bbe54aa
1
.github/workflows/master.yml
vendored
1
.github/workflows/master.yml
vendored
@ -19,6 +19,7 @@ jobs:
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
1
.github/workflows/pull_requests.yml
vendored
1
.github/workflows/pull_requests.yml
vendored
@ -18,6 +18,7 @@ jobs:
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
export DETECT_CHROMEDRIVER_VERSION=true
|
||||
npm install
|
||||
npm run setheapsize
|
||||
|
||||
|
44
package-lock.json
generated
44
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "10.19.2",
|
||||
"version": "10.19.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cyberchef",
|
||||
"version": "10.19.2",
|
||||
"version": "10.19.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -115,9 +115,10 @@
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^127.0.2",
|
||||
"chromedriver": "^130.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"core-js": "^3.37.1",
|
||||
"css-loader": "7.1.2",
|
||||
@ -3982,12 +3983,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
|
||||
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
@ -5093,14 +5094,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chromedriver": {
|
||||
"version": "127.0.2",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-127.0.2.tgz",
|
||||
"integrity": "sha512-mYfJ/8FqzsdFOs2rPiAI4y0suFnv78cRnzZK0MHdSfSIDeRPbqZz0rNX4lrXt14hXc9vqXa+a8cMxlrhWtXKSQ==",
|
||||
"version": "130.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-130.0.0.tgz",
|
||||
"integrity": "sha512-1g1eMoKF22Uh6l8DTFOPvWLovINPrkAMw7yDHlF6Rx+4W4JI9aGdCZ2Cx7c181hUgALU1oSKGH3uKNryYM5DaQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@testim/chrome-version": "^1.1.4",
|
||||
"axios": "^1.6.7",
|
||||
"axios": "^1.7.4",
|
||||
"compare-versions": "^6.1.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"proxy-agent": "^6.4.0",
|
||||
@ -5344,6 +5345,27 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression-webpack-plugin": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz",
|
||||
"integrity": "sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"schema-utils": "^4.2.0",
|
||||
"serialize-javascript": "^6.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compression/node_modules/bytes": {
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cyberchef",
|
||||
"version": "10.19.2",
|
||||
"version": "10.19.4",
|
||||
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
|
||||
"author": "n1474335 <n1474335@gmail.com>",
|
||||
"homepage": "https://gchq.github.io/CyberChef",
|
||||
@ -55,9 +55,10 @@
|
||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||
"babel-plugin-transform-builtin-extend": "1.1.2",
|
||||
"base64-loader": "^1.0.0",
|
||||
"chromedriver": "^127.0.2",
|
||||
"chromedriver": "^130.0.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"colors": "^1.4.0",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"core-js": "^3.37.1",
|
||||
"css-loader": "7.1.2",
|
||||
|
@ -164,6 +164,7 @@
|
||||
"name": "Public Key",
|
||||
"ops": [
|
||||
"Parse X.509 certificate",
|
||||
"Parse X.509 CRL",
|
||||
"Parse ASN.1 hex string",
|
||||
"PEM to Hex",
|
||||
"Hex to PEM",
|
||||
@ -234,8 +235,12 @@
|
||||
"Parse IP range",
|
||||
"Parse IPv6 address",
|
||||
"Parse IPv4 header",
|
||||
"Strip IPv4 header",
|
||||
"Parse TCP",
|
||||
"Strip TCP header",
|
||||
"Parse TLS record",
|
||||
"Parse UDP",
|
||||
"Strip UDP header",
|
||||
"Parse SSH Host Key",
|
||||
"Parse URI",
|
||||
"URL Encode",
|
||||
|
@ -26,6 +26,9 @@ export function objToTable(obj, nested=false) {
|
||||
</tr>`;
|
||||
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "function")
|
||||
continue;
|
||||
|
||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||
if (typeof obj[key] === "object")
|
||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
||||
|
@ -22,7 +22,13 @@ class AddLineNumbers extends Operation {
|
||||
this.description = "Adds line numbers to the output.";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [];
|
||||
this.args = [
|
||||
{
|
||||
"name": "Offset",
|
||||
"type": "number",
|
||||
"value": 0
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,10 +39,11 @@ class AddLineNumbers extends Operation {
|
||||
run(input, args) {
|
||||
const lines = input.split("\n"),
|
||||
width = lines.length.toString().length;
|
||||
const offset = args[0] ? parseInt(args[0], 10) : 0;
|
||||
let output = "";
|
||||
|
||||
for (let n = 0; n < lines.length; n++) {
|
||||
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||
output += (n+1+offset).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||
}
|
||||
return output.slice(0, output.length-1);
|
||||
}
|
||||
|
@ -36,6 +36,11 @@ class JWTSign extends Operation {
|
||||
name: "Signing algorithm",
|
||||
type: "option",
|
||||
value: JWT_ALGORITHMS
|
||||
},
|
||||
{
|
||||
name: "Header",
|
||||
type: "text",
|
||||
value: "{}"
|
||||
}
|
||||
];
|
||||
}
|
||||
@ -46,11 +51,12 @@ class JWTSign extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [key, algorithm] = args;
|
||||
const [key, algorithm, header] = args;
|
||||
|
||||
try {
|
||||
return jwt.sign(input, key, {
|
||||
algorithm: algorithm === "None" ? "none" : algorithm
|
||||
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||
header: JSON.parse(header || "{}")
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.
|
||||
|
@ -22,7 +22,7 @@ class JWTVerify extends Operation {
|
||||
|
||||
this.name = "JWT Verify";
|
||||
this.module = "Crypto";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA.";
|
||||
this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.<br><br>The key should be either the secret for HMAC algorithms or the PEM-encoded public key for RSA and ECDSA.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||
this.inputType = "string";
|
||||
this.outputType = "JSON";
|
||||
|
884
src/core/operations/ParseTLSRecord.mjs
Normal file
884
src/core/operations/ParseTLSRecord.mjs
Normal file
@ -0,0 +1,884 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import {toHexFast} from "../lib/Hex.mjs";
|
||||
import {objToTable} from "../lib/Protocol.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Parse TLS record operation.
|
||||
*/
|
||||
class ParseTLSRecord extends Operation {
|
||||
|
||||
/**
|
||||
* ParseTLSRecord constructor.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse TLS record";
|
||||
this.module = "Default";
|
||||
this.description = "Parses one or more TLS records";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transport_Layer_Security";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "json";
|
||||
this.presentType = "html";
|
||||
this.args = [];
|
||||
this._handshakeParser = new HandshakeParser();
|
||||
this._contentTypes = new Map();
|
||||
|
||||
for (const key in ContentType) {
|
||||
this._contentTypes[ContentType[key]] = key.toString().toLocaleLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input - Stream, containing one or more raw TLS Records.
|
||||
* @param {Object[]} args
|
||||
* @returns {Object[]} Array of Object representations of TLS Records contained within input.
|
||||
*/
|
||||
run(input, args) {
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
|
||||
const output = [];
|
||||
|
||||
while (s.hasMore()) {
|
||||
const record = this._readRecord(s);
|
||||
if (record) {
|
||||
output.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a TLS Record from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw TLS Record.
|
||||
* @returns {Object} Object representation of TLS Record.
|
||||
*/
|
||||
_readRecord(input) {
|
||||
const RECORD_HEADER_LEN = 5;
|
||||
|
||||
if (input.position + RECORD_HEADER_LEN > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = input.readInt(1);
|
||||
const typeString = this._contentTypes[type] ?? type.toString();
|
||||
const version = "0x" + toHexFast(input.getBytes(2));
|
||||
const length = input.readInt(2);
|
||||
const content = input.getBytes(length);
|
||||
const truncated = content.length < length;
|
||||
|
||||
const recordHeader = new RecordHeader(typeString, version, length, truncated);
|
||||
|
||||
if (!content.length) {
|
||||
return {...recordHeader};
|
||||
}
|
||||
|
||||
if (type === ContentType.HANDSHAKE) {
|
||||
return this._handshakeParser.parse(new Stream(content), recordHeader);
|
||||
}
|
||||
|
||||
const record = {...recordHeader};
|
||||
record.value = "0x" + toHexFast(content);
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the parsed TLS Records in a tabular style.
|
||||
*
|
||||
* @param {Object[]} data - Array of Object representations of the TLS Records.
|
||||
* @returns {html} HTML representation of TLS Records contained within data.
|
||||
*/
|
||||
present(data) {
|
||||
return data.map(r => objToTable(r)).join("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
export default ParseTLSRecord;
|
||||
|
||||
/**
|
||||
* Repesents the known values of type field of a TLS Record header.
|
||||
*/
|
||||
const ContentType = Object.freeze({
|
||||
CHANGE_CIPHER_SPEC: 20,
|
||||
ALERT: 21,
|
||||
HANDSHAKE: 22,
|
||||
APPLICATION_DATA: 23,
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a TLS Record header
|
||||
*/
|
||||
class RecordHeader {
|
||||
/**
|
||||
* RecordHeader cosntructor.
|
||||
*
|
||||
* @param {string} type - String representation of TLS Record type field.
|
||||
* @param {string} version - Hex representation of TLS Record version field.
|
||||
* @param {int} length - Length of TLS Record.
|
||||
* @param {bool} truncated - Is TLS Record truncated.
|
||||
*/
|
||||
constructor(type, version, length, truncated) {
|
||||
this.type = type;
|
||||
this.version = version;
|
||||
this.length = length;
|
||||
|
||||
if (truncated) {
|
||||
this.truncated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake messages.
|
||||
*/
|
||||
class HandshakeParser {
|
||||
|
||||
/**
|
||||
* HandshakeParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._clientHelloParser = new ClientHelloParser();
|
||||
this._serverHelloParser = new ServerHelloParser();
|
||||
this._newSessionTicketParser = new NewSessionTicketParser();
|
||||
this._certificateParser = new CertificateParser();
|
||||
this._certificateRequestParser = new CertificateRequestParser();
|
||||
this._certificateVerifyParser = new CertificateVerifyParser();
|
||||
this._handshakeTypes = new Map();
|
||||
|
||||
for (const key in HandshakeType) {
|
||||
this._handshakeTypes[HandshakeType[key]] = key.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS handshake message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Handshake message.
|
||||
* @param {RecordHeader} recordHeader - TLS Record header.
|
||||
* @returns {Object} Object representation of Handshake.
|
||||
*/
|
||||
parse(input, recordHeader) {
|
||||
const output = {...recordHeader};
|
||||
|
||||
if (!input.hasMore()) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeType = input.readInt(1);
|
||||
output.handshakeType = this._handshakeTypes[handshakeType] ?? handshakeType.toString();
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const handshakeLength = input.readInt(3);
|
||||
|
||||
if (handshakeLength + 4 !== recordHeader.length) {
|
||||
input.moveTo(0);
|
||||
|
||||
output.handshakeType = this._handshakeTypes[HandshakeType.FINISHED];
|
||||
output.handshakeValue = "0x" + toHexFast(input.bytes);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const content = input.getBytes(handshakeLength);
|
||||
if (!content.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
switch (handshakeType) {
|
||||
case HandshakeType.CLIENT_HELLO:
|
||||
return {...output, ...this._clientHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.SERVER_HELLO:
|
||||
return {...output, ...this._serverHelloParser.parse(new Stream(content))};
|
||||
case HandshakeType.NEW_SESSION_TICKET:
|
||||
return {...output, ...this._newSessionTicketParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE:
|
||||
return {...output, ...this._certificateParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_REQUEST:
|
||||
return {...output, ...this._certificateRequestParser.parse(new Stream(content))};
|
||||
case HandshakeType.CERTIFICATE_VERIFY:
|
||||
return {...output, ...this._certificateVerifyParser.parse(new Stream(content))};
|
||||
default:
|
||||
output.handshakeValue = "0x" + toHexFast(content);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the known values of the msg_type field of a TLS Handshake message.
|
||||
*/
|
||||
const HandshakeType = Object.freeze({
|
||||
HELLO_REQUEST: 0,
|
||||
CLIENT_HELLO: 1,
|
||||
SERVER_HELLO: 2,
|
||||
NEW_SESSION_TICKET: 4,
|
||||
CERTIFICATE: 11,
|
||||
SERVER_KEY_EXCHANGE: 12,
|
||||
CERTIFICATE_REQUEST: 13,
|
||||
SERVER_HELLO_DONE: 14,
|
||||
CERTIFICATE_VERIFY: 15,
|
||||
CLIENT_KEY_EXCHANGE: 16,
|
||||
FINISHED: 20,
|
||||
});
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ClientHello messages.
|
||||
*/
|
||||
class ClientHelloParser {
|
||||
|
||||
/**
|
||||
* ClientHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ClientHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message.
|
||||
* @returns {Object} Object representation of ClientHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.clientVersion = this._readClientVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuites = this._readCipherSuites(input);
|
||||
output.compressionMethods = this._readCompressionMethods(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the client_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before client_version field.
|
||||
* @returns {string} Hex representation of client_version.
|
||||
*/
|
||||
_readClientVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suites field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before cipher_suites length field.
|
||||
* @returns {Object} Object represention of cipher_suites field.
|
||||
*/
|
||||
_readCipherSuites(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const cipherSuites = new Stream(input.getBytes(output.length));
|
||||
if (cipherSuites.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (cipherSuites.hasMore()) {
|
||||
const cipherSuite = readBytesAsHex(cipherSuites, 2);
|
||||
if (cipherSuite) {
|
||||
output.values.push(cipherSuite);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_methods field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before compression_methods length field.
|
||||
* @returns {Object} Object representation of compression_methods field.
|
||||
*/
|
||||
_readCompressionMethods(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const compressionMethods = new Stream(input.getBytes(output.length));
|
||||
if (compressionMethods.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (compressionMethods.hasMore()) {
|
||||
const compressionMethod = readBytesAsHex(compressionMethods, 1);
|
||||
if (compressionMethod) {
|
||||
output.values.push(compressionMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ClientHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representations of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake ServeHello messages.
|
||||
*/
|
||||
class ServerHelloParser {
|
||||
|
||||
/**
|
||||
* ServerHelloParser constructor.
|
||||
*/
|
||||
constructor() {
|
||||
this._extensionsParser = new ExtensionsParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake ServerHello message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message.
|
||||
* @return {Object} Object representation of ServerHello.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.serverVersion = this._readServerVersion(input);
|
||||
output.random = this._readRandom(input);
|
||||
|
||||
const sessionID = this._readSessionID(input);
|
||||
if (sessionID) {
|
||||
output.sessionID = sessionID;
|
||||
}
|
||||
|
||||
output.cipherSuite = this._readCipherSuite(input);
|
||||
output.compressionMethod = this._readCompressionMethod(input);
|
||||
output.extensions = this._readExtensions(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the server_version field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before server_version field.
|
||||
* @returns {string} Hex representation of server_version.
|
||||
*/
|
||||
_readServerVersion(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the random field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before random field.
|
||||
* @returns {string} Hex representation of random.
|
||||
*/
|
||||
_readRandom(input) {
|
||||
return readBytesAsHex(input, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session_id field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServertHello message, with position before session_id length field.
|
||||
* @returns {string} Hex representation of session_id, or empty string if session_id not present.
|
||||
*/
|
||||
_readSessionID(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the cipher_suite field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before cipher_suite field.
|
||||
* @returns {string} Hex represention of cipher_suite.
|
||||
*/
|
||||
_readCipherSuite(input) {
|
||||
return readBytesAsHex(input, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the compression_method field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before compression_method field.
|
||||
* @returns {string} Hex represention of compression_method.
|
||||
*/
|
||||
_readCompressionMethod(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the extensions field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw ServerHello message, with position before extensions length field.
|
||||
* @returns {Object} Object representation of extensions field.
|
||||
*/
|
||||
_readExtensions(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const extensions = new Stream(input.getBytes(output.length));
|
||||
if (extensions.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = this._extensionsParser.parse(extensions);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Hello Extensions.
|
||||
*/
|
||||
class ExtensionsParser {
|
||||
|
||||
/**
|
||||
* Parses a stream of TLS Handshake Hello Extensions.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing multiple raw Extensions, with position before first extension length field.
|
||||
* @returns {Object[]} Array of Object representations of Extensions contained within input.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = [];
|
||||
|
||||
while (input.hasMore()) {
|
||||
const extension = this._readExtension(input);
|
||||
if (extension) {
|
||||
output.push(extension);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single Extension from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of Extensions, with position before the length field of the next Extension.
|
||||
* @returns {Object} Object representation of Extension.
|
||||
*/
|
||||
_readExtension(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
output.type = "0x" + toHexFast(input.getBytes(2));
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const value = input.getBytes(output.length);
|
||||
if (!value || value.length !== output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
if (value && value.length) {
|
||||
output.value = "0x" + toHexFast(value);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake NewSessionTicket messages.
|
||||
*/
|
||||
class NewSessionTicketParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake NewSessionTicket message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message.
|
||||
* @returns {Object} Object representation of NewSessionTicket.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
ticketLifetimeHint: this._readTicketLifetimeHint(input),
|
||||
ticket: this._readTicket(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket_lifetime_hint field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket_lifetime_hint field.
|
||||
* @returns {string} Lifetime hint, in seconds.
|
||||
*/
|
||||
_readTicketLifetimeHint(input) {
|
||||
if (input.position + 4 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return "";
|
||||
}
|
||||
|
||||
return input.readInt(4) + "s";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the ticket field fromt the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw NewSessionTicket message, with position before ticket length field.
|
||||
* @returns {string} Hex representation of ticket.
|
||||
*/
|
||||
_readTicket(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake Certificate messages.
|
||||
*/
|
||||
class CertificateParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake Certificate message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message.
|
||||
* @returns {Object} Object representation of Certificate.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateList = this._readCertificateList(input);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_list field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw Certificate message, with position before certificate_list length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_list field.
|
||||
*/
|
||||
_readCertificateList(input) {
|
||||
const output = {};
|
||||
|
||||
if (input.position + 3 > input.length) {
|
||||
input.moveTo(input.length);
|
||||
return output;
|
||||
}
|
||||
|
||||
output.length = input.readInt(3);
|
||||
if (!output.length) {
|
||||
return output;
|
||||
}
|
||||
|
||||
const certificates = new Stream(input.getBytes(output.length));
|
||||
if (certificates.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificates.hasMore()) {
|
||||
const certificate = this._readCertificate(certificates);
|
||||
if (certificate) {
|
||||
output.values.push(certificate);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of certificicates, with position before the length field of the next certificate.
|
||||
* @returns {string} Hex representation of certificate.
|
||||
*/
|
||||
_readCertificate(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateRequest messages.
|
||||
*/
|
||||
class CertificateRequestParser {
|
||||
|
||||
/**
|
||||
* Parses a single TLS Handshake CertificateRequest message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message.
|
||||
* @return {Object} Object representation of CertificateRequest.
|
||||
*/
|
||||
parse(input) {
|
||||
const output = {};
|
||||
|
||||
output.certificateTypes = this._readCertificateTypes(input);
|
||||
output.supportedSignatureAlgorithms = this._readSupportedSignatureAlgorithms(input);
|
||||
|
||||
const certificateAuthorities = this._readCertificateAuthorities(input);
|
||||
if (certificateAuthorities.length) {
|
||||
output.certificateAuthorities = certificateAuthorities;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_types field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_types length field.
|
||||
* @return {string[]} Array of strings, each containing a hex representation of a value within the certificate_types field.
|
||||
*/
|
||||
_readCertificateTypes(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(1);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateTypes = new Stream(input.getBytes(output.length));
|
||||
if (certificateTypes.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateTypes.hasMore()) {
|
||||
const certificateType = readBytesAsHex(certificateTypes, 1);
|
||||
if (certificateType) {
|
||||
output.values.push(certificateType);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the supported_signature_algorithms field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before supported_signature_algorithms length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the supported_signature_algorithms field.
|
||||
*/
|
||||
_readSupportedSignatureAlgorithms(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const signatureAlgorithms = new Stream(input.getBytes(output.length));
|
||||
if (signatureAlgorithms.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (signatureAlgorithms.hasMore()) {
|
||||
const signatureAlgorithm = readBytesAsHex(signatureAlgorithms, 2);
|
||||
if (signatureAlgorithm) {
|
||||
output.values.push(signatureAlgorithm);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the certificate_authorities field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateRequest message, with position before certificate_authorities length field.
|
||||
* @returns {string[]} Array of strings, each containing a hex representation of a value within the certificate_authorities field.
|
||||
*/
|
||||
_readCertificateAuthorities(input) {
|
||||
const output = {};
|
||||
|
||||
output.length = input.readInt(2);
|
||||
if (!output.length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const certificateAuthorities = new Stream(input.getBytes(output.length));
|
||||
if (certificateAuthorities.length < output.length) {
|
||||
output.truncated = true;
|
||||
}
|
||||
|
||||
output.values = [];
|
||||
|
||||
while (certificateAuthorities.hasMore()) {
|
||||
const certificateAuthority = this._readCertificateAuthority(certificateAuthorities);
|
||||
if (certificateAuthority) {
|
||||
output.values.push(certificateAuthority);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single certificate authority from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a list of raw certificate authorities, with position before the length field of the next certificate authority.
|
||||
* @returns {string} Hex representation of certificate authority.
|
||||
*/
|
||||
_readCertificateAuthority(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses TLS Handshake CertificateVerify messages.
|
||||
*/
|
||||
class CertificateVerifyParser {
|
||||
|
||||
/**
|
||||
* Parses a single CertificateVerify Message.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message.
|
||||
* @returns {Object} Object representation of CertificateVerify.
|
||||
*/
|
||||
parse(input) {
|
||||
return {
|
||||
algorithmHash: this._readAlgorithmHash(input),
|
||||
algorithmSignature: this._readAlgorithmSignature(input),
|
||||
signature: this._readSignature(input),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.hash field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.hash field.
|
||||
* @return {string} Hex representation of hash algorithm.
|
||||
*/
|
||||
_readAlgorithmHash(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the algorithm.signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before algorithm.signature field.
|
||||
* @return {string} Hex representation of signature algorithm.
|
||||
*/
|
||||
_readAlgorithmSignature(input) {
|
||||
return readBytesAsHex(input, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the signature field from the following bytes in the provided Stream.
|
||||
*
|
||||
* @param {Stream} input - Stream, containing a raw CertificateVerify message, with position before signature field.
|
||||
* @return {string} Hex representation of signature.
|
||||
*/
|
||||
_readSignature(input) {
|
||||
return readSizePrefixedBytesAsHex(input, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the following size prefixed bytes from the provided Stream, and reuturn as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} sizePrefixLength - Length of the size prefix field.
|
||||
* @returns {string} Hex representation of bytes read from Stream, empty string is returned if
|
||||
* field cannot be read in full.
|
||||
*/
|
||||
function readSizePrefixedBytesAsHex(input, sizePrefixLength) {
|
||||
const length = input.readInt(sizePrefixLength);
|
||||
if (!length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return readBytesAsHex(input, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read n bytes from the provided Stream, and return as a hex string.
|
||||
*
|
||||
* @param {Stream} input - Stream to read from.
|
||||
* @param {int} n - Number of bytes to read.
|
||||
* @returns {string} Hex representation of bytes read from Stream, or empty string if field cannot
|
||||
* be read in full.
|
||||
*/
|
||||
function readBytesAsHex(input, n) {
|
||||
const bytes = input.getBytes(n);
|
||||
if (!bytes || bytes.length !== n) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "0x" + toHexFast(bytes);
|
||||
}
|
391
src/core/operations/ParseX509CRL.mjs
Normal file
391
src/core/operations/ParseX509CRL.mjs
Normal file
@ -0,0 +1,391 @@
|
||||
/**
|
||||
* @author robinsandhu
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import r from "jsrsasign";
|
||||
import Operation from "../Operation.mjs";
|
||||
import { fromBase64 } from "../lib/Base64.mjs";
|
||||
import { toHex } from "../lib/Hex.mjs";
|
||||
import { formatDnObj } from "../lib/PublicKey.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* Parse X.509 CRL operation
|
||||
*/
|
||||
class ParseX509CRL extends Operation {
|
||||
|
||||
/**
|
||||
* ParseX509CRL constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Parse X.509 CRL";
|
||||
this.module = "PublicKey";
|
||||
this.description = "Parse Certificate Revocation List (CRL)";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Certificate_revocation_list";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input format",
|
||||
"type": "option",
|
||||
"value": ["PEM", "DER Hex", "Base64", "Raw"]
|
||||
}
|
||||
];
|
||||
this.checks = [
|
||||
{
|
||||
"pattern": "^-+BEGIN X509 CRL-+\\r?\\n[\\da-z+/\\n\\r]+-+END X509 CRL-+\\r?\\n?$",
|
||||
"flags": "i",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string} Human-readable description of a Certificate Revocation List (CRL).
|
||||
*/
|
||||
run(input, args) {
|
||||
if (!input.length) {
|
||||
return "No input";
|
||||
}
|
||||
|
||||
const inputFormat = args[0];
|
||||
|
||||
let undefinedInputFormat = false;
|
||||
try {
|
||||
switch (inputFormat) {
|
||||
case "DER Hex":
|
||||
input = input.replace(/\s/g, "").toLowerCase();
|
||||
break;
|
||||
case "PEM":
|
||||
break;
|
||||
case "Base64":
|
||||
input = toHex(fromBase64(input, null, "byteArray"), "");
|
||||
break;
|
||||
case "Raw":
|
||||
input = toHex(Utils.strToArrayBuffer(input), "");
|
||||
break;
|
||||
default:
|
||||
undefinedInputFormat = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw "Certificate load error (non-certificate input?)";
|
||||
}
|
||||
if (undefinedInputFormat) throw "Undefined input format";
|
||||
|
||||
const crl = new r.X509CRL(input);
|
||||
|
||||
let out = `Certificate Revocation List (CRL):
|
||||
Version: ${crl.getVersion() === null ? "1 (0x0)" : "2 (0x1)"}
|
||||
Signature Algorithm: ${crl.getSignatureAlgorithmField()}
|
||||
Issuer:\n${formatDnObj(crl.getIssuer(), 8)}
|
||||
Last Update: ${generalizedDateTimeToUTC(crl.getThisUpdate())}
|
||||
Next Update: ${generalizedDateTimeToUTC(crl.getNextUpdate())}\n`;
|
||||
|
||||
if (crl.getParam().ext !== undefined) {
|
||||
out += `\tCRL extensions:\n${formatCRLExtensions(crl.getParam().ext, 8)}\n`;
|
||||
}
|
||||
|
||||
out += `Revoked Certificates:\n${formatRevokedCertificates(crl.getRevCertArray(), 4)}
|
||||
Signature Value:\n${formatCRLSignature(crl.getSignatureValueHex(), 8)}`;
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generalized date time string to UTC.
|
||||
* @param {string} datetime
|
||||
* @returns UTC datetime string.
|
||||
*/
|
||||
function generalizedDateTimeToUTC(datetime) {
|
||||
// Ensure the string is in the correct format
|
||||
if (!/^\d{12,14}Z$/.test(datetime)) {
|
||||
throw new OperationError(`failed to format datetime string ${datetime}`);
|
||||
}
|
||||
|
||||
// Extract components
|
||||
let centuary = "20";
|
||||
if (datetime.length === 15) {
|
||||
centuary = datetime.substring(0, 2);
|
||||
datetime = datetime.slice(2);
|
||||
}
|
||||
const year = centuary + datetime.substring(0, 2);
|
||||
const month = datetime.substring(2, 4);
|
||||
const day = datetime.substring(4, 6);
|
||||
const hour = datetime.substring(6, 8);
|
||||
const minute = datetime.substring(8, 10);
|
||||
const second = datetime.substring(10, 12);
|
||||
|
||||
// Construct ISO 8601 format string
|
||||
const isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`;
|
||||
|
||||
// Parse using standard Date object
|
||||
const isoDateTime = new Date(isoString);
|
||||
|
||||
return isoDateTime.toUTCString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL extensions.
|
||||
* @param {r.ExtParam[] | undefined} extensions
|
||||
* @param {Number} indent
|
||||
* @returns Formatted string detailing CRL extensions.
|
||||
*/
|
||||
function formatCRLExtensions(extensions, indent) {
|
||||
if (Array.isArray(extensions) === false || extensions.length === 0) {
|
||||
return indentString(`No CRL extensions.`, indent);
|
||||
}
|
||||
|
||||
let out = ``;
|
||||
|
||||
extensions.sort((a, b) => {
|
||||
if (!Object.hasOwn(a, "extname") || !Object.hasOwn(b, "extname")) {
|
||||
return 0;
|
||||
}
|
||||
if (a.extname < b.extname) {
|
||||
return -1;
|
||||
} else if (a.extname === b.extname) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
extensions.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "authorityKeyIdentifier":
|
||||
out += `X509v3 Authority Key Identifier:\n`;
|
||||
if (Object.hasOwn(ext, "kid")) {
|
||||
out += `\tkeyid:${colonDelimitedHexFormatString(ext.kid.hex.toUpperCase())}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "issuer")) {
|
||||
out += `\tDirName:${ext.issuer.str}\n`;
|
||||
}
|
||||
if (Object.hasOwn(ext, "sn")) {
|
||||
out += `\tserial:${colonDelimitedHexFormatString(ext.sn.hex.toUpperCase())}\n`;
|
||||
}
|
||||
break;
|
||||
case "cRLDistributionPoints":
|
||||
out += `X509v3 CRL Distribution Points:\n`;
|
||||
ext.array.forEach((distPoint) => {
|
||||
const fullName = `Full Name:\n${formatGeneralNames(distPoint.dpname.full, 4)}`;
|
||||
out += indentString(fullName, 4) + "\n";
|
||||
});
|
||||
break;
|
||||
case "cRLNumber":
|
||||
if (!Object.hasOwn(ext, "num")) {
|
||||
throw new OperationError(`'cRLNumber' CRL entry extension missing 'num' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Number:\n\t${ext.num.hex.toUpperCase()}\n`;
|
||||
break;
|
||||
case "issuerAltName":
|
||||
out += `X509v3 Issuer Alternative Name:\n${formatGeneralNames(ext.array, 4)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format general names array.
|
||||
* @param {Object[]} names
|
||||
* @returns Multi-line formatted string describing all supported general name types.
|
||||
*/
|
||||
function formatGeneralNames(names, indent) {
|
||||
let out = ``;
|
||||
|
||||
names.forEach((name) => {
|
||||
const key = Object.keys(name)[0];
|
||||
|
||||
switch (key) {
|
||||
case "ip":
|
||||
out += `IP:${name.ip}\n`;
|
||||
break;
|
||||
case "dns":
|
||||
out += `DNS:${name.dns}\n`;
|
||||
break;
|
||||
case "uri":
|
||||
out += `URI:${name.uri}\n`;
|
||||
break;
|
||||
case "rfc822":
|
||||
out += `EMAIL:${name.rfc822}\n`;
|
||||
break;
|
||||
case "dn":
|
||||
out += `DIR:${name.dn.str}\n`;
|
||||
break;
|
||||
case "other":
|
||||
out += `OtherName:${name.other.oid}::${Object.values(name.other.value)[0].str}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${key}: unsupported general name type`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colon-delimited hex formatted output.
|
||||
* @param {string} hexString Hex String
|
||||
* @returns String representing input hex string with colon delimiter.
|
||||
*/
|
||||
function colonDelimitedHexFormatString(hexString) {
|
||||
if (hexString.length % 2 !== 0) {
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
|
||||
return chop(hexString.replace(/(..)/g, "$&:"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format revoked certificates array
|
||||
* @param {r.RevokedCertificate[] | null} revokedCertificates
|
||||
* @param {Number} indent
|
||||
* @returns Multi-line formatted string output of revoked certificates array
|
||||
*/
|
||||
function formatRevokedCertificates(revokedCertificates, indent) {
|
||||
if (Array.isArray(revokedCertificates) === false || revokedCertificates.length === 0) {
|
||||
return indentString("No Revoked Certificates.", indent);
|
||||
}
|
||||
|
||||
let out=``;
|
||||
|
||||
revokedCertificates.forEach((revCert) => {
|
||||
if (!Object.hasOwn(revCert, "sn") || !Object.hasOwn(revCert, "date")) {
|
||||
throw new OperationError("invalid revoked certificate object, missing either serial number or date");
|
||||
}
|
||||
|
||||
out += `Serial Number: ${revCert.sn.hex.toUpperCase()}
|
||||
Revocation Date: ${generalizedDateTimeToUTC(revCert.date)}\n`;
|
||||
if (Object.hasOwn(revCert, "ext") && Array.isArray(revCert.ext) && revCert.ext.length !== 0) {
|
||||
out += `\tCRL entry extensions:\n${indentString(formatCRLEntryExtensions(revCert.ext), 2*indent)}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return indentString(chop(out), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL entry extensions.
|
||||
* @param {Object[]} exts
|
||||
* @returns Formatted multi-line string describing CRL entry extensions.
|
||||
*/
|
||||
function formatCRLEntryExtensions(exts) {
|
||||
let out = ``;
|
||||
|
||||
const crlReasonCodeToReasonMessage = {
|
||||
0: "Unspecified",
|
||||
1: "Key Compromise",
|
||||
2: "CA Compromise",
|
||||
3: "Affiliation Changed",
|
||||
4: "Superseded",
|
||||
5: "Cessation Of Operation",
|
||||
6: "Certificate Hold",
|
||||
8: "Remove From CRL",
|
||||
9: "Privilege Withdrawn",
|
||||
10: "AA Compromise",
|
||||
};
|
||||
|
||||
const holdInstructionOIDToName = {
|
||||
"1.2.840.10040.2.1": "Hold Instruction None",
|
||||
"1.2.840.10040.2.2": "Hold Instruction Call Issuer",
|
||||
"1.2.840.10040.2.3": "Hold Instruction Reject",
|
||||
};
|
||||
|
||||
exts.forEach((ext) => {
|
||||
if (!Object.hasOwn(ext, "extname")) {
|
||||
throw new OperationError(`CRL entry extension object missing 'extname' key: ${ext}`);
|
||||
}
|
||||
switch (ext.extname) {
|
||||
case "cRLReason":
|
||||
if (!Object.hasOwn(ext, "code")) {
|
||||
throw new OperationError(`'cRLReason' CRL entry extension missing 'code' key: ${ext}`);
|
||||
}
|
||||
out += `X509v3 CRL Reason Code:
|
||||
${Object.hasOwn(crlReasonCodeToReasonMessage, ext.code) ? crlReasonCodeToReasonMessage[ext.code] : `invalid reason code: ${ext.code}`}\n`;
|
||||
break;
|
||||
case "2.5.29.23": // Hold instruction
|
||||
out += `Hold Instruction Code:\n\t${Object.hasOwn(holdInstructionOIDToName, ext.extn.oid) ? holdInstructionOIDToName[ext.extn.oid] : `${ext.extn.oid}: unknown hold instruction OID`}\n`;
|
||||
break;
|
||||
case "2.5.29.24": // Invalidity Date
|
||||
out += `Invalidity Date:\n\t${generalizedDateTimeToUTC(ext.extn.gentime.str)}\n`;
|
||||
break;
|
||||
default:
|
||||
out += `${ext.extname}:\n`;
|
||||
out += `\tUnsupported CRL entry extension. Try openssl CLI.\n`;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return chop(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CRL signature.
|
||||
* @param {String} sigHex
|
||||
* @param {Number} indent
|
||||
* @returns String representing hex signature value formatted on multiple lines.
|
||||
*/
|
||||
function formatCRLSignature(sigHex, indent) {
|
||||
if (sigHex.length % 2 !== 0) {
|
||||
sigHex = "0" + sigHex;
|
||||
}
|
||||
|
||||
return indentString(formatMultiLine(chop(sigHex.replace(/(..)/g, "$&:"))), indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format string onto multiple lines.
|
||||
* @param {string} longStr
|
||||
* @returns String as a multi-line string.
|
||||
*/
|
||||
function formatMultiLine(longStr) {
|
||||
const lines = [];
|
||||
|
||||
for (let remain = longStr ; remain !== "" ; remain = remain.substring(54)) {
|
||||
lines.push(remain.substring(0, 54));
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Indent a multi-line string by n spaces.
|
||||
* @param {string} input String
|
||||
* @param {number} spaces How many leading spaces
|
||||
* @returns Indented string.
|
||||
*/
|
||||
function indentString(input, spaces) {
|
||||
const indent = " ".repeat(spaces);
|
||||
return input.replace(/^/gm, indent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove last character from a string.
|
||||
* @param {string} s String
|
||||
* @returns Chopped string.
|
||||
*/
|
||||
function chop(s) {
|
||||
if (s.length < 1) {
|
||||
return s;
|
||||
}
|
||||
return s.substring(0, s.length - 1);
|
||||
}
|
||||
|
||||
export default ParseX509CRL;
|
@ -60,7 +60,7 @@ class RSASign extends Operation {
|
||||
const privateKey = forge.pki.decryptRsaPrivateKey(key, password);
|
||||
// Generate message hash
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(input, "utf8");
|
||||
md.update(input, "raw");
|
||||
// Sign message hash
|
||||
const sig = privateKey.sign(md);
|
||||
return sig;
|
||||
|
@ -8,6 +8,7 @@ import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import forge from "node-forge";
|
||||
import { MD_ALGORITHMS } from "../lib/RSA.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
|
||||
/**
|
||||
* RSA Verify operation
|
||||
@ -37,6 +38,11 @@ class RSAVerify extends Operation {
|
||||
type: "text",
|
||||
value: ""
|
||||
},
|
||||
{
|
||||
name: "Message format",
|
||||
type: "option",
|
||||
value: ["Raw", "Hex", "Base64"]
|
||||
},
|
||||
{
|
||||
name: "Message Digest Algorithm",
|
||||
type: "option",
|
||||
@ -51,7 +57,7 @@ class RSAVerify extends Operation {
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [pemKey, message, mdAlgo] = args;
|
||||
const [pemKey, message, format, mdAlgo] = args;
|
||||
if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) {
|
||||
throw new OperationError("Please enter a public key.");
|
||||
}
|
||||
@ -60,7 +66,8 @@ class RSAVerify extends Operation {
|
||||
const pubKey = forge.pki.publicKeyFromPem(pemKey);
|
||||
// Generate message digest
|
||||
const md = MD_ALGORITHMS[mdAlgo].create();
|
||||
md.update(message, "utf8");
|
||||
const messageStr = Utils.convertToByteString(message, format);
|
||||
md.update(messageStr, "raw");
|
||||
// Compare signed message digest and generated message digest
|
||||
const result = pubKey.verify(md.digest().bytes(), input);
|
||||
return result ? "Verified OK" : "Verification Failure";
|
||||
|
57
src/core/operations/StripIPv4Header.mjs
Normal file
57
src/core/operations/StripIPv4Header.mjs
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Strip IPv4 header operation
|
||||
*/
|
||||
class StripIPv4Header extends Operation {
|
||||
|
||||
/**
|
||||
* StripIPv4Header constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip IPv4 header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the IPv4 header from an IPv4 packet, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/IPv4";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MIN_HEADER_LEN = 20;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < MIN_HEADER_LEN) {
|
||||
throw new OperationError("Input length is less than minimum IPv4 header length");
|
||||
}
|
||||
|
||||
const ihl = s.readInt(1) & 0x0f;
|
||||
const dataOffsetBytes = ihl * 4;
|
||||
if (s.length < dataOffsetBytes) {
|
||||
throw new OperationError("Input length is less than IHL");
|
||||
}
|
||||
|
||||
s.moveTo(dataOffsetBytes);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripIPv4Header;
|
60
src/core/operations/StripTCPHeader.mjs
Normal file
60
src/core/operations/StripTCPHeader.mjs
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
|
||||
/**
|
||||
* Strip TCP header operation
|
||||
*/
|
||||
class StripTCPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripTCPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip TCP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the TCP header from a TCP segment, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const MIN_HEADER_LEN = 20;
|
||||
const DATA_OFFSET_OFFSET = 12;
|
||||
const DATA_OFFSET_LEN_BITS = 4;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < MIN_HEADER_LEN) {
|
||||
throw new OperationError("Need at least 20 bytes for a TCP Header");
|
||||
}
|
||||
|
||||
s.moveTo(DATA_OFFSET_OFFSET);
|
||||
const dataOffsetWords = s.readBits(DATA_OFFSET_LEN_BITS);
|
||||
const dataOffsetBytes = dataOffsetWords * 4;
|
||||
if (s.length < dataOffsetBytes) {
|
||||
throw new OperationError("Input length is less than data offset");
|
||||
}
|
||||
|
||||
s.moveTo(dataOffsetBytes);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripTCPHeader;
|
51
src/core/operations/StripUDPHeader.mjs
Normal file
51
src/core/operations/StripUDPHeader.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Stream from "../lib/Stream.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
|
||||
/**
|
||||
* Strip UDP header operation
|
||||
*/
|
||||
class StripUDPHeader extends Operation {
|
||||
|
||||
/**
|
||||
* StripUDPHeader constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Strip UDP header";
|
||||
this.module = "Default";
|
||||
this.description = "Strips the UDP header from a UDP datagram, outputting the payload.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
|
||||
this.inputType = "ArrayBuffer";
|
||||
this.outputType = "ArrayBuffer";
|
||||
this.args = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer} input
|
||||
* @param {Object[]} args
|
||||
* @returns {ArrayBuffer}
|
||||
*/
|
||||
run(input, args) {
|
||||
const HEADER_LEN = 8;
|
||||
|
||||
const s = new Stream(new Uint8Array(input));
|
||||
if (s.length < HEADER_LEN) {
|
||||
throw new OperationError("Need 8 bytes for a UDP Header");
|
||||
}
|
||||
|
||||
s.moveTo(HEADER_LEN);
|
||||
|
||||
return s.getBytes().buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StripUDPHeader;
|
@ -23,7 +23,7 @@ const dir = path.join(`${process.cwd()}/src/node`);
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log("\nCWD: " + process.cwd());
|
||||
console.log("Error: generateNodeIndex.mjs should be run from the project root");
|
||||
console.log("Example> node --experimental-modules src/core/config/scripts/generateNodeIndex.mjs");
|
||||
console.log("Example> node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
@ -675,42 +675,42 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
"Loading from URL": browser => {
|
||||
utils.clear(browser);
|
||||
// "Loading from URL": browser => {
|
||||
// utils.clear(browser);
|
||||
|
||||
/* Side panel displays correct info */
|
||||
utils.uploadFile(browser, "files/TowelDay.jpeg");
|
||||
// /* 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");
|
||||
// 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")
|
||||
.waitForElementVisible("#rec-list li.operation");
|
||||
// /* 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")
|
||||
// .waitForElementVisible("#rec-list li.operation");
|
||||
|
||||
browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/);
|
||||
browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66");
|
||||
browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
|
||||
// browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/);
|
||||
// browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66");
|
||||
// browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
|
||||
|
||||
browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic");
|
||||
browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE");
|
||||
// browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic");
|
||||
// browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE");
|
||||
|
||||
browser.expect.element("#input-text .eol-value").text.that.equals("FF");
|
||||
browser.expect.element("#output-text .eol-value").text.that.equals("PS");
|
||||
// browser.expect.element("#input-text .eol-value").text.that.equals("FF");
|
||||
// browser.expect.element("#output-text .eol-value").text.that.equals("PS");
|
||||
|
||||
utils.bake(browser);
|
||||
// utils.bake(browser);
|
||||
|
||||
browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/);
|
||||
browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44");
|
||||
browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1");
|
||||
},
|
||||
// browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/);
|
||||
// browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44");
|
||||
// browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1");
|
||||
// },
|
||||
|
||||
"Replace input with output": browser => {
|
||||
/* Input is correctly populated */
|
||||
|
@ -116,6 +116,7 @@ import "./tests/ParseObjectIDTimestamp.mjs";
|
||||
import "./tests/ParseQRCode.mjs";
|
||||
import "./tests/ParseSSHHostKey.mjs";
|
||||
import "./tests/ParseTCP.mjs";
|
||||
import "./tests/ParseTLSRecord.mjs";
|
||||
import "./tests/ParseTLV.mjs";
|
||||
import "./tests/ParseUDP.mjs";
|
||||
import "./tests/PEMtoHex.mjs";
|
||||
@ -143,6 +144,9 @@ import "./tests/SIGABA.mjs";
|
||||
import "./tests/SM4.mjs";
|
||||
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
||||
import "./tests/StrUtils.mjs";
|
||||
import "./tests/StripIPv4Header.mjs";
|
||||
import "./tests/StripTCPHeader.mjs";
|
||||
import "./tests/StripUDPHeader.mjs";
|
||||
import "./tests/Subsection.mjs";
|
||||
import "./tests/SwapCase.mjs";
|
||||
import "./tests/SymmetricDifference.mjs";
|
||||
|
@ -44,7 +44,18 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [hsKey, "HS256"],
|
||||
args: [hsKey, "HS256", "{}"],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "JWT Sign: HS256 with custom header",
|
||||
input: inputObject,
|
||||
expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImN1c3RvbS5rZXkifQ.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.kXln8btJburfRlND8IDZAQ8NZGFFZhvHyooHa6N9za8",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [hsKey, "HS256", `{"kid":"custom.key"}`],
|
||||
}
|
||||
],
|
||||
},
|
||||
@ -55,7 +66,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [hsKey, "HS384"],
|
||||
args: [hsKey, "HS384", "{}"],
|
||||
}
|
||||
],
|
||||
},
|
||||
@ -66,7 +77,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [hsKey, "HS512"],
|
||||
args: [hsKey, "HS512", "{}"],
|
||||
}
|
||||
],
|
||||
},
|
||||
@ -77,7 +88,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [esKey, "ES256"],
|
||||
args: [esKey, "ES256", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
@ -92,7 +103,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [esKey, "ES384"],
|
||||
args: [esKey, "ES384", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
@ -107,7 +118,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [esKey, "ES512"],
|
||||
args: [esKey, "ES512", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
@ -122,7 +133,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [rsKey, "RS256"],
|
||||
args: [rsKey, "RS256", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
@ -137,7 +148,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [rsKey, "RS384"],
|
||||
args: [rsKey, "RS384", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
@ -152,7 +163,7 @@ TestRegister.addTests([
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "JWT Sign",
|
||||
args: [esKey, "RS512"],
|
||||
args: [esKey, "RS512", "{}"],
|
||||
},
|
||||
{
|
||||
op: "JWT Decode",
|
||||
|
2045
tests/operations/tests/ParseTLSRecord.mjs
Normal file
2045
tests/operations/tests/ParseTLSRecord.mjs
Normal file
File diff suppressed because one or more lines are too long
331
tests/operations/tests/ParseX509CRL.mjs
Normal file
331
tests/operations/tests/ParseX509CRL.mjs
Normal file
@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Parse X.509 CRL tests.
|
||||
*
|
||||
* @author robinsandhu
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
const IN_CRL_PEM_RSA = `-----BEGIN X509 CRL-----
|
||||
MIID7jCCAdYCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV
|
||||
BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN
|
||||
MjQwODI1MTE0OTEwWhcNMjQwOTI0MTE0OTEwWjA1MDMCAhAAFw0yNDA4MjUwMzIz
|
||||
MDhaMB4wCgYDVR0VBAMKAQYwEAYDVR0XBAkGByqGSM44AgOgggEnMIIBIzAJBgNV
|
||||
HRIEAjAAMH0GA1UdIwR2MHSAFLjJrf2oUFTVhW40i0xgL7BJtodGoUakRDBCMQsw
|
||||
CQYDVQQGEwJVSzEPMA0GA1UECAwGTG9uZG9uMQswCQYDVQQKDAJCQjEVMBMGA1UE
|
||||
AwwMVGVzdCBSb290IENBghQ3XUv2vXwRfMxGGv/XLywm+B5LPTAtBgNVHS4EJjAk
|
||||
MCKgIKAehhxodHRwOi8vZXhhbXBsZS5jb20vZGVsdGEtY3JsMFsGA1UdHwRUMFIw
|
||||
IaAfoB2GG2h0dHA6Ly9leGFtcGxlLmNvbS9mdWxsLWNybDAhoB+gHYYbbGRhcDov
|
||||
L2V4YW1wbGUuY29tL2Z1bGwtY3JsMAqgCKAGhwR/AAABMAsGA1UdFAQEAgIePDAN
|
||||
BgkqhkiG9w0BAQsFAAOCAgEAAxsr+9nELUVWhFekwy6GsqH8xOf6EqGjRaEdX49W
|
||||
mB40m2VajOkK8UHGoVyZzoDI2r/c8OPXUtbpK0fpvEl3SZU5j/C8JbZaZFFrEGeH
|
||||
fSEqdVHFjohpawNcG41Qs+YT21TBqH1hD5yVI7gjVvfKICRfxDpl5oGClxBCVOSV
|
||||
gVtLbe9q44uCBJ1kUkoc9Vz47Hv7JyckgqVXkORWHt2SFNALxlMEzOEQTpuC5Kcb
|
||||
4i7hTCUF+kpkIvr02LJImq0Aaqzs6cC/DcdJiRPPyfaN8fQryFv76gg9i8zZcb6c
|
||||
W42rvumiyw+7nnZfmq53webr5fCHaXhZk47ASOJD6GC5cX9rje1qGRgULXRhqcvK
|
||||
n319s2iXj3FStDDorKGgsCV2zYmotX17ExB98CcCgBE52zMtRZilwhOGeh8mx3qT
|
||||
l0W2B8uKKAq5BMmiziSBzQt700JPiruURZXbQ1fH1n7pKP6wGEh2e9TfQMlN20hE
|
||||
I+CMt+1bG0Bpt5AfiwE8UykQ/WvpVxdJrgj0JM0yA37KfC8XD+cmavJ5/grorbj3
|
||||
t0zBdK7bl+Y45VU/5/mX5ZR3O3ea1RclPM3hKMREfPneOlpan6r3dVwFqEN/TeTu
|
||||
46vuDeKaEr3yJkOFfy0lSYPhPhzhU5vDR5ibxqvwxZNznI2AdTnZLEf8LRqnTVo1
|
||||
qx0=
|
||||
-----END X509 CRL-----`;
|
||||
|
||||
const OUT_CRL_PEM_RSA = `Certificate Revocation List (CRL):
|
||||
Version: 2 (0x1)
|
||||
Signature Algorithm: SHA256withRSA
|
||||
Issuer:
|
||||
C = UK
|
||||
ST = London
|
||||
O = BB
|
||||
CN = Test Root CA
|
||||
Last Update: Sun, 25 Aug 2024 11:49:10 GMT
|
||||
Next Update: Tue, 24 Sep 2024 11:49:10 GMT
|
||||
CRL extensions:
|
||||
2.5.29.46:
|
||||
Unsupported CRL extension. Try openssl CLI.
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46
|
||||
DirName:/C=UK/ST=London/O=BB/CN=Test Root CA
|
||||
serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D
|
||||
X509v3 CRL Distribution Points:
|
||||
Full Name:
|
||||
URI:http://example.com/full-crl
|
||||
Full Name:
|
||||
URI:ldap://example.com/full-crl
|
||||
Full Name:
|
||||
IP:127.0.0.1
|
||||
X509v3 CRL Number:
|
||||
1E3C
|
||||
issuerAltName:
|
||||
Unsupported CRL extension. Try openssl CLI.
|
||||
Revoked Certificates:
|
||||
Serial Number: 1000
|
||||
Revocation Date: Sun, 25 Aug 2024 03:23:08 GMT
|
||||
CRL entry extensions:
|
||||
X509v3 CRL Reason Code:
|
||||
Certificate Hold
|
||||
Hold Instruction Code:
|
||||
Hold Instruction Reject
|
||||
Signature Value:
|
||||
03:1b:2b:fb:d9:c4:2d:45:56:84:57:a4:c3:2e:86:b2:a1:fc:
|
||||
c4:e7:fa:12:a1:a3:45:a1:1d:5f:8f:56:98:1e:34:9b:65:5a:
|
||||
8c:e9:0a:f1:41:c6:a1:5c:99:ce:80:c8:da:bf:dc:f0:e3:d7:
|
||||
52:d6:e9:2b:47:e9:bc:49:77:49:95:39:8f:f0:bc:25:b6:5a:
|
||||
64:51:6b:10:67:87:7d:21:2a:75:51:c5:8e:88:69:6b:03:5c:
|
||||
1b:8d:50:b3:e6:13:db:54:c1:a8:7d:61:0f:9c:95:23:b8:23:
|
||||
56:f7:ca:20:24:5f:c4:3a:65:e6:81:82:97:10:42:54:e4:95:
|
||||
81:5b:4b:6d:ef:6a:e3:8b:82:04:9d:64:52:4a:1c:f5:5c:f8:
|
||||
ec:7b:fb:27:27:24:82:a5:57:90:e4:56:1e:dd:92:14:d0:0b:
|
||||
c6:53:04:cc:e1:10:4e:9b:82:e4:a7:1b:e2:2e:e1:4c:25:05:
|
||||
fa:4a:64:22:fa:f4:d8:b2:48:9a:ad:00:6a:ac:ec:e9:c0:bf:
|
||||
0d:c7:49:89:13:cf:c9:f6:8d:f1:f4:2b:c8:5b:fb:ea:08:3d:
|
||||
8b:cc:d9:71:be:9c:5b:8d:ab:be:e9:a2:cb:0f:bb:9e:76:5f:
|
||||
9a:ae:77:c1:e6:eb:e5:f0:87:69:78:59:93:8e:c0:48:e2:43:
|
||||
e8:60:b9:71:7f:6b:8d:ed:6a:19:18:14:2d:74:61:a9:cb:ca:
|
||||
9f:7d:7d:b3:68:97:8f:71:52:b4:30:e8:ac:a1:a0:b0:25:76:
|
||||
cd:89:a8:b5:7d:7b:13:10:7d:f0:27:02:80:11:39:db:33:2d:
|
||||
45:98:a5:c2:13:86:7a:1f:26:c7:7a:93:97:45:b6:07:cb:8a:
|
||||
28:0a:b9:04:c9:a2:ce:24:81:cd:0b:7b:d3:42:4f:8a:bb:94:
|
||||
45:95:db:43:57:c7:d6:7e:e9:28:fe:b0:18:48:76:7b:d4:df:
|
||||
40:c9:4d:db:48:44:23:e0:8c:b7:ed:5b:1b:40:69:b7:90:1f:
|
||||
8b:01:3c:53:29:10:fd:6b:e9:57:17:49:ae:08:f4:24:cd:32:
|
||||
03:7e:ca:7c:2f:17:0f:e7:26:6a:f2:79:fe:0a:e8:ad:b8:f7:
|
||||
b7:4c:c1:74:ae:db:97:e6:38:e5:55:3f:e7:f9:97:e5:94:77:
|
||||
3b:77:9a:d5:17:25:3c:cd:e1:28:c4:44:7c:f9:de:3a:5a:5a:
|
||||
9f:aa:f7:75:5c:05:a8:43:7f:4d:e4:ee:e3:ab:ee:0d:e2:9a:
|
||||
12:bd:f2:26:43:85:7f:2d:25:49:83:e1:3e:1c:e1:53:9b:c3:
|
||||
47:98:9b:c6:ab:f0:c5:93:73:9c:8d:80:75:39:d9:2c:47:fc:
|
||||
2d:1a:a7:4d:5a:35:ab:1d`;
|
||||
|
||||
const IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `-----BEGIN X509 CRL-----
|
||||
MIID9jCCAd4CAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV
|
||||
BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN
|
||||
MjQwODI1MTIwODU2WhcNMjQwOTI0MTIwODU2WjA9MDsCAhAAFw0yNDA4MjUxMjA4
|
||||
NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAScw
|
||||
ggEjMAkGA1UdEgQCMAAwfQYDVR0jBHYwdIAUuMmt/ahQVNWFbjSLTGAvsEm2h0ah
|
||||
RqREMEIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQIDAZMb25kb24xCzAJBgNVBAoMAkJC
|
||||
MRUwEwYDVQQDDAxUZXN0IFJvb3QgQ0GCFDddS/a9fBF8zEYa/9cvLCb4Hks9MC0G
|
||||
A1UdLgQmMCQwIqAgoB6GHGh0dHA6Ly9leGFtcGxlLmNvbS9kZWx0YS1jcmwwWwYD
|
||||
VR0fBFQwUjAhoB+gHYYbaHR0cDovL2V4YW1wbGUuY29tL2Z1bGwtY3JsMCGgH6Ad
|
||||
hhtsZGFwOi8vZXhhbXBsZS5jb20vZnVsbC1jcmwwCqAIoAaHBH8AAAEwCwYDVR0U
|
||||
BAQCAh49MA0GCSqGSIb3DQEBCwUAA4ICAQByLp7JWQmB1NhlLACH6zFOe31yCTVy
|
||||
xJQtgujtSri1LNu6IwzBGsKBQIl3ucwMxPvoZzlujNLmshUT3nSogV0/5n1q0Gyj
|
||||
5Yiz2iw8mmKJLmGZ9Oz3QoGxgFww0/0x/VwRHuS2hw+A7JB8tO/2nW3oTclvS55l
|
||||
R+VtkDjUN58+Yl2SQksvb3qD6bHHJTCaP7Dskls0fdBIoYIDvZejrTYSSzTX/Kw4
|
||||
735P0GBMhj7zVF8azGz2PFpSISg4huJMyp7EDKZf2c2dnkuwmEUlPQEBLX25j/Il
|
||||
81OxfVVFja+wUagaGtjEPGy5gsU8zFwkWhjaD5PGBbZvnT+EDsOtJPU7Ot/sBHfz
|
||||
XqUtMrfmz/S/GsQ+QCpnBvarBy9QYuk9M0ePBGy33CUQpjPULxuJJVAHxNoetHCv
|
||||
7udng2Pi4D8vDNfzbMwHt7HurMo0CsSju+cL4rnIfsz02RrD9WC84KxBLWkqC7Hi
|
||||
IKGIpF740Yc4BliVE1HDaOKyI6FEft5asj3OgXwmBw7pVlxSNWACaA2vOFkdN/V5
|
||||
XZZjVJdRJxkgEfCvsJVenFp8ND6gmJmWum7tqM5ytmiXjPtejsPpVq4IclG+Yhnr
|
||||
tFQ9TDEuCrNsRIGGGDodyXq1+kGXY0w8RqGEb7J4Og/M6r4LMAKPkO7e0nEibTqX
|
||||
d2igvR2e5p+yKw==
|
||||
-----END X509 CRL-----`;
|
||||
|
||||
const OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE = `Certificate Revocation List (CRL):
|
||||
Version: 2 (0x1)
|
||||
Signature Algorithm: SHA256withRSA
|
||||
Issuer:
|
||||
C = UK
|
||||
ST = London
|
||||
O = BB
|
||||
CN = Test Root CA
|
||||
Last Update: Sun, 25 Aug 2024 12:08:56 GMT
|
||||
Next Update: Tue, 24 Sep 2024 12:08:56 GMT
|
||||
CRL extensions:
|
||||
2.5.29.46:
|
||||
Unsupported CRL extension. Try openssl CLI.
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46
|
||||
DirName:/C=UK/ST=London/O=BB/CN=Test Root CA
|
||||
serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D
|
||||
X509v3 CRL Distribution Points:
|
||||
Full Name:
|
||||
URI:http://example.com/full-crl
|
||||
Full Name:
|
||||
URI:ldap://example.com/full-crl
|
||||
Full Name:
|
||||
IP:127.0.0.1
|
||||
X509v3 CRL Number:
|
||||
1E3D
|
||||
issuerAltName:
|
||||
Unsupported CRL extension. Try openssl CLI.
|
||||
Revoked Certificates:
|
||||
Serial Number: 1000
|
||||
Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT
|
||||
CRL entry extensions:
|
||||
X509v3 CRL Reason Code:
|
||||
Key Compromise
|
||||
Invalidity Date:
|
||||
Sun, 25 Aug 2024 00:00:00 GMT
|
||||
Signature Value:
|
||||
72:2e:9e:c9:59:09:81:d4:d8:65:2c:00:87:eb:31:4e:7b:7d:
|
||||
72:09:35:72:c4:94:2d:82:e8:ed:4a:b8:b5:2c:db:ba:23:0c:
|
||||
c1:1a:c2:81:40:89:77:b9:cc:0c:c4:fb:e8:67:39:6e:8c:d2:
|
||||
e6:b2:15:13:de:74:a8:81:5d:3f:e6:7d:6a:d0:6c:a3:e5:88:
|
||||
b3:da:2c:3c:9a:62:89:2e:61:99:f4:ec:f7:42:81:b1:80:5c:
|
||||
30:d3:fd:31:fd:5c:11:1e:e4:b6:87:0f:80:ec:90:7c:b4:ef:
|
||||
f6:9d:6d:e8:4d:c9:6f:4b:9e:65:47:e5:6d:90:38:d4:37:9f:
|
||||
3e:62:5d:92:42:4b:2f:6f:7a:83:e9:b1:c7:25:30:9a:3f:b0:
|
||||
ec:92:5b:34:7d:d0:48:a1:82:03:bd:97:a3:ad:36:12:4b:34:
|
||||
d7:fc:ac:38:ef:7e:4f:d0:60:4c:86:3e:f3:54:5f:1a:cc:6c:
|
||||
f6:3c:5a:52:21:28:38:86:e2:4c:ca:9e:c4:0c:a6:5f:d9:cd:
|
||||
9d:9e:4b:b0:98:45:25:3d:01:01:2d:7d:b9:8f:f2:25:f3:53:
|
||||
b1:7d:55:45:8d:af:b0:51:a8:1a:1a:d8:c4:3c:6c:b9:82:c5:
|
||||
3c:cc:5c:24:5a:18:da:0f:93:c6:05:b6:6f:9d:3f:84:0e:c3:
|
||||
ad:24:f5:3b:3a:df:ec:04:77:f3:5e:a5:2d:32:b7:e6:cf:f4:
|
||||
bf:1a:c4:3e:40:2a:67:06:f6:ab:07:2f:50:62:e9:3d:33:47:
|
||||
8f:04:6c:b7:dc:25:10:a6:33:d4:2f:1b:89:25:50:07:c4:da:
|
||||
1e:b4:70:af:ee:e7:67:83:63:e2:e0:3f:2f:0c:d7:f3:6c:cc:
|
||||
07:b7:b1:ee:ac:ca:34:0a:c4:a3:bb:e7:0b:e2:b9:c8:7e:cc:
|
||||
f4:d9:1a:c3:f5:60:bc:e0:ac:41:2d:69:2a:0b:b1:e2:20:a1:
|
||||
88:a4:5e:f8:d1:87:38:06:58:95:13:51:c3:68:e2:b2:23:a1:
|
||||
44:7e:de:5a:b2:3d:ce:81:7c:26:07:0e:e9:56:5c:52:35:60:
|
||||
02:68:0d:af:38:59:1d:37:f5:79:5d:96:63:54:97:51:27:19:
|
||||
20:11:f0:af:b0:95:5e:9c:5a:7c:34:3e:a0:98:99:96:ba:6e:
|
||||
ed:a8:ce:72:b6:68:97:8c:fb:5e:8e:c3:e9:56:ae:08:72:51:
|
||||
be:62:19:eb:b4:54:3d:4c:31:2e:0a:b3:6c:44:81:86:18:3a:
|
||||
1d:c9:7a:b5:fa:41:97:63:4c:3c:46:a1:84:6f:b2:78:3a:0f:
|
||||
cc:ea:be:0b:30:02:8f:90:ee:de:d2:71:22:6d:3a:97:77:68:
|
||||
a0:bd:1d:9e:e6:9f:b2:2b`;
|
||||
|
||||
const IN_CRL_PEM_RSA_CRL_EXTENSIONS = `-----BEGIN X509 CRL-----
|
||||
MIIE0DCCArgCAQEwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVUsxDzANBgNV
|
||||
BAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBDQRcN
|
||||
MjQwODI1MTIzNzEwWhcNMjQwOTI0MTIzNzEwWjA9MDsCAhAAFw0yNDA4MjUxMjA4
|
||||
NDhaMCYwCgYDVR0VBAMKAQEwGAYDVR0YBBEYDzIwMjQwODI1MDAwMDAwWqCCAgEw
|
||||
ggH9MIHiBgNVHRIEgdowgdegFAYEKgMEBaAMFgpDdXN0b21OYW1lgQ5jYUBleGFt
|
||||
cGxlLmNvbYYSaHR0cDovL2V4YW1wbGUuY29tgg5jYS5leGFtcGxlLmNvbYcEwKgB
|
||||
AaSBhDCBgTELMAkGA1UEBhMCVVMxFTATBgNVBAgMDEV4YW1wbGVTdGF0ZTEUMBIG
|
||||
A1UEBwwLRXhhbXBsZUNpdHkxEzARBgNVBAoMCkV4YW1wbGVPcmcxFDASBgNVBAsM
|
||||
C0V4YW1wbGVVbml0MRowGAYDVQQDDBFFeGFtcGxlQ29tbW9uTmFtZTB9BgNVHSME
|
||||
djB0gBS4ya39qFBU1YVuNItMYC+wSbaHRqFGpEQwQjELMAkGA1UEBhMCVUsxDzAN
|
||||
BgNVBAgMBkxvbmRvbjELMAkGA1UECgwCQkIxFTATBgNVBAMMDFRlc3QgUm9vdCBD
|
||||
QYIUN11L9r18EXzMRhr/1y8sJvgeSz0wLQYDVR0uBCYwJDAioCCgHoYcaHR0cDov
|
||||
L2V4YW1wbGUuY29tL2RlbHRhLWNybDBbBgNVHR8EVDBSMCGgH6AdhhtodHRwOi8v
|
||||
ZXhhbXBsZS5jb20vZnVsbC1jcmwwIaAfoB2GG2xkYXA6Ly9leGFtcGxlLmNvbS9m
|
||||
dWxsLWNybDAKoAigBocEfwAAATALBgNVHRQEBAICHkIwDQYJKoZIhvcNAQELBQAD
|
||||
ggIBAF/9L4aGmId2igw7+MfDxokevIJkJX/MkmHpXBl1b4hL85FGD7OPCmn47VzC
|
||||
Wejlc/AQB7mWyUugvrVEq/FiCO8a8Fieyjw5uCYz0eiNnuvHVRGM2mOEkiA0I/rn
|
||||
F5AFB1YfCFGXPyRkXNRbOBE91mhOzh1H9PX2qVnj5l3KsPE/7YuteacR0TkfkRJa
|
||||
BXLic+5F/CCV/J/iYR7LncuLUlhBfsosG/ucHL70EytlfX6CBWY3kBbmj7nd497T
|
||||
QG392+m9xp7MIsJAS+3qEzwJAfni6zUV0fWh/ucOl8BIjHEh97VqI3+8yzhdXfkF
|
||||
2gkfpkqJQY0+5OO1VSRYTlQNld3QjN/VVJjatfHyaXfPCx4VEKW1kWYo+0zxO4SL
|
||||
SB/+S/o99bCeNy1MXqEvy5HoDwFHePXGsAEPHWPdj7EWm7g9T/Fl1iSR6hpohvDD
|
||||
K4LaGdVhzvCraLIh8H7XW3KztvZvDQejYQAgADW0UO0rFHJ1XXhKYSqXNGnfDt+3
|
||||
cRpt2XxSxt5HJtHlatiI25PuBMNWV2Zod4RHB/8UEvs1KC7dcwkAiCEY+E3o/zkC
|
||||
rdZ/8XtNf5a4WSN/D7pPsfsO6SE+7lxkJ+UQcZLXAz8b5ArPTlWt2HdJIBEVs25K
|
||||
FAkizyldhnAcNHFk7XN94eTLNeD6hUbFL9pNHiSmKu5A9YW0
|
||||
-----END X509 CRL-----`;
|
||||
|
||||
const OUT_CRL_PEM_RSA_CRL_EXTENSIONS = `Certificate Revocation List (CRL):
|
||||
Version: 2 (0x1)
|
||||
Signature Algorithm: SHA256withRSA
|
||||
Issuer:
|
||||
C = UK
|
||||
ST = London
|
||||
O = BB
|
||||
CN = Test Root CA
|
||||
Last Update: Sun, 25 Aug 2024 12:37:10 GMT
|
||||
Next Update: Tue, 24 Sep 2024 12:37:10 GMT
|
||||
CRL extensions:
|
||||
2.5.29.46:
|
||||
Unsupported CRL extension. Try openssl CLI.
|
||||
X509v3 Authority Key Identifier:
|
||||
keyid:B8:C9:AD:FD:A8:50:54:D5:85:6E:34:8B:4C:60:2F:B0:49:B6:87:46
|
||||
DirName:/C=UK/ST=London/O=BB/CN=Test Root CA
|
||||
serial:37:5D:4B:F6:BD:7C:11:7C:CC:46:1A:FF:D7:2F:2C:26:F8:1E:4B:3D
|
||||
X509v3 CRL Distribution Points:
|
||||
Full Name:
|
||||
URI:http://example.com/full-crl
|
||||
Full Name:
|
||||
URI:ldap://example.com/full-crl
|
||||
Full Name:
|
||||
IP:127.0.0.1
|
||||
X509v3 CRL Number:
|
||||
1E42
|
||||
X509v3 Issuer Alternative Name:
|
||||
OtherName:1.2.3.4.5::CustomName
|
||||
EMAIL:ca@example.com
|
||||
URI:http://example.com
|
||||
DNS:ca.example.com
|
||||
IP:192.168.1.1
|
||||
DIR:/C=US/ST=ExampleState/L=ExampleCity/O=ExampleOrg/OU=ExampleUnit/CN=ExampleCommonName
|
||||
Revoked Certificates:
|
||||
Serial Number: 1000
|
||||
Revocation Date: Sun, 25 Aug 2024 12:08:48 GMT
|
||||
CRL entry extensions:
|
||||
X509v3 CRL Reason Code:
|
||||
Key Compromise
|
||||
Invalidity Date:
|
||||
Sun, 25 Aug 2024 00:00:00 GMT
|
||||
Signature Value:
|
||||
5f:fd:2f:86:86:98:87:76:8a:0c:3b:f8:c7:c3:c6:89:1e:bc:
|
||||
82:64:25:7f:cc:92:61:e9:5c:19:75:6f:88:4b:f3:91:46:0f:
|
||||
b3:8f:0a:69:f8:ed:5c:c2:59:e8:e5:73:f0:10:07:b9:96:c9:
|
||||
4b:a0:be:b5:44:ab:f1:62:08:ef:1a:f0:58:9e:ca:3c:39:b8:
|
||||
26:33:d1:e8:8d:9e:eb:c7:55:11:8c:da:63:84:92:20:34:23:
|
||||
fa:e7:17:90:05:07:56:1f:08:51:97:3f:24:64:5c:d4:5b:38:
|
||||
11:3d:d6:68:4e:ce:1d:47:f4:f5:f6:a9:59:e3:e6:5d:ca:b0:
|
||||
f1:3f:ed:8b:ad:79:a7:11:d1:39:1f:91:12:5a:05:72:e2:73:
|
||||
ee:45:fc:20:95:fc:9f:e2:61:1e:cb:9d:cb:8b:52:58:41:7e:
|
||||
ca:2c:1b:fb:9c:1c:be:f4:13:2b:65:7d:7e:82:05:66:37:90:
|
||||
16:e6:8f:b9:dd:e3:de:d3:40:6d:fd:db:e9:bd:c6:9e:cc:22:
|
||||
c2:40:4b:ed:ea:13:3c:09:01:f9:e2:eb:35:15:d1:f5:a1:fe:
|
||||
e7:0e:97:c0:48:8c:71:21:f7:b5:6a:23:7f:bc:cb:38:5d:5d:
|
||||
f9:05:da:09:1f:a6:4a:89:41:8d:3e:e4:e3:b5:55:24:58:4e:
|
||||
54:0d:95:dd:d0:8c:df:d5:54:98:da:b5:f1:f2:69:77:cf:0b:
|
||||
1e:15:10:a5:b5:91:66:28:fb:4c:f1:3b:84:8b:48:1f:fe:4b:
|
||||
fa:3d:f5:b0:9e:37:2d:4c:5e:a1:2f:cb:91:e8:0f:01:47:78:
|
||||
f5:c6:b0:01:0f:1d:63:dd:8f:b1:16:9b:b8:3d:4f:f1:65:d6:
|
||||
24:91:ea:1a:68:86:f0:c3:2b:82:da:19:d5:61:ce:f0:ab:68:
|
||||
b2:21:f0:7e:d7:5b:72:b3:b6:f6:6f:0d:07:a3:61:00:20:00:
|
||||
35:b4:50:ed:2b:14:72:75:5d:78:4a:61:2a:97:34:69:df:0e:
|
||||
df:b7:71:1a:6d:d9:7c:52:c6:de:47:26:d1:e5:6a:d8:88:db:
|
||||
93:ee:04:c3:56:57:66:68:77:84:47:07:ff:14:12:fb:35:28:
|
||||
2e:dd:73:09:00:88:21:18:f8:4d:e8:ff:39:02:ad:d6:7f:f1:
|
||||
7b:4d:7f:96:b8:59:23:7f:0f:ba:4f:b1:fb:0e:e9:21:3e:ee:
|
||||
5c:64:27:e5:10:71:92:d7:03:3f:1b:e4:0a:cf:4e:55:ad:d8:
|
||||
77:49:20:11:15:b3:6e:4a:14:09:22:cf:29:5d:86:70:1c:34:
|
||||
71:64:ed:73:7d:e1:e4:cb:35:e0:fa:85:46:c5:2f:da:4d:1e:
|
||||
24:a6:2a:ee:40:f5:85:b4`;
|
||||
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature",
|
||||
input: IN_CRL_PEM_RSA,
|
||||
expectedOutput: OUT_CRL_PEM_RSA,
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse X.509 CRL",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature, CRL Reason and Invalidity Date",
|
||||
input: IN_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE,
|
||||
expectedOutput: OUT_CRL_PEM_RSA_CRL_REASON_AND_INVALIDITY_DATE,
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse X.509 CRL",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Parse X.509 CRL: Example PEM encoded CRL with RSA signature and CRL Extensions",
|
||||
input: IN_CRL_PEM_RSA_CRL_EXTENSIONS,
|
||||
expectedOutput: OUT_CRL_PEM_RSA_CRL_EXTENSIONS,
|
||||
recipeConfig: [
|
||||
{
|
||||
"op": "Parse X.509 CRL",
|
||||
"args": ["PEM"]
|
||||
}
|
||||
]
|
||||
},
|
||||
]);
|
126
tests/operations/tests/StripIPv4Header.mjs
Normal file
126
tests/operations/tests/StripIPv4Header.mjs
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Strip IPv4 header tests.
|
||||
*
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Strip IPv4 header: No options, No payload",
|
||||
input: "450000140005400080060000c0a80001c0a80002",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip IPv4 header: No options, Payload",
|
||||
input: "450000140005400080060000c0a80001c0a80002ffffffffffffffff",
|
||||
expectedOutput: "ffffffffffffffff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip IPv4 header: Options, No payload",
|
||||
input: "460000140005400080060000c0a80001c0a8000207000000",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip IPv4 header: Options, Payload",
|
||||
input: "460000140005400080060000c0a80001c0a8000207000000ffffffffffffffff",
|
||||
expectedOutput: "ffffffffffffffff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip IPv4 header: Input length lesss than minimum header length",
|
||||
input: "450000140005400080060000c0a80001c0a800",
|
||||
expectedOutput: "Input length is less than minimum IPv4 header length",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip IPv4 header: Input length less than IHL",
|
||||
input: "460000140005400080060000c0a80001c0a80000",
|
||||
expectedOutput: "Input length is less than IHL",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip IPv4 header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
126
tests/operations/tests/StripTCPHeader.mjs
Normal file
126
tests/operations/tests/StripTCPHeader.mjs
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Strip TCP header tests.
|
||||
*
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Strip TCP header: No options, No payload",
|
||||
input: "7f900050000fa4b2000cb2a45010bff100000000",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip TCP header: No options, Payload",
|
||||
input: "7f900050000fa4b2000cb2a45010bff100000000ffffffffffffffff",
|
||||
expectedOutput: "ffffffffffffffff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip TCP header: Options, No payload",
|
||||
input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip TCP header: Options, Payload",
|
||||
input: "7f900050000fa4b2000cb2a47010bff100000000020405b404020000ffffffffffffffff",
|
||||
expectedOutput: "ffffffffffffffff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip TCP header: Input length less than minimum header length",
|
||||
input: "7f900050000fa4b2000cb2a45010bff1000000",
|
||||
expectedOutput: "Need at least 20 bytes for a TCP Header",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip TCP header: Input length less than data offset",
|
||||
input: "7f900050000fa4b2000cb2a47010bff100000000",
|
||||
expectedOutput: "Input length is less than data offset",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip TCP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
69
tests/operations/tests/StripUDPHeader.mjs
Normal file
69
tests/operations/tests/StripUDPHeader.mjs
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Strip UDP header tests.
|
||||
*
|
||||
* @author c65722 []
|
||||
* @copyright Crown Copyright 2024
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "Strip UDP header: No payload",
|
||||
input: "8111003500000000",
|
||||
expectedOutput: "",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip UDP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip UDP header: Payload",
|
||||
input: "8111003500080000ffffffffffffffff",
|
||||
expectedOutput: "ffffffffffffffff",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip UDP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Strip UDP header: Input length less than header length",
|
||||
input: "81110035000000",
|
||||
expectedOutput: "Need 8 bytes for a UDP Header",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "From Hex",
|
||||
args: ["None"]
|
||||
},
|
||||
{
|
||||
op: "Strip UDP header",
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
@ -1,8 +1,10 @@
|
||||
const webpack = require("webpack");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { ModifySourcePlugin, ReplaceOperation } = require("modify-source-webpack-plugin");
|
||||
const path = require("path");
|
||||
const zlib = require("zlib");
|
||||
|
||||
/**
|
||||
* Webpack configuration details for use with Grunt.
|
||||
@ -64,6 +66,21 @@ module.exports = {
|
||||
new MiniCssExtractPlugin({
|
||||
filename: "assets/[name].css"
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
filename: "[path][base].gz",
|
||||
algorithm: "gzip",
|
||||
test: /\.(js|css|html)$/,
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
filename: "[path][base].br",
|
||||
algorithm: "brotliCompress",
|
||||
test: /\.(js|css|html)$/,
|
||||
compressionOptions: {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: 11,
|
||||
},
|
||||
},
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user