mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-20 14:56:19 -04:00
Merge branch 'master' into feature/change-to-nginx-unprivileged
This commit is contained in:
commit
d5a245647a
41 changed files with 10782 additions and 3354 deletions
1
.github/workflows/master.yml
vendored
1
.github/workflows/master.yml
vendored
|
@ -19,6 +19,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
export DETECT_CHROMEDRIVER_VERSION=true
|
||||||
npm install
|
npm install
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
|
|
1
.github/workflows/pull_requests.yml
vendored
1
.github/workflows/pull_requests.yml
vendored
|
@ -18,6 +18,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
export DETECT_CHROMEDRIVER_VERSION=true
|
||||||
npm install
|
npm install
|
||||||
npm run setheapsize
|
npm run setheapsize
|
||||||
|
|
||||||
|
|
8428
package-lock.json
generated
8428
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -58,6 +58,7 @@
|
||||||
"chromedriver": "^130.0.0",
|
"chromedriver": "^130.0.0",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.37.1",
|
||||||
"css-loader": "7.1.2",
|
"css-loader": "7.1.2",
|
||||||
|
@ -159,7 +160,7 @@
|
||||||
"notepack.io": "^3.0.1",
|
"notepack.io": "^3.0.1",
|
||||||
"ntlm": "^0.1.3",
|
"ntlm": "^0.1.3",
|
||||||
"nwmatcher": "^1.4.4",
|
"nwmatcher": "^1.4.4",
|
||||||
"otp": "0.1.3",
|
"otpauth": "9.3.6",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
|
|
|
@ -164,6 +164,7 @@
|
||||||
"name": "Public Key",
|
"name": "Public Key",
|
||||||
"ops": [
|
"ops": [
|
||||||
"Parse X.509 certificate",
|
"Parse X.509 certificate",
|
||||||
|
"Parse X.509 CRL",
|
||||||
"Parse ASN.1 hex string",
|
"Parse ASN.1 hex string",
|
||||||
"PEM to Hex",
|
"PEM to Hex",
|
||||||
"Hex to PEM",
|
"Hex to PEM",
|
||||||
|
@ -233,9 +234,14 @@
|
||||||
"Parse User Agent",
|
"Parse User Agent",
|
||||||
"Parse IP range",
|
"Parse IP range",
|
||||||
"Parse IPv6 address",
|
"Parse IPv6 address",
|
||||||
|
"IPv6 Transition Addresses",
|
||||||
"Parse IPv4 header",
|
"Parse IPv4 header",
|
||||||
|
"Strip IPv4 header",
|
||||||
"Parse TCP",
|
"Parse TCP",
|
||||||
|
"Strip TCP header",
|
||||||
|
"Parse TLS record",
|
||||||
"Parse UDP",
|
"Parse UDP",
|
||||||
|
"Strip UDP header",
|
||||||
"Parse SSH Host Key",
|
"Parse SSH Host Key",
|
||||||
"Parse URI",
|
"Parse URI",
|
||||||
"URL Encode",
|
"URL Encode",
|
||||||
|
@ -268,7 +274,8 @@
|
||||||
"Unicode Text Format",
|
"Unicode Text Format",
|
||||||
"Remove Diacritics",
|
"Remove Diacritics",
|
||||||
"Unescape Unicode Characters",
|
"Unescape Unicode Characters",
|
||||||
"Convert to NATO alphabet"
|
"Convert to NATO alphabet",
|
||||||
|
"Convert Leet Speak"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -320,7 +327,9 @@
|
||||||
"Unescape string",
|
"Unescape string",
|
||||||
"Pseudo-Random Number Generator",
|
"Pseudo-Random Number Generator",
|
||||||
"Sleep",
|
"Sleep",
|
||||||
"File Tree"
|
"File Tree",
|
||||||
|
"Take nth bytes",
|
||||||
|
"Drop nth bytes"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,6 +26,9 @@ export function objToTable(obj, nested=false) {
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
|
if (typeof obj[key] === "function")
|
||||||
|
continue;
|
||||||
|
|
||||||
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||||
if (typeof obj[key] === "object")
|
if (typeof obj[key] === "object")
|
||||||
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
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.description = "Adds line numbers to the output.";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "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) {
|
run(input, args) {
|
||||||
const lines = input.split("\n"),
|
const lines = input.split("\n"),
|
||||||
width = lines.length.toString().length;
|
width = lines.length.toString().length;
|
||||||
|
const offset = args[0] ? parseInt(args[0], 10) : 0;
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (let n = 0; n < lines.length; n++) {
|
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);
|
return output.slice(0, output.length-1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,8 @@ class BlowfishDecrypt extends Operation {
|
||||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iv.length !== 8) {
|
if (mode !== "ECB" && iv.length !== 8) {
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
|
@ -76,8 +76,8 @@ class BlowfishEncrypt extends Operation {
|
||||||
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iv.length !== 8) {
|
if (mode !== "ECB" && iv.length !== 8) {
|
||||||
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`);
|
throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
input = Utils.convertToByteString(input, inputType);
|
input = Utils.convertToByteString(input, inputType);
|
||||||
|
|
113
src/core/operations/ConvertLeetSpeak.mjs
Normal file
113
src/core/operations/ConvertLeetSpeak.mjs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/**
|
||||||
|
* @author bartblaze []
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Leet Speak operation
|
||||||
|
*/
|
||||||
|
class ConvertLeetSpeak extends Operation {
|
||||||
|
/**
|
||||||
|
* ConvertLeetSpeak constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Convert Leet Speak";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts to and from Leet Speak";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Leet";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Direction",
|
||||||
|
type: "option",
|
||||||
|
value: ["To Leet Speak", "From Leet Speak"],
|
||||||
|
defaultIndex: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const direction = args[0];
|
||||||
|
if (direction === "To Leet Speak") {
|
||||||
|
return input.replace(/[abcdefghijklmnopqrstuvwxyz]/gi, char => {
|
||||||
|
return toLeetMap[char.toLowerCase()] || char;
|
||||||
|
});
|
||||||
|
} else if (direction === "From Leet Speak") {
|
||||||
|
return input.replace(/[48cd3f6h1jklmn0pqr57uvwxyz]/g, char => {
|
||||||
|
return fromLeetMap[char] || char;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toLeetMap = {
|
||||||
|
"a": "4",
|
||||||
|
"b": "b",
|
||||||
|
"c": "c",
|
||||||
|
"d": "d",
|
||||||
|
"e": "3",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"i": "1",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"m": "m",
|
||||||
|
"n": "n",
|
||||||
|
"o": "0",
|
||||||
|
"p": "p",
|
||||||
|
"q": "q",
|
||||||
|
"r": "r",
|
||||||
|
"s": "5",
|
||||||
|
"t": "7",
|
||||||
|
"u": "u",
|
||||||
|
"v": "v",
|
||||||
|
"w": "w",
|
||||||
|
"x": "x",
|
||||||
|
"y": "y",
|
||||||
|
"z": "z"
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromLeetMap = {
|
||||||
|
"4": "a",
|
||||||
|
"b": "b",
|
||||||
|
"c": "c",
|
||||||
|
"d": "d",
|
||||||
|
"3": "e",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"1": "i",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"m": "m",
|
||||||
|
"n": "n",
|
||||||
|
"0": "o",
|
||||||
|
"p": "p",
|
||||||
|
"q": "q",
|
||||||
|
"r": "r",
|
||||||
|
"5": "s",
|
||||||
|
"7": "t",
|
||||||
|
"u": "u",
|
||||||
|
"v": "v",
|
||||||
|
"w": "w",
|
||||||
|
"x": "x",
|
||||||
|
"y": "y",
|
||||||
|
"z": "z"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConvertLeetSpeak;
|
||||||
|
|
79
src/core/operations/DropNthBytes.mjs
Normal file
79
src/core/operations/DropNthBytes.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* @author Oshawk [oshawk@protonmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop nth bytes operation
|
||||||
|
*/
|
||||||
|
class DropNthBytes extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DropNthBytes constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Drop nth bytes";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Drops every nth byte starting with a given byte.";
|
||||||
|
this.infoURL = "";
|
||||||
|
this.inputType = "byteArray";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Drop every",
|
||||||
|
type: "number",
|
||||||
|
value: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Starting at",
|
||||||
|
type: "number",
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Apply to each line",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {byteArray} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const n = args[0];
|
||||||
|
const start = args[1];
|
||||||
|
const eachLine = args[2];
|
||||||
|
|
||||||
|
if (parseInt(n, 10) !== n || n <= 0) {
|
||||||
|
throw new OperationError("'Drop every' must be a positive integer.");
|
||||||
|
}
|
||||||
|
if (parseInt(start, 10) !== start || start < 0) {
|
||||||
|
throw new OperationError("'Starting at' must be a positive or zero integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
const output = [];
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
if (eachLine && input[i] === 0x0a) {
|
||||||
|
output.push(0x0a);
|
||||||
|
offset = i + 1;
|
||||||
|
} else if (i - offset < start || (i - (start + offset)) % n !== 0) {
|
||||||
|
output.push(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DropNthBytes;
|
|
@ -5,16 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import otp from "otp";
|
import * as OTPAuth from "otpauth";
|
||||||
import ToBase32 from "./ToBase32.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate HOTP operation
|
* Generate HOTP operation
|
||||||
*/
|
*/
|
||||||
class GenerateHOTP extends Operation {
|
class GenerateHOTP extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateHOTP constructor
|
*
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -31,11 +29,6 @@ class GenerateHOTP extends Operation {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Key size",
|
|
||||||
"type": "number",
|
|
||||||
"value": 32
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -50,21 +43,26 @@ class GenerateHOTP extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
*
|
||||||
* @param {Object[]} args
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const otpObj = otp({
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
name: args[0],
|
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||||
keySize: args[1],
|
|
||||||
codeLength: args[2],
|
|
||||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
|
||||||
});
|
|
||||||
const counter = args[3];
|
|
||||||
return `URI: ${otpObj.hotpURL}\n\nPassword: ${otpObj.hotp(counter)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const hotp = new OTPAuth.HOTP({
|
||||||
|
issuer: "",
|
||||||
|
label: args[0],
|
||||||
|
algorithm: "SHA1",
|
||||||
|
digits: args[1],
|
||||||
|
counter: args[2],
|
||||||
|
secret: OTPAuth.Secret.fromBase32(secret)
|
||||||
|
});
|
||||||
|
|
||||||
|
const uri = hotp.toString();
|
||||||
|
const code = hotp.generate();
|
||||||
|
|
||||||
|
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateHOTP;
|
export default GenerateHOTP;
|
||||||
|
|
|
@ -5,20 +5,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import otp from "otp";
|
import * as OTPAuth from "otpauth";
|
||||||
import ToBase32 from "./ToBase32.mjs";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate TOTP operation
|
* Generate TOTP operation
|
||||||
*/
|
*/
|
||||||
class GenerateTOTP extends Operation {
|
class GenerateTOTP extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenerateTOTP constructor
|
*
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.name = "Generate TOTP";
|
this.name = "Generate TOTP";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
this.description = "The Time-based One-Time Password algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It has been adopted as Internet Engineering Task Force standard RFC 6238, is the cornerstone of Initiative For Open Authentication (OAUTH), and is used in a number of two-factor authentication systems. A TOTP is an HOTP where the counter is the current time.<br><br>Enter the secret as the input or leave it blank for a random secret to be generated. T0 and T1 are in seconds.";
|
||||||
|
@ -31,11 +28,6 @@ class GenerateTOTP extends Operation {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Key size",
|
|
||||||
"type": "number",
|
|
||||||
"value": 32
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Code length",
|
"name": "Code length",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -55,22 +47,27 @@ class GenerateTOTP extends Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
*
|
||||||
* @param {Object[]} args
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const otpObj = otp({
|
const secretStr = new TextDecoder("utf-8").decode(input).trim();
|
||||||
name: args[0],
|
const secret = secretStr ? secretStr.toUpperCase().replace(/\s+/g, "") : "";
|
||||||
keySize: args[1],
|
|
||||||
codeLength: args[2],
|
|
||||||
secret: (new ToBase32).run(input, []).split("=")[0],
|
|
||||||
epoch: args[3],
|
|
||||||
timeSlice: args[4]
|
|
||||||
});
|
|
||||||
return `URI: ${otpObj.totpURL}\n\nPassword: ${otpObj.totp()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const totp = new OTPAuth.TOTP({
|
||||||
|
issuer: "",
|
||||||
|
label: args[0],
|
||||||
|
algorithm: "SHA1",
|
||||||
|
digits: args[1],
|
||||||
|
period: args[3],
|
||||||
|
epoch: args[2] * 1000, // Convert seconds to milliseconds
|
||||||
|
secret: OTPAuth.Secret.fromBase32(secret)
|
||||||
|
});
|
||||||
|
|
||||||
|
const uri = totp.toString();
|
||||||
|
const code = totp.generate();
|
||||||
|
|
||||||
|
return `URI: ${uri}\n\nPassword: ${code}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GenerateTOTP;
|
export default GenerateTOTP;
|
||||||
|
|
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
209
src/core/operations/IPv6TransitionAddresses.mjs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* @author jb30795 [jb30795@proton.me]
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6 Transition Addresses operation
|
||||||
|
*/
|
||||||
|
class IPv6TransitionAddresses extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPv6TransitionAddresses constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "IPv6 Transition Addresses";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Converts IPv4 addresses to their IPv6 Transition addresses. IPv6 Transition addresses can also be converted back into their original IPv4 address. MAC addresses can also be converted into the EUI-64 format, this can them be appended to your IPv6 /64 range to obtain a full /128 address.<br><br>Transition technologies enable translation between IPv4 and IPv6 addresses or tunneling to allow traffic to pass through the incompatible network, allowing the two standards to coexist.<br><br>Only /24 ranges and currently handled. Remove headers to easily copy out results.";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/IPv6_transition_mechanism";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "string";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Ignore ranges",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Remove headers",
|
||||||
|
"type": "boolean",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const XOR = {"0": "2", "1": "3", "2": "0", "3": "1", "4": "6", "5": "7", "6": "4", "7": "5", "8": "a", "9": "b", "a": "8", "b": "9", "c": "e", "d": "f", "e": "c", "f": "d"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to convert to hex
|
||||||
|
*/
|
||||||
|
function hexify(octet) {
|
||||||
|
return Number(octet).toString(16).padStart(2, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to convert Hex to Int
|
||||||
|
*/
|
||||||
|
function intify(hex) {
|
||||||
|
return parseInt(hex, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function converts IPv4 to IPv6 Transtion address
|
||||||
|
*/
|
||||||
|
function ipTransition(input, range) {
|
||||||
|
let output = "";
|
||||||
|
const HEXIP = input.split(".");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6to4
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "6to4: ";
|
||||||
|
}
|
||||||
|
output += "2002:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00::/40\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "::/48\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapped
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4 Mapped: ";
|
||||||
|
}
|
||||||
|
output += "::ffff:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translated
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4 Translated: ";
|
||||||
|
}
|
||||||
|
output += "::ffff:0:" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nat64
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "Nat 64: ";
|
||||||
|
}
|
||||||
|
output += "64:ff9b::" + hexify(HEXIP[0]) + hexify(HEXIP[1]) + ":" + hexify(HEXIP[2]);
|
||||||
|
if (range) {
|
||||||
|
output += "00/120\n";
|
||||||
|
} else {
|
||||||
|
output += hexify(HEXIP[3]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MAC to EUI-64
|
||||||
|
*/
|
||||||
|
function macTransition(input) {
|
||||||
|
let output = "";
|
||||||
|
const MACPARTS = input.split(":");
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "EUI-64 Interface ID: ";
|
||||||
|
}
|
||||||
|
const MAC = MACPARTS[0] + MACPARTS[1] + ":" + MACPARTS[2] + "ff:fe" + MACPARTS[3] + ":" + MACPARTS[4] + MACPARTS[5];
|
||||||
|
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert IPv6 address to its original IPv4 or MAC address
|
||||||
|
*/
|
||||||
|
function unTransition(input) {
|
||||||
|
let output = "";
|
||||||
|
let hextets = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6to4
|
||||||
|
*/
|
||||||
|
if (input.startsWith("2002:")) {
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4: ";
|
||||||
|
}
|
||||||
|
output += String(intify(input.slice(5, 7))) + "." + String(intify(input.slice(7, 9)))+ "." + String(intify(input.slice(10, 12)))+ "." + String(intify(input.slice(12, 14))) + "\n";
|
||||||
|
} else if (input.startsWith("::ffff:") || input.startsWith("0000:0000:0000:0000:0000:ffff:") || input.startsWith("::ffff:0000:") || input.startsWith("0000:0000:0000:0000:ffff:0000:") || input.startsWith("64:ff9b::") || input.startsWith("0064:ff9b:0000:0000:0000:0000:")) {
|
||||||
|
/**
|
||||||
|
* Mapped/Translated/Nat64
|
||||||
|
*/
|
||||||
|
hextets = /:([0-9a-z]{1,4}):[0-9a-z]{1,4}$/.exec(input)[1].padStart(4, "0") + /:([0-9a-z]{1,4})$/.exec(input)[1].padStart(4, "0");
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "IPv4: ";
|
||||||
|
}
|
||||||
|
output += intify(hextets.slice(-8, -7) + hextets.slice(-7, -6)) + "." +intify(hextets.slice(-6, -5) + hextets.slice(-5, -4)) + "." +intify(hextets.slice(-4, -3) + hextets.slice(-3, -2)) + "." +intify(hextets.slice(-2, -1) + hextets.slice(-1,)) + "\n";
|
||||||
|
} else if (input.slice(-12, -7).toUpperCase() === "FF:FE") {
|
||||||
|
/**
|
||||||
|
* EUI-64
|
||||||
|
*/
|
||||||
|
if (!args[1]) {
|
||||||
|
output += "Mac Address: ";
|
||||||
|
}
|
||||||
|
const MAC = (input.slice(-19, -17) + ":" + input.slice(-17, -15) + ":" + input.slice(-14, -12) + ":" + input.slice(-7, -5) + ":" + input.slice(-4, -2) + ":" + input.slice(-2,)).toUpperCase();
|
||||||
|
output += MAC.slice(0, 1) + XOR[MAC.slice(1, 2)] + MAC.slice(2) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main
|
||||||
|
*/
|
||||||
|
let output = "";
|
||||||
|
let inputs = input.split("\n");
|
||||||
|
// Remove blank rows
|
||||||
|
inputs = inputs.filter(Boolean);
|
||||||
|
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
// if ignore ranges is checked and input is a range, skip
|
||||||
|
if ((args[0] && !inputs[i].includes("/")) || (!args[0])) {
|
||||||
|
if (/^[0-9]{1,3}(?:\.[0-9]{1,3}){3}$/.test(inputs[i])) {
|
||||||
|
output += ipTransition(inputs[i], false);
|
||||||
|
} else if (/\/24$/.test(inputs[i])) {
|
||||||
|
output += ipTransition(inputs[i], true);
|
||||||
|
} else if (/^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$/.test(inputs[i])) {
|
||||||
|
output += macTransition(inputs[i]);
|
||||||
|
} else if (/^((?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/.test(inputs[i])) {
|
||||||
|
output += unTransition(inputs[i]);
|
||||||
|
} else {
|
||||||
|
output = "Enter compressed or expanded IPv6 address, IPv4 address or MAC Address.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IPv6TransitionAddresses;
|
|
@ -36,6 +36,11 @@ class JWTSign extends Operation {
|
||||||
name: "Signing algorithm",
|
name: "Signing algorithm",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: JWT_ALGORITHMS
|
value: JWT_ALGORITHMS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Header",
|
||||||
|
type: "text",
|
||||||
|
value: "{}"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -46,11 +51,12 @@ class JWTSign extends Operation {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [key, algorithm] = args;
|
const [key, algorithm, header] = args;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return jwt.sign(input, key, {
|
return jwt.sign(input, key, {
|
||||||
algorithm: algorithm === "None" ? "none" : algorithm
|
algorithm: algorithm === "None" ? "none" : algorithm,
|
||||||
|
header: JSON.parse(header || "{}")
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} 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.
|
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.name = "JWT Verify";
|
||||||
this.module = "Crypto";
|
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.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "JSON";
|
this.outputType = "JSON";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* @author n1073645 [n1073645@gmail.com]
|
* @author n1073645 [n1073645@gmail.com]
|
||||||
|
* @author k3ach [k3ach@proton.me]
|
||||||
* @copyright Crown Copyright 2020
|
* @copyright Crown Copyright 2020
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -20,39 +21,46 @@ class LuhnChecksum extends Operation {
|
||||||
|
|
||||||
this.name = "Luhn Checksum";
|
this.name = "Luhn Checksum";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "The Luhn algorithm, also known as the modulus 10 or mod 10 algorithm, is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers, IMEI numbers and Canadian Social Insurance Numbers.";
|
this.description = "The Luhn mod N algorithm using the english alphabet. The Luhn mod N algorithm is an extension to the Luhn algorithm (also known as mod 10 algorithm) that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of N characters where N is divisible by 2.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/Luhn_algorithm";
|
this.infoURL = "https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [];
|
this.args = [
|
||||||
|
{
|
||||||
|
"name": "Radix",
|
||||||
|
"type": "number",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the Luhn Checksum from the input.
|
* Generates the Luhn checksum from the input.
|
||||||
*
|
*
|
||||||
* @param {string} inputStr
|
* @param {string} inputStr
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
checksum(inputStr) {
|
checksum(inputStr, radix = 10) {
|
||||||
let even = false;
|
let even = false;
|
||||||
return inputStr.split("").reverse().reduce((acc, elem) => {
|
return inputStr.split("").reverse().reduce((acc, elem) => {
|
||||||
// Convert element to integer.
|
// Convert element to an integer based on the provided radix.
|
||||||
let temp = parseInt(elem, 10);
|
let temp = parseInt(elem, radix);
|
||||||
|
|
||||||
// If element is not an integer.
|
// If element is not a valid number in the given radix.
|
||||||
if (isNaN(temp))
|
if (isNaN(temp)) {
|
||||||
throw new OperationError("Character: " + elem + " is not a digit.");
|
throw new Error("Character: " + elem + " is not valid in radix " + radix + ".");
|
||||||
|
}
|
||||||
|
|
||||||
// If element is in an even position
|
// If element is in an even position
|
||||||
if (even) {
|
if (even) {
|
||||||
// Double the element and add the quotient and remainder together.
|
// Double the element and sum the quotient and remainder.
|
||||||
temp = 2 * elem;
|
temp = 2 * temp;
|
||||||
temp = Math.floor(temp/10) + (temp % 10);
|
temp = Math.floor(temp / radix) + (temp % radix);
|
||||||
}
|
}
|
||||||
|
|
||||||
even = !even;
|
even = !even;
|
||||||
return acc + temp;
|
return acc + temp;
|
||||||
}, 0) % 10;
|
}, 0) % radix; // Use radix as the modulus base
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,9 +71,20 @@ class LuhnChecksum extends Operation {
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
if (!input) return "";
|
if (!input) return "";
|
||||||
|
|
||||||
const checkSum = this.checksum(input);
|
const radix = args[0];
|
||||||
let checkDigit = this.checksum(input + "0");
|
|
||||||
checkDigit = checkDigit === 0 ? 0 : (10-checkDigit);
|
if (radix < 2 || radix > 36) {
|
||||||
|
throw new OperationError("Error: Radix argument must be between 2 and 36");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radix % 2 !== 0) {
|
||||||
|
throw new OperationError("Error: Radix argument must be divisible by 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkSum = this.checksum(input, radix).toString(radix);
|
||||||
|
let checkDigit = this.checksum(input + "0", radix);
|
||||||
|
checkDigit = checkDigit === 0 ? 0 : (radix - checkDigit);
|
||||||
|
checkDigit = checkDigit.toString(radix);
|
||||||
|
|
||||||
return `Checksum: ${checkSum}
|
return `Checksum: ${checkSum}
|
||||||
Checkdigit: ${checkDigit}
|
Checkdigit: ${checkDigit}
|
||||||
|
|
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;
|
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;
|
79
src/core/operations/TakeNthBytes.mjs
Normal file
79
src/core/operations/TakeNthBytes.mjs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* @author Oshawk [oshawk@protonmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take nth bytes operation
|
||||||
|
*/
|
||||||
|
class TakeNthBytes extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TakeNthBytes constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Take nth bytes";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Takes every nth byte starting with a given byte.";
|
||||||
|
this.infoURL = "";
|
||||||
|
this.inputType = "byteArray";
|
||||||
|
this.outputType = "byteArray";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Take every",
|
||||||
|
type: "number",
|
||||||
|
value: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Starting at",
|
||||||
|
type: "number",
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Apply to each line",
|
||||||
|
type: "boolean",
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {byteArray} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {byteArray}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const n = args[0];
|
||||||
|
const start = args[1];
|
||||||
|
const eachLine = args[2];
|
||||||
|
|
||||||
|
if (parseInt(n, 10) !== n || n <= 0) {
|
||||||
|
throw new OperationError("'Take every' must be a positive integer.");
|
||||||
|
}
|
||||||
|
if (parseInt(start, 10) !== start || start < 0) {
|
||||||
|
throw new OperationError("'Starting at' must be a positive or zero integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
const output = [];
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
if (eachLine && input[i] === 0x0a) {
|
||||||
|
output.push(0x0a);
|
||||||
|
offset = i + 1;
|
||||||
|
} else if (i - offset >= start && (i - (start + offset)) % n === 0) {
|
||||||
|
output.push(input[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TakeNthBytes;
|
|
@ -23,7 +23,7 @@ const dir = path.join(`${process.cwd()}/src/node`);
|
||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
console.log("\nCWD: " + process.cwd());
|
console.log("\nCWD: " + process.cwd());
|
||||||
console.log("Error: generateNodeIndex.mjs should be run from the project root");
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -675,42 +675,42 @@ module.exports = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"Loading from URL": browser => {
|
// "Loading from URL": browser => {
|
||||||
utils.clear(browser);
|
// utils.clear(browser);
|
||||||
|
|
||||||
/* Side panel displays correct info */
|
// /* Side panel displays correct info */
|
||||||
utils.uploadFile(browser, "files/TowelDay.jpeg");
|
// utils.uploadFile(browser, "files/TowelDay.jpeg");
|
||||||
|
|
||||||
browser
|
// browser
|
||||||
.waitForElementVisible("#input-text .cm-file-details")
|
// .waitForElementVisible("#input-text .cm-file-details")
|
||||||
.waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
|
// .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-thumbnail")
|
||||||
.waitForElementVisible("#input-text .cm-file-details .file-details-name")
|
// .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-size")
|
||||||
.waitForElementVisible("#input-text .cm-file-details .file-details-type")
|
// .waitForElementVisible("#input-text .cm-file-details .file-details-type")
|
||||||
.waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
|
// .waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
|
||||||
|
|
||||||
/* Complex deep link populates the input correctly (encoding, eol, input) */
|
// /* Complex deep link populates the input correctly (encoding, eol, input) */
|
||||||
browser
|
// browser
|
||||||
.urlHash("recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=FF&oeol=PS")
|
// .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");
|
// .waitForElementVisible("#rec-list li.operation");
|
||||||
|
|
||||||
browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/);
|
// 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-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-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("#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("#output-text .chr-enc-value").text.that.equals("UTF-16BE");
|
||||||
|
|
||||||
browser.expect.element("#input-text .eol-value").text.that.equals("FF");
|
// 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("#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-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-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-status-bar .stats-lines-value").text.to.equal("1");
|
||||||
},
|
// },
|
||||||
|
|
||||||
"Replace input with output": browser => {
|
"Replace input with output": browser => {
|
||||||
/* Input is correctly populated */
|
/* Input is correctly populated */
|
||||||
|
|
|
@ -575,12 +575,11 @@ Top Drawer`, {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
it("Generate HOTP", () => {
|
it("Generate HOTP", () => {
|
||||||
const result = chef.generateHOTP("Cut The Mustard", {
|
const result = chef.generateHOTP("JBSWY3DPEHPK3PXP", {
|
||||||
name: "colonel",
|
|
||||||
});
|
});
|
||||||
const expected = `URI: otpauth://hotp/colonel?secret=IN2XIICUNBSSATLVON2GC4TE
|
const expected = `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0
|
||||||
|
|
||||||
Password: 034148`;
|
Password: 282760`;
|
||||||
assert.strictEqual(result.toString(), expected);
|
assert.strictEqual(result.toString(), expected);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
@ -54,11 +54,13 @@ import "./tests/Comment.mjs";
|
||||||
import "./tests/Compress.mjs";
|
import "./tests/Compress.mjs";
|
||||||
import "./tests/ConditionalJump.mjs";
|
import "./tests/ConditionalJump.mjs";
|
||||||
import "./tests/ConvertCoordinateFormat.mjs";
|
import "./tests/ConvertCoordinateFormat.mjs";
|
||||||
|
import "./tests/ConvertLeetSpeak.mjs";
|
||||||
import "./tests/ConvertToNATOAlphabet.mjs";
|
import "./tests/ConvertToNATOAlphabet.mjs";
|
||||||
import "./tests/Crypt.mjs";
|
import "./tests/Crypt.mjs";
|
||||||
import "./tests/CSV.mjs";
|
import "./tests/CSV.mjs";
|
||||||
import "./tests/DateTime.mjs";
|
import "./tests/DateTime.mjs";
|
||||||
import "./tests/DefangIP.mjs";
|
import "./tests/DefangIP.mjs";
|
||||||
|
import "./tests/DropNthBytes.mjs";
|
||||||
import "./tests/ECDSA.mjs";
|
import "./tests/ECDSA.mjs";
|
||||||
import "./tests/ELFInfo.mjs";
|
import "./tests/ELFInfo.mjs";
|
||||||
import "./tests/Enigma.mjs";
|
import "./tests/Enigma.mjs";
|
||||||
|
@ -115,6 +117,7 @@ import "./tests/ParseObjectIDTimestamp.mjs";
|
||||||
import "./tests/ParseQRCode.mjs";
|
import "./tests/ParseQRCode.mjs";
|
||||||
import "./tests/ParseSSHHostKey.mjs";
|
import "./tests/ParseSSHHostKey.mjs";
|
||||||
import "./tests/ParseTCP.mjs";
|
import "./tests/ParseTCP.mjs";
|
||||||
|
import "./tests/ParseTLSRecord.mjs";
|
||||||
import "./tests/ParseTLV.mjs";
|
import "./tests/ParseTLV.mjs";
|
||||||
import "./tests/ParseUDP.mjs";
|
import "./tests/ParseUDP.mjs";
|
||||||
import "./tests/PEMtoHex.mjs";
|
import "./tests/PEMtoHex.mjs";
|
||||||
|
@ -142,9 +145,13 @@ import "./tests/SIGABA.mjs";
|
||||||
import "./tests/SM4.mjs";
|
import "./tests/SM4.mjs";
|
||||||
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
|
||||||
import "./tests/StrUtils.mjs";
|
import "./tests/StrUtils.mjs";
|
||||||
|
import "./tests/StripIPv4Header.mjs";
|
||||||
|
import "./tests/StripTCPHeader.mjs";
|
||||||
|
import "./tests/StripUDPHeader.mjs";
|
||||||
import "./tests/Subsection.mjs";
|
import "./tests/Subsection.mjs";
|
||||||
import "./tests/SwapCase.mjs";
|
import "./tests/SwapCase.mjs";
|
||||||
import "./tests/SymmetricDifference.mjs";
|
import "./tests/SymmetricDifference.mjs";
|
||||||
|
import "./tests/TakeNthBytes.mjs";
|
||||||
import "./tests/TextEncodingBruteForce.mjs";
|
import "./tests/TextEncodingBruteForce.mjs";
|
||||||
import "./tests/ToFromInsensitiveRegex.mjs";
|
import "./tests/ToFromInsensitiveRegex.mjs";
|
||||||
import "./tests/TranslateDateTimeFormat.mjs";
|
import "./tests/TranslateDateTimeFormat.mjs";
|
||||||
|
|
33
tests/operations/tests/ConvertLeetSpeak.mjs
Normal file
33
tests/operations/tests/ConvertLeetSpeak.mjs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/**
|
||||||
|
* @author bartblaze []
|
||||||
|
* @copyright Crown Copyright 2025
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Convert to Leet Speak: basic text",
|
||||||
|
input: "leet",
|
||||||
|
expectedOutput: "l337",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Convert Leet Speak",
|
||||||
|
args: ["To Leet Speak"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Convert from Leet Speak: basic leet",
|
||||||
|
input: "l337",
|
||||||
|
expectedOutput: "leet",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Convert Leet Speak",
|
||||||
|
args: ["From Leet Speak"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
|
@ -1579,19 +1579,31 @@ DES uses a key length of 8 bytes (64 bits).`,
|
||||||
from Crypto.Cipher import Blowfish
|
from Crypto.Cipher import Blowfish
|
||||||
import binascii
|
import binascii
|
||||||
|
|
||||||
input_data = b"The quick brown fox jumps over the lazy dog."
|
# Blowfish cipher parameters - key, mode, iv, segment_size, nonce
|
||||||
key = binascii.unhexlify("0011223344556677")
|
key = binascii.unhexlify("0011223344556677")
|
||||||
iv = binascii.unhexlify("0000000000000000")
|
|
||||||
mode = Blowfish.MODE_CBC
|
mode = Blowfish.MODE_CBC
|
||||||
|
kwargs = {}
|
||||||
|
iv = binascii.unhexlify("ffeeddccbbaa9988")
|
||||||
|
if mode in [Blowfish.MODE_CBC, Blowfish.MODE_CFB, Blowfish.MODE_OFB]:
|
||||||
|
kwargs = {"iv": iv}
|
||||||
|
if mode == Blowfish.MODE_CFB:
|
||||||
|
kwargs["segment_size"] = 64
|
||||||
|
if mode == Blowfish.MODE_CTR:
|
||||||
|
nonce = binascii.unhexlify("0000000000000000")
|
||||||
|
nonce = nonce[:7]
|
||||||
|
kwargs["nonce"] = nonce
|
||||||
|
|
||||||
|
cipher = Blowfish.new(key, mode, **kwargs)
|
||||||
|
|
||||||
|
# Input data and padding
|
||||||
|
input_data = b"The quick brown fox jumps over the lazy dog."
|
||||||
if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC:
|
if mode == Blowfish.MODE_ECB or mode == Blowfish.MODE_CBC:
|
||||||
padding_len = 8-(len(input_data) & 7)
|
padding_len = 8-(len(input_data) & 7)
|
||||||
for i in range(padding_len):
|
for i in range(padding_len):
|
||||||
input_data += bytes([padding_len])
|
input_data += bytes([padding_len])
|
||||||
|
|
||||||
cipher = Blowfish.new(key, mode) # set iv, nonce, segment_size etc. here
|
# Encrypted text
|
||||||
cipher_text = cipher.encrypt(input_data)
|
cipher_text = cipher.encrypt(input_data)
|
||||||
|
|
||||||
cipher_text = binascii.hexlify(cipher_text).decode("UTF-8")
|
cipher_text = binascii.hexlify(cipher_text).decode("UTF-8")
|
||||||
|
|
||||||
print("Encrypted: {}".format(cipher_text))
|
print("Encrypted: {}".format(cipher_text))
|
||||||
|
|
123
tests/operations/tests/DropNthBytes.mjs
Normal file
123
tests/operations/tests/DropNthBytes.mjs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* @author Oshawk [oshawk@protonmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop nth bytes tests
|
||||||
|
*/
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Nothing",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Nothing (apply to each line)",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Basic single line",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "1235679",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Basic single line (apply to each line)",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "1235679",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Complex single line",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "01234678",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 5, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Complex single line (apply to each line)",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "01234678",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 5, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Basic multi line",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "123\n5689",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Basic multi line (apply to each line)",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "123\n678",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Complex multi line",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "012345679",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 5, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Drop nth bytes: Complex multi line (apply to each line)",
|
||||||
|
input: "012345\n6789ab",
|
||||||
|
expectedOutput: "01234\n6789a",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Drop nth bytes",
|
||||||
|
args: [4, 5, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
63
tests/operations/tests/IPv6Transition.mjs
Normal file
63
tests/operations/tests/IPv6Transition.mjs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/**
|
||||||
|
* IPv6Transition tests.
|
||||||
|
*
|
||||||
|
* @author jb30795
|
||||||
|
*
|
||||||
|
* @copyright Crown Copyright 2024
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "IPv6 Transition: IPv4 to IPv6",
|
||||||
|
input: "198.51.100.7",
|
||||||
|
expectedOutput: "6to4: 2002:c633:6407::/48\nIPv4 Mapped: ::ffff:c633:6407\nIPv4 Translated: ::ffff:0:c633:6407\nNat 64: 64:ff9b::c633:6407",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "IPv6 Transition Addresses",
|
||||||
|
args: [true, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: "IPv6 Transition: IPv4 /24 Range to IPv6",
|
||||||
|
input: "198.51.100.0/24",
|
||||||
|
expectedOutput: "6to4: 2002:c633:6400::/40\nIPv4 Mapped: ::ffff:c633:6400/120\nIPv4 Translated: ::ffff:0:c633:6400/120\nNat 64: 64:ff9b::c633:6400/120",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "IPv6 Transition Addresses",
|
||||||
|
args: [false, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: "IPv6 Transition: IPv4 to IPv6 Remove headers",
|
||||||
|
input: "198.51.100.7",
|
||||||
|
expectedOutput: "2002:c633:6407::/48\n::ffff:c633:6407\n::ffff:0:c633:6407\n64:ff9b::c633:6407",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "IPv6 Transition Addresses",
|
||||||
|
args: [true, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: "IPv6 Transition: IPv6 to IPv4",
|
||||||
|
input: "64:ff9b::c633:6407",
|
||||||
|
expectedOutput: "IPv4: 198.51.100.7",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "IPv6 Transition Addresses",
|
||||||
|
args: [true, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: "IPv6 Transition: MAC to EUI-64",
|
||||||
|
input: "a1:b2:c3:d4:e5:f6",
|
||||||
|
expectedOutput: "EUI-64 Interface ID: a3b2:c3ff:fed4:e5f6",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "IPv6 Transition Addresses",
|
||||||
|
args: [true, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
|
@ -44,7 +44,18 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
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: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [hsKey, "HS384"],
|
args: [hsKey, "HS384", "{}"],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -66,7 +77,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [hsKey, "HS512"],
|
args: [hsKey, "HS512", "{}"],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -77,7 +88,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [esKey, "ES256"],
|
args: [esKey, "ES256", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
@ -92,7 +103,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [esKey, "ES384"],
|
args: [esKey, "ES384", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
@ -107,7 +118,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [esKey, "ES512"],
|
args: [esKey, "ES512", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
@ -122,7 +133,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [rsKey, "RS256"],
|
args: [rsKey, "RS256", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
@ -137,7 +148,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [rsKey, "RS384"],
|
args: [rsKey, "RS384", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
@ -152,7 +163,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "JWT Sign",
|
op: "JWT Sign",
|
||||||
args: [esKey, "RS512"],
|
args: [esKey, "RS512", "{}"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JWT Decode",
|
op: "JWT Decode",
|
||||||
|
|
|
@ -2,11 +2,392 @@
|
||||||
* From Decimal tests
|
* From Decimal tests
|
||||||
*
|
*
|
||||||
* @author n1073645 [n1073645@gmail.com]
|
* @author n1073645 [n1073645@gmail.com]
|
||||||
|
* @author k3ach [k3ach@proton.me]
|
||||||
* @copyright Crown Copyright 2020
|
* @copyright Crown Copyright 2020
|
||||||
* @licence Apache-2.0
|
* @licence Apache-2.0
|
||||||
*/
|
*/
|
||||||
import TestRegister from "../../lib/TestRegister.mjs";
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
radix: 2,
|
||||||
|
input: "01",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 2,
|
||||||
|
input: "001111",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "0",
|
||||||
|
}, {
|
||||||
|
radix: 2,
|
||||||
|
input: "00011101",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "0",
|
||||||
|
}, {
|
||||||
|
radix: 2,
|
||||||
|
input: "0100101101",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 4,
|
||||||
|
input: "0123",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 4,
|
||||||
|
input: "130100",
|
||||||
|
checksum: "2",
|
||||||
|
checkdigit: "2",
|
||||||
|
}, {
|
||||||
|
radix: 4,
|
||||||
|
input: "32020313",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "0",
|
||||||
|
}, {
|
||||||
|
radix: 4,
|
||||||
|
input: "302233210112",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "0",
|
||||||
|
}, {
|
||||||
|
radix: 6,
|
||||||
|
input: "012345",
|
||||||
|
checksum: "4",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 6,
|
||||||
|
input: "134255",
|
||||||
|
checksum: "2",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 6,
|
||||||
|
input: "15021453",
|
||||||
|
checksum: "5",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 6,
|
||||||
|
input: "211450230513",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 8,
|
||||||
|
input: "01234567",
|
||||||
|
checksum: "2",
|
||||||
|
checkdigit: "2",
|
||||||
|
}, {
|
||||||
|
radix: 8,
|
||||||
|
input: "340624",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 8,
|
||||||
|
input: "07260247",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "3",
|
||||||
|
}, {
|
||||||
|
radix: 8,
|
||||||
|
input: "026742114675",
|
||||||
|
checksum: "7",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 10,
|
||||||
|
input: "0123456789",
|
||||||
|
checksum: "7",
|
||||||
|
checkdigit: "7",
|
||||||
|
}, {
|
||||||
|
radix: 10,
|
||||||
|
input: "468543",
|
||||||
|
checksum: "7",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 10,
|
||||||
|
input: "59377601",
|
||||||
|
checksum: "5",
|
||||||
|
checkdigit: "6",
|
||||||
|
}, {
|
||||||
|
radix: 10,
|
||||||
|
input: "013909981254",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "3",
|
||||||
|
}, {
|
||||||
|
radix: 12,
|
||||||
|
input: "0123456789ab",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "3",
|
||||||
|
}, {
|
||||||
|
radix: 12,
|
||||||
|
input: "284685",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "6",
|
||||||
|
}, {
|
||||||
|
radix: 12,
|
||||||
|
input: "951a2661",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "8",
|
||||||
|
}, {
|
||||||
|
radix: 12,
|
||||||
|
input: "898202676387",
|
||||||
|
checksum: "b",
|
||||||
|
checkdigit: "9",
|
||||||
|
}, {
|
||||||
|
radix: 14,
|
||||||
|
input: "0123456789abcd",
|
||||||
|
checksum: "a",
|
||||||
|
checkdigit: "a",
|
||||||
|
}, {
|
||||||
|
radix: 14,
|
||||||
|
input: "33db25",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "d",
|
||||||
|
}, {
|
||||||
|
radix: 14,
|
||||||
|
input: "0b4ac128",
|
||||||
|
checksum: "b",
|
||||||
|
checkdigit: "3",
|
||||||
|
}, {
|
||||||
|
radix: 14,
|
||||||
|
input: "3d1c6d16160d",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "c",
|
||||||
|
}, {
|
||||||
|
radix: 16,
|
||||||
|
input: "0123456789abcdef",
|
||||||
|
checksum: "4",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 16,
|
||||||
|
input: "e1fe64",
|
||||||
|
checksum: "b",
|
||||||
|
checkdigit: "6",
|
||||||
|
}, {
|
||||||
|
radix: 16,
|
||||||
|
input: "241a5dcd",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "9",
|
||||||
|
}, {
|
||||||
|
radix: 16,
|
||||||
|
input: "1fea740e0e1f",
|
||||||
|
checksum: "7",
|
||||||
|
checkdigit: "4",
|
||||||
|
}, {
|
||||||
|
radix: 18,
|
||||||
|
input: "0123456789abcdefgh",
|
||||||
|
checksum: "d",
|
||||||
|
checkdigit: "d",
|
||||||
|
}, {
|
||||||
|
radix: 18,
|
||||||
|
input: "995dgf",
|
||||||
|
checksum: "9",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 18,
|
||||||
|
input: "9f80h32h",
|
||||||
|
checksum: "1",
|
||||||
|
checkdigit: "0",
|
||||||
|
}, {
|
||||||
|
radix: 18,
|
||||||
|
input: "5f9428e493g4",
|
||||||
|
checksum: "8",
|
||||||
|
checkdigit: "c",
|
||||||
|
}, {
|
||||||
|
radix: 20,
|
||||||
|
input: "0123456789abcdefghij",
|
||||||
|
checksum: "5",
|
||||||
|
checkdigit: "5",
|
||||||
|
}, {
|
||||||
|
radix: 20,
|
||||||
|
input: "918jci",
|
||||||
|
checksum: "h",
|
||||||
|
checkdigit: "d",
|
||||||
|
}, {
|
||||||
|
radix: 20,
|
||||||
|
input: "jab7j50d",
|
||||||
|
checksum: "g",
|
||||||
|
checkdigit: "j",
|
||||||
|
}, {
|
||||||
|
radix: 20,
|
||||||
|
input: "c56fe85eb6gg",
|
||||||
|
checksum: "g",
|
||||||
|
checkdigit: "5",
|
||||||
|
}, {
|
||||||
|
radix: 22,
|
||||||
|
input: "0123456789abcdefghijkl",
|
||||||
|
checksum: "g",
|
||||||
|
checkdigit: "g",
|
||||||
|
}, {
|
||||||
|
radix: 22,
|
||||||
|
input: "de57le",
|
||||||
|
checksum: "5",
|
||||||
|
checkdigit: "l",
|
||||||
|
}, {
|
||||||
|
radix: 22,
|
||||||
|
input: "e3fg6dfc",
|
||||||
|
checksum: "f",
|
||||||
|
checkdigit: "d",
|
||||||
|
}, {
|
||||||
|
radix: 22,
|
||||||
|
input: "1f8l80ai4kbg",
|
||||||
|
checksum: "l",
|
||||||
|
checkdigit: "f",
|
||||||
|
}, {
|
||||||
|
radix: 24,
|
||||||
|
input: "0123456789abcdefghijklmn",
|
||||||
|
checksum: "6",
|
||||||
|
checkdigit: "6",
|
||||||
|
}, {
|
||||||
|
radix: 24,
|
||||||
|
input: "agne7d",
|
||||||
|
checksum: "4",
|
||||||
|
checkdigit: "f",
|
||||||
|
}, {
|
||||||
|
radix: 24,
|
||||||
|
input: "1l4d9cf4",
|
||||||
|
checksum: "d",
|
||||||
|
checkdigit: "c",
|
||||||
|
}, {
|
||||||
|
radix: 24,
|
||||||
|
input: "blc1j09i3296",
|
||||||
|
checksum: "8",
|
||||||
|
checkdigit: "7",
|
||||||
|
}, {
|
||||||
|
radix: 26,
|
||||||
|
input: "0123456789abcdefghijklmnop",
|
||||||
|
checksum: "j",
|
||||||
|
checkdigit: "j",
|
||||||
|
}, {
|
||||||
|
radix: 26,
|
||||||
|
input: "82n9op",
|
||||||
|
checksum: "i",
|
||||||
|
checkdigit: "2",
|
||||||
|
}, {
|
||||||
|
radix: 26,
|
||||||
|
input: "e9cddn70",
|
||||||
|
checksum: "9",
|
||||||
|
checkdigit: "i",
|
||||||
|
}, {
|
||||||
|
radix: 26,
|
||||||
|
input: "ck0ep419knom",
|
||||||
|
checksum: "p",
|
||||||
|
checkdigit: "g",
|
||||||
|
}, {
|
||||||
|
radix: 28,
|
||||||
|
input: "0123456789abcdefghijklmnopqr",
|
||||||
|
checksum: "7",
|
||||||
|
checkdigit: "7",
|
||||||
|
}, {
|
||||||
|
radix: 28,
|
||||||
|
input: "a6hnoo",
|
||||||
|
checksum: "h",
|
||||||
|
checkdigit: "9",
|
||||||
|
}, {
|
||||||
|
radix: 28,
|
||||||
|
input: "lblc7kh0",
|
||||||
|
checksum: "a",
|
||||||
|
checkdigit: "f",
|
||||||
|
}, {
|
||||||
|
radix: 28,
|
||||||
|
input: "64k5piod3lmf",
|
||||||
|
checksum: "0",
|
||||||
|
checkdigit: "p",
|
||||||
|
}, {
|
||||||
|
radix: 30,
|
||||||
|
input: "0123456789abcdefghijklmnopqrst",
|
||||||
|
checksum: "m",
|
||||||
|
checkdigit: "m",
|
||||||
|
}, {
|
||||||
|
radix: 30,
|
||||||
|
input: "t69j7d",
|
||||||
|
checksum: "9",
|
||||||
|
checkdigit: "s",
|
||||||
|
}, {
|
||||||
|
radix: 30,
|
||||||
|
input: "p54o9ig3",
|
||||||
|
checksum: "a",
|
||||||
|
checkdigit: "o",
|
||||||
|
}, {
|
||||||
|
radix: 30,
|
||||||
|
input: "gc1njrt55030",
|
||||||
|
checksum: "6",
|
||||||
|
checkdigit: "1",
|
||||||
|
}, {
|
||||||
|
radix: 32,
|
||||||
|
input: "0123456789abcdefghijklmnopqrstuv",
|
||||||
|
checksum: "8",
|
||||||
|
checkdigit: "8",
|
||||||
|
}, {
|
||||||
|
radix: 32,
|
||||||
|
input: "rdou19",
|
||||||
|
checksum: "u",
|
||||||
|
checkdigit: "3",
|
||||||
|
}, {
|
||||||
|
radix: 32,
|
||||||
|
input: "ighj0pc7",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "8",
|
||||||
|
}, {
|
||||||
|
radix: 32,
|
||||||
|
input: "op4nn5fvjsrs",
|
||||||
|
checksum: "g",
|
||||||
|
checkdigit: "j",
|
||||||
|
}, {
|
||||||
|
radix: 34,
|
||||||
|
input: "0123456789abcdefghijklmnopqrstuvwx",
|
||||||
|
checksum: "p",
|
||||||
|
checkdigit: "p",
|
||||||
|
}, {
|
||||||
|
radix: 34,
|
||||||
|
input: "nvftj5",
|
||||||
|
checksum: "b",
|
||||||
|
checkdigit: "f",
|
||||||
|
}, {
|
||||||
|
radix: 34,
|
||||||
|
input: "u9v9g162",
|
||||||
|
checksum: "j",
|
||||||
|
checkdigit: "b",
|
||||||
|
}, {
|
||||||
|
radix: 34,
|
||||||
|
input: "o5gqg5d7gjh9",
|
||||||
|
checksum: "5",
|
||||||
|
checkdigit: "q",
|
||||||
|
}, {
|
||||||
|
radix: 36,
|
||||||
|
input: "0123456789abcdefghijklmnopqrstuvwxyz",
|
||||||
|
checksum: "9",
|
||||||
|
checkdigit: "9",
|
||||||
|
}, {
|
||||||
|
radix: 36,
|
||||||
|
input: "29zehu",
|
||||||
|
checksum: "i",
|
||||||
|
checkdigit: "j",
|
||||||
|
}, {
|
||||||
|
radix: 36,
|
||||||
|
input: "1snmikbu",
|
||||||
|
checksum: "s",
|
||||||
|
checkdigit: "v",
|
||||||
|
}, {
|
||||||
|
radix: 36,
|
||||||
|
input: "jpkar545q7gb",
|
||||||
|
checksum: "3",
|
||||||
|
checkdigit: "d",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(element => {
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Luhn Checksum Mod " + element.radix + " on " + element.input,
|
||||||
|
input: element.input,
|
||||||
|
expectedOutput: "Checksum: " + element.checksum + "\nCheckdigit: " + element.checkdigit + "\nLuhn Validated String: " + element.input + element.checkdigit,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Luhn Checksum",
|
||||||
|
args: [element.radix]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
TestRegister.addTests([
|
TestRegister.addTests([
|
||||||
{
|
{
|
||||||
name: "Luhn Checksum on standard data",
|
name: "Luhn Checksum on standard data",
|
||||||
|
@ -15,7 +396,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Luhn Checksum",
|
op: "Luhn Checksum",
|
||||||
args: []
|
args: [10]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -26,7 +407,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Luhn Checksum",
|
op: "Luhn Checksum",
|
||||||
args: []
|
args: [10]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -37,18 +418,7 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Luhn Checksum",
|
op: "Luhn Checksum",
|
||||||
args: []
|
args: [10]
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Luhn Checksum on invalid data",
|
|
||||||
input: "35641709b012469",
|
|
||||||
expectedOutput: "Character: b is not a digit.",
|
|
||||||
recipeConfig: [
|
|
||||||
{
|
|
||||||
op: "Luhn Checksum",
|
|
||||||
args: []
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -59,8 +429,8 @@ TestRegister.addTests([
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Luhn Checksum",
|
op: "Luhn Checksum",
|
||||||
args: []
|
args: [10]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -11,12 +11,12 @@ import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
TestRegister.addTests([
|
TestRegister.addTests([
|
||||||
{
|
{
|
||||||
name: "Generate HOTP",
|
name: "Generate HOTP",
|
||||||
input: "12345678901234567890",
|
input: "JBSWY3DPEHPK3PXP",
|
||||||
expectedOutput: "URI: otpauth://hotp/OTPAuthentication?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ\n\nPassword: 755224",
|
expectedOutput: `URI: otpauth://hotp/?secret=JBSWY3DPEHPK3PXP&algorithm=SHA1&digits=6&counter=0\n\nPassword: 282760`,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Generate HOTP",
|
op: "Generate HOTP",
|
||||||
args: ["", 32, 6, 0],
|
args: ["", 6, 0], // [Name, Code length, Counter]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
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]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
123
tests/operations/tests/TakeNthBytes.mjs
Normal file
123
tests/operations/tests/TakeNthBytes.mjs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/**
|
||||||
|
* @author Oshawk [oshawk@protonmail.com]
|
||||||
|
* @copyright Crown Copyright 2019
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take nth bytes tests
|
||||||
|
*/
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Nothing",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Nothing (apply to each line)",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: "",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Basic single line",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "048",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Basic single line (apply to each line)",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "048",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Complex single line",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "59",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 5, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Complex single line (apply to each line)",
|
||||||
|
input: "0123456789",
|
||||||
|
expectedOutput: "59",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 5, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Basic multi line",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "047",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Basic multi line (apply to each line)",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "04\n59",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 0, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Complex multi line",
|
||||||
|
input: "01234\n56789",
|
||||||
|
expectedOutput: "\n8",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 5, false],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Take nth bytes: Complex multi line (apply to each line)",
|
||||||
|
input: "012345\n6789ab",
|
||||||
|
expectedOutput: "5\nb",
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Take nth bytes",
|
||||||
|
args: [4, 5, true],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
|
@ -1,8 +1,10 @@
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
const CompressionPlugin = require("compression-webpack-plugin");
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const { ModifySourcePlugin, ReplaceOperation } = require("modify-source-webpack-plugin");
|
const { ModifySourcePlugin, ReplaceOperation } = require("modify-source-webpack-plugin");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const zlib = require("zlib");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Webpack configuration details for use with Grunt.
|
* Webpack configuration details for use with Grunt.
|
||||||
|
@ -64,6 +66,21 @@ module.exports = {
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: "assets/[name].css"
|
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({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue