cyberchef 11.0.0 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +170 -1
- package/Dockerfile +2 -2
- package/Gruntfile.js +10 -5
- package/README.md +10 -7
- package/SECURITY.md +11 -19
- package/package.json +35 -34
- package/src/core/Chef.mjs +2 -0
- package/src/core/config/Categories.json +6 -0
- package/src/core/config/OperationConfig.json +144 -19
- package/src/core/config/modules/Default.mjs +8 -0
- package/src/core/config/modules/PGP.mjs +2 -0
- package/src/core/config/scripts/generateOpsIndex.mjs +63 -0
- package/src/core/config/scripts/newOperation.mjs +31 -4
- package/src/core/dishTypes/DishType.mjs +1 -1
- package/src/core/errors/ExcludedOperationError.mjs +1 -1
- package/src/core/lib/Charts.mjs +3 -3
- package/src/core/lib/Protocol.mjs +8 -6
- package/src/core/operations/AESDecrypt.mjs +61 -16
- package/src/core/operations/AESEncrypt.mjs +26 -11
- package/src/core/operations/BLAKE3.mjs +13 -7
- package/src/core/operations/BSONDeserialise.mjs +2 -2
- package/src/core/operations/BSONSerialise.mjs +3 -2
- package/src/core/operations/Bcrypt.mjs +1 -1
- package/src/core/operations/BcryptCompare.mjs +1 -1
- package/src/core/operations/DecodeText.mjs +4 -0
- package/src/core/operations/EncodeText.mjs +4 -0
- package/src/core/operations/EscapeSmartCharacters.mjs +129 -0
- package/src/core/operations/GenerateDeBruijnSequence.mjs +8 -0
- package/src/core/operations/GenerateLoremIpsum.mjs +34 -3
- package/src/core/operations/GeneratePGPKeyPair.mjs +8 -7
- package/src/core/operations/PGPSign.mjs +83 -0
- package/src/core/operations/ParityBit.mjs +1 -1
- package/src/core/operations/ParseEthernetFrame.mjs +1 -1
- package/src/core/operations/ParseIPv4Header.mjs +2 -2
- package/src/core/operations/ParseObjectIDTimestamp.mjs +2 -2
- package/src/core/operations/ParseUserAgent.mjs +1 -1
- package/src/core/operations/ROR13.mjs +83 -0
- package/src/core/operations/RemoveANSIEscapeCodes.mjs +41 -0
- package/src/core/operations/SHA2.mjs +1 -1
- package/src/core/operations/SeriesChart.mjs +16 -0
- package/src/core/operations/ToHexdump.mjs +7 -1
- package/src/core/operations/Wrap.mjs +47 -0
- package/src/core/operations/XORChecksum.mjs +10 -3
- package/src/core/operations/index.mjs +10 -0
- package/src/node/index.mjs +25 -0
- package/src/web/App.mjs +19 -1
- package/src/web/HTMLIngredient.mjs +1 -0
- package/src/web/html/index.html +3 -3
- package/src/web/static/sitemap.mjs +3 -3
- package/src/web/waiters/RecipeWaiter.mjs +9 -1
- package/tests/browser/02_ops.js +7 -7
- package/tests/browser/03_recipe_load.js +48 -0
- package/tests/browser/browserUtils.js +6 -3
- package/tests/node/index.mjs +2 -0
- package/tests/node/tests/PGP.mjs +69 -0
- package/tests/node/tests/lib/ChartsProtocolPrototypePollution.mjs +90 -0
- package/tests/node/tests/operations.mjs +41 -2
- package/tests/operations/index.mjs +71 -66
- package/tests/operations/tests/BLAKE3.mjs +18 -0
- package/tests/operations/tests/CharEnc.mjs +26 -0
- package/tests/operations/tests/Charts.mjs +11 -0
- package/tests/operations/tests/Crypt.mjs +288 -62
- package/tests/operations/tests/EscapeSmartCharacters.mjs +132 -0
- package/tests/operations/tests/FlaskSession.mjs +11 -8
- package/tests/operations/tests/GenerateLoremIpsum.mjs +80 -0
- package/tests/operations/tests/Hexdump.mjs +11 -0
- package/tests/operations/tests/IPv6Transition.mjs +4 -4
- package/tests/operations/tests/PGP.mjs +178 -154
- package/tests/operations/tests/ParseEthernetFrame.mjs +11 -0
- package/tests/operations/tests/ParseIPv4Header.mjs +34 -0
- package/tests/operations/tests/ParseX509CRL.mjs +16 -16
- package/tests/operations/tests/ROR13.mjs +45 -0
- package/tests/operations/tests/Register.mjs +3 -1
- package/tests/operations/tests/RemoveANSIEscapeCodes.mjs +62 -0
- package/tests/operations/tests/Wrap.mjs +44 -0
|
@@ -39,41 +39,46 @@ class AESDecrypt extends Operation {
|
|
|
39
39
|
"value": "",
|
|
40
40
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
"name": "IV Length",
|
|
44
|
+
"type": "number",
|
|
45
|
+
"value": 16
|
|
46
|
+
},
|
|
42
47
|
{
|
|
43
48
|
"name": "Mode",
|
|
44
49
|
"type": "argSelector",
|
|
45
50
|
"value": [
|
|
46
51
|
{
|
|
47
52
|
name: "CBC",
|
|
48
|
-
off: [
|
|
53
|
+
off: [6, 7]
|
|
49
54
|
},
|
|
50
55
|
{
|
|
51
56
|
name: "CFB",
|
|
52
|
-
off: [
|
|
57
|
+
off: [6, 7]
|
|
53
58
|
},
|
|
54
59
|
{
|
|
55
60
|
name: "OFB",
|
|
56
|
-
off: [
|
|
61
|
+
off: [6, 7]
|
|
57
62
|
},
|
|
58
63
|
{
|
|
59
64
|
name: "CTR",
|
|
60
|
-
off: [
|
|
65
|
+
off: [6, 7]
|
|
61
66
|
},
|
|
62
67
|
{
|
|
63
68
|
name: "GCM",
|
|
64
|
-
on: [
|
|
69
|
+
on: [6, 7]
|
|
65
70
|
},
|
|
66
71
|
{
|
|
67
72
|
name: "ECB",
|
|
68
|
-
off: [
|
|
73
|
+
off: [6, 7]
|
|
69
74
|
},
|
|
70
75
|
{
|
|
71
76
|
name: "CBC/NoPadding",
|
|
72
|
-
off: [
|
|
77
|
+
off: [6, 7]
|
|
73
78
|
},
|
|
74
79
|
{
|
|
75
80
|
name: "ECB/NoPadding",
|
|
76
|
-
off: [
|
|
81
|
+
off: [6, 7]
|
|
77
82
|
}
|
|
78
83
|
]
|
|
79
84
|
},
|
|
@@ -98,6 +103,26 @@ class AESDecrypt extends Operation {
|
|
|
98
103
|
"type": "toggleString",
|
|
99
104
|
"value": "",
|
|
100
105
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "IV from input",
|
|
109
|
+
"type": "argSelector",
|
|
110
|
+
"value": [
|
|
111
|
+
{
|
|
112
|
+
name: "Off",
|
|
113
|
+
on: [1],
|
|
114
|
+
off: [2]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "From start",
|
|
118
|
+
on: [2],
|
|
119
|
+
off: [1]
|
|
120
|
+
}, {
|
|
121
|
+
name: "From end",
|
|
122
|
+
on: [2],
|
|
123
|
+
off: [1]
|
|
124
|
+
}
|
|
125
|
+
]
|
|
101
126
|
}
|
|
102
127
|
];
|
|
103
128
|
}
|
|
@@ -110,14 +135,18 @@ class AESDecrypt extends Operation {
|
|
|
110
135
|
* @throws {OperationError} if cannot decrypt input or invalid key length
|
|
111
136
|
*/
|
|
112
137
|
run(input, args) {
|
|
138
|
+
let iv;
|
|
139
|
+
|
|
113
140
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
|
114
|
-
|
|
115
|
-
mode = args[
|
|
116
|
-
noPadding = args[
|
|
117
|
-
inputType = args[
|
|
118
|
-
outputType = args[
|
|
119
|
-
gcmTag = Utils.convertToByteString(args[
|
|
120
|
-
aad = Utils.convertToByteString(args[
|
|
141
|
+
ivLength = args[2],
|
|
142
|
+
mode = args[3].split("/")[0],
|
|
143
|
+
noPadding = args[3].endsWith("NoPadding"),
|
|
144
|
+
inputType = args[4],
|
|
145
|
+
outputType = args[5],
|
|
146
|
+
gcmTag = Utils.convertToByteString(args[6].string, args[6].option),
|
|
147
|
+
aad = Utils.convertToByteString(args[7].string, args[7].option),
|
|
148
|
+
ivFromInput = args[8];
|
|
149
|
+
|
|
121
150
|
|
|
122
151
|
if ([16, 24, 32].indexOf(key.length) < 0) {
|
|
123
152
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
|
@@ -130,11 +159,27 @@ The following algorithms will be used based on the size of the key:
|
|
|
130
159
|
|
|
131
160
|
input = Utils.convertToByteString(input, inputType);
|
|
132
161
|
|
|
162
|
+
if (ivFromInput !== "Off") {
|
|
163
|
+
if (input.length <= ivLength) {
|
|
164
|
+
throw new OperationError(`Input is too short to contain an IV of ${ivLength} bytes.`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (ivFromInput === "From start") {
|
|
168
|
+
iv = input.substr(0, ivLength);
|
|
169
|
+
input = input.substr(ivLength);
|
|
170
|
+
} else {
|
|
171
|
+
iv = input.substr(input.length - ivLength);
|
|
172
|
+
input = input.substr(0, input.length - ivLength);
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
iv = Utils.convertToByteString(args[1].string, args[1].option);
|
|
176
|
+
}
|
|
177
|
+
|
|
133
178
|
const decipher = forge.cipher.createDecipher("AES-" + mode, key);
|
|
134
179
|
|
|
135
180
|
/* Allow for a "no padding" mode */
|
|
136
181
|
if (noPadding) {
|
|
137
|
-
decipher.mode.unpad = function(output, options) {
|
|
182
|
+
decipher.mode.unpad = function (output, options) {
|
|
138
183
|
return true;
|
|
139
184
|
};
|
|
140
185
|
}
|
|
@@ -8,6 +8,7 @@ import Operation from "../Operation.mjs";
|
|
|
8
8
|
import Utils from "../Utils.mjs";
|
|
9
9
|
import forge from "node-forge";
|
|
10
10
|
import OperationError from "../errors/OperationError.mjs";
|
|
11
|
+
import { toHexFast } from "../lib/Hex.mjs";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* AES Encrypt operation
|
|
@@ -92,6 +93,11 @@ class AESEncrypt extends Operation {
|
|
|
92
93
|
"type": "toggleString",
|
|
93
94
|
"value": "",
|
|
94
95
|
"toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Include IV in output",
|
|
99
|
+
"type": "option",
|
|
100
|
+
"value": ["Off", "Prepend", "Append"]
|
|
95
101
|
}
|
|
96
102
|
];
|
|
97
103
|
}
|
|
@@ -107,10 +113,11 @@ class AESEncrypt extends Operation {
|
|
|
107
113
|
const key = Utils.convertToByteString(args[0].string, args[0].option),
|
|
108
114
|
iv = Utils.convertToByteString(args[1].string, args[1].option),
|
|
109
115
|
mode = args[2].split("/")[0],
|
|
110
|
-
noPadding =
|
|
116
|
+
noPadding = args[2].endsWith("NoPadding"),
|
|
111
117
|
inputType = args[3],
|
|
112
118
|
outputType = args[4],
|
|
113
|
-
aad = Utils.convertToByteString(args[5].string, args[5].option)
|
|
119
|
+
aad = Utils.convertToByteString(args[5].string, args[5].option),
|
|
120
|
+
includeIV = args[6];
|
|
114
121
|
|
|
115
122
|
if ([16, 24, 32].indexOf(key.length) < 0) {
|
|
116
123
|
throw new OperationError(`Invalid key length: ${key.length} bytes
|
|
@@ -133,26 +140,34 @@ The following algorithms will be used based on the size of the key:
|
|
|
133
140
|
additionalData: mode === "GCM" ? aad : undefined
|
|
134
141
|
});
|
|
135
142
|
if (noPadding) {
|
|
136
|
-
cipher.mode.pad = function(output, options) {
|
|
143
|
+
cipher.mode.pad = function (output, options) {
|
|
137
144
|
return true;
|
|
138
145
|
};
|
|
139
146
|
}
|
|
140
147
|
cipher.update(forge.util.createBuffer(input));
|
|
141
148
|
cipher.finish();
|
|
142
149
|
|
|
150
|
+
let output = cipher.output.getBytes();
|
|
151
|
+
|
|
152
|
+
if (includeIV === "Prepend") {
|
|
153
|
+
output = iv + output;
|
|
154
|
+
} else if (includeIV === "Append") {
|
|
155
|
+
output = output + iv;
|
|
156
|
+
}
|
|
157
|
+
|
|
143
158
|
if (outputType === "Hex") {
|
|
159
|
+
output = toHexFast(Utils.strToByteArray(output));
|
|
160
|
+
|
|
144
161
|
if (mode === "GCM") {
|
|
145
|
-
return
|
|
162
|
+
return output + "\n\n" +
|
|
146
163
|
"Tag: " + cipher.mode.tag.toHex();
|
|
147
164
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return cipher.output.getBytes() + "\n\n" +
|
|
152
|
-
"Tag: " + cipher.mode.tag.getBytes();
|
|
153
|
-
}
|
|
154
|
-
return cipher.output.getBytes();
|
|
165
|
+
} else if (mode === "GCM") {
|
|
166
|
+
return output + "\n\n" +
|
|
167
|
+
"Tag: " + cipher.mode.tag.getBytes();
|
|
155
168
|
}
|
|
169
|
+
|
|
170
|
+
return output;
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
}
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
8
|
import OperationError from "../errors/OperationError.mjs";
|
|
9
|
-
import
|
|
9
|
+
import Utils from "../Utils.mjs";
|
|
10
|
+
import { blake3 } from "@noble/hashes/blake3.js";
|
|
11
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* BLAKE3 operation
|
|
12
15
|
*/
|
|
@@ -44,13 +47,16 @@ class BLAKE3 extends Operation {
|
|
|
44
47
|
run(input, args) {
|
|
45
48
|
const key = args[1];
|
|
46
49
|
const size = args[0];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const opts = { dkLen: size };
|
|
51
|
+
const inputBytes = new Uint8Array(Utils.strToArrayBuffer(input));
|
|
52
|
+
if (key !== "") {
|
|
53
|
+
const keyBytes = new Uint8Array(Utils.strToArrayBuffer(key));
|
|
54
|
+
if (keyBytes.length !== 32) {
|
|
55
|
+
throw new OperationError("The key must be exactly 32 bytes long");
|
|
56
|
+
}
|
|
57
|
+
opts.key = keyBytes;
|
|
52
58
|
}
|
|
53
|
-
return blake3(
|
|
59
|
+
return bytesToHex(blake3(inputBytes, opts));
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
|
-
import
|
|
8
|
+
import { deserialize } from "bson";
|
|
9
9
|
import OperationError from "../errors/OperationError.mjs";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -37,7 +37,7 @@ class BSONDeserialise extends Operation {
|
|
|
37
37
|
if (!input.byteLength) return "";
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
|
-
const data =
|
|
40
|
+
const data = deserialize(new Uint8Array(input));
|
|
41
41
|
return JSON.stringify(data, null, 2);
|
|
42
42
|
} catch (err) {
|
|
43
43
|
throw new OperationError(err.toString());
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
|
-
import
|
|
8
|
+
import { serialize } from "bson";
|
|
9
9
|
import OperationError from "../errors/OperationError.mjs";
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -38,7 +38,8 @@ class BSONSerialise extends Operation {
|
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
40
|
const data = JSON.parse(input);
|
|
41
|
-
|
|
41
|
+
const result = serialize(data);
|
|
42
|
+
return result.buffer.slice(result.byteOffset, result.byteOffset + result.byteLength);
|
|
42
43
|
} catch (err) {
|
|
43
44
|
throw new OperationError(err.toString());
|
|
44
45
|
}
|
|
@@ -43,7 +43,7 @@ class Bcrypt extends Operation {
|
|
|
43
43
|
const rounds = args[0];
|
|
44
44
|
const salt = await bcrypt.genSalt(rounds);
|
|
45
45
|
|
|
46
|
-
return await bcrypt.hash(input, salt,
|
|
46
|
+
return await bcrypt.hash(input, salt, undefined, p => {
|
|
47
47
|
// Progress callback
|
|
48
48
|
if (isWorkerEnvironment())
|
|
49
49
|
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
|
@@ -43,7 +43,7 @@ class BcryptCompare extends Operation {
|
|
|
43
43
|
async run(input, args) {
|
|
44
44
|
const hash = args[0];
|
|
45
45
|
|
|
46
|
-
const match = await bcrypt.compare(input, hash,
|
|
46
|
+
const match = await bcrypt.compare(input, hash, undefined, p => {
|
|
47
47
|
// Progress callback
|
|
48
48
|
if (isWorkerEnvironment())
|
|
49
49
|
self.sendStatusMessage(`Progress: ${(p * 100).toFixed(0)}%`);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
|
+
import OperationError from "../errors/OperationError.mjs";
|
|
8
9
|
import cptable from "codepage";
|
|
9
10
|
import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
|
|
10
11
|
|
|
@@ -48,6 +49,9 @@ class DecodeText extends Operation {
|
|
|
48
49
|
*/
|
|
49
50
|
run(input, args) {
|
|
50
51
|
const format = CHR_ENC_CODE_PAGES[args[0]];
|
|
52
|
+
if (!format) {
|
|
53
|
+
throw new OperationError("Invalid encoding");
|
|
54
|
+
}
|
|
51
55
|
return cptable.utils.decode(format, new Uint8Array(input));
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Operation from "../Operation.mjs";
|
|
8
|
+
import OperationError from "../errors/OperationError.mjs";
|
|
8
9
|
import cptable from "codepage";
|
|
9
10
|
import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs";
|
|
10
11
|
|
|
@@ -48,6 +49,9 @@ class EncodeText extends Operation {
|
|
|
48
49
|
*/
|
|
49
50
|
run(input, args) {
|
|
50
51
|
const format = CHR_ENC_CODE_PAGES[args[0]];
|
|
52
|
+
if (!format) {
|
|
53
|
+
throw new OperationError("Invalid encoding");
|
|
54
|
+
}
|
|
51
55
|
const encoded = cptable.utils.encode(format, input);
|
|
52
56
|
return new Uint8Array(encoded).buffer;
|
|
53
57
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author HarelKatz [github.com/HarelKatz]
|
|
3
|
+
* @copyright Crown Copyright 2026
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Operation from "../Operation.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Escape Smart Characters operation
|
|
11
|
+
*/
|
|
12
|
+
class EscapeSmartCharacters extends Operation {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* EscapeSmartCharacters constructor
|
|
16
|
+
*/
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
this.name = "Escape Smart Characters";
|
|
21
|
+
this.module = "Default";
|
|
22
|
+
this.description = "Converts smart (typographic) Unicode characters — e.g. smart quotes, em/en dashes, ellipses, ©, ®, ™, arrows — into their plain ASCII equivalents.<br><br>Characters with no ASCII mapping (e.g. <code>☣</code>) are handled according to the 'Unmappable characters' option.<br><br>e.g. <code>“Hello” — world…</code> becomes <code>\"Hello\" -- world...</code>";
|
|
23
|
+
this.infoURL = "";
|
|
24
|
+
this.inputType = "string";
|
|
25
|
+
this.outputType = "string";
|
|
26
|
+
this.args = [
|
|
27
|
+
{
|
|
28
|
+
name: "Unmappable characters",
|
|
29
|
+
type: "option",
|
|
30
|
+
value: ["Include", "Remove", "Replace with '.'"]
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} input
|
|
37
|
+
* @param {Object[]} args
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
run(input, args) {
|
|
41
|
+
const [unmappable] = args;
|
|
42
|
+
let result = "";
|
|
43
|
+
for (const ch of input) {
|
|
44
|
+
if (ch.codePointAt(0) < 128) {
|
|
45
|
+
result += ch;
|
|
46
|
+
} else if (Object.prototype.hasOwnProperty.call(SMART_MAP, ch)) {
|
|
47
|
+
result += SMART_MAP[ch];
|
|
48
|
+
} else {
|
|
49
|
+
switch (unmappable) {
|
|
50
|
+
case "Remove":
|
|
51
|
+
break;
|
|
52
|
+
case "Replace with '.'":
|
|
53
|
+
result += ".";
|
|
54
|
+
break;
|
|
55
|
+
case "Include":
|
|
56
|
+
default:
|
|
57
|
+
result += ch;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const SMART_MAP = {
|
|
68
|
+
// Smart double quotes
|
|
69
|
+
"“": "\"", // “ left double quotation mark
|
|
70
|
+
"”": "\"", // ” right double quotation mark
|
|
71
|
+
"„": "\"", // „ double low-9 quotation mark
|
|
72
|
+
"‟": "\"", // ‟ double high-reversed-9 quotation mark
|
|
73
|
+
"″": "\"", // ″ double prime
|
|
74
|
+
|
|
75
|
+
// Smart single quotes / apostrophes
|
|
76
|
+
"‘": "'", // ‘ left single quotation mark
|
|
77
|
+
"’": "'", // ’ right single quotation mark / apostrophe
|
|
78
|
+
"‚": "'", // ‚ single low-9 quotation mark
|
|
79
|
+
"‛": "'", // ‛ single high-reversed-9 quotation mark
|
|
80
|
+
"′": "'", // ′ prime
|
|
81
|
+
|
|
82
|
+
// Dashes & hyphens
|
|
83
|
+
"‐": "-", // ‐ hyphen
|
|
84
|
+
"‑": "-", // ‑ non-breaking hyphen
|
|
85
|
+
"‒": "-", // ‒ figure dash
|
|
86
|
+
"–": "-", // – en dash
|
|
87
|
+
"—": "--", // — em dash
|
|
88
|
+
"―": "--", // ― horizontal bar
|
|
89
|
+
|
|
90
|
+
// Ellipsis
|
|
91
|
+
"…": "...", // …
|
|
92
|
+
|
|
93
|
+
// Trademark / copyright symbols
|
|
94
|
+
"©": "(c)", // ©
|
|
95
|
+
"®": "(r)", // ®
|
|
96
|
+
"™": "(tm)", // ™
|
|
97
|
+
|
|
98
|
+
// Arrows
|
|
99
|
+
"←": "<--", // ←
|
|
100
|
+
"→": "-->", // →
|
|
101
|
+
"↑": "^", // ↑
|
|
102
|
+
"↓": "v", // ↓
|
|
103
|
+
"↔": "<->", // ↔
|
|
104
|
+
"⇐": "<==", // ⇐
|
|
105
|
+
"⇒": "==>", // ⇒
|
|
106
|
+
"⇔": "<=>", // ⇔
|
|
107
|
+
|
|
108
|
+
// Guillemets
|
|
109
|
+
"«": "<<", // «
|
|
110
|
+
"»": ">>", // »
|
|
111
|
+
"‹": "<", // ‹
|
|
112
|
+
"›": ">", // ›
|
|
113
|
+
|
|
114
|
+
// Math & misc symbols
|
|
115
|
+
"×": "x", // ×
|
|
116
|
+
"÷": "/", // ÷
|
|
117
|
+
"±": "+/-", // ±
|
|
118
|
+
"•": "*", // •
|
|
119
|
+
"·": ".", // ·
|
|
120
|
+
|
|
121
|
+
// Non-ASCII spaces
|
|
122
|
+
"\u00A0": " ", // NBSP
|
|
123
|
+
"\u2002": " ", // en space
|
|
124
|
+
"\u2003": " ", // em space
|
|
125
|
+
"\u2009": " ", // thin space
|
|
126
|
+
"\u200A": " " // hair space
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default EscapeSmartCharacters;
|
|
@@ -50,6 +50,14 @@ class GenerateDeBruijnSequence extends Operation {
|
|
|
50
50
|
throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive).");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
if (!Number.isInteger(k)) {
|
|
54
|
+
throw new OperationError("Invalid alphabet size, required to be integer.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!Number.isInteger(n)) {
|
|
58
|
+
throw new OperationError("Invalid key length, required to be integer.");
|
|
59
|
+
}
|
|
60
|
+
|
|
53
61
|
if (n < 2) {
|
|
54
62
|
throw new OperationError("Invalid key length, required to be at least 2.");
|
|
55
63
|
}
|
|
@@ -8,6 +8,10 @@ import Operation from "../Operation.mjs";
|
|
|
8
8
|
import OperationError from "../errors/OperationError.mjs";
|
|
9
9
|
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum.mjs";
|
|
10
10
|
|
|
11
|
+
// arbitrary limits set to avoid DoS by requesting ridiculous amounts of data
|
|
12
|
+
const maxLoremWords = 100_000; // same limit also used for paragraphs/sentences
|
|
13
|
+
const maxLoremCharacters = 1_000_000;
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* Generate Lorem Ipsum operation
|
|
13
17
|
*/
|
|
@@ -47,9 +51,7 @@ class GenerateLoremIpsum extends Operation {
|
|
|
47
51
|
*/
|
|
48
52
|
run(input, args) {
|
|
49
53
|
const [length, lengthType] = args;
|
|
50
|
-
|
|
51
|
-
throw new OperationError("Length must be greater than 0");
|
|
52
|
-
}
|
|
54
|
+
checkLimits(lengthType, length);
|
|
53
55
|
switch (lengthType) {
|
|
54
56
|
case "Paragraphs":
|
|
55
57
|
return GenerateParagraphs(length);
|
|
@@ -68,3 +70,32 @@ class GenerateLoremIpsum extends Operation {
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export default GenerateLoremIpsum;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* check combined validity of lengthType and length arguments
|
|
76
|
+
* @param {string} lengthType
|
|
77
|
+
* @param {number} length
|
|
78
|
+
* @throws {OperationError}
|
|
79
|
+
*/
|
|
80
|
+
function checkLimits(lengthType, length) {
|
|
81
|
+
if (length < 1) {
|
|
82
|
+
throw new OperationError("Length must be greater than 0");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
switch (lengthType) {
|
|
86
|
+
case "Paragraphs":
|
|
87
|
+
case "Sentences":
|
|
88
|
+
case "Words":
|
|
89
|
+
if (length > maxLoremWords) {
|
|
90
|
+
throw new OperationError("Length must be less than " + maxLoremWords);
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case "Bytes":
|
|
94
|
+
if (length > maxLoremCharacters) {
|
|
95
|
+
throw new OperationError("Length must be less than " + maxLoremCharacters);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
throw new OperationError("Invalid length type");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -12,6 +12,7 @@ import { getSubkeySize, ASP } from "../lib/PGP.mjs";
|
|
|
12
12
|
import { cryptNotice } from "../lib/Crypt.mjs";
|
|
13
13
|
import * as es6promisify from "es6-promisify";
|
|
14
14
|
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
|
|
15
|
+
const KEY_FLAGS = kbpgp.const.openpgp.key_flags;
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -73,11 +74,11 @@ class GeneratePGPKeyPair extends Operation {
|
|
|
73
74
|
if (name) userIdentifier += name;
|
|
74
75
|
if (email) userIdentifier += ` <${email}>`;
|
|
75
76
|
|
|
76
|
-
let flags =
|
|
77
|
-
flags |=
|
|
78
|
-
flags |=
|
|
79
|
-
flags |=
|
|
80
|
-
flags |=
|
|
77
|
+
let flags = KEY_FLAGS.certify_keys;
|
|
78
|
+
flags |= KEY_FLAGS.sign_data;
|
|
79
|
+
flags |= KEY_FLAGS.auth;
|
|
80
|
+
flags |= KEY_FLAGS.encrypt_comm;
|
|
81
|
+
flags |= KEY_FLAGS.encrypt_storage;
|
|
81
82
|
|
|
82
83
|
const keyGenerationOptions = {
|
|
83
84
|
userid: userIdentifier,
|
|
@@ -89,11 +90,11 @@ class GeneratePGPKeyPair extends Operation {
|
|
|
89
90
|
},
|
|
90
91
|
subkeys: [{
|
|
91
92
|
"nbits": getSubkeySize(keySize),
|
|
92
|
-
"flags":
|
|
93
|
+
"flags": KEY_FLAGS.sign_data,
|
|
93
94
|
"expire_in": 86400 * 365 * 8
|
|
94
95
|
}, {
|
|
95
96
|
"nbits": getSubkeySize(keySize),
|
|
96
|
-
"flags":
|
|
97
|
+
"flags": KEY_FLAGS.encrypt_comm | KEY_FLAGS.encrypt_storage,
|
|
97
98
|
"expire_in": 86400 * 365 * 2
|
|
98
99
|
}],
|
|
99
100
|
asp: ASP
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author GCHQDeveloper581
|
|
3
|
+
* @copyright Crown Copyright 2026
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Operation from "../Operation.mjs";
|
|
8
|
+
import kbpgp from "kbpgp";
|
|
9
|
+
import { ASP, importPrivateKey } from "../lib/PGP.mjs";
|
|
10
|
+
import OperationError from "../errors/OperationError.mjs";
|
|
11
|
+
import * as es6promisify from "es6-promisify";
|
|
12
|
+
const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* PGP Sign operation
|
|
16
|
+
*/
|
|
17
|
+
class PGPSign extends Operation {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* PGPSign constructor
|
|
21
|
+
*/
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
|
|
25
|
+
this.name = "PGP Sign";
|
|
26
|
+
this.module = "PGP";
|
|
27
|
+
this.description = [
|
|
28
|
+
"Input: the message you want to sign",
|
|
29
|
+
"<br><br>",
|
|
30
|
+
"Arguments: the ASCII-armoured PGP private key of the sender.",
|
|
31
|
+
"<br><br>",
|
|
32
|
+
"Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
|
|
33
|
+
"<br><br>",
|
|
34
|
+
"This function uses the Keybase implementation of PGP.",
|
|
35
|
+
].join("\n");
|
|
36
|
+
this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; // Usually a Wikipedia link. Remember to remove localisation (i.e. https://wikipedia.org/etc rather than https://en.wikipedia.org/etc)
|
|
37
|
+
this.inputType = "string";
|
|
38
|
+
this.outputType = "string";
|
|
39
|
+
this.args = [
|
|
40
|
+
{
|
|
41
|
+
"name": "Private key of signer",
|
|
42
|
+
"type": "text",
|
|
43
|
+
"value": ""
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "Private key passphrase (optional)",
|
|
47
|
+
"type": "string",
|
|
48
|
+
"value": ""
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} input
|
|
55
|
+
* @param {Object[]} args
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*
|
|
58
|
+
* @throws {OperationError} if failed private key import or failed encryption
|
|
59
|
+
*/
|
|
60
|
+
async run(input, args) {
|
|
61
|
+
const message = input,
|
|
62
|
+
[privateKey, passphrase] = args;
|
|
63
|
+
let signedMessage;
|
|
64
|
+
|
|
65
|
+
if (!privateKey) throw new OperationError("Enter the private key of the signer.");
|
|
66
|
+
const privKey = await importPrivateKey(privateKey, passphrase);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
signedMessage = await promisify(kbpgp.box)({
|
|
70
|
+
"msg": message,
|
|
71
|
+
"sign_with": privKey,
|
|
72
|
+
"asp": ASP
|
|
73
|
+
});
|
|
74
|
+
} catch (err) {
|
|
75
|
+
throw new OperationError(`Couldn't sign message: ${err}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return signedMessage;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default PGPSign;
|