Merge branch 'master' into master

This commit is contained in:
a3957273 2025-02-11 00:46:18 +00:00 committed by GitHub
commit 41c5403700
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 4039 additions and 13 deletions

View file

@ -19,6 +19,7 @@ jobs:
- name: Install
run: |
export DETECT_CHROMEDRIVER_VERSION=true
npm install
npm run setheapsize

View file

@ -18,6 +18,7 @@ jobs:
- name: Install
run: |
export DETECT_CHROMEDRIVER_VERSION=true
npm install
npm run setheapsize

22
package-lock.json generated
View file

@ -118,6 +118,7 @@
"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",
@ -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,

View file

@ -58,6 +58,7 @@
"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",

View file

@ -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",
@ -235,7 +236,10 @@
"Parse IPv6 address",
"Parse IPv4 header",
"Parse TCP",
"Strip TCP header",
"Parse TLS record",
"Parse UDP",
"Strip UDP header",
"Parse SSH Host Key",
"Parse URI",
"URL Encode",

View file

@ -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>`;

View file

@ -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.

View file

@ -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";

View 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);
}

View 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;

View 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;

View 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;

View file

@ -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);
}

View file

@ -115,6 +115,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";
@ -142,6 +143,8 @@ 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/StripTCPHeader.mjs";
import "./tests/StripUDPHeader.mjs";
import "./tests/Subsection.mjs";
import "./tests/SwapCase.mjs";
import "./tests/SymmetricDifference.mjs";

View file

@ -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",

File diff suppressed because one or more lines are too long

View 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"]
}
]
},
]);

View 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]
}
]
}
]);

View 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]
}
]
}
]);

View file

@ -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: [
{