2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-05 23:53:27 +00:00

Compare commits

...

546 Commits

Author SHA1 Message Date
n1474335
4d4ebf7279 8.38.0 2019-07-03 17:28:23 +01:00
n1474335
892632a814 Updated CHANGELOG 2019-07-03 17:28:19 +01:00
n1474335
2ba6a3fd8d Merge branch 'MShwed-feature/streebog-hash' 2019-07-03 17:24:23 +01:00
n1474335
09bea6e231 Added 'GOST Hash' operation 2019-07-03 17:24:11 +01:00
n1474335
99f4091c1a Tidied up Streebog operation, splitting out GOST hash into a separate op. 2019-07-03 17:23:45 +01:00
n1474335
666c447e36 Merge branch 'feature/streebog-hash' of https://github.com/MShwed/CyberChef into MShwed-feature/streebog-hash 2019-07-03 15:21:44 +01:00
n1474335
a43dff558a 8.37.0 2019-07-03 15:10:03 +01:00
n1474335
f39006ea1d Updated CHANGELOG 2019-07-03 15:09:59 +01:00
n1474335
2355ef9d31 Merge branch 'MShwed-feature/crc-8-checksum' 2019-07-03 15:07:38 +01:00
n1474335
13b0ab73d0 Tidied up CRC-8 operation and added it to 'Generate all hashes' 2019-07-03 15:07:26 +01:00
n1474335
3766982734 Merge branch 'feature/crc-8-checksum' of https://github.com/MShwed/CyberChef into MShwed-feature/crc-8-checksum 2019-07-03 14:54:41 +01:00
n1474335
54a864e18c 8.36.1 2019-07-03 14:50:23 +01:00
n1474335
9a1f4e40dd Merge branch 'MShwed-bugfix/missing-html-entities' 2019-07-03 14:50:15 +01:00
n1474335
5a83ef4f48 Tidied up HTML Entity operations 2019-07-03 14:49:57 +01:00
n1474335
a7d4e37851 Merge branch 'bugfix/missing-html-entities' of https://github.com/MShwed/CyberChef into MShwed-bugfix/missing-html-entities 2019-07-03 12:43:58 +01:00
n1474335
57e71f0e1a 8.36.0 2019-07-03 12:41:59 +01:00
n1474335
30be3cab53 Updated CHANGELOG 2019-07-03 12:41:52 +01:00
n1474335
0e6626bf39 Merge branch 'pgp-verify' of https://github.com/artemisbot/CyberChef into artemisbot-pgp-verify 2019-07-03 12:38:10 +01:00
n1474335
bb0eaf4597 8.35.0 2019-07-03 12:34:17 +01:00
n1474335
8ada3bae0c Updated CHANGELOG 2019-07-03 12:34:13 +01:00
n1474335
db72cad610 Merge branch 'more-image-ops' of https://github.com/j433866/CyberChef into j433866-more-image-ops 2019-07-03 12:24:03 +01:00
j433866
bed66298d1 Change jpeg test data to be a full image 2019-07-03 08:53:44 +01:00
mshwed
44643c151a Fixed linting issues and added CRC-8 tests 2019-07-02 15:30:59 -04:00
mshwed
bc1bd2427d Added checksum calculation and helper functions 2019-07-02 15:13:11 -04:00
n1474335
6726994ad5 8.34.3 2019-07-02 18:40:03 +01:00
n1474335
5abc5279f5 Fixed spread operator support in older browsers 2019-07-02 18:39:21 +01:00
j433866
1135ca5fb4 Remove duplicate function.
Fix reading QR codes with transparent backgrounds
2019-07-02 16:31:26 +01:00
j433866
be08a62f52 Add webpack config for font files 2019-07-02 15:31:29 +01:00
j433866
f473807459 Bring up to date with master 2019-07-02 14:23:17 +01:00
mshwed
4c9a61f5a7 Added bit reversal 2019-07-01 19:02:07 -04:00
mshwed
550ab403f6 Initial operation setup 2019-06-30 21:28:00 -04:00
n1474335
d148cae814 8.34.2 2019-06-28 17:13:58 +01:00
n1474335
f22211ce8c Backslashes are now escaped correctly by 'Unescape string'. CLoses #582 2019-06-28 17:13:54 +01:00
n1474335
f9c24f2528 8.34.1 2019-06-28 17:09:04 +01:00
n1474335
6f6786d79e Baking controls now scale to fit the pane correctly 2019-06-28 17:09:00 +01:00
n1474335
a5ea7f7d58 8.34.0 2019-06-28 15:03:55 +01:00
n1474335
d637ac7633 Updated CHANGELOG 2019-06-28 15:03:49 +01:00
n1474335
c1ad2386ef Improvements to Entropy operation. Converted to ArrayBuffers, improved efficiency with large files, added present method back in. 2019-06-28 15:00:19 +01:00
mshwed
b0b6de116d Fixed linting issue 2019-06-27 14:11:26 -04:00
mshwed
1b161f997b Refactored advanced entropy operation into entropy operation 2019-06-27 14:09:41 -04:00
mshwed
b99af58636 Merge branch 'master' of https://github.com/gchq/CyberChef into feature/advanced-entropy 2019-06-27 13:15:19 -04:00
Matt
c46928aefe Added tests 2019-06-27 17:51:37 +01:00
n1474335
d4edbb3c3e 8.33.1 2019-06-27 17:30:43 +01:00
n1474335
1b765605ca Updated dependencies 2019-06-27 17:29:27 +01:00
n1474335
8286dc26ad Updated patch dependencies 2019-06-27 17:06:07 +01:00
n1474335
19a438c15b Updated Bootstrap dependecy 2019-06-27 16:58:26 +01:00
n1474335
12898a1a8e Merge branch 'artemisbot-features/bz2-comp' 2019-06-27 16:48:15 +01:00
Matt
4c27a46d35 Added PGP verify operation
Added some more metadata to decrypt & verify
Fixes gchq/CyberChef#581
2019-06-27 16:48:03 +01:00
n1474335
541f2a2988 8.33.0 2019-06-27 16:47:32 +01:00
n1474335
d0277dde3f Updated CHANGELOG 2019-06-27 16:47:26 +01:00
n1474335
d184e40116 Added min and max for Bzip2 block size arg 2019-06-27 16:45:16 +01:00
n1474335
596db07647 Merge branch 'features/bz2-comp' of https://github.com/artemisbot/CyberChef into artemisbot-features/bz2-comp 2019-06-27 16:44:42 +01:00
n1474335
c233c5c67e Modified operation request template description 2019-06-27 16:01:44 +01:00
n1474335
db788b57e7 Merge branch 'update-issue-templates' of https://github.com/h345983745/CyberChef into h345983745-update-issue-templates 2019-06-27 15:58:38 +01:00
n1474335
c0983654d8 8.32.1 2019-06-27 15:56:16 +01:00
n1474335
30bf95f2c1 Merge branch 'oct' of https://github.com/wh0/CyberChef into wh0-oct 2019-06-27 15:55:34 +01:00
n1474335
da178107f9 8.32.0 2019-06-27 15:50:30 +01:00
n1474335
4dda1d9e49 Updated CHANGELOG 2019-06-27 15:45:17 +01:00
n1474335
e11aec64cd Modified wording for IC op 2019-06-27 15:42:32 +01:00
n1474335
71575e49d7 Merge branch 'index-of-coincidence' of https://github.com/Ge0rg3/CyberChef into Ge0rg3-index-of-coincidence 2019-06-27 15:41:20 +01:00
n1474335
393d070b05 8.31.8 2019-06-27 15:37:12 +01:00
n1474335
d7e4c28cd0 Merge branch 'bugfix/json-to-csv' of https://github.com/MShwed/CyberChef into MShwed-bugfix/json-to-csv 2019-06-27 15:36:12 +01:00
n1474335
ccf2348cd6 8.31.7 2019-06-27 15:34:22 +01:00
n1474335
cde3eb2c39 Merge branch 'popover-patch' of https://github.com/Ge0rg3/CyberChef into Ge0rg3-popover-patch 2019-06-27 15:31:57 +01:00
wh0
f29d8eeda8 Parse octal escape sequences 2019-06-25 18:35:25 -07:00
mshwed
daee7ac761 Merge branch 'master' of https://github.com/gchq/CyberChef into feature/advanced-entropy 2019-06-20 14:11:51 -04:00
mshwed
313d1a580e Merge branch 'master' of https://github.com/gchq/CyberChef into bugfix/json-to-csv 2019-06-20 13:59:08 -04:00
mshwed
824d8e929a Fixed linting issues 2019-06-20 13:26:35 -04:00
mshwed
862d2adf1d Added missing HTML entities 2019-06-20 13:08:20 -04:00
j433866
0bcf57e89c Improve printing text to improve output quality.
Now resizes the text instead of the image
2019-06-20 12:58:02 +01:00
j433866
ca9bab5d4c 8.31.6 2019-06-14 14:32:15 +01:00
j433866
e35ef8f39b Escape HTML for error messages being sent to alert 2019-06-14 14:31:38 +01:00
George O
e709582062 Disappearing Popover Fix 2019-06-10 19:19:50 +01:00
George O
a6732ba815 Added Index of Coincidence Tests 2019-06-09 00:57:02 +01:00
George O
466d872d30 Added Index of Coincidence Operation 2019-06-09 00:57:02 +01:00
j433866
3cf7238106 8.31.5 2019-06-07 11:04:00 +01:00
j433866
f8d08cc5db Merge branch 'j433866-aes-gcm-fix' 2019-06-07 10:55:53 +01:00
j433866
c1bdca8df3 Change to use byte strings instead of byte array 2019-06-04 10:19:00 +01:00
j433866
2be2c83f67 8.31.4 2019-05-21 16:56:23 +01:00
j433866
a271eaabd0 Merge branch 'j433866-load-regex-fix' 2019-05-21 16:53:47 +01:00
h345983745
1d130c88a8 Inital commit 2019-05-19 15:40:17 +01:00
j433866
6f5018d45e Don't change option value if new value is blank 2019-05-16 11:59:25 +01:00
j433866
f51ee76c72 Merge pull request #3 from gchq/master
Bring up to date with gchq/master
2019-05-16 11:53:58 +01:00
j433866
0c9e8fe050 8.31.3 2019-05-09 17:07:12 +01:00
j433866
8f41571e47 Merge branch 'd98762625-use-node-lts' 2019-05-09 16:43:08 +01:00
j433866
6d14368e2f Merge branch 'use-node-lts' of https://github.com/d98762625/CyberChef into d98762625-use-node-lts 2019-05-09 16:42:19 +01:00
j433866
f90ad48906 Update dependencies 2019-05-09 16:20:42 +01:00
j433866
e95dac82c2 8.31.2 2019-05-09 11:56:27 +01:00
j433866
144601ffd4 Merge branch 'j433866-load-recipe-fix' 2019-05-09 11:54:18 +01:00
j433866
cbcc2aa731 Fix regex to handle multiple escaped backslashes 2019-05-09 10:04:06 +01:00
j433866
f9354c8cd1 Merge remote-tracking branch 'upstream/master' 2019-05-09 09:08:01 +01:00
d98762625
7a4f418e75 configure travis to use node LTS instead of latest 2019-04-29 17:10:40 +01:00
mshwed
8fa8e34027 Added support for parsing JSON with number type values. Added support for non-array JSON objects. Added extra tests for JSON to CSV operation. 2019-04-28 16:29:15 -04:00
mshwed
5225874498 Fixed handling of large files and fixed issue with line histogram colour fill 2019-04-28 14:38:03 -04:00
mshwed
802493fec4 Merge branch 'master' of https://github.com/gchq/CyberChef into feature/advanced-entropy 2019-04-24 19:48:03 -04:00
n1474335
01f0625d6a Fixed XSS in 'Text Encoding Brute Force. Closes #539 2019-04-14 22:00:17 +01:00
n1474335
38ff7ec89f 8.31.1 2019-04-14 21:56:01 +01:00
n1474335
7163a0802d Tidied up build directory 2019-04-14 21:55:52 +01:00
n1474335
8d0fcf37c5 8.31.0 2019-04-12 18:58:48 +01:00
n1474335
3da5a8bb34 Merge branch 'downloadable-zip' 2019-04-12 18:58:36 +01:00
n1474335
fad33b583b Updated CHANGELOG 2019-04-12 18:58:22 +01:00
n1474335
8f450501cc Downloadble version is now a .zip file instead of a single .htm file 2019-04-12 18:54:31 +01:00
n1474335
b3ae0e577a 8.30.1 2019-04-12 16:25:47 +01:00
n1474335
aedac94e40 Merge branch 'd98762625-init-dish' 2019-04-12 16:25:38 +01:00
n1474335
08c5dbce09 Merge branch 'init-dish' of https://github.com/d98762625/CyberChef into d98762625-init-dish 2019-04-12 16:24:23 +01:00
n1474335
482d658de7 8.30.0 2019-04-12 16:20:00 +01:00
n1474335
39e34081fc Updated CHANGELOG 2019-04-12 16:18:25 +01:00
n1474335
5797786a75 Merge branch 'feature-protobuf' 2019-04-12 16:16:03 +01:00
n1474335
f6977ea264 Added test for 'Decode Protobuf' operation 2019-04-12 16:13:10 +01:00
d98762625
cdc15c0f20 change empty Dish initialisation to use ArrayBuffer 2019-04-08 17:58:46 +01:00
Matt
18408901be removed old bzip2 dependency 2019-04-07 19:11:46 +01:00
Matt
982c915931 Change author 2019-04-07 19:02:27 +01:00
Matt
a339eacd45 Bzip2 compression support changed to use wasm backend
x4 speed.
2019-04-07 18:59:03 +01:00
mshwed
8fc0e012e3 Fixed formatting issues 2019-04-06 23:09:46 -04:00
mshwed
b7fb9635e5 Added operation for entropy visualization as an image 2019-04-06 19:40:07 -04:00
mshwed
f988a958bb Added support for generating an entropy curve based on the input data 2019-04-06 15:59:36 -04:00
mshwed
c80cb57b07 Added histogram line, refactored axes generation 2019-04-05 14:30:24 -04:00
mshwed
dec28e16d4 Added histogram visualization for text entropy 2019-04-05 11:12:44 -04:00
n1474335
525cb0689f Added 'Protobuf Decode', 'VarInt Decode' and 'VarInt Encode' operations 2019-04-02 17:27:14 +01:00
Matt
7796c473ae Fix lint issue 2019-04-02 17:01:47 +01:00
Matt
8445165491 Use all the arraybuffers
cuts a solid 1/3 off the compression time
2019-04-02 16:47:38 +01:00
Matt
c5698fcd65 Merge remote-tracking branch 'upstream/master' into features/bz2-comp 2019-04-02 16:26:29 +01:00
n1474335
786e50c3c3 8.29.1 2019-04-02 15:34:35 +01:00
n1474335
7d03be3a77 Dish._translate now uses ArrayBuffer as its intermediate type instead of byteArray. This should speed up operations on large files. 2019-04-02 15:34:30 +01:00
mshwed
4496d7762b Converted js files to mjs. Added test cases for hashing 2019-04-02 09:41:49 -04:00
Matt
8b12caad78 Merge gchq/master into bz2-comp 2019-04-02 12:08:30 +01:00
Matt
e1492c3bb1 Added (non-garbage) description and fixed wikipedia link. 2019-04-02 12:05:17 +01:00
Matt
3cc66e9db9 Added Bzip2 compression support 2019-04-02 11:55:59 +01:00
mshwed
37389a62c1 Added support for hashing version 1994 and 2012. Added S-Box selection for 1994 version. Added length selection 2019-04-01 23:14:40 -04:00
mshwed
aef65620da Merge remote-tracking branch 'origin/master' into feature/streebog-hash 2019-04-01 21:57:33 -04:00
j433866
99bef09e0e Fix invalid file type error 2019-04-01 14:13:30 +01:00
j433866
c97e77c765 Merge with qr-improvements.
Change QR code ops to use ArrayBuffer.
Add new function to Utils to convert a string to arraybuffer.
2019-04-01 14:11:37 +01:00
j433866
e44a22e143 Change ops to use ArrayBuffer instead of byteArray 2019-04-01 10:54:46 +01:00
j433866
c2496fe63e Change to use Promise.all 2019-04-01 09:00:41 +01:00
n1474335
27677adbe8 Fixed option default 2019-04-01 00:29:10 +01:00
n1474335
6fa06a4f8b 8.29.0 2019-03-31 22:42:16 +01:00
n1474335
a3be4d2945 Merge branch 'h345983745-blake2' 2019-03-31 22:41:13 +01:00
n1474335
3dc5b5c31a Changed input type for BLAKE hashing ops to ArrayBuffer and tidied 2019-03-31 22:40:54 +01:00
n1474335
342e11f83e Merge branch 'blake2' of https://github.com/h345983745/CyberChef into h345983745-blake2 2019-03-31 22:25:40 +01:00
n1474335
b6d78b4001 8.28.0 2019-03-31 21:47:24 +01:00
n1474335
2aaa6db538 Updated CHANGELOG 2019-03-31 21:47:18 +01:00
n1474335
fc909d8199 Fix lint 2019-03-31 21:43:20 +01:00
n1474335
262136393b Merge branch 'artemisbot-module-charts' 2019-03-31 21:41:32 +01:00
n1474335
1640859542 Tidied up charts operations 2019-03-31 21:40:54 +01:00
n1474335
f0b48acaf9 Merge branch 'master' into artemisbot-module-charts 2019-03-31 21:21:08 +01:00
h345983745
bb8c305fc8 Updated description 2019-03-31 18:02:04 +00:00
h345983745
3950dba2c5 Added Key Option 2019-03-31 18:02:04 +00:00
h345983745
903ea45228 Update Generate All Hashes 2019-03-31 18:02:03 +00:00
h345983745
b116b8ba1e Added Output Encoding Option 2019-03-31 18:02:03 +00:00
h345983745
908043fb7f Added to Categories and updated info URL's 2019-03-31 18:02:03 +00:00
h345983745
2d7e3f180e Added to "Generate All Hashes" operation 2019-03-31 18:02:03 +00:00
h345983745
c813d17595 Fixed Author Tags 2019-03-31 18:02:03 +00:00
h345983745
7d16265c4e Initial Commit – Working hash functionality 2019-03-31 18:02:02 +00:00
n1474335
4fb4764d3f 8.27.2 2019-03-30 14:56:53 +00:00
n1474335
2385f1cbf8 Updated dependencies 2019-03-30 14:56:43 +00:00
mshwed
db491cbb09 Added initial hashing for Streebog GOST R 34.11-94 2019-03-28 11:46:04 -04:00
n1474335
3d80d66925 Merge branch 'module-charts' of https://github.com/artemisbot/CyberChef into artemisbot-module-charts 2019-03-27 23:17:30 +00:00
n1474335
ce208b69fe 8.27.1 2019-03-27 23:12:08 +00:00
n1474335
a44418c6a1 Merge branch 'MShwed-feature/hex-support-percent-delimiter' 2019-03-27 23:11:08 +00:00
n1474335
5ac84491c1 Merge branch 'feature/hex-support-percent-delimiter' of https://github.com/MShwed/CyberChef into MShwed-feature/hex-support-percent-delimiter 2019-03-27 23:09:25 +00:00
n1474335
58769eb06e Merge branch 'h345983745-missing-comment' 2019-03-27 23:07:57 +00:00
n1474335
59ae9c6437 Improved 'Extract Files' description 2019-03-27 23:07:43 +00:00
n1474335
81d698c091 Merge branch 'missing-comment' of https://github.com/h345983745/CyberChef into h345983745-missing-comment 2019-03-27 23:03:15 +00:00
n1474335
bce0895392 Updated helper capabilities 2019-03-27 23:02:10 +00:00
h345983745
e909eea82a Added description 2019-03-23 16:14:40 +00:00
n1474335
ef4b977bef Added a cyber helper 2019-03-22 17:10:00 +00:00
mshwed
8707287349 Added percent delimiter for "To Hex" operation 2019-03-21 20:10:45 -04:00
j433866
6a01e40394 Fix bug where GIF input would error on output.
Also minor corrections to jsdocs in ImageManipulation
2019-03-20 11:20:34 +00:00
j433866
328c0ade22 Merge remote-tracking branch 'upstream/master' 2019-03-19 14:21:28 +00:00
j433866
b312e17904 Change title to title case 2019-03-19 13:54:39 +00:00
j433866
2cd3e9cacd Add new implementation of gaussian blur.
Changed SharpenImage to use the new algorithm.
2019-03-19 13:54:26 +00:00
j433866
ce72acdd61 Add 'add text to image' operation.
Included variants of the Roboto fonts as bitmap fonts for jimp.
Changed webpack config to import the font files.
2019-03-19 13:53:09 +00:00
Matt
b3d92b04cb Updated nodom dependency to upstream 2019-03-19 11:24:29 +00:00
j433866
8e74acbf3e Add opaque background option 2019-03-18 09:43:37 +00:00
n1474335
a5703cb4f1 Updated CHANGELOG 2019-03-15 15:17:15 +00:00
n1474335
bb5b92571e Updated CHANGELOG 2019-03-14 16:08:25 +00:00
Matt
3ad5f889a0 Wrote some tests, fixed imports for node 2019-03-14 13:37:11 +00:00
n1474335
3ff10bfeae 8.27.0 2019-03-14 12:26:07 +00:00
n1474335
c8a2a8b003 Updated CHANGELOG 2019-03-14 12:26:00 +00:00
n1474335
6acb3a7ca5 Merge branch 's2224834-feature/bombe' 2019-03-14 12:20:12 +00:00
n1474335
ef38897a01 Updated CHANGELOG 2019-03-14 12:20:05 +00:00
n1474335
33db0e666a Final tweaks to Bombe svg and preloader css 2019-03-14 12:11:41 +00:00
n1474335
cf32372a57 Added Enigma wiki article link to Enigma, Typex, Bombe and Multi-Bombe operation descriptions. 2019-03-14 12:08:35 +00:00
n1474335
b98cf9538d Long HTML output is now replaced with an overlay in the same way as long string output. 2019-03-14 11:43:23 +00:00
Matt
768fef502d Changed version of nodom to actually functioning fork 2019-03-14 11:39:46 +00:00
j433866
4fafa39e54 Fix magic library to better handle operation error 2019-03-13 13:09:02 +00:00
j433866
bb7487c476 Change to use new FileType library 2019-03-13 09:20:13 +00:00
j433866
308195279c Bring up to date with master 2019-03-13 09:17:43 +00:00
s2224834
1a707eab86 Merge branch 'feature/bombe' into feature/typex 2019-03-12 18:21:34 +00:00
s2224834
e2efc3e8e8 package lock changes 2019-03-12 18:21:27 +00:00
s2224834
fa6d5e60ce Merge branch 'master' into feature/bombe 2019-03-12 18:07:39 +00:00
j433866
e95b7075b9 Add convert image format operation 2019-03-12 16:29:20 +00:00
j433866
dfbc1beccd Add sharpen image operation 2019-03-12 16:24:43 +00:00
j433866
0a7a0ac681 Merge pull request #1 from gchq/master
bring up to date with master
2019-03-12 13:27:24 +00:00
n1474335
981d4be44d 8.26.3 2019-03-11 13:31:33 +00:00
n1474335
80bdf7410b Merge branch 'artemisbot-bugs/disassembler-fix' 2019-03-11 13:31:26 +00:00
Matt
6f45d33c8a Fix disassembler issue
Fix disassembler issue properly
2019-03-11 13:06:17 +00:00
Matt
cd22985f11 Fix categories JSON issue 2019-03-11 12:09:29 +00:00
Matt
66c0425080 Merge branch 'master' into module-charts 2019-03-11 11:57:19 +00:00
Matt
fd7fd9ca35 Remove jsdom from dependencies 2019-03-11 11:55:44 +00:00
n1474335
7b3efa746e 8.26.2 2019-03-11 11:54:19 +00:00
n1474335
90ddc2bfa7 Merge branch 'MShwed-bugs/xpath-namespace-prefix-fix' 2019-03-11 11:54:11 +00:00
n1474335
8e3425ed6d Merge branch 'bugs/xpath-namespace-prefix-fix' of https://github.com/MShwed/CyberChef into MShwed-bugs/xpath-namespace-prefix-fix 2019-03-11 11:53:06 +00:00
n1474335
c1bb42fe65 8.26.1 2019-03-11 11:44:39 +00:00
n1474335
b99f73919f Merge branch 'artemisbot-bugs/disassembler' 2019-03-11 11:44:32 +00:00
n1474335
978bf75765 Merge branch 'bugs/disassembler' of https://github.com/artemisbot/CyberChef into artemisbot-bugs/disassembler 2019-03-11 11:44:11 +00:00
Matt
432d5b43a1 Fix some misspellings 2019-03-10 17:51:30 +00:00
mshwed
3412372d1e Added support for non-prefixed default namespace selectors 2019-03-10 13:02:13 -04:00
Matt
ca6d472e5d Update nodom 2019-03-10 16:07:14 +00:00
Matt
c2e130f369 Update disassembler 2019-03-10 13:19:04 +00:00
Matt
6501454424 Cleanup 2019-03-10 13:08:47 +00:00
Matt
0019a4e1db Found a different dom implementation that removes 6MB 2019-03-10 12:03:53 +00:00
Matt
f8874fc586 Actually made operations work (and made the module 8MB)
Unfortunately they need jsdom
2019-03-10 11:44:02 +00:00
Matt
4ae875601a Ported final two chart operations 2019-03-10 10:33:47 +00:00
n1474335
e8880f068f 8.26.0 2019-03-09 07:23:57 +00:00
n1474335
45c1c23e09 Merge branch 'j433866-image-operations' 2019-03-09 07:23:31 +00:00
n1474335
e10d4bf45c Tidied up image manipulation ops 2019-03-09 07:23:11 +00:00
n1474335
369b640408 Merge branch 'master' into j433866-image-operations 2019-03-09 06:29:19 +00:00
n1474335
d2d30bf668 8.25.0 2019-03-09 06:28:05 +00:00
n1474335
dcb59edb79 Merge branch 'feature-extract-files' 2019-03-09 06:26:36 +00:00
n1474335
84d31c1d59 Added 'Move to input' button to output file list. Improved zlib extraction efficiency. 2019-03-09 06:25:27 +00:00
n1474335
58d41f4458 8.24.3 2019-03-09 05:38:13 +00:00
n1474335
360effb839 Merge branch 'j433866-fork-fix' 2019-03-09 05:37:57 +00:00
j433866
3e428c044a Add min values to operation args 2019-03-08 13:38:59 +00:00
j433866
d923c99975 Fix same bug in subsection 2019-03-07 16:33:38 +00:00
j433866
2b538061e9 Fix fork operation not setting ingredient values correctly. 2019-03-07 16:26:42 +00:00
j433866
11451ac6b9 Add image format pattern.
("borrowed" from RenderImage)
2019-03-07 13:35:37 +00:00
j433866
21a8d03201 Move parsing and generation of QR codes to lib folder.
Also rewrote QR code parsing to be more readable and actually error out properly.
2019-03-07 13:21:26 +00:00
j433866
0c9db5afe9 Fix typo 2019-03-07 11:36:29 +00:00
j433866
1031429550 Add error handling 2019-03-07 11:19:04 +00:00
j433866
4a7ea469d4 Add status messages for image operations 2019-03-07 10:03:09 +00:00
j433866
833c1cd98f Add Contain Image, Cover Image and Image Hue / Saturation / Lightness ops 2019-03-07 10:02:37 +00:00
j433866
662922be6f Add resizing status message 2019-03-06 10:32:58 +00:00
j433866
370ae323f6 Fix linting 2019-03-05 11:49:25 +00:00
j433866
514eef50de Add image filter operation 2019-03-04 14:48:17 +00:00
j433866
ec1fd7b923 Add image opacity operation 2019-03-04 14:38:25 +00:00
j433866
737ce99398 Add image brightness / contrast operation 2019-03-04 14:24:57 +00:00
j433866
4f1a897e18 Add Crop Image operation 2019-03-04 13:48:48 +00:00
j433866
588a8b2a3a Fix code syntax 2019-03-04 13:48:29 +00:00
j433866
f281a32a4e Add Wikipedia URLs 2019-03-04 13:48:13 +00:00
j433866
d09e6089ca Add min width and height values 2019-03-04 11:52:54 +00:00
j433866
7b6062a4a2 Set min blur amount to 1, add status message for gaussian blur. 2019-03-04 11:47:50 +00:00
j433866
7975fadfe9 Add options for min, max and step values for number inputs. 2019-03-04 11:46:27 +00:00
n1474335
9fa7edffbf Improved file extraction error handling 2019-03-02 16:12:21 +00:00
n1474335
24a47445f6 Merge branch 'master' into feature-extract-files 2019-03-02 15:40:32 +00:00
s2224834
9a0b784153 Typex: improve operation description 2019-02-28 18:56:59 +00:00
s2224834
a446ec31c7 Improve Enigma/Bombe descriptions a little. 2019-02-28 18:48:36 +00:00
s2224834
9323737d1d Bombe: fix rotor listing order for multibombe 2019-02-28 18:37:48 +00:00
s2224834
d288acdd4e Add some files that escaped commit before 2019-02-28 17:50:10 +00:00
s2224834
765aded208 Typex: add simple tests 2019-02-28 17:22:09 +00:00
s2224834
1f9fd92b01 Typex: rotors in same order as Enigma 2019-02-28 17:21:47 +00:00
s2224834
2be642e4c9 Merge branch 'feature/bombe' into feature/typex 2019-02-28 17:00:33 +00:00
s2224834
934356dea8 Tweaks for new rotor order 2019-02-28 16:56:28 +00:00
s2224834
a037bf6f9d Merge branch 'master' into feature/bombe 2019-02-28 16:29:21 +00:00
n1474335
c005c86c27 Added argSelector ingredient type and reversed rotors in Enigma and Bombe operations. 2019-02-28 16:05:23 +00:00
Matt
68278267e1 Update libyara-wasm 2019-02-23 15:13:58 +00:00
Matt
da2d5674a5 Ported heatmap and hex density chart ops 2019-02-23 00:41:19 +00:00
Matt
5bb8eb22ec Merge branch 'master' into module-charts 2019-02-22 23:36:14 +00:00
j433866
0d86a7e427 Add resize algorithm option 2019-02-20 15:35:53 +00:00
j433866
9f4aa0a123 Remove trailing space 2019-02-20 13:17:57 +00:00
j433866
da838e266e Add flip image operation 2019-02-20 13:04:15 +00:00
j433866
fd160e87e8 Add image operations to Categories 2019-02-20 11:54:59 +00:00
j433866
0dd4304902 Add new Blur Image operation.
Performs both fast blur and gaussian blur
2019-02-20 11:48:24 +00:00
j433866
a0b94bba4e Change run() functions to be async 2019-02-20 11:26:39 +00:00
j433866
74c2a2b5cb Add Invert Image operation 2019-02-20 11:12:15 +00:00
j433866
b691c30677 Add dither image operation 2019-02-20 09:20:38 +00:00
j433866
01acefe4cf Remove scale image operation.
(Same functionality is implemented in Resize Image)
2019-02-19 16:20:36 +00:00
j433866
1a2c5a95c7 Add resize image operation 2019-02-19 16:19:34 +00:00
j433866
eb8725a0db Fix degrees error 2019-02-19 16:10:53 +00:00
j433866
57e1061063 Add Scale Image operation 2019-02-19 15:37:59 +00:00
j433866
91f4681a3c Add rotate image operation 2019-02-19 15:37:49 +00:00
n1474335
8148c1a8a8 8.24.2 2019-02-11 18:45:27 +00:00
n1474335
ce61bcc078 Merge branch 'd98762625-dynamic-import' 2019-02-11 18:45:00 +00:00
n1474335
49e2b05a11 Dynamic module loading is now cached and Webpack includes the import in the main chunk. 2019-02-11 18:44:41 +00:00
d98762625
481f2a4717 Merge branch 'master' of github.com:gchq/CyberChef into dynamic-import 2019-02-11 16:54:31 +00:00
d98762625
c01c076561 try/catch to allow chef to run from prod file 2019-02-11 16:51:23 +00:00
d98762625
2391e08ac1 tidy up 2019-02-11 15:47:28 +00:00
d98762625
a7a2fe243a prod working with mini css webpack plugin 2019-02-11 15:40:18 +00:00
n1474335
b5c655dd70 8.24.1 2019-02-11 14:48:45 +00:00
n1474335
d84a61d108 Merge branch 'j433866-open-file-button' 2019-02-11 14:48:38 +00:00
n1474335
13abbd2c5d Abstracted out loadFile logic to separate function 2019-02-11 14:48:25 +00:00
d98762625
d22eac9f35 WIP getting prod working with minicss webpack plugin 2019-02-11 14:29:29 +00:00
n1474335
d71aa8d7e2 Merge branch 'open-file-button' of https://github.com/j433866/CyberChef into j433866-open-file-button 2019-02-11 14:28:32 +00:00
n1474335
4db6199fd9 Fixed timings for Bombe animation fast rotor 2019-02-10 21:45:55 +00:00
s2224834
dd9cbbac77 Bombe: add note about rotor step in crib 2019-02-09 23:01:52 +00:00
s2224834
069d4956aa Bombe: Handle boxing stop correctly 2019-02-09 22:57:57 +00:00
s2224834
61fee3122a Bombe: add Rebuild Project to authors 2019-02-08 21:16:42 +00:00
s2224834
5a8255a9f4 Bombe: fix tests after output table patch 2019-02-08 19:25:28 +00:00
s2224834
5a2a8b4c8e Typex: input wiring is reversed 2019-02-08 18:53:08 +00:00
n1474335
1079080f5c Bombe results are now presented in a table 2019-02-08 18:41:20 +00:00
n1474335
1e914c4cf2 Fixed Bombe svg animation in standalone version 2019-02-08 18:40:09 +00:00
n1474335
53226c1050 Added populateMultiOption ingredient type 2019-02-08 18:36:59 +00:00
n1474335
7a4e0301d2 8.24.0 2019-02-08 18:07:10 +00:00
n1474335
42826e542d Updated changelog 2019-02-08 18:07:03 +00:00
n1474335
42d1c9403c Merge branch 'h345983745-dnsoverhttps' 2019-02-08 18:02:39 +00:00
n1474335
ab43635583 Tidied up 'DNS over HTTPS' operation and fixed manualBake flag. 2019-02-08 18:02:13 +00:00
n1474335
b8ecd83bfd Merge branch 'dnsoverhttps' of https://github.com/h345983745/CyberChef into h345983745-dnsoverhttps 2019-02-08 17:34:33 +00:00
n1474335
0db0ced1ab 8.23.4 2019-02-08 17:28:16 +00:00
n1474335
3d20833d42 Fixed populateOption HTML escape bug. Closes #490 2019-02-08 17:28:10 +00:00
n1474335
7d09ba5669 8.23.3 2019-02-08 17:06:42 +00:00
n1474335
310ff30278 Merge branch 'j433866-xss_fixes' 2019-02-08 17:06:32 +00:00
n1474335
821bc9405c Merge branch 'xss_fixes' of https://github.com/j433866/CyberChef into j433866-xss_fixes 2019-02-08 17:05:51 +00:00
n1474335
d54d66cffc 8.23.2 2019-02-08 16:54:11 +00:00
n1474335
d2b4c40357 Fixed BigNumber/XRegExp incompatibility. CLoses #481 2019-02-08 16:54:04 +00:00
n1474335
f48af97ddc 8.23.1 2019-02-08 15:53:42 +00:00
n1474335
f1264d6310 Update package-lock.json 2019-02-08 15:53:40 +00:00
n1474335
4dc5a1499a Merge branch 'j433866-coords-fix' 2019-02-08 15:52:40 +00:00
d98762625
58a8af20a6 Highligting for Recipe now working. Discovered bug when highlighting on a test case 2019-02-08 14:28:53 +00:00
h345983745
75a58f465c Removed jpath import 2019-02-07 21:05:07 +00:00
s2224834
220053c044 Typex: add ring setting 2019-02-07 18:10:16 +00:00
h345983745
613cbaa556 Fixing Formating Issues 2019-02-07 08:28:23 +00:00
h345983745
0d0a634255 Added More Request Types 2019-02-06 23:27:27 +00:00
h345983745
105090db60 Spelling Check 2019-02-06 22:50:46 +00:00
h345983745
3e9c75f735 Added to Categories 2019-02-06 22:34:43 +00:00
h345983745
d42075072b Small Updates 2019-02-06 20:54:06 +00:00
h345983745
6a099f0813 Inital Commit 2019-02-06 20:20:20 +00:00
d98762625
9af5e40071 update linting to allow dybnamic import. Recipe highlight still broken 2019-02-01 14:05:48 +00:00
d98762625
4bf2a29070 WIP: bundle all css into main.css with mini-css-extract. Cannot split into vendor and styles without breaking at the moment. 2019-02-01 13:45:47 +00:00
d98762625
069d0e48c1 WIP: use mini-css-extract-plugin instead of extract-text, to work with dynamic import. Currently outputting only one css and recipe highlight doesnt work 2019-02-01 12:00:45 +00:00
d98762625
c8cb2692dd WIP: Recipe refactored to use dynamic import 2019-02-01 10:52:21 +00:00
j433866
74a22bcf9c Swap ordering of truncating and escaping 2019-01-31 15:22:25 +00:00
j433866
8b44927cb6 Fix XSS for To Table operation and Magic button 2019-01-31 15:18:37 +00:00
j433866
3209c94622 Fix conversion breaking when compass directions are used as delimiters 2019-01-21 12:50:30 +00:00
n1474335
6f8a5ea1be Merge branch 'j433866-coordinates' 2019-01-18 17:14:39 +00:00
n1474335
69837837b0 Tidied up co-ordinate operation 2019-01-18 17:14:25 +00:00
n1474335
03d8bf2836 Merge branch 'coordinates' of https://github.com/j433866/CyberChef into j433866-coordinates 2019-01-18 16:05:44 +00:00
n1474335
eca2c142f3 8.23.0 2019-01-18 15:37:42 +00:00
n1474335
715f7bbbc2 Lint 2019-01-18 15:37:25 +00:00
n1474335
291ebd5c12 Merge branch 'artemisbot-features/yara' 2019-01-18 15:35:14 +00:00
n1474335
ba04cac7ac Tidied up YARA operation 2019-01-18 15:34:56 +00:00
j433866
acb8c0b5af Change icon from folder to input 2019-01-18 15:12:03 +00:00
j433866
0c14bacea7 Add button to input to allow opening of files using the file prompt. 2019-01-18 15:07:19 +00:00
n1474335
4cabb849f3 Merge branch 'features/yara' of https://github.com/artemisbot/CyberChef into artemisbot-features/yara 2019-01-18 14:56:26 +00:00
n1474335
445a85798b 8.22.1 2019-01-18 14:50:00 +00:00
n1474335
55775f48e9 Merge branch 'picapi-b64-description-fix' 2019-01-18 14:49:53 +00:00
j433866
4bd923dc06 Improved handling of negative numbers and weirder inputs.
Negative numbers shouldn't make it go weird any more.
Automatic detection of input formats should be more reliable.
2019-01-17 13:53:42 +00:00
j433866
439654ed7f Add tests for new co-ordinate conversion module.
Removed To/From geohash tests
2019-01-17 13:49:36 +00:00
n1474335
87e956fe7d Added old loading icon back for initial page load. 2019-01-16 17:54:55 +00:00
n1474335
02b9dbdee9 Replaced loading animation with animated Bombe SVG 2019-01-16 17:54:55 +00:00
j433866
69797e58cb Add better error handling.
Also now doesn't do anything if there's no input
2019-01-16 16:57:58 +00:00
Matt
d1961ca3fa Marginally reduced size of libyara-wasm 2019-01-16 01:15:51 +00:00
Matt
2e9b1e079c Merge remote-tracking branch 'upstream/master' into features/yara 2019-01-15 23:46:49 +00:00
Matt
3dfaaf4c25 Update libyara for test pass 2019-01-15 23:45:40 +00:00
Matt
fcc39a0397 Added File upload support to textarea 2019-01-15 23:42:05 +00:00
Matt
0602f457ce Added initial tests & counts support 2019-01-15 16:24:29 +00:00
j433866
d00b0f4c0e Basically rewrote the whole thing using the new geodesy module 2019-01-15 15:55:49 +00:00
j433866
5e68959c03 Catch when OS grid references aren't calculated 2019-01-15 10:25:49 +00:00
j433866
ad4451a757 Rewrite MGRS to use new Geodesy module.
Added Ordnance Survey grid reference support
2019-01-15 10:13:11 +00:00
Callum Fraser
4d8127a7d9 Modified description of ToBase64 operation
Addresses #472
2019-01-14 22:25:49 +00:00
n1474335
cd2c8078c8 Added ELF extractor. You can now specific which categories to search for in file type operations. 2019-01-14 18:55:10 +00:00
s2224834
ffc4b0a0a8 Bombe: lol 2019-01-14 17:15:54 +00:00
j433866
ee360521bb Remove MGRS npm module 2019-01-14 16:41:06 +00:00
j433866
04b0b8c723 Tidy up code 2019-01-14 14:58:41 +00:00
j433866
b3ac8d0835 Removed some debug logging 2019-01-14 13:49:49 +00:00
j433866
1a88a0164c Fix delimiter breaking Geohash detection 2019-01-14 13:00:14 +00:00
j433866
8b77ad7748 Stop delimiters breaking MGRS conversion 2019-01-14 12:49:28 +00:00
j433866
8d1f668fc5 Remove old Geohash modules 2019-01-14 11:56:27 +00:00
j433866
68fbbb64db Add new Convert co-ordinate format module.
Also added autodetect of co-ordinate format / delimiter
2019-01-14 11:49:57 +00:00
s2224834
eee92aa1aa Bombe: fix some outdated docs 2019-01-12 12:56:21 +00:00
s2224834
74eb4cca86 Bombe: tweaks
Twiddle the default rotor sets a bit.
Add a time remaining estimate for the multibombe.
2019-01-12 01:35:24 +00:00
s2224834
49f5c94a75 Bombe: further optimisation 2019-01-12 01:10:47 +00:00
Matt
8bba4b2973 More speedrun stats (literally 10x faster) 2019-01-12 00:20:25 +00:00
s2224834
d94e8c8187 Bombe: wording/docs tweaks 2019-01-11 18:24:16 +00:00
n1474335
2307325af8 Added Zlib extraction 2019-01-11 17:58:25 +00:00
n1474335
4e57b4be88 Completed GZIP extraction 2019-01-11 17:44:13 +00:00
s2224834
21335e7d05 Bombe: Add checking machine 2019-01-11 13:18:25 +00:00
j433866
abdd70c6fa Add ConvertCoordinates to lib folder 2019-01-11 11:59:13 +00:00
s2224834
78768e00d4 Bombe: Firefox optimisation
Switch a couple of for of loops in the critical path for classic fors.
This loses about 10% performance in Chrome, but it brings Firefox
performance in line with Chrome's, rather than 2.5 times slower.
2019-01-10 18:49:06 +00:00
s2224834
3eb44708e5 Add MultiBombe
Runs the Bombe multiple times with different rotor specs.
Edits the core BombeMachine a little to add the ability to switch rotors
without rewiring everything
2019-01-10 18:04:02 +00:00
n1474335
c077b22410 Stream.readBits() method implemented. Unfinished. 2019-01-10 17:30:52 +00:00
n1474335
9787ab04cd 8.22.0 2019-01-10 15:44:02 +00:00
n1474335
79d3c90026 Merge branch 'j433866-subsection' 2019-01-10 15:43:05 +00:00
n1474335
c2068b343b Tidied up and added global matching to Subsection operation 2019-01-10 15:42:48 +00:00
j433866
9e63e40dab Add new MGRS module and update webpack-dev-server 2019-01-10 15:24:29 +00:00
n1474335
6424839731 Merge branch 'subsection' of https://github.com/j433866/CyberChef into j433866-subsection 2019-01-10 15:11:34 +00:00
n1474335
863a525625 8.21.0 2019-01-10 15:02:26 +00:00
n1474335
f82a727e24 Merge branch 'masq-insense' 2019-01-10 15:01:22 +00:00
n1474335
995fcab071 Tidied up Case Insensitive Regex ops 2019-01-10 15:01:01 +00:00
n1474335
c5270d75a1 Merge branch 'insense' of https://github.com/masq/CyberChef into masq-insense 2019-01-10 14:53:21 +00:00
s2224834
8c757d1e03 Bombe: optimise
This cuts about 85% off the execution time.
2019-01-09 21:07:05 +00:00
n1474335
324c409ff1 8.20.0 2019-01-09 16:38:53 +00:00
n1474335
1db8e6dddc Merge branch 'klaxon1-feature/lorem-ipsum-generator' 2019-01-09 16:38:46 +00:00
n1474335
c49a770c59 Tidied up Lorem Ipsum op 2019-01-09 16:36:34 +00:00
Matt
dd9ba4d250 Fixed problems flagged by n's review 2019-01-09 15:28:50 +00:00
n1474335
0e601d5b5f Merge branch 'feature/lorem-ipsum-generator' of https://github.com/klaxon1/CyberChef into klaxon1-feature/lorem-ipsum-generator 2019-01-09 14:50:48 +00:00
Matt
ebb632e888 Added metadata, string identifiers and operation args 2019-01-09 14:29:14 +00:00
Matt
4db2335107 Speedrunning strats (increased speed on big files) 2019-01-09 11:45:11 +00:00
Matt
26a2fb6662 Increased size of rule inp & expanded memory for wasm 2019-01-09 09:56:55 +00:00
Matt
4c1521a98e No data matches & warnings support 2019-01-08 23:26:14 +00:00
Matt
df8abb099c Added code argtype 2019-01-08 22:23:14 +00:00
s2224834
e74c86842b Bombe: add trial decryption preview 2019-01-08 19:37:34 +00:00
n1474335
fe1332f18e 8.19.7 2019-01-08 18:29:14 +00:00
n1474335
cb9ab7a2c9 Fixed 'Maximise output' button functionality 2019-01-08 18:29:07 +00:00
s2224834
ad6e30f3d4 Bombe: review, tests, validation 2019-01-08 18:25:42 +00:00
n1474335
3a6b2875d5 8.19.6 2019-01-08 17:51:47 +00:00
n1474335
766de7e6fa Fixed bug in 'Regular expression' operation when highlighting lookaheads 2019-01-08 17:51:43 +00:00
Matt
13439e100e Merge remote-tracking branch 'upstream/master' into features/yara 2019-01-08 16:28:14 +00:00
Matt
5ac469b174 Added yara rule support 2019-01-08 16:19:58 +00:00
j433866
8ac5b48493 Update operation description 2019-01-08 11:51:33 +00:00
j433866
1a827ef44f Add Subsection to Flow Control category 2019-01-08 11:17:06 +00:00
j433866
0f0e346a02 Add new Subsection operation 2019-01-08 11:12:02 +00:00
s2224834
b6eb4e84e4 Add Bombe operation
Still needs some work, but functional
2019-01-07 19:48:44 +00:00
n1474335
2a6db47aeb Began implementing GZIP/DEFLATE extraction. Unfinished. 2019-01-04 18:12:49 +00:00
n1474335
19b7957523 Added RTF extractor 2019-01-04 14:57:31 +00:00
s2224834
1b1a3c261d Typex: random rotors 2019-01-04 13:21:15 +00:00
n1474335
0d2cb02f97 Fixed FLV previous tag size error 2019-01-04 11:49:12 +00:00
n1474335
7d8d80ca2c Added extractor for MS Office 2007+ files 2019-01-03 19:01:12 +00:00
s2224834
b4e130234c Enigma: make sure op class is called Enigma 2019-01-03 18:51:39 +00:00
s2224834
53cc413eae Typex: move machine implementation to lib/ 2019-01-03 18:49:01 +00:00
n1474335
0449c46b38 Added FLV extractor. 2019-01-03 18:40:22 +00:00
s2224834
f8450183c1 Enigma: fix 4th rotor ringstellung 2019-01-03 17:51:20 +00:00
s2224834
eb1f8b4bc6 Add Typex operation
WIP
2019-01-03 16:40:29 +00:00
s2224834
088864fd9c Add Enigma operation 2019-01-03 16:36:56 +00:00
n1474335
cd0c86e0d6 File scan now uses bytesMatch() instead of signatureMatches(), reducing call stack size 2019-01-03 13:03:41 +00:00
n1474335
a56f92cdee Significantly improved performance when scanning for embedded files by implementing a fastcheck algorithm. 2019-01-02 17:50:47 +00:00
n1474335
c82971f8db 8.19.5 2019-01-01 20:49:24 +00:00
n1474335
cb2c376c63 Increasing Node memory limit from 1G to 2G 2019-01-01 20:27:38 +00:00
n1474335
bc00fa0694 8.19.4 2019-01-01 20:15:29 +00:00
n1474335
c86007da71 Removed increase-memory-limit plugin in favour of NODE_OPTIONS environment variable. 2019-01-01 20:15:16 +00:00
n1474335
1bf513ca74 8.19.3 2019-01-01 19:56:20 +00:00
n1474335
29411c903f Added increase-memory-limit plugin to TravisCI build process to reduce 'JavaScript heap out of memory' errors. 2019-01-01 19:56:12 +00:00
n1474335
017dde364c 8.19.2 2019-01-01 19:22:10 +00:00
n1474335
c123d7370a Merge branch 'edwardwall-patch-2' 2019-01-01 19:21:11 +00:00
n1474335
76f1e5e8f3 Merge branch 'patch-2' of https://github.com/edwardwall/CyberChef into edwardwall-patch-2 2019-01-01 19:20:07 +00:00
n1474335
4e466c7886 8.19.1 2019-01-01 19:19:16 +00:00
n1474335
d469fb9c58 Updated dependencies 2019-01-01 19:19:07 +00:00
n1474335
4c285bce57 Refactored scanning for file types to be more than twice as fast. 2019-01-01 15:12:01 +00:00
Edward Wall
050ab03448 Simplify to improve readability 2018-12-30 17:06:48 +00:00
Edward Wall
40acf751a8 Update to understand Generalized / UTC Time
Future proofing for when certificates with dates after 2049 begin being issued.
These certificates' dates will be in Generalized Time not UTC Time as per RFC 5280
2018-12-30 16:46:18 +00:00
Spencer Walden
126ad585c0 Registers tests for 'To/From Case Insensitive Regex' operations 2018-12-30 03:26:44 -08:00
Spencer Walden
1d04b649e0 Adds 'To/From Case Insensitive Regex' operations under 'Utils' 2018-12-30 03:26:44 -08:00
Spencer Walden
b750006cf0 Adds tests for 'To/From Case Insensitive Regex' operations 2018-12-30 03:26:44 -08:00
Spencer Walden
3c16b839b6 Adds 'From Case Insensitive Regex' operation 2018-12-30 03:26:44 -08:00
Spencer Walden
32aea6b86c Adds 'To Case Insensitive Regex' operation 2018-12-30 03:26:44 -08:00
Edward Wall
688c2d0df5 Update ParseX509Certificate.mjs 2018-12-30 03:15:07 +00:00
n1474335
ede75530d0 Added PNG and BMP extractors 2018-12-30 02:21:45 +00:00
n1474335
3ae225ac59 Untar operation now uses lib/Stream library 2018-12-30 01:36:58 +00:00
n1474335
fd07b89028 Merge branch 'master' into feature-extract-files 2018-12-30 01:16:46 +00:00
n1474335
0cea56dc62 8.19.0 2018-12-30 01:07:54 +00:00
n1474335
bb44268c30 Merge branch 'feature-browser-testsuite' 2018-12-30 01:07:42 +00:00
n1474335
19b3dcf1c2 Updated CHANGELOG 2018-12-30 01:07:26 +00:00
n1474335
71e0a4e0ce Increased UI test timeouts 2018-12-30 00:47:10 +00:00
n1474335
7f2e879e24 Added explicit bake after input added in test suite. 2018-12-30 00:37:44 +00:00
n1474335
840e44deac Tidied up UI tests 2018-12-30 00:26:28 +00:00
n1474335
f7707faece Added Chrome to TravisCI config 2018-12-30 00:02:41 +00:00
n1474335
b631e3fef6 Added nightwatch tests to TravisCI build process for prod and inline versions. 2018-12-29 23:46:13 +00:00
n1474335
b0fb9db4b8 Added nightwatch.js test suite for confirming that the app loads correctly and can run operations from each module. Currently only support the latest version of Chrome. 2018-12-29 02:58:05 +00:00
n1474335
c7e9115994 Restructured tests directory 2018-12-28 21:49:40 +00:00
Klaxon
f2d115ee4d add lorem ipsum generator 2018-12-29 00:44:59 +10:00
n1474335
0198f05112 Added and improved file signatures. 2018-12-27 00:03:41 +00:00
n1474335
729307336e Converted all previous file signatures to the new format. 2018-12-26 23:19:46 +00:00
n1474335
f4f9b5c91c Added 'isImage' and 'isType' functions 2018-12-26 18:40:27 +00:00
n1474335
f355fe3447 Merge branch 'feature-extract-files' of github.com:gchq/CyberChef into feature-extract-files 2018-12-26 18:01:55 +00:00
n1474335
321718d43a Merge branch 'master' into feature-extract-files 2018-12-26 16:57:34 +00:00
n1474335
a1b161493c 8.18.1 2018-12-26 16:50:36 +00:00
n1474335
5acee80463 'editableOption's are now full width. 'editableOptionShort' type added to replace the old style. 2018-12-26 16:50:32 +00:00
n1474335
e6932401ad 8.18.0 2018-12-26 16:35:34 +00:00
n1474335
7a4eff0f5c Merge branch 'artemisbot-features/colour-channel' 2018-12-26 16:33:26 +00:00
n1474335
8b533e9893 Tidied up 'Split Colour Channels' operation and added 'Multimedia' category 2018-12-26 16:33:10 +00:00
n1474335
02b92c7977 Merge branch 'features/colour-channel' of https://github.com/artemisbot/CyberChef into artemisbot-features/colour-channel 2018-12-26 16:10:44 +00:00
n1474335
e386863bdb 8.17.2 2018-12-26 00:39:38 +00:00
n1474335
16be7cb28a Fixed grunt-webpack circular reference issue 2018-12-26 00:39:30 +00:00
n1474335
f6d97c19d9 8.17.1 2018-12-26 00:01:21 +00:00
n1474335
8fef01d961 Fixed bundle analyzer filepath 2018-12-26 00:01:12 +00:00
n1474335
43dcd544f2 Added webpack-bundle-analyzer to production build. Refactored some modules to improve performance. Removed moment-timezone from Utils to drastically reduce module size. 2018-12-25 23:58:00 +00:00
n1474335
b29bb6fdd7 Added 'DishError' and refined test results. 2018-12-25 22:38:53 +00:00
n1474335
d2325306db 8.17.0 2018-12-25 21:55:51 +00:00
n1474335
dfe31980b7 Merge branch 'j433866-qrcodes' 2018-12-25 21:55:35 +00:00
n1474335
9734b78aeb Tidied up QR code operations 2018-12-25 21:54:38 +00:00
n1474335
4ee0800990 Merge branch 'qrcodes' of https://github.com/j433866/CyberChef into j433866-qrcodes 2018-12-25 19:07:59 +00:00
n1474335
387008bd9c 8.16.1 2018-12-25 19:02:13 +00:00
n1474335
bf24547202 The 'option' and 'editableOption' arguments can now specify a 'defaultIndex' to populate, instead of using the first in the provided list. 2018-12-25 19:02:05 +00:00
Matt C
454ef0076b Disabled tests 2018-12-21 17:19:33 +00:00
Matt C
18693d2471 Add tests, however non-functional due to lack of File in Node
Also add jimp to package.json
2018-12-21 17:17:11 +00:00
Matt C
5a9583c970 Add to categories 2018-12-21 17:15:30 +00:00
Matt C
0046f7e3d7 Added colour channel splitting support 2018-12-21 17:08:09 +00:00
n1474335
8d3836cb16 Added support for a number of further file types and file detection methods. 2018-12-21 12:48:08 +00:00
j433866
c2f6b8df66 Add tests for Parse QR Code module 2018-12-21 11:26:03 +00:00
j433866
d0e428b728 Improve image normalising 2018-12-21 11:24:31 +00:00
j433866
ae5128a33a Always generate a PNG if the format isn't SVG 2018-12-21 11:23:51 +00:00
j433866
ed25017e2d Add process.browser to webpack config, so we don't include stuff we don't need 2018-12-20 14:46:24 +00:00
j433866
e5b2b84073 Add new ParseQRCode operation 2018-12-20 14:45:23 +00:00
j433866
1953d9a4c8 Add Jimp and jsqr modules 2018-12-20 14:42:32 +00:00
j433866
b3113c109b Change module to Image, change default format value to PNG 2018-12-20 14:42:12 +00:00
n1474335
9829491c4c Merge branch 'master' into feature-extract-files 2018-12-20 12:28:23 +00:00
n1474335
8c3569ea63 8.16.0 2018-12-19 17:59:55 +00:00
n1474335
ae28d476de Merge branch 'anthony-arnold-play-media' 2018-12-19 17:58:51 +00:00
n1474335
01c4cfdc8d Tidied up 'Play Media' operation 2018-12-19 17:58:38 +00:00
n1474335
8c6c3a1c01 Merge branch 'play-media' of https://github.com/anthony-arnold/CyberChef into anthony-arnold-play-media 2018-12-19 17:39:07 +00:00
n1474335
f367c1f78b 8.15.1 2018-12-19 17:25:05 +00:00
n1474335
13d87d397d Merge branch 'j433866-master' 2018-12-19 17:24:52 +00:00
n1474335
ed2c886359 Magic crib now only applies after all branches have been explored. 2018-12-19 17:24:38 +00:00
n1474335
6337e84708 Merge branch 'master' of https://github.com/j433866/CyberChef into j433866-master 2018-12-19 16:38:34 +00:00
j433866
ae20a951be Add customisation arguments.
Add presentation of QR code in HTML.
2018-12-19 14:43:31 +00:00
Anthony Arnold
866dd546c8 Add the Play Media operation and place it under a new "Multimedia" category. Move the Render Image to this new category as well. 2018-12-20 00:05:10 +10:00
j433866
2070e1a96b Add new Generate QR Code module 2018-12-19 13:27:45 +00:00
j433866
952f49e2e1 Add qr-image to package 2018-12-19 13:27:18 +00:00
j433866
f600571c6d Fix to make the filter work when intensive mode was turned on. 2018-12-19 09:42:09 +00:00
n1474335
e6fb0be1d0 Refactored file type detection engine 2018-12-18 17:44:42 +00:00
j433866
5d4c7244e1 Add note about crib to operation description 2018-12-18 16:52:09 +00:00
n1474335
d02124550b Merge branch 'master' into feature-extract-files 2018-12-18 15:45:53 +00:00
j433866
b6bdcaa71f Rename output filter argument to Crib 2018-12-18 14:19:52 +00:00
j433866
c8eab5d218 Commenting 2018-12-18 14:06:39 +00:00
n1474335
8ab56a29ac 8.15.0 2018-12-18 13:50:30 +00:00
n1474335
973b5f3f5c Merge branch 'Cynser-text-brute-force' 2018-12-18 13:50:23 +00:00
n1474335
076a1f97c2 Tidied up 'Text Encoding Brute Force' operations and updated CHANGELOG 2018-12-18 13:50:10 +00:00
n1474335
d96ef37d81 Merge branch 'text-brute-force' of https://github.com/Cynser/CyberChef into Cynser-text-brute-force 2018-12-18 12:28:27 +00:00
n1474335
56f8302402 8.14.0 2018-12-18 12:20:03 +00:00
n1474335
97e6a7cbd8 Merge branch 'tcode2k16-master' 2018-12-18 12:19:56 +00:00
n1474335
d89d79116c Cleaned up Base62 ops and updated CHANGELOG 2018-12-18 12:19:42 +00:00
j433866
367d79e820 Fix filtering to work on all the data and not just the result snippet 2018-12-18 11:55:49 +00:00
j433866
283d7f2159 Add Output Filter option to Magic operation 2018-12-18 10:40:18 +00:00
Cynser
dacb3ef6c3 Added decode option 2018-12-17 19:39:12 +00:00
tcode2k16
22454ae842 Add "To Base62" and "From Base62" operations 2018-12-17 12:37:00 +08:00
n1474335
79b9b63982 8.13.0 2018-12-15 00:26:41 +00:00
n1474335
2f68bf30a4 Merge branch 'jarmovanlenthe-master' 2018-12-15 00:26:31 +00:00
n1474335
50f078cc45 Updated CHANGELOG 2018-12-15 00:26:15 +00:00
n1474335
47a410d6ab Merge branch 'master' of https://github.com/jarmovanlenthe/CyberChef into jarmovanlenthe-master 2018-12-15 00:19:16 +00:00
n1474335
31cbf8cccc 8.12.4 2018-12-15 00:16:23 +00:00
n1474335
88bd321e3e Merge branch 'imhotepisinvisible-master' 2018-12-15 00:16:13 +00:00
Jarmo van Lenthe
b4a586c0b9 Some lets to consts and removing of trailing spaces from grunt lint 2018-12-14 22:35:43 +01:00
Oliver Grubin
63593f1b6c Fix HMAC operation when hex key has bytes >= 0x80 (#437)
Add test vectors from RFC4231
2018-12-14 21:32:44 +00:00
Jarmo van Lenthe
dcff8971e8 Added simple A1Z26 'cipher' 2018-12-14 22:29:51 +01:00
n1474335
6aa9d2b492 Added 'Extract Files' operation and 'Forensics' category. 2018-12-14 16:43:03 +00:00
Cynser
3f7059a235 Remove unnecessary escape character 2018-12-12 17:49:11 +00:00
Cynser
71c743ff5a Add Text Encoding Brute Force operation 2018-12-12 17:34:45 +00:00
n1474335
15fbe5a459 8.12.3 2018-11-23 16:07:30 +00:00
n1474335
cd47394709 Merge branch 'PhillipNordwall-JSON_Object_Sort' 2018-11-23 16:07:21 +00:00
n1474335
6f4ee8b7b6 Tidied JSON Beautify op. Changed default indent to 4 spaces instead of a tab. 2018-11-23 16:05:51 +00:00
n1474335
8c5d05b611 Merge branch 'JSON_Object_Sort' of https://github.com/PhillipNordwall/CyberChef into PhillipNordwall-JSON_Object_Sort 2018-11-23 15:56:59 +00:00
n1474335
3bdcf4d851 8.12.2 2018-11-23 14:58:38 +00:00
n1474335
1d1c69ca51 Updated magic pattern matches for QP and B64 encodings to handle whitespace better 2018-11-23 14:58:30 +00:00
Phillip Nordwall
c4c679021d Adding Sort Object Keys, and tests for it. 2018-11-22 11:10:10 -08:00
Phillip Nordwall
c16d13e2c9 Adding test cases for JSON Minify 2018-11-22 11:07:48 -08:00
Phillip Nordwall
97613eb3c7 Adding test cases for JSON Beautify 2018-11-22 10:31:22 -08:00
toby
6784a1c027 Add Series chart operation 2017-06-20 15:25:16 -04:00
toby
39ab600887 Add scatter plot operation 2017-06-06 14:08:21 -04:00
toby
49ea532cdc Tweak extent of hex density charts 2017-06-06 09:46:46 -04:00
toby
247e9bfbde Add "HTML to Text" operation 2017-06-05 21:47:32 -04:00
toby
5944568565 Change margins in hex density chart 2017-06-05 10:24:15 -04:00
toby
1c87707a76 Add heatmap chart operation 2017-06-05 10:24:06 -04:00
toby
b4188db671 Hexagon density: allow dense plotting of hexagons 2017-05-31 15:00:57 -04:00
toby
dc642be1f5 Hex plot: add edge drawing & changing colour opts 2017-05-30 15:49:22 -04:00
toby
6cdc7d3966 Hex density: split radius into draw & pack radii 2017-05-30 15:24:23 -04:00
toby
281d558111 Add hex density chart 2017-05-30 14:53:32 -04:00
toby
fa89713f19 Add d3 as a dependency 2017-05-30 14:50:56 -04:00
292 changed files with 35361 additions and 5315 deletions

View File

@@ -9,6 +9,6 @@ trim_trailing_whitespace = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[{package.json,.travis.yml}] [{package.json,.travis.yml,nightwatch.json}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

View File

@@ -1 +1,2 @@
src/core/vendor/** src/core/vendor/**
src/web/static/clippy_assets/**

View File

@@ -1,10 +1,12 @@
{ {
"parser": "babel-eslint",
"parserOptions": { "parserOptions": {
"ecmaVersion": 9, "ecmaVersion": 9,
"ecmaFeatures": { "ecmaFeatures": {
"impliedStrict": true "impliedStrict": true
}, },
"sourceType": "module" "sourceType": "module",
"allowImportExportEverywhere": true
}, },
"env": { "env": {
"browser": true, "browser": true,
@@ -87,10 +89,20 @@
"no-var": "error", "no-var": "error",
"prefer-const": "error" "prefer-const": "error"
}, },
"overrides": [
{
"files": "tests/**/*",
"rules": {
"no-unused-expressions": "off",
"no-console": "off"
}
}
],
"globals": { "globals": {
"$": false, "$": false,
"jQuery": false, "jQuery": false,
"log": false, "log": false,
"app": false,
"COMPILE_TIME": false, "COMPILE_TIME": false,
"COMPILE_MSG": false, "COMPILE_MSG": false,

View File

@@ -1,14 +1 @@
<!-- Prefix the title above with one of the following: --> <!-- Prefix the title above with 'Misc:' -->
<!-- Bug report: -->
<!-- Operation request: -->
<!-- Feature request: -->
<!-- Misc: -->
### Summary
### Example
<!-- If describing a bug, tell us what happens instead of the expected behavior -->
<!-- Include a link that triggers the bug if possible -->
<!-- If you are requesting a new operation, include example input and output -->

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug report: <Insert title here>'
labels: bug
assignees: ''
---
<!-- Prefix the title above with 'Bug report:' -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior or a link to the recipe / input used to cause the bug:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (if relevant, please complete the following information):**
- OS: [e.g. Windows]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for the project
title: 'Feature request: <Insert title here>'
labels: feature
assignees: ''
---
<!-- Prefix the title above with 'Feature request:' -->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,16 @@
---
name: Operation request
about: Suggest a new operation
title: 'Operation request: <Insert title here>'
labels: operation
assignees: ''
---
<!-- Prefix the title above with 'Operation request:' -->
## Summary
### Example Input
### Example Output

3
.gitignore vendored
View File

@@ -6,7 +6,10 @@ docs/*
!docs/*.conf.json !docs/*.conf.json
!docs/*.ico !docs/*.ico
.vscode .vscode
.*.swp
.DS_Store
src/core/config/modules/* src/core/config/modules/*
src/core/config/OperationConfig.json src/core/config/OperationConfig.json
src/core/operations/index.mjs src/core/operations/index.mjs
tests/browser/output/*

View File

@@ -1,15 +1,19 @@
language: node_js language: node_js
node_js: node_js:
- node - lts/*
addons:
chrome: stable
install: npm install install: npm install
before_script: before_script:
- npm install -g grunt - npm install -g grunt
- export NODE_OPTIONS=--max_old_space_size=2048
script: script:
- grunt lint - grunt lint
- grunt test - grunt test
- grunt docs - grunt docs
- grunt node - grunt node
- grunt prod --msg="$COMPILE_MSG" - grunt prod --msg="$COMPILE_MSG"
- xvfb-run --server-args="-screen 0 1200x800x24" grunt testui
before_deploy: before_deploy:
- grunt exec:sitemap - grunt exec:sitemap
- grunt copy:ghPages - grunt copy:ghPages
@@ -26,8 +30,9 @@ deploy:
skip_cleanup: true skip_cleanup: true
api_key: api_key:
secure: "HV1WSKv4l/0Y2bKKs1iBJocBcmLj08PCRUeEM/jTwA4jqJ8EiLHWiXtER/D5sEg2iibRVKd2OQjfrmS6bo4AiwdeVgAKmv0FtS2Jw+391N8Nd5AkEANHa5Om/IpHLTL2YRAjpJTsDpY72bMUTJIwjQA3TFJkgrpOw6KYfohOcgbxLpZ4XuNJRU3VL4Hsxdv5V9aOVmfFOmMOVPQlakXy7NgtW5POp1f2WJwgcZxylkR1CjwaqMyXmSoVl46pyH3tr5+dptsQoKSGdi6sIHGA60oDotFPcm+0ifa47wZw+vapuuDi4tdNxhrHGaDMG8xiE0WFDHwQUDlk2/+W7j9SEX0H3Em7us371JXRp56EDwEcDa34VpVkC6i8HGcHK55hnxVbMZXGf3qhOFD8wY7qMbjMRvIpucrMHBi86OfkDfv0vDj2LyvIl5APj/AX50BrE0tfH1MZbH26Jkx4NdlkcxQ14GumarmUqfmVvbX/fsoA6oUuAAE9ZgRRi3KHO4wci6KUcRfdm+XOeUkaBFsL86G3EEYIvrtBTuaypdz+Cx7nd1iPZyWMx5Y1gXnVzha4nBdV4+7l9JIsFggD8QVpw2uHXQiS1KXFjOeqA3DBD8tjMB7q26Fl2fD3jkOo4BTbQ2NrRIZUu/iL+fOmMPsyMt2qulB0yaSBCfkbEq8xrUA=" secure: "HV1WSKv4l/0Y2bKKs1iBJocBcmLj08PCRUeEM/jTwA4jqJ8EiLHWiXtER/D5sEg2iibRVKd2OQjfrmS6bo4AiwdeVgAKmv0FtS2Jw+391N8Nd5AkEANHa5Om/IpHLTL2YRAjpJTsDpY72bMUTJIwjQA3TFJkgrpOw6KYfohOcgbxLpZ4XuNJRU3VL4Hsxdv5V9aOVmfFOmMOVPQlakXy7NgtW5POp1f2WJwgcZxylkR1CjwaqMyXmSoVl46pyH3tr5+dptsQoKSGdi6sIHGA60oDotFPcm+0ifa47wZw+vapuuDi4tdNxhrHGaDMG8xiE0WFDHwQUDlk2/+W7j9SEX0H3Em7us371JXRp56EDwEcDa34VpVkC6i8HGcHK55hnxVbMZXGf3qhOFD8wY7qMbjMRvIpucrMHBi86OfkDfv0vDj2LyvIl5APj/AX50BrE0tfH1MZbH26Jkx4NdlkcxQ14GumarmUqfmVvbX/fsoA6oUuAAE9ZgRRi3KHO4wci6KUcRfdm+XOeUkaBFsL86G3EEYIvrtBTuaypdz+Cx7nd1iPZyWMx5Y1gXnVzha4nBdV4+7l9JIsFggD8QVpw2uHXQiS1KXFjOeqA3DBD8tjMB7q26Fl2fD3jkOo4BTbQ2NrRIZUu/iL+fOmMPsyMt2qulB0yaSBCfkbEq8xrUA="
file_glob: true
file: file:
- build/prod/cyberchef.htm - build/prod/*.zip
- build/node/CyberChef.js - build/node/CyberChef.js
on: on:
repo: gchq/CyberChef repo: gchq/CyberChef

View File

@@ -1,6 +1,92 @@
# Changelog # Changelog
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master). All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
### [8.38.0] - 2019-07-03
- 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
### [8.37.0] - 2019-07-03
- 'CRC-8 Checksum' operation added [@MShwed] | [#591]
### [8.36.0] - 2019-07-03
- 'PGP Verify' operation added [@artemisbot] | [#585]
### [8.35.0] - 2019-07-03
- 'Sharpen Image', 'Convert Image Format' and 'Add Text To Image' operations added [@j433866] | [#515]
### [8.34.0] - 2019-06-28
- Various new visualisations added to the 'Entropy' operation [@MShwed] | [#535]
- Efficiency improvements made to the 'Entropy' operation for large file support [@n1474335]
### [8.33.0] - 2019-06-27
- 'Bzip2 Compress' operation added and 'Bzip2 Decompress' operation greatly improved [@artemisbot] | [#531]
### [8.32.0] - 2019-06-27
- 'Indec of Coincidence' operation added [@Ge0rg3] | [#571]
### [8.31.0] - 2019-04-12
- The downloadable version of CyberChef is now a .zip file containing separate modules rather than a single .htm file. It is still completely standalone and will not make any external network requests. This change reduces the complexity of the build process significantly. [@n1474335]
### [8.30.0] - 2019-04-12
- 'Decode Protobuf' operation added [@n1474335] | [#533]
### [8.29.0] - 2019-03-31
- 'BLAKE2s' and 'BLAKE2b' hashing operations added [@h345983745] | [#525]
### [8.28.0] - 2019-03-31
- 'Heatmap Chart', 'Hex Density Chart', 'Scatter Chart' and 'Series Chart' operation added [@artemisbot] [@tlwr] | [#496] [#143]
### [8.27.0] - 2019-03-14
- 'Enigma', 'Typex', 'Bombe' and 'Multiple Bombe' operations added [@s2224834] | [#516]
- See [this wiki article](https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex) for a full explanation of these operations.
- New Bombe-style loading animation added for long-running operations [@n1474335]
- New operation argument types added: `populateMultiOption` and `argSelector` [@n1474335]
### [8.26.0] - 2019-03-09
- Various image manipulation operations added [@j433866] | [#506]
### [8.25.0] - 2019-03-09
- 'Extract Files' operation added and more file formats supported [@n1474335] | [#440]
### [8.24.0] - 2019-02-08
- 'DNS over HTTPS' operation added [@h345983745] | [#489]
### [8.23.1] - 2019-01-18
- 'Convert co-ordinate format' operation added [@j433866] | [#476]
### [8.23.0] - 2019-01-18
- 'YARA Rules' operation added [@artemisbot] | [#468]
### [8.22.0] - 2019-01-10
- 'Subsection' operation added [@j433866] | [#467]
### [8.21.0] - 2019-01-10
- 'To Case Insensitive Regex' and 'From Case Insensitive Regex' operations added [@masq] | [#461]
### [8.20.0] - 2019-01-09
- 'Generate Lorem Ipsum' operation added [@klaxon1] | [#455]
### [8.19.0] - 2018-12-30
- UI test suite added to confirm that the app loads correctly in a reasonable time and that various operations from each module can be run [@n1474335] | [#458]
### [8.18.0] - 2018-12-26
- 'Split Colour Channels' operation added [@artemisbot] | [#449]
### [8.17.0] - 2018-12-25
- 'Generate QR Code' and 'Parse QR Code' operations added [@j433866] | [#448]
### [8.16.0] - 2018-12-19
- 'Play Media' operation added [@anthony-arnold] | [#446]
### [8.15.0] - 2018-12-18
- 'Text Encoding Brute Force' operation added [@Cynser] | [#439]
### [8.14.0] - 2018-12-18
- 'To Base62' and 'From Base62' operations added [@tcode2k16] | [#443]
### [8.13.0] - 2018-12-15
- 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441]
### [8.12.0] - 2018-11-21 ### [8.12.0] - 2018-11-21
- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428] - 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428]
@@ -20,7 +106,7 @@ All major and minor version changes will be documented in this file. Details of
- 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348] - 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348]
### [8.6.0] - 2018-08-29 ### [8.6.0] - 2018-08-29
- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344] - 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344]
### [8.5.0] - 2018-08-23 ### [8.5.0] - 2018-08-23
- 'To Braille' and 'From Braille' operations added [@n1474335] | [#255] - 'To Braille' and 'From Braille' operations added [@n1474335] | [#255]
@@ -66,6 +152,33 @@ All major and minor version changes will be documented in this file. Details of
[8.38.0]: https://github.com/gchq/CyberChef/releases/tag/v8.38.0
[8.37.0]: https://github.com/gchq/CyberChef/releases/tag/v8.37.0
[8.36.0]: https://github.com/gchq/CyberChef/releases/tag/v8.36.0
[8.35.0]: https://github.com/gchq/CyberChef/releases/tag/v8.35.0
[8.34.0]: https://github.com/gchq/CyberChef/releases/tag/v8.34.0
[8.33.0]: https://github.com/gchq/CyberChef/releases/tag/v8.33.0
[8.32.0]: https://github.com/gchq/CyberChef/releases/tag/v8.32.0
[8.31.0]: https://github.com/gchq/CyberChef/releases/tag/v8.31.0
[8.30.0]: https://github.com/gchq/CyberChef/releases/tag/v8.30.0
[8.29.0]: https://github.com/gchq/CyberChef/releases/tag/v8.29.0
[8.28.0]: https://github.com/gchq/CyberChef/releases/tag/v8.28.0
[8.27.0]: https://github.com/gchq/CyberChef/releases/tag/v8.27.0
[8.26.0]: https://github.com/gchq/CyberChef/releases/tag/v8.26.0
[8.25.0]: https://github.com/gchq/CyberChef/releases/tag/v8.25.0
[8.24.0]: https://github.com/gchq/CyberChef/releases/tag/v8.24.0
[8.23.1]: https://github.com/gchq/CyberChef/releases/tag/v8.23.1
[8.23.0]: https://github.com/gchq/CyberChef/releases/tag/v8.23.0
[8.22.0]: https://github.com/gchq/CyberChef/releases/tag/v8.22.0
[8.21.0]: https://github.com/gchq/CyberChef/releases/tag/v8.21.0
[8.20.0]: https://github.com/gchq/CyberChef/releases/tag/v8.20.0
[8.19.0]: https://github.com/gchq/CyberChef/releases/tag/v8.19.0
[8.18.0]: https://github.com/gchq/CyberChef/releases/tag/v8.18.0
[8.17.0]: https://github.com/gchq/CyberChef/releases/tag/v8.17.0
[8.16.0]: https://github.com/gchq/CyberChef/releases/tag/v8.16.0
[8.15.0]: https://github.com/gchq/CyberChef/releases/tag/v8.15.0
[8.14.0]: https://github.com/gchq/CyberChef/releases/tag/v8.14.0
[8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0
[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0 [8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0
[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0 [8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0
[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0 [8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0
@@ -86,8 +199,12 @@ All major and minor version changes will be documented in this file. Details of
[@n1474335]: https://github.com/n1474335 [@n1474335]: https://github.com/n1474335
[@d98762625]: https://github.com/d98762625 [@d98762625]: https://github.com/d98762625
[@j433866]: https://github.com/j433866
[@GCHQ77703]: https://github.com/GCHQ77703 [@GCHQ77703]: https://github.com/GCHQ77703
[@h345983745]: https://github.com/h345983745
[@s2224834]: https://github.com/s2224834
[@artemisbot]: https://github.com/artemisbot [@artemisbot]: https://github.com/artemisbot
[@tlwr]: https://github.com/tlwr
[@picapi]: https://github.com/picapi [@picapi]: https://github.com/picapi
[@Dachande663]: https://github.com/Dachande663 [@Dachande663]: https://github.com/Dachande663
[@JustAnotherMark]: https://github.com/JustAnotherMark [@JustAnotherMark]: https://github.com/JustAnotherMark
@@ -96,9 +213,17 @@ All major and minor version changes will be documented in this file. Details of
[@arnydo]: https://github.com/arnydo [@arnydo]: https://github.com/arnydo
[@klaxon1]: https://github.com/klaxon1 [@klaxon1]: https://github.com/klaxon1
[@bwhitn]: https://github.com/bwhitn [@bwhitn]: https://github.com/bwhitn
[@jarmovanlenthe]: https://github.com/jarmovanlenthe
[@tcode2k16]: https://github.com/tcode2k16
[@Cynser]: https://github.com/Cynser
[@anthony-arnold]: https://github.com/anthony-arnold
[@masq]: https://github.com/masq
[@Ge0rg3]: https://github.com/Ge0rg3
[@MShwed]: https://github.com/MShwed
[#95]: https://github.com/gchq/CyberChef/pull/299 [#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173 [#173]: https://github.com/gchq/CyberChef/pull/173
[#143]: https://github.com/gchq/CyberChef/pull/143
[#224]: https://github.com/gchq/CyberChef/pull/224 [#224]: https://github.com/gchq/CyberChef/pull/224
[#239]: https://github.com/gchq/CyberChef/pull/239 [#239]: https://github.com/gchq/CyberChef/pull/239
[#248]: https://github.com/gchq/CyberChef/pull/248 [#248]: https://github.com/gchq/CyberChef/pull/248
@@ -119,3 +244,29 @@ All major and minor version changes will be documented in this file. Details of
[#387]: https://github.com/gchq/CyberChef/pull/387 [#387]: https://github.com/gchq/CyberChef/pull/387
[#394]: https://github.com/gchq/CyberChef/pull/394 [#394]: https://github.com/gchq/CyberChef/pull/394
[#428]: https://github.com/gchq/CyberChef/pull/428 [#428]: https://github.com/gchq/CyberChef/pull/428
[#439]: https://github.com/gchq/CyberChef/pull/439
[#440]: https://github.com/gchq/CyberChef/pull/440
[#441]: https://github.com/gchq/CyberChef/pull/441
[#443]: https://github.com/gchq/CyberChef/pull/443
[#446]: https://github.com/gchq/CyberChef/pull/446
[#448]: https://github.com/gchq/CyberChef/pull/448
[#449]: https://github.com/gchq/CyberChef/pull/449
[#455]: https://github.com/gchq/CyberChef/pull/455
[#458]: https://github.com/gchq/CyberChef/pull/458
[#461]: https://github.com/gchq/CyberChef/pull/461
[#467]: https://github.com/gchq/CyberChef/pull/467
[#468]: https://github.com/gchq/CyberChef/pull/468
[#476]: https://github.com/gchq/CyberChef/pull/476
[#489]: https://github.com/gchq/CyberChef/pull/489
[#496]: https://github.com/gchq/CyberChef/pull/496
[#506]: https://github.com/gchq/CyberChef/pull/506
[#515]: https://github.com/gchq/CyberChef/pull/515
[#516]: https://github.com/gchq/CyberChef/pull/516
[#525]: https://github.com/gchq/CyberChef/pull/525
[#530]: https://github.com/gchq/CyberChef/pull/530
[#531]: https://github.com/gchq/CyberChef/pull/531
[#533]: https://github.com/gchq/CyberChef/pull/533
[#535]: https://github.com/gchq/CyberChef/pull/535
[#571]: https://github.com/gchq/CyberChef/pull/571
[#585]: https://github.com/gchq/CyberChef/pull/585
[#591]: https://github.com/gchq/CyberChef/pull/591

View File

@@ -2,8 +2,8 @@
const webpack = require("webpack"); const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const NodeExternals = require("webpack-node-externals"); const NodeExternals = require("webpack-node-externals");
const Inliner = require("web-resource-inliner");
const glob = require("glob"); const glob = require("glob");
const path = require("path"); const path = require("path");
@@ -29,8 +29,12 @@ module.exports = function (grunt) {
["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]); ["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
grunt.registerTask("test", grunt.registerTask("test",
"A task which runs all the tests in test/tests.", "A task which runs all the operation tests in the tests directory.",
["exec:generateConfig", "exec:tests"]); ["exec:generateConfig", "exec:opTests"]);
grunt.registerTask("testui",
"A task which runs all the UI tests in the tests directory. The prod task must already have been run.",
["connect:prod", "exec:browserTests"]);
grunt.registerTask("docs", grunt.registerTask("docs",
"Compiles documentation in the /docs directory.", "Compiles documentation in the /docs directory.",
@@ -38,18 +42,16 @@ module.exports = function (grunt) {
grunt.registerTask("prod", grunt.registerTask("prod",
"Creates a production-ready build. Use the --msg flag to add a compile message.", "Creates a production-ready build. Use the --msg flag to add a compile message.",
["eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web", "inline", "chmod"]); [
"eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web",
"copy:standalone", "zip:standalone", "clean:standalone", "chmod"
]);
grunt.registerTask("default", grunt.registerTask("default",
"Lints the code base", "Lints the code base",
["eslint", "exec:repoSize"]); ["eslint", "exec:repoSize"]);
grunt.registerTask("inline",
"Compiles a production build of CyberChef into a single, portable web page.",
["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]);
grunt.registerTask("runInliner", runInliner);
grunt.registerTask("doc", "docs"); grunt.registerTask("doc", "docs");
grunt.registerTask("tests", "test"); grunt.registerTask("tests", "test");
grunt.registerTask("lint", "eslint"); grunt.registerTask("lint", "eslint");
@@ -66,6 +68,8 @@ module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-exec"); grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-accessibility"); grunt.loadNpmTasks("grunt-accessibility");
grunt.loadNpmTasks("grunt-concurrent"); grunt.loadNpmTasks("grunt-concurrent");
grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-zip");
// Project configuration // Project configuration
@@ -88,32 +92,6 @@ module.exports = function (grunt) {
}, },
moduleEntryPoints = listEntryModules(); moduleEntryPoints = listEntryModules();
/**
* Compiles a production build of CyberChef into a single, portable web page.
*/
function runInliner() {
const done = this.async();
Inliner.html({
relativeTo: "build/prod/",
fileContent: grunt.file.read("build/prod/cyberchef.htm"),
images: true,
svgs: true,
scripts: true,
links: true,
strict: true
}, function(error, result) {
if (error) {
if (error instanceof Error) {
done(error);
} else {
done(new Error(error));
}
} else {
grunt.file.write("build/prod/cyberchef.htm", result);
done(true);
}
});
}
/** /**
* Generates an entry list for all the modules. * Generates an entry list for all the modules.
@@ -124,7 +102,7 @@ module.exports = function (grunt) {
glob.sync("./src/core/config/modules/*.mjs").forEach(file => { glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
const basename = path.basename(file); const basename = path.basename(file);
if (basename !== "Default.mjs" && basename !== "OpModules.mjs") if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
entryModules[basename.split(".mjs")[0]] = path.resolve(file); entryModules["modules/" + basename.split(".mjs")[0]] = path.resolve(file);
}); });
return entryModules; return entryModules;
@@ -137,17 +115,17 @@ module.exports = function (grunt) {
node: ["build/node/*"], node: ["build/node/*"],
config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"], config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"], docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
inlineScripts: ["build/prod/scripts.js"], standalone: ["build/prod/CyberChef*.html"]
}, },
eslint: { eslint: {
options: { options: {
configFile: "./.eslintrc.json" configFile: "./.eslintrc.json"
}, },
configs: ["*.js"], configs: ["*.{js,mjs}"],
core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"], core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
web: ["src/web/**/*.{js,mjs}"], web: ["src/web/**/*.{js,mjs}", "!src/web/static/**/*"],
node: ["src/node/**/*.{js,mjs}"], node: ["src/node/**/*.{js,mjs}"],
tests: ["test/**/*.{js,mjs}"], tests: ["tests/**/*.{js,mjs}"],
}, },
jsdoc: { jsdoc: {
options: { options: {
@@ -179,77 +157,48 @@ module.exports = function (grunt) {
}, },
webpack: { webpack: {
options: webpackConfig, options: webpackConfig,
web: { web: () => {
mode: "production", return {
target: "web", mode: "production",
entry: Object.assign({ target: "web",
main: "./src/web/index.js", entry: Object.assign({
sitemap: "./src/web/static/sitemap.js" main: "./src/web/index.js",
}, moduleEntryPoints), sitemap: "./src/web/static/sitemap.js"
output: { }, moduleEntryPoints),
path: __dirname + "/build/prod" output: {
}, path: __dirname + "/build/prod",
resolve: { filename: chunkData => {
alias: { return chunkData.chunk.name === "main" ? "assets/[name].js": "[name].js";
"./config/modules/OpModules": "./config/modules/Default" },
} globalObject: "this"
}, },
plugins: [ resolve: {
new webpack.DefinePlugin(BUILD_CONSTANTS), alias: {
new HtmlWebpackPlugin({ "./config/modules/OpModules": "./config/modules/Default"
filename: "index.html",
template: "./src/web/html/index.html",
chunks: ["main"],
compileTime: compileTime,
version: pkg.version,
minify: {
removeComments: true,
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true
} }
}), },
] plugins: [
}, new webpack.DefinePlugin(BUILD_CONSTANTS),
webInline: { new HtmlWebpackPlugin({
mode: "production", filename: "index.html",
target: "web", template: "./src/web/html/index.html",
entry: "./src/web/index.js", chunks: ["main"],
output: { compileTime: compileTime,
filename: "scripts.js", version: pkg.version,
path: __dirname + "/build/prod" minify: {
}, removeComments: true,
plugins: [ collapseWhitespace: true,
new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, { minifyJS: true,
INLINE: "true" minifyCSS: true
})), }
new HtmlWebpackPlugin({ }),
filename: "cyberchef.htm", new BundleAnalyzerPlugin({
template: "./src/web/html/index.html", analyzerMode: "static",
compileTime: compileTime, reportFilename: "BundleAnalyzerReport.html",
version: pkg.version + "s", openAnalyzer: false
inline: true, }),
minify: { ]
removeComments: true, };
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true
}
}),
]
},
tests: {
mode: "development",
target: "node",
entry: "./test/index.mjs",
externals: [NodeExternals()],
output: {
filename: "index.js",
path: __dirname + "/build/test"
},
plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS)
]
}, },
node: { node: {
mode: "production", mode: "production",
@@ -283,7 +232,8 @@ module.exports = function (grunt) {
warningsFilter: [ warningsFilter: [
/source-map/, /source-map/,
/dependency is an expression/, /dependency is an expression/,
/export 'default'/ /export 'default'/,
/Can't resolve 'sodium'/
], ],
} }
}, },
@@ -299,6 +249,9 @@ module.exports = function (grunt) {
"./config/modules/OpModules": "./config/modules/Default" "./config/modules/OpModules": "./config/modules/Default"
} }
}, },
output: {
globalObject: "this",
},
plugins: [ plugins: [
new webpack.DefinePlugin(BUILD_CONSTANTS), new webpack.DefinePlugin(BUILD_CONSTANTS),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
@@ -312,14 +265,40 @@ module.exports = function (grunt) {
} }
} }
}, },
zip: {
standalone: {
cwd: "build/prod/",
src: [
"build/prod/**/*",
"!build/prod/index.html",
"!build/prod/BundleAnalyzerReport.html",
"!build/prod/sitemap.js"
],
dest: `build/prod/CyberChef_v${pkg.version}.zip`
}
},
connect: {
prod: {
options: {
port: 8000,
base: "build/prod/"
}
}
},
copy: { copy: {
ghPages: { ghPages: {
options: { options: {
process: function (content, srcpath) { process: function (content, srcpath) {
// Add Google Analytics code to index.html
if (srcpath.indexOf("index.html") >= 0) { if (srcpath.indexOf("index.html") >= 0) {
// Add Google Analytics code to index.html
content = content.replace("</body></html>", content = content.replace("</body></html>",
grunt.file.read("src/web/static/ga.html") + "</body></html>"); grunt.file.read("src/web/static/ga.html") + "</body></html>");
// Add Structured Data for SEO
content = content.replace("</head>",
"<script type='application/ld+json'>" +
JSON.stringify(JSON.parse(grunt.file.read("src/web/static/structuredData.json"))) +
"</script></head>");
return grunt.template.process(content, srcpath); return grunt.template.process(content, srcpath);
} else { } else {
return content; return content;
@@ -338,6 +317,28 @@ module.exports = function (grunt) {
dest: "build/prod/" dest: "build/prod/"
}, },
] ]
},
standalone: {
options: {
process: function (content, srcpath) {
if (srcpath.indexOf("index.html") >= 0) {
// Replace download link with version number
content = content.replace(/<a [^>]+>Download CyberChef.+?<\/a>/,
`<span>Version ${pkg.version}</span>`);
return grunt.template.process(content, srcpath);
} else {
return content;
}
},
noProcess: ["**", "!**/*.html"]
},
files: [
{
src: "build/prod/index.html",
dest: `build/prod/CyberChef_v${pkg.version}.html`
}
]
} }
}, },
chmod: { chmod: {
@@ -383,16 +384,17 @@ module.exports = function (grunt) {
generateConfig: { generateConfig: {
command: [ command: [
"echo '\n--- Regenerating config files. ---'", "echo '\n--- Regenerating config files. ---'",
"mkdir -p src/core/config/modules", "echo [] > src/core/config/OperationConfig.json",
"echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
"echo '[]\n' > src/core/config/OperationConfig.json",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs", "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
"node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs", "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
"echo '--- Config scripts finished. ---\n'" "echo '--- Config scripts finished. ---\n'"
].join(";") ].join(";")
}, },
tests: { opTests: {
command: "node --experimental-modules --no-warnings --no-deprecation test/index.mjs" command: "node --experimental-modules --no-warnings --no-deprecation tests/operations/index.mjs"
},
browserTests: {
command: "./node_modules/.bin/nightwatch --env prod"
} }
}, },
}); });

View File

@@ -1,23 +1,26 @@
module.exports = function(api) { module.exports = function(api) {
api.cache.forever(); api.cache.forever();
return { return {
"presets": [ "presets": [
["@babel/preset-env", { ["@babel/preset-env", {
"targets": {
"chrome": 40,
"firefox": 35,
"edge": 14,
"node": "6.5"
},
"modules": false, "modules": false,
"useBuiltIns": "entry" "useBuiltIns": "entry",
"corejs": 3
}] }]
], ],
"plugins": [ "plugins": [
["babel-plugin-transform-builtin-extend", { "babel-plugin-syntax-dynamic-import",
"globals": ["Error"] [
}] "babel-plugin-transform-builtin-extend", {
"globals": ["Error"]
}
],
[
"@babel/plugin-transform-runtime", {
"regenerator": true
}
]
] ]
}; };
}; };

30
nightwatch.json Normal file
View File

@@ -0,0 +1,30 @@
{
"src_folders": ["tests/browser"],
"output_folder": "tests/browser/output",
"test_settings": {
"default": {
"launch_url": "http://localhost:8080",
"webdriver": {
"start_process": true,
"server_path": "./node_modules/.bin/chromedriver",
"port": 9515,
"log_path": false
},
"desiredCapabilities": {
"browserName": "chrome"
}
},
"dev": {
"launch_url": "http://localhost:8080"
},
"prod": {
"launch_url": "http://localhost:8000/index.html"
}
}
}

8418
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "8.12.1", "version": "8.38.0",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>", "author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef", "homepage": "https://gchq.github.io/CyberChef",
@@ -29,107 +29,134 @@
}, },
"main": "build/node/CyberChef.js", "main": "build/node/CyberChef.js",
"bugs": "https://github.com/gchq/CyberChef/issues", "bugs": "https://github.com/gchq/CyberChef/issues",
"browserslist": [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14",
"node >= 6.5"
],
"devDependencies": { "devDependencies": {
"@babel/core": "^7.1.5", "@babel/core": "^7.4.5",
"@babel/preset-env": "^7.1.5", "@babel/plugin-transform-runtime": "^7.4.4",
"autoprefixer": "^9.3.1", "@babel/preset-env": "^7.4.5",
"babel-loader": "^8.0.4", "autoprefixer": "^9.6.0",
"bootstrap": "^4.1.3", "babel-eslint": "^10.0.2",
"colors": "^1.3.2", "babel-loader": "^8.0.6",
"css-loader": "^1.0.1", "babel-plugin-syntax-dynamic-import": "^6.18.0",
"eslint": "^5.8.0", "chromedriver": "^75.0.0",
"colors": "^1.3.3",
"css-loader": "^3.0.0",
"eslint": "^6.0.1",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-alpha0", "file-loader": "^4.0.0",
"file-loader": "^2.0.0", "grunt": "^1.0.4",
"grunt": "^1.0.3",
"grunt-accessibility": "~6.0.0", "grunt-accessibility": "~6.0.0",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-concurrent": "^2.3.1", "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "~2.0.0", "grunt-contrib-clean": "~2.0.0",
"grunt-contrib-connect": "^2.0.0",
"grunt-contrib-copy": "~1.0.0", "grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^21.0.0", "grunt-eslint": "^21.1.0",
"grunt-exec": "~3.0.0", "grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.3.0", "grunt-jsdoc": "^2.4.0",
"grunt-webpack": "^3.1.3", "grunt-webpack": "^3.1.3",
"grunt-zip": "^0.18.2",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"jsdoc-babel": "^0.5.0", "jsdoc-babel": "^0.5.0",
"node-sass": "^4.10.0", "mini-css-extract-plugin": "^0.7.0",
"postcss-css-variables": "^0.11.0", "nightwatch": "^1.1.12",
"node-sass": "^4.12.0",
"postcss-css-variables": "^0.13.0",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"prompt": "^1.0.0", "prompt": "^1.0.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"sitemap": "^2.1.0", "sitemap": "^2.2.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"url-loader": "^1.1.2", "svg-url-loader": "^2.3.3",
"web-resource-inliner": "^4.2.1", "url-loader": "^2.0.1",
"webpack": "^4.25.1", "webpack": "^4.35.0",
"webpack-dev-server": "^3.1.10", "webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-server": "^3.7.2",
"webpack-node-externals": "^1.7.2", "webpack-node-externals": "^1.7.2",
"worker-loader": "^2.0.0" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.4.5",
"arrive": "^2.4.1", "arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2", "babel-plugin-transform-builtin-extend": "1.1.2",
"babel-polyfill": "^6.26.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bignumber.js": "^8.0.1", "bignumber.js": "^9.0.0",
"blakejs": "^1.1.0",
"bootstrap": "4.3.1",
"bootstrap-colorpicker": "^2.5.3", "bootstrap-colorpicker": "^2.5.3",
"bootstrap-material-design": "^4.1.1", "bootstrap-material-design": "^4.1.2",
"bson": "^3.0.2", "bson": "^4.0.2",
"chi-squared": "^1.1.0", "chi-squared": "^1.1.0",
"clippyjs": "0.0.3",
"core-js": "^3.1.4",
"crypto-api": "^0.8.3", "crypto-api": "^0.8.3",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5", "ctph.js": "0.0.5",
"diff": "^3.5.0", "d3": "^5.9.4",
"d3-hexbin": "^0.2.2",
"diff": "^4.0.1",
"es6-promisify": "^6.0.1", "es6-promisify": "^6.0.1",
"escodegen": "^1.11.0", "escodegen": "^1.11.1",
"esmangle": "^1.0.1", "esmangle": "^1.0.1",
"esprima": "^4.0.1", "esprima": "^4.0.1",
"exif-parser": "^0.1.12", "exif-parser": "^0.1.12",
"file-saver": "^2.0.0-rc.4", "file-saver": "^2.0.2",
"highlight.js": "^9.13.1", "geodesy": "^1.1.3",
"jquery": "^3.3.1", "highlight.js": "^9.15.8",
"jimp": "^0.6.4",
"jquery": "3.4.1",
"js-crc": "^0.2.0", "js-crc": "^0.2.0",
"js-sha3": "^0.8.0", "js-sha3": "^0.8.0",
"jsbn": "^1.1.0", "jsesc": "^2.5.2",
"jsesc": "^2.5.1", "jsonpath": "^1.0.2",
"jsonpath": "^1.0.0", "jsonwebtoken": "^8.5.1",
"jsonwebtoken": "^8.3.0", "jsqr": "^1.2.0",
"jsrsasign": "8.0.12", "jsrsasign": "8.0.12",
"kbpgp": "^2.0.82", "kbpgp": "2.1.2",
"libbzip2-wasm": "0.0.4",
"libyara-wasm": "0.0.12",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"loglevel": "^1.6.1", "loglevel": "^1.6.3",
"loglevel-message-prefix": "^3.0.0", "loglevel-message-prefix": "^3.0.0",
"moment": "^2.22.2", "moment": "^2.24.0",
"moment-timezone": "^0.5.23", "moment-timezone": "^0.5.25",
"ngeohash": "^0.6.0", "ngeohash": "^0.6.3",
"node-forge": "^0.7.6", "node-forge": "^0.8.5",
"node-md6": "^0.1.0", "node-md6": "^0.1.0",
"notepack.io": "^2.1.3", "nodom": "^2.2.0",
"notepack.io": "^2.2.0",
"nwmatcher": "^1.4.4", "nwmatcher": "^1.4.4",
"otp": "^0.1.3", "otp": "^0.1.3",
"popper.js": "^1.14.4", "popper.js": "^1.15.0",
"scryptsy": "^2.0.0", "qr-image": "^3.2.0",
"scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0", "snackbarjs": "^1.1.0",
"sortablejs": "^1.7.0", "sortablejs": "^1.9.0",
"split.js": "^1.5.9", "split.js": "^1.5.11",
"ssdeep.js": "0.0.2", "ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.19", "ua-parser-js": "^0.7.20",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",
"xpath": "0.0.27", "xpath": "0.0.27",
"xregexp": "^4.2.0", "xregexp": "^4.2.4",
"zlibjs": "^0.3.1" "zlibjs": "^0.3.1"
}, },
"scripts": { "scripts": {
"start": "grunt dev", "start": "grunt dev",
"build": "grunt prod", "build": "grunt prod",
"test": "grunt test", "test": "grunt test",
"testui": "grunt testui",
"docs": "grunt docs", "docs": "grunt docs",
"lint": "grunt lint", "lint": "grunt lint",
"newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs" "newop": "node --experimental-modules src/core/config/scripts/newOperation.mjs"

View File

@@ -1,13 +1,7 @@
module.exports = { module.exports = {
plugins: [ plugins: [
require("postcss-import"), require("postcss-import"),
require("autoprefixer")({ require("autoprefixer"),
browsers: [
"Chrome >= 40",
"Firefox >= 35",
"Edge >= 14"
]
}),
require("postcss-css-variables")({ require("postcss-css-variables")({
preserve: true preserve: true
}), }),

View File

@@ -89,23 +89,26 @@ class Chef {
progress = err.progress; progress = err.progress;
} }
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
// Create a raw version of the dish, unpresented // Create a raw version of the dish, unpresented
const rawDish = this.dish.clone(); const rawDish = this.dish.clone();
// Present the raw result // Present the raw result
await recipe.present(this.dish); await recipe.present(this.dish);
// Depending on the size of the output, we may send it back as a string or an ArrayBuffer.
// This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file.
// The threshold is specified in KiB.
const threshold = (options.ioDisplayThreshold || 1024) * 1024;
const returnType =
this.dish.size > threshold ?
Dish.ARRAY_BUFFER :
this.dish.type === Dish.HTML ?
Dish.HTML :
Dish.STRING;
return { return {
dish: rawDish, dish: rawDish,
result: this.dish.type === Dish.HTML ? result: await this.dish.get(returnType, notUTF8),
await this.dish.get(Dish.HTML, notUTF8) :
await this.dish.get(returnType, notUTF8),
type: Dish.enumLookup(this.dish.type), type: Dish.enumLookup(this.dish.type),
progress: progress, progress: progress,
duration: new Date().getTime() - startTime, duration: new Date().getTime() - startTime,
@@ -157,9 +160,9 @@ class Chef {
* @param {number} pos.end - The end offset. * @param {number} pos.end - The end offset.
* @returns {Object} * @returns {Object}
*/ */
calculateHighlights(recipeConfig, direction, pos) { async calculateHighlights(recipeConfig, direction, pos) {
const recipe = new Recipe(recipeConfig); const recipe = new Recipe(recipeConfig);
const highlights = recipe.generateHighlightList(); const highlights = await recipe.generateHighlightList();
if (!highlights) return false; if (!highlights) return false;

View File

@@ -6,7 +6,6 @@
* @license Apache-2.0 * @license Apache-2.0
*/ */
import "babel-polyfill";
import Chef from "./Chef"; import Chef from "./Chef";
import OperationConfig from "./config/OperationConfig.json"; import OperationConfig from "./config/OperationConfig.json";
import OpModules from "./config/modules/OpModules"; import OpModules from "./config/modules/OpModules";
@@ -157,8 +156,8 @@ async function getDishAs(data) {
* @param {number} pos.start - The start offset. * @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset. * @param {number} pos.end - The end offset.
*/ */
function calculateHighlights(recipeConfig, direction, pos) { async function calculateHighlights(recipeConfig, direction, pos) {
pos = self.chef.calculateHighlights(recipeConfig, direction, pos); pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
self.postMessage({ self.postMessage({
action: "highlightsCalculated", action: "highlightsCalculated",
@@ -179,7 +178,7 @@ self.loadRequiredModules = function(recipeConfig) {
if (!OpModules.hasOwnProperty(module)) { if (!OpModules.hasOwnProperty(module)) {
log.info(`Loading ${module} module`); log.info(`Loading ${module} module`);
self.sendStatusMessage(`Loading ${module} module`); self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(`${self.docURL}/${module}.js`); self.importScripts(`${self.docURL}/modules/${module}.js`);
self.sendStatusMessage(""); self.sendStatusMessage("");
} }
}); });

View File

@@ -6,6 +6,7 @@
*/ */
import Utils from "./Utils"; import Utils from "./Utils";
import DishError from "./errors/DishError";
import BigNumber from "bignumber.js"; import BigNumber from "bignumber.js";
import log from "loglevel"; import log from "loglevel";
@@ -20,8 +21,8 @@ class Dish {
* @param {Dish} [dish=null] - A dish to clone * @param {Dish} [dish=null] - A dish to clone
*/ */
constructor(dish=null) { constructor(dish=null) {
this.value = []; this.value = new ArrayBuffer(0);
this.type = Dish.BYTE_ARRAY; this.type = Dish.ARRAY_BUFFER;
if (dish && if (dish &&
dish.hasOwnProperty("value") && dish.hasOwnProperty("value") &&
@@ -61,7 +62,7 @@ class Dish {
case "list<file>": case "list<file>":
return Dish.LIST_FILE; return Dish.LIST_FILE;
default: default:
throw "Invalid data type string. No matching enum."; throw new DishError("Invalid data type string. No matching enum.");
} }
} }
@@ -93,7 +94,7 @@ class Dish {
case Dish.LIST_FILE: case Dish.LIST_FILE:
return "List<File>"; return "List<File>";
default: default:
throw "Invalid data type enum. No matching type."; throw new DishError("Invalid data type enum. No matching type.");
} }
} }
@@ -117,7 +118,7 @@ class Dish {
if (!this.valid()) { if (!this.valid()) {
const sample = Utils.truncate(JSON.stringify(this.value), 13); const sample = Utils.truncate(JSON.stringify(this.value), 13);
throw "Data is not a valid " + Dish.enumLookup(type) + ": " + sample; throw new DishError(`Data is not a valid ${Dish.enumLookup(type)}: ${sample}`);
} }
} }
@@ -148,80 +149,85 @@ class Dish {
*/ */
async _translate(toType, notUTF8=false) { async _translate(toType, notUTF8=false) {
log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`);
const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8;
// Convert data to intermediate byteArray type // Convert data to intermediate ArrayBuffer type
switch (this.type) { try {
case Dish.STRING: switch (this.type) {
this.value = this.value ? Utils.strToByteArray(this.value) : []; case Dish.STRING:
break; this.value = this.value ? Utils.strToArrayBuffer(this.value) : new ArrayBuffer;
case Dish.NUMBER: break;
this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : []; case Dish.NUMBER:
break; this.value = typeof this.value === "number" ? Utils.strToArrayBuffer(this.value.toString()) : new ArrayBuffer;
case Dish.HTML: break;
this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; case Dish.HTML:
break; this.value = this.value ? Utils.strToArrayBuffer(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : new ArrayBuffer;
case Dish.ARRAY_BUFFER: break;
// Array.from() would be nicer here, but it's slightly slower case Dish.BYTE_ARRAY:
this.value = Array.prototype.slice.call(new Uint8Array(this.value)); this.value = new Uint8Array(this.value).buffer;
break; break;
case Dish.BIG_NUMBER: case Dish.BIG_NUMBER:
this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; this.value = BigNumber.isBigNumber(this.value) ? Utils.strToArrayBuffer(this.value.toFixed()) : new ArrayBuffer;
break; break;
case Dish.JSON: case Dish.JSON:
this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value, null, 4)) : []; this.value = this.value ? Utils.strToArrayBuffer(JSON.stringify(this.value, null, 4)) : new ArrayBuffer;
break; break;
case Dish.FILE: case Dish.FILE:
this.value = await Utils.readFile(this.value); this.value = (await Utils.readFile(this.value)).buffer;
this.value = Array.prototype.slice.call(this.value); break;
break; case Dish.LIST_FILE:
case Dish.LIST_FILE: this.value = await Promise.all(this.value.map(async f => Utils.readFile(f)));
this.value = await Promise.all(this.value.map(async f => Utils.readFile(f))); this.value = concatenateTypedArrays(...this.value).buffer;
this.value = this.value.map(b => Array.prototype.slice.call(b)); break;
this.value = [].concat.apply([], this.value); default:
break; break;
default: }
break; } catch (err) {
throw new DishError(`Error translating from ${Dish.enumLookup(this.type)} to ArrayBuffer: ${err}`);
} }
this.type = Dish.BYTE_ARRAY; this.type = Dish.ARRAY_BUFFER;
// Convert from byteArray to toType // Convert from ArrayBuffer to toType
switch (toType) { try {
case Dish.STRING: switch (toType) {
case Dish.HTML: case Dish.STRING:
this.value = this.value ? byteArrayToStr(this.value) : ""; case Dish.HTML:
this.type = Dish.STRING; this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : "";
break; this.type = Dish.STRING;
case Dish.NUMBER: break;
this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; case Dish.NUMBER:
this.type = Dish.NUMBER; this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0;
break; this.type = Dish.NUMBER;
case Dish.ARRAY_BUFFER: break;
this.value = new Uint8Array(this.value).buffer; case Dish.BYTE_ARRAY:
this.type = Dish.ARRAY_BUFFER; this.value = Array.prototype.slice.call(new Uint8Array(this.value));
break; this.type = Dish.ARRAY_BUFFER;
case Dish.BIG_NUMBER: break;
try { case Dish.BIG_NUMBER:
this.value = new BigNumber(byteArrayToStr(this.value)); try {
} catch (err) { this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8));
this.value = new BigNumber(NaN); } catch (err) {
} this.value = new BigNumber(NaN);
this.type = Dish.BIG_NUMBER; }
break; this.type = Dish.BIG_NUMBER;
case Dish.JSON: break;
this.value = JSON.parse(byteArrayToStr(this.value)); case Dish.JSON:
this.type = Dish.JSON; this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8));
break; this.type = Dish.JSON;
case Dish.FILE: break;
this.value = new File(this.value, "unknown"); case Dish.FILE:
break; this.value = new File(this.value, "unknown");
case Dish.LIST_FILE: this.type = Dish.FILE;
this.value = [new File(this.value, "unknown")]; break;
this.type = Dish.LIST_FILE; case Dish.LIST_FILE:
break; this.value = [new File(this.value, "unknown")];
default: this.type = Dish.LIST_FILE;
break; break;
default:
break;
}
} catch (err) {
throw new DishError(`Error translating from ArrayBuffer to ${Dish.enumLookup(toType)}: ${err}`);
} }
} }
@@ -256,7 +262,7 @@ class Dish {
case Dish.ARRAY_BUFFER: case Dish.ARRAY_BUFFER:
return this.value instanceof ArrayBuffer; return this.value instanceof ArrayBuffer;
case Dish.BIG_NUMBER: case Dish.BIG_NUMBER:
return this.value instanceof BigNumber; return BigNumber.isBigNumber(this.value);
case Dish.JSON: case Dish.JSON:
// All values can be serialised in some manner, so we return true in all cases // All values can be serialised in some manner, so we return true in all cases
return true; return true;
@@ -357,7 +363,7 @@ class Dish {
); );
break; break;
default: default:
throw new Error("Cannot clone Dish, unknown type"); throw new DishError("Cannot clone Dish, unknown type");
} }
return newDish; return newDish;
@@ -365,6 +371,26 @@ class Dish {
} }
/**
* Concatenates a list of Uint8Arrays together
*
* @param {Uint8Array[]} arrays
* @returns {Uint8Array}
*/
function concatenateTypedArrays(...arrays) {
let totalLength = 0;
for (const arr of arrays) {
totalLength += arr.length;
}
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
/** /**
* Dish data type enum for byte arrays. * Dish data type enum for byte arrays.

View File

@@ -23,8 +23,13 @@ class Ingredient {
this._value = null; this._value = null;
this.disabled = false; this.disabled = false;
this.hint = ""; this.hint = "";
this.rows = 0;
this.toggleValues = []; this.toggleValues = [];
this.target = null; this.target = null;
this.defaultIndex = 0;
this.min = null;
this.max = null;
this.step = 1;
if (ingredientConfig) { if (ingredientConfig) {
this._parseConfig(ingredientConfig); this._parseConfig(ingredientConfig);
@@ -44,8 +49,13 @@ class Ingredient {
this.defaultValue = ingredientConfig.value; this.defaultValue = ingredientConfig.value;
this.disabled = !!ingredientConfig.disabled; this.disabled = !!ingredientConfig.disabled;
this.hint = ingredientConfig.hint || false; this.hint = ingredientConfig.hint || false;
this.rows = ingredientConfig.rows || false;
this.toggleValues = ingredientConfig.toggleValues; this.toggleValues = ingredientConfig.toggleValues;
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0;
this.min = ingredientConfig.min;
this.max = ingredientConfig.max;
this.step = ingredientConfig.step;
} }
@@ -93,6 +103,7 @@ class Ingredient {
case "binaryString": case "binaryString":
case "binaryShortString": case "binaryShortString":
case "editableOption": case "editableOption":
case "editableOptionShort":
return Utils.parseEscapedChars(data); return Utils.parseEscapedChars(data);
case "byteArray": case "byteArray":
if (typeof data == "string") { if (typeof data == "string") {

View File

@@ -23,6 +23,7 @@ class Operation {
this._breakpoint = false; this._breakpoint = false;
this._disabled = false; this._disabled = false;
this._flowControl = false; this._flowControl = false;
this._manualBake = false;
this._ingList = []; this._ingList = [];
// Public fields // Public fields
@@ -179,8 +180,13 @@ class Operation {
if (ing.toggleValues) conf.toggleValues = ing.toggleValues; if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
if (ing.hint) conf.hint = ing.hint; if (ing.hint) conf.hint = ing.hint;
if (ing.rows) conf.rows = ing.rows;
if (ing.disabled) conf.disabled = ing.disabled; if (ing.disabled) conf.disabled = ing.disabled;
if (ing.target) conf.target = ing.target; if (ing.target) conf.target = ing.target;
if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex;
if (typeof ing.min === "number") conf.min = ing.min;
if (typeof ing.max === "number") conf.max = ing.max;
if (ing.step) conf.step = ing.step;
return conf; return conf;
}); });
} }
@@ -280,6 +286,7 @@ class Operation {
return this._flowControl; return this._flowControl;
} }
/** /**
* Set whether this Operation is a flowcontrol op. * Set whether this Operation is a flowcontrol op.
* *
@@ -289,6 +296,26 @@ class Operation {
this._flowControl = !!value; this._flowControl = !!value;
} }
/**
* Returns true if this Operation should not trigger AutoBake.
*
* @returns {boolean}
*/
get manualBake() {
return this._manualBake;
}
/**
* Set whether this Operation should trigger AutoBake.
*
* @param {boolean} value
*/
set manualBake(value) {
this._manualBake = !!value;
}
} }
export default Operation; export default Operation;

View File

@@ -4,12 +4,15 @@
* @license Apache-2.0 * @license Apache-2.0
*/ */
// import Operation from "./Operation.js";
import OpModules from "./config/modules/OpModules";
import OperationConfig from "./config/OperationConfig.json"; import OperationConfig from "./config/OperationConfig.json";
import OperationError from "./errors/OperationError"; import OperationError from "./errors/OperationError";
import Operation from "./Operation";
import DishError from "./errors/DishError";
import log from "loglevel"; import log from "loglevel";
// Cache container for modules
let modules = null;
/** /**
* The Recipe controls a list of Operations and the Dish they operate on. * The Recipe controls a list of Operations and the Dish they operate on.
*/ */
@@ -36,16 +39,43 @@ class Recipe {
* @param {Object} recipeConfig * @param {Object} recipeConfig
*/ */
_parseConfig(recipeConfig) { _parseConfig(recipeConfig) {
for (let c = 0; c < recipeConfig.length; c++) { recipeConfig.forEach(c => {
const operationName = recipeConfig[c].op; this.opList.push({
const opConf = OperationConfig[operationName]; name: c.op,
const opObj = OpModules[opConf.module][operationName]; module: OperationConfig[c.op].module,
const operation = new opObj(); ingValues: c.args,
operation.ingValues = recipeConfig[c].args; breakpoint: c.breakpoint,
operation.breakpoint = recipeConfig[c].breakpoint; disabled: c.disabled,
operation.disabled = recipeConfig[c].disabled; });
this.addOperation(operation); });
}
/**
* Populate elements of opList with operation instances.
* Dynamic import here removes top-level cyclic dependency issue.
*
* @private
*/
async _hydrateOpList() {
if (!modules) {
// Using Webpack Magic Comments to force the dynamic import to be included in the main chunk
// https://webpack.js.org/api/module-methods/
modules = await import(/* webpackMode: "eager" */ "./config/modules/OpModules");
modules = modules.default;
} }
this.opList = this.opList.map(o => {
if (o instanceof Operation) {
return o;
} else {
const op = new modules[o.module][o.name]();
op.ingValues = o.ingValues;
op.breakpoint = o.breakpoint;
op.disabled = o.disabled;
return op;
}
});
} }
@@ -55,7 +85,10 @@ class Recipe {
* @returns {Object[]} * @returns {Object[]}
*/ */
get config() { get config() {
return this.opList.map(op => op.config); return this.opList.map(op => ({
op: op.name,
args: op.ingValues,
}));
} }
@@ -75,7 +108,19 @@ class Recipe {
* @param {Operation[]} operations * @param {Operation[]} operations
*/ */
addOperations(operations) { addOperations(operations) {
this.opList = this.opList.concat(operations); operations.forEach(o => {
if (o instanceof Operation) {
this.opList.push(o);
} else {
this.opList.push({
name: o.name,
module: o.module,
ingValues: o.args,
breakpoint: o.breakpoint,
disabled: o.disabled,
});
}
});
} }
@@ -108,7 +153,7 @@ class Recipe {
/** /**
* Returns true if there is an Flow Control Operation in this Recipe. * Returns true if there is a Flow Control Operation in this Recipe.
* *
* @returns {boolean} * @returns {boolean}
*/ */
@@ -137,6 +182,8 @@ class Recipe {
if (startFrom === 0) this.lastRunOp = null; if (startFrom === 0) this.lastRunOp = null;
await this._hydrateOpList();
log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`); log.debug(`[*] Executing recipe of ${this.opList.length} operations, starting at ${startFrom}`);
for (let i = startFrom; i < this.opList.length; i++) { for (let i = startFrom; i < this.opList.length; i++) {
@@ -183,6 +230,10 @@ class Recipe {
// native types is not fully supported yet. // native types is not fully supported yet.
dish.set(err.message, "string"); dish.set(err.message, "string");
return i; return i;
} else if (err instanceof DishError ||
(err.type && err.type === "DishError")) {
dish.set(err.message, "string");
return i;
} else { } else {
const e = typeof err == "string" ? { message: err } : err; const e = typeof err == "string" ? { message: err } : err;
@@ -250,7 +301,8 @@ class Recipe {
* @returns {function} highlights[].b * @returns {function} highlights[].b
* @returns {Object[]} highlights[].args * @returns {Object[]} highlights[].args
*/ */
generateHighlightList() { async generateHighlightList() {
await this._hydrateOpList();
const highlights = []; const highlights = [];
for (let i = 0; i < this.opList.length; i++) { for (let i = 0; i < this.opList.length; i++) {

View File

@@ -5,8 +5,7 @@
*/ */
import utf8 from "utf8"; import utf8 from "utf8";
import moment from "moment-timezone"; import {fromBase64, toBase64} from "./lib/Base64";
import {fromBase64} from "./lib/Base64";
import {fromHex} from "./lib/Hex"; import {fromHex} from "./lib/Hex";
import {fromDecimal} from "./lib/Decimal"; import {fromDecimal} from "./lib/Decimal";
import {fromBinary} from "./lib/Binary"; import {fromBinary} from "./lib/Binary";
@@ -202,11 +201,20 @@ class Utils {
* Utils.parseEscapedChars("\\n"); * Utils.parseEscapedChars("\\n");
*/ */
static parseEscapedChars(str) { static parseEscapedChars(str) {
return str.replace(/(\\)?\\([bfnrtv0'"]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\})/g, function(m, a, b) { return str.replace(/(\\)?\\([bfnrtv'"]|[0-3][0-7]{2}|[0-7]{1,2}|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]{1,6}\}|\\)/g, function(m, a, b) {
if (a === "\\") return "\\"+b; if (a === "\\") return "\\"+b;
switch (b[0]) { switch (b[0]) {
case "\\":
return "\\";
case "0": case "0":
return "\0"; case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
return String.fromCharCode(parseInt(b, 8));
case "b": case "b":
return "\b"; return "\b";
case "t": case "t":
@@ -368,6 +376,61 @@ class Utils {
} }
/**
* Converts a string to an ArrayBuffer.
* Treats the string as UTF-8 if any values are over 255.
*
* @param {string} str
* @returns {ArrayBuffer}
*
* @example
* // returns [72,101,108,108,111]
* Utils.strToArrayBuffer("Hello");
*
* // returns [228,189,160,229,165,189]
* Utils.strToArrayBuffer("你好");
*/
static strToArrayBuffer(str) {
const arr = new Uint8Array(str.length);
let i = str.length, b;
while (i--) {
b = str.charCodeAt(i);
arr[i] = b;
// If any of the bytes are over 255, read as UTF-8
if (b > 255) return Utils.strToUtf8ArrayBuffer(str);
}
return arr.buffer;
}
/**
* Converts a string to a UTF-8 ArrayBuffer.
*
* @param {string} str
* @returns {ArrayBuffer}
*
* @example
* // returns [72,101,108,108,111]
* Utils.strToUtf8ArrayBuffer("Hello");
*
* // returns [228,189,160,229,165,189]
* Utils.strToUtf8ArrayBuffer("你好");
*/
static strToUtf8ArrayBuffer(str) {
const utf8Str = utf8.encode(str);
if (str.length !== utf8Str.length) {
if (ENVIRONMENT_IS_WORKER()) {
self.setOption("attemptHighlight", false);
} else if (ENVIRONMENT_IS_WEB()) {
window.app.options.attemptHighlight = false;
}
}
return Utils.strToArrayBuffer(utf8Str);
}
/** /**
* Converts a string to a byte array. * Converts a string to a byte array.
* Treats the string as UTF-8 if any values are over 255. * Treats the string as UTF-8 if any values are over 255.
@@ -460,7 +523,7 @@ class Utils {
/** /**
* Attempts to convert a byte array to a UTF-8 string. * Attempts to convert a byte array to a UTF-8 string.
* *
* @param {byteArray} byteArray * @param {byteArray|Uint8Array} byteArray
* @returns {string} * @returns {string}
* *
* @example * @example
@@ -506,6 +569,7 @@ class Utils {
static byteArrayToChars(byteArray) { static byteArrayToChars(byteArray) {
if (!byteArray) return ""; if (!byteArray) return "";
let str = ""; let str = "";
// String concatenation appears to be faster than an array join
for (let i = 0; i < byteArray.length;) { for (let i = 0; i < byteArray.length;) {
str += String.fromCharCode(byteArray[i++]); str += String.fromCharCode(byteArray[i++]);
} }
@@ -525,8 +589,8 @@ class Utils {
* Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer);
*/ */
static arrayBufferToStr(arrayBuffer, utf8=true) { static arrayBufferToStr(arrayBuffer, utf8=true) {
const byteArray = Array.prototype.slice.call(new Uint8Array(arrayBuffer)); const arr = new Uint8Array(arrayBuffer);
return utf8 ? Utils.byteArrayToUtf8(byteArray) : Utils.byteArrayToChars(byteArray); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr);
} }
@@ -781,7 +845,7 @@ class Utils {
args = m[2] args = m[2]
.replace(/"/g, '\\"') // Escape double quotes .replace(/"/g, '\\"') // Escape double quotes
.replace(/(^|,|{|:)'/g, '$1"') // Replace opening ' with " .replace(/(^|,|{|:)'/g, '$1"') // Replace opening ' with "
.replace(/([^\\]|[^\\]\\\\)'(,|:|}|$)/g, '$1"$2') // Replace closing ' with " .replace(/([^\\]|(?:\\\\)+)'(,|:|}|$)/g, '$1"$2') // Replace closing ' with "
.replace(/\\'/g, "'"); // Unescape single quotes .replace(/\\'/g, "'"); // Unescape single quotes
args = "[" + args + "]"; args = "[" + args + "]";
@@ -797,38 +861,6 @@ class Utils {
} }
/**
* Expresses a number of milliseconds in a human readable format.
*
* Range | Sample Output
* -----------------------------|-------------------------------
* 0 to 45 seconds | a few seconds ago
* 45 to 90 seconds | a minute ago
* 90 seconds to 45 minutes | 2 minutes ago ... 45 minutes ago
* 45 to 90 minutes | an hour ago
* 90 minutes to 22 hours | 2 hours ago ... 22 hours ago
* 22 to 36 hours | a day ago
* 36 hours to 25 days | 2 days ago ... 25 days ago
* 25 to 45 days | a month ago
* 45 to 345 days | 2 months ago ... 11 months ago
* 345 to 545 days (1.5 years) | a year ago
* 546 days+ | 2 years ago ... 20 years ago
*
* @param {number} ms
* @returns {string}
*
* @example
* // returns "3 minutes"
* Utils.fuzzyTime(152435);
*
* // returns "5 days"
* Utils.fuzzyTime(456851321);
*/
static fuzzyTime(ms) {
return moment.duration(ms, "milliseconds").humanize();
}
/** /**
* Formats a list of files or directories. * Formats a list of files or directories.
* *
@@ -850,12 +882,24 @@ class Utils {
return html; return html;
}; };
const formatContent = function (buff, type) {
if (type.startsWith("image")) {
let dataURI = "data:";
dataURI += type + ";";
dataURI += "base64," + toBase64(buff);
return "<img style='max-width: 100%;' src='" + dataURI + "'>";
} else {
return `<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>`;
}
};
const formatFile = async function(file, i) { const formatFile = async function(file, i) {
const buff = await Utils.readFile(file); const buff = await Utils.readFile(file);
const blob = new Blob( const blob = new Blob(
[buff], [buff],
{type: "octet/stream"} {type: file.type || "octet/stream"}
); );
const blobURL = URL.createObjectURL(blob);
const html = `<div class='card' style='white-space: normal;'> const html = `<div class='card' style='white-space: normal;'>
<div class='card-header' id='heading${i}'> <div class='card-header' id='heading${i}'>
@@ -870,16 +914,25 @@ class Utils {
<span class='float-right' style="margin-top: -3px"> <span class='float-right' style="margin-top: -3px">
${file.size.toLocaleString()} bytes ${file.size.toLocaleString()} bytes
<a title="Download ${Utils.escapeHtml(file.name)}" <a title="Download ${Utils.escapeHtml(file.name)}"
href='${URL.createObjectURL(blob)}' href="${blobURL}"
download='${Utils.escapeHtml(file.name)}'> download="${Utils.escapeHtml(file.name)}"
data-toggle="tooltip">
<i class="material-icons" style="vertical-align: bottom">save</i> <i class="material-icons" style="vertical-align: bottom">save</i>
</a> </a>
<a title="Move to input"
href="#"
blob-url="${blobURL}"
file-name="${Utils.escapeHtml(file.name)}"
class="extract-file"
data-toggle="tooltip">
<i class="material-icons" style="vertical-align: bottom">open_in_browser</i>
</a>
</span> </span>
</h6> </h6>
</div> </div>
<div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent="#files"> <div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent="#files">
<div class='card-body'> <div class='card-body'>
<pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre> ${formatContent(buff, file.type)}
</div> </div>
</div> </div>
</div>`; </div>`;
@@ -1035,9 +1088,11 @@ class Utils {
static charRep(token) { static charRep(token) {
return { return {
"Space": " ", "Space": " ",
"Percent": "%",
"Comma": ",", "Comma": ",",
"Semi-colon": ";", "Semi-colon": ";",
"Colon": ":", "Colon": ":",
"Tab": "\t",
"Line feed": "\n", "Line feed": "\n",
"CRLF": "\r\n", "CRLF": "\r\n",
"Forward slash": "/", "Forward slash": "/",
@@ -1059,6 +1114,7 @@ class Utils {
static regexRep(token) { static regexRep(token) {
return { return {
"Space": /\s+/g, "Space": /\s+/g,
"Percent": /%/g,
"Comma": /,/g, "Comma": /,/g,
"Semi-colon": /;/g, "Semi-colon": /;/g,
"Colon": /:/g, "Colon": /:/g,
@@ -1185,6 +1241,21 @@ String.prototype.count = function(chr) {
}; };
/**
* Wrapper for self.sendStatusMessage to handle different environments.
*
* @param {string} msg
*/
export function sendStatusMessage(msg) {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage(msg);
else if (ENVIRONMENT_IS_WEB())
app.alert(msg, 10000);
else if (ENVIRONMENT_IS_NODE())
log.debug(msg);
}
/* /*
* Polyfills * Polyfills
*/ */

View File

@@ -25,6 +25,8 @@
"From Base32", "From Base32",
"To Base58", "To Base58",
"From Base58", "From Base58",
"To Base62",
"From Base62",
"To Base85", "To Base85",
"From Base85", "From Base85",
"To Base", "To Base",
@@ -49,6 +51,7 @@
"Change IP format", "Change IP format",
"Encode text", "Encode text",
"Decode text", "Decode text",
"Text Encoding Brute Force",
"Swap endianness", "Swap endianness",
"To MessagePack", "To MessagePack",
"From MessagePack", "From MessagePack",
@@ -86,6 +89,8 @@
"Bifid Cipher Decode", "Bifid Cipher Decode",
"Affine Cipher Encode", "Affine Cipher Encode",
"Affine Cipher Decode", "Affine Cipher Decode",
"A1Z26 Cipher Encode",
"A1Z26 Cipher Decode",
"Atbash Cipher", "Atbash Cipher",
"Substitute", "Substitute",
"Derive PBKDF2 key", "Derive PBKDF2 key",
@@ -97,7 +102,11 @@
"JWT Decode", "JWT Decode",
"Citrix CTX1 Encode", "Citrix CTX1 Encode",
"Citrix CTX1 Decode", "Citrix CTX1 Decode",
"Pseudo-Random Number Generator" "Pseudo-Random Number Generator",
"Enigma",
"Bombe",
"Multiple Bombe",
"Typex"
] ]
}, },
{ {
@@ -150,6 +159,7 @@
"name": "Networking", "name": "Networking",
"ops": [ "ops": [
"HTTP request", "HTTP request",
"DNS over HTTPS",
"Strip HTTP headers", "Strip HTTP headers",
"Dechunk HTTP response", "Dechunk HTTP response",
"Parse User Agent", "Parse User Agent",
@@ -159,6 +169,9 @@
"Parse URI", "Parse URI",
"URL Encode", "URL Encode",
"URL Decode", "URL Decode",
"Protobuf Decode",
"VarInt Encode",
"VarInt Decode",
"Format MAC addresses", "Format MAC addresses",
"Change IP format", "Change IP format",
"Group IP addresses", "Group IP addresses",
@@ -184,6 +197,8 @@
"Remove null bytes", "Remove null bytes",
"To Upper case", "To Upper case",
"To Lower case", "To Lower case",
"To Case Insensitive Regex",
"From Case Insensitive Regex",
"Add line numbers", "Add line numbers",
"Remove line numbers", "Remove line numbers",
"To Table", "To Table",
@@ -208,6 +223,7 @@
"Convert mass", "Convert mass",
"Convert speed", "Convert speed",
"Convert data units", "Convert data units",
"Convert co-ordinate format",
"Parse UNIX file permissions", "Parse UNIX file permissions",
"Swap endianness", "Swap endianness",
"Parse colour code", "Parse colour code",
@@ -245,7 +261,8 @@
"XPath expression", "XPath expression",
"JPath expression", "JPath expression",
"CSS selector", "CSS selector",
"Extract EXIF" "Extract EXIF",
"Extract Files"
] ]
}, },
{ {
@@ -283,6 +300,10 @@
"HAS-160", "HAS-160",
"Whirlpool", "Whirlpool",
"Snefru", "Snefru",
"BLAKE2b",
"BLAKE2s",
"GOST hash",
"Streebog",
"SSDEEP", "SSDEEP",
"CTPH", "CTPH",
"Compare SSDEEP hashes", "Compare SSDEEP hashes",
@@ -297,11 +318,10 @@
"Fletcher-32 Checksum", "Fletcher-32 Checksum",
"Fletcher-64 Checksum", "Fletcher-64 Checksum",
"Adler-32 Checksum", "Adler-32 Checksum",
"CRC-8 Checksum",
"CRC-16 Checksum", "CRC-16 Checksum",
"CRC-32 Checksum", "CRC-32 Checksum",
"TCP/IP Checksum", "TCP/IP Checksum"
"To Geohash",
"From Geohash"
] ]
}, },
{ {
@@ -336,23 +356,63 @@
"From MessagePack" "From MessagePack"
] ]
}, },
{
"name": "Forensics",
"ops": [
"Detect File Type",
"Scan for Embedded Files",
"Extract Files",
"Remove EXIF",
"Extract EXIF"
]
},
{
"name": "Multimedia",
"ops": [
"Render Image",
"Play Media",
"Remove EXIF",
"Extract EXIF",
"Split Colour Channels",
"Rotate Image",
"Resize Image",
"Blur Image",
"Dither Image",
"Invert Image",
"Flip Image",
"Crop Image",
"Image Brightness / Contrast",
"Image Opacity",
"Image Filter",
"Contain Image",
"Cover Image",
"Image Hue/Saturation/Lightness",
"Sharpen Image",
"Convert Image Format",
"Add Text To Image",
"Hex Density chart",
"Scatter chart",
"Series chart",
"Heatmap chart"
]
},
{ {
"name": "Other", "name": "Other",
"ops": [ "ops": [
"Entropy", "Entropy",
"Frequency distribution", "Frequency distribution",
"Index of Coincidence",
"Chi Square", "Chi Square",
"Detect File Type",
"Scan for Embedded Files",
"Disassemble x86", "Disassemble x86",
"Pseudo-Random Number Generator", "Pseudo-Random Number Generator",
"Generate UUID", "Generate UUID",
"Generate TOTP", "Generate TOTP",
"Generate HOTP", "Generate HOTP",
"Generate QR Code",
"Parse QR Code",
"Haversine distance", "Haversine distance",
"Render Image", "HTML To Text",
"Remove EXIF", "Generate Lorem Ipsum",
"Extract EXIF",
"Numberwang", "Numberwang",
"XKCD Random Number" "XKCD Random Number"
] ]
@@ -362,6 +422,7 @@
"ops": [ "ops": [
"Magic", "Magic",
"Fork", "Fork",
"Subsection",
"Merge", "Merge",
"Register", "Register",
"Label", "Label",

View File

@@ -41,6 +41,7 @@ for (const opObj in Ops) {
inputType: op.inputType, inputType: op.inputType,
outputType: op.presentType, outputType: op.presentType,
flowControl: op.flowControl, flowControl: op.flowControl,
manualBake: op.manualBake,
args: op.args args: op.args
}; };

View File

@@ -222,7 +222,7 @@ export default ${moduleName};
console.log(`\nNext steps: console.log(`\nNext steps:
1. Add your operation to ${colors.green("src/core/config/Categories.json")} 1. Add your operation to ${colors.green("src/core/config/Categories.json")}
2. Write your operation code. 2. Write your operation code.
3. Write tests in ${colors.green("test/tests/operations/")} 3. Write tests in ${colors.green("tests/operations/tests/")}
4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")} 4. Run ${colors.cyan("npm run lint")} and ${colors.cyan("npm run test")}
5. Submit a Pull Request to get your operation added to the official CyberChef repository.`); 5. Submit a Pull Request to get your operation added to the official CyberChef repository.`);

View File

@@ -0,0 +1,26 @@
/**
* Custom error type for handling Dish type errors.
* i.e. where the Dish cannot be successfully translated between types
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
class DishError extends Error {
/**
* Standard error constructor. Adds no new behaviour.
*
* @param args - Standard error args
*/
constructor(...args) {
super(...args);
this.type = "DishError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DishError);
}
}
}
export default DishError;

0
src/core/lib/BCD.mjs Executable file → Normal file
View File

0
src/core/lib/Base58.mjs Executable file → Normal file
View File

0
src/core/lib/Base64.mjs Executable file → Normal file
View File

756
src/core/lib/Bombe.mjs Normal file
View File

@@ -0,0 +1,756 @@
/**
* Emulation of the Bombe machine.
*
* @author s2224834
* @author The National Museum of Computing - Bombe Rebuild Project
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import {Rotor, Plugboard, a2i, i2a} from "./Enigma";
/**
* Convenience/optimisation subclass of Rotor
*
* This allows creating multiple Rotors which share backing maps, to avoid repeatedly parsing the
* rotor spec strings and duplicating the maps in memory.
*/
class CopyRotor extends Rotor {
/**
* Return a copy of this Rotor.
* @returns {Object}
*/
copy() {
const clone = {
map: this.map,
revMap: this.revMap,
pos: this.pos,
step: this.step,
transform: this.transform,
revTransform: this.revTransform,
};
return clone;
}
}
/**
* Node in the menu graph
*
* A node represents a cipher/plaintext letter.
*/
class Node {
/**
* Node constructor.
* @param {number} letter - The plain/ciphertext letter this node represents (as a number).
*/
constructor(letter) {
this.letter = letter;
this.edges = new Set();
this.visited = false;
}
}
/**
* Edge in the menu graph
*
* An edge represents an Enigma machine transformation between two letters.
*/
class Edge {
/**
* Edge constructor - an Enigma machine mapping between letters
* @param {number} pos - The rotor position, relative to the beginning of the crib, at this edge
* @param {number} node1 - Letter at one end (as a number)
* @param {number} node2 - Letter at the other end
*/
constructor(pos, node1, node2) {
this.pos = pos;
this.node1 = node1;
this.node2 = node2;
node1.edges.add(this);
node2.edges.add(this);
this.visited = false;
}
/**
* Given the node at one end of this edge, return the other end.
* @param node {number} - The node we have
* @returns {number}
*/
getOther(node) {
return this.node1 === node ? this.node2 : this.node1;
}
}
/**
* As all the Bombe's rotors move in step, at any given point the vast majority of the scramblers
* in the machine share the majority of their state, which is hosted in this class.
*/
class SharedScrambler {
/**
* SharedScrambler constructor.
* @param {Object[]} rotors - List of rotors in the shared state _only_.
* @param {Object} reflector - The reflector in use.
*/
constructor(rotors, reflector) {
this.lowerCache = new Array(26);
this.higherCache = new Array(26);
for (let i=0; i<26; i++) {
this.higherCache[i] = new Array(26);
}
this.changeRotors(rotors, reflector);
}
/**
* Replace the rotors and reflector in this SharedScrambler.
* This takes care of flushing caches as well.
* @param {Object[]} rotors - List of rotors in the shared state _only_.
* @param {Object} reflector - The reflector in use.
*/
changeRotors(rotors, reflector) {
this.reflector = reflector;
this.rotors = rotors;
this.rotorsRev = [].concat(rotors).reverse();
this.cacheGen();
}
/**
* Step the rotors forward.
* @param {number} n - How many rotors to step. This includes the rotors which are not part of
* the shared state, so should be 2 or more.
*/
step(n) {
for (let i=0; i<n-1; i++) {
this.rotors[i].step();
}
this.cacheGen();
}
/**
* Optimisation: We pregenerate all routes through the machine with the top rotor removed,
* as these rarely change. This saves a lot of lookups. This function generates this route
* table.
* We also just-in-time cache the full routes through the scramblers, because after stepping
* the fast rotor some scramblers will be in states occupied by other scrambles on previous
* iterations.
*/
cacheGen() {
for (let i=0; i<26; i++) {
this.lowerCache[i] = undefined;
for (let j=0; j<26; j++) {
this.higherCache[i][j] = undefined;
}
}
for (let i=0; i<26; i++) {
if (this.lowerCache[i] !== undefined) {
continue;
}
let letter = i;
for (const rotor of this.rotors) {
letter = rotor.transform(letter);
}
letter = this.reflector.transform(letter);
for (const rotor of this.rotorsRev) {
letter = rotor.revTransform(letter);
}
// By symmetry
this.lowerCache[i] = letter;
this.lowerCache[letter] = i;
}
}
/**
* Map a letter through this (partial) scrambler.
* @param {number} i - The letter
* @returns {number}
*/
transform(i) {
return this.lowerCache[i];
}
}
/**
* Scrambler.
*
* This is effectively just an Enigma machine, but it only operates on one character at a time and
* the stepping mechanism is different.
*/
class Scrambler {
/** Scrambler constructor.
* @param {Object} base - The SharedScrambler whose state this scrambler uses
* @param {Object} rotor - The non-shared fast rotor in this scrambler
* @param {number} pos - Position offset from start of crib
* @param {number} end1 - Letter in menu this scrambler is attached to
* @param {number} end2 - Other letter in menu this scrambler is attached to
*/
constructor(base, rotor, pos, end1, end2) {
this.baseScrambler = base;
this.initialPos = pos;
this.changeRotor(rotor);
this.end1 = end1;
this.end2 = end2;
// For efficiency reasons, we pull the relevant shared cache from the baseScrambler into
// this object - this saves us a few pointer dereferences
this.cache = this.baseScrambler.higherCache[pos];
}
/**
* Replace the rotor in this scrambler.
* The position is reset automatically.
* @param {Object} rotor - New rotor
*/
changeRotor(rotor) {
this.rotor = rotor;
this.rotor.pos += this.initialPos;
}
/**
* Step the rotor forward.
*
* The base SharedScrambler needs to be instructed to step separately.
*/
step() {
// The Bombe steps the slowest rotor on an actual Enigma fastest, for reasons.
// ...but for optimisation reasons I'm going to cheat and not do that, as this vastly
// simplifies caching the state of the majority of the scramblers. The results are the
// same, just in a slightly different order.
this.rotor.step();
this.cache = this.baseScrambler.higherCache[this.rotor.pos];
}
/**
* Run a letter through the scrambler.
* @param {number} i - The letter to transform (as a number)
* @returns {number}
*/
transform(i) {
let letter = i;
const cached = this.cache[i];
if (cached !== undefined) {
return cached;
}
letter = this.rotor.transform(letter);
letter = this.baseScrambler.transform(letter);
letter = this.rotor.revTransform(letter);
this.cache[i] = letter;
this.cache[letter] = i;
return letter;
}
/**
* Given one letter in the menu this scrambler maps to, return the other.
* @param end {number} - The node we have
* @returns {number}
*/
getOtherEnd(end) {
return this.end1 === end ? this.end2 : this.end1;
}
/**
* Read the position this scrambler is set to.
* Note that because of Enigma's stepping, you need to set an actual Enigma to the previous
* position in order to get it to make a certain set of electrical connections when a button
* is pressed - this function *does* take this into account.
* However, as with the rest of the Bombe, it does not take stepping into account - the middle
* and slow rotors are treated as static.
* @return {string}
*/
getPos() {
let result = "";
// Roll back the fast rotor by one step
let pos = Utils.mod(this.rotor.pos - 1, 26);
result += i2a(pos);
for (let i=0; i<this.baseScrambler.rotors.length; i++) {
pos = this.baseScrambler.rotors[i].pos;
result += i2a(pos);
}
return result.split("").reverse().join("");
}
}
/**
* Bombe simulator class.
*/
export class BombeMachine {
/**
* Construct a Bombe.
*
* Note that there is no handling of offsets here: the crib specified must exactly match the
* ciphertext. It will check that the crib is sane (length is vaguely sensible and there's no
* matching characters between crib and ciphertext) but cannot check further - if it's wrong
* your results will be wrong!
*
* There is also no handling of rotor stepping - if the target Enigma stepped in the middle of
* your crib, you're out of luck. TODO: Allow specifying a step point - this is fairly easy to
* configure on a real Bombe, but we're not clear on whether it was ever actually done for
* real (there would almost certainly have been better ways of attacking in most situations
* than attempting to exhaust options for the stepping point, but in some circumstances, e.g.
* via Banburismus, the stepping point might have been known).
*
* @param {string[]} rotors - list of rotor spec strings (without step points!)
* @param {Object} reflector - Reflector object
* @param {string} ciphertext - The ciphertext to attack
* @param {string} crib - Known plaintext for this ciphertext
* @param {boolean} check - Whether to use the checking machine
* @param {function} update - Function to call to send status updates (optional)
*/
constructor(rotors, reflector, ciphertext, crib, check, update=undefined) {
if (ciphertext.length < crib.length) {
throw new OperationError("Crib overruns supplied ciphertext");
}
if (crib.length < 2) {
// This is the absolute bare minimum to be sane, and even then it's likely too short to
// be useful
throw new OperationError("Crib is too short");
}
if (crib.length > 25) {
// A crib longer than this will definitely cause the middle rotor to step somewhere
// A shorter crib is preferable to reduce this chance, of course
throw new OperationError("Crib is too long");
}
for (let i=0; i<crib.length; i++) {
if (ciphertext[i] === crib[i]) {
throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);
}
}
this.ciphertext = ciphertext;
this.crib = crib;
this.initRotors(rotors);
this.check = check;
this.updateFn = update;
const [mostConnected, edges] = this.makeMenu();
// This is the bundle of wires corresponding to the 26 letters within each of the 26
// possible nodes in the menu
this.wires = new Array(26*26);
// These are the pseudo-Engima devices corresponding to each edge in the menu, and the
// nodes in the menu they each connect to
this.scramblers = new Array();
for (let i=0; i<26; i++) {
this.scramblers.push(new Array());
}
this.sharedScrambler = new SharedScrambler(this.baseRotors.slice(1), reflector);
this.allScramblers = new Array();
this.indicator = undefined;
for (const edge of edges) {
const cRotor = this.baseRotors[0].copy();
const end1 = a2i(edge.node1.letter);
const end2 = a2i(edge.node2.letter);
const scrambler = new Scrambler(this.sharedScrambler, cRotor, edge.pos, end1, end2);
if (edge.pos === 0) {
this.indicator = scrambler;
}
this.scramblers[end1].push(scrambler);
this.scramblers[end2].push(scrambler);
this.allScramblers.push(scrambler);
}
// The Bombe uses a set of rotors to keep track of what settings it's testing. We cheat and
// use one of the actual scramblers if there's one in the right position, but if not we'll
// just create one.
if (this.indicator === undefined) {
this.indicator = new Scrambler(this.sharedScrambler, this.baseRotors[0].copy(), 0, undefined, undefined);
this.allScramblers.push(this.indicator);
}
this.testRegister = a2i(mostConnected.letter);
// This is an arbitrary letter other than the most connected letter
for (const edge of mostConnected.edges) {
this.testInput = [this.testRegister, a2i(edge.getOther(mostConnected).letter)];
break;
}
}
/**
* Build Rotor objects from list of rotor wiring strings.
* @param {string[]} rotors - List of rotor wiring strings
*/
initRotors(rotors) {
// This is ordered from the Enigma fast rotor to the slow, so bottom to top for the Bombe
this.baseRotors = [];
for (const rstr of rotors) {
const rotor = new CopyRotor(rstr, "", "A", "A");
this.baseRotors.push(rotor);
}
}
/**
* Replace the rotors and reflector in all components of this Bombe.
* @param {string[]} rotors - List of rotor wiring strings
* @param {Object} reflector - Reflector object
*/
changeRotors(rotors, reflector) {
// At the end of the run, the rotors are all back in the same position they started
this.initRotors(rotors);
this.sharedScrambler.changeRotors(this.baseRotors.slice(1), reflector);
for (const scrambler of this.allScramblers) {
scrambler.changeRotor(this.baseRotors[0].copy());
}
}
/**
* If we have a way of sending status messages, do so.
* @param {...*} msg - Message to send.
*/
update(...msg) {
if (this.updateFn !== undefined) {
this.updateFn(...msg);
}
}
/**
* Recursive depth-first search on the menu graph.
* This is used to a) isolate unconnected sub-graphs, and b) count the number of loops in each
* of those graphs.
* @param {Object} node - Node object to start the search from
* @returns {[number, number, Object, number, Object[]} - loop count, node count, most connected
* node, order of most connected node, list of edges in this sub-graph
*/
dfs(node) {
let loops = 0;
let nNodes = 1;
let mostConnected = node;
let nConnections = mostConnected.edges.size;
let edges = new Set();
node.visited = true;
for (const edge of node.edges) {
if (edge.visited) {
// Already been here from the other end.
continue;
}
edge.visited = true;
edges.add(edge);
const other = edge.getOther(node);
if (other.visited) {
// We have a loop, record that and continue
loops += 1;
continue;
}
// This is a newly visited node
const [oLoops, oNNodes, oMostConnected, oNConnections, oEdges] = this.dfs(other);
loops += oLoops;
nNodes += oNNodes;
edges = new Set([...edges, ...oEdges]);
if (oNConnections > nConnections) {
mostConnected = oMostConnected;
nConnections = oNConnections;
}
}
return [loops, nNodes, mostConnected, nConnections, edges];
}
/**
* Build a menu from the ciphertext and crib.
* A menu is just a graph where letters in either the ciphertext or crib (Enigma is symmetric,
* so there's no difference mathematically) are nodes and states of the Enigma machine itself
* are the edges.
* Additionally, we want a single connected graph, and of the subgraphs available, we want the
* one with the most loops (since these generate feedback cycles which efficiently close off
* disallowed states).
* Finally, we want to identify the most connected node in that graph (as it's the best choice
* of measurement point).
* @returns [Object, Object[]] - the most connected node, and the list of edges in the subgraph
*/
makeMenu() {
// First, we make a graph of all of the mappings given by the crib
// Make all nodes first
const nodes = new Map();
for (const c of this.ciphertext + this.crib) {
if (!nodes.has(c)) {
const node = new Node(c);
nodes.set(c, node);
}
}
// Then all edges
for (let i=0; i<this.crib.length; i++) {
const a = this.crib[i];
const b = this.ciphertext[i];
new Edge(i, nodes.get(a), nodes.get(b));
}
// list of [loop_count, node_count, most_connected_node, connections_on_most_connected, edges]
const graphs = [];
// Then, for each unconnected subgraph, we count the number of loops and nodes
for (const start of nodes.keys()) {
if (nodes.get(start).visited) {
continue;
}
const subgraph = this.dfs(nodes.get(start));
graphs.push(subgraph);
}
// Return the subgraph with the most loops (ties broken by node count)
graphs.sort((a, b) => {
let result = b[0] - a[0];
if (result === 0) {
result = b[1] - a[1];
}
return result;
});
this.nLoops = graphs[0][0];
return [graphs[0][2], graphs[0][4]];
}
/**
* Bombe electrical simulation. Energise a wire. For all connected wires (both via the diagonal
* board and via the scramblers), energise them too, recursively.
* @param {number} i - Bombe wire bundle
* @param {number} j - Bombe stecker hypothesis wire within bundle
*/
energise(i, j) {
const idx = 26*i + j;
if (this.wires[idx]) {
return;
}
this.wires[idx] = true;
// Welchman's diagonal board: if A steckers to B, that implies B steckers to A. Handle
// both.
const idxPair = 26*j + i;
this.wires[idxPair] = true;
if (i === this.testRegister || j === this.testRegister) {
this.energiseCount++;
if (this.energiseCount === 26) {
// no point continuing, bail out
return;
}
}
for (let k=0; k<this.scramblers[i].length; k++) {
const scrambler = this.scramblers[i][k];
const out = scrambler.transform(j);
const other = scrambler.getOtherEnd(i);
// Lift the pre-check before the call, to save some function call overhead
const otherIdx = 26*other + out;
if (!this.wires[otherIdx]) {
this.energise(other, out);
if (this.energiseCount === 26) {
return;
}
}
}
if (i === j) {
return;
}
for (let k=0; k<this.scramblers[j].length; k++) {
const scrambler = this.scramblers[j][k];
const out = scrambler.transform(i);
const other = scrambler.getOtherEnd(j);
const otherIdx = 26*other + out;
if (!this.wires[otherIdx]) {
this.energise(other, out);
if (this.energiseCount === 26) {
return;
}
}
}
}
/**
* Trial decryption at the current setting.
* Used after we get a stop.
* This applies the detected stecker pair if we have one. It does not handle the other
* steckering or stepping (which is why we limit it to 26 characters, since it's guaranteed to
* be wrong after that anyway).
* @param {string} stecker - Known stecker spec string.
* @returns {string}
*/
tryDecrypt(stecker) {
const fastRotor = this.indicator.rotor;
const initialPos = fastRotor.pos;
const res = [];
const plugboard = new Plugboard(stecker);
// The indicator scrambler starts in the right place for the beginning of the ciphertext.
for (let i=0; i<Math.min(26, this.ciphertext.length); i++) {
const t = this.indicator.transform(plugboard.transform(a2i(this.ciphertext[i])));
res.push(i2a(plugboard.transform(t)));
this.indicator.step(1);
}
fastRotor.pos = initialPos;
return res.join("");
}
/**
* Format a steckered pair, in sorted order to allow uniquing.
* @param {number} a - A letter
* @param {number} b - Its stecker pair
* @returns {string}
*/
formatPair(a, b) {
if (a < b) {
return `${i2a(a)}${i2a(b)}`;
}
return `${i2a(b)}${i2a(a)}`;
}
/**
* The checking machine was used to manually verify Bombe stops. Using a device which was
* effectively a non-stepping Enigma, the user would walk through each of the links in the
* menu at the rotor positions determined by the Bombe. By starting with the stecker pair the
* Bombe gives us, we find the stecker pair of each connected letter in the graph, and so on.
* If a contradiction is reached, the stop is invalid. If not, we have most (but not
* necessarily all) of the plugboard connections.
* You will notice that this procedure is exactly the same as what the Bombe itself does, only
* we start with an assumed good hypothesis and read out the stecker pair for every letter.
* On the real hardware that wasn't practical, but fortunately we're not the real hardware, so
* we don't need to implement the manual checking machine procedure.
* @param {number} pair - The stecker pair of the test register.
* @returns {string} - The empty string for invalid stops, or a plugboard configuration string
* containing all known pairs.
*/
checkingMachine(pair) {
if (pair !== this.testInput[1]) {
// We have a new hypothesis for this stop - apply the new one.
// De-energise the board
for (let i=0; i<this.wires.length; i++) {
this.wires[i] = false;
}
this.energiseCount = 0;
// Re-energise with the corrected hypothesis
this.energise(this.testRegister, pair);
}
const results = new Set();
results.add(this.formatPair(this.testRegister, pair));
for (let i=0; i<26; i++) {
let count = 0;
let other;
for (let j=0; j<26; j++) {
if (this.wires[i*26 + j]) {
count++;
other = j;
}
}
if (count > 1) {
// This is an invalid stop.
return "";
} else if (count === 0) {
// No information about steckering from this wire
continue;
}
results.add(this.formatPair(i, other));
}
return [...results].join(" ");
}
/**
* Check to see if the Bombe has stopped. If so, process the stop.
* @returns {(undefined|string[3])} - Undefined for no stop, or [rotor settings, plugboard settings, decryption preview]
*/
checkStop() {
// Count the energised outputs
const count = this.energiseCount;
if (count === 26) {
return undefined;
}
// If it's not all of them, we have a stop
let steckerPair;
// The Bombe tells us one stecker pair as well. The input wire and test register we
// started with are hypothesised to be a stecker pair.
if (count === 25) {
// Our steckering hypothesis is wrong. Correct value is the un-energised wire.
for (let j=0; j<26; j++) {
if (!this.wires[26*this.testRegister + j]) {
steckerPair = j;
break;
}
}
} else if (count === 1) {
// This means our hypothesis for the steckering is correct.
steckerPair = this.testInput[1];
} else {
// This was known as a "boxing stop" - we have a stop but not a single hypothesis.
// If this happens a lot it implies the menu isn't good enough.
// If we have the checking machine enabled, we're going to just check each wire in
// turn. If we get 0 or 1 hit, great.
// If we get multiple hits, or the checking machine is off, the user will just have to
// deal with it.
if (!this.check) {
// We can't draw any conclusions about the steckering (one could maybe suggest
// options in some cases, but too hard to present clearly).
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
}
let stecker = undefined;
for (let i = 0; i < 26; i++) {
const newStecker = this.checkingMachine(i);
if (newStecker !== "") {
if (stecker !== undefined) {
// Multiple hypotheses can't be ruled out.
return [this.indicator.getPos(), "??", this.tryDecrypt("")];
}
stecker = newStecker;
}
}
if (stecker === undefined) {
// Checking machine ruled all possibilities out.
return undefined;
}
// If we got here, there was just one possibility allowed by the checking machine. Success.
return [this.indicator.getPos(), stecker, this.tryDecrypt(stecker)];
}
let stecker;
if (this.check) {
stecker = this.checkingMachine(steckerPair);
if (stecker === "") {
// Invalid stop - don't count it, don't return it
return undefined;
}
} else {
stecker = `${i2a(this.testRegister)}${i2a(steckerPair)}`;
}
const testDecrypt = this.tryDecrypt(stecker);
return [this.indicator.getPos(), stecker, testDecrypt];
}
/**
* Having set up the Bombe, do the actual attack run. This tries every possible rotor setting
* and attempts to logically invalidate them. If it can't, it's added to the list of candidate
* solutions.
* @returns {string[][3]} - list of 3-tuples of candidate rotor setting, plugboard settings, and decryption preview
*/
run() {
let stops = 0;
const result = [];
// For each possible rotor setting
const nChecks = Math.pow(26, this.baseRotors.length);
for (let i=1; i<=nChecks; i++) {
// Benchmarking suggests this is faster than using .fill()
for (let i=0; i<this.wires.length; i++) {
this.wires[i] = false;
}
this.energiseCount = 0;
// Energise the test input, follow the current through each scrambler
// (and the diagonal board)
this.energise(...this.testInput);
const stop = this.checkStop();
if (stop !== undefined) {
stops++;
result.push(stop);
}
// Step all the scramblers
// This loop counts how many rotors have reached their starting position (meaning the
// next one needs to step as well)
let n = 1;
for (let j=1; j<this.baseRotors.length; j++) {
if ((i % Math.pow(26, j)) === 0) {
n++;
} else {
break;
}
}
if (n > 1) {
this.sharedScrambler.step(n);
}
for (const scrambler of this.allScramblers) {
scrambler.step();
}
// Send status messages at what seems to be a reasonably sensible frequency
// (note this won't be triggered on 3-rotor runs - they run fast enough it doesn't seem necessary)
if (n > 3) {
this.update(this.nLoops, stops, i/nChecks);
}
}
return result;
}
}

0
src/core/lib/CanvasComponents.mjs Executable file → Normal file
View File

178
src/core/lib/Charts.mjs Normal file
View File

@@ -0,0 +1,178 @@
/**
* @author tlwr [toby@toby.codes]
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
/**
* @constant
* @default
*/
export const RECORD_DELIMITER_OPTIONS = ["Line feed", "CRLF"];
/**
* @constant
* @default
*/
export const FIELD_DELIMITER_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Tab"];
/**
* Default from colour
*
* @constant
* @default
*/
export const COLOURS = {
min: "white",
max: "black"
};
/**
* Gets values from input for a plot.
*
* @param {string} input
* @param {string} recordDelimiter
* @param {string} fieldDelimiter
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
* @param {number} length
* @returns {Object[]}
*/
export function getValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded, length) {
let headings;
const values = [];
input
.split(recordDelimiter)
.forEach((row, rowIndex) => {
const split = row.split(fieldDelimiter);
if (split.length !== length) throw new OperationError(`Each row must have length ${length}.`);
if (columnHeadingsAreIncluded && rowIndex === 0) {
headings = split;
} else {
values.push(split);
}
});
return { headings, values };
}
/**
* Gets values from input for a scatter plot.
*
* @param {string} input
* @param {string} recordDelimiter
* @param {string} fieldDelimiter
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
* @returns {Object[]}
*/
export function getScatterValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
let { headings, values } = getValues(
input,
recordDelimiter,
fieldDelimiter,
columnHeadingsAreIncluded,
2
);
if (headings) {
headings = {x: headings[0], y: headings[1]};
}
values = values.map(row => {
const x = parseFloat(row[0], 10),
y = parseFloat(row[1], 10);
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
return [x, y];
});
return { headings, values };
}
/**
* Gets values from input for a scatter plot with colour from the third column.
*
* @param {string} input
* @param {string} recordDelimiter
* @param {string} fieldDelimiter
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
* @returns {Object[]}
*/
export function getScatterValuesWithColour(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
let { headings, values } = getValues(
input,
recordDelimiter, fieldDelimiter,
columnHeadingsAreIncluded,
3
);
if (headings) {
headings = {x: headings[0], y: headings[1]};
}
values = values.map(row => {
const x = parseFloat(row[0], 10),
y = parseFloat(row[1], 10),
colour = row[2];
if (Number.isNaN(x)) throw new OperationError("Values must be numbers in base 10.");
if (Number.isNaN(y)) throw new OperationError("Values must be numbers in base 10.");
return [x, y, colour];
});
return { headings, values };
}
/**
* Gets values from input for a time series plot.
*
* @param {string} input
* @param {string} recordDelimiter
* @param {string} fieldDelimiter
* @param {boolean} columnHeadingsAreIncluded - whether we should skip the first record
* @returns {Object[]}
*/
export function getSeriesValues(input, recordDelimiter, fieldDelimiter, columnHeadingsAreIncluded) {
const { values } = getValues(
input,
recordDelimiter, fieldDelimiter,
false,
3
);
let xValues = new Set();
const series = {};
values.forEach(row => {
const serie = row[0],
xVal = row[1],
val = parseFloat(row[2], 10);
if (Number.isNaN(val)) throw new OperationError("Values must be numbers in base 10.");
xValues.add(xVal);
if (typeof series[serie] === "undefined") series[serie] = {};
series[serie][xVal] = val;
});
xValues = new Array(...xValues);
const seriesList = [];
for (const seriesName in series) {
const serie = series[seriesName];
seriesList.push({name: seriesName, data: serie});
}
return { xValues, series: seriesList };
}

View File

@@ -0,0 +1,655 @@
/**
* Co-ordinate conversion resources.
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import geohash from "ngeohash";
import geodesy from "geodesy";
import OperationError from "../errors/OperationError";
/**
* Co-ordinate formats
*/
export const FORMATS = [
"Degrees Minutes Seconds",
"Degrees Decimal Minutes",
"Decimal Degrees",
"Geohash",
"Military Grid Reference System",
"Ordnance Survey National Grid",
"Universal Transverse Mercator"
];
/**
* Formats that should be passed to the conversion module as-is
*/
const NO_CHANGE = [
"Geohash",
"Military Grid Reference System",
"Ordnance Survey National Grid",
"Universal Transverse Mercator",
];
/**
* Convert a given latitude and longitude into a different format.
*
* @param {string} input - Input string to be converted
* @param {string} inFormat - Format of the input coordinates
* @param {string} inDelim - The delimiter splitting the lat/long of the input
* @param {string} outFormat - Format to convert to
* @param {string} outDelim - The delimiter to separate the output with
* @param {string} includeDir - Whether or not to include the compass direction in the output
* @param {number} precision - Precision of the result
* @returns {string} A formatted string of the converted co-ordinates
*/
export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
let isPair = false,
split,
latlon,
convLat,
convLon,
conv,
hash,
utm,
mgrs,
osng,
splitLat,
splitLong,
lat,
lon;
// Can't have a precision less than 0!
if (precision < 0) {
precision = 0;
}
if (inDelim === "Auto") {
// Try to detect a delimiter in the input.
inDelim = findDelim(input);
if (inDelim === null) {
throw new OperationError("Unable to detect the input delimiter automatically.");
}
} else if (!inDelim.includes("Direction")) {
// Convert the delimiter argument value to the actual character
inDelim = realDelim(inDelim);
}
if (inFormat === "Auto") {
// Try to detect the format of the input data
inFormat = findFormat(input, inDelim);
if (inFormat === null) {
throw new OperationError("Unable to detect the input format automatically.");
}
}
// Convert the output delimiter argument to the real character
outDelim = realDelim(outDelim);
if (!NO_CHANGE.includes(inFormat)) {
if (inDelim.includes("Direction")) {
// Split on directions
split = input.split(/[NnEeSsWw]/g);
if (split[0] === "") {
// Remove first element if direction preceding
split = split.slice(1);
}
} else {
split = input.split(inDelim);
}
// Replace any co-ordinate symbols with spaces so we can split on them later
for (let i = 0; i < split.length; i++) {
split[i] = split[i].replace(/[°˝´'"]/g, " ");
}
if (split.length > 1) {
isPair = true;
}
} else {
// Remove any delimiters from the input
input = input.replace(inDelim, "");
isPair = true;
}
// Conversions from the input format into a geodesy latlon object
switch (inFormat) {
case "Geohash":
hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
break;
case "Military Grid Reference System":
utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
latlon = utm.toLatLonE();
break;
case "Ordnance Survey National Grid":
osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
latlon = geodesy.OsGridRef.osGridToLatLon(osng);
break;
case "Universal Transverse Mercator":
// Geodesy needs a space between the first 2 digits and the next letter
if (/^[\d]{2}[A-Za-z]/.test(input)) {
input = input.slice(0, 2) + " " + input.slice(2);
}
utm = geodesy.Utm.parse(input);
latlon = utm.toLatLonE();
break;
case "Degrees Minutes Seconds":
if (isPair) {
// Split up the lat/long into degrees / minutes / seconds values
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length >= 3 && splitLong.length >= 3) {
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2], 10);
lon = convDMSToDD(splitLong[0], splitLong[1], splitLong[2], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
} else {
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
}
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(split[0]);
if (splitLat.length >= 3) {
lat = convDMSToDD(splitLat[0], splitLat[1], splitLat[2]);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
} else {
throw new OperationError("Invalid co-ordinate format for Degrees Minutes Seconds");
}
}
break;
case "Degrees Decimal Minutes":
if (isPair) {
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length !== 2 || splitLong.length !== 2) {
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
}
// Convert to decimal degrees, and then convert to a geodesy object
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
lon = convDDMToDD(splitLong[0], splitLong[1], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lon.degrees);
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(input);
if (splitLat.length !== 2) {
throw new OperationError("Invalid co-ordinate format for Degrees Decimal Minutes.");
}
lat = convDDMToDD(splitLat[0], splitLat[1], 10);
latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
}
break;
case "Decimal Degrees":
if (isPair) {
splitLat = splitInput(split[0]);
splitLong = splitInput(split[1]);
if (splitLat.length !== 1 || splitLong.length !== 1) {
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
}
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
} else {
// Not a pair, so only try to convert one set of co-ordinates
splitLat = splitInput(split[0]);
if (splitLat.length !== 1) {
throw new OperationError("Invalid co-ordinate format for Decimal Degrees.");
}
latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
}
break;
default:
throw new OperationError(`Unknown input format '${inFormat}'`);
}
// Everything is now a geodesy latlon object
// These store the latitude and longitude as decimal
if (inFormat.includes("Degrees")) {
// If the input string contains directions, we need to check if they're S or W.
// If either of the directions are, we should make the decimal value negative
const dirs = input.toUpperCase().match(/[NESW]/g);
if (dirs && dirs.length >= 1) {
// Make positive lat/lon values with S/W directions into negative values
if (dirs[0] === "S" || dirs[0] === "W" && latlon.lat > 0) {
latlon.lat = -latlon.lat;
}
if (dirs.length >= 2) {
if (dirs[1] === "S" || dirs[1] === "W" && latlon.lon > 0) {
latlon.lon = -latlon.lon;
}
}
}
}
// Try to find the compass directions of the lat and long
const [latDir, longDir] = findDirs(latlon.lat + "," + latlon.lon, ",");
// Output conversions for each output format
switch (outFormat) {
case "Decimal Degrees":
// We could use the built in latlon.toString(),
// but this makes adjusting the output harder
lat = convDDToDD(latlon.lat, precision);
lon = convDDToDD(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Degrees Decimal Minutes":
lat = convDDToDDM(latlon.lat, precision);
lon = convDDToDDM(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Degrees Minutes Seconds":
lat = convDDToDMS(latlon.lat, precision);
lon = convDDToDMS(latlon.lon, precision);
convLat = lat.string;
convLon = lon.string;
break;
case "Geohash":
convLat = geohash.encode(latlon.lat, latlon.lon, precision);
break;
case "Military Grid Reference System":
utm = latlon.toUtm();
mgrs = utm.toMgrs();
// MGRS wants a precision that's an even number between 2 and 10
if (precision % 2 !== 0) {
precision = precision + 1;
}
if (precision > 10) {
precision = 10;
}
convLat = mgrs.toString(precision);
break;
case "Ordnance Survey National Grid":
osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
if (osng.toString() === "") {
throw new OperationError("Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?");
}
// OSNG wants a precision that's an even number between 2 and 10
if (precision % 2 !== 0) {
precision = precision + 1;
}
if (precision > 10) {
precision = 10;
}
convLat = osng.toString(precision);
break;
case "Universal Transverse Mercator":
utm = latlon.toUtm();
convLat = utm.toString(precision);
break;
}
if (convLat === undefined) {
throw new OperationError("Error converting co-ordinates.");
}
if (outFormat.includes("Degrees")) {
// Format DD/DDM/DMS for output
// If we're outputting a compass direction, remove the negative sign
if (latDir === "S" && includeDir !== "None") {
convLat = convLat.replace("-", "");
}
if (longDir === "W" && includeDir !== "None") {
convLon = convLon.replace("-", "");
}
let outConv = "";
if (includeDir === "Before") {
outConv += latDir + " ";
}
outConv += convLat;
if (includeDir === "After") {
outConv += " " + latDir;
}
outConv += outDelim;
if (isPair) {
if (includeDir === "Before") {
outConv += longDir + " ";
}
outConv += convLon;
if (includeDir === "After") {
outConv += " " + longDir;
}
outConv += outDelim;
}
conv = outConv;
} else {
conv = convLat + outDelim;
}
return conv;
}
/**
* Split up the input using a space or degrees signs, and sanitise the result
*
* @param {string} input - The input data to be split
* @returns {number[]} An array of the different items in the string, stored as floats
*/
function splitInput (input){
const split = [];
input.split(/\s+/).forEach(item => {
// Remove any character that isn't a digit, decimal point or negative sign
item = item.replace(/[^0-9.-]/g, "");
if (item.length > 0){
// Turn the item into a float
split.push(parseFloat(item));
}
});
return split;
}
/**
* Convert Degrees Minutes Seconds to Decimal Degrees
*
* @param {number} degrees - The degrees of the input co-ordinates
* @param {number} minutes - The minutes of the input co-ordinates
* @param {number} seconds - The seconds of the input co-ordinates
* @param {number} precision - The precision the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDMSToDD (degrees, minutes, seconds, precision){
const absDegrees = Math.abs(degrees);
let conv = absDegrees + (minutes / 60) + (seconds / 3600);
let outString = round(conv, precision) + "°";
if (isNegativeZero(degrees) || degrees < 0) {
conv = -conv;
outString = "-" + outString;
}
return {
"degrees": conv,
"string": outString
};
}
/**
* Convert Decimal Degrees Minutes to Decimal Degrees
*
* @param {number} degrees - The input degrees to be converted
* @param {number} minutes - The input minutes to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDDMToDD (degrees, minutes, precision) {
const absDegrees = Math.abs(degrees);
let conv = absDegrees + minutes / 60;
let outString = round(conv, precision) + "°";
if (isNegativeZero(degrees) || degrees < 0) {
conv = -conv;
outString = "-" + outString;
}
return {
"degrees": conv,
"string": outString
};
}
/**
* Convert Decimal Degrees to Decimal Degrees
*
* Doesn't affect the input, just puts it into an object
* @param {number} degrees - The input degrees to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number}} An object containing the raw converted value (obj.degrees), and a formatted string version (obj.string)
*/
function convDDToDD (degrees, precision) {
return {
"degrees": degrees,
"string": round(degrees, precision) + "°"
};
}
/**
* Convert Decimal Degrees to Degrees Minutes Seconds
*
* @param {number} decDegrees - The input data to be converted
* @param {number} precision - The precision which the result should be rounded to
* @returns {{string: string, degrees: number, minutes: number, seconds: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes, .seconds), and a formatted string version (obj.string)
*/
function convDDToDMS (decDegrees, precision) {
const absDegrees = Math.abs(decDegrees);
let degrees = Math.floor(absDegrees);
const minutes = Math.floor(60 * (absDegrees - degrees)),
seconds = round(3600 * (absDegrees - degrees) - 60 * minutes, precision);
let outString = degrees + "° " + minutes + "' " + seconds + "\"";
if (isNegativeZero(decDegrees) || decDegrees < 0) {
degrees = -degrees;
outString = "-" + outString;
}
return {
"degrees": degrees,
"minutes": minutes,
"seconds": seconds,
"string": outString
};
}
/**
* Convert Decimal Degrees to Degrees Decimal Minutes
*
* @param {number} decDegrees - The input degrees to be converted
* @param {number} precision - The precision the input data should be rounded to
* @returns {{string: string, degrees: number, minutes: number}} An object containing the raw converted value as separate numbers (.degrees, .minutes), and a formatted string version (obj.string)
*/
function convDDToDDM (decDegrees, precision) {
const absDegrees = Math.abs(decDegrees);
let degrees = Math.floor(absDegrees);
const minutes = absDegrees - degrees,
decMinutes = round(minutes * 60, precision);
let outString = degrees + "° " + decMinutes + "'";
if (decDegrees < 0 || isNegativeZero(decDegrees)) {
degrees = -degrees;
outString = "-" + outString;
}
return {
"degrees": degrees,
"minutes": decMinutes,
"string": outString,
};
}
/**
* Finds and returns the compass directions in an input string
*
* @param {string} input - The input co-ordinates containing the direction
* @param {string} delim - The delimiter separating latitide and longitude
* @returns {string[]} String array containing the latitude and longitude directions
*/
export function findDirs(input, delim) {
const upperInput = input.toUpperCase();
const dirExp = new RegExp(/[NESW]/g);
const dirs = upperInput.match(dirExp);
if (dirs) {
// If there's actually compass directions
// in the input, use these to work out the direction
if (dirs.length <= 2 && dirs.length >= 1) {
return dirs.length === 2 ? [dirs[0], dirs[1]] : [dirs[0], ""];
}
}
// Nothing was returned, so guess the directions
let lat = upperInput,
long,
latDir = "",
longDir = "";
if (!delim.includes("Direction")) {
if (upperInput.includes(delim)) {
const split = upperInput.split(delim);
if (split.length >= 1) {
if (split[0] !== "") {
lat = split[0];
}
if (split.length >= 2 && split[1] !== "") {
long = split[1];
}
}
}
} else {
const split = upperInput.split(dirExp);
if (split.length > 1) {
lat = split[0] === "" ? split[1] : split[0];
if (split.length > 2 && split[2] !== "") {
long = split[2];
}
}
}
if (lat) {
lat = parseFloat(lat);
latDir = lat < 0 ? "S" : "N";
}
if (long) {
long = parseFloat(long);
longDir = long < 0 ? "W" : "E";
}
return [latDir, longDir];
}
/**
* Detects the co-ordinate format of the input data
*
* @param {string} input - The input data whose format we need to detect
* @param {string} delim - The delimiter separating the data in input
* @returns {string} The input format
*/
export function findFormat (input, delim) {
let testData;
const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
osngPattern = new RegExp(/^[A-HJ-Z]{2}\s+[0-9\s]+$/),
geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9.]+\s?[0-9.]+$/),
degPattern = new RegExp(/[°'"]/g);
input = input.trim();
if (delim !== null && delim.includes("Direction")) {
const split = input.split(/[NnEeSsWw]/);
if (split.length > 1) {
testData = split[0] === "" ? split[1] : split[0];
}
} else if (delim !== null && delim !== "") {
if (input.includes(delim)) {
const split = input.split(delim);
if (split.length > 1) {
testData = split[0] === "" ? split[1] : split[0];
}
} else {
testData = input;
}
}
// Test non-degrees formats
if (!degPattern.test(input)) {
const filteredInput = input.toUpperCase().replace(delim, "");
if (utmPattern.test(filteredInput)) {
return "Universal Transverse Mercator";
}
if (mgrsPattern.test(filteredInput)) {
return "Military Grid Reference System";
}
if (osngPattern.test(filteredInput)) {
return "Ordnance Survey National Grid";
}
if (geohashPattern.test(filteredInput)) {
return "Geohash";
}
}
// Test DMS/DDM/DD formats
if (testData !== undefined) {
const split = splitInput(testData);
switch (split.length){
case 3:
return "Degrees Minutes Seconds";
case 2:
return "Degrees Decimal Minutes";
case 1:
return "Decimal Degrees";
}
}
return null;
}
/**
* Automatically find the delimeter type from the given input
*
* @param {string} input
* @returns {string} Delimiter type
*/
export function findDelim (input) {
input = input.trim();
const delims = [",", ";", ":"];
const testDir = input.match(/[NnEeSsWw]/g);
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
// Possibly contains a direction
const splitInput = input.split(/[NnEeSsWw]/);
if (splitInput.length <= 3 && splitInput.length > 0) {
// If there's 3 splits (one should be empty), then assume we have directions
if (splitInput[0] === "") {
return "Direction Preceding";
} else if (splitInput[splitInput.length - 1] === "") {
return "Direction Following";
}
}
}
// Loop through the standard delimiters, and try to find them in the input
for (let i = 0; i < delims.length; i++) {
const delim = delims[i];
if (input.includes(delim)) {
const splitInput = input.split(delim);
if (splitInput.length <= 3 && splitInput.length > 0) {
// Don't want to try and convert more than 2 co-ordinates
return delim;
}
}
}
return null;
}
/**
* Gets the real string for a delimiter name.
*
* @param {string} delim The delimiter to be matched
* @returns {string}
*/
export function realDelim (delim) {
return {
"Auto": "Auto",
"Space": " ",
"\\n": "\n",
"Comma": ",",
"Semi-colon": ";",
"Colon": ":"
}[delim];
}
/**
* Returns true if a zero is negative
*
* @param {number} zero
* @returns {boolean}
*/
function isNegativeZero(zero) {
return zero === 0 && (1/zero < 0);
}
/**
* Rounds a number to a specified number of decimal places
*
* @param {number} input - The number to be rounded
* @param {precision} precision - The number of decimal places the number should be rounded to
* @returns {number}
*/
function round(input, precision) {
precision = Math.pow(10, precision);
return Math.round(input * precision) / precision;
}

369
src/core/lib/Enigma.mjs Normal file
View File

@@ -0,0 +1,369 @@
/**
* Emulation of the Enigma machine.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
/**
* Provided default Enigma rotor set.
* These are specified as a list of mappings from the letters A through Z in order, optionally
* followed by < and a list of letters at which the rotor steps.
*/
export const ROTORS = [
{name: "I", value: "EKMFLGDQVZNTOWYHXUSPAIBRCJ<R"},
{name: "II", value: "AJDKSIRUXBLHWTMCQGZNPYFVOE<F"},
{name: "III", value: "BDFHJLCPRTXVZNYEIWGAKMUSQO<W"},
{name: "IV", value: "ESOVPZJAYQUIRHXLNFTGKDCMWB<K"},
{name: "V", value: "VZBRGITYUPSDNHLXAWMJQOFECK<A"},
{name: "VI", value: "JPGVOUMFYQBENHZRDKASXLICTW<AN"},
{name: "VII", value: "NZJHGRCXMYSWBOUFAIVLPEKQDT<AN"},
{name: "VIII", value: "FKQHTLXOCBJSPDZRAMEWNIUYGV<AN"},
];
export const ROTORS_FOURTH = [
{name: "Beta", value: "LEYJVCNIXWPBQMDRTAKZGFUHOS"},
{name: "Gamma", value: "FSOKANUERHMBTIYCWLQPZXVGJD"},
];
/**
* Provided default Enigma reflector set.
* These are specified as 13 space-separated transposed pairs covering every letter.
*/
export const REFLECTORS = [
{name: "B", value: "AY BR CU DH EQ FS GL IP JX KN MO TZ VW"},
{name: "C", value: "AF BV CP DJ EI GO HY KR LZ MX NW TQ SU"},
{name: "B Thin", value: "AE BN CK DQ FU GY HW IJ LO MP RX SZ TV"},
{name: "C Thin", value: "AR BD CO EJ FN GT HK IV LM PW QZ SX UY"},
];
export const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
/**
* Map a letter to a number in 0..25.
*
* @param {char} c
* @param {boolean} permissive - Case insensitive; don't throw errors on other chars.
* @returns {number}
*/
export function a2i(c, permissive=false) {
const i = Utils.ord(c);
if (i >= 65 && i <= 90) {
return i - 65;
}
if (permissive) {
// Allow case insensitivity
if (i >= 97 && i <= 122) {
return i - 97;
}
return -1;
}
throw new OperationError("a2i called on non-uppercase ASCII character");
}
/**
* Map a number in 0..25 to a letter.
*
* @param {number} i
* @returns {char}
*/
export function i2a(i) {
if (i >= 0 && i < 26) {
return Utils.chr(i+65);
}
throw new OperationError("i2a called on value outside 0..25");
}
/**
* A rotor in the Enigma machine.
*/
export class Rotor {
/**
* Rotor constructor.
*
* @param {string} wiring - A 26 character string of the wiring order.
* @param {string} steps - A 0..26 character string of stepping points.
* @param {char} ringSetting - The ring setting.
* @param {char} initialPosition - The initial position of the rotor.
*/
constructor(wiring, steps, ringSetting, initialPosition) {
if (!/^[A-Z]{26}$/.test(wiring)) {
throw new OperationError("Rotor wiring must be 26 unique uppercase letters");
}
if (!/^[A-Z]{0,26}$/.test(steps)) {
throw new OperationError("Rotor steps must be 0-26 unique uppercase letters");
}
if (!/^[A-Z]$/.test(ringSetting)) {
throw new OperationError("Rotor ring setting must be exactly one uppercase letter");
}
if (!/^[A-Z]$/.test(initialPosition)) {
throw new OperationError("Rotor initial position must be exactly one uppercase letter");
}
this.map = new Array(26);
this.revMap = new Array(26);
const uniq = {};
for (let i=0; i<LETTERS.length; i++) {
const a = a2i(LETTERS[i]);
const b = a2i(wiring[i]);
this.map[a] = b;
this.revMap[b] = a;
uniq[b] = true;
}
if (Object.keys(uniq).length !== LETTERS.length) {
throw new OperationError("Rotor wiring must have each letter exactly once");
}
const rs = a2i(ringSetting);
this.steps = new Set();
for (const x of steps) {
this.steps.add(Utils.mod(a2i(x) - rs, 26));
}
if (this.steps.size !== steps.length) {
// This isn't strictly fatal, but it's probably a mistake
throw new OperationError("Rotor steps must be unique");
}
this.pos = Utils.mod(a2i(initialPosition) - rs, 26);
}
/**
* Step the rotor forward by one.
*/
step() {
this.pos = Utils.mod(this.pos + 1, 26);
return this.pos;
}
/**
* Transform a character through this rotor forwards.
*
* @param {number} c - The character.
* @returns {number}
*/
transform(c) {
return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);
}
/**
* Transform a character through this rotor backwards.
*
* @param {number} c - The character.
* @returns {number}
*/
revTransform(c) {
return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);
}
}
/**
* Base class for plugboard and reflector (since these do effectively the same
* thing).
*/
class PairMapBase {
/**
* PairMapBase constructor.
*
* @param {string} pairs - A whitespace separated string of letter pairs to swap.
* @param {string} [name='PairMapBase'] - For errors, the name of this object.
*/
constructor(pairs, name="PairMapBase") {
// I've chosen to make whitespace significant here to make a) code and
// b) inputs easier to read
this.pairs = pairs;
this.map = {};
if (pairs === "") {
return;
}
pairs.split(/\s+/).forEach(pair => {
if (!/^[A-Z]{2}$/.test(pair)) {
throw new OperationError(name + " must be a whitespace-separated list of uppercase letter pairs");
}
const a = a2i(pair[0]), b = a2i(pair[1]);
if (a === b) {
// self-stecker
return;
}
if (this.map.hasOwnProperty(a)) {
throw new OperationError(`${name} connects ${pair[0]} more than once`);
}
if (this.map.hasOwnProperty(b)) {
throw new OperationError(`${name} connects ${pair[1]} more than once`);
}
this.map[a] = b;
this.map[b] = a;
});
}
/**
* Transform a character through this object.
* Returns other characters unchanged.
*
* @param {number} c - The character.
* @returns {number}
*/
transform(c) {
if (!this.map.hasOwnProperty(c)) {
return c;
}
return this.map[c];
}
/**
* Alias for transform, to allow interchangeable use with rotors.
*
* @param {number} c - The character.
* @returns {number}
*/
revTransform(c) {
return this.transform(c);
}
}
/**
* Reflector. PairMapBase but requires that all characters are accounted for.
*
* Includes a couple of optimisations on that basis.
*/
export class Reflector extends PairMapBase {
/**
* Reflector constructor. See PairMapBase.
* Additional restriction: every character must be accounted for.
*/
constructor(pairs) {
super(pairs, "Reflector");
const s = Object.keys(this.map).length;
if (s !== 26) {
throw new OperationError("Reflector must have exactly 13 pairs covering every letter");
}
const optMap = new Array(26);
for (const x of Object.keys(this.map)) {
optMap[x] = this.map[x];
}
this.map = optMap;
}
/**
* Transform a character through this object.
*
* @param {number} c - The character.
* @returns {number}
*/
transform(c) {
return this.map[c];
}
}
/**
* Plugboard. Unmodified PairMapBase.
*/
export class Plugboard extends PairMapBase {
/**
* Plugboard constructor. See PairMapbase.
*/
constructor(pairs) {
super(pairs, "Plugboard");
}
}
/**
* Base class for the Enigma machine itself. Holds rotors, a reflector, and a plugboard.
*/
export class EnigmaBase {
/**
* EnigmaBase constructor.
*
* @param {Object[]} rotors - List of Rotors.
* @param {Object} reflector - A Reflector.
* @param {Plugboard} plugboard - A Plugboard.
*/
constructor(rotors, reflector, plugboard) {
this.rotors = rotors;
this.rotorsRev = [].concat(rotors).reverse();
this.reflector = reflector;
this.plugboard = plugboard;
}
/**
* Step the rotors forward by one.
*
* This happens before the output character is generated.
*
* Note that rotor 4, if it's there, never steps.
*
* Why is all the logic in EnigmaBase and not a nice neat method on
* Rotor that knows when it should advance the next item?
* Because the double stepping anomaly is a thing. tl;dr if the left rotor
* should step the next time the middle rotor steps, the middle rotor will
* immediately step.
*/
step() {
const r0 = this.rotors[0];
const r1 = this.rotors[1];
r0.step();
// The second test here is the double-stepping anomaly
if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {
r1.step();
if (r1.steps.has(r1.pos)) {
const r2 = this.rotors[2];
r2.step();
}
}
}
/**
* Encrypt (or decrypt) some data.
* Takes an arbitrary string and runs the Engima machine on that data from
* *its current state*, and outputs the result. Non-alphabetic characters
* are returned unchanged.
*
* @param {string} input - Data to encrypt.
* @returns {string}
*/
crypt(input) {
let result = "";
for (const c of input) {
let letter = a2i(c, true);
if (letter === -1) {
result += c;
continue;
}
// First, step the rotors forward.
this.step();
// Now, run through the plugboard.
letter = this.plugboard.transform(letter);
// Then through each wheel in sequence, through the reflector, and
// backwards through the wheels again.
for (const rotor of this.rotors) {
letter = rotor.transform(letter);
}
letter = this.reflector.transform(letter);
for (const rotor of this.rotorsRev) {
letter = rotor.revTransform(letter);
}
// Finally, back through the plugboard.
letter = this.plugboard.revTransform(letter);
result += i2a(letter);
}
return result;
}
}
/**
* The Enigma machine itself. Holds 3-4 rotors, a reflector, and a plugboard.
*/
export class EnigmaMachine extends EnigmaBase {
/**
* EnigmaMachine constructor.
*
* @param {Object[]} rotors - List of Rotors.
* @param {Object} reflector - A Reflector.
* @param {Plugboard} plugboard - A Plugboard.
*/
constructor(rotors, reflector, plugboard) {
super(rotors, reflector, plugboard);
if (rotors.length !== 3 && rotors.length !== 4) {
throw new OperationError("Enigma must have 3 or 4 rotors");
}
}
}

File diff suppressed because it is too large Load Diff

263
src/core/lib/FileType.mjs Normal file
View File

@@ -0,0 +1,263 @@
/**
* File type functions
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
*/
import {FILE_SIGNATURES} from "./FileSignatures";
import {sendStatusMessage} from "../Utils";
/**
* Checks whether a signature matches a buffer.
*
* @param {Object|Object[]} sig - A dictionary of offsets with values assigned to them.
* These values can be numbers for static checks, arrays of potential valid matches,
* or bespoke functions to check the validity of the buffer value at that offset.
* @param {Uint8Array} buf
* @param {number} [offset=0] Where in the buffer to start searching from
* @returns {boolean}
*/
function signatureMatches(sig, buf, offset=0) {
// Using a length check seems to be more performant than `sig instanceof Array`
if (sig.length) {
// sig is an Array - return true if any of them match
// The following `reduce` method is nice, but performance matters here, so we
// opt for a faster, if less elegant, for loop.
// return sig.reduce((acc, s) => acc || bytesMatch(s, buf, offset), false);
for (let i = 0; i < sig.length; i++) {
if (bytesMatch(sig[i], buf, offset)) return true;
}
return false;
} else {
return bytesMatch(sig, buf, offset);
}
}
/**
* Checks whether a set of bytes match the given buffer.
*
* @param {Object} sig - A dictionary of offsets with values assigned to them.
* These values can be numbers for static checks, arrays of potential valid matches,
* or bespoke functions to check the validity of the buffer value at that offset.
* @param {Uint8Array} buf
* @param {number} [offset=0] Where in the buffer to start searching from
* @returns {boolean}
*/
function bytesMatch(sig, buf, offset=0) {
for (const sigoffset in sig) {
const pos = parseInt(sigoffset, 10) + offset;
switch (typeof sig[sigoffset]) {
case "number": // Static check
if (buf[pos] !== sig[sigoffset])
return false;
break;
case "object": // Array of options
if (sig[sigoffset].indexOf(buf[pos]) < 0)
return false;
break;
case "function": // More complex calculation
if (!sig[sigoffset](buf[pos]))
return false;
break;
default:
throw new Error(`Unrecognised signature type at offset ${sigoffset}`);
}
}
return true;
}
/**
* Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type.
*
* @param {Uint8Array} buf
* @param {string[]} [categories=All] - Which categories of file to look for
* @returns {Object[]} types
* @returns {string} type.name - Name of file type
* @returns {string} type.ext - File extension
* @returns {string} type.mime - Mime type
* @returns {string} [type.desc] - Description
*/
export function detectFileType(buf, categories=Object.keys(FILE_SIGNATURES)) {
if (!(buf && buf.length > 1)) {
return [];
}
const matchingFiles = [];
const signatures = {};
for (const cat in FILE_SIGNATURES) {
if (categories.includes(cat)) {
signatures[cat] = FILE_SIGNATURES[cat];
}
}
for (const cat in signatures) {
const category = signatures[cat];
category.forEach(filetype => {
if (signatureMatches(filetype.signature, buf)) {
matchingFiles.push(filetype);
}
});
}
return matchingFiles;
}
/**
* Given a buffer, searches for magic byte sequences at all possible positions and returns
* the extensions and mime types.
*
* @param {Uint8Array} buf
* @param {string[]} [categories=All] - Which categories of file to look for
* @returns {Object[]} foundFiles
* @returns {number} foundFiles.offset - The position in the buffer at which this file was found
* @returns {Object} foundFiles.fileDetails
* @returns {string} foundFiles.fileDetails.name - Name of file type
* @returns {string} foundFiles.fileDetails.ext - File extension
* @returns {string} foundFiles.fileDetails.mime - Mime type
* @returns {string} [foundFiles.fileDetails.desc] - Description
*/
export function scanForFileTypes(buf, categories=Object.keys(FILE_SIGNATURES)) {
if (!(buf && buf.length > 1)) {
return [];
}
const foundFiles = [];
const signatures = {};
for (const cat in FILE_SIGNATURES) {
if (categories.includes(cat)) {
signatures[cat] = FILE_SIGNATURES[cat];
}
}
for (const cat in signatures) {
const category = signatures[cat];
for (let i = 0; i < category.length; i++) {
const filetype = category[i];
const sigs = filetype.signature.length ? filetype.signature : [filetype.signature];
sigs.forEach(sig => {
let pos = 0;
while ((pos = locatePotentialSig(buf, sig, pos)) >= 0) {
if (bytesMatch(sig, buf, pos)) {
sendStatusMessage(`Found potential signature for ${filetype.name} at pos ${pos}`);
foundFiles.push({
offset: pos,
fileDetails: filetype
});
}
pos++;
}
});
}
}
// Return found files in order of increasing offset
return foundFiles.sort((a, b) => {
return a.offset - b.offset;
});
}
/**
* Fastcheck function to quickly scan the buffer for the first byte in a signature.
*
* @param {Uint8Array} buf - The buffer to search
* @param {Object} sig - A single signature object (Not an array of signatures)
* @param {number} offset - Where to start search from
* @returs {number} The position of the match or -1 if one cannot be found.
*/
function locatePotentialSig(buf, sig, offset) {
// Find values for first key and value in sig
const k = parseInt(Object.keys(sig)[0], 10);
const v = Object.values(sig)[0];
switch (typeof v) {
case "number":
return buf.indexOf(v, offset + k) - k;
case "object":
for (let i = offset + k; i < buf.length; i++) {
if (v.indexOf(buf[i]) >= 0) return i - k;
}
return -1;
case "function":
for (let i = offset + k; i < buf.length; i++) {
if (v(buf[i])) return i - k;
}
return -1;
default:
throw new Error("Unrecognised signature type");
}
}
/**
* Detects whether the given buffer is a file of the type specified.
*
* @param {string|RegExp} type
* @param {Uint8Array} buf
* @returns {string|false} The mime type or false if the type does not match
*/
export function isType(type, buf) {
const types = detectFileType(buf);
if (!(types && types.length)) return false;
if (typeof type === "string") {
return types.reduce((acc, t) => {
const mime = t.mime.startsWith(type) ? t.mime : false;
return acc || mime;
}, false);
} else if (type instanceof RegExp) {
return types.reduce((acc, t) => {
const mime = type.test(t.mime) ? t.mime : false;
return acc || mime;
}, false);
} else {
throw new Error("Invalid type input.");
}
}
/**
* Detects whether the given buffer contains an image file.
*
* @param {Uint8Array} buf
* @returns {string|false} The mime type or false if the type does not match
*/
export function isImage(buf) {
return isType("image", buf);
}
/**
* Attempts to extract a file from a data stream given its offset and extractor function.
*
* @param {Uint8Array} bytes
* @param {Object} fileDetail
* @param {string} fileDetail.mime
* @param {string} fileDetail.extension
* @param {Function} fileDetail.extractor
* @param {number} offset
* @returns {File}
*/
export function extractFile(bytes, fileDetail, offset) {
if (fileDetail.extractor) {
sendStatusMessage(`Attempting to extract ${fileDetail.name} at pos ${offset}...`);
const fileData = fileDetail.extractor(bytes, offset);
const ext = fileDetail.extension.split(",")[0];
return new File([fileData], `extracted_at_0x${offset.toString(16)}.${ext}`, {
type: fileDetail.mime
});
}
throw new Error(`No extraction algorithm available for "${fileDetail.mime}" files`);
}

View File

@@ -12,7 +12,7 @@ import Utils from "../Utils";
/** /**
* Convert a byte array into a hex string. * Convert a byte array into a hex string.
* *
* @param {Uint8Array|byteArray} data * @param {byteArray|Uint8Array|ArrayBuffer} data
* @param {string} [delim=" "] * @param {string} [delim=" "]
* @param {number} [padding=2] * @param {number} [padding=2]
* @returns {string} * @returns {string}
@@ -26,6 +26,7 @@ import Utils from "../Utils";
*/ */
export function toHex(data, delim=" ", padding=2) { export function toHex(data, delim=" ", padding=2) {
if (!data) return ""; if (!data) return "";
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
let output = ""; let output = "";
@@ -47,7 +48,7 @@ export function toHex(data, delim=" ", padding=2) {
/** /**
* Convert a byte array into a hex string as efficiently as possible with no options. * Convert a byte array into a hex string as efficiently as possible with no options.
* *
* @param {byteArray} data * @param {byteArray|Uint8Array|ArrayBuffer} data
* @returns {string} * @returns {string}
* *
* @example * @example
@@ -56,6 +57,7 @@ export function toHex(data, delim=" ", padding=2) {
*/ */
export function toHexFast(data) { export function toHexFast(data) {
if (!data) return ""; if (!data) return "";
if (data instanceof ArrayBuffer) data = new Uint8Array(data);
const output = []; const output = [];
@@ -100,7 +102,7 @@ export function fromHex(data, delim="Auto", byteLen=2) {
/** /**
* To Hexadecimal delimiters. * To Hexadecimal delimiters.
*/ */
export const TO_HEX_DELIM_OPTIONS = ["Space", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"]; export const TO_HEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "0x", "\\x", "None"];
/** /**

View File

@@ -0,0 +1,251 @@
/**
* Image manipulation resources
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
/**
* Gaussian blurs an image.
*
* @param {jimp} input
* @param {number} radius
* @param {boolean} fast
* @returns {jimp}
*/
export function gaussianBlur (input, radius) {
try {
// From http://blog.ivank.net/fastest-gaussian-blur.html
const boxes = boxesForGauss(radius, 3);
for (let i = 0; i < 3; i++) {
input = boxBlur(input, (boxes[i] - 1) / 2);
}
} catch (err) {
throw new OperationError(`Error blurring image. (${err})`);
}
return input;
}
/**
*
* @param {number} radius
* @param {number} numBoxes
* @returns {Array}
*/
function boxesForGauss(radius, numBoxes) {
const idealWidth = Math.sqrt((12 * radius * radius / numBoxes) + 1);
let wl = Math.floor(idealWidth);
if (wl % 2 === 0) {
wl--;
}
const wu = wl + 2;
const mIdeal = (12 * radius * radius - numBoxes * wl * wl - 4 * numBoxes * wl - 3 * numBoxes) / (-4 * wl - 4);
const m = Math.round(mIdeal);
const sizes = [];
for (let i = 0; i < numBoxes; i++) {
sizes.push(i < m ? wl : wu);
}
return sizes;
}
/**
* Applies a box blur effect to the image
*
* @param {jimp} source
* @param {number} radius
* @returns {jimp}
*/
function boxBlur (source, radius) {
const width = source.bitmap.width;
const height = source.bitmap.height;
let output = source.clone();
output = boxBlurH(source, output, width, height, radius);
source = boxBlurV(output, source, width, height, radius);
return source;
}
/**
* Applies the horizontal blur
*
* @param {jimp} source
* @param {jimp} output
* @param {number} width
* @param {number} height
* @param {number} radius
* @returns {jimp}
*/
function boxBlurH (source, output, width, height, radius) {
const iarr = 1 / (radius + radius + 1);
for (let i = 0; i < height; i++) {
let ti = 0,
li = ti,
ri = ti + radius;
const idx = source.getPixelIndex(ti, i);
const firstValRed = source.bitmap.data[idx],
firstValGreen = source.bitmap.data[idx + 1],
firstValBlue = source.bitmap.data[idx + 2],
firstValAlpha = source.bitmap.data[idx + 3];
const lastIdx = source.getPixelIndex(width - 1, i),
lastValRed = source.bitmap.data[lastIdx],
lastValGreen = source.bitmap.data[lastIdx + 1],
lastValBlue = source.bitmap.data[lastIdx + 2],
lastValAlpha = source.bitmap.data[lastIdx + 3];
let red = (radius + 1) * firstValRed;
let green = (radius + 1) * firstValGreen;
let blue = (radius + 1) * firstValBlue;
let alpha = (radius + 1) * firstValAlpha;
for (let j = 0; j < radius; j++) {
const jIdx = source.getPixelIndex(ti + j, i);
red += source.bitmap.data[jIdx];
green += source.bitmap.data[jIdx + 1];
blue += source.bitmap.data[jIdx + 2];
alpha += source.bitmap.data[jIdx + 3];
}
for (let j = 0; j <= radius; j++) {
const jIdx = source.getPixelIndex(ri++, i);
red += source.bitmap.data[jIdx] - firstValRed;
green += source.bitmap.data[jIdx + 1] - firstValGreen;
blue += source.bitmap.data[jIdx + 2] - firstValBlue;
alpha += source.bitmap.data[jIdx + 3] - firstValAlpha;
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = radius + 1; j < width - radius; j++) {
const riIdx = source.getPixelIndex(ri++, i);
const liIdx = source.getPixelIndex(li++, i);
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = width - radius; j < width; j++) {
const liIdx = source.getPixelIndex(li++, i);
red += lastValRed - source.bitmap.data[liIdx];
green += lastValGreen - source.bitmap.data[liIdx + 1];
blue += lastValBlue - source.bitmap.data[liIdx + 2];
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(ti++, i);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
}
return output;
}
/**
* Applies the vertical blur
*
* @param {jimp} source
* @param {jimp} output
* @param {number} width
* @param {number} height
* @param {number} radius
* @returns {jimp}
*/
function boxBlurV (source, output, width, height, radius) {
const iarr = 1 / (radius + radius + 1);
for (let i = 0; i < width; i++) {
let ti = 0,
li = ti,
ri = ti + radius;
const idx = source.getPixelIndex(i, ti);
const firstValRed = source.bitmap.data[idx],
firstValGreen = source.bitmap.data[idx + 1],
firstValBlue = source.bitmap.data[idx + 2],
firstValAlpha = source.bitmap.data[idx + 3];
const lastIdx = source.getPixelIndex(i, height - 1),
lastValRed = source.bitmap.data[lastIdx],
lastValGreen = source.bitmap.data[lastIdx + 1],
lastValBlue = source.bitmap.data[lastIdx + 2],
lastValAlpha = source.bitmap.data[lastIdx + 3];
let red = (radius + 1) * firstValRed;
let green = (radius + 1) * firstValGreen;
let blue = (radius + 1) * firstValBlue;
let alpha = (radius + 1) * firstValAlpha;
for (let j = 0; j < radius; j++) {
const jIdx = source.getPixelIndex(i, ti + j);
red += source.bitmap.data[jIdx];
green += source.bitmap.data[jIdx + 1];
blue += source.bitmap.data[jIdx + 2];
alpha += source.bitmap.data[jIdx + 3];
}
for (let j = 0; j <= radius; j++) {
const riIdx = source.getPixelIndex(i, ri++);
red += source.bitmap.data[riIdx] - firstValRed;
green += source.bitmap.data[riIdx + 1] - firstValGreen;
blue += source.bitmap.data[riIdx + 2] - firstValBlue;
alpha += source.bitmap.data[riIdx + 3] - firstValAlpha;
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = radius + 1; j < height - radius; j++) {
const riIdx = source.getPixelIndex(i, ri++);
const liIdx = source.getPixelIndex(i, li++);
red += source.bitmap.data[riIdx] - source.bitmap.data[liIdx];
green += source.bitmap.data[riIdx + 1] - source.bitmap.data[liIdx + 1];
blue += source.bitmap.data[riIdx + 2] - source.bitmap.data[liIdx + 2];
alpha += source.bitmap.data[riIdx + 3] - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
for (let j = height - radius; j < height; j++) {
const liIdx = source.getPixelIndex(i, li++);
red += lastValRed - source.bitmap.data[liIdx];
green += lastValGreen - source.bitmap.data[liIdx + 1];
blue += lastValBlue - source.bitmap.data[liIdx + 2];
alpha += lastValAlpha - source.bitmap.data[liIdx + 3];
const tiIdx = source.getPixelIndex(i, ti++);
output.bitmap.data[tiIdx] = Math.round(red * iarr);
output.bitmap.data[tiIdx + 1] = Math.round(green * iarr);
output.bitmap.data[tiIdx + 2] = Math.round(blue * iarr);
output.bitmap.data[tiIdx + 3] = Math.round(alpha * iarr);
}
}
return output;
}

230
src/core/lib/LoremIpsum.mjs Normal file
View File

@@ -0,0 +1,230 @@
/**
* Lorem Ipsum generator.
*
* @author Klaxon [klaxon@veyr.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
/**
* Generate lorem ipsum paragraphs.
*
* @param {number} length
* @returns {string}
*/
export function GenerateParagraphs(length=3) {
const paragraphs = [];
while (paragraphs.length < length) {
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
const sentences = [];
while (sentences.length < paragraphLength) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
const sentence = getWords(sentenceLength);
sentences.push(formatSentence(sentence));
}
paragraphs.push(formatParagraph(sentences));
}
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -2);
paragraphs[0] = replaceStart(paragraphs[0]);
return paragraphs.join("");
}
/**
* Generate lorem ipsum sentences.
*
* @param {number} length
* @returns {string}
*/
export function GenerateSentences(length=3) {
const sentences = [];
while (sentences.length < length) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
const sentence = getWords(sentenceLength);
sentences.push(formatSentence(sentence));
}
const paragraphs = sentencesToParagraphs(sentences);
return paragraphs.join("");
}
/**
* Generate lorem ipsum words.
*
* @param {number} length
* @returns {string}
*/
export function GenerateWords(length=3) {
const words = getWords(length);
const sentences = wordsToSentences(words);
const paragraphs = sentencesToParagraphs(sentences);
return paragraphs.join("");
}
/**
* Generate lorem ipsum bytes.
*
* @param {number} length
* @returns {string}
*/
export function GenerateBytes(length=3) {
const str = GenerateWords(length/3);
return str.slice(0, length);
}
/**
* Get array of randomly selected words from the lorem ipsum wordList.
*
* @param {number} length
* @returns {string[]}
* @private
*/
function getWords(length=3) {
const words = [];
let word;
let previousWord;
while (words.length < length){
do {
word = wordList[Math.floor(Math.random() * wordList.length)];
} while (previousWord === word);
words.push(word);
previousWord = word;
}
return words;
}
/**
* Convert an array of words into an array of sentences
*
* @param {string[]} words
* @returns {string[]}
* @private
*/
function wordsToSentences(words) {
const sentences = [];
while (words.length > 0) {
const sentenceLength = getRandomLength(SENTENCE_LENGTH_MEAN, SENTENCE_LENGTH_STD_DEV);
if (sentenceLength <= words.length) {
sentences.push(formatSentence(words.splice(0, sentenceLength)));
} else {
sentences.push(formatSentence(words.splice(0, words.length)));
}
}
return sentences;
}
/**
* Convert an array of sentences into an array of paragraphs
*
* @param {string[]} sentences
* @returns {string[]}
* @private
*/
function sentencesToParagraphs(sentences) {
const paragraphs = [];
while (sentences.length > 0) {
const paragraphLength = getRandomLength(PARAGRAPH_LENGTH_MEAN, PARAGRAPH_LENGTH_STD_DEV);
paragraphs.push(formatParagraph(sentences.splice(0, paragraphLength)));
}
paragraphs[paragraphs.length-1] = paragraphs[paragraphs.length-1].slice(0, -1);
paragraphs[0] = replaceStart(paragraphs[0]);
return paragraphs;
}
/**
* Format an array of words into a sentence.
*
* @param {string[]} words
* @returns {string}
* @private
*/
function formatSentence(words) {
// 0.35 chance of a comma being added randomly to the sentence.
if (Math.random() < PROBABILITY_OF_A_COMMA) {
const pos = Math.round(Math.random()*(words.length-1));
words[pos] +=",";
}
let sentence = words.join(" ");
sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1);
sentence += ".";
return sentence;
}
/**
* Format an array of sentences into a paragraph.
*
* @param {string[]} sentences
* @returns {string}
* @private
*/
function formatParagraph(sentences) {
let paragraph = sentences.join(" ");
paragraph += "\n\n";
return paragraph;
}
/**
* Get a random number based on a mean and standard deviation.
*
* @param {number} mean
* @param {number} stdDev
* @returns {number}
* @private
*/
function getRandomLength(mean, stdDev) {
let length;
do {
length = Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1)*stdDev+mean);
} while (length <= 0);
return length;
}
/**
* Replace first 5 words with "Lorem ipsum dolor sit amet"
*
* @param {string[]} str
* @returns {string[]}
* @private
*/
function replaceStart(str) {
let words = str.split(" ");
if (words.length > 5) {
words.splice(0, 5, "Lorem", "ipsum", "dolor", "sit", "amet");
return words.join(" ");
} else {
const lorem = ["Lorem", "ipsum", "dolor", "sit", "amet"];
words = lorem.slice(0, words.length);
str = words.join(" ");
str += ".";
return str;
}
}
const SENTENCE_LENGTH_MEAN = 15;
const SENTENCE_LENGTH_STD_DEV = 9;
const PARAGRAPH_LENGTH_MEAN = 5;
const PARAGRAPH_LENGTH_STD_DEV = 2;
const PROBABILITY_OF_A_COMMA = 0.35;
const wordList = [
"ad", "adipisicing", "aliqua", "aliquip", "amet", "anim",
"aute", "cillum", "commodo", "consectetur", "consequat", "culpa",
"cupidatat", "deserunt", "do", "dolor", "dolore", "duis",
"ea", "eiusmod", "elit", "enim", "esse", "est",
"et", "eu", "ex", "excepteur", "exercitation", "fugiat",
"id", "in", "incididunt", "ipsum", "irure", "labore",
"laboris", "laborum", "Lorem", "magna", "minim", "mollit",
"nisi", "non", "nostrud", "nulla", "occaecat", "officia",
"pariatur", "proident", "qui", "quis", "reprehenderit", "sint",
"sit", "sunt", "tempor", "ullamco", "ut", "velit",
"veniam", "voluptate",
];

View File

@@ -2,6 +2,7 @@ import OperationConfig from "../config/OperationConfig.json";
import Utils from "../Utils"; import Utils from "../Utils";
import Recipe from "../Recipe"; import Recipe from "../Recipe";
import Dish from "../Dish"; import Dish from "../Dish";
import {detectFileType} from "./FileType";
import chiSquared from "chi-squared"; import chiSquared from "chi-squared";
/** /**
@@ -92,7 +93,14 @@ class Magic {
* @returns {string} [type.desc] - Description * @returns {string} [type.desc] - Description
*/ */
detectFileType() { detectFileType() {
return Magic.magicFileType(this.inputBuffer); const fileType = detectFileType(this.inputBuffer);
if (!fileType.length) return null;
return {
ext: fileType[0].extension,
mime: fileType[0].mime,
desc: fileType[0].description
};
} }
/** /**
@@ -265,9 +273,10 @@ class Magic {
* performance) * performance)
* @param {Object[]} [recipeConfig=[]] - The recipe configuration up to this point * @param {Object[]} [recipeConfig=[]] - The recipe configuration up to this point
* @param {boolean} [useful=false] - Whether the current recipe should be scored highly * @param {boolean} [useful=false] - Whether the current recipe should be scored highly
* @param {string} [crib=null] - The regex crib provided by the user, for filtering the operation output
* @returns {Object[]} - A sorted list of the recipes most likely to result in correct decoding * @returns {Object[]} - A sorted list of the recipes most likely to result in correct decoding
*/ */
async speculativeExecution(depth=0, extLang=false, intensive=false, recipeConfig=[], useful=false) { async speculativeExecution(depth=0, extLang=false, intensive=false, recipeConfig=[], useful=false, crib=null) {
if (depth < 0) return []; if (depth < 0) return [];
// Find any operations that can be run on this data // Find any operations that can be run on this data
@@ -284,9 +293,9 @@ class Magic {
isUTF8: this.isUTF8(), isUTF8: this.isUTF8(),
entropy: this.calcEntropy(), entropy: this.calcEntropy(),
matchingOps: matchingOps, matchingOps: matchingOps,
useful: useful useful: useful,
matchesCrib: crib && crib.test(this.inputStr)
}); });
const prevOp = recipeConfig[recipeConfig.length - 1]; const prevOp = recipeConfig[recipeConfig.length - 1];
// Execute each of the matching operations, then recursively call the speculativeExecution() // Execute each of the matching operations, then recursively call the speculativeExecution()
@@ -303,9 +312,14 @@ class Magic {
return; return;
} }
// If the recipe returned an empty buffer, do not continue
if (_buffersEqual(output, new ArrayBuffer())) {
return;
}
const magic = new Magic(output, this.opPatterns), const magic = new Magic(output, this.opPatterns),
speculativeResults = await magic.speculativeExecution( speculativeResults = await magic.speculativeExecution(
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful); depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
results = results.concat(speculativeResults); results = results.concat(speculativeResults);
})); }));
@@ -317,7 +331,7 @@ class Magic {
await Promise.all(bfEncodings.map(async enc => { await Promise.all(bfEncodings.map(async enc => {
const magic = new Magic(enc.data, this.opPatterns), const magic = new Magic(enc.data, this.opPatterns),
bfResults = await magic.speculativeExecution( bfResults = await magic.speculativeExecution(
depth-1, extLang, false, [...recipeConfig, enc.conf]); depth-1, extLang, false, [...recipeConfig, enc.conf], false, crib);
results = results.concat(bfResults); results = results.concat(bfResults);
})); }));
@@ -386,7 +400,12 @@ class Magic {
const recipe = new Recipe(recipeConfig); const recipe = new Recipe(recipeConfig);
try { try {
await recipe.execute(dish); await recipe.execute(dish);
return dish.get(Dish.ARRAY_BUFFER); // Return an empty buffer if the recipe did not run to completion
if (recipe.lastRunOp === recipe.opList[recipe.opList.length - 1]) {
return dish.get(Dish.ARRAY_BUFFER);
} else {
return new ArrayBuffer();
}
} catch (err) { } catch (err) {
// If there are errors, return an empty buffer // If there are errors, return an empty buffer
return new ArrayBuffer(); return new ArrayBuffer();
@@ -784,452 +803,9 @@ class Magic {
}[code]; }[code];
} }
/**
* Given a buffer, detects magic byte sequences at specific positions and returns the
* extension and mime type.
*
* @param {Uint8Array} buf
* @returns {Object} type
* @returns {string} type.ext - File extension
* @returns {string} type.mime - Mime type
* @returns {string} [type.desc] - Description
*/
static magicFileType(buf) {
if (!(buf && buf.length > 1)) {
return null;
}
if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) {
return {
ext: "jpg",
mime: "image/jpeg"
};
}
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) {
return {
ext: "png",
mime: "image/png"
};
}
if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) {
return {
ext: "gif",
mime: "image/gif"
};
}
if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) {
return {
ext: "webp",
mime: "image/webp"
};
}
// needs to be before `tif` check
if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) {
return {
ext: "cr2",
mime: "image/x-canon-cr2"
};
}
if ((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) {
return {
ext: "tif",
mime: "image/tiff"
};
}
if (buf[0] === 0x42 && buf[1] === 0x4D) {
return {
ext: "bmp",
mime: "image/bmp"
};
}
if (buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0xBC) {
return {
ext: "jxr",
mime: "image/vnd.ms-photo"
};
}
if (buf[0] === 0x38 && buf[1] === 0x42 && buf[2] === 0x50 && buf[3] === 0x53) {
return {
ext: "psd",
mime: "image/vnd.adobe.photoshop"
};
}
// needs to be before `zip` check
if (buf[0] === 0x50 && buf[1] === 0x4B && buf[2] === 0x3 && buf[3] === 0x4 && buf[30] === 0x6D && buf[31] === 0x69 && buf[32] === 0x6D && buf[33] === 0x65 && buf[34] === 0x74 && buf[35] === 0x79 && buf[36] === 0x70 && buf[37] === 0x65 && buf[38] === 0x61 && buf[39] === 0x70 && buf[40] === 0x70 && buf[41] === 0x6C && buf[42] === 0x69 && buf[43] === 0x63 && buf[44] === 0x61 && buf[45] === 0x74 && buf[46] === 0x69 && buf[47] === 0x6F && buf[48] === 0x6E && buf[49] === 0x2F && buf[50] === 0x65 && buf[51] === 0x70 && buf[52] === 0x75 && buf[53] === 0x62 && buf[54] === 0x2B && buf[55] === 0x7A && buf[56] === 0x69 && buf[57] === 0x70) {
return {
ext: "epub",
mime: "application/epub+zip"
};
}
if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) {
return {
ext: "zip",
mime: "application/zip"
};
}
if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) {
return {
ext: "tar",
mime: "application/x-tar"
};
}
if (buf[0] === 0x52 && buf[1] === 0x61 && buf[2] === 0x72 && buf[3] === 0x21 && buf[4] === 0x1A && buf[5] === 0x7 && (buf[6] === 0x0 || buf[6] === 0x1)) {
return {
ext: "rar",
mime: "application/x-rar-compressed"
};
}
if (buf[0] === 0x1F && buf[1] === 0x8B && buf[2] === 0x8) {
return {
ext: "gz",
mime: "application/gzip"
};
}
if (buf[0] === 0x42 && buf[1] === 0x5A && buf[2] === 0x68) {
return {
ext: "bz2",
mime: "application/x-bzip2"
};
}
if (buf[0] === 0x37 && buf[1] === 0x7A && buf[2] === 0xBC && buf[3] === 0xAF && buf[4] === 0x27 && buf[5] === 0x1C) {
return {
ext: "7z",
mime: "application/x-7z-compressed"
};
}
if (buf[0] === 0x78 && buf[1] === 0x01) {
return {
ext: "dmg, zlib",
mime: "application/x-apple-diskimage, application/x-deflate"
};
}
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && (buf[3] === 0x18 || buf[3] === 0x20) && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) || (buf[0] === 0x33 && buf[1] === 0x67 && buf[2] === 0x70 && buf[3] === 0x35) || (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x6D && buf[9] === 0x70 && buf[10] === 0x34 && buf[11] === 0x32 && buf[16] === 0x6D && buf[17] === 0x70 && buf[18] === 0x34 && buf[19] === 0x31 && buf[20] === 0x6D && buf[21] === 0x70 && buf[22] === 0x34 && buf[23] === 0x32 && buf[24] === 0x69 && buf[25] === 0x73 && buf[26] === 0x6F && buf[27] === 0x6D)) {
return {
ext: "mp4",
mime: "video/mp4"
};
}
if ((buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x1C && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x56)) {
return {
ext: "m4v",
mime: "video/x-m4v"
};
}
if (buf[0] === 0x4D && buf[1] === 0x54 && buf[2] === 0x68 && buf[3] === 0x64) {
return {
ext: "mid",
mime: "audio/midi"
};
}
// needs to be before the `webm` check
if (buf[31] === 0x6D && buf[32] === 0x61 && buf[33] === 0x74 && buf[34] === 0x72 && buf[35] === 0x6f && buf[36] === 0x73 && buf[37] === 0x6B && buf[38] === 0x61) {
return {
ext: "mkv",
mime: "video/x-matroska"
};
}
if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) {
return {
ext: "webm",
mime: "video/webm"
};
}
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) {
return {
ext: "mov",
mime: "video/quicktime"
};
}
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x41 && buf[9] === 0x56 && buf[10] === 0x49) {
return {
ext: "avi",
mime: "video/x-msvideo"
};
}
if (buf[0] === 0x30 && buf[1] === 0x26 && buf[2] === 0xB2 && buf[3] === 0x75 && buf[4] === 0x8E && buf[5] === 0x66 && buf[6] === 0xCF && buf[7] === 0x11 && buf[8] === 0xA6 && buf[9] === 0xD9) {
return {
ext: "wmv",
mime: "video/x-ms-wmv"
};
}
if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x1 && buf[3].toString(16)[0] === "b") {
return {
ext: "mpg",
mime: "video/mpeg"
};
}
if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) || (buf[0] === 0xFF && buf[1] === 0xfb)) {
return {
ext: "mp3",
mime: "audio/mpeg"
};
}
if ((buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70 && buf[8] === 0x4D && buf[9] === 0x34 && buf[10] === 0x41) || (buf[0] === 0x4D && buf[1] === 0x34 && buf[2] === 0x41 && buf[3] === 0x20)) {
return {
ext: "m4a",
mime: "audio/m4a"
};
}
if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) {
return {
ext: "ogg",
mime: "audio/ogg"
};
}
if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) {
return {
ext: "flac",
mime: "audio/x-flac"
};
}
if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) {
return {
ext: "wav",
mime: "audio/x-wav"
};
}
if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) {
return {
ext: "amr",
mime: "audio/amr"
};
}
if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) {
return {
ext: "pdf",
mime: "application/pdf"
};
}
if (buf[0] === 0x4D && buf[1] === 0x5A) {
return {
ext: "exe",
mime: "application/x-msdownload"
};
}
if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) {
return {
ext: "swf",
mime: "application/x-shockwave-flash"
};
}
if (buf[0] === 0x7B && buf[1] === 0x5C && buf[2] === 0x72 && buf[3] === 0x74 && buf[4] === 0x66) {
return {
ext: "rtf",
mime: "application/rtf"
};
}
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x46 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
return {
ext: "woff",
mime: "application/font-woff"
};
}
if (buf[0] === 0x77 && buf[1] === 0x4F && buf[2] === 0x46 && buf[3] === 0x32 && buf[4] === 0x00 && buf[5] === 0x01 && buf[6] === 0x00 && buf[7] === 0x00) {
return {
ext: "woff2",
mime: "application/font-woff"
};
}
if (buf[34] === 0x4C && buf[35] === 0x50 && ((buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x01) || (buf[8] === 0x01 && buf[9] === 0x00 && buf[10] === 0x00) || (buf[8] === 0x02 && buf[9] === 0x00 && buf[10] === 0x02))) {
return {
ext: "eot",
mime: "application/octet-stream"
};
}
if (buf[0] === 0x00 && buf[1] === 0x01 && buf[2] === 0x00 && buf[3] === 0x00 && buf[4] === 0x00) {
return {
ext: "ttf",
mime: "application/font-sfnt"
};
}
if (buf[0] === 0x4F && buf[1] === 0x54 && buf[2] === 0x54 && buf[3] === 0x4F && buf[4] === 0x00) {
return {
ext: "otf",
mime: "application/font-sfnt"
};
}
if (buf[0] === 0x00 && buf[1] === 0x00 && buf[2] === 0x01 && buf[3] === 0x00) {
return {
ext: "ico",
mime: "image/x-icon"
};
}
if (buf[0] === 0x46 && buf[1] === 0x4C && buf[2] === 0x56 && buf[3] === 0x01) {
return {
ext: "flv",
mime: "video/x-flv"
};
}
if (buf[0] === 0x25 && buf[1] === 0x21) {
return {
ext: "ps",
mime: "application/postscript"
};
}
if (buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7A && buf[3] === 0x58 && buf[4] === 0x5A && buf[5] === 0x00) {
return {
ext: "xz",
mime: "application/x-xz"
};
}
if (buf[0] === 0x53 && buf[1] === 0x51 && buf[2] === 0x4C && buf[3] === 0x69) {
return {
ext: "sqlite",
mime: "application/x-sqlite3"
};
}
/**
*
* Added by n1474335 [n1474335@gmail.com] from here on
*
*/
if ((buf[0] === 0x1F && buf[1] === 0x9D) || (buf[0] === 0x1F && buf[1] === 0xA0)) {
return {
ext: "z, tar.z",
mime: "application/x-gtar"
};
}
if (buf[0] === 0x7F && buf[1] === 0x45 && buf[2] === 0x4C && buf[3] === 0x46) {
return {
ext: "none, axf, bin, elf, o, prx, puff, so",
mime: "application/x-executable",
desc: "Executable and Linkable Format file. No standard file extension."
};
}
if (buf[0] === 0xCA && buf[1] === 0xFE && buf[2] === 0xBA && buf[3] === 0xBE) {
return {
ext: "class",
mime: "application/java-vm"
};
}
if (buf[0] === 0xEF && buf[1] === 0xBB && buf[2] === 0xBF) {
return {
ext: "txt",
mime: "text/plain",
desc: "UTF-8 encoded Unicode byte order mark detected, commonly but not exclusively seen in text files."
};
}
// Must be before Little-endian UTF-16 BOM
if (buf[0] === 0xFF && buf[1] === 0xFE && buf[2] === 0x00 && buf[3] === 0x00) {
return {
ext: "UTF32LE",
mime: "charset/utf32le",
desc: "Little-endian UTF-32 encoded Unicode byte order mark detected."
};
}
if (buf[0] === 0xFF && buf[1] === 0xFE) {
return {
ext: "UTF16LE",
mime: "charset/utf16le",
desc: "Little-endian UTF-16 encoded Unicode byte order mark detected."
};
}
if ((buf[0x8001] === 0x43 && buf[0x8002] === 0x44 && buf[0x8003] === 0x30 && buf[0x8004] === 0x30 && buf[0x8005] === 0x31) ||
(buf[0x8801] === 0x43 && buf[0x8802] === 0x44 && buf[0x8803] === 0x30 && buf[0x8804] === 0x30 && buf[0x8805] === 0x31) ||
(buf[0x9001] === 0x43 && buf[0x9002] === 0x44 && buf[0x9003] === 0x30 && buf[0x9004] === 0x30 && buf[0x9005] === 0x31)) {
return {
ext: "iso",
mime: "application/octet-stream",
desc: "ISO 9660 CD/DVD image file"
};
}
if (buf[0] === 0xD0 && buf[1] === 0xCF && buf[2] === 0x11 && buf[3] === 0xE0 && buf[4] === 0xA1 && buf[5] === 0xB1 && buf[6] === 0x1A && buf[7] === 0xE1) {
return {
ext: "doc, xls, ppt",
mime: "application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint",
desc: "Microsoft Office documents"
};
}
if (buf[0] === 0x64 && buf[1] === 0x65 && buf[2] === 0x78 && buf[3] === 0x0A && buf[4] === 0x30 && buf[5] === 0x33 && buf[6] === 0x35 && buf[7] === 0x00) {
return {
ext: "dex",
mime: "application/octet-stream",
desc: "Dalvik Executable (Android)"
};
}
if (buf[0] === 0x4B && buf[1] === 0x44 && buf[2] === 0x4D) {
return {
ext: "vmdk",
mime: "application/vmdk, application/x-virtualbox-vmdk"
};
}
if (buf[0] === 0x43 && buf[1] === 0x72 && buf[2] === 0x32 && buf[3] === 0x34) {
return {
ext: "crx",
mime: "application/crx",
desc: "Google Chrome extension or packaged app"
};
}
if (buf[0] === 0x78 && (buf[1] === 0x01 || buf[1] === 0x9C || buf[1] === 0xDA || buf[1] === 0x5e)) {
return {
ext: "zlib",
mime: "application/x-deflate"
};
}
return null;
}
} }
/** /**
* Byte frequencies of various languages generated from Wikipedia dumps taken in late 2017 and early 2018. * Byte frequencies of various languages generated from Wikipedia dumps taken in late 2017 and early 2018.
* The Chi-Squared test cannot accept expected values of 0, so 0.0001 has been used to account for bytes * The Chi-Squared test cannot accept expected values of 0, so 0.0001 has been used to account for bytes

285
src/core/lib/Protobuf.mjs Normal file
View File

@@ -0,0 +1,285 @@
import Utils from "../Utils";
/**
* Protobuf lib. Contains functions to decode protobuf serialised
* data without a schema or .proto file.
*
* Provides utility functions to encode and decode variable length
* integers (varint).
*
* @author GCHQ Contributor [3]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
class Protobuf {
/**
* Protobuf constructor
*
* @param {byteArray} data
*/
constructor(data) {
// Check we have a byteArray
if (data instanceof Array) {
this.data = data;
} else {
throw new Error("Protobuf input must be a byteArray");
}
// Set up masks
this.TYPE = 0x07;
this.NUMBER = 0x78;
this.MSB = 0x80;
this.VALUE = 0x7f;
// Declare offset and length
this.offset = 0;
this.LENGTH = data.length;
}
// Public Functions
/**
* Encode a varint from a number
*
* @param {number} number
* @returns {byteArray}
*/
static varIntEncode(number) {
const MSB = 0x80,
VALUE = 0x7f,
MSBALL = ~VALUE,
INT = Math.pow(2, 31);
const out = [];
let offset = 0;
while (number >= INT) {
out[offset++] = (number & 0xff) | MSB;
number /= 128;
}
while (number & MSBALL) {
out[offset++] = (number & 0xff) | MSB;
number >>>= 7;
}
out[offset] = number | 0;
return out;
}
/**
* Decode a varint from the byteArray
*
* @param {byteArray} input
* @returns {number}
*/
static varIntDecode(input) {
const pb = new Protobuf(input);
return pb._varInt();
}
/**
* Parse Protobuf data
*
* @param {byteArray} input
* @returns {Object}
*/
static decode(input) {
const pb = new Protobuf(input);
return pb._parse();
}
// Private Class Functions
/**
* Main private parsing function
*
* @private
* @returns {Object}
*/
_parse() {
let object = {};
// Continue reading whilst we still have data
while (this.offset < this.LENGTH) {
const field = this._parseField();
object = this._addField(field, object);
}
// Throw an error if we have gone beyond the end of the data
if (this.offset > this.LENGTH) {
throw new Error("Exhausted Buffer");
}
return object;
}
/**
* Add a field read from the protobuf data into the Object. As
* protobuf fields can appear multiple times, if the field already
* exists we need to add the new field into an array of fields
* for that key.
*
* @private
* @param {Object} field
* @param {Object} object
* @returns {Object}
*/
_addField(field, object) {
// Get the field key/values
const key = field.key;
const value = field.value;
object[key] = object.hasOwnProperty(key) ?
object[key] instanceof Array ?
object[key].concat([value]) :
[object[key], value] :
value;
return object;
}
/**
* Parse a field and return the Object read from the record
*
* @private
* @returns {Object}
*/
_parseField() {
// Get the field headers
const header = this._fieldHeader();
const type = header.type;
const key = header.key;
switch (type) {
// varint
case 0:
return { "key": key, "value": this._varInt() };
// fixed 64
case 1:
return { "key": key, "value": this._uint64() };
// length delimited
case 2:
return { "key": key, "value": this._lenDelim() };
// fixed 32
case 5:
return { "key": key, "value": this._uint32() };
// unknown type
default:
throw new Error("Unknown type 0x" + type.toString(16));
}
}
/**
* Parse the field header and return the type and key
*
* @private
* @returns {Object}
*/
_fieldHeader() {
// Make sure we call type then number to preserve offset
return { "type": this._fieldType(), "key": this._fieldNumber() };
}
/**
* Parse the field type from the field header. Type is stored in the
* lower 3 bits of the tag byte. This does not move the offset on as
* we need to read the field number from the tag byte too.
*
* @private
* @returns {number}
*/
_fieldType() {
// Field type stored in lower 3 bits of tag byte
return this.data[this.offset] & this.TYPE;
}
/**
* Parse the field number (i.e. the key) from the field header. The
* field number is stored in the upper 5 bits of the tag byte - but
* is also varint encoded so the follow on bytes may need to be read
* when field numbers are > 15.
*
* @private
* @returns {number}
*/
_fieldNumber() {
let shift = -3;
let fieldNumber = 0;
do {
fieldNumber += shift < 28 ?
shift === -3 ?
(this.data[this.offset] & this.NUMBER) >> -shift :
(this.data[this.offset] & this.VALUE) << shift :
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
shift += 7;
} while ((this.data[this.offset++] & this.MSD) === this.MSB);
return fieldNumber;
}
// Field Parsing Functions
/**
* Read off a varint from the data
*
* @private
* @returns {number}
*/
_varInt() {
let value = 0;
let shift = 0;
// Keep reading while upper bit set
do {
value += shift < 28 ?
(this.data[this.offset] & this.VALUE) << shift :
(this.data[this.offset] & this.VALUE) * Math.pow(2, shift);
shift += 7;
} while ((this.data[this.offset++] & this.MSB) === this.MSB);
return value;
}
/**
* Read off a 64 bit unsigned integer from the data
*
* @private
* @returns {number}
*/
_uint64() {
// Read off a Uint64
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
return num;
}
/**
* Read off a length delimited field from the data
*
* @private
* @returns {Object|string}
*/
_lenDelim() {
// Read off the field length
const length = this._varInt();
const fieldBytes = this.data.slice(this.offset, this.offset + length);
let field;
try {
// Attempt to parse as a new Protobuf Object
const pbObject = new Protobuf(fieldBytes);
field = pbObject._parse();
} catch (err) {
// Otherwise treat as bytes
field = Utils.byteArrayToChars(fieldBytes);
}
// Move the offset and return the field
this.offset += length;
return field;
}
/**
* Read a 32 bit unsigned integer from the data
*
* @private
* @returns {number}
*/
_uint32() {
// Use a dataview to read off the integer
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
const value = dataview.getUint32(0);
this.offset += 4;
return value;
}
}
export default Protobuf;

93
src/core/lib/QRCode.mjs Normal file
View File

@@ -0,0 +1,93 @@
/**
* QR code resources
*
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import jsQR from "jsqr";
import qr from "qr-image";
import jimp from "jimp";
import Utils from "../Utils";
/**
* Parses a QR code image from an image
*
* @param {ArrayBuffer} input
* @param {boolean} normalise
* @returns {string}
*/
export async function parseQrCode(input, normalise) {
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error opening image. (${err})`);
}
try {
if (normalise) {
image.rgba(false);
image.background(0xFFFFFFFF);
image.normalize();
image.greyscale();
image = await image.getBufferAsync(jimp.MIME_JPEG);
image = await jimp.read(image);
}
} catch (err) {
throw new OperationError(`Error normalising iamge. (${err})`);
}
const qrData = jsQR(image.bitmap.data, image.getWidth(), image.getHeight());
if (qrData) {
return qrData.data;
} else {
throw new OperationError("Could not read a QR code from the image.");
}
}
/**
* Generates a QR code from the input string
*
* @param {string} input
* @param {string} format
* @param {number} moduleSize
* @param {number} margin
* @param {string} errorCorrection
* @returns {ArrayBuffer}
*/
export function generateQrCode(input, format, moduleSize, margin, errorCorrection) {
const formats = ["SVG", "EPS", "PDF", "PNG"];
if (!formats.includes(format.toUpperCase())) {
throw new OperationError("Unsupported QR code format.");
}
let qrImage;
try {
qrImage = qr.imageSync(input, {
type: format,
size: moduleSize,
margin: margin,
"ec_level": errorCorrection.charAt(0).toUpperCase()
});
} catch (err) {
throw new OperationError(`Error generating QR code. (${err})`);
}
if (!qrImage) {
throw new OperationError("Error generating QR code.");
}
switch (format) {
case "SVG":
case "EPS":
case "PDF":
return Utils.strToArrayBuffer(qrImage);
case "PNG":
return qrImage.buffer;
default:
throw new OperationError("Unsupported QR code format.");
}
}

263
src/core/lib/Stream.mjs Normal file
View File

@@ -0,0 +1,263 @@
/**
* Stream class for parsing binary protocols.
*
* @author n1474335 [n1474335@gmail.com]
* @author tlwr [toby@toby.codes]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*
*/
/**
* A Stream can be used to traverse a binary blob, interpreting sections of it
* as various data types.
*/
export default class Stream {
/**
* Stream constructor.
*
* @param {Uint8Array} input
*/
constructor(input) {
this.bytes = input;
this.length = this.bytes.length;
this.position = 0;
this.bitPos = 0;
}
/**
* Get a number of bytes from the current position.
*
* @param {number} numBytes
* @returns {Uint8Array}
*/
getBytes(numBytes) {
if (this.position > this.length) return undefined;
const newPosition = this.position + numBytes;
const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition;
this.bitPos = 0;
return bytes;
}
/**
* Interpret the following bytes as a string, stopping at the next null byte or
* the supplied limit.
*
* @param {number} numBytes
* @returns {string}
*/
readString(numBytes) {
if (this.position > this.length) return undefined;
let result = "";
for (let i = this.position; i < this.position + numBytes; i++) {
const currentByte = this.bytes[i];
if (currentByte === 0) break;
result += String.fromCharCode(currentByte);
}
this.position += numBytes;
this.bitPos = 0;
return result;
}
/**
* Interpret the following bytes as an integer in big or little endian.
*
* @param {number} numBytes
* @param {string} [endianness="be"]
* @returns {number}
*/
readInt(numBytes, endianness="be") {
if (this.position > this.length) return undefined;
let val = 0;
if (endianness === "be") {
for (let i = this.position; i < this.position + numBytes; i++) {
val = val << 8;
val |= this.bytes[i];
}
} else {
for (let i = this.position + numBytes - 1; i >= this.position; i--) {
val = val << 8;
val |= this.bytes[i];
}
}
this.position += numBytes;
this.bitPos = 0;
return val;
}
/**
* Reads a number of bits from the buffer.
*
* @TODO Add endianness
*
* @param {number} numBits
* @returns {number}
*/
readBits(numBits) {
if (this.position > this.length) return undefined;
let bitBuf = 0,
bitBufLen = 0;
// Add remaining bits from current byte
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
bitBufLen = 8 - this.bitPos;
this.bitPos = 0;
// Not enough bits yet
while (bitBufLen < numBits) {
bitBuf |= this.bytes[this.position++] << bitBufLen;
bitBufLen += 8;
}
// Reverse back to numBits
if (bitBufLen > numBits) {
const excess = bitBufLen - numBits;
bitBuf &= (1 << numBits) - 1;
bitBufLen -= excess;
this.position--;
this.bitPos = 8 - excess;
}
return bitBuf;
/**
* Calculates the bit mask based on the current bit position.
*
* @param {number} bitPos
* @returns {number} The bit mask
*/
function bitMask(bitPos) {
return 256 - (1 << bitPos);
}
}
/**
* Consume the stream until we reach the specified byte or sequence of bytes.
*
* @param {number|List<number>} val
*/
continueUntil(val) {
if (this.position > this.length) return;
this.bitPos = 0;
if (typeof val === "number") {
while (++this.position < this.length && this.bytes[this.position] !== val) {
continue;
}
return;
}
// val is an array
let found = false;
while (!found && this.position < this.length) {
while (++this.position < this.length && this.bytes[this.position] !== val[0]) {
continue;
}
found = true;
for (let i = 1; i < val.length; i++) {
if (this.position + i > this.length || this.bytes[this.position + i] !== val[i])
found = false;
}
}
}
/**
* Consume the next byte if it matches the supplied value.
*
* @param {number} val
*/
consumeIf(val) {
if (this.bytes[this.position] === val) {
this.position++;
this.bitPos = 0;
}
}
/**
* Move forwards through the stream by the specified number of bytes.
*
* @param {number} numBytes
*/
moveForwardsBy(numBytes) {
const pos = this.position + numBytes;
if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos;
this.bitPos = 0;
}
/**
* Move backwards through the stream by the specified number of bytes.
*
* @param {number} numBytes
*/
moveBackwardsBy(numBytes) {
const pos = this.position - numBytes;
if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos;
this.bitPos = 0;
}
/**
* Move backwards through the strem by the specified number of bits.
*
* @param {number} numBits
*/
moveBackwardsByBits(numBits) {
if (numBits <= this.bitPos) {
this.bitPos -= numBits;
} else {
if (this.bitPos > 0) {
numBits -= this.bitPos;
this.bitPos = 0;
}
while (numBits > 0) {
this.moveBackwardsBy(1);
this.bitPos = 8;
this.moveBackwardsByBits(numBits);
numBits -= 8;
}
}
}
/**
* Move to a specified position in the stream.
*
* @param {number} pos
*/
moveTo(pos) {
if (pos < 0 || pos > this.length)
throw new Error("Cannot move to position " + pos + " in stream. Out of bounds.");
this.position = pos;
this.bitPos = 0;
}
/**
* Returns true if there are more bytes left in the stream.
*
* @returns {boolean}
*/
hasMore() {
return this.position < this.length;
}
/**
* Returns a slice of the stream up to the current position.
*
* @returns {Uint8Array}
*/
carve() {
if (this.bitPos > 0) this.position++;
return this.bytes.slice(0, this.position);
}
}

227
src/core/lib/Typex.mjs Normal file
View File

@@ -0,0 +1,227 @@
/**
* Emulation of the Typex machine.
*
* @author s2224834
* @author The National Museum of Computing - Bombe Rebuild Project
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError";
import * as Enigma from "../lib/Enigma";
import Utils from "../Utils";
/**
* A set of example Typex rotors. No Typex rotor wirings are publicly available, so these are
* randomised.
*/
export const ROTORS = [
{name: "Example 1", value: "MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW"},
{name: "Example 2", value: "KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW"},
{name: "Example 3", value: "BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW"},
{name: "Example 4", value: "ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW"},
{name: "Example 5", value: "QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW"},
{name: "Example 6", value: "BDCNWUEIQVFTSXALOGZJYMHKPR<BFHNQUW"},
{name: "Example 7", value: "WJUKEIABMSGFTQZVCNPHORDXYL<BFHNQUW"},
{name: "Example 8", value: "TNVCZXDIPFWQKHSJMAOYLEURGB<BFHNQUW"},
];
/**
* An example Typex reflector. Again, randomised.
*/
export const REFLECTORS = [
{name: "Example", value: "AN BC FG IE KD LU MH OR TS VZ WQ XJ YP"},
];
// Special character handling on Typex keyboard
const KEYBOARD = {
"Q": "1", "W": "2", "E": "3", "R": "4", "T": "5", "Y": "6", "U": "7", "I": "8", "O": "9", "P": "0",
"A": "-", "S": "/", "D": "Z", "F": "%", "G": "X", "H": "£", "K": "(", "L": ")",
"C": "V", "B": "'", "N": ",", "M": "."
};
const KEYBOARD_REV = {};
for (const i of Object.keys(KEYBOARD)) {
KEYBOARD_REV[KEYBOARD[i]] = i;
}
/**
* Typex machine. A lot like the Enigma, but five rotors, of which the first two are static.
*/
export class TypexMachine extends Enigma.EnigmaBase {
/**
* TypexMachine constructor.
*
* @param {Object[]} rotors - List of Rotors.
* @param {Object} reflector - A Reflector.
* @param {Plugboard} plugboard - A Plugboard.
*/
constructor(rotors, reflector, plugboard, keyboard) {
super(rotors, reflector, plugboard);
if (rotors.length !== 5) {
throw new OperationError("Typex must have 5 rotors");
}
this.keyboard = keyboard;
}
/**
* This is the same as the Enigma step function, it's just that the right-
* most two rotors are static.
*/
step() {
const r0 = this.rotors[2];
const r1 = this.rotors[3];
r0.step();
// The second test here is the double-stepping anomaly
if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) {
r1.step();
if (r1.steps.has(r1.pos)) {
const r2 = this.rotors[4];
r2.step();
}
}
}
/**
* Encrypt/decrypt data. This is identical to the Enigma version cryptographically, but we have
* additional handling for the Typex's keyboard (which handles various special characters by
* mapping them to particular letter combinations).
*
* @param {string} input - The data to encrypt/decrypt.
* @return {string}
*/
crypt(input) {
let inputMod = input;
if (this.keyboard === "Encrypt") {
inputMod = "";
// true = in symbol mode
let mode = false;
for (const x of input) {
if (x === " ") {
inputMod += "X";
} else if (mode) {
if (KEYBOARD_REV.hasOwnProperty(x)) {
inputMod += KEYBOARD_REV[x];
} else {
mode = false;
inputMod += "V" + x;
}
} else {
if (KEYBOARD_REV.hasOwnProperty(x)) {
mode = true;
inputMod += "Z" + KEYBOARD_REV[x];
} else {
inputMod += x;
}
}
}
}
const output = super.crypt(inputMod);
let outputMod = output;
if (this.keyboard === "Decrypt") {
outputMod = "";
let mode = false;
for (const x of output) {
if (x === "X") {
outputMod += " ";
} else if (x === "V") {
mode = false;
} else if (x === "Z") {
mode = true;
} else if (mode) {
outputMod += KEYBOARD[x];
} else {
outputMod += x;
}
}
}
return outputMod;
}
}
/**
* Typex rotor. Like an Enigma rotor, but no ring setting, and can be reversed.
*/
export class Rotor extends Enigma.Rotor {
/**
* Rotor constructor.
*
* @param {string} wiring - A 26 character string of the wiring order.
* @param {string} steps - A 0..26 character string of stepping points.
* @param {bool} reversed - Whether to reverse the rotor.
* @param {char} ringSetting - Ring setting of the rotor.
* @param {char} initialPosition - The initial position of the rotor.
*/
constructor(wiring, steps, reversed, ringSetting, initialPos) {
let wiringMod = wiring;
if (reversed) {
const outMap = new Array(26);
for (let i=0; i<26; i++) {
// wiring[i] is the original output
// Enigma.LETTERS[i] is the original input
const input = Utils.mod(26 - Enigma.a2i(wiring[i]), 26);
const output = Enigma.i2a(Utils.mod(26 - Enigma.a2i(Enigma.LETTERS[i]), 26));
outMap[input] = output;
}
wiringMod = outMap.join("");
}
super(wiringMod, steps, ringSetting, initialPos);
}
}
/**
* Typex input plugboard. Based on a Rotor, because it allows arbitrary maps, not just switches
* like the Enigma plugboard.
* Not to be confused with the reflector plugboard.
* This is also where the Typex's backwards input wiring is implemented - it's a bit of a hack, but
* it means everything else continues to work like in the Enigma.
*/
export class Plugboard extends Enigma.Rotor {
/**
* Typex plugboard constructor.
*
* @param {string} wiring - 26 character string of mappings from A-Z, as per rotors, or "".
*/
constructor(wiring) {
// Typex input wiring is backwards vs Enigma: that is, letters enter the rotors in a
// clockwise order, vs. Enigma's anticlockwise (or vice versa depending on which side
// you're looking at it from). I'm doing the transform here to avoid having to rewrite
// the Engima crypt() method in Typex as well.
// Note that the wiring for the reflector is the same way around as Enigma, so no
// transformation is necessary on that side.
// We're going to achieve this by mapping the plugboard settings through an additional
// transform that mirrors the alphabet before we pass it to the superclass.
if (!/^[A-Z]{26}$/.test(wiring)) {
throw new OperationError("Plugboard wiring must be 26 unique uppercase letters");
}
const reversed = "AZYXWVUTSRQPONMLKJIHGFEDCB";
wiring = wiring.replace(/./g, x => {
return reversed[Enigma.a2i(x)];
});
try {
super(wiring, "", "A", "A");
} catch (err) {
throw new OperationError(err.message.replace("Rotor", "Plugboard"));
}
}
/**
* Transform a character through this rotor forwards.
*
* @param {number} c - The character.
* @returns {number}
*/
transform(c) {
return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26);
}
/**
* Transform a character through this rotor backwards.
*
* @param {number} c - The character.
* @returns {number}
*/
revTransform(c) {
return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26);
}
}

View File

@@ -0,0 +1,63 @@
/**
* @author Jarmo van Lenthe [github.com/jarmovanlenthe]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {DELIM_OPTIONS} from "../lib/Delim";
import OperationError from "../errors/OperationError";
/**
* A1Z26 Cipher Decode operation
*/
class A1Z26CipherDecode extends Operation {
/**
* A1Z26CipherDecode constructor
*/
constructor() {
super();
this.name = "A1Z26 Cipher Decode";
this.module = "Ciphers";
this.description = "Converts alphabet order numbers into their corresponding alphabet character.<br><br>e.g. <code>1</code> becomes <code>a</code> and <code>2</code> becomes <code>b</code>.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Delimiter",
type: "option",
value: DELIM_OPTIONS
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0] || "Space");
if (input.length === 0) {
return [];
}
const bites = input.split(delim);
let latin1 = "";
for (let i = 0; i < bites.length; i++) {
if (bites[i] < 1 || bites[i] > 26) {
throw new OperationError("Error: all numbers must be between 1 and 26.");
}
latin1 += Utils.chr(parseInt(bites[i], 10) + 96);
}
return latin1;
}
}
export default A1Z26CipherDecode;

View File

@@ -0,0 +1,61 @@
/**
* @author Jarmo van Lenthe [github.com/jarmovanlenthe]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import {DELIM_OPTIONS} from "../lib/Delim";
/**
* A1Z26 Cipher Encode operation
*/
class A1Z26CipherEncode extends Operation {
/**
* A1Z26CipherEncode constructor
*/
constructor() {
super();
this.name = "A1Z26 Cipher Encode";
this.module = "Ciphers";
this.description = "Converts alphabet characters into their corresponding alphabet order number.<br><br>e.g. <code>a</code> becomes <code>1</code> and <code>b</code> becomes <code>2</code>.<br><br>Non-alphabet characters are dropped.";
this.infoURL = "";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Delimiter",
type: "option",
value: DELIM_OPTIONS
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const delim = Utils.charRep(args[0] || "Space");
let output = "";
const sanitizedinput = input.toLowerCase(),
charcode = Utils.strToCharcode(sanitizedinput);
for (let i = 0; i < charcode.length; i++) {
const ordinal = charcode[i] - 96;
if (ordinal > 0 && ordinal <= 26) {
output += ordinal.toString(10) + delim;
}
}
return output.slice(0, -delim.length);
}
}
export default A1Z26CipherEncode;

View File

@@ -65,8 +65,8 @@ class AESEncrypt extends Operation {
* @throws {OperationError} if invalid key length * @throws {OperationError} if invalid key length
*/ */
run(input, args) { run(input, args) {
const key = Utils.convertToByteArray(args[0].string, args[0].option), const key = Utils.convertToByteString(args[0].string, args[0].option),
iv = Utils.convertToByteArray(args[1].string, args[1].option), iv = Utils.convertToByteString(args[1].string, args[1].option),
mode = args[2], mode = args[2],
inputType = args[3], inputType = args[3],
outputType = args[4]; outputType = args[4];

View File

@@ -0,0 +1,266 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Add Text To Image operation
*/
class AddTextToImage extends Operation {
/**
* AddTextToImage constructor
*/
constructor() {
super();
this.name = "Add Text To Image";
this.module = "Image";
this.description = "Adds text onto an image.<br><br>Text can be horizontally or vertically aligned, or the position can be manually specified.<br>Variants of the Roboto font face are available in any size or colour.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Text",
type: "string",
value: ""
},
{
name: "Horizontal align",
type: "option",
value: ["None", "Left", "Center", "Right"]
},
{
name: "Vertical align",
type: "option",
value: ["None", "Top", "Middle", "Bottom"]
},
{
name: "X position",
type: "number",
value: 0
},
{
name: "Y position",
type: "number",
value: 0
},
{
name: "Size",
type: "number",
value: 32,
min: 8
},
{
name: "Font face",
type: "option",
value: [
"Roboto",
"Roboto Black",
"Roboto Mono",
"Roboto Slab"
]
},
{
name: "Red",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Green",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Blue",
type: "number",
value: 255,
min: 0,
max: 255
},
{
name: "Alpha",
type: "number",
value: 255,
min: 0,
max: 255
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const text = args[0],
hAlign = args[1],
vAlign = args[2],
size = args[5],
fontFace = args[6],
red = args[7],
green = args[8],
blue = args[9],
alpha = args[10];
let xPos = args[3],
yPos = args[4];
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Adding text to image...");
const fontsMap = {};
const fonts = [
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.fnt"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.fnt")
];
await Promise.all(fonts)
.then(fonts => {
fontsMap.Roboto = fonts[0];
fontsMap["Roboto Black"] = fonts[1];
fontsMap["Roboto Mono"] = fonts[2];
fontsMap["Roboto Slab"] = fonts[3];
});
// Make Webpack load the png font images
await Promise.all([
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/Roboto72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoSlab72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoMono72White.png"),
import(/* webpackMode: "eager" */ "../../web/static/fonts/bmfonts/RobotoBlack72White.png")
]);
const font = fontsMap[fontFace];
// LoadFont needs an absolute url, so append the font name to self.docURL
const jimpFont = await jimp.loadFont(self.docURL + "/" + font.default);
jimpFont.pages.forEach(function(page) {
if (page.bitmap) {
// Adjust the RGB values of the image pages to change the font colour.
const pageWidth = page.bitmap.width;
const pageHeight = page.bitmap.height;
for (let ix = 0; ix < pageWidth; ix++) {
for (let iy = 0; iy < pageHeight; iy++) {
const idx = (iy * pageWidth + ix) << 2;
const newRed = page.bitmap.data[idx] - (255 - red);
const newGreen = page.bitmap.data[idx + 1] - (255 - green);
const newBlue = page.bitmap.data[idx + 2] - (255 - blue);
const newAlpha = page.bitmap.data[idx + 3] - (255 - alpha);
// Make sure the bitmap values don't go below 0 as that makes jimp very unhappy
page.bitmap.data[idx] = (newRed > 0) ? newRed : 0;
page.bitmap.data[idx + 1] = (newGreen > 0) ? newGreen : 0;
page.bitmap.data[idx + 2] = (newBlue > 0) ? newBlue : 0;
page.bitmap.data[idx + 3] = (newAlpha > 0) ? newAlpha : 0;
}
}
}
});
// Create a temporary image to hold the rendered text
const textImage = new jimp(jimp.measureText(jimpFont, text), jimp.measureTextHeight(jimpFont, text));
textImage.print(jimpFont, 0, 0, text);
// Scale the rendered text image to the correct size
const scaleFactor = size / 72;
if (size !== 1) {
// Use bicubic for decreasing size
if (size > 1) {
textImage.scale(scaleFactor, jimp.RESIZE_BICUBIC);
} else {
textImage.scale(scaleFactor, jimp.RESIZE_BILINEAR);
}
}
// If using the alignment options, calculate the pixel values AFTER the image has been scaled
switch (hAlign) {
case "Left":
xPos = 0;
break;
case "Center":
xPos = (image.getWidth() / 2) - (textImage.getWidth() / 2);
break;
case "Right":
xPos = image.getWidth() - textImage.getWidth();
break;
}
switch (vAlign) {
case "Top":
yPos = 0;
break;
case "Middle":
yPos = (image.getHeight() / 2) - (textImage.getHeight() / 2);
break;
case "Bottom":
yPos = image.getHeight() - textImage.getHeight();
break;
}
// Blit the rendered text image onto the original source image
image.blit(textImage, xPos, yPos);
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error adding text to image. (${err})`);
}
}
/**
* Displays the blurred image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default AddTextToImage;

View File

@@ -19,7 +19,7 @@ class Adler32Checksum extends Operation {
super(); super();
this.name = "Adler-32 Checksum"; this.name = "Adler-32 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32."; this.description = "Adler-32 is a checksum algorithm which was invented by Mark Adler in 1995, and is a modification of the Fletcher checksum. Compared to a cyclic redundancy check of the same length, it trades reliability for speed (preferring the latter).<br><br>Adler-32 is more reliable than Fletcher-16, and slightly less reliable than Fletcher-32.";
this.infoURL = "https://wikipedia.org/wiki/Adler-32"; this.infoURL = "https://wikipedia.org/wiki/Adler-32";
this.inputType = "byteArray"; this.inputType = "byteArray";

View File

@@ -19,7 +19,7 @@ class AnalyseHash extends Operation {
super(); super();
this.name = "Analyse hash"; this.name = "Analyse hash";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length."; this.description = "Tries to determine information about a given hash and suggests which algorithm may have been used to generate it based on its length.";
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
this.inputType = "string"; this.inputType = "string";

View File

@@ -0,0 +1,79 @@
/**
* @author h345983745
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import blakejs from "blakejs";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { toBase64 } from "../lib/Base64";
/**
* BLAKE2b operation
*/
class BLAKE2b extends Operation {
/**
* BLAKE2b constructor
*/
constructor() {
super();
this.name = "BLAKE2b";
this.module = "Hashing";
this.description = `Performs BLAKE2b hashing on the input.
<br><br> BLAKE2b is a flavour of the BLAKE cryptographic hash function that is optimized for 64-bit platforms and produces digests of any size between 1 and 64 bytes.
<br><br> Supports the use of an optional key.`;
this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2b_algorithm";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Size",
"type": "option",
"value": ["512", "384", "256", "160", "128"]
}, {
"name": "Output Encoding",
"type": "option",
"value": ["Hex", "Base64", "Raw"]
}, {
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string} The input having been hashed with BLAKE2b in the encoding format speicifed.
*/
run(input, args) {
const [outSize, outFormat] = args;
let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
if (key.length === 0) {
key = null;
} else if (key.length > 64) {
throw new OperationError(["Key cannot be greater than 64 bytes", "It is currently " + key.length + " bytes."].join("\n"));
}
input = new Uint8Array(input);
switch (outFormat) {
case "Hex":
return blakejs.blake2bHex(input, key, outSize / 8);
case "Base64":
return toBase64(blakejs.blake2b(input, key, outSize / 8));
case "Raw":
return Utils.arrayBufferToStr(blakejs.blake2b(input, key, outSize / 8).buffer);
default:
return new OperationError("Unsupported Output Type");
}
}
}
export default BLAKE2b;

View File

@@ -0,0 +1,80 @@
/**
* @author h345983745
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import blakejs from "blakejs";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { toBase64 } from "../lib/Base64";
/**
* BLAKE2s Operation
*/
class BLAKE2s extends Operation {
/**
* BLAKE2s constructor
*/
constructor() {
super();
this.name = "BLAKE2s";
this.module = "Hashing";
this.description = `Performs BLAKE2s hashing on the input.
<br><br>BLAKE2s is a flavour of the BLAKE cryptographic hash function that is optimized for 8- to 32-bit platforms and produces digests of any size between 1 and 32 bytes.
<br><br>Supports the use of an optional key.`;
this.infoURL = "https://wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Size",
"type": "option",
"value": ["256", "160", "128"]
}, {
"name": "Output Encoding",
"type": "option",
"value": ["Hex", "Base64", "Raw"]
},
{
"name": "Key",
"type": "toggleString",
"value": "",
"toggleValues": ["UTF8", "Decimal", "Base64", "Hex", "Latin1"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string} The input having been hashed with BLAKE2s in the encoding format speicifed.
*/
run(input, args) {
const [outSize, outFormat] = args;
let key = Utils.convertToByteArray(args[2].string || "", args[2].option);
if (key.length === 0) {
key = null;
} else if (key.length > 32) {
throw new OperationError(["Key cannot be greater than 32 bytes", "It is currently " + key.length + " bytes."].join("\n"));
}
input = new Uint8Array(input);
switch (outFormat) {
case "Hex":
return blakejs.blake2sHex(input, key, outSize / 8);
case "Base64":
return toBase64(blakejs.blake2s(input, key, outSize / 8));
case "Raw":
return Utils.arrayBufferToStr(blakejs.blake2s(input, key, outSize / 8).buffer);
default:
return new OperationError("Unsupported Output Type");
}
}
}
export default BLAKE2s;

View File

@@ -5,7 +5,7 @@
*/ */
import Operation from "../Operation"; import Operation from "../Operation";
import bsonjs from "bson"; import bson from "bson";
import OperationError from "../errors/OperationError"; import OperationError from "../errors/OperationError";
/** /**
@@ -36,8 +36,6 @@ class BSONDeserialise extends Operation {
run(input, args) { run(input, args) {
if (!input.byteLength) return ""; if (!input.byteLength) return "";
const bson = new bsonjs();
try { try {
const data = bson.deserialize(new Buffer(input)); const data = bson.deserialize(new Buffer(input));
return JSON.stringify(data, null, 2); return JSON.stringify(data, null, 2);

View File

@@ -5,7 +5,7 @@
*/ */
import Operation from "../Operation"; import Operation from "../Operation";
import bsonjs from "bson"; import bson from "bson";
import OperationError from "../errors/OperationError"; import OperationError from "../errors/OperationError";
/** /**
@@ -36,8 +36,6 @@ class BSONSerialise extends Operation {
run(input, args) { run(input, args) {
if (!input) return new ArrayBuffer(); if (!input) return new ArrayBuffer();
const bson = new bsonjs();
try { try {
const data = JSON.parse(input); const data = JSON.parse(input);
return bson.serialize(data).buffer; return bson.serialize(data).buffer;

View File

@@ -19,7 +19,7 @@ class Bcrypt extends Operation {
super(); super();
this.name = "Bcrypt"; this.name = "Bcrypt";
this.module = "Hashing"; this.module = "Crypto";
this.description = "bcrypt is a password hashing function designed by Niels Provos and David Mazi\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.<br><br>Enter the password in the input to generate its hash."; this.description = "bcrypt is a password hashing function designed by Niels Provos and David Mazi\xe8res, based on the Blowfish cipher, and presented at USENIX in 1999. Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count (rounds) can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.<br><br>Enter the password in the input to generate its hash.";
this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
this.inputType = "string"; this.inputType = "string";

View File

@@ -19,7 +19,7 @@ class BcryptCompare extends Operation {
super(); super();
this.name = "Bcrypt compare"; this.name = "Bcrypt compare";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation."; this.description = "Tests whether the input matches the given bcrypt hash. To test multiple possible passwords, use the 'Fork' operation.";
this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
this.inputType = "string"; this.inputType = "string";

View File

@@ -20,7 +20,7 @@ class BcryptParse extends Operation {
super(); super();
this.name = "Bcrypt parse"; this.name = "Bcrypt parse";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash."; this.description = "Parses a bcrypt hash to determine the number of rounds used, the salt, and the password hash.";
this.infoURL = "https://wikipedia.org/wiki/Bcrypt"; this.infoURL = "https://wikipedia.org/wiki/Bcrypt";
this.inputType = "string"; this.inputType = "string";

View File

@@ -0,0 +1,111 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
import { gaussianBlur } from "../lib/ImageManipulation";
/**
* Blur Image operation
*/
class BlurImage extends Operation {
/**
* BlurImage constructor
*/
constructor() {
super();
this.name = "Blur Image";
this.module = "Image";
this.description = "Applies a blur effect to the image.<br><br>Gaussian blur is much slower than fast blur, but produces better results.";
this.infoURL = "https://wikipedia.org/wiki/Gaussian_blur";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Amount",
type: "number",
value: 5,
min: 1
},
{
name: "Type",
type: "option",
value: ["Fast", "Gaussian"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [blurAmount, blurType] = args;
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
switch (blurType){
case "Fast":
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Fast blurring image...");
image.blur(blurAmount);
break;
case "Gaussian":
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Gaussian blurring image...");
image = gaussianBlur(image, blurAmount);
break;
}
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error blurring image. (${err})`);
}
}
/**
* Displays the blurred image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default BlurImage;

View File

@@ -0,0 +1,175 @@
/**
* Emulation of the Bombe machine.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {BombeMachine} from "../lib/Bombe";
import {ROTORS, ROTORS_FOURTH, REFLECTORS, Reflector} from "../lib/Enigma";
/**
* Bombe operation
*/
class Bombe extends Operation {
/**
* Bombe constructor
*/
constructor() {
super();
this.name = "Bombe";
this.module = "Default";
this.description = "Emulation of the Bombe machine used at Bletchley Park to attack Enigma, based on work by Polish and British cryptanalysts.<br><br>To run this you need to have a 'crib', which is some known plaintext for a chunk of the target ciphertext, and know the rotors used. (See the 'Bombe (multiple runs)' operation if you don't know the rotors.) The machine will suggest possible configurations of the Enigma. Each suggestion has the rotor start positions (left to right) and known plugboard pairs.<br><br>Choosing a crib: First, note that Enigma cannot encrypt a letter to itself, which allows you to rule out some positions for possible cribs. Secondly, the Bombe does not simulate the Enigma's middle rotor stepping. The longer your crib, the more likely a step happened within it, which will prevent the attack working. However, other than that, longer cribs are generally better. The attack produces a 'menu' which maps ciphertext letters to plaintext, and the goal is to produce 'loops': for example, with ciphertext ABC and crib CAB, we have the mappings A&lt;-&gt;C, B&lt;-&gt;A, and C&lt;-&gt;B, which produces a loop A-B-C-A. The more loops, the better the crib. The operation will output this: if your menu has too few loops or is too short, a large number of incorrect outputs will usually be produced. Try a different crib. If the menu seems good but the right answer isn't produced, your crib may be wrong, or you may have overlapped the middle rotor stepping - try a different crib.<br><br>Output is not sufficient to fully decrypt the data. You will have to recover the rest of the plugboard settings by inspection. And the ring position is not taken into account: this affects when the middle rotor steps. If your output is correct for a bit, and then goes wrong, adjust the ring and start position on the right-hand rotor together until the output improves. If necessary, repeat for the middle rotor.<br><br>By default this operation runs the checking machine, a manual process to verify the quality of Bombe stops, on each stop, discarding stops which fail. If you want to see how many times the hardware actually stops for a given input, disable the checking machine.<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.";
this.infoURL = "https://wikipedia.org/wiki/Bombe";
this.inputType = "string";
this.outputType = "JSON";
this.presentType = "html";
this.args = [
{
name: "Model",
type: "argSelector",
value: [
{
name: "3-rotor",
off: [1]
},
{
name: "4-rotor",
on: [1]
}
]
},
{
name: "Left-most (4th) rotor",
type: "editableOption",
value: ROTORS_FOURTH,
defaultIndex: 0
},
{
name: "Left-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 0
},
{
name: "Middle rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 1
},
{
name: "Right-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 2
},
{
name: "Reflector",
type: "editableOption",
value: REFLECTORS
},
{
name: "Crib",
type: "string",
value: ""
},
{
name: "Crib offset",
type: "number",
value: 0
},
{
name: "Use checking machine",
type: "boolean",
value: true
}
];
}
/**
* Format and send a status update message.
* @param {number} nLoops - Number of loops in the menu
* @param {number} nStops - How many stops so far
* @param {number} progress - Progress (as a float in the range 0..1)
*/
updateStatus(nLoops, nStops, progress) {
const msg = `Bombe run with ${nLoops} loop${nLoops === 1 ? "" : "s"} in menu (2+ desirable): ${nStops} stops, ${Math.floor(100 * progress)}% done`;
self.sendStatusMessage(msg);
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const model = args[0];
const reflectorstr = args[5];
let crib = args[6];
const offset = args[7];
const check = args[8];
const rotors = [];
for (let i=0; i<4; i++) {
if (i === 0 && model === "3-rotor") {
// No fourth rotor
continue;
}
let rstr = args[i + 1];
// The Bombe doesn't take stepping into account so we'll just ignore it here
if (rstr.includes("<")) {
rstr = rstr.split("<", 2)[0];
}
rotors.push(rstr);
}
// Rotors are handled in reverse
rotors.reverse();
if (crib.length === 0) {
throw new OperationError("Crib cannot be empty");
}
if (offset < 0) {
throw new OperationError("Offset cannot be negative");
}
// For symmetry with the Enigma op, for the input we'll just remove all invalid characters
input = input.replace(/[^A-Za-z]/g, "").toUpperCase();
crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase();
const ciphertext = input.slice(offset);
const reflector = new Reflector(reflectorstr);
let update;
if (ENVIRONMENT_IS_WORKER()) {
update = this.updateStatus;
} else {
update = undefined;
}
const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, check, update);
const result = bombe.run();
return {
nLoops: bombe.nLoops,
result: result
};
}
/**
* Displays the Bombe results in an HTML table
*
* @param {Object} output
* @param {number} output.nLoops
* @param {Array[]} output.result
* @returns {html}
*/
present(output) {
let html = `Bombe run on menu with ${output.nLoops} loop${output.nLoops === 1 ? "" : "s"} (2+ desirable). Note: Rotor positions are listed left to right and start at the beginning of the crib, and ignore stepping and the ring setting. Some plugboard settings are determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided.\n\n`;
html += "<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Rotor stops</th> <th>Partial plugboard</th> <th>Decryption preview</th></tr>\n";
for (const [setting, stecker, decrypt] of output.result) {
html += `<tr><td>${setting}</td> <td>${stecker}</td> <td>${decrypt}</td></tr>\n`;
}
html += "</table>";
return html;
}
}
export default Bombe;

View File

@@ -0,0 +1,72 @@
/**
* @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Bzip2 from "libbzip2-wasm";
/**
* Bzip2 Compress operation
*/
class Bzip2Compress extends Operation {
/**
* Bzip2Compress constructor
*/
constructor() {
super();
this.name = "Bzip2 Compress";
this.module = "Compression";
this.description = "Bzip2 is a compression library developed by Julian Seward (of GHC fame) that uses the Burrows-Wheeler algorithm. It only supports compressing single files and its compression is slow, however is more effective than Deflate (.gz & .zip).";
this.infoURL = "https://wikipedia.org/wiki/Bzip2";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.args = [
{
name: "Block size (100s of kb)",
type: "number",
value: 9,
min: 1,
max: 9
},
{
name: "Work factor",
type: "number",
value: 30
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {File}
*/
run(input, args) {
const [blockSize, workFactor] = args;
if (input.byteLength <= 0) {
throw new OperationError("Please provide an input.");
}
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Loading Bzip2...");
return new Promise((resolve, reject) => {
Bzip2().then(bzip2 => {
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Compressing data...");
const inpArray = new Uint8Array(input);
const bzip2cc = bzip2.compressBZ2(inpArray, blockSize, workFactor);
if (bzip2cc.error !== 0) {
reject(new OperationError(bzip2cc.error_msg));
} else {
const output = bzip2cc.output;
resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));
}
});
});
}
}
export default Bzip2Compress;

View File

@@ -1,12 +1,12 @@
/** /**
* @author n1474335 [n1474335@gmail.com] * @author Matt C [me@mitt.dev]
* @copyright Crown Copyright 2016 * @copyright Crown Copyright 2019
* @license Apache-2.0 * @license Apache-2.0
*/ */
import Operation from "../Operation"; import Operation from "../Operation";
import bzip2 from "../vendor/bzip2";
import OperationError from "../errors/OperationError"; import OperationError from "../errors/OperationError";
import Bzip2 from "libbzip2-wasm";
/** /**
* Bzip2 Decompress operation * Bzip2 Decompress operation
@@ -23,9 +23,15 @@ class Bzip2Decompress extends Operation {
this.module = "Compression"; this.module = "Compression";
this.description = "Decompresses data using the Bzip2 algorithm."; this.description = "Decompresses data using the Bzip2 algorithm.";
this.infoURL = "https://wikipedia.org/wiki/Bzip2"; this.infoURL = "https://wikipedia.org/wiki/Bzip2";
this.inputType = "byteArray"; this.inputType = "ArrayBuffer";
this.outputType = "string"; this.outputType = "ArrayBuffer";
this.args = []; this.args = [
{
name: "Use low-memory, slower decompression algorithm",
type: "boolean",
value: false
}
];
this.patterns = [ this.patterns = [
{ {
"match": "^\\x42\\x5a\\x68", "match": "^\\x42\\x5a\\x68",
@@ -41,14 +47,24 @@ class Bzip2Decompress extends Operation {
* @returns {string} * @returns {string}
*/ */
run(input, args) { run(input, args) {
const compressed = new Uint8Array(input); const [small] = args;
if (input.byteLength <= 0) {
try { throw new OperationError("Please provide an input.");
const bzip2Reader = bzip2.array(compressed);
return bzip2.simple(bzip2Reader);
} catch (err) {
throw new OperationError(err);
} }
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Loading Bzip2...");
return new Promise((resolve, reject) => {
Bzip2().then(bzip2 => {
if (ENVIRONMENT_IS_WORKER()) self.sendStatusMessage("Decompressing data...");
const inpArray = new Uint8Array(input);
const bzip2cc = bzip2.decompressBZ2(inpArray, small ? 1 : 0);
if (bzip2cc.error !== 0) {
reject(new OperationError(bzip2cc.error_msg));
} else {
const output = bzip2cc.output;
resolve(output.buffer.slice(output.byteOffset, output.byteLength + output.byteOffset));
}
});
});
} }
} }

View File

@@ -19,7 +19,7 @@ class CRC16Checksum extends Operation {
super(); super();
this.name = "CRC-16 Checksum"; this.name = "CRC-16 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961."; this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.";
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check"; this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";

View File

@@ -19,7 +19,7 @@ class CRC32Checksum extends Operation {
super(); super();
this.name = "CRC-32 Checksum"; this.name = "CRC-32 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975."; this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961; the 32-bit CRC function of Ethernet and many other standards is the work of several researchers and was published in 1975.";
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check"; this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";

View File

@@ -0,0 +1,157 @@
/**
* @author mshwed [m@ttshwed.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { toHexFast } from "../lib/Hex";
/**
* CRC-8 Checksum operation
*/
class CRC8Checksum extends Operation {
/**
* CRC8Checksum constructor
*/
constructor() {
super();
this.name = "CRC-8 Checksum";
this.module = "Crypto";
this.description = "A cyclic redundancy check (CRC) is an error-detecting code commonly used in digital networks and storage devices to detect accidental changes to raw data.<br><br>The CRC was invented by W. Wesley Peterson in 1961.";
this.infoURL = "https://wikipedia.org/wiki/Cyclic_redundancy_check";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "Algorithm",
"type": "option",
"value": [
"CRC-8",
"CRC-8/CDMA2000",
"CRC-8/DARC",
"CRC-8/DVB-S2",
"CRC-8/EBU",
"CRC-8/I-CODE",
"CRC-8/ITU",
"CRC-8/MAXIM",
"CRC-8/ROHC",
"CRC-8/WCDMA"
]
}
];
}
/**
* Generates the pre-computed lookup table for byte division
*
* @param polynomial
*/
calculateCRC8LookupTable(polynomial) {
const crc8Table = new Uint8Array(256);
let currentByte;
for (let i = 0; i < 256; i++) {
currentByte = i;
for (let bit = 0; bit < 8; bit++) {
if ((currentByte & 0x80) !== 0) {
currentByte <<= 1;
currentByte ^= polynomial;
} else {
currentByte <<= 1;
}
}
crc8Table[i] = currentByte;
}
return crc8Table;
}
/**
* Calculates the CRC-8 Checksum from an input
*
* @param {ArrayBuffer} input
* @param {number} polynomial
* @param {number} initializationValue
* @param {boolean} inputReflection
* @param {boolean} outputReflection
* @param {number} xorOut
*/
calculateCRC8(input, polynomial, initializationValue, inputReflection, outputReflection, xorOut) {
const crcSize = 8;
const crcTable = this.calculateCRC8LookupTable(polynomial);
let crc = initializationValue !== 0 ? initializationValue : 0;
let currentByte, position;
input = new Uint8Array(input);
for (const inputByte of input) {
currentByte = inputReflection ? this.reverseBits(inputByte, crcSize) : inputByte;
position = (currentByte ^ crc) & 255;
crc = crcTable[position];
}
crc = outputReflection ? this.reverseBits(crc, crcSize) : crc;
if (xorOut !== 0) crc = crc ^ xorOut;
return toHexFast(new Uint8Array([crc]));
}
/**
* Reverse the bits for a given input byte.
*
* @param {number} input
*/
reverseBits(input, hashSize) {
let reversedByte = 0;
for (let i = hashSize - 1; i >= 0; i--) {
reversedByte |= ((input & 1) << i);
input >>= 1;
}
return reversedByte;
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const algorithm = args[0];
switch (algorithm) {
case "CRC-8":
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x0);
case "CRC-8/CDMA2000":
return this.calculateCRC8(input, 0x9B, 0xFF, false, false, 0x0);
case "CRC-8/DARC":
return this.calculateCRC8(input, 0x39, 0x0, true, true, 0x0);
case "CRC-8/DVB-S2":
return this.calculateCRC8(input, 0xD5, 0x0, false, false, 0x0);
case "CRC-8/EBU":
return this.calculateCRC8(input, 0x1D, 0xFF, true, true, 0x0);
case "CRC-8/I-CODE":
return this.calculateCRC8(input, 0x1D, 0xFD, false, false, 0x0);
case "CRC-8/ITU":
return this.calculateCRC8(input, 0x7, 0x0, false, false, 0x55);
case "CRC-8/MAXIM":
return this.calculateCRC8(input, 0x31, 0x0, true, true, 0x0);
case "CRC-8/ROHC":
return this.calculateCRC8(input, 0x7, 0xFF, true, true, 0x0);
case "CRC-8/WCDMA":
return this.calculateCRC8(input, 0x9B, 0x0, true, true, 0x0);
default:
throw new OperationError("Unknown checksum algorithm");
}
}
}
export default CRC8Checksum;

View File

@@ -19,7 +19,7 @@ class CTPH extends Operation {
super(); super();
this.name = "CTPH"; this.name = "CTPH";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'."; this.description = "Context Triggered Piecewise Hashing, also called Fuzzy Hashing, can match inputs that have homologies. Such inputs have sequences of identical bytes in the same order, although bytes in between these sequences may be different in both content and length.<br><br>CTPH was originally based on the work of Dr. Andrew Tridgell and a spam email detector called SpamSum. This method was adapted by Jesse Kornblum and published at the DFRWS conference in 2006 in a paper 'Identifying Almost Identical Files Using Context Triggered Piecewise Hashing'.";
this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing"; this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing";
this.inputType = "string"; this.inputType = "string";

View File

@@ -22,7 +22,7 @@ class CompareCTPHHashes extends Operation {
super(); super();
this.name = "Compare CTPH hashes"; this.name = "Compare CTPH hashes";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; this.description = "Compares two Context Triggered Piecewise Hashing (CTPH) fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing"; this.infoURL = "https://forensicswiki.org/wiki/Context_Triggered_Piecewise_Hashing";
this.inputType = "string"; this.inputType = "string";

View File

@@ -22,7 +22,7 @@ class CompareSSDEEPHashes extends Operation {
super(); super();
this.name = "Compare SSDEEP hashes"; this.name = "Compare SSDEEP hashes";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100."; this.description = "Compares two SSDEEP fuzzy hashes to determine the similarity between them on a scale of 0 to 100.";
this.infoURL = "https://forensicswiki.org/wiki/Ssdeep"; this.infoURL = "https://forensicswiki.org/wiki/Ssdeep";
this.inputType = "string"; this.inputType = "string";

View File

@@ -0,0 +1,161 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64.mjs";
import jimp from "jimp";
/**
* Contain Image operation
*/
class ContainImage extends Operation {
/**
* ContainImage constructor
*/
constructor() {
super();
this.name = "Contain Image";
this.module = "Image";
this.description = "Scales an image to the specified width and height, maintaining the aspect ratio. The image may be letterboxed.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Width",
type: "number",
value: 100,
min: 1
},
{
name: "Height",
type: "number",
value: 100,
min: 1
},
{
name: "Horizontal align",
type: "option",
value: [
"Left",
"Center",
"Right"
],
defaultIndex: 1
},
{
name: "Vertical align",
type: "option",
value: [
"Top",
"Middle",
"Bottom"
],
defaultIndex: 1
},
{
name: "Resizing algorithm",
type: "option",
value: [
"Nearest Neighbour",
"Bilinear",
"Bicubic",
"Hermite",
"Bezier"
],
defaultIndex: 1
},
{
name: "Opaque background",
type: "boolean",
value: true
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [width, height, hAlign, vAlign, alg, opaqueBg] = args;
const resizeMap = {
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
"Bilinear": jimp.RESIZE_BILINEAR,
"Bicubic": jimp.RESIZE_BICUBIC,
"Hermite": jimp.RESIZE_HERMITE,
"Bezier": jimp.RESIZE_BEZIER
};
const alignMap = {
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
"Top": jimp.VERTICAL_ALIGN_TOP,
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Containing image...");
image.contain(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
if (opaqueBg) {
const newImage = await jimp.read(width, height, 0x000000FF);
newImage.blit(image, 0, 0);
image = newImage;
}
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error containing image. (${err})`);
}
}
/**
* Displays the contained image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default ContainImage;

View File

@@ -0,0 +1,95 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates";
/**
* Convert co-ordinate format operation
*/
class ConvertCoordinateFormat extends Operation {
/**
* ConvertCoordinateFormat constructor
*/
constructor() {
super();
this.name = "Convert co-ordinate format";
this.module = "Hashing";
this.description = "Converts geographical coordinates between different formats.<br><br>Supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly.";
this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Input Format",
"type": "option",
"value": ["Auto"].concat(FORMATS)
},
{
"name": "Input Delimiter",
"type": "option",
"value": [
"Auto",
"Direction Preceding",
"Direction Following",
"\\n",
"Comma",
"Semi-colon",
"Colon"
]
},
{
"name": "Output Format",
"type": "option",
"value": FORMATS
},
{
"name": "Output Delimiter",
"type": "option",
"value": [
"Space",
"\\n",
"Comma",
"Semi-colon",
"Colon"
]
},
{
"name": "Include Compass Directions",
"type": "option",
"value": [
"None",
"Before",
"After"
]
},
{
"name": "Precision",
"type": "number",
"value": 3
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (input.replace(/[\s+]/g, "") !== "") {
const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args;
const result = convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision);
return result;
} else {
return input;
}
}
}
export default ConvertCoordinateFormat;

View File

@@ -0,0 +1,143 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Convert Image Format operation
*/
class ConvertImageFormat extends Operation {
/**
* ConvertImageFormat constructor
*/
constructor() {
super();
this.name = "Convert Image Format";
this.module = "Image";
this.description = "Converts an image between different formats. Supported formats:<br><ul><li>Joint Photographic Experts Group (JPEG)</li><li>Portable Network Graphics (PNG)</li><li>Bitmap (BMP)</li><li>Tagged Image File Format (TIFF)</li></ul><br>Note: GIF files are supported for input, but cannot be outputted.";
this.infoURL = "https://wikipedia.org/wiki/Image_file_formats";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Output Format",
type: "option",
value: [
"JPEG",
"PNG",
"BMP",
"TIFF"
]
},
{
name: "JPEG Quality",
type: "number",
value: 80,
min: 1,
max: 100
},
{
name: "PNG Filter Type",
type: "option",
value: [
"Auto",
"None",
"Sub",
"Up",
"Average",
"Paeth"
]
},
{
name: "PNG Deflate Level",
type: "number",
value: 9,
min: 0,
max: 9
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [format, jpegQuality, pngFilterType, pngDeflateLevel] = args;
const formatMap = {
"JPEG": jimp.MIME_JPEG,
"PNG": jimp.MIME_PNG,
"BMP": jimp.MIME_BMP,
"TIFF": jimp.MIME_TIFF
};
const pngFilterMap = {
"Auto": jimp.PNG_FILTER_AUTO,
"None": jimp.PNG_FILTER_NONE,
"Sub": jimp.PNG_FILTER_SUB,
"Up": jimp.PNG_FILTER_UP,
"Average": jimp.PNG_FILTER_AVERAGE,
"Paeth": jimp.PNG_FILTER_PATH // Incorrect spelling in Jimp library
};
const mime = formatMap[format];
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file format.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error opening image file. (${err})`);
}
try {
switch (format) {
case "JPEG":
image.quality(jpegQuality);
break;
case "PNG":
image.filterType(pngFilterMap[pngFilterType]);
image.deflateLevel(pngDeflateLevel);
break;
}
const imageBuffer = await image.getBufferAsync(mime);
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error converting image format. (${err})`);
}
}
/**
* Displays the converted image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default ConvertImageFormat;

View File

@@ -0,0 +1,149 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64.mjs";
import jimp from "jimp";
/**
* Cover Image operation
*/
class CoverImage extends Operation {
/**
* CoverImage constructor
*/
constructor() {
super();
this.name = "Cover Image";
this.module = "Image";
this.description = "Scales the image to the given width and height, keeping the aspect ratio. The image may be clipped.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Width",
type: "number",
value: 100,
min: 1
},
{
name: "Height",
type: "number",
value: 100,
min: 1
},
{
name: "Horizontal align",
type: "option",
value: [
"Left",
"Center",
"Right"
],
defaultIndex: 1
},
{
name: "Vertical align",
type: "option",
value: [
"Top",
"Middle",
"Bottom"
],
defaultIndex: 1
},
{
name: "Resizing algorithm",
type: "option",
value: [
"Nearest Neighbour",
"Bilinear",
"Bicubic",
"Hermite",
"Bezier"
],
defaultIndex: 1
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [width, height, hAlign, vAlign, alg] = args;
const resizeMap = {
"Nearest Neighbour": jimp.RESIZE_NEAREST_NEIGHBOR,
"Bilinear": jimp.RESIZE_BILINEAR,
"Bicubic": jimp.RESIZE_BICUBIC,
"Hermite": jimp.RESIZE_HERMITE,
"Bezier": jimp.RESIZE_BEZIER
};
const alignMap = {
"Left": jimp.HORIZONTAL_ALIGN_LEFT,
"Center": jimp.HORIZONTAL_ALIGN_CENTER,
"Right": jimp.HORIZONTAL_ALIGN_RIGHT,
"Top": jimp.VERTICAL_ALIGN_TOP,
"Middle": jimp.VERTICAL_ALIGN_MIDDLE,
"Bottom": jimp.VERTICAL_ALIGN_BOTTOM
};
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Covering image...");
image.cover(width, height, alignMap[hAlign] | alignMap[vAlign], resizeMap[alg]);
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error covering image. (${err})`);
}
}
/**
* Displays the covered image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default CoverImage;

View File

@@ -0,0 +1,150 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64.mjs";
import jimp from "jimp";
/**
* Crop Image operation
*/
class CropImage extends Operation {
/**
* CropImage constructor
*/
constructor() {
super();
this.name = "Crop Image";
this.module = "Image";
this.description = "Crops an image to the specified region, or automatically crops edges.<br><br><b><u>Autocrop</u></b><br>Automatically crops same-colour borders from the image.<br><br><u>Autocrop tolerance</u><br>A percentage value for the tolerance of colour difference between pixels.<br><br><u>Only autocrop frames</u><br>Only crop real frames (all sides must have the same border)<br><br><u>Symmetric autocrop</u><br>Force autocrop to be symmetric (top/bottom and left/right are cropped by the same amount)<br><br><u>Autocrop keep border</u><br>The number of pixels of border to leave around the image.";
this.infoURL = "https://wikipedia.org/wiki/Cropping_(image)";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "X Position",
type: "number",
value: 0,
min: 0
},
{
name: "Y Position",
type: "number",
value: 0,
min: 0
},
{
name: "Width",
type: "number",
value: 10,
min: 1
},
{
name: "Height",
type: "number",
value: 10,
min: 1
},
{
name: "Autocrop",
type: "boolean",
value: false
},
{
name: "Autocrop tolerance (%)",
type: "number",
value: 0.02,
min: 0,
max: 100,
step: 0.01
},
{
name: "Only autocrop frames",
type: "boolean",
value: true
},
{
name: "Symmetric autocrop",
type: "boolean",
value: false
},
{
name: "Autocrop keep border (px)",
type: "number",
value: 0,
min: 0
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [xPos, yPos, width, height, autocrop, autoTolerance, autoFrames, autoSymmetric, autoBorder] = args;
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Cropping image...");
if (autocrop) {
image.autocrop({
tolerance: (autoTolerance / 100),
cropOnlyFrames: autoFrames,
cropSymmetric: autoSymmetric,
leaveBorder: autoBorder
});
} else {
image.crop(xPos, yPos, width, height);
}
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error cropping image. (${err})`);
}
}
/**
* Displays the cropped image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default CropImage;

View File

@@ -0,0 +1,125 @@
/**
* @author h345983745
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* DNS over HTTPS operation
*/
class DNSOverHTTPS extends Operation {
/**
* DNSOverHTTPS constructor
*/
constructor() {
super();
this.name = "DNS over HTTPS";
this.module = "Default";
this.description = [
"Takes a single domain name and performs a DNS lookup using DNS over HTTPS.",
"<br><br>",
"By default, <a href='https://developers.cloudflare.com/1.1.1.1/dns-over-https/'>Cloudflare</a> and <a href='https://developers.google.com/speed/public-dns/docs/dns-over-https'>Google</a> DNS over HTTPS services are supported.",
"<br><br>",
"Can be used with any service that supports the GET parameters <code>name</code> and <code>type</code>."
].join("\n");
this.infoURL = "https://wikipedia.org/wiki/DNS_over_HTTPS";
this.inputType = "string";
this.outputType = "JSON";
this.manualBake = true;
this.args = [
{
name: "Resolver",
type: "editableOption",
value: [
{
name: "Google",
value: "https://dns.google.com/resolve"
},
{
name: "Cloudflare",
value: "https://cloudflare-dns.com/dns-query"
}
]
},
{
name: "Request Type",
type: "option",
value: [
"A",
"AAAA",
"TXT",
"MX",
"DNSKEY",
"NS"
]
},
{
name: "Answer Data Only",
type: "boolean",
value: false
},
{
name: "Validate DNSSEC",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {JSON}
*/
run(input, args) {
const [resolver, requestType, justAnswer, DNSSEC] = args;
let url = URL;
try {
url = new URL(resolver);
} catch (error) {
throw new OperationError(error.toString() +
"\n\nThis error could be caused by one of the following:\n" +
" - An invalid Resolver URL\n");
}
const params = {name: input, type: requestType, cd: DNSSEC};
url.search = new URLSearchParams(params);
return fetch(url, {headers: {"accept": "application/dns-json"}}).then(response => {
return response.json();
}).then(data => {
if (justAnswer) {
return extractData(data.Answer);
}
return data;
}).catch(e => {
throw new OperationError(`Error making request to ${url}\n${e.toString()}`);
});
}
}
/**
* Construct an array of just data from a DNS Answer section
*
* @private
* @param {JSON} data
* @returns {JSON}
*/
function extractData(data) {
if (typeof(data) == "undefined"){
return [];
} else {
const dataValues = [];
data.forEach(element => {
dataValues.push(element.data);
});
return dataValues;
}
}
export default DNSOverHTTPS;

View File

@@ -20,7 +20,7 @@ class DecodeText extends Operation {
super(); super();
this.name = "Decode text"; this.name = "Decode text";
this.module = "CharEnc"; this.module = "Encodings";
this.description = [ this.description = [
"Decodes text from the chosen character encoding.", "Decodes text from the chosen character encoding.",
"<br><br>", "<br><br>",

View File

@@ -5,7 +5,8 @@
*/ */
import Operation from "../Operation"; import Operation from "../Operation";
import Magic from "../lib/Magic"; import {detectFileType} from "../lib/FileType";
import {FILE_SIGNATURES} from "../lib/FileSignatures";
/** /**
* Detect File Type operation * Detect File Type operation
@@ -24,7 +25,13 @@ class DetectFileType extends Operation {
this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures"; this.infoURL = "https://wikipedia.org/wiki/List_of_file_signatures";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";
this.outputType = "string"; this.outputType = "string";
this.args = []; this.args = Object.keys(FILE_SIGNATURES).map(cat => {
return {
name: cat,
type: "boolean",
value: true
};
});
} }
/** /**
@@ -34,17 +41,27 @@ class DetectFileType extends Operation {
*/ */
run(input, args) { run(input, args) {
const data = new Uint8Array(input), const data = new Uint8Array(input),
type = Magic.magicFileType(data); categories = [];
if (!type) { args.forEach((cat, i) => {
if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);
});
const types = detectFileType(data, categories);
if (!types.length) {
return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?";
} else { } else {
let output = "File extension: " + type.ext + "\n" + let output = "";
"MIME type: " + type.mime;
if (type.desc && type.desc.length) { types.forEach(type => {
output += "\nDescription: " + type.desc; output += "File extension: " + type.extension + "\n" +
} "MIME type: " + type.mime + "\n";
if (type.description && type.description.length) {
output += "\nDescription: " + type.description + "\n";
}
});
return output; return output;
} }

View File

@@ -0,0 +1,86 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Image Dither operation
*/
class DitherImage extends Operation {
/**
* DitherImage constructor
*/
constructor() {
super();
this.name = "Dither Image";
this.module = "Image";
this.description = "Apply a dither effect to an image.";
this.infoURL = "https://wikipedia.org/wiki/Dither";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Applying dither to image...");
image.dither565();
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error applying dither to image. (${err})`);
}
}
/**
* Displays the dithered image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default DitherImage;

View File

@@ -43,7 +43,7 @@ class Divide extends Operation {
*/ */
run(input, args) { run(input, args) {
const val = div(createNumArray(input, args[0])); const val = div(createNumArray(input, args[0]));
return val instanceof BigNumber ? val : new BigNumber(NaN); return BigNumber.isBigNumber(val) ? val : new BigNumber(NaN);
} }
} }

View File

@@ -20,7 +20,7 @@ class EncodeText extends Operation {
super(); super();
this.name = "Encode text"; this.name = "Encode text";
this.module = "CharEnc"; this.module = "Encodings";
this.description = [ this.description = [
"Encodes text into the chosen character encoding.", "Encodes text into the chosen character encoding.",
"<br><br>", "<br><br>",

View File

@@ -0,0 +1,214 @@
/**
* Emulation of the Enigma machine.
*
* @author s2224834
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import {ROTORS, LETTERS, ROTORS_FOURTH, REFLECTORS, Rotor, Reflector, Plugboard, EnigmaMachine} from "../lib/Enigma";
/**
* Enigma operation
*/
class Enigma extends Operation {
/**
* Enigma constructor
*/
constructor() {
super();
this.name = "Enigma";
this.module = "Default";
this.description = "Encipher/decipher with the WW2 Enigma machine.<br><br>Enigma was used by the German military, among others, around the WW2 era as a portable cipher machine to protect sensitive military, diplomatic and commercial communications.<br><br>The standard set of German military rotors and reflectors are provided. To configure the plugboard, enter a string of connected pairs of letters, e.g. <code>AB CD EF</code> connects A to B, C to D, and E to F. This is also used to create your own reflectors. To create your own rotor, enter the letters that the rotor maps A to Z to, in order, optionally followed by <code>&lt;</code> then a list of stepping points.<br>This is deliberately fairly permissive with rotor placements etc compared to a real Enigma (on which, for example, a four-rotor Enigma uses only the thin reflectors and the beta or gamma rotor in the 4th slot).<br><br>More detailed descriptions of the Enigma, Typex and Bombe operations <a href='https://github.com/gchq/CyberChef/wiki/Enigma,-the-Bombe,-and-Typex'>can be found here</a>.";
this.infoURL = "https://wikipedia.org/wiki/Enigma_machine";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Model",
type: "argSelector",
value: [
{
name: "3-rotor",
off: [1, 2, 3]
},
{
name: "4-rotor",
on: [1, 2, 3]
}
]
},
{
name: "Left-most (4th) rotor",
type: "editableOption",
value: ROTORS_FOURTH,
defaultIndex: 0
},
{
name: "Left-most rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Left-most rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Left-hand rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 0
},
{
name: "Left-hand rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Left-hand rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Middle rotor",
type: "editableOption",
value: ROTORS,
defaultIndex: 1
},
{
name: "Middle rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Middle rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Right-hand rotor",
type: "editableOption",
value: ROTORS,
// Default config is the rotors I-III *left to right*
defaultIndex: 2
},
{
name: "Right-hand rotor ring setting",
type: "option",
value: LETTERS
},
{
name: "Right-hand rotor initial value",
type: "option",
value: LETTERS
},
{
name: "Reflector",
type: "editableOption",
value: REFLECTORS
},
{
name: "Plugboard",
type: "string",
value: ""
},
{
name: "Strict output",
hint: "Remove non-alphabet letters and group output",
type: "boolean",
value: true
},
];
}
/**
* Helper - for ease of use rotors are specified as a single string; this
* method breaks the spec string into wiring and steps parts.
*
* @param {string} rotor - Rotor specification string.
* @param {number} i - For error messages, the number of this rotor.
* @returns {string[]}
*/
parseRotorStr(rotor, i) {
if (rotor === "") {
throw new OperationError(`Rotor ${i} must be provided.`);
}
if (!rotor.includes("<")) {
return [rotor, ""];
}
return rotor.split("<", 2);
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const model = args[0];
const reflectorstr = args[13];
const plugboardstr = args[14];
const removeOther = args[15];
const rotors = [];
for (let i=0; i<4; i++) {
if (i === 0 && model === "3-rotor") {
// Skip the 4th rotor settings
continue;
}
const [rotorwiring, rotorsteps] = this.parseRotorStr(args[i*3 + 1], 1);
rotors.push(new Rotor(rotorwiring, rotorsteps, args[i*3 + 2], args[i*3 + 3]));
}
// Rotors are handled in reverse
rotors.reverse();
const reflector = new Reflector(reflectorstr);
const plugboard = new Plugboard(plugboardstr);
if (removeOther) {
input = input.replace(/[^A-Za-z]/g, "");
}
const enigma = new EnigmaMachine(rotors, reflector, plugboard);
let result = enigma.crypt(input);
if (removeOther) {
// Five character cipher groups is traditional
result = result.replace(/([A-Z]{5})(?!$)/g, "$1 ");
}
return result;
}
/**
* Highlight Enigma
* This is only possible if we're passing through non-alphabet characters.
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlight(pos, args) {
if (args[13] === false) {
return pos;
}
}
/**
* Highlight Enigma in reverse
*
* @param {Object[]} pos
* @param {number} pos[].start
* @param {number} pos[].end
* @param {Object[]} args
* @returns {Object[]} pos
*/
highlightReverse(pos, args) {
if (args[13] === false) {
return pos;
}
}
}
export default Enigma;

View File

@@ -4,8 +4,13 @@
* @license Apache-2.0 * @license Apache-2.0
*/ */
import * as d3temp from "d3";
import * as nodomtemp from "nodom";
import Operation from "../Operation"; import Operation from "../Operation";
import Utils from "../Utils";
const d3 = d3temp.default ? d3temp.default : d3temp;
const nodom = nodomtemp.default ? nodomtemp.default: nodomtemp;
/** /**
* Entropy operation * Entropy operation
@@ -19,30 +24,45 @@ class Entropy extends Operation {
super(); super();
this.name = "Entropy"; this.name = "Entropy";
this.module = "Default"; this.module = "Charts";
this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5."; this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5.";
this.infoURL = "https://wikipedia.org/wiki/Entropy_(information_theory)"; this.infoURL = "https://wikipedia.org/wiki/Entropy_(information_theory)";
this.inputType = "byteArray"; this.inputType = "ArrayBuffer";
this.outputType = "number"; this.outputType = "json";
this.presentType = "html"; this.presentType = "html";
this.args = []; this.args = [
{
"name": "Visualisation",
"type": "option",
"value": ["Shannon scale", "Histogram (Bar)", "Histogram (Line)", "Curve", "Image"]
}
];
} }
/** /**
* @param {byteArray} input * Calculates the frequency of bytes in the input.
* @param {Object[]} args *
* @param {Uint8Array} input
* @returns {number} * @returns {number}
*/ */
run(input, args) { calculateShannonEntropy(input) {
const prob = [], const prob = [],
uniques = input.unique(), occurrences = new Array(256).fill(0);
str = Utils.byteArrayToChars(input);
let i;
for (i = 0; i < uniques.length; i++) { // Count occurrences of each byte in the input
prob.push(str.count(Utils.chr(uniques[i])) / input.length); let i;
for (i = 0; i < input.length; i++) {
occurrences[input[i]]++;
} }
// Store probability list
for (i = 0; i < occurrences.length; i++) {
if (occurrences[i] > 0) {
prob.push(occurrences[i] / input.length);
}
}
// Calculate Shannon entropy
let entropy = 0, let entropy = 0,
p; p;
@@ -54,44 +74,357 @@ class Entropy extends Operation {
return -entropy; return -entropy;
} }
/**
* Calculates the scanning entropy of the input
*
* @param {Uint8Array} inputBytes
* @returns {Object}
*/
calculateScanningEntropy(inputBytes) {
const entropyData = [];
const binWidth = inputBytes.length < 256 ? 8 : 256;
for (let bytePos = 0; bytePos < inputBytes.length; bytePos += binWidth) {
const block = inputBytes.slice(bytePos, bytePos+binWidth);
entropyData.push(this.calculateShannonEntropy(block));
}
return { entropyData, binWidth };
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {object} svg
* @param {function} xScale
* @param {function} yScale
* @param {integer} svgHeight
* @param {integer} svgWidth
* @param {object} margins
* @param {string} xTitle
* @param {string} yTitle
*/
createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, title, xTitle, yTitle) {
// Axes
const yAxis = d3.axisLeft()
.scale(yScale);
const xAxis = d3.axisBottom()
.scale(xScale);
svg.append("g")
.attr("transform", `translate(0, ${svgHeight - margins.bottom})`)
.call(xAxis);
svg.append("g")
.attr("transform", `translate(${margins.left},0)`)
.call(yAxis);
// Axes labels
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margins.left)
.attr("x", 0 - (svgHeight / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text(yTitle);
svg.append("text")
.attr("transform", `translate(${svgWidth / 2}, ${svgHeight - margins.bottom + 40})`)
.style("text-anchor", "middle")
.text(xTitle);
// Add title
svg.append("text")
.attr("transform", `translate(${svgWidth / 2}, ${margins.top - 10})`)
.style("text-anchor", "middle")
.text(title);
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {Uint8Array} inputBytes
* @returns {number[]}
*/
calculateByteFrequency(inputBytes) {
const freq = new Array(256).fill(0);
if (inputBytes.length === 0) return freq;
// Count occurrences of each byte in the input
let i;
for (i = 0; i < inputBytes.length; i++) {
freq[inputBytes[i]]++;
}
for (i = 0; i < freq.length; i++) {
freq[i] = freq[i] / inputBytes.length;
}
return freq;
}
/**
* Calculates the frequency of bytes in the input.
*
* @param {number[]} byteFrequency
* @returns {HTML}
*/
createByteFrequencyLineHistogram(byteFrequency) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yScale = d3.scaleLinear()
.domain([0, d3.max(byteFrequency, d => d)])
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, byteFrequency.length - 1])
.range([margins.left, svgWidth - margins.right]);
const line = d3.line()
.x((_, i) => xScale(i))
.y(d => yScale(d))
.curve(d3.curveMonotoneX);
svg.append("path")
.datum(byteFrequency)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("d", line);
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency");
return svg._groups[0][0].outerHTML;
}
/**
* Creates a byte frequency histogram
*
* @param {number[]} byteFrequency
* @returns {HTML}
*/
createByteFrequencyBarHistogram(byteFrequency) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500,
binWidth = 1;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yExtent = d3.extent(byteFrequency, d => d);
const yScale = d3.scaleLinear()
.domain(yExtent)
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, byteFrequency.length - 1])
.range([margins.left - binWidth, svgWidth - margins.right]);
svg.selectAll("rect")
.data(byteFrequency)
.enter().append("rect")
.attr("x", (_, i) => xScale(i) + binWidth)
.attr("y", dataPoint => yScale(dataPoint))
.attr("width", binWidth)
.attr("height", dataPoint => yScale(yExtent[0]) - yScale(dataPoint))
.attr("fill", "blue");
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "", "Byte", "Byte Frequency");
return svg._groups[0][0].outerHTML;
}
/**
* Creates a byte frequency histogram
*
* @param {number[]} entropyData
* @returns {HTML}
*/
createEntropyCurve(entropyData) {
const margins = { top: 30, right: 20, bottom: 50, left: 30 };
const svgWidth = 500,
svgHeight = 500;
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const yScale = d3.scaleLinear()
.domain([0, d3.max(entropyData, d => d)])
.range([svgHeight - margins.bottom, margins.top]);
const xScale = d3.scaleLinear()
.domain([0, entropyData.length])
.range([margins.left, svgWidth - margins.right]);
const line = d3.line()
.x((_, i) => xScale(i))
.y(d => yScale(d))
.curve(d3.curveMonotoneX);
if (entropyData.length > 0) {
svg.append("path")
.datum(entropyData)
.attr("d", line);
svg.selectAll("path").attr("fill", "none").attr("stroke", "steelblue");
}
this.createAxes(svg, xScale, yScale, svgHeight, svgWidth, margins, "Scanning Entropy", "Block", "Entropy");
return svg._groups[0][0].outerHTML;
}
/**
* Creates an image representation of the entropy
*
* @param {number[]} entropyData
* @returns {HTML}
*/
createEntropyImage(entropyData) {
const svgHeight = 100,
svgWidth = 100,
cellSize = 1,
nodes = [];
for (let i = 0; i < entropyData.length; i++) {
nodes.push({
x: i % svgWidth,
y: Math.floor(i / svgWidth),
entropy: entropyData[i]
});
}
const document = new nodom.Document();
let svg = document.createElement("svg");
svg = d3.select(svg)
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
const greyScale = d3.scaleLinear()
.domain([0, d3.max(entropyData, d => d)])
.range(["#000000", "#FFFFFF"])
.interpolate(d3.interpolateRgb);
svg
.selectAll("rect")
.data(nodes)
.enter().append("rect")
.attr("x", d => d.x * cellSize)
.attr("y", d => d.y * cellSize)
.attr("width", cellSize)
.attr("height", cellSize)
.style("fill", d => greyScale(d.entropy));
return svg._groups[0][0].outerHTML;
}
/** /**
* Displays the entropy as a scale bar for web apps. * Displays the entropy as a scale bar for web apps.
* *
* @param {number} entropy * @param {number} entropy
* @returns {html} * @returns {HTML}
*/ */
present(entropy) { createShannonEntropyVisualization(entropy) {
return `Shannon entropy: ${entropy} return `Shannon entropy: ${entropy}
<br><canvas id='chart-area'></canvas><br> <br><canvas id='chart-area'></canvas><br>
- 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string. - 0 represents no randomness (i.e. all the bytes in the data have the same value) whereas 8, the maximum, represents a completely random string.
- Standard English text usually falls somewhere between 3.5 and 5. - Standard English text usually falls somewhere between 3.5 and 5.
- Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5. - Properly encrypted or compressed data of a reasonable length should have an entropy of over 7.5.
The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections. The following results show the entropy of chunks of the input data. Chunks with particularly high entropy could suggest encrypted or compressed sections.
<br><script> <br><script>
var canvas = document.getElementById("chart-area"), var canvas = document.getElementById("chart-area"),
parentRect = canvas.parentNode.getBoundingClientRect(), parentRect = canvas.parentNode.getBoundingClientRect(),
entropy = ${entropy}, entropy = ${entropy},
height = parentRect.height * 0.25; height = parentRect.height * 0.25;
canvas.width = parentRect.width * 0.95; canvas.width = parentRect.width * 0.95;
canvas.height = height > 150 ? 150 : height; canvas.height = height > 150 ? 150 : height;
CanvasComponents.drawScaleBar(canvas, entropy, 8, [ CanvasComponents.drawScaleBar(canvas, entropy, 8, [
{ {
label: "English text", label: "English text",
min: 3.5, min: 3.5,
max: 5 max: 5
},{ },{
label: "Encrypted/compressed", label: "Encrypted/compressed",
min: 7.5, min: 7.5,
max: 8 max: 8
} }
]); ]);
</script>`; </script>`;
} }
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {json}
*/
run(input, args) {
const visualizationType = args[0];
input = new Uint8Array(input);
switch (visualizationType) {
case "Histogram (Bar)":
case "Histogram (Line)":
return this.calculateByteFrequency(input);
case "Curve":
case "Image":
return this.calculateScanningEntropy(input).entropyData;
case "Shannon scale":
default:
return this.calculateShannonEntropy(input);
}
}
/**
* Displays the entropy in a visualisation for web apps.
*
* @param {json} entropyData
* @param {Object[]} args
* @returns {html}
*/
present(entropyData, args) {
const visualizationType = args[0];
switch (visualizationType) {
case "Histogram (Bar)":
return this.createByteFrequencyBarHistogram(entropyData);
case "Histogram (Line)":
return this.createByteFrequencyLineHistogram(entropyData);
case "Curve":
return this.createEntropyCurve(entropyData);
case "Image":
return this.createEntropyImage(entropyData);
case "Shannon scale":
default:
return this.createShannonEntropyVisualization(entropyData);
}
}
} }
export default Entropy; export default Entropy;

View File

@@ -0,0 +1,100 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import {scanForFileTypes, extractFile} from "../lib/FileType";
import {FILE_SIGNATURES} from "../lib/FileSignatures";
/**
* Extract Files operation
*/
class ExtractFiles extends Operation {
/**
* ExtractFiles constructor
*/
constructor() {
super();
this.name = "Extract Files";
this.module = "Default";
this.description = "Performs file carving to attempt to extract files from the input.<br><br>This operation is currently capable of carving out the following formats:<ul><li>JPG</li><li>EXE</li><li>ZIP</li><li>PDF</li><li>PNG</li><li>BMP</li><li>FLV</li><li>RTF</li><li>DOCX, PPTX, XLSX</li><li>EPUB</li><li>GZIP</li><li>ZLIB</li><li>ELF, BIN, AXF, O, PRX, SO</li></ul>";
this.infoURL = "https://forensicswiki.org/wiki/File_Carving";
this.inputType = "ArrayBuffer";
this.outputType = "List<File>";
this.presentType = "html";
this.args = Object.keys(FILE_SIGNATURES).map(cat => {
return {
name: cat,
type: "boolean",
value: cat === "Miscellaneous" ? false : true
};
}).concat([
{
name: "Ignore failed extractions",
type: "boolean",
value: "true"
}
]);
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {List<File>}
*/
run(input, args) {
const bytes = new Uint8Array(input),
categories = [],
ignoreFailedExtractions = args.pop(1);
args.forEach((cat, i) => {
if (cat) categories.push(Object.keys(FILE_SIGNATURES)[i]);
});
// Scan for embedded files
const detectedFiles = scanForFileTypes(bytes, categories);
// Extract each file that we support
const files = [];
const errors = [];
detectedFiles.forEach(detectedFile => {
try {
files.push(extractFile(bytes, detectedFile.fileDetails, detectedFile.offset));
} catch (err) {
if (!ignoreFailedExtractions && err.message.indexOf("No extraction algorithm available") < 0) {
errors.push(
`Error while attempting to extract ${detectedFile.fileDetails.name} ` +
`at offset ${detectedFile.offset}:\n` +
`${err.message}`
);
}
}
});
if (errors.length) {
throw new OperationError(errors.join("\n\n"));
}
return files;
}
/**
* Displays the files in HTML for web apps.
*
* @param {File[]} files
* @returns {html}
*/
async present(files) {
return await Utils.displayFilesAsHTML(files);
}
}
export default ExtractFiles;

View File

@@ -19,7 +19,7 @@ class Fletcher16Checksum extends Operation {
super(); super();
this.name = "Fletcher-16 Checksum"; this.name = "Fletcher-16 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16"; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-16";
this.inputType = "byteArray"; this.inputType = "byteArray";

View File

@@ -19,7 +19,7 @@ class Fletcher32Checksum extends Operation {
super(); super();
this.name = "Fletcher-32 Checksum"; this.name = "Fletcher-32 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32"; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-32";
this.inputType = "byteArray"; this.inputType = "byteArray";

View File

@@ -19,7 +19,7 @@ class Fletcher64Checksum extends Operation {
super(); super();
this.name = "Fletcher-64 Checksum"; this.name = "Fletcher-64 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64"; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum#Fletcher-64";
this.inputType = "byteArray"; this.inputType = "byteArray";

View File

@@ -19,7 +19,7 @@ class Fletcher8Checksum extends Operation {
super(); super();
this.name = "Fletcher-8 Checksum"; this.name = "Fletcher-8 Checksum";
this.module = "Hashing"; this.module = "Crypto";
this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques."; this.description = "The Fletcher checksum is an algorithm for computing a position-dependent checksum devised by John Gould Fletcher at Lawrence Livermore Labs in the late 1970s.<br><br>The objective of the Fletcher checksum was to provide error-detection properties approaching those of a cyclic redundancy check but with the lower computational effort associated with summation techniques.";
this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum"; this.infoURL = "https://wikipedia.org/wiki/Fletcher%27s_checksum";
this.inputType = "byteArray"; this.inputType = "byteArray";

View File

@@ -0,0 +1,100 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";
/**
* Flip Image operation
*/
class FlipImage extends Operation {
/**
* FlipImage constructor
*/
constructor() {
super();
this.name = "Flip Image";
this.module = "Image";
this.description = "Flips an image along its X or Y axis.";
this.infoURL = "";
this.inputType = "ArrayBuffer";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
name: "Axis",
type: "option",
value: ["Horizontal", "Vertical"]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
const [flipAxis] = args;
if (!isImage(new Uint8Array(input))) {
throw new OperationError("Invalid input file type.");
}
let image;
try {
image = await jimp.read(input);
} catch (err) {
throw new OperationError(`Error loading image. (${err})`);
}
try {
if (ENVIRONMENT_IS_WORKER())
self.sendStatusMessage("Flipping image...");
switch (flipAxis){
case "Horizontal":
image.flip(true, false);
break;
case "Vertical":
image.flip(false, true);
break;
}
let imageBuffer;
if (image.getMIME() === "image/gif") {
imageBuffer = await image.getBufferAsync(jimp.MIME_PNG);
} else {
imageBuffer = await image.getBufferAsync(jimp.AUTO);
}
return imageBuffer.buffer;
} catch (err) {
throw new OperationError(`Error flipping image. (${err})`);
}
}
/**
* Displays the flipped image using HTML for web apps
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data) {
if (!data.byteLength) return "";
const dataArray = new Uint8Array(data);
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
}
export default FlipImage;

View File

@@ -89,7 +89,7 @@ class Fork extends Operation {
// Run recipe over each tranche // Run recipe over each tranche
for (i = 0; i < inputs.length; i++) { for (i = 0; i < inputs.length; i++) {
// Baseline ing values for each tranche so that registers are reset // Baseline ing values for each tranche so that registers are reset
subOpList.forEach((op, i) => { recipe.opList.forEach((op, i) => {
op.ingValues = JSON.parse(JSON.stringify(ingValues[i])); op.ingValues = JSON.parse(JSON.stringify(ingValues[i]));
}); });

View File

@@ -0,0 +1,58 @@
/**
* @author tcode2k16 [tcode2k16@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import BigNumber from "bignumber.js";
import Utils from "../Utils";
/**
* From Base62 operation
*/
class FromBase62 extends Operation {
/**
* FromBase62 constructor
*/
constructor() {
super();
this.name = "From Base62";
this.module = "Default";
this.description = "Base62 is a notation for encoding arbitrary byte data using a restricted set of symbols that can be conveniently used by humans and processed by computers. The high number base results in shorter strings than with the decimal or hexadecimal system.";
this.infoURL = "https://wikipedia.org/wiki/List_of_numeral_systems";
this.inputType = "string";
this.outputType = "byteArray";
this.args = [
{
name: "Alphabet",
type: "string",
value: "0-9A-Za-z"
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
if (input.length < 1) return [];
const ALPHABET = Utils.expandAlphRange(args[0]).join("");
const BN = BigNumber.clone({ ALPHABET });
const re = new RegExp("[^" + ALPHABET.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g");
input = input.replace(re, "");
const number = new BN(input, 62);
return Utils.convertToByteArray(number.toString(16), "Hex");
}
}
export default FromBase62;

View File

@@ -38,44 +38,44 @@ class FromBase64 extends Operation {
]; ];
this.patterns = [ this.patterns = [
{ {
match: "^(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", match: "^\\s*(?:[A-Z\\d+/]{4})+(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["A-Za-z0-9+/=", false] args: ["A-Za-z0-9+/=", true]
}, },
{ {
match: "^[A-Z\\d\\-_]{20,}$", match: "^\\s*[A-Z\\d\\-_]{20,}\\s*$",
flags: "i", flags: "i",
args: ["A-Za-z0-9-_", false] args: ["A-Za-z0-9-_", true]
}, },
{ {
match: "^(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?$", match: "^\\s*(?:[A-Z\\d+\\-]{4}){5,}(?:[A-Z\\d+\\-]{2}==|[A-Z\\d+\\-]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["A-Za-z0-9+\\-=", false] args: ["A-Za-z0-9+\\-=", true]
}, },
{ {
match: "^(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?$", match: "^\\s*(?:[A-Z\\d./]{4}){5,}(?:[A-Z\\d./]{2}==|[A-Z\\d./]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["./0-9A-Za-z=", false] args: ["./0-9A-Za-z=", true]
}, },
{ {
match: "^[A-Z\\d_.]{20,}$", match: "^\\s*[A-Z\\d_.]{20,}\\s*$",
flags: "i", flags: "i",
args: ["A-Za-z0-9_.", false] args: ["A-Za-z0-9_.", true]
}, },
{ {
match: "^(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?$", match: "^\\s*(?:[A-Z\\d._]{4}){5,}(?:[A-Z\\d._]{2}--|[A-Z\\d._]{3}-)?\\s*$",
flags: "i", flags: "i",
args: ["A-Za-z0-9._-", false] args: ["A-Za-z0-9._-", true]
}, },
{ {
match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["0-9a-zA-Z+/=", false] args: ["0-9a-zA-Z+/=", true]
}, },
{ {
match: "^(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?$", match: "^\\s*(?:[A-Z\\d+/]{4}){5,}(?:[A-Z\\d+/]{2}==|[A-Z\\d+/]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["0-9A-Za-z+/=", false] args: ["0-9A-Za-z+/=", true]
}, },
{ {
match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$", match: "^[ !\"#$%&'()*+,\\-./\\d:;<=>?@A-Z[\\\\\\]^_]{20,}$",
@@ -83,24 +83,24 @@ class FromBase64 extends Operation {
args: [" -_", false] args: [" -_", false]
}, },
{ {
match: "^[A-Z\\d+\\-]{20,}$", match: "^\\s*[A-Z\\d+\\-]{20,}\\s*$",
flags: "i", flags: "i",
args: ["+\\-0-9A-Za-z", false] args: ["+\\-0-9A-Za-z", true]
}, },
{ {
match: "^[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}$", match: "^\\s*[!\"#$%&'()*+,\\-0-689@A-NP-VX-Z[`a-fh-mp-r]{20,}\\s*$",
flags: "", flags: "",
args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", false] args: ["!-,-0-689@A-NP-VX-Z[`a-fh-mp-r", true]
}, },
{ {
match: "^(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?$", match: "^\\s*(?:[N-ZA-M\\d+/]{4}){5,}(?:[N-ZA-M\\d+/]{2}==|[N-ZA-M\\d+/]{3}=)?\\s*$",
flags: "i", flags: "i",
args: ["N-ZA-Mn-za-m0-9+/=", false] args: ["N-ZA-Mn-za-m0-9+/=", true]
}, },
{ {
match: "^[A-Z\\d./]{20,}$", match: "^\\s*[A-Z\\d./]{20,}\\s*$",
flags: "i", flags: "i",
args: ["./0-9A-Za-z", false] args: ["./0-9A-Za-z", true]
}, },
]; ];
} }

View File

@@ -0,0 +1,39 @@
/**
* @author masq [github.cyberchef@masq.cc]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
/**
* From Case Insensitive Regex operation
*/
class FromCaseInsensitiveRegex extends Operation {
/**
* FromCaseInsensitiveRegex constructor
*/
constructor() {
super();
this.name = "From Case Insensitive Regex";
this.module = "Default";
this.description = "Converts a case-insensitive regex string to a case sensitive regex string (no guarantee on it being the proper original casing) in case the i flag wasn't available at the time but now is, or you need it to be case-sensitive again.<br><br>e.g. <code>[mM][oO][zZ][iI][lL][lL][aA]/[0-9].[0-9] .*</code> becomes <code>Mozilla/[0-9].[0-9] .*</code>";
this.infoURL = "https://wikipedia.org/wiki/Regular_expression";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.replace(/\[[a-z]{2}\]/ig, m => m[1].toUpperCase() === m[2].toUpperCase() ? m[1] : m);
}
}
export default FromCaseInsensitiveRegex;

View File

@@ -1,44 +0,0 @@
/**
* @author gchq77703 []
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import geohash from "ngeohash";
/**
* From Geohash operation
*/
class FromGeohash extends Operation {
/**
* FromGeohash constructor
*/
constructor() {
super();
this.name = "From Geohash";
this.module = "Hashing";
this.description = "Converts Geohash strings into Lat/Long coordinates. For example, <code>ww8p1r4t8</code> becomes <code>37.8324,112.5584</code>.";
this.infoURL = "https://wikipedia.org/wiki/Geohash";
this.inputType = "string";
this.outputType = "string";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
return input.split("\n").map(line => {
const coords = geohash.decode(line);
return [coords.latitude, coords.longitude].join(",");
}).join("\n");
}
}
export default FromGeohash;

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,7 @@ class FromQuotedPrintable extends Operation {
this.args = []; this.args = [];
this.patterns = [ this.patterns = [
{ {
match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]*(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$", match: "^[\\x21-\\x3d\\x3f-\\x7e \\t]{0,76}(?:=[\\da-f]{2}|=\\r?\\n)(?:[\\x21-\\x3d\\x3f-\\x7e \\t]|=[\\da-f]{2}|=\\r?\\n)*$",
flags: "i", flags: "i",
args: [] args: []
}, },

View File

@@ -0,0 +1,71 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import GostDigest from "../vendor/gost/gostDigest";
import {toHexFast} from "../lib/Hex";
/**
* GOST hash operation
*/
class GOSTHash extends Operation {
/**
* GOSTHash constructor
*/
constructor() {
super();
this.name = "GOST hash";
this.module = "Hashing";
this.description = "The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 <i>Information Technology Cryptographic Information Security Hash Function</i>. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.<br><br>This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.<br><br>The GOST hash function is based on the GOST block cipher.";
this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)";
this.inputType = "ArrayBuffer";
this.outputType = "string";
this.args = [
{
"name": "S-Box",
"type": "option",
"value": [
"D-A",
"D-SC",
"E-TEST",
"E-A",
"E-B",
"E-C",
"E-D",
"E-SC",
"E-Z",
"D-TEST"
]
}
];
}
/**
* @param {ArrayBuffer} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
try {
const sBox = args[1];
const gostDigest = new GostDigest({
name: "GOST R 34.11",
version: 1994,
sBox: sBox
});
return toHexFast(gostDigest.digest(input));
} catch (err) {
throw new OperationError(err);
}
}
}
export default GOSTHash;

View File

@@ -26,8 +26,11 @@ import Fletcher16Checksum from "./Fletcher16Checksum";
import Fletcher32Checksum from "./Fletcher32Checksum"; import Fletcher32Checksum from "./Fletcher32Checksum";
import Fletcher64Checksum from "./Fletcher64Checksum"; import Fletcher64Checksum from "./Fletcher64Checksum";
import Adler32Checksum from "./Adler32Checksum"; import Adler32Checksum from "./Adler32Checksum";
import CRC8Checksum from "./CRC8Checksum";
import CRC16Checksum from "./CRC16Checksum"; import CRC16Checksum from "./CRC16Checksum";
import CRC32Checksum from "./CRC32Checksum"; import CRC32Checksum from "./CRC32Checksum";
import BLAKE2b from "./BLAKE2b";
import BLAKE2s from "./BLAKE2s";
/** /**
* Generate all hashes operation * Generate all hashes operation
@@ -41,7 +44,7 @@ class GenerateAllHashes extends Operation {
super(); super();
this.name = "Generate all hashes"; this.name = "Generate all hashes";
this.module = "Hashing"; this.module = "Crypto";
this.description = "Generates all available hashes and checksums for the input."; this.description = "Generates all available hashes and checksums for the input.";
this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions"; this.infoURL = "https://wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";
@@ -86,6 +89,14 @@ class GenerateAllHashes extends Operation {
"\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) + "\nWhirlpool-0: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-0"]) +
"\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) + "\nWhirlpool-T: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool-T"]) +
"\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) + "\nWhirlpool: " + (new Whirlpool()).run(arrayBuffer, ["Whirlpool"]) +
"\nBLAKE2b-128: " + (new BLAKE2b).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-160: " + (new BLAKE2b).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-256: " + (new BLAKE2b).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-384: " + (new BLAKE2b).run(arrayBuffer, ["384", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2b-512: " + (new BLAKE2b).run(arrayBuffer, ["512", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-128: " + (new BLAKE2s).run(arrayBuffer, ["128", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-160: " + (new BLAKE2s).run(arrayBuffer, ["160", "Hex", {string: "", option: "UTF8"}]) +
"\nBLAKE2s-256: " + (new BLAKE2s).run(arrayBuffer, ["256", "Hex", {string: "", option: "UTF8"}]) +
"\nSSDEEP: " + (new SSDEEP()).run(str) + "\nSSDEEP: " + (new SSDEEP()).run(str) +
"\nCTPH: " + (new CTPH()).run(str) + "\nCTPH: " + (new CTPH()).run(str) +
"\n\nChecksums:" + "\n\nChecksums:" +
@@ -94,8 +105,9 @@ class GenerateAllHashes extends Operation {
"\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) + "\nFletcher-32: " + (new Fletcher32Checksum).run(byteArray, []) +
"\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) + "\nFletcher-64: " + (new Fletcher64Checksum).run(byteArray, []) +
"\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) + "\nAdler-32: " + (new Adler32Checksum).run(byteArray, []) +
"\nCRC-16: " + (new CRC16Checksum).run(str, []) + "\nCRC-8: " + (new CRC8Checksum).run(arrayBuffer, ["CRC-8"]) +
"\nCRC-32: " + (new CRC32Checksum).run(str, []); "\nCRC-16: " + (new CRC16Checksum).run(arrayBuffer, []) +
"\nCRC-32: " + (new CRC32Checksum).run(arrayBuffer, []);
return output; return output;
} }

View File

@@ -0,0 +1,70 @@
/**
* @author klaxon [klaxon@veyr.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { GenerateParagraphs, GenerateSentences, GenerateWords, GenerateBytes } from "../lib/LoremIpsum";
/**
* Generate Lorem Ipsum operation
*/
class GenerateLoremIpsum extends Operation {
/**
* GenerateLoremIpsum constructor
*/
constructor() {
super();
this.name = "Generate Lorem Ipsum";
this.module = "Default";
this.description = "Generate varying length lorem ipsum placeholder text.";
this.infoURL = "https://wikipedia.org/wiki/Lorem_ipsum";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
"name": "Length",
"type": "number",
"value": "3"
},
{
"name": "Length in",
"type": "option",
"value": ["Paragraphs", "Sentences", "Words", "Bytes"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [length, lengthType] = args;
if (length < 1){
throw new OperationError("Length must be greater than 0");
}
switch (lengthType) {
case "Paragraphs":
return GenerateParagraphs(length);
case "Sentences":
return GenerateSentences(length);
case "Words":
return GenerateWords(length);
case "Bytes":
return GenerateBytes(length);
default:
throw new OperationError("Invalid length type");
}
}
}
export default GenerateLoremIpsum;

View File

@@ -0,0 +1,94 @@
/**
* @author j433866 [j433866@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import { generateQrCode } from "../lib/QRCode";
import { toBase64 } from "../lib/Base64";
import { isImage } from "../lib/FileType";
import Utils from "../Utils";
/**
* Generate QR Code operation
*/
class GenerateQRCode extends Operation {
/**
* GenerateQRCode constructor
*/
constructor() {
super();
this.name = "Generate QR Code";
this.module = "Image";
this.description = "Generates a Quick Response (QR) code from the input text.<br><br>A QR code is a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached.";
this.infoURL = "https://wikipedia.org/wiki/QR_code";
this.inputType = "string";
this.outputType = "ArrayBuffer";
this.presentType = "html";
this.args = [
{
"name": "Image Format",
"type": "option",
"value": ["PNG", "SVG", "EPS", "PDF"]
},
{
"name": "Module size (px)",
"type": "number",
"value": 5,
"min": 1
},
{
"name": "Margin (num modules)",
"type": "number",
"value": 2,
"min": 0
},
{
"name": "Error correction",
"type": "option",
"value": ["Low", "Medium", "Quartile", "High"],
"defaultIndex": 1
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
const [format, size, margin, errorCorrection] = args;
return generateQrCode(input, format, size, margin, errorCorrection);
}
/**
* Displays the QR image using HTML for web apps
*
* @param {ArrayBuffer} data
* @returns {html}
*/
present(data, args) {
if (!data.byteLength && !data.length) return "";
const dataArray = new Uint8Array(data),
[format] = args;
if (format === "PNG") {
const type = isImage(dataArray);
if (!type) {
throw new OperationError("Invalid file type.");
}
return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
}
return Utils.arrayBufferToStr(data);
}
}
export default GenerateQRCode;

View File

@@ -22,7 +22,7 @@ class GroupIPAddresses extends Operation {
super(); super();
this.name = "Group IP addresses"; this.name = "Group IP addresses";
this.module = "JSBN"; this.module = "Default";
this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses."; this.description = "Groups a list of IP addresses into subnets. Supports both IPv4 and IPv6 addresses.";
this.infoURL = "https://wikipedia.org/wiki/Subnetwork"; this.infoURL = "https://wikipedia.org/wiki/Subnetwork";
this.inputType = "string"; this.inputType = "string";

View File

@@ -19,7 +19,7 @@ class HAS160 extends Operation {
super(); super();
this.name = "HAS-160"; this.name = "HAS-160";
this.module = "Hashing"; this.module = "Crypto";
this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.<br><br>HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.<br><br>The message digest algorithm consists of 80 rounds."; this.description = "HAS-160 is a cryptographic hash function designed for use with the Korean KCDSA digital signature algorithm. It is derived from SHA-1, with assorted changes intended to increase its security. It produces a 160-bit output.<br><br>HAS-160 is used in the same way as SHA-1. First it divides input in blocks of 512 bits each and pads the final block. A digest function updates the intermediate hash value by processing the input blocks in turn.<br><br>The message digest algorithm consists of 80 rounds.";
this.infoURL = "https://wikipedia.org/wiki/HAS-160"; this.infoURL = "https://wikipedia.org/wiki/HAS-160";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";

Some files were not shown because too many files have changed in this diff Show More