diff --git a/.gitignore b/.gitignore index 9931b74..3fb7c71 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ dist dist-test lib-web lib-esm +test_cal_db diff --git a/package-lock.json b/package-lock.json index 184d7ea..525ff36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,17 @@ "version": "2.1.0", "license": "MIT", "dependencies": { + "@nmshd/rs-crypto-types": "^0.2.1", + "@types/lodash": "^4.17.15", "libsodium-wrappers-sumo": "0.7.15", + "lodash": "^4.17.21", "uuid": "10.0.0" }, "devDependencies": { "@js-soft/eslint-config-ts": "^1.6.13", "@js-soft/license-check": "1.0.9", "@js-soft/ts-serval": "2.0.10", + "@nmshd/rs-crypto-node": "^0.2.0", "@types/chai": "^5.0.0", "@types/libsodium-wrappers-sumo": "^0.7.8", "@types/mocha": "^10.0.9", @@ -452,6 +456,97 @@ "reflect-metadata": "^0.1.13" } }, + "node_modules/@neon-rs/load": { + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.1.82.tgz", + "integrity": "sha512-H4Gu2o5kPp+JOEhRrOQCnJnf7X6sv9FBLttM/wSbb4efsgFWeHzfU/ItZ01E5qqEk+U6QGdeVO7lxXIAtYHr5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nmshd/rs-crypto-node": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node/-/rs-crypto-node-0.2.0.tgz", + "integrity": "sha512-jU377nXB4CDZaIbWYj0zrWvWHDyn1xN2BajyNS57GqrCSfYdxup4q5WKx4ra6SB0jBMw41Go7L6+/RXIlOF2/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@neon-rs/load": "^0.1.73", + "@nmshd/rs-crypto-types": "^0.2.1" + }, + "optionalDependencies": { + "@nmshd/darwin-arm64": "0.2.0", + "@nmshd/darwin-x64": "0.2.0", + "@nmshd/linux-arm64-gnu": "0.2.0", + "@nmshd/linux-x64-gnu": "0.2.0", + "@nmshd/rs-crypto-node-darwin-arm64": "0.2.0", + "@nmshd/rs-crypto-node-darwin-x64": "0.2.0", + "@nmshd/rs-crypto-node-linux-x64-gnu": "0.2.0", + "@nmshd/rs-crypto-node-win32-x64-msvc": "0.2.0", + "@nmshd/win32-x64-msvc": "0.2.0" + } + }, + "node_modules/@nmshd/rs-crypto-node-darwin-arm64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-darwin-arm64/-/rs-crypto-node-darwin-arm64-0.2.0.tgz", + "integrity": "sha512-ciZnPQA0DZlncpPkNOiLN18qfOt0oMVnXcG76iUJ02QEA/F4not++EbfdbU3xqSqlljlrTAdiRe3JDVBb/bjDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nmshd/rs-crypto-node-darwin-x64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-darwin-x64/-/rs-crypto-node-darwin-x64-0.2.0.tgz", + "integrity": "sha512-qMHI/wJHlvvFfgmefIS2w3Z0LS7cKV896bSoeBB7RRtQ8p5/l+QDCzKLRBZWO1b076NuVW5Vks8t2Kkklxg33w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nmshd/rs-crypto-node-linux-x64-gnu": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-linux-x64-gnu/-/rs-crypto-node-linux-x64-gnu-0.2.0.tgz", + "integrity": "sha512-SQCqnFyQTUik9eRvJPebPtgBEhCCOPZ476rPkZ5JrTnZDyGpHicj/VqT24BEfyo0shkLeR34pZ350zJ+eAVopg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nmshd/rs-crypto-node-win32-x64-msvc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-win32-x64-msvc/-/rs-crypto-node-win32-x64-msvc-0.2.0.tgz", + "integrity": "sha512-AkD3l5dfA6/wxbF7Jm7evgbQM5xdSjHP5KduZ/LMzjaWBkJPgSFeKy/7cSMaiAooEm27In86qHypbWbXXG9qFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nmshd/rs-crypto-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-types/-/rs-crypto-types-0.2.1.tgz", + "integrity": "sha512-zrlBlTO3IbC/brRIlAT3n92ZzvQyJpZdthbVGb/sg9jLke5z8TvXP5gG8uxFEndAMGreQCS7k8LAgXWT/V+XMQ==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -679,6 +774,12 @@ "@types/libsodium-wrappers": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.9", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", @@ -2540,10 +2641,11 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3436,9 +3538,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "license": "MIT", "dependencies": { @@ -3461,7 +3563,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -3476,6 +3578,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-http-proxy": { @@ -4792,7 +4898,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5197,9 +5303,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -5591,9 +5697,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, @@ -8109,6 +8215,64 @@ "reflect-metadata": "^0.1.13" } }, + "@neon-rs/load": { + "version": "0.1.82", + "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.1.82.tgz", + "integrity": "sha512-H4Gu2o5kPp+JOEhRrOQCnJnf7X6sv9FBLttM/wSbb4efsgFWeHzfU/ItZ01E5qqEk+U6QGdeVO7lxXIAtYHr5A==", + "dev": true + }, + "@nmshd/rs-crypto-node": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node/-/rs-crypto-node-0.2.0.tgz", + "integrity": "sha512-jU377nXB4CDZaIbWYj0zrWvWHDyn1xN2BajyNS57GqrCSfYdxup4q5WKx4ra6SB0jBMw41Go7L6+/RXIlOF2/Q==", + "dev": true, + "requires": { + "@neon-rs/load": "^0.1.73", + "@nmshd/darwin-arm64": "0.2.0", + "@nmshd/darwin-x64": "0.2.0", + "@nmshd/linux-arm64-gnu": "0.2.0", + "@nmshd/linux-x64-gnu": "0.2.0", + "@nmshd/rs-crypto-node-darwin-arm64": "0.2.0", + "@nmshd/rs-crypto-node-darwin-x64": "0.2.0", + "@nmshd/rs-crypto-node-linux-x64-gnu": "0.2.0", + "@nmshd/rs-crypto-node-win32-x64-msvc": "0.2.0", + "@nmshd/rs-crypto-types": "^0.2.1", + "@nmshd/win32-x64-msvc": "0.2.0" + } + }, + "@nmshd/rs-crypto-node-darwin-arm64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-darwin-arm64/-/rs-crypto-node-darwin-arm64-0.2.0.tgz", + "integrity": "sha512-ciZnPQA0DZlncpPkNOiLN18qfOt0oMVnXcG76iUJ02QEA/F4not++EbfdbU3xqSqlljlrTAdiRe3JDVBb/bjDg==", + "dev": true, + "optional": true + }, + "@nmshd/rs-crypto-node-darwin-x64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-darwin-x64/-/rs-crypto-node-darwin-x64-0.2.0.tgz", + "integrity": "sha512-qMHI/wJHlvvFfgmefIS2w3Z0LS7cKV896bSoeBB7RRtQ8p5/l+QDCzKLRBZWO1b076NuVW5Vks8t2Kkklxg33w==", + "dev": true, + "optional": true + }, + "@nmshd/rs-crypto-node-linux-x64-gnu": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-linux-x64-gnu/-/rs-crypto-node-linux-x64-gnu-0.2.0.tgz", + "integrity": "sha512-SQCqnFyQTUik9eRvJPebPtgBEhCCOPZ476rPkZ5JrTnZDyGpHicj/VqT24BEfyo0shkLeR34pZ350zJ+eAVopg==", + "dev": true, + "optional": true + }, + "@nmshd/rs-crypto-node-win32-x64-msvc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-node-win32-x64-msvc/-/rs-crypto-node-win32-x64-msvc-0.2.0.tgz", + "integrity": "sha512-AkD3l5dfA6/wxbF7Jm7evgbQM5xdSjHP5KduZ/LMzjaWBkJPgSFeKy/7cSMaiAooEm27In86qHypbWbXXG9qFg==", + "dev": true, + "optional": true + }, + "@nmshd/rs-crypto-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@nmshd/rs-crypto-types/-/rs-crypto-types-0.2.1.tgz", + "integrity": "sha512-zrlBlTO3IbC/brRIlAT3n92ZzvQyJpZdthbVGb/sg9jLke5z8TvXP5gG8uxFEndAMGreQCS7k8LAgXWT/V+XMQ==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -8254,6 +8418,11 @@ "@types/libsodium-wrappers": "*" } }, + "@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==" + }, "@types/mocha": { "version": "10.0.9", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", @@ -9522,9 +9691,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -10123,9 +10292,9 @@ "dev": true }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -10147,7 +10316,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -11114,8 +11283,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.merge": { "version": "4.6.2", @@ -11402,9 +11570,9 @@ "dev": true }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true }, "natural-compare": { @@ -11682,9 +11850,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "path-type": { diff --git a/package.json b/package.json index d74de91..27e9771 100644 --- a/package.json +++ b/package.json @@ -34,19 +34,23 @@ "test": "npm run test:node && npm run test:web", "test:local": "npm run test", "test:ci": "npm run test", - "test:node": "mocha -r ts-node/register -r tsconfig-paths/register -r test/fixtures.ts ./test/index.ts --project ./test/tsconfig.json --exit", + "test:node": "mocha -r ts-node/register -r tsconfig-paths/register -r test/fixtures.ts ./test/index.ts --project ./test/tsconfig.json --exit --delay --fail-zero", "test:local:node": "npm run test:node", "test:web": "browsertest-runner", "test:web:debug": "browsertest-runner --debug" }, "dependencies": { + "@nmshd/rs-crypto-types": "^0.2.1", + "@types/lodash": "^4.17.15", "libsodium-wrappers-sumo": "0.7.15", + "lodash": "^4.17.21", "uuid": "10.0.0" }, "devDependencies": { "@js-soft/eslint-config-ts": "^1.6.13", "@js-soft/license-check": "1.0.9", "@js-soft/ts-serval": "2.0.10", + "@nmshd/rs-crypto-node": "^0.2.0", "@types/chai": "^5.0.0", "@types/libsodium-wrappers-sumo": "^0.7.8", "@types/mocha": "^10.0.9", diff --git a/src/CryptoErrorCode.ts b/src/CryptoErrorCode.ts index 9291b50..77f1b1c 100644 --- a/src/CryptoErrorCode.ts +++ b/src/CryptoErrorCode.ts @@ -46,5 +46,11 @@ export enum CryptoErrorCode { StateWrongNonce = "error.crypto.state.wrongNonce", StateWrongCounter = "error.crypto.state.wrongCounter", StateWrongOrder = "error.crypto.state.orderDoesNotMatch", - StateWrongType = "error.crypto.state.wrongType" + StateWrongType = "error.crypto.state.wrongType", + + CalNonExtractable = "error.crypto.cal.nonExtractable", + CalUnsupportedAlgorithm = "error.crypto.cal.unsupportedAlgorithm", + CalWrongProvider = "error.crypto.cal.wrongProvider", + CalUninitializedKey = "error.crypto.cal.uninitializedKey", + CalFailedLoadingProvider = "error.crypto.cal.failedLoadingProvider" } diff --git a/src/CryptoSerializable.ts b/src/CryptoSerializable.ts index 7907a86..b29466d 100644 --- a/src/CryptoSerializable.ts +++ b/src/CryptoSerializable.ts @@ -1,4 +1,4 @@ -import { ISerializable, Serializable } from "@js-soft/ts-serval"; +import { ISerializable, ISerializableAsync, Serializable, SerializableAsync } from "@js-soft/ts-serval"; import { CoreBuffer } from "./CoreBuffer"; export abstract class CryptoSerializable extends Serializable implements ISerializable { @@ -10,3 +10,13 @@ export abstract class CryptoSerializable extends Serializable implements ISerial return CoreBuffer.utf8_base64(this.serialize(verbose)); } } + +export abstract class CryptoSerializableAsync extends SerializableAsync implements ISerializableAsync { + public override serialize(verbose = true): string { + return JSON.stringify(this.toJSON(verbose)); + } + + public toBase64(verbose = true): string { + return CoreBuffer.utf8_base64(this.serialize(verbose)); + } +} \ No newline at end of file diff --git a/src/crypto-layer/CryptoAsymmetricKeyHandle.ts b/src/crypto-layer/CryptoAsymmetricKeyHandle.ts new file mode 100644 index 0000000..ca4e081 --- /dev/null +++ b/src/crypto-layer/CryptoAsymmetricKeyHandle.ts @@ -0,0 +1,80 @@ +import { SerializableAsync, serialize, type, validate } from "@js-soft/ts-serval"; +import { KeyPairHandle, KeyPairSpec, Provider } from "@nmshd/rs-crypto-types"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoError } from "../CryptoError"; +import { CryptoErrorCode } from "../CryptoErrorCode"; +import { CryptoSerializableAsync } from "../CryptoSerializable"; +import { getProvider } from "./CryptoLayerProviders"; + +/** + * Loose check if `value` can be initialized as if it was a `CryptoAsymmetricKeyHandle`. + */ +function isCryptoAsymmetricKeyHandle(value: any): value is CryptoAsymmetricKeyHandle { + return typeof value["providerName"] === "string" && typeof value["id"] === "string"; +} + +@type("CryptoAsymmetricKeyHandle") +export class CryptoAsymmetricKeyHandle extends CryptoSerializableAsync { + @validate() + @serialize() + public spec: KeyPairSpec; + + @validate() + @serialize() + public id: string; + + @validate() + @serialize() + public providerName: string; + + public provider: Provider; + + public keyPairHandle: KeyPairHandle; + + public static async newFromProviderAndKeyPairHandle( + this: new () => T, + provider: Provider, + keyPairHandle: KeyPairHandle, + other?: { + providerName?: string; + keyId?: string; + keySpec?: KeyPairSpec; + } + ): Promise { + const result = new this(); + + result.providerName = other?.providerName ?? (await provider.providerName()); + result.id = other?.keyId ?? (await keyPairHandle.id()); + result.spec = other?.keySpec ?? (await keyPairHandle.spec()); + + result.provider = provider; + result.keyPairHandle = keyPairHandle; + return result; + } + + public static async from(value: any): Promise { + return await this.fromAny(value); + } + + public static async fromBase64(value: string): Promise { + return await this.deserialize(CoreBuffer.base64_utf8(value)); + } + + public static override async postFrom(value: T): Promise { + if (!isCryptoAsymmetricKeyHandle(value)) { + throw new CryptoError(CryptoErrorCode.WrongParameters, `Expected 'CryptoAsymmetricKeyHandle'.`); + } + const provider = getProvider({ providerName: value.providerName }); + if (!provider) { + throw new CryptoError( + CryptoErrorCode.CalFailedLoadingProvider, + `Failed loading provider ${value.providerName}` + ); + } + const keyHandle = await provider.loadKeyPair(value.id); + + value.keyPairHandle = keyHandle; + value.provider = provider; + return value; + } +} diff --git a/src/crypto-layer/CryptoExportedPublicKey.ts b/src/crypto-layer/CryptoExportedPublicKey.ts new file mode 100644 index 0000000..dcebc95 --- /dev/null +++ b/src/crypto-layer/CryptoExportedPublicKey.ts @@ -0,0 +1,33 @@ +import { serialize, type, validate } from "@js-soft/ts-serval"; +import { KeyPairHandle, KeyPairSpec, Provider } from "@nmshd/rs-crypto-types"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoSerializable } from "src/CryptoSerializable"; +import { getProviderOrThrow, ProviderIdentifier } from "./CryptoLayerProviders"; +import { CryptoPublicKeyHandle } from "./CryptoPublicKeyHandle"; + +@type("CryptoExportedPublicKey") +export class CryptoExportedPublicKey extends CryptoSerializable { + @validate() + @serialize() + public rawPublicKey: CoreBuffer; + + @validate() + @serialize() + public spec: KeyPairSpec; + + public async into( + constructor: { newFromProviderAndKeyPairHandle(provider: Provider, keyPairHandle: KeyPairHandle): Promise }, + providerIdent: ProviderIdentifier + ): Promise { + const provider = getProviderOrThrow(providerIdent); + const keyPairHandle = await provider.importPublicKey(this.spec, this.rawPublicKey.buffer); + return await constructor.newFromProviderAndKeyPairHandle(provider, keyPairHandle); + } + + public static async from(publicKeyHandle: CryptoPublicKeyHandle): Promise { + const exportedPublicKey = new CryptoExportedPublicKey(); + exportedPublicKey.spec = publicKeyHandle.spec; + exportedPublicKey.rawPublicKey = new CoreBuffer(await publicKeyHandle.keyPairHandle.getPublicKey()); + return exportedPublicKey; + } +} diff --git a/src/crypto-layer/CryptoLayerConfig.ts b/src/crypto-layer/CryptoLayerConfig.ts new file mode 100644 index 0000000..d3548c3 --- /dev/null +++ b/src/crypto-layer/CryptoLayerConfig.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AdditionalConfig, ProviderConfig, ProviderFactoryFunctions, SecurityLevel } from "@nmshd/rs-crypto-types"; + +/** + * Interface holding functions for listing and creating providers and configuration for initializing the key meta data store. + * + * @property factoryFunctions - Functions that list providers or create providers. + * @property keyMetadataStoreConfig - Configuration needed for saving key metadata. + * @property keyMetadataStoreAuth - ~~Optional~~ configuration for authenticating key metadata. + * @property providersToBeInitialized - Array of providers to initalize. + */ +export interface CryptoLayerConfig { + factoryFunctions: ProviderFactoryFunctions; + keyMetadataStoreConfig: Extract; + keyMetadataStoreAuth?: Extract< + AdditionalConfig, + { StorageConfigHMAC: any } | { StorageConfigDSA: any } | { StorageConfigPass: any } + >; + providersToBeInitialized: CryptoLayerProviderFilter[]; +} + +/** + * Reference to a specific provider, if a name is supplied, or any provider that fullfills the other requirements. + */ +export type CryptoLayerProviderFilter = + | { + providerName: string; + } + | { + securityLevel: SecurityLevel; + } + | { + providerConfig: ProviderConfig; + }; diff --git a/src/crypto-layer/CryptoLayerProviders.ts b/src/crypto-layer/CryptoLayerProviders.ts new file mode 100644 index 0000000..c7c1fd6 --- /dev/null +++ b/src/crypto-layer/CryptoLayerProviders.ts @@ -0,0 +1,153 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + Provider, + ProviderConfig, + ProviderFactoryFunctions, + ProviderImplConfig, + SecurityLevel +} from "@nmshd/rs-crypto-types"; + +import { defaults } from "lodash"; +import { CryptoError } from "../CryptoError"; +import { CryptoErrorCode } from "../CryptoErrorCode"; +import { CryptoLayerConfig, CryptoLayerProviderFilter } from "./CryptoLayerConfig"; + +let PROVIDERS_BY_SECURITY: Map | undefined = undefined; +let PROVIDERS_BY_NAME: Map | undefined = undefined; + +const DEFAULT_PROVIDER_CONFIG: ProviderConfig = { + max_security_level: "Hardware", + min_security_level: "Software", + supported_asym_spec: ["P256", "Curve25519"], + supported_ciphers: ["AesCbc256", "AesGcm256"], + supported_hashes: ["Sha2_256", "Sha2_512"] +}; + +async function providerBySecurityMapFromProviderByNameMap( + providersByName: Map +): Promise> { + const providersBySecurity = new Map(); + for (const [providerName, provider] of providersByName) { + const caps = await provider.getCapabilities(); + if (!caps) { + throw new CryptoError( + CryptoErrorCode.CalFailedLoadingProvider, + `Failed fetching capabilities or security levels of provider ${providerName}` + ); + } + if (caps.max_security_level !== caps.min_security_level) { + throw new CryptoError( + CryptoErrorCode.CalFailedLoadingProvider, + `Minimum and maximum security levels of provider ${providerName} must be the same.` + ); + } + const securityLevel = caps.min_security_level; + + if (!providersBySecurity.has(securityLevel)) { + providersBySecurity.set(securityLevel, []); + } + + providersBySecurity.get(securityLevel)!.push(provider); + } + return providersBySecurity; +} + +/** + * Creates a provider if possible with the given provider filter. This means, that the provider created must adhere to the filter. + * + * If a `SecurityLevel` is given, the default provider config (`DEFAULT_PROVIDER_CONFIG`) will be used to fill in the rest for the selection. + */ +async function createProviderFromProviderFilter( + providerToBeInitialized: CryptoLayerProviderFilter, + factoryFunctions: ProviderFactoryFunctions, + providerImplConfig: ProviderImplConfig +): Promise { + if ("providerName" in providerToBeInitialized) { + return await factoryFunctions.createProviderFromName(providerToBeInitialized.providerName, providerImplConfig); + } + if ("securityLevel" in providerToBeInitialized) { + const providerConfig: ProviderConfig = defaults( + { + max_security_level: providerToBeInitialized.securityLevel, + min_security_level: providerToBeInitialized.securityLevel + }, + DEFAULT_PROVIDER_CONFIG + ); + return await factoryFunctions.createProvider(providerConfig, providerImplConfig); + } + if ("providerConfig" in providerToBeInitialized) { + return await factoryFunctions.createProvider(providerToBeInitialized.providerConfig, providerImplConfig); + } + + throw new CryptoError(CryptoErrorCode.WrongParameters); +} + +/** + * Intializes global providers with the given configuration. + * + * This enables the crypto layer functionality. + * + * @param config Configuration to initialize providers. At least one software provider should be initialized. + */ +export async function initCryptoLayerProviders(config: CryptoLayerConfig): Promise { + if (PROVIDERS_BY_NAME || PROVIDERS_BY_SECURITY) { + return; + } + + const providerImplConfig: ProviderImplConfig = { additional_config: [config.keyMetadataStoreConfig] }; + if (config.keyMetadataStoreAuth) { + providerImplConfig.additional_config.push(config.keyMetadataStoreAuth); + } + + const providers: Map = new Map(); + + for (const providerFilter of config.providersToBeInitialized) { + const provider = await createProviderFromProviderFilter( + providerFilter, + config.factoryFunctions, + providerImplConfig + ); + + if (!provider) { + throw new CryptoError(CryptoErrorCode.CalFailedLoadingProvider, `Failed loading provider.`); + } + + providers.set(await provider.providerName(), provider); + } + + PROVIDERS_BY_NAME = providers; + PROVIDERS_BY_SECURITY = await providerBySecurityMapFromProviderByNameMap(PROVIDERS_BY_NAME); +} + +export type ProviderIdentifier = Exclude; + +/** + * Returns an initialized provider with the given name or security level if possible. + * + * Returns `undefined` if providers are not initialized or provider asked for was not initialized by `initCryptoLayerProviders`. + */ +export function getProvider(identifier: ProviderIdentifier): Provider | undefined { + if (!PROVIDERS_BY_NAME || !PROVIDERS_BY_SECURITY) { + return undefined; + } + + if ("providerName" in identifier) { + return PROVIDERS_BY_NAME.get(identifier.providerName); + } + if ("securityLevel" in identifier) { + return PROVIDERS_BY_SECURITY.get(identifier.securityLevel)?.[0]; + } + + throw new CryptoError(CryptoErrorCode.WrongParameters); +} + +export function getProviderOrThrow(identifier: ProviderIdentifier): Provider { + const provider = getProvider(identifier); + if (!provider) { + throw new CryptoError( + CryptoErrorCode.WrongParameters, + `Failed finding provider with name or security level ${identifier}` + ); + } + return provider; +} diff --git a/src/crypto-layer/CryptoLayerUtils.ts b/src/crypto-layer/CryptoLayerUtils.ts new file mode 100644 index 0000000..7082ea1 --- /dev/null +++ b/src/crypto-layer/CryptoLayerUtils.ts @@ -0,0 +1,136 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AsymmetricKeySpec, CryptoHash, KeyPairHandle, KeyPairSpec, Provider } from "@nmshd/rs-crypto-types"; +import { defaults } from "lodash"; +import { CoreBuffer } from "../CoreBuffer"; +import { CryptoError } from "../CryptoError"; +import { CryptoErrorCode } from "../CryptoErrorCode"; +import { CryptoExchangeAlgorithm } from "../exchange/CryptoExchange"; +import { CryptoHashAlgorithm } from "../hash/CryptoHash"; +import { CryptoSignatureAlgorithm } from "../signature/CryptoSignatureAlgorithm"; +import { getProviderOrThrow, ProviderIdentifier } from "./CryptoLayerProviders"; + +export const DEFAULT_KEY_PAIR_SPEC: KeyPairSpec = { + asym_spec: "P256", + cipher: "AesGcm256", + signing_hash: "Sha2_512", + ephemeral: false, + non_exportable: false +}; + +export function asymSpecFromCryptoAlgorithm( + algorithm: CryptoExchangeAlgorithm | CryptoSignatureAlgorithm +): AsymmetricKeySpec { + switch (algorithm) { + case CryptoExchangeAlgorithm.ECDH_P256: + return "P256"; + case CryptoExchangeAlgorithm.ECDH_P521: + return "P521"; + case CryptoExchangeAlgorithm.ECDH_X25519: + return "Curve25519"; + case CryptoSignatureAlgorithm.ECDSA_P256: + return "P256"; + case CryptoSignatureAlgorithm.ECDSA_P521: + return "P521"; + case CryptoSignatureAlgorithm.ECDSA_ED25519: + return "Curve25519"; + } +} + +export function cryptoHashAlgorithmFromAsymSpec(hashFunction: CryptoHash): CryptoHashAlgorithm { + switch (hashFunction) { + case "Sha2_256": + return CryptoHashAlgorithm.SHA256; + case "Sha2_512": + return CryptoHashAlgorithm.SHA512; + default: + throw new CryptoError( + CryptoErrorCode.CalUnsupportedAlgorithm, + `Hash function ${hashFunction} is not supported by ts crypto.` + ); + } +} + +export function cryptoHashFromCryptoHashAlgorithm(algorithm: CryptoHashAlgorithm): CryptoHash { + switch (algorithm) { + case CryptoHashAlgorithm.SHA256: + return "Sha2_256"; + case CryptoHashAlgorithm.SHA512: + return "Sha2_512"; + case CryptoHashAlgorithm.BLAKE2B: + throw new CryptoError(CryptoErrorCode.CalUnsupportedAlgorithm); + } +} + +export class CryptoLayerUtils { + /** + * Returns a provider and a key pair handle from default key spec and a raw private key. + * + * @param providerIdent An identifier of an initialized provider. + * @param privateKeyBuffer The raw private key. + * @param specOverride Override default key pair spec. + * @returns Tuple with provider and handle to key pair. + * + * @throws `CryptoErrorCode.WrongParameters` if providerIdent does not match any initialized providers or if provider cannot import key pair. + */ + public static async providerAndKeyPairFromPrivateBuffer( + providerIdent: ProviderIdentifier, + privateKeyBuffer: CoreBuffer, + specOverride: Partial + ): Promise<[Provider, KeyPairHandle]> { + const provider = getProviderOrThrow(providerIdent); + const spec = defaults(specOverride, DEFAULT_KEY_PAIR_SPEC); + const keyPair = await provider.importKeyPair(spec, new Uint8Array(0), privateKeyBuffer.buffer); + return [provider, keyPair]; + } + + public static async providerAndKeyPairFromPrivateBufferWithAlgorithm( + providerIdent: ProviderIdentifier, + privateKeyBuffer: CoreBuffer, + asymmetricAlgorithm?: CryptoExchangeAlgorithm | CryptoSignatureAlgorithm, + hashAlgorithm?: CryptoHashAlgorithm + ): Promise<[Provider, KeyPairHandle]> { + const override: Partial = { + asym_spec: asymmetricAlgorithm ? asymSpecFromCryptoAlgorithm(asymmetricAlgorithm) : undefined, + signing_hash: hashAlgorithm ? cryptoHashFromCryptoHashAlgorithm(hashAlgorithm) : undefined + }; + return await this.providerAndKeyPairFromPrivateBuffer(providerIdent, privateKeyBuffer, override); + } + + /** + * Returns a provider and a key pair handle from default key spec and a raw public key. + * + * @param providerIdent An identifier of an initialized provider. + * @param privateKeyBuffer The raw public key. + * @param specOverride Override default key pair spec. + * @returns Tuple with provider and handle to key pair. + * + * @throws `CryptoErrorCode.WrongParameters` if providerIdent does not match any initialized providers or if provider cannot import key pair. + */ + public static async providerAndKeyPairFromPublicBuffer( + providerIdent: ProviderIdentifier, + publicKeyBuffer: CoreBuffer, + specOverride: Partial + ): Promise<[Provider, KeyPairHandle]> { + const provider = getProviderOrThrow(providerIdent); + const spec = defaults(specOverride, DEFAULT_KEY_PAIR_SPEC); + const keyPair = await provider.importPublicKey(spec, publicKeyBuffer.buffer); + return [provider, keyPair]; + } + + public static async providerAndKeyPairFromPublicBufferWithAlgorithm( + providerIdent: ProviderIdentifier, + privateKeyBuffer: CoreBuffer, + asymmetricAlgorithm?: CryptoExchangeAlgorithm | CryptoSignatureAlgorithm, + hashAlgorithm?: CryptoHashAlgorithm + ): Promise { + const override: Partial = { + asym_spec: asymmetricAlgorithm ? asymSpecFromCryptoAlgorithm(asymmetricAlgorithm) : undefined, + signing_hash: hashAlgorithm ? cryptoHashFromCryptoHashAlgorithm(hashAlgorithm) : undefined + }; + return await this.providerAndKeyPairFromPublicBuffer(providerIdent, privateKeyBuffer, override); + } + + public static getHashAlgorithm(key: T): CryptoHashAlgorithm { + return cryptoHashAlgorithmFromAsymSpec(key.spec.signing_hash); + } +} diff --git a/src/crypto-layer/CryptoPrivateKeyHandle.ts b/src/crypto-layer/CryptoPrivateKeyHandle.ts new file mode 100644 index 0000000..8ca1a9f --- /dev/null +++ b/src/crypto-layer/CryptoPrivateKeyHandle.ts @@ -0,0 +1,61 @@ +import { type } from "@js-soft/ts-serval"; +import { KeyPairHandle, KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { CoreBuffer, Encoding } from "src/CoreBuffer"; +import { CryptoAsymmetricKeyHandle } from "./CryptoAsymmetricKeyHandle"; +import { getProviderOrThrow, ProviderIdentifier } from "./CryptoLayerProviders"; + +export interface ICryptoPrivateKeyHandle { + keyPairHandle: KeyPairHandle; + spec: KeyPairSpec; + toSerializedString(): Promise; + toPEM(): Promise; +} + +export interface ICryptoPrivateKeyHandleStatic { + new (): ICryptoPrivateKeyHandle; + fromPEM(providerIdent: ProviderIdentifier, pem: string, spec: KeyPairSpec): Promise; + fromString( + providerIdent: ProviderIdentifier, + value: string, + spec: KeyPairSpec, + encoding: Encoding + ): Promise; + // fromNativeKey(providerIdent: ProviderIdentifier, key: any, config: KeyPairSpec): Promise; +} + +@type("CryptoPrivateKeyHandle") +export class CryptoPrivateKeyHandle extends CryptoAsymmetricKeyHandle implements ICryptoPrivateKeyHandle { + public async toSerializedString(): Promise { + const raw = await this.keyPairHandle.extractKey(); + return CoreBuffer.from(raw).toString(Encoding.Base64_UrlSafe_NoPadding); + } + + public async toPEM(): Promise { + const raw = await this.keyPairHandle.extractKey(); + return CoreBuffer.from(raw).toString(Encoding.Pem, "PRIVATE KEY"); + } + + public static async fromString( + providerIdent: ProviderIdentifier, + value: string, + spec: KeyPairSpec, + encoding: Encoding + ): Promise { + const raw = CoreBuffer.fromString(value, encoding).buffer; + const provider = getProviderOrThrow(providerIdent); + const keyPairHandle = await provider.importKeyPair(spec, new Uint8Array(0), raw); + return await CryptoPrivateKeyHandle.newFromProviderAndKeyPairHandle(provider, keyPairHandle, { + keySpec: spec + }); + } + + public static async fromPEM( + providerIdent: ProviderIdentifier, + pem: string, + spec: KeyPairSpec + ): Promise { + return await CryptoPrivateKeyHandle.fromString(providerIdent, pem, spec, Encoding.Pem); + } +} + +const _testAssign: ICryptoPrivateKeyHandleStatic = CryptoPrivateKeyHandle; diff --git a/src/crypto-layer/CryptoPublicKeyHandle.ts b/src/crypto-layer/CryptoPublicKeyHandle.ts new file mode 100644 index 0000000..e4cd16b --- /dev/null +++ b/src/crypto-layer/CryptoPublicKeyHandle.ts @@ -0,0 +1,61 @@ +import { type } from "@js-soft/ts-serval"; +import { KeyPairHandle, KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { CoreBuffer, Encoding } from "src/CoreBuffer"; +import { CryptoAsymmetricKeyHandle } from "./CryptoAsymmetricKeyHandle"; +import { getProviderOrThrow, ProviderIdentifier } from "./CryptoLayerProviders"; + +export interface ICryptoPublicKeyHandle { + keyPairHandle: KeyPairHandle; + spec: KeyPairSpec; + providerName: string; + toSerializedString(): Promise; + toPEM(): Promise; + toJSON(): Object; +} + +export interface ICryptoPublicKeyHandleStatic { + new (): ICryptoPublicKeyHandle; + fromPEM(providerIdent: ProviderIdentifier, pem: string, spec: KeyPairSpec): Promise; + fromString( + providerIdent: ProviderIdentifier, + value: string, + spec: KeyPairSpec, + encoding: Encoding + ): Promise; + // fromNativeKey(key: any, spec: KeyPairSpec): Promise; +} + +@type("CryptoPublicKeyHandle") +export class CryptoPublicKeyHandle extends CryptoAsymmetricKeyHandle implements ICryptoPublicKeyHandle { + public async toSerializedString(): Promise { + const raw = await this.keyPairHandle.getPublicKey(); + return CoreBuffer.from(raw).toString(Encoding.Base64_UrlSafe_NoPadding); + } + + public async toPEM(): Promise { + const raw = await this.keyPairHandle.getPublicKey(); + return CoreBuffer.from(raw).toString(Encoding.Pem, "PRIVATE KEY"); + } + + public static async fromString( + providerIdent: ProviderIdentifier, + value: string, + spec: KeyPairSpec, + encoding: Encoding + ): Promise { + const raw = CoreBuffer.fromString(value, encoding).buffer; + const provider = getProviderOrThrow(providerIdent); + const keyPairHandle = await provider.importPublicKey(spec, raw); + return await CryptoPublicKeyHandle.newFromProviderAndKeyPairHandle(provider, keyPairHandle, { + keySpec: spec + }); + } + + public static async fromPEM( + providerIdent: ProviderIdentifier, + pem: string, + spec: KeyPairSpec + ): Promise { + return await CryptoPublicKeyHandle.fromString(providerIdent, pem, spec, Encoding.Pem); + } +} diff --git a/src/crypto-layer/index.ts b/src/crypto-layer/index.ts new file mode 100644 index 0000000..f68a06e --- /dev/null +++ b/src/crypto-layer/index.ts @@ -0,0 +1,5 @@ +export * from "./CryptoExportedPublicKey"; +export * from "./CryptoLayerConfig"; +export * from "./CryptoLayerProviders"; +export * from "./CryptoPrivateKeyHandle"; +export * from "./CryptoPublicKeyHandle"; diff --git a/src/crypto-layer/signature/CryptoSignatureKeypair.ts b/src/crypto-layer/signature/CryptoSignatureKeypair.ts new file mode 100644 index 0000000..8704da6 --- /dev/null +++ b/src/crypto-layer/signature/CryptoSignatureKeypair.ts @@ -0,0 +1,105 @@ +import { ISerializable, ISerialized, serialize, type, validate } from "@js-soft/ts-serval"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoError } from "src/CryptoError"; +import { CryptoErrorCode } from "src/CryptoErrorCode"; +import { CryptoSerializableAsync } from "src/CryptoSerializable"; +import { + CryptoSignaturePrivateKeyHandle, + ICryptoSignaturePrivateKeyHandle, + ICryptoSignaturePrivateKeyHandleSerialized +} from "./CryptoSignaturePrivateKeyHandle"; +import { + CryptoSignaturePublicKeyHandle, + ICryptoSignaturePublicKeyHandle, + ICryptoSignaturePublicKeyHandleSerialized +} from "./CryptoSignaturePublicKeyHandle"; + +export interface ICryptoSignatureKeypairHandleSerialized extends ISerialized { + pub: ICryptoSignaturePublicKeyHandleSerialized; + prv: ICryptoSignaturePrivateKeyHandleSerialized; +} + +export interface ICryptoSignatureKeypairHandle extends ISerializable { + publicKey: ICryptoSignaturePublicKeyHandle; + privateKey: ICryptoSignaturePrivateKeyHandle; +} + +@type("CryptoSignatureKeypairHandle") +export class CryptoSignatureKeypairHandle extends CryptoSerializableAsync implements ICryptoSignatureKeypairHandle { + @validate() + @serialize() + public publicKey: CryptoSignaturePublicKeyHandle; + + @validate() + @serialize() + public privateKey: CryptoSignaturePrivateKeyHandle; + + public override toJSON(verbose = true): ICryptoSignatureKeypairHandleSerialized { + return { + pub: this.publicKey.toJSON(false), + prv: this.privateKey.toJSON(false), + "@type": verbose ? "CryptoSignatureKeypairHandle" : undefined + }; + } + + public override toBase64(verbose = true): string { + return CoreBuffer.utf8_base64(this.serialize(verbose)); + } + + public static async from( + value: CryptoSignatureKeypairHandle | ICryptoSignatureKeypairHandle + ): Promise { + return await this.fromAny(value); + } + + public static fromPublicAndPrivateKeys( + publicKey: CryptoSignaturePublicKeyHandle, + privateKey: CryptoSignaturePrivateKeyHandle + ): CryptoSignatureKeypairHandle { + const keyPair = new this(); + keyPair.privateKey = privateKey; + keyPair.publicKey = publicKey; + return keyPair; + } + + protected static override preFrom(value: any): any { + if (value.pub) { + value = { publicKey: value.pub, privateKey: value.prv }; + } + + if (value.privateKey && value.privateKey.spec !== value.publicKey.spec) { + throw new CryptoError( + CryptoErrorCode.SignatureWrongAlgorithm, + "Spec of private and public key handles do not match." + ); + } + + // Strips the neon JsBox. Otherwise ts-serval will use the neon objects for the + // new CryptoSignatureKeypairHandle and change them in a way that makes them unusable. + if (value.privateKey.keyPairHandle) { + value = { + publicKey: { + id: value.publicKey.id, + spec: value.publicKey.spec, + providerName: value.publicKey.providerName + }, + privateKey: { + id: value.privateKey.id, + spec: value.privateKey.spec, + providerName: value.privateKey.providerName + } + }; + } + return value; + } + + public static async fromJSON( + value: ICryptoSignatureKeypairHandleSerialized + ): Promise { + return await this.fromAny(value); + } + + public static async fromBase64(value: string): Promise { + return await this.deserialize(CoreBuffer.base64_utf8(value)); + } +} diff --git a/src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle.ts b/src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle.ts new file mode 100644 index 0000000..d6110a7 --- /dev/null +++ b/src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle.ts @@ -0,0 +1,68 @@ +import { ISerializable, ISerialized, type } from "@js-soft/ts-serval"; +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoPrivateKeyHandle } from "../CryptoPrivateKeyHandle"; +import { CryptoSignaturePublicKeyHandle } from "./CryptoSignaturePublicKeyHandle"; + +export interface ICryptoSignaturePrivateKeyHandleSerialized extends ISerialized { + spc: KeyPairSpec; // Specification/Config of key pair stored. + cid: string; // Crypto layer key pair id used for loading key from a provider. + pnm: string; // Provider name +} +export interface ICryptoSignaturePrivateKeyHandle extends ISerializable { + spec: KeyPairSpec; + id: string; + providerName: string; +} + +@type("CryptoSignaturePrivateKeyHandle") +export class CryptoSignaturePrivateKeyHandle extends CryptoPrivateKeyHandle { + public override toJSON(verbose = true): ICryptoSignaturePrivateKeyHandleSerialized { + return { + spc: this.spec, + cid: this.id, + pnm: this.providerName, + "@type": verbose ? "CryptoSignaturePrivateKeyHandle" : undefined + }; + } + + public override toBase64(verbose = true): string { + return CoreBuffer.utf8_base64(this.serialize(verbose)); + } + + public async toPublicKey(): Promise { + return await CryptoSignaturePublicKeyHandle.newFromProviderAndKeyPairHandle(this.provider, this.keyPairHandle, { + providerName: this.providerName, + keyId: this.id, + keySpec: this.spec + }); + } + + public static override async from( + value: CryptoSignaturePrivateKeyHandle | ICryptoSignaturePrivateKeyHandle + ): Promise { + return await this.fromAny(value); + } + + public static override preFrom(value: any): any { + if (value.cid) { + value = { + spec: value.spc, + id: value.cid, + providerName: value.pnm + }; + } + + return value; + } + + public static async fromJSON( + value: ICryptoSignaturePrivateKeyHandleSerialized + ): Promise { + return await this.fromAny(value); + } + + public static override async fromBase64(value: string): Promise { + return await this.deserialize(CoreBuffer.base64_utf8(value)); + } +} diff --git a/src/crypto-layer/signature/CryptoSignaturePublicKeyHandle.ts b/src/crypto-layer/signature/CryptoSignaturePublicKeyHandle.ts new file mode 100644 index 0000000..3cf3aff --- /dev/null +++ b/src/crypto-layer/signature/CryptoSignaturePublicKeyHandle.ts @@ -0,0 +1,59 @@ +import { ISerializable, ISerialized, type } from "@js-soft/ts-serval"; +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoPublicKeyHandle } from "../CryptoPublicKeyHandle"; + +export interface ICryptoSignaturePublicKeyHandleSerialized extends ISerialized { + spc: KeyPairSpec; // Specification/Config of key pair stored. + cid: string; // Crypto layer key pair id used for loading key from a provider. + pnm: string; // Provider name +} +export interface ICryptoSignaturePublicKeyHandle extends ISerializable { + spec: KeyPairSpec; + id: string; + providerName: string; +} + +@type("CryptoSignaturePublicKeyHandle") +export class CryptoSignaturePublicKeyHandle extends CryptoPublicKeyHandle { + public override toJSON(verbose = true): ICryptoSignaturePublicKeyHandleSerialized { + return { + spc: this.spec, + cid: this.id, + pnm: this.providerName, + "@type": verbose ? "CryptoSignaturePublicKeyHandle" : undefined + }; + } + + public override toBase64(verbose = true): string { + return CoreBuffer.utf8_base64(this.serialize(verbose)); + } + + public static override async from( + value: CryptoSignaturePublicKeyHandle | ICryptoSignaturePublicKeyHandle + ): Promise { + return await this.fromAny(value); + } + + public static override preFrom(value: any): any { + if (value.cid) { + value = { + spec: value.spc, + id: value.cid, + providerName: value.pnm + }; + } + + return value; + } + + public static async fromJSON( + value: ICryptoSignaturePublicKeyHandleSerialized + ): Promise { + return await this.fromAny(value); + } + + public static override async fromBase64(value: string): Promise { + return await this.deserialize(CoreBuffer.base64_utf8(value)); + } +} diff --git a/src/crypto-layer/signature/CryptoSignatures.ts b/src/crypto-layer/signature/CryptoSignatures.ts new file mode 100644 index 0000000..cdafbaf --- /dev/null +++ b/src/crypto-layer/signature/CryptoSignatures.ts @@ -0,0 +1,79 @@ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoError } from "src/CryptoError"; +import { CryptoErrorCode } from "src/CryptoErrorCode"; +import { CryptoSignature } from "src/signature/CryptoSignature"; +import { CryptoSignatureValidation } from "src/signature/CryptoSignatureValidation"; +import { getProviderOrThrow, ProviderIdentifier } from "../CryptoLayerProviders"; +import { cryptoHashFromCryptoHashAlgorithm, CryptoLayerUtils } from "../CryptoLayerUtils"; +import { CryptoSignatureKeypairHandle } from "./CryptoSignatureKeypair"; +import { CryptoSignaturePrivateKeyHandle } from "./CryptoSignaturePrivateKeyHandle"; +import { CryptoSignaturePublicKeyHandle } from "./CryptoSignaturePublicKeyHandle"; + +export class CryptoSignaturesWithCryptoLayer { + public static async privateKeyToPublicKey( + privateKey: CryptoSignaturePrivateKeyHandle + ): Promise { + return await privateKey.toPublicKey(); + } + + /** + * Generates a keypair for the specified elliptic algorithm + * @param algorithm + */ + public static async generateKeypair( + providerIdent: ProviderIdentifier, + spec: KeyPairSpec + ): Promise { + const provider = getProviderOrThrow(providerIdent); + const rawKeyPairHandle = await provider.createKeyPair(spec); + const privateKey = await CryptoSignaturePrivateKeyHandle.newFromProviderAndKeyPairHandle( + provider, + rawKeyPairHandle + ); + const publicKey = await privateKey.toPublicKey(); + + return CryptoSignatureKeypairHandle.fromPublicAndPrivateKeys(publicKey, privateKey); + } + + public static async sign( + content: CoreBuffer, + privateKey: CryptoSignaturePrivateKeyHandle, + id?: string + ): Promise { + CryptoSignatureValidation.checkBuffer(content, 1); + + try { + const signatureArray = await privateKey.keyPairHandle.signData(content.buffer); + const algorithm = CryptoLayerUtils.getHashAlgorithm(privateKey); + const keyId = privateKey.id; + + const signatureBuffer: CoreBuffer = new CoreBuffer(signatureArray); + const signature = CryptoSignature.from({ signature: signatureBuffer, algorithm, keyId, id }); + return signature; + } catch (e) { + throw new CryptoError(CryptoErrorCode.SignatureSign, `${e}`); + } + } + + public static async verify( + content: CoreBuffer, + signature: CryptoSignature, + publicKey: CryptoSignaturePublicKeyHandle + ): Promise { + CryptoSignatureValidation.checkBuffer(content, 1); + + if (cryptoHashFromCryptoHashAlgorithm(signature.algorithm) !== publicKey.spec.signing_hash) { + throw new CryptoError( + CryptoErrorCode.SignatureWrongAlgorithm, + `Algorithm ${signature.algorithm} != the algorithm the public key was initialized with.` + ); + } + + try { + return await publicKey.keyPairHandle.verifySignature(content.buffer, signature.signature.buffer); + } catch (e) { + throw new CryptoError(CryptoErrorCode.SignatureVerify, `${e}`); + } + } +} diff --git a/src/crypto-layer/signature/index.ts b/src/crypto-layer/signature/index.ts new file mode 100644 index 0000000..092dabb --- /dev/null +++ b/src/crypto-layer/signature/index.ts @@ -0,0 +1,4 @@ +export * from "./CryptoSignatureKeypair"; +export * from "./CryptoSignaturePrivateKeyHandle"; +export * from "./CryptoSignaturePublicKeyHandle"; +export * from "./CryptoSignatures"; diff --git a/src/encryption/CryptoEncryption.ts b/src/encryption/CryptoEncryption.ts index 9bedb00..4cd0282 100644 --- a/src/encryption/CryptoEncryption.ts +++ b/src/encryption/CryptoEncryption.ts @@ -1,11 +1,13 @@ +import { Cipher, KeySpec } from "@nmshd/rs-crypto-types"; +import { getProviderOrThrow, ProviderIdentifier } from "src/crypto-layer/CryptoLayerProviders"; import { CoreBuffer } from "../CoreBuffer"; import { CryptoError } from "../CryptoError"; import { CryptoErrorCode } from "../CryptoErrorCode"; import { CryptoValidation } from "../CryptoValidation"; import { SodiumWrapper } from "../SodiumWrapper"; import { CryptoCipher } from "./CryptoCipher"; +import { CryptoLayerSecretKey } from "./CryptoLayerSecretKey"; import { CryptoSecretKey } from "./CryptoSecretKey"; - /** * The symmetric encryption algorithm to use. */ @@ -28,7 +30,67 @@ export const enum CryptoEncryptionAlgorithm { XCHACHA20_POLY1305 = 3 } +export class CryptoEncryptionAlgorithmUtil { + public static toCalCipher(alg: CryptoEncryptionAlgorithm): Cipher { + switch (alg) { + case CryptoEncryptionAlgorithm.AES128_GCM: + return "AesGcm128"; + case CryptoEncryptionAlgorithm.AES256_GCM: + return "AesGcm256"; + case CryptoEncryptionAlgorithm.XCHACHA20_POLY1305: + return "XChaCha20Poly1305"; + } + } + + public static fromCalCipher(cipher: Cipher): CryptoEncryptionAlgorithm { + switch (cipher) { + case "AesGcm128": + return CryptoEncryptionAlgorithm.AES128_GCM; + case "AesGcm256": + return CryptoEncryptionAlgorithm.AES256_GCM; + case "XChaCha20Poly1305": + return CryptoEncryptionAlgorithm.XCHACHA20_POLY1305; + default: + throw new CryptoError(CryptoErrorCode.NotYetImplemented); + } + } +} + export abstract class CryptoEncryption { + /* public static async generateKey( + algorithm: CryptoEncryptionAlgorithm = CryptoEncryptionAlgorithm.XCHACHA20_POLY1305, + provider?: Provider + ): Promise { + CryptoValidation.checkEncryptionAlgorithm(algorithm); + + if (provider) { + const spec: KeySpec = { + ephemeral: false, + // eslint-disable-next-line @typescript-eslint/naming-convention + signing_hash: "Sha2_256", + cipher: CryptoEncryptionAlgorithmUtil.toCalCipher(algorithm) + }; + const key = await provider.createKey(spec); + return await CryptoLayerSecretKey.getFromHandle(await provider.providerName(), await key.id()); + } + + let buffer: CoreBuffer; + switch (algorithm) { + case CryptoEncryptionAlgorithm.XCHACHA20_POLY1305: + try { + buffer = new CoreBuffer((await SodiumWrapper.ready()).crypto_aead_xchacha20poly1305_ietf_keygen()); + } catch (e) { + throw new CryptoError(CryptoErrorCode.EncryptionKeyGeneration, `${e}`); + } + + break; + default: + throw new CryptoError(CryptoErrorCode.NotYetImplemented); + } + + return CryptoSecretKey.from({ secretKey: buffer, algorithm }); + } */ + public static async generateKey( algorithm: CryptoEncryptionAlgorithm = CryptoEncryptionAlgorithm.XCHACHA20_POLY1305 ): Promise { @@ -51,6 +113,15 @@ export abstract class CryptoEncryption { return CryptoSecretKey.from({ secretKey: buffer, algorithm }); } + public static async generateKeyHandle( + spec: KeySpec, + providerIdent: ProviderIdentifier + ): Promise { + const provider = getProviderOrThrow(providerIdent); + const key = await provider.createKey(spec); + return await CryptoLayerSecretKey.getFromHandle(await provider.providerName(), await key.id()); + } + /** * Encrypts a given plaintext [[CoreBuffer]] object with the given secretKey. If a nonce is set, * please be advised that this nonce MUST be uniquely used for this secretKey. The nonce MUST be @@ -63,10 +134,27 @@ export abstract class CryptoEncryption { */ public static async encrypt( plaintext: CoreBuffer, - secretKey: CryptoSecretKey | CoreBuffer, + secretKey: CryptoSecretKey | CoreBuffer | CryptoLayerSecretKey, nonce?: CoreBuffer, algorithm: CryptoEncryptionAlgorithm = CryptoEncryptionAlgorithm.XCHACHA20_POLY1305 ): Promise { + // CryptoLayerSecretKey uses its KeyHandle encryption method + if (secretKey instanceof CryptoLayerSecretKey) { + if (algorithm !== secretKey.algorithm) { + throw new CryptoError( + CryptoErrorCode.EncryptionWrongAlgorithm, + "The algorithm of the secret key does not match the given algorithm." + ); + } + const buffer = plaintext.buffer; + const [secretbuffer, iv] = await secretKey.keyHandle.encryptData(buffer); + return CryptoCipher.from({ + cipher: CoreBuffer.from(secretbuffer), + algorithm: secretKey.algorithm, + nonce: CoreBuffer.from(iv) + }); + } + let correctAlgorithm; let secretKeyBuffer; @@ -176,10 +264,30 @@ export abstract class CryptoEncryption { public static async decrypt( cipher: CryptoCipher, - secretKey: CryptoSecretKey | CoreBuffer, + secretKey: CryptoSecretKey | CoreBuffer | CryptoLayerSecretKey, nonce?: CoreBuffer, algorithm: CryptoEncryptionAlgorithm = CryptoEncryptionAlgorithm.XCHACHA20_POLY1305 ): Promise { + if (secretKey instanceof CryptoLayerSecretKey) { + if (algorithm !== secretKey.algorithm) { + throw new CryptoError( + CryptoErrorCode.EncryptionWrongAlgorithm, + "The algorithm of the secret key does not match the given algorithm." + ); + } + if (!nonce && !cipher.nonce) { + throw new CryptoError( + CryptoErrorCode.EncryptionWrongNonce, + "Cipher does not contain a nonce and no nonce is given." + ); + } + + const buffer = cipher.cipher.buffer; + const iv = nonce ? nonce.buffer : cipher.nonce!.buffer; + const plaintext = await secretKey.keyHandle.decryptData(buffer, iv); + return CoreBuffer.from(plaintext); + } + let correctAlgorithm; let secretKeyBuffer; diff --git a/src/encryption/CryptoLayerSecretKey.ts b/src/encryption/CryptoLayerSecretKey.ts new file mode 100644 index 0000000..304a1ad --- /dev/null +++ b/src/encryption/CryptoLayerSecretKey.ts @@ -0,0 +1,71 @@ +import { ISerialized, serialize, type, validate } from "@js-soft/ts-serval"; +import { KeyHandle } from "@nmshd/rs-crypto-types"; +import { getProviderOrThrow } from "src/crypto-layer/CryptoLayerProviders"; +import { CryptoSerializableAsync } from "src/CryptoSerializable"; +import { CryptoValidation } from "../CryptoValidation"; +import { CryptoEncryptionAlgorithm, CryptoEncryptionAlgorithmUtil } from "./CryptoEncryption"; + +interface ICryptoLayerSecretKeySerialized extends ISerialized { + algorithm: number; + id: string; + providername: string; +} + +@type("CryptoLayerSecretKey") +export class CryptoLayerSecretKey extends CryptoSerializableAsync { + @validate() + @serialize() + public algorithm: CryptoEncryptionAlgorithm; + + @validate() + @serialize() + public id: string; + + @validate() + @serialize() + public providername: string; + + public keyHandle: KeyHandle; + + public static async getFromHandle(providername: string, id: string): Promise { + // load keyHandle from the id + const provider = getProviderOrThrow({ providerName: providername }); + + const key = await provider.loadKey(id); + const alg = CryptoEncryptionAlgorithmUtil.fromCalCipher((await key.spec()).cipher); + return await this.fromAny({ algorithm: alg, id, providername }); + } + + public constructor(algorithm: CryptoEncryptionAlgorithm, id: string, providername: string) { + super(); + + this.algorithm = algorithm; + this.id = id; + this.providername = providername; + } + + public override toJSON(verbose = true): ICryptoLayerSecretKeySerialized { + return { + id: this.id, + algorithm: this.algorithm, + providername: this.providername, + "@type": verbose ? "CryptoLayerSecretKey" : "CryptoLayerSecretKey" + }; + } + + protected static override async postFrom(value: any): Promise { + CryptoValidation.checkEncryptionAlgorithm(value.algorithm); + + // load keyHandle from the id + const provider = getProviderOrThrow(value.providername); + + const key = await provider.loadKey(value.id); + + value.keyHandle = key; + return value; + } + + public static fromJSON(value: ICryptoLayerSecretKeySerialized): Promise { + return this.fromAny(value); + } +} diff --git a/src/index.ts b/src/index.ts index 845b583..f7b6beb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from "./BaseX"; export * from "./buildInformation"; export * from "./CoreBuffer"; +export * from "./crypto-layer"; export * from "./CryptoDerivation"; export * from "./CryptoError"; export * from "./CryptoErrorCode"; diff --git a/src/signature/CryptoSignatures.ts b/src/signature/CryptoSignatures.ts index 7531844..8f2e38c 100644 --- a/src/signature/CryptoSignatures.ts +++ b/src/signature/CryptoSignatures.ts @@ -1,3 +1,10 @@ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { ProviderIdentifier } from "src/crypto-layer/CryptoLayerProviders"; +import { cryptoHashFromCryptoHashAlgorithm } from "src/crypto-layer/CryptoLayerUtils"; +import { CryptoSignatureKeypairHandle } from "src/crypto-layer/signature/CryptoSignatureKeypair"; +import { CryptoSignaturePrivateKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle"; +import { CryptoSignaturePublicKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePublicKeyHandle"; +import { CryptoSignaturesWithCryptoLayer } from "src/crypto-layer/signature/CryptoSignatures"; import { CoreBuffer } from "../CoreBuffer"; import { CryptoError } from "../CryptoError"; import { CryptoErrorCode } from "../CryptoErrorCode"; @@ -10,7 +17,7 @@ import { CryptoSignaturePrivateKey } from "./CryptoSignaturePrivateKey"; import { CryptoSignaturePublicKey } from "./CryptoSignaturePublicKey"; import { CryptoSignatureValidation } from "./CryptoSignatureValidation"; -export class CryptoSignatures { +export class CryptoSignaturesWithLibsodium { public static async privateKeyToPublicKey( privateKey: CryptoSignaturePrivateKey ): Promise { @@ -142,3 +149,49 @@ export class CryptoSignatures { return buffer.buffer; } } + +export class CryptoSignatures extends CryptoSignaturesWithLibsodium { + public static async privateKeyHandleToPublicKeyHandle( + privateKey: CryptoSignaturePrivateKeyHandle + ): Promise { + return await CryptoSignaturesWithCryptoLayer.privateKeyToPublicKey(privateKey); + } + + public static async generateKeypairHandle( + providerIdent: ProviderIdentifier, + spec: KeyPairSpec + ): Promise { + return await CryptoSignaturesWithCryptoLayer.generateKeypair(providerIdent, spec); + } + + public static override async sign( + content: CoreBuffer, + privateKey: CryptoSignaturePrivateKey | CoreBuffer | CryptoSignaturePrivateKeyHandle, + algorithm: CryptoHashAlgorithm = CryptoHashAlgorithm.SHA512, + keyId?: string, + id?: string + ): Promise { + if (privateKey instanceof CryptoSignaturePrivateKeyHandle) { + if (cryptoHashFromCryptoHashAlgorithm(algorithm) !== privateKey.spec.signing_hash) { + throw new CryptoError( + CryptoErrorCode.SignatureWrongAlgorithm, + `Algorithm ${algorithm} != the algorithm the private key was initialized with.` + ); + } + return await CryptoSignaturesWithCryptoLayer.sign(content, privateKey, id); + } + + return await super.sign(content, privateKey, algorithm, keyId, id); + } + + public static override async verify( + content: CoreBuffer, + signature: CryptoSignature, + publicKey: CryptoSignaturePublicKey | CoreBuffer | CryptoSignaturePublicKeyHandle + ): Promise { + if (publicKey instanceof CryptoSignaturePublicKeyHandle) { + return await CryptoSignaturesWithCryptoLayer.verify(content, signature, publicKey); + } + return await super.verify(content, signature, publicKey); + } +} diff --git a/test/crypto-layer/CryptoExportedPublicKey.test.ts b/test/crypto-layer/CryptoExportedPublicKey.test.ts new file mode 100644 index 0000000..1b93520 --- /dev/null +++ b/test/crypto-layer/CryptoExportedPublicKey.test.ts @@ -0,0 +1,41 @@ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { expect } from "chai"; +import { CoreBuffer } from "src/CoreBuffer"; +import { CryptoExportedPublicKey } from "src/crypto-layer"; +import { CryptoPublicKeyHandle } from "src/crypto-layer/CryptoPublicKeyHandle"; +import { CryptoSignatures } from "src/signature/CryptoSignatures"; +import { expectCryptoSignatureAsymmetricKeyHandle } from "./CryptoLayerTestUtil"; + +/* eslint-disable @typescript-eslint/naming-convention */ +export class CryptoExportedPublicKeyTest { + public static run(): void { + describe("CryptoExportedPublicKey", function () { + const spec: KeyPairSpec = { + asym_spec: "P256", + cipher: null, + signing_hash: "Sha2_512", + ephemeral: false, + non_exportable: false + }; + + const providerIdent = { providerName: "SoftwareProvider" }; + it("from and to CryptoPublicKeyHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const publicKeyHandle = cryptoKeyPairHandle.publicKey; + const id = publicKeyHandle.id; + await expectCryptoSignatureAsymmetricKeyHandle(publicKeyHandle, id, spec, providerIdent.providerName); + + const exportedPublicKey = await CryptoExportedPublicKey.from(publicKeyHandle); + expect(exportedPublicKey.rawPublicKey).to.be.instanceOf(CoreBuffer); + expect(exportedPublicKey.spec).to.be.deep.equal(publicKeyHandle.spec); + + const importedPublicKey = await exportedPublicKey.into(CryptoPublicKeyHandle, providerIdent); + expect(importedPublicKey).to.be.ok.and.to.be.instanceOf(CryptoPublicKeyHandle); + expect(await importedPublicKey.keyPairHandle.getPublicKey()).to.deep.equal( + await publicKeyHandle.keyPairHandle.getPublicKey() + ); + expect(importedPublicKey.spec).to.deep.equal(publicKeyHandle.spec); + }); + }); + } +} diff --git a/test/crypto-layer/CryptoLayerProviderTest.test.ts b/test/crypto-layer/CryptoLayerProviderTest.test.ts new file mode 100644 index 0000000..c27293f --- /dev/null +++ b/test/crypto-layer/CryptoLayerProviderTest.test.ts @@ -0,0 +1,24 @@ +import { getProviderOrThrow } from "@nmshd/crypto"; +import { expect } from "chai"; + +export class CryptoLayerProviderTest { + public static run(): void { + describe("CryptoLayerProvider", function () { + it("getProvider() should return a valid provider", async function () { + const provider = getProviderOrThrow({ providerName: "SoftwareProvider" }); + expect(provider).to.exist; + expect(provider.createKey).to.exist.that.is.a("function"); + expect(provider.loadKey).to.exist.that.is.a("function"); + expect(provider.importKey).to.exist.that.is.a("function"); + expect(provider.createKeyPair).to.exist.that.is.a("function"); + expect(provider.loadKeyPair).to.exist.that.is.a("function"); + expect(provider.importKeyPair).to.exist.that.is.a("function"); + expect(provider.importPublicKey).to.exist.that.is.a("function"); + expect(provider.getCapabilities).to.exist.that.is.a("function"); + expect(provider.providerName).to.exist.that.is.a("function"); + expect(provider.startEphemeralDhExchange).to.exist.that.is.a("function"); + expect(await provider.providerName()).to.equal("SoftwareProvider"); + }); + }); + } +} diff --git a/test/crypto-layer/CryptoLayerTestUtil.ts b/test/crypto-layer/CryptoLayerTestUtil.ts new file mode 100644 index 0000000..eebc141 --- /dev/null +++ b/test/crypto-layer/CryptoLayerTestUtil.ts @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AsymmetricKeySpec, Cipher, CryptoHash, KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { expect } from "chai"; +import { defaults } from "lodash"; +import { CryptoAsymmetricKeyHandle } from "src/crypto-layer/CryptoAsymmetricKeyHandle"; + +export interface ParametersKeyPairSpec { + asymSpec: AsymmetricKeySpec[]; + cipher: (Cipher | null)[]; + signingHash: CryptoHash[]; + ephemeral: boolean[]; + nonExportable: boolean[]; +} + +export function parameterizedKeyPairSpec( + name: string, + testFunction: (spec: KeyPairSpec) => Promise, + override?: Partial +): void { + const matrix: ParametersKeyPairSpec = defaults(override, { + asymSpec: ["P256", "Curve25519"], + cipher: [null, "AesCbc256", "AesGcm256", "AesCbc128", "AesGcm128"], + signingHash: ["Sha2_512", "Sha2_256"], + ephemeral: [false, true], + nonExportable: [false, true] + }); + + matrix.asymSpec.forEach((asym_spec) => { + matrix.signingHash.forEach((signing_hash) => { + matrix.cipher.forEach((cipher) => { + matrix.ephemeral.forEach((ephemeral) => { + matrix.nonExportable.forEach((non_exportable) => { + // eslint-disable-next-line jest/expect-expect + it(`${name} with ${asym_spec}, ${signing_hash}${cipher ? `, ${cipher}` : ""}${ephemeral ? `, ephemeral` : ""}${non_exportable ? `, non_exportable` : ""}`, async function () { + const spec: KeyPairSpec = { + asym_spec: asym_spec, + cipher: cipher, + signing_hash: signing_hash, + ephemeral: ephemeral, + non_exportable: non_exportable + }; + await testFunction(spec); + }); + }); + }); + }); + }); + }); +} + +export async function expectCryptoSignatureAsymmetricKeyHandle( + value: T, + id: string, + spec: KeyPairSpec, + providerName: string +): Promise { + expect(value).to.be.instanceOf(CryptoAsymmetricKeyHandle); + expect(value.id).to.equal(id); + expect(value.providerName).to.equal(providerName); + expect(value.spec).to.deep.equal(spec); + expect(value.keyPairHandle).to.be.ok; + expect(value.provider).to.be.ok; + expect(await value.keyPairHandle.id()).to.equal(id); + expect(await value.provider.providerName()).to.equal(providerName); +} diff --git a/test/crypto-layer/CryptoSignatureKeypairHandle.test.ts b/test/crypto-layer/CryptoSignatureKeypairHandle.test.ts new file mode 100644 index 0000000..2769780 --- /dev/null +++ b/test/crypto-layer/CryptoSignatureKeypairHandle.test.ts @@ -0,0 +1,156 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { expect } from "chai"; +import { CoreBuffer, CryptoHashAlgorithm, CryptoSignature, CryptoSignatures } from "src/"; +import { CryptoSignatureKeypairHandle } from "src/crypto-layer/signature/CryptoSignatureKeypair"; +import { CryptoSignaturePrivateKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle"; +import { CryptoSignaturePublicKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePublicKeyHandle"; +import { parameterizedKeyPairSpec } from "./CryptoLayerTestUtil"; + +export async function expectCryptoSignatureKeypairHandle( + cryptoKeyPairHandle: CryptoSignatureKeypairHandle, + expectedProvider: string, + spec: KeyPairSpec +): Promise { + expect(cryptoKeyPairHandle).to.be.instanceOf(CryptoSignatureKeypairHandle); + expect(cryptoKeyPairHandle.privateKey).to.be.instanceOf(CryptoSignaturePrivateKeyHandle); + expect(cryptoKeyPairHandle.publicKey).to.be.instanceOf(CryptoSignaturePublicKeyHandle); + expect(cryptoKeyPairHandle.privateKey.keyPairHandle).to.be.ok.and.deep.equal( + cryptoKeyPairHandle.publicKey.keyPairHandle + ); + expect(cryptoKeyPairHandle.privateKey.id) + .to.be.a("string") + .and.to.equal(await cryptoKeyPairHandle.privateKey.keyPairHandle.id()); + expect(cryptoKeyPairHandle.publicKey.id) + .to.be.a("string") + .and.to.equal(await cryptoKeyPairHandle.publicKey.keyPairHandle.id()) + .and.to.be.string(cryptoKeyPairHandle.privateKey.id); + + expect(cryptoKeyPairHandle.privateKey.provider).to.be.ok.and.deep.equal(cryptoKeyPairHandle.publicKey.provider); + expect(cryptoKeyPairHandle.privateKey.providerName) + .to.be.a("string") + .and.to.be.string(cryptoKeyPairHandle.publicKey.providerName) + .and.to.be.string(expectedProvider); + expect(await cryptoKeyPairHandle.privateKey.keyPairHandle.spec()).to.deep.equal(spec); + expect(await cryptoKeyPairHandle.publicKey.keyPairHandle.spec()).to.deep.equal(spec); +} + +export class CryptoSignatureKeypairHandleTest { + public static run(): void { + describe("CryptoSignatureKeypairHandle", function () { + describe("generateKeyPairHandle() SoftwareProvider", function () { + const spec: KeyPairSpec = { + asym_spec: "P256", + cipher: null, + signing_hash: "Sha2_512", + ephemeral: false, + non_exportable: false + }; + const providerIdent = { providerName: "SoftwareProvider" }; + + parameterizedKeyPairSpec("generateKeyPairHandle()", async function (spec: KeyPairSpec) { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle( + { providerName: "SoftwareProvider" }, + spec + ); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + }); + + it("from() ICryptoSignatureKeypairHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + + const loadedKeyPairHandle = await CryptoSignatureKeypairHandle.fromAny({ + publicKey: { + id: cryptoKeyPairHandle.publicKey.id, + spec: cryptoKeyPairHandle.publicKey.spec, + providerName: cryptoKeyPairHandle.publicKey.providerName + }, + privateKey: { + id: cryptoKeyPairHandle.publicKey.id, + spec: cryptoKeyPairHandle.publicKey.spec, + providerName: cryptoKeyPairHandle.publicKey.providerName + } + }); + await expectCryptoSignatureKeypairHandle(loadedKeyPairHandle, "SoftwareProvider", spec); + + expect(loadedKeyPairHandle.privateKey.id).to.equal(cryptoKeyPairHandle.privateKey.id); + expect(loadedKeyPairHandle.publicKey.id).to.equal(cryptoKeyPairHandle.publicKey.id); + }); + + it("from() ICryptoSignatureKeypairHandle 2", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle( + { providerName: "SoftwareProvider" }, + spec + ); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + + const loadedKeyPairHandle = await CryptoSignatureKeypairHandle.fromAny({ + publicKey: cryptoKeyPairHandle.publicKey, + privateKey: cryptoKeyPairHandle.privateKey + }); + await expectCryptoSignatureKeypairHandle(loadedKeyPairHandle, "SoftwareProvider", spec); + + expect(loadedKeyPairHandle.privateKey.id).to.equal(cryptoKeyPairHandle.privateKey.id); + expect(loadedKeyPairHandle.publicKey.id).to.equal(cryptoKeyPairHandle.publicKey.id); + }); + + it("from() CryptoSignatureKeypairHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + + const loadedKeyPairHandle = await CryptoSignatureKeypairHandle.from(cryptoKeyPairHandle); + await expectCryptoSignatureKeypairHandle(loadedKeyPairHandle, "SoftwareProvider", spec); + + expect(loadedKeyPairHandle.privateKey.id).to.equal(cryptoKeyPairHandle.privateKey.id); + expect(loadedKeyPairHandle.publicKey.id).to.equal(cryptoKeyPairHandle.publicKey.id); + }); + + it("toJSON() and fromJSON()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + + const jsonKeyPairHandle = cryptoKeyPairHandle.toJSON(); + expect(jsonKeyPairHandle).to.be.ok; + expect(jsonKeyPairHandle.prv).to.be.ok; + expect(jsonKeyPairHandle.pub).to.be.ok; + + const loadedKeyPairHandle = await CryptoSignatureKeypairHandle.fromJSON(jsonKeyPairHandle); + await expectCryptoSignatureKeypairHandle(loadedKeyPairHandle, "SoftwareProvider", spec); + }); + + it("toBase64() and fromBase64()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + await expectCryptoSignatureKeypairHandle(cryptoKeyPairHandle, "SoftwareProvider", spec); + + const encodedKeyPairHandle = cryptoKeyPairHandle.toBase64(); + expect(encodedKeyPairHandle).to.be.ok.and.be.a("string"); + + const loadedKeyPairHandle = await CryptoSignatureKeypairHandle.fromBase64(encodedKeyPairHandle); + await expectCryptoSignatureKeypairHandle(loadedKeyPairHandle, "SoftwareProvider", spec); + }); + + it("sign() and verify()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + + const data = new CoreBuffer("0123456789ABCDEF"); + const signature = await CryptoSignatures.sign( + data, + cryptoKeyPairHandle.privateKey, + CryptoHashAlgorithm.SHA512, + undefined, + "1234" + ); + expect(signature).to.be.ok.and.to.be.instanceOf(CryptoSignature); + expect(signature.keyId).to.be.ok.and.to.equal(cryptoKeyPairHandle.privateKey.id); + expect(signature.id).to.equal("1234"); + expect(signature.algorithm).to.equal(CryptoHashAlgorithm.SHA512); + + expect(await CryptoSignatures.verify(data, signature, cryptoKeyPairHandle.publicKey)).to.equal( + true + ); + }); + }); + }); + } +} diff --git a/test/crypto-layer/CryptoSignaturePrivateKeyHandle.test.ts b/test/crypto-layer/CryptoSignaturePrivateKeyHandle.test.ts new file mode 100644 index 0000000..1c88579 --- /dev/null +++ b/test/crypto-layer/CryptoSignaturePrivateKeyHandle.test.ts @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { expect } from "chai"; +import { CryptoSignaturePrivateKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePrivateKeyHandle"; +import { CryptoSignatures } from "src/signature/CryptoSignatures"; +import { expectCryptoSignatureAsymmetricKeyHandle } from "./CryptoLayerTestUtil"; + +export class CryptoSignaturePrivateKeyHandleTest { + public static run(): void { + describe("CryptoSignaturePrivateKeyHandle", function () { + describe("CryptoSignaturePrivateKeyHandle SoftwareProvider P256 Sha2_512", function () { + const spec: KeyPairSpec = { + asym_spec: "P256", + cipher: null, + signing_hash: "Sha2_512", + ephemeral: false, + non_exportable: false + }; + const providerIdent = { providerName: "SoftwareProvider" }; + + it("toJSON() and fromJSON()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const privateKeyHandle = cryptoKeyPairHandle.privateKey; + const id = privateKeyHandle.id; + const providerName = privateKeyHandle.providerName; + + const serializedPrivateKeyHandle = privateKeyHandle.toJSON(); + expect(serializedPrivateKeyHandle).to.be.instanceOf(Object); + expect(serializedPrivateKeyHandle.cid).to.equal(id); + expect(serializedPrivateKeyHandle.pnm).to.equal(providerName); + expect(serializedPrivateKeyHandle.spc).to.deep.equal(spec); + expect(serializedPrivateKeyHandle["@type"]).to.equal("CryptoSignaturePrivateKeyHandle"); + + const loadedPrivateKeyHandle = + await CryptoSignaturePrivateKeyHandle.fromJSON(serializedPrivateKeyHandle); + await expectCryptoSignatureAsymmetricKeyHandle(loadedPrivateKeyHandle, id, spec, providerName); + }); + + it("toBase64() and fromBase64()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const privateKeyHandle = cryptoKeyPairHandle.privateKey; + const id = privateKeyHandle.id; + const providerName = privateKeyHandle.providerName; + + const serializedPrivateKey = privateKeyHandle.toBase64(); + expect(serializedPrivateKey).to.be.ok; + const deserializedPrivateKey = CryptoSignaturePrivateKeyHandle.fromBase64(serializedPrivateKey); + await expectCryptoSignatureAsymmetricKeyHandle( + await deserializedPrivateKey, + id, + spec, + providerName + ); + }); + + // eslint-disable-next-line jest/expect-expect + it("from() ICryptoSignaturePrivateKeyHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const privateKeyHandle = cryptoKeyPairHandle.privateKey; + const id = privateKeyHandle.id; + const providerName = privateKeyHandle.providerName; + + const loadedPrivateKeyHandle = await CryptoSignaturePrivateKeyHandle.from({ + spec: spec, + id: id, + providerName: providerName + }); + await expectCryptoSignatureAsymmetricKeyHandle(loadedPrivateKeyHandle, id, spec, providerName); + }); + + // eslint-disable-next-line jest/expect-expect + it("from() CryptoSignaturePrivateKeyHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const privateKeyHandle = cryptoKeyPairHandle.privateKey; + const id = privateKeyHandle.id; + const providerName = privateKeyHandle.providerName; + + const loadedPrivateKeyHandle = await CryptoSignaturePrivateKeyHandle.from(privateKeyHandle); + await expectCryptoSignatureAsymmetricKeyHandle(loadedPrivateKeyHandle, id, spec, providerName); + }); + }); + }); + } +} diff --git a/test/crypto-layer/CryptoSignaturePublicKeyHandle.test.ts b/test/crypto-layer/CryptoSignaturePublicKeyHandle.test.ts new file mode 100644 index 0000000..8e0f3c9 --- /dev/null +++ b/test/crypto-layer/CryptoSignaturePublicKeyHandle.test.ts @@ -0,0 +1,79 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { KeyPairSpec } from "@nmshd/rs-crypto-types"; +import { expect } from "chai"; +import { CryptoSignaturePublicKeyHandle } from "src/crypto-layer/signature/CryptoSignaturePublicKeyHandle"; +import { CryptoSignatures } from "src/signature/CryptoSignatures"; +import { expectCryptoSignatureAsymmetricKeyHandle } from "./CryptoLayerTestUtil"; + +export class CryptoSignaturePublicKeyHandleTest { + public static run(): void { + describe("CryptoSignaturePublicKeyHandle", function () { + describe("CryptoSignaturePublicKeyHandle SoftwareProvider P256 Sha2_512", function () { + const spec: KeyPairSpec = { + asym_spec: "P256", + cipher: null, + signing_hash: "Sha2_512", + ephemeral: false, + non_exportable: true + }; + const providerIdent = { providerName: "SoftwareProvider" }; + + it("toJSON() and fromJSON()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const publicKeyHandle = cryptoKeyPairHandle.publicKey; + const id = publicKeyHandle.id; + const providerName = publicKeyHandle.providerName; + + const serializedpublicKeyHandle = publicKeyHandle.toJSON(); + expect(serializedpublicKeyHandle).to.be.instanceOf(Object); + expect(serializedpublicKeyHandle.cid).to.equal(id); + expect(serializedpublicKeyHandle.pnm).to.equal(providerName); + expect(serializedpublicKeyHandle.spc).to.deep.equal(spec); + expect(serializedpublicKeyHandle["@type"]).to.equal("CryptoSignaturePublicKeyHandle"); + + const loadedpublicKeyHandle = + await CryptoSignaturePublicKeyHandle.fromJSON(serializedpublicKeyHandle); + await expectCryptoSignatureAsymmetricKeyHandle(loadedpublicKeyHandle, id, spec, providerName); + }); + + it("toBase64() and fromBase64()", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const publicKeyHandle = cryptoKeyPairHandle.publicKey; + const id = publicKeyHandle.id; + const providerName = publicKeyHandle.providerName; + + const serializedpublicKey = publicKeyHandle.toBase64(); + expect(serializedpublicKey).to.be.ok; + const deserializedpublicKey = CryptoSignaturePublicKeyHandle.fromBase64(serializedpublicKey); + await expectCryptoSignatureAsymmetricKeyHandle(await deserializedpublicKey, id, spec, providerName); + }); + + // eslint-disable-next-line jest/expect-expect + it("from() ICryptoSignaturePublicKeyHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const publicKeyHandle = cryptoKeyPairHandle.publicKey; + const id = publicKeyHandle.id; + const providerName = publicKeyHandle.providerName; + + const loadedpublicKeyHandle = await CryptoSignaturePublicKeyHandle.from({ + spec: spec, + id: id, + providerName: providerName + }); + await expectCryptoSignatureAsymmetricKeyHandle(loadedpublicKeyHandle, id, spec, providerName); + }); + + // eslint-disable-next-line jest/expect-expect + it("from() CryptoSignaturePublicKeyHandle", async function () { + const cryptoKeyPairHandle = await CryptoSignatures.generateKeypairHandle(providerIdent, spec); + const publicKeyHandle = cryptoKeyPairHandle.publicKey; + const id = publicKeyHandle.id; + const providerName = publicKeyHandle.providerName; + + const loadedpublicKeyHandle = await CryptoSignaturePublicKeyHandle.from(publicKeyHandle); + await expectCryptoSignatureAsymmetricKeyHandle(loadedpublicKeyHandle, id, spec, providerName); + }); + }); + }); + } +} diff --git a/test/index.ts b/test/index.ts index 411728f..26004bc 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1,5 +1,17 @@ -import { SodiumWrapper } from "@nmshd/crypto"; +import { initCryptoLayerProviders, SodiumWrapper } from "@nmshd/crypto"; +import { + createProvider, + createProviderFromName, + getAllProviders, + getProviderCapabilities +} from "@nmshd/rs-crypto-node"; +import chai from "chai"; import { BufferTest } from "./BufferTest.test"; +import { CryptoExportedPublicKeyTest } from "./crypto-layer/CryptoExportedPublicKey.test"; +import { CryptoLayerProviderTest } from "./crypto-layer/CryptoLayerProviderTest.test"; +import { CryptoSignatureKeypairHandleTest } from "./crypto-layer/CryptoSignatureKeypairHandle.test"; +import { CryptoSignaturePrivateKeyHandleTest } from "./crypto-layer/CryptoSignaturePrivateKeyHandle.test"; +import { CryptoSignaturePublicKeyHandleTest } from "./crypto-layer/CryptoSignaturePublicKeyHandle.test"; import { CryptoDerivationTest } from "./crypto/CryptoDerivationTest.test"; import { CryptoEncryptionTest } from "./crypto/CryptoEncryptionTest.test"; import { CryptoExchangeTest } from "./crypto/CryptoExchangeTest.test"; @@ -15,22 +27,43 @@ import { CryptoSignatureTest } from "./crypto/CryptoSignature.test"; import { CryptoStateTest } from "./crypto/CryptoStateTest.test"; import { SodiumWrapperTest } from "./crypto/SodiumWrapperTest.test"; -SodiumWrapper.ready() - .then(() => { - SodiumWrapperTest.run(); - CryptoDerivationTest.run(); - CryptoReflectionTest.run(); - CryptoRelationshipTest.run(); - CryptoEncryptionTest.run(); - CryptoHashTest.run(); - CryptoExchangeTest.run(); - CryptoPrivateKeyTest.run(); - CryptoPublicKeyTest.run(); - CryptoRandomTest.run(); - CryptoPasswordGeneratorTest.run(); - CryptoSecretKeyTest.run(); - CryptoSignatureTest.run(); - CryptoStateTest.run(); - BufferTest.run(); - }) - .catch((e) => console.log(e)); +chai.config.truncateThreshold = 0; + +// This is valid: https://mochajs.org/#delayed-root-suite +// eslint-disable-next-line @typescript-eslint/no-floating-promises +(async function () { + // === CAL === + await initCryptoLayerProviders({ + factoryFunctions: { getAllProviders, createProvider, createProviderFromName, getProviderCapabilities }, + // eslint-disable-next-line @typescript-eslint/naming-convention + keyMetadataStoreConfig: { FileStoreConfig: { db_dir: "./test_cal_db" } }, + // eslint-disable-next-line @typescript-eslint/naming-convention + keyMetadataStoreAuth: { StorageConfigPass: "12345678" }, + providersToBeInitialized: [{ providerName: "SoftwareProvider" }] + }); + CryptoLayerProviderTest.run(); + CryptoSignatureKeypairHandleTest.run(); + CryptoSignaturePrivateKeyHandleTest.run(); + CryptoSignaturePublicKeyHandleTest.run(); + CryptoExportedPublicKeyTest.run(); + + // === Other === + await SodiumWrapper.ready(); + SodiumWrapperTest.run(); + CryptoDerivationTest.run(); + CryptoReflectionTest.run(); + CryptoRelationshipTest.run(); + CryptoEncryptionTest.run(); + CryptoHashTest.run(); + CryptoExchangeTest.run(); + CryptoPrivateKeyTest.run(); + CryptoPublicKeyTest.run(); + CryptoRandomTest.run(); + CryptoPasswordGeneratorTest.run(); + CryptoSecretKeyTest.run(); + CryptoSignatureTest.run(); + CryptoStateTest.run(); + BufferTest.run(); + + run(); +})();