diff --git a/Gruntfile.js b/Gruntfile.js index 943fae48..4a9d9837 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -196,7 +196,7 @@ module.exports = function (grunt) { tests: ["tests/**/*.{js,mjs}"], }, webpack: { - options: webpackConfig, + myConfig: webpackConfig, web: webpackProdConf(), }, "webpack-dev-server": { diff --git a/package-lock.json b/package-lock.json index 9b939b88..7317dd39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", @@ -2820,6 +2821,11 @@ "dev": true, "license": "ISC" }, + "node_modules/@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "dev": true, @@ -15769,6 +15775,11 @@ "version": "1.1.2", "dev": true }, + "@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==" + }, "@webassemblyjs/ast": { "version": "1.11.1", "dev": true, diff --git a/package.json b/package.json index f5d9a7f9..690795b4 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", @@ -181,7 +182,7 @@ "build": "npx grunt prod", "node": "npx grunt node", "repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs", - "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/operations/index.mjs", + "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs", "testnodeconsumer": "npx grunt testnodeconsumer", "testui": "npx grunt testui", "testuidev": "npx nightwatch --env=dev", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ce2f01f5..cf4d91be 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -91,6 +91,12 @@ "Rabbit", "SM4 Encrypt", "SM4 Decrypt", + "GOST Encrypt", + "GOST Decrypt", + "GOST Sign", + "GOST Verify", + "GOST Key Wrap", + "GOST Key Unwrap", "ROT13", "ROT13 Brute Force", "ROT47", @@ -370,7 +376,7 @@ "Snefru", "BLAKE2b", "BLAKE2s", - "GOST hash", + "GOST Hash", "Streebog", "SSDEEP", "CTPH", diff --git a/src/core/operations/GOSTDecrypt.mjs b/src/core/operations/GOSTDecrypt.mjs new file mode 100644 index 00000000..8259a0d4 --- /dev/null +++ b/src/core/operations/GOSTDecrypt.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Decrypt operation + */ +class GOSTDecrypt extends Operation { + + /** + * GOSTDecrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Decrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTDecrypt; diff --git a/src/core/operations/GOSTEncrypt.mjs b/src/core/operations/GOSTEncrypt.mjs new file mode 100644 index 00000000..ce92ecda --- /dev/null +++ b/src/core/operations/GOSTEncrypt.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Encrypt operation + */ +class GOSTEncrypt extends Operation { + + /** + * GOSTEncrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Encrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTEncrypt; diff --git a/src/core/operations/GOSTHash.mjs b/src/core/operations/GOSTHash.mjs index d67a594c..5c8cc6f7 100644 --- a/src/core/operations/GOSTHash.mjs +++ b/src/core/operations/GOSTHash.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import GostDigest from "../vendor/gost/gostDigest.mjs"; -import {toHexFast} from "../lib/Hex.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; /** * GOST hash operation @@ -20,7 +20,7 @@ class GOSTHash extends Operation { constructor() { super(); - this.name = "GOST hash"; + 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 Information Technology – Cryptographic Information Security – Hash Function. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.

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.

The GOST hash function is based on the GOST block cipher."; this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)"; @@ -28,20 +28,30 @@ class GOSTHash extends Operation { 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" + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1994)", + off: [1], + on: [2] + }, + { + name: "GOST R 34.11 (Streebog, 2012)", + on: [1], + off: [2] + } ] + }, + { + name: "Digest length", + type: "option", + value: ["256", "512"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] } ]; } @@ -52,13 +62,23 @@ class GOSTHash extends Operation { * @returns {string} */ run(input, args) { + const [version, length, sBox] = args; + + const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012; + const algorithm = { + name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10", + version: versionNum, + mode: "HASH" + }; + + if (versionNum === 1994) { + algorithm.sBox = sBox; + } else { + algorithm.length = parseInt(length, 10); + } + try { - const sBox = args[1]; - const gostDigest = new GostDigest({ - name: "GOST R 34.11", - version: 1994, - sBox: sBox - }); + const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { diff --git a/src/core/operations/GOSTKeyUnwrap.mjs b/src/core/operations/GOSTKeyUnwrap.mjs new file mode 100644 index 00000000..afcd6287 --- /dev/null +++ b/src/core/operations/GOSTKeyUnwrap.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Unwrap operation + */ +class GOSTKeyUnwrap extends Operation { + + /** + * GOSTKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Unwrap"; + this.module = "Ciphers"; + this.description = "A decryptor for keys wrapped using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyUnwrap; diff --git a/src/core/operations/GOSTKeyWrap.mjs b/src/core/operations/GOSTKeyWrap.mjs new file mode 100644 index 00000000..5a3fd4e6 --- /dev/null +++ b/src/core/operations/GOSTKeyWrap.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Wrap operation + */ +class GOSTKeyWrap extends Operation { + + /** + * GOSTKeyWrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Wrap"; + this.module = "Ciphers"; + this.description = "A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyWrap; diff --git a/src/core/operations/GOSTSign.mjs b/src/core/operations/GOSTSign.mjs new file mode 100644 index 00000000..9195f469 --- /dev/null +++ b/src/core/operations/GOSTSign.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Sign operation + */ +class GOSTSign extends Operation { + + /** + * GOSTSign constructor + */ + constructor() { + super(); + + this.name = "GOST Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "MAC length", + type: "number", + value: 32, + min: 8, + max: 64, + step: 8 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, macLength] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: macLength + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTSign; diff --git a/src/core/operations/GOSTVerify.mjs b/src/core/operations/GOSTVerify.mjs new file mode 100644 index 00000000..a270e7c5 --- /dev/null +++ b/src/core/operations/GOSTVerify.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Verify operation + */ +class GOSTVerify extends Operation { + + /** + * GOSTVerify constructor + */ + constructor() { + super(); + + this.name = "GOST Verify"; + this.module = "Ciphers"; + this.description = "Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "IV", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "MAC", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, macObj, inputType, version, length, sBox] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: mac.length * 4 + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input)); + + return out ? "The signature matches" : "The signature does not match"; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTVerify; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs index 2a4a2b1a..d9af8065 100644 --- a/src/core/operations/GenerateAllHashes.mjs +++ b/src/core/operations/GenerateAllHashes.mjs @@ -108,7 +108,7 @@ class GenerateAllHashes extends Operation { {name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, {name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]}, {name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]}, - {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["D-A"]}, + {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]}, {name: "LM Hash", algo: (new LMHash), inputType: "str", params: []}, {name: "NT Hash", algo: (new NTHash), inputType: "str", params: []}, {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, diff --git a/src/core/operations/Streebog.mjs b/src/core/operations/Streebog.mjs index c5e5bb89..b65accf6 100644 --- a/src/core/operations/Streebog.mjs +++ b/src/core/operations/Streebog.mjs @@ -28,7 +28,7 @@ class Streebog extends Operation { this.outputType = "string"; this.args = [ { - "name": "Size", + "name": "Digest length", "type": "option", "value": ["256", "512"] } @@ -41,13 +41,16 @@ class Streebog extends Operation { * @returns {string} */ run(input, args) { + const [length] = args; + + const algorithm = { + version: 2012, + mode: "HASH", + length: parseInt(length, 10) + }; + try { - const length = parseInt(args[0], 10); - const gostDigest = new GostDigest({ - name: "GOST R 34.11", - version: 2012, - length: length - }); + const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 56f432e0..570fbb6f 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -134,6 +134,7 @@ import "./tests/LevenshteinDistance.mjs"; import "./tests/SwapCase.mjs"; import "./tests/HKDF.mjs"; import "./tests/GenerateDeBruijnSequence.mjs"; +import "./tests/GOST.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/GOST.mjs b/tests/operations/tests/GOST.mjs new file mode 100644 index 00000000..f332051b --- /dev/null +++ b/tests/operations/tests/GOST.mjs @@ -0,0 +1,183 @@ +/** + * GOST tests. + * + * The GOST library already includes a range of tests for the correctness of + * the algorithms. These tests are intended only to confirm that the library + * has been correctly integrated into CyberChef. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "GOST Encrypt: Magma", + input: "Hello, World!", + expectedOutput: "f124ac5c0853870906dbaf9b56", + recipeConfig: [ + { + op: "GOST Encrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST 28147 (Magma, 1989)", + "64", + "E-SC", + "OFB", + "CP", + "ZERO" + ] + } + ], + }, + { + name: "GOST Encrypt: Kuznyechik", + input: "Hello, World!", + expectedOutput: "8673d490dfa4a66d5e3ff00ba316724f", + recipeConfig: [ + { + op: "GOST Encrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + "Raw", + "Hex", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-SC", + "CBC", + "CP", + "PKCS5" + ] + } + ], + }, + { + name: "GOST Decrypt: Magma", + input: "f124ac5c0853870906dbaf9b56", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "GOST Decrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Hex", + "Raw", + "GOST 28147 (Magma, 1989)", + "128", + "E-SC", + "OFB", + "CP", + "ZERO" + ] + } + ], + }, + { + name: "GOST Decrypt: Kuznyechik", + input: "8673d490dfa4a66d5e3ff00ba316724f", + expectedOutput: "Hello, World!\0\0\0", + recipeConfig: [ + { + op: "GOST Decrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + "Hex", + "Raw", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-TEST", + "CBC", + "CP", + "PKCS5" + ] + } + ], + }, + { + name: "GOST Sign", + input: "Hello, World!", + expectedOutput: "810d0c40e965", + recipeConfig: [ + { + op: "GOST Sign", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST 28147 (Magma, 1989)", + "64", + "E-C", + 48 + ] + } + ], + }, + { + name: "GOST Verify", + input: "Hello, World!", + expectedOutput: "The signature matches", + recipeConfig: [ + { + op: "GOST Verify", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + { "option": "Hex", "string": "42b77fb3d6f6bf04" }, + "Raw", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-TEST" + ] + } + ], + }, + { + name: "GOST Key Wrap", + input: "Hello, World!123", + expectedOutput: "0bb706e92487fceef97589911faeb28200000000000000000000000000000000\r\n6b7bfd16", + recipeConfig: [ + { + op: "GOST Key Wrap", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST R 34.12 (Kuznyechik, 2015)", + "64", + "E-TEST", + "CP" + ] + } + ], + }, + { + name: "GOST Key Unwrap", + input: "c8e58458a42d21974d50103d59b469f2c8e58458a42d21974d50103d59b469f2\r\na32a1575", + expectedOutput: "0123456789abcdef0123456789abcdef", + recipeConfig: [ + { + op: "GOST Key Unwrap", + args: [ + { "option": "Hex", "string": "" }, + { "option": "Latin1", "string": "00112233" }, + "Hex", + "Raw", + "GOST 28147 (Magma, 1989)", + "64", + "E-Z", + "CP" + ] + } + ], + }, +]); diff --git a/tests/operations/tests/Hash.mjs b/tests/operations/tests/Hash.mjs index 4480f65a..ba502934 100644 --- a/tests/operations/tests/Hash.mjs +++ b/tests/operations/tests/Hash.mjs @@ -1094,8 +1094,8 @@ TestRegister.addTests([ expectedOutput: "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", recipeConfig: [ { - op: "GOST hash", - args: ["D-A"] + op: "GOST Hash", + args: ["GOST 28147 (1994)", "256", "D-A"] } ] }, @@ -1105,8 +1105,8 @@ TestRegister.addTests([ expectedOutput: "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", recipeConfig: [ { - op: "GOST hash", - args: ["D-A"] + op: "GOST Hash", + args: ["GOST 28147 (1994)", "256", "D-A"] } ] },