diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ebfef4e0..73431fbfe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,11 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.lcov flags: unittests + - name: Run deno test for /core + working-directory: ./core + run: | + deno fmt --check + deno task test - name: Run deno test for /x/dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1 run: | diff --git a/.github/workflows/ci_browser.yml b/.github/workflows/ci_browser.yml index 4b4b433a9..2fc14c7ee 100644 --- a/.github/workflows/ci_browser.yml +++ b/.github/workflows/ci_browser.yml @@ -24,9 +24,15 @@ jobs: npm install -g esbuild deno task dnt deno task minify > test/runtimes/browsers/pages/src/hpke.js + mkdir test/runtimes/browsers/pages/core + cp -rf core/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/core mkdir test/runtimes/browsers/pages/dhkem-secp256k1 cp -rf x/dhkem-secp256k1/test/runtimes/browsers/pages/* test/runtimes/browsers/pages/dhkem-secp256k1 deno task minify > test/runtimes/browsers/pages/dhkem-secp256k1/src/hpke.js + - working-directory: ./core + run: | + deno task dnt + deno task minify > ../test/runtimes/browsers/pages/core/src/hpke-core.js - working-directory: ./x/dhkem-secp256k1 run: | deno task dnt @@ -38,7 +44,7 @@ jobs: playwright-test: needs: pages - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -48,5 +54,7 @@ jobs: npm ci npx playwright install --with-deps npx playwright test + - working-directory: ./core/test/runtimes/browsers + run: npm install && npx playwright install && npx playwright test - working-directory: ./x/dhkem-secp256k1/test/runtimes/browsers run: npm install && npx playwright install && npx playwright test diff --git a/.github/workflows/ci_bun.yml b/.github/workflows/ci_bun.yml index 809f7878f..1d74150df 100644 --- a/.github/workflows/ci_bun.yml +++ b/.github/workflows/ci_bun.yml @@ -30,10 +30,6 @@ jobs: deno task dnt deno task minify > test/runtimes/hpke.js deno task minify > x/dhkem-secp256k1/test/runtimes/hpke.js - - working-directory: ./x/dhkem-secp256k1/ - run: | - deno task dnt - deno task minify > test/runtimes/hpke-dhkem-secp256k1.js - uses: antongolub/action-setup-bun@v1 with: bun-version: ${{ matrix.bun-version }} @@ -43,6 +39,20 @@ jobs: nohup bun src/index.js & sleep 3 deno test hpke.spec.ts --allow-net + - working-directory: ./core/ + run: | + deno task dnt + deno task minify > test/runtimes/hpke-core.js + - name: Run test for core + working-directory: ./core/test/runtimes/bun + run: | + nohup bun src/index.js & + sleep 3 + deno test core.spec.ts --allow-net + - working-directory: ./x/dhkem-secp256k1/ + run: | + deno task dnt + deno task minify > test/runtimes/hpke-dhkem-secp256k1.js - name: Run test for dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1/test/runtimes/bun run: | diff --git a/.github/workflows/ci_cloudflare.yml b/.github/workflows/ci_cloudflare.yml index 8ded627b4..bf8dc00d7 100644 --- a/.github/workflows/ci_cloudflare.yml +++ b/.github/workflows/ci_cloudflare.yml @@ -24,11 +24,8 @@ jobs: npm install -g esbuild deno task dnt deno task minify > test/runtimes/hpke.js + deno task minify > core/test/runtimes/hpke.js deno task minify > x/dhkem-secp256k1/test/runtimes/hpke.js - - working-directory: ./x/dhkem-secp256k1/ - run: | - deno task dnt - deno task minify > test/runtimes/hpke-dhkem-secp256k1.js - name: Run test working-directory: ./test/runtimes/cloudflare run: | @@ -36,6 +33,21 @@ jobs: nohup npm start & sleep 3 deno test hpke.spec.ts --allow-net + - working-directory: ./core/ + run: | + deno task dnt + deno task minify > test/runtimes/hpke-core.js + - name: Run test for core + working-directory: ./core/test/runtimes/cloudflare + run: | + npm install + nohup npm start & + sleep 3 + deno test core.spec.ts --allow-net + - working-directory: ./x/dhkem-secp256k1/ + run: | + deno task dnt + deno task minify > test/runtimes/hpke-dhkem-secp256k1.js - name: Run test for dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1/test/runtimes/cloudflare run: | diff --git a/.github/workflows/ci_node.yml b/.github/workflows/ci_node.yml index 6367032be..22ebe5258 100644 --- a/.github/workflows/ci_node.yml +++ b/.github/workflows/ci_node.yml @@ -27,6 +27,11 @@ jobs: npm install -g esbuild deno task dnt deno task minify > ./npm/hpke.min.js + - name: Run dnt & minify for /core + working-directory: ./core + run: | + deno task dnt + deno task minify > ./npm/hpke-core.min.js - name: Run dnt & minify for /x/dhkem-secp256k1 working-directory: ./x/dhkem-secp256k1 run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6e79fb332..f0716fcb8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,6 +26,28 @@ jobs: env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-core: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: https://registry.npmjs.org/ + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Run dnt & minify + working-directory: ./core + run: | + npm install -g esbuild + deno task dnt + deno task minify > ./npm/hpke-core.min.js + - working-directory: ./core/npm + run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + publish-dhkem-secp256k1: runs-on: ubuntu-latest steps: diff --git a/core/README.md b/core/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/core/deno.json b/core/deno.json new file mode 100644 index 000000000..5fc94cc7b --- /dev/null +++ b/core/deno.json @@ -0,0 +1,35 @@ +{ + "imports": { + "testing/": "https://deno.land/std@0.195.0/testing/", + "dnt": "https://deno.land/x/dnt@0.38.0/mod.ts" + }, + "fmt": { + "include": [ + "README.md", + "deno.json", + "dnt.ts", + "mod.ts", + "src/", + "test/" + ], + "exclude": [ + "test/runtimes/bun", + "test/runtimes/browsers/node_modules", + "test/runtimes/cloudflare" + ] + }, + "lint": { + "include": ["mod.ts", "src/", "test/"], + "exclude": [ + "test/runtimes/bun", + "test/runtimes/browsers/node_modules", + "test/runtimes/cloudflare" + ] + }, + "tasks": { + "test": "deno fmt && deno lint && deno test test -A --fail-fast --doc --coverage=coverage --parallel --allow-read", + "cov": "deno coverage ./coverage --lcov --exclude='test'", + "dnt": "deno run -A dnt.ts $(git describe --tags $(git rev-list --tags --max-count=1))", + "minify": "esbuild npm/esm/core/mod.js --bundle --format=esm --minify" + } +} diff --git a/core/deno.lock b/core/deno.lock new file mode 100644 index 000000000..05b6378bd --- /dev/null +++ b/core/deno.lock @@ -0,0 +1,123 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", + "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", + "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", + "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9", + "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf", + "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37", + "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", + "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", + "https://deno.land/std@0.140.0/hash/sha256.ts": "803846c7a5a8a5a97f31defeb37d72f519086c880837129934f5d6f72102a8e8", + "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b", + "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", + "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", + "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d", + "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", + "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", + "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21", + "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", + "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40", + "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", + "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32", + "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", + "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/std@0.182.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462", + "https://deno.land/std@0.182.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.182.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e", + "https://deno.land/std@0.182.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32", + "https://deno.land/std@0.182.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688", + "https://deno.land/std@0.182.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a", + "https://deno.land/std@0.182.0/fs/walk.ts": "920be35a7376db6c0b5b1caf1486fb962925e38c9825f90367f8f26b5e5d0897", + "https://deno.land/std@0.182.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.182.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.182.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", + "https://deno.land/std@0.182.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.182.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1", + "https://deno.land/std@0.182.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c", + "https://deno.land/std@0.182.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.182.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", + "https://deno.land/std@0.182.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", + "https://deno.land/std@0.195.0/_util/diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", + "https://deno.land/std@0.195.0/assert/_constants.ts": "8a9da298c26750b28b326b297316cdde860bc237533b07e1337c021379e6b2a9", + "https://deno.land/std@0.195.0/assert/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", + "https://deno.land/std@0.195.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.195.0/assert/assert_almost_equals.ts": "e15ca1f34d0d5e0afae63b3f5d975cbd18335a132e42b0c747d282f62ad2cd6c", + "https://deno.land/std@0.195.0/assert/assert_array_includes.ts": "6856d7f2c3544bc6e62fb4646dfefa3d1df5ff14744d1bca19f0cbaf3b0d66c9", + "https://deno.land/std@0.195.0/assert/assert_equals.ts": "a0ee60574e437bcab2dcb79af9d48dc88845f8fd559468d9c21b15fd638ef943", + "https://deno.land/std@0.195.0/assert/assert_exists.ts": "407cb6b9fb23a835cd8d5ad804e2e2edbbbf3870e322d53f79e1c7a512e2efd7", + "https://deno.land/std@0.195.0/assert/assert_false.ts": "a9962749f4bf5844e3fa494257f1de73d69e4fe0e82c34d0099287552163a2dc", + "https://deno.land/std@0.195.0/assert/assert_instance_of.ts": "09fd297352a5b5bbb16da2b5e1a0d8c6c44da5447772648622dcc7df7af1ddb8", + "https://deno.land/std@0.195.0/assert/assert_is_error.ts": "b4eae4e5d182272efc172bf28e2e30b86bb1650cd88aea059e5d2586d4160fb9", + "https://deno.land/std@0.195.0/assert/assert_match.ts": "c4083f80600bc190309903c95e397a7c9257ff8b5ae5c7ef91e834704e672e9b", + "https://deno.land/std@0.195.0/assert/assert_not_equals.ts": "9f1acab95bd1f5fc9a1b17b8027d894509a745d91bac1718fdab51dc76831754", + "https://deno.land/std@0.195.0/assert/assert_not_instance_of.ts": "0c14d3dfd9ab7a5276ed8ed0b18c703d79a3d106102077ec437bfe7ed912bd22", + "https://deno.land/std@0.195.0/assert/assert_not_match.ts": "3796a5b0c57a1ce6c1c57883dd4286be13a26f715ea662318ab43a8491a13ab0", + "https://deno.land/std@0.195.0/assert/assert_not_strict_equals.ts": "ca6c6d645e95fbc873d25320efeb8c4c6089a9a5e09f92d7c1c4b6e935c2a6ad", + "https://deno.land/std@0.195.0/assert/assert_object_match.ts": "27439c4f41dce099317566144299468ca822f556f1cc697f4dc8ed61fe9fee4c", + "https://deno.land/std@0.195.0/assert/assert_rejects.ts": "45c59724de2701e3b1f67c391d6c71c392363635aad3f68a1b3408f9efca0057", + "https://deno.land/std@0.195.0/assert/assert_strict_equals.ts": "5cf29b38b3f8dece95287325723272aa04e04dbf158d886d662fa594fddc9ed3", + "https://deno.land/std@0.195.0/assert/assert_string_includes.ts": "b821d39ebf5cb0200a348863c86d8c4c4b398e02012ce74ad15666fc4b631b0c", + "https://deno.land/std@0.195.0/assert/assert_throws.ts": "63784e951475cb7bdfd59878cd25a0931e18f6dc32a6077c454b2cd94f4f4bcd", + "https://deno.land/std@0.195.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.195.0/assert/equal.ts": "9f1a46d5993966d2596c44e5858eec821859b45f783a5ee2f7a695dfc12d8ece", + "https://deno.land/std@0.195.0/assert/fail.ts": "c36353d7ae6e1f7933d45f8ea51e358c8c4b67d7e7502028598fe1fea062e278", + "https://deno.land/std@0.195.0/assert/mod.ts": "08d55a652c22c5da0215054b21085cec25a5da47ce4a6f9de7d9ad36df35bdee", + "https://deno.land/std@0.195.0/assert/unimplemented.ts": "d56fbeecb1f108331a380f72e3e010a1f161baa6956fd0f7cf3e095ae1a4c75a", + "https://deno.land/std@0.195.0/assert/unreachable.ts": "4600dc0baf7d9c15a7f7d234f00c23bca8f3eba8b140286aaca7aa998cf9a536", + "https://deno.land/std@0.195.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", + "https://deno.land/std@0.195.0/testing/_test_suite.ts": "30f018feeb3835f12ab198d8a518f9089b1bcb2e8c838a8b615ab10d5005465c", + "https://deno.land/std@0.195.0/testing/asserts.ts": "4da3dca17ff6b852e4e689f7e42562c24c3ee182cc55ff04852811e321538c24", + "https://deno.land/std@0.195.0/testing/bdd.ts": "3f446df5ef8e856a869e8eec54c8482590415741ff0b6358a00c43486cc15769", + "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", + "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", + "https://deno.land/x/deno_cache@0.4.1/auth_tokens.ts": "5fee7e9155e78cedf3f6ff3efacffdb76ac1a76c86978658d9066d4fb0f7326e", + "https://deno.land/x/deno_cache@0.4.1/cache.ts": "51f72f4299411193d780faac8c09d4e8cbee951f541121ef75fcc0e94e64c195", + "https://deno.land/x/deno_cache@0.4.1/deno_dir.ts": "f2a9044ce8c7fe1109004cda6be96bf98b08f478ce77e7a07f866eff1bdd933f", + "https://deno.land/x/deno_cache@0.4.1/deps.ts": "8974097d6c17e65d9a82d39377ae8af7d94d74c25c0cbb5855d2920e063f2343", + "https://deno.land/x/deno_cache@0.4.1/dirs.ts": "d2fa473ef490a74f2dcb5abb4b9ab92a48d2b5b6320875df2dee64851fa64aa9", + "https://deno.land/x/deno_cache@0.4.1/disk_cache.ts": "1f3f5232cba4c56412d93bdb324c624e95d5dd179d0578d2121e3ccdf55539f9", + "https://deno.land/x/deno_cache@0.4.1/file_fetcher.ts": "07a6c5f8fd94bf50a116278cc6012b4921c70d2251d98ce1c9f3c352135c39f7", + "https://deno.land/x/deno_cache@0.4.1/http_cache.ts": "f632e0d6ec4a5d61ae3987737a72caf5fcdb93670d21032ddb78df41131360cd", + "https://deno.land/x/deno_cache@0.4.1/mod.ts": "ef1cda9235a93b89cb175fe648372fc0f785add2a43aa29126567a05e3e36195", + "https://deno.land/x/deno_cache@0.4.1/util.ts": "8cb686526f4be5205b92c819ca2ce82220aa0a8dd3613ef0913f6dc269dbbcfe", + "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66", + "https://deno.land/x/dnt@0.38.0/lib/compiler.ts": "209ad2e1b294f93f87ec02ade9a0821f942d2e524104552d0aa8ff87021050a5", + "https://deno.land/x/dnt@0.38.0/lib/compiler_transforms.ts": "f21aba052f5dcf0b0595c734450842855c7f572e96165d3d34f8fed2fc1f7ba1", + "https://deno.land/x/dnt@0.38.0/lib/mod.deps.ts": "30367fc68bcd2acf3b7020cf5cdd26f817f7ac9ac35c4bfb6c4551475f91bc3e", + "https://deno.land/x/dnt@0.38.0/lib/npm_ignore.ts": "57fbb7e7b935417d225eec586c6aa240288905eb095847d3f6a88e290209df4e", + "https://deno.land/x/dnt@0.38.0/lib/package_json.ts": "61f35b06e374ed39ca776d29d67df4be7ee809d0bca29a8239687556c6d027c2", + "https://deno.land/x/dnt@0.38.0/lib/pkg/dnt_wasm.generated.js": "82aeecfb055af0b2700e1e9b886e4a44fe3bf9cd11a9c4195cb169f53a134b15", + "https://deno.land/x/dnt@0.38.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "a6b95adc943a68d513fe8ed9ec7d260ac466b7a4bced4e942f733e494bb9f1be", + "https://deno.land/x/dnt@0.38.0/lib/shims.ts": "df1bd4d9a196dca4b2d512b1564fff64ac6c945189a273d706391f87f210d7e6", + "https://deno.land/x/dnt@0.38.0/lib/test_runner/get_test_runner_code.ts": "4dc7a73a13b027341c0688df2b29a4ef102f287c126f134c33f69f0339b46968", + "https://deno.land/x/dnt@0.38.0/lib/test_runner/test_runner.ts": "4d0da0500ec427d5f390d9a8d42fb882fbeccc92c92d66b6f2e758606dbd40e6", + "https://deno.land/x/dnt@0.38.0/lib/transform.deps.ts": "e42f2bdef46d098453bdba19261a67cf90b583f5d868f7fe83113c1380d9b85c", + "https://deno.land/x/dnt@0.38.0/lib/types.ts": "b8e228b2fac44c2ae902fbb73b1689f6ab889915bd66486c8a85c0c24255f5fb", + "https://deno.land/x/dnt@0.38.0/lib/utils.ts": "878b7ac7003a10c16e6061aa49dbef9b42bd43174853ebffc9b67ea47eeb11d8", + "https://deno.land/x/dnt@0.38.0/mod.ts": "b13349fe77847cf58e26b40bcd58797a8cec5d71b31a1ca567071329c8489de1", + "https://deno.land/x/dnt@0.38.0/transform.ts": "f68743a14cf9bf53bfc9c81073871d69d447a7f9e3453e0447ca2fb78926bb1d", + "https://deno.land/x/ts_morph@18.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", + "https://deno.land/x/ts_morph@18.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", + "https://deno.land/x/ts_morph@18.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", + "https://deno.land/x/ts_morph@18.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", + "https://deno.land/x/ts_morph@18.0.0/common/ts_morph_common.js": "845671ca951073400ce142f8acefa2d39ea9a51e29ca80928642f3f8cf2b7700", + "https://deno.land/x/ts_morph@18.0.0/common/typescript.js": "d5c598b6a2db2202d0428fca5fd79fc9a301a71880831a805d778797d2413c59", + "https://deno.land/x/wasmbuild@0.14.1/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4", + "https://deno.land/x/wasmbuild@0.14.1/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02" + } +} diff --git a/core/dnt.ts b/core/dnt.ts new file mode 100644 index 000000000..3405444e7 --- /dev/null +++ b/core/dnt.ts @@ -0,0 +1,59 @@ +import { build, emptyDir } from "dnt"; + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./mod.ts"], + outDir: "./npm", + typeCheck: false, + test: true, + declaration: true, + scriptModule: "umd", + importMap: "./deno.json", + compilerOptions: { + lib: ["es2021", "dom"], + }, + shims: { + deno: "dev", + }, + package: { + name: "@hpke/core", + version: Deno.args[0], + description: + "A Hybrid Public Key Encryption (HPKE) core module for various JavaScript runtimes", + repository: { + type: "git", + url: "git+https://github.com/dajiaji/hpke-js.git", + }, + homepage: "https://github.com/dajiaji/hpke-js#readme", + license: "MIT", + main: "./script/core/mod.js", + types: "./script/core/mod.d.ts", + exports: { + ".": { + "import": "./esm/core/mod.js", + "require": "./script/core/mod.js", + }, + "./package.json": "./package.json", + }, + keywords: [ + "hpke", + "rfc9180", + "hkdf", + "dh", + "security", + "encryption", + ], + engines: { + "node": ">=16.0.0", + }, + author: "Ajitomi Daisuke", + bugs: { + url: "https://github.com/dajiaji/hpke-js/issues", + }, + }, +}); + +// post build steps +Deno.copyFileSync("../LICENSE", "npm/LICENSE"); +Deno.copyFileSync("README.md", "npm/README.md"); diff --git a/core/mod.ts b/core/mod.ts new file mode 100644 index 000000000..1166c9358 --- /dev/null +++ b/core/mod.ts @@ -0,0 +1,29 @@ +export type { AeadEncryptionContext } from "../src/interfaces/aeadEncryptionContext.ts"; +export type { AeadInterface } from "../src/interfaces/aeadInterface.ts"; +export type { CipherSuiteParams } from "../src/interfaces/cipherSuiteParams.ts"; +export type { + EncryptionContextInterface, + RecipientContextInterface, + SenderContextInterface, +} from "../src/interfaces/encryptionContextInterface.ts"; +export type { KdfInterface } from "../src/interfaces/kdfInterface.ts"; +export type { KemInterface } from "../src/interfaces/kemInterface.ts"; +export type { PreSharedKey } from "../src/interfaces/preSharedKey.ts"; +export type { RecipientContextParams } from "../src/interfaces/recipientContextParams.ts"; +export type { CipherSuiteSealResponse } from "../src/interfaces/responses.ts"; +export type { SenderContextParams } from "../src/interfaces/senderContextParams.ts"; + +export { Aes128Gcm, Aes256Gcm } from "../src/aeads/aesGcm.ts"; +export { AeadId, KdfId, KemId } from "../src/identifiers.ts"; + +export { + CipherSuite, + DhkemP256HkdfSha256, + DhkemP384HkdfSha384, + DhkemP521HkdfSha512, + HkdfSha256, + HkdfSha384, + HkdfSha512, +} from "./src/native.ts"; + +export * from "../src/errors.ts"; diff --git a/core/src/native.ts b/core/src/native.ts new file mode 100644 index 000000000..2dfa57819 --- /dev/null +++ b/core/src/native.ts @@ -0,0 +1,19 @@ +import { CipherSuiteNative } from "../../src/cipherSuiteNative.ts"; +import { + DhkemP256HkdfSha256Native, + DhkemP384HkdfSha384Native, + DhkemP521HkdfSha512Native, +} from "../../src/kems/dhkemNative.ts"; +import { + HkdfSha256Native, + HkdfSha384Native, + HkdfSha512Native, +} from "../../src/kdfs/hkdf.ts"; + +export class CipherSuite extends CipherSuiteNative {} +export class DhkemP256HkdfSha256 extends DhkemP256HkdfSha256Native {} +export class DhkemP384HkdfSha384 extends DhkemP384HkdfSha384Native {} +export class DhkemP521HkdfSha512 extends DhkemP521HkdfSha512Native {} +export class HkdfSha256 extends HkdfSha256Native {} +export class HkdfSha384 extends HkdfSha384Native {} +export class HkdfSha512 extends HkdfSha512Native {} diff --git a/core/test/cipherSuite.test.ts b/core/test/cipherSuite.test.ts new file mode 100644 index 000000000..9a8a7daf6 --- /dev/null +++ b/core/test/cipherSuite.test.ts @@ -0,0 +1,1851 @@ +import { assertEquals, assertRejects, assertThrows } from "testing/asserts.ts"; + +import { describe, it } from "testing/bdd.ts"; + +import { isDeno } from "../../src/utils/misc.ts"; +import { loadCrypto } from "../../src/webCrypto.ts"; +import { concat } from "../../src/utils/misc.ts"; + +import { + AeadId, + Aes128Gcm, + CipherSuite, + DeserializeError, + DhkemP256HkdfSha256, + DhkemP384HkdfSha384, + DhkemP521HkdfSha512, + HkdfSha256, + HkdfSha384, + HkdfSha512, + InvalidParamError, + KdfId, + KemId, + NotSupportedError, +} from "../mod.ts"; + +import { hexStringToBytes } from "../../test/utils.ts"; + +describe("CipherSuite", () => { + // RFC9180 A.1. + describe("constructor with DhkemX25519HkdfSha256/HkdfSha256/Aes128Gcm", () => { + it("should have a correct ciphersuite", () => { + // assert + assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // const suite: CipherSuite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // // assert + // assertEquals(suite.kem.id, KemId.DhkemX25519HkdfSha256); + // assertEquals(suite.kem.id, 0x0020); + // assertEquals(suite.kem.secretSize, 32); + // assertEquals(suite.kem.encSize, 32); + // assertEquals(suite.kem.publicKeySize, 32); + // assertEquals(suite.kem.privateKeySize, 32); + // assertEquals(suite.aead.keySize, 16); + // assertEquals(suite.aead.nonceSize, 12); + // assertEquals(suite.aead.tagSize, 16); + // assertEquals(suite.kdf.id, KdfId.HkdfSha256); + // assertEquals(suite.kdf.id, 0x0001); + // assertEquals(suite.aead.id, AeadId.Aes128Gcm); + // assertEquals(suite.aead.id, 0x0001); + }); + }); + + // RFC9180 A.2. + describe("constructor with DhkemX25519HkdfSha256/HkdfSha256/ChaCha20Poly1305", () => { + it("should have a correct ciphersuite", () => { + // assert + assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Chacha20Poly1305, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // const suite: CipherSuite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Chacha20Poly1305, + // }); + + // // assert + // assertEquals(suite.kem.id, KemId.DhkemX25519HkdfSha256); + // assertEquals(suite.kem.id, 0x0020); + // assertEquals(suite.kdf.id, KdfId.HkdfSha256); + // assertEquals(suite.kdf.id, 0x0001); + // assertEquals(suite.aead.id, AeadId.Chacha20Poly1305); + // assertEquals(suite.aead.id, 0x0003); + }); + }); + + // RFC9180 A.3. + describe("constructor with DhkemP256HkdfSha256/HkdfSha256/Aes128Gcm", () => { + it("should have ciphersuites", () => { + const suite: CipherSuite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + // assert + assertEquals(suite.kem.id, KemId.DhkemP256HkdfSha256); + assertEquals(suite.kem.id, 0x0010); + assertEquals(suite.kdf.id, KdfId.HkdfSha256); + assertEquals(suite.kdf.id, 0x0001); + assertEquals(suite.aead.id, AeadId.Aes128Gcm); + assertEquals(suite.aead.id, 0x0001); + }); + }); + + // RFC9180 A.4. + describe("constructor with DhkemP256HkdfSha256/HkdfSha512/Aes128Gcm", () => { + it("should have ciphersuites", () => { + const suite: CipherSuite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha512, + aead: AeadId.Aes128Gcm, + }); + + // assert + assertEquals(suite.kem.id, KemId.DhkemP256HkdfSha256); + assertEquals(suite.kem.id, 0x0010); + assertEquals(suite.kdf.id, KdfId.HkdfSha512); + assertEquals(suite.kdf.id, 0x0003); + assertEquals(suite.aead.id, AeadId.Aes128Gcm); + assertEquals(suite.aead.id, 0x0001); + }); + }); + + // RFC9180 A.5. + describe("constructor with DhkemP256HkdfSha256/HkdfSha256/ChaCha20Poly1305", () => { + it("should have ciphersuites", () => { + // assert + assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Chacha20Poly1305, + }), + InvalidParamError, + "The AEAD (3) cannot be specified by AeadId. Use submodule for the AEAD", + ); + + // const suite: CipherSuite = new CipherSuite({ + // kem: KemId.DhkemP256HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Chacha20Poly1305, + // }); + + // // assert + // assertEquals(suite.kem.id, KemId.DhkemP256HkdfSha256); + // assertEquals(suite.kem.id, 0x0010); + // assertEquals(suite.kdf.id, KdfId.HkdfSha256); + // assertEquals(suite.kdf.id, 0x0001); + // assertEquals(suite.aead.id, AeadId.Chacha20Poly1305); + // assertEquals(suite.aead.id, 0x0003); + }); + }); + + // RFC9180 A.6. + describe("constructor with DhkemP521HkdfSha512/HkdfSha512/Aes256Gcm", () => { + it("should have ciphersuites", () => { + const suite: CipherSuite = new CipherSuite({ + kem: KemId.DhkemP521HkdfSha512, + kdf: KdfId.HkdfSha512, + aead: AeadId.Aes256Gcm, + }); + + // assert + assertEquals(suite.kem.id, KemId.DhkemP521HkdfSha512); + assertEquals(suite.kem.id, 0x0012); + assertEquals(suite.kdf.id, KdfId.HkdfSha512); + assertEquals(suite.kdf.id, 0x0003); + assertEquals(suite.aead.id, AeadId.Aes256Gcm); + assertEquals(suite.aead.id, 0x0002); + }); + }); + + // RFC9180 A.7. + describe("constructor with DhkemP256HkdfSha256/HkdfSha256/ExportOnly", () => { + it("should have ciphersuites", () => { + const suite: CipherSuite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.ExportOnly, + }); + + // assert + assertEquals(suite.kem.id, KemId.DhkemP256HkdfSha256); + assertEquals(suite.kem.id, 0x0010); + assertEquals(suite.kdf.id, KdfId.HkdfSha256); + assertEquals(suite.kdf.id, 0x0001); + assertEquals(suite.aead.id, AeadId.ExportOnly); + assertEquals(suite.aead.id, 0xFFFF); + }); + }); + + describe("constructor with DhkemSecp256KHkdfSha256/HkdfSha256/ExportOnly", () => { + it("should throw InvalidParamError", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemSecp256k1HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.ExportOnly, + }), + InvalidParamError, + "The KEM (19) cannot be specified by KemId. Use submodule for the KEM", + ); + }); + }); + + describe("A README example of Base mode", () => { + it("should work normally with ids", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + + it("should work normally with instances", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP256HkdfSha256(), + kdf: new HkdfSha256(), + aead: new Aes128Gcm(), + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const jwkPkR = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + key_ops: [], + }; + const pkR = await suite.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-256", + kid: "P-256-01", + x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc", + y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI", + d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo", + key_ops: ["deriveBits"], + }; + const skR = await suite.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("A README example of Base mode (KemId.DhkemP384HkdfSha384/KdfId.HkdfSha384)", () => { + it("should work normally with ids", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP384HkdfSha384, + kdf: KdfId.HkdfSha384, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with instances", async () => { + // setup + const suite = new CipherSuite({ + kem: new DhkemP384HkdfSha384(), + kdf: new HkdfSha384(), + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP384HkdfSha384, + kdf: KdfId.HkdfSha384, + aead: AeadId.Aes128Gcm, + }); + + const jwkPkR = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + key_ops: [], + }; + const pkR = await suite.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-384", + kid: "P-384-01", + x: "_XyN9woHaS0mPimSW-etwJMEDSzxIMjp4PjezavU8SHJoClz1bQrcmPb1ZJxHxhI", + y: "GCNfc32p9sRotx7u2oDGJ3Eqz6q5zPHLdizNn83oRsUTN31eCWfGLHWRury3xF50", + d: "1pImEKbrr771-RKi8Tb7tou_WjiR7kwui_nMu16449rk3lzAqf9buUhTkJ-pogkb", + key_ops: ["deriveBits"], + }; + const skR = await suite.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("A README example of Base mode (KemId.DhkemP521HkdfSha512/KdfId.HkdfSha512)", () => { + it("should work normally with ids", async () => { + if (isDeno()) { + return; + } + + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP521HkdfSha512, + kdf: KdfId.HkdfSha512, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with instances", async () => { + if (isDeno()) { + return; + } + + // setup + const suite = new CipherSuite({ + kem: new DhkemP521HkdfSha512(), + kdf: new HkdfSha512(), + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + if (isDeno()) { + return; + } + + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP521HkdfSha512, + kdf: KdfId.HkdfSha512, + aead: AeadId.Aes128Gcm, + }); + + const jwkPkR = { + kty: "EC", + crv: "P-521", + kid: "P-521-01", + x: "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc", + y: "ASx-Cb--149HJ-e1KlSaY-1BOhwOdcTkxSt8BGbW7_hnGfzHsoXM3ywwNcp1Yad-FHUKwmCyMelMQEn2Rh4V2l3I", + key_ops: [], + }; + const pkR = await suite.importKey("jwk", jwkPkR, true); + + const sender = await suite.createSenderContext({ + recipientPublicKey: pkR, + }); + + const jwkSkR = { + kty: "EC", + crv: "P-521", + kid: "P-521-01", + x: "APkZitSJMJUMB-iPCt47sWu_CrnUHg6IAR4qjmHON-2u41Rjg6DNOS0LZYJJt-AVH5NgGVi8ElIfjo71b9HXCTOc", + y: "ASx-Cb--149HJ-e1KlSaY-1BOhwOdcTkxSt8BGbW7_hnGfzHsoXM3ywwNcp1Yad-FHUKwmCyMelMQEn2Rh4V2l3I", + d: "ADYyo73ZKicOjwGDYQ_ybZKnVzdAcxGm9OVAxQjzgVM4jaS-Iwtkz90oLdDz3shgKlDgtRK2Aa9lMhqR94hBo4IE", + key_ops: ["deriveBits"], + }; + const skR = await suite.importKey("jwk", jwkSkR, false); + const recipient = await suite.createRecipientContext({ + recipientKey: skR, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + await assertRejects(() => recipient.seal(pt), NotSupportedError); + await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("A README example of Base mode (KemId.DhkemX25519HkdfSha256/KdfId.HkdfSha256)", () => { + it("should work normally with ids", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const rkp = await suite.generateKeyPair(); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const jwkPkR = { + // kty: "OKP", + // crv: "X25519", + // kid: "X25519-01", + // x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + // key_ops: [], + // }; + // const pkR = await suite.importKey("jwk", jwkPkR, true); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: pkR, + // }); + + // const jwkSkR = { + // kty: "OKP", + // crv: "X25519", + // kid: "X25519-01", + // x: "y3wJq3uXPHeoCO4FubvTc7VcBuqpvUrSvU6ZMbHDTCI", + // d: "vsJ1oX5NNi0IGdwGldiac75r-Utmq3Jq4LGv48Q_Qc4", + // key_ops: ["deriveBits"], + // }; + // const skR = await suite.importKey("jwk", jwkSkR, false); + // const recipient = await suite.createRecipientContext({ + // recipientKey: skR, + // enc: sender.enc, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + // await assertRejects(() => recipient.seal(pt), NotSupportedError); + // await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("A README example of Base mode (KemId.DhkemX448HkdfSha256/KdfId.HkdfSha512)", () => { + it("should work normally with ids", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX448HkdfSha512, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (33) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX448HkdfSha512, + // kdf: KdfId.HkdfSha512, + // aead: AeadId.Aes256Gcm, + // }); + + // const rkp = await suite.generateKeyPair(); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally with importKey('jwk')", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX448HkdfSha512, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (33) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX448HkdfSha512, + // kdf: KdfId.HkdfSha512, + // aead: AeadId.Aes256Gcm, + // }); + + // const jwkPkR = { + // kty: "OKP", + // crv: "X448", + // kid: "X448-01", + // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + // key_ops: [], + // }; + // const pkR = await suite.importKey("jwk", jwkPkR, true); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: pkR, + // }); + + // const jwkSkR = { + // kty: "OKP", + // crv: "X448", + // kid: "X448-01", + // x: "IkLmc0klvEMXYneHMKAB6ePohryAwAPVe2pRSffIDY6NrjeYNWVX5J-fG4NV2OoU77C88A0mvxI", + // d: "rJJRG3nshyCtd9CgXld8aNaB9YXKR0UOi7zj7hApg9YH4XdBO0G8NcAFNz_uPH2GnCZVcSDgV5c", + // key_ops: ["deriveBits"], + // }; + // const skR = await suite.importKey("jwk", jwkSkR, false); + // const recipient = await suite.createRecipientContext({ + // recipientKey: skR, + // enc: sender.enc, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + // await assertRejects(() => recipient.seal(pt), NotSupportedError); + // await assertRejects(() => sender.open(ct), NotSupportedError); + }); + }); + + describe("A README example of Base mode (ExportOnly)", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.ExportOnly, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + const te = new TextEncoder(); + + // export + const pskS = sender.export(te.encode("jugemujugemu"), 32); + const pskR = recipient.export(te.encode("jugemujugemu"), 32); + assertEquals(pskR, pskS); + + // other functions are disabled. + await assertRejects( + () => sender.seal(te.encode("my-secret-message")), + NotSupportedError, + ); + await assertRejects( + () => sender.open(te.encode("xxxxxxxxxxxxxxxxx")), + NotSupportedError, + ); + }); + }); + + describe("A README example of Base mode (ExportOnly/X25519)", () => { + it("should work normally", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.ExportOnly, + // }); + + // const rkp = await suite.generateKeyPair(); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // }); + + // const te = new TextEncoder(); + + // // export + // const pskS = sender.export(te.encode("jugemujugemu"), 32); + // const pskR = recipient.export(te.encode("jugemujugemu"), 32); + // assertEquals(pskR, pskS); + + // // other functions are disabled. + // await assertRejects( + // () => sender.seal(te.encode("my-secret-message")), + // NotSupportedError, + // ); + // await assertRejects( + // () => sender.open(te.encode("xxxxxxxxxxxxxxxxx")), + // NotSupportedError, + // ); + }); + }); + + describe("A README example of PSK mode", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("A README example of Auth mode", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + const skp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("A README example of AuthPSK mode", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + const skp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + psk: { + id: new TextEncoder().encode("our-pre-shared-key-id"), + key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + }, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("A README example of AuthPSK mode (X25519)", () => { + it("should work normally", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const rkp = await suite.generateKeyPair(); + // const skp = await suite.generateKeyPair(); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // senderKey: skp, + // psk: { + // id: new TextEncoder().encode("our-pre-shared-key-id"), + // key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + // }, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // senderPublicKey: skp.publicKey, + // psk: { + // id: new TextEncoder().encode("our-pre-shared-key-id"), + // key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + // }, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("A README example of AuthPSK mode (X448)", () => { + it("should work normally", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX448HkdfSha512, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (33) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX448HkdfSha512, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const rkp = await suite.generateKeyPair(); + // const skp = await suite.generateKeyPair(); + + // const sender = await suite.createSenderContext({ + // recipientPublicKey: rkp.publicKey, + // senderKey: skp, + // psk: { + // id: new TextEncoder().encode("our-pre-shared-key-id"), + // key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + // }, + // }); + + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp, + // enc: sender.enc, + // senderPublicKey: skp.publicKey, + // psk: { + // id: new TextEncoder().encode("our-pre-shared-key-id"), + // key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"), + // }, + // }); + + // // encrypt + // const ct = await sender.seal( + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await recipient.open(ct); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("createRecipientContext with a private key as recipientKey", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp.privateKey, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("createSenderContext with a privatekey as senderKey", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + const skp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + senderKey: skp.privateKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + senderPublicKey: skp.publicKey, + }); + + // encrypt + const ct = await sender.seal( + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("seal and open (single-shot apis)", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + // encrypt + const { ct, enc } = await suite.seal( + { + recipientPublicKey: rkp.publicKey, + }, + new TextEncoder().encode("my-secret-message"), + ); + + // decrypt + const pt = await suite.open( + { + recipientKey: rkp, + enc: enc, + }, + ct, + ); + + // assert + assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + + it("should work normally (X25519)", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const rkp = await suite.generateKeyPair(); + + // // encrypt + // const { ct, enc } = await suite.seal( + // { + // recipientPublicKey: rkp.publicKey, + // }, + // new TextEncoder().encode("my-secret-message"), + // ); + + // // decrypt + // const pt = await suite.open( + // { + // recipientKey: rkp, + // enc: enc, + // }, + // ct, + // ); + + // // assert + // assertEquals(new TextDecoder().decode(pt), "my-secret-message"); + }); + }); + + describe("seal empty byte string", () => { + it("should work normally", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + + // encrypt + const ct = await sender.seal(new TextEncoder().encode("")); + + // decrypt + const pt = await recipient.open(ct); + + // assert + assertEquals(new TextDecoder().decode(pt), ""); + }); + }); + + describe("deriveKeyPair with too long ikm", () => { + it("should throw InvalidParamError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + await assertRejects( + () => suite.deriveKeyPair((new Uint8Array(129)).buffer), + InvalidParamError, + "Too long ikm", + ); + }); + }); + + describe("createSenderContext with too long info", () => { + it("should throw InvalidParamError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + await assertRejects( + () => + suite.createSenderContext({ + info: (new Uint8Array(129)).buffer, + recipientPublicKey: rkp.publicKey, + }), + InvalidParamError, + "Too long info", + ); + }); + }); + + describe("createSenderContext with too long psk.key", () => { + it("should throw InvalidParamError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + await assertRejects( + () => + suite.createSenderContext({ + psk: { + key: (new Uint8Array(129)).buffer, + id: new Uint8Array([1, 2, 3, 4]), + }, + recipientPublicKey: rkp.publicKey, + }), + InvalidParamError, + "Too long psk.key", + ); + }); + }); + + describe("createSenderContext with short psk.key", () => { + it("should throw InvalidParamError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + await assertRejects( + () => + suite.createSenderContext({ + psk: { + key: (new Uint8Array(31)).buffer, + id: new Uint8Array([1, 2, 3, 4]), + }, + recipientPublicKey: rkp.publicKey, + }), + InvalidParamError, + "PSK must have at least 32 bytes", + ); + }); + }); + + describe("createSenderContext with too long psk.id", () => { + it("should throw InvalidParamError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const rkp = await suite.generateKeyPair(); + + await assertRejects( + () => + suite.createSenderContext({ + psk: { + key: new Uint8Array(32), + id: (new Uint8Array(129)).buffer, + }, + recipientPublicKey: rkp.publicKey, + }), + InvalidParamError, + "Too long psk.id", + ); + }); + }); + + describe("importKey with invalid EC(P-256) public key", () => { + it("should throw DeserializeError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const kStr = "aabbccddeeff"; + const k = hexStringToBytes(kStr); + + // assert + await assertRejects( + () => suite.importKey("raw", k), + DeserializeError, + ); + }); + }); + + describe("importKey with invalid EC(P-256) private key", () => { + it("should throw DeserializeError", async () => { + // setup + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + + const kStr = "aabbccddeeff"; + const k = hexStringToBytes(kStr); + + // assert + await assertRejects( + () => suite.importKey("raw", k, false), + DeserializeError, + ); + }); + }); + + describe("importKey with invalid x25519 public key", () => { + it("should throw DeserializeError", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const kStr = "aabbccddeeff"; + // const k = hexStringToBytes(kStr); + + // // assert + // await assertRejects( + // () => suite.importKey("raw", k), + // DeserializeError, + // ); + }); + }); + + describe("importKey with invalid x25519 private key", () => { + it("should throw DeserializeError", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX25519HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (32) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX25519HkdfSha256, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const kStr = "aabbccddeeff"; + // const k = hexStringToBytes(kStr); + + // // assert + // await assertRejects( + // () => suite.importKey("raw", k, false), + // DeserializeError, + // ); + }); + }); + + describe("importKey with invalid x448 public key", () => { + it("should throw DeserializeError", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX448HkdfSha512, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (33) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX448HkdfSha512, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const kStr = "aabbccddeeff"; + // const k = hexStringToBytes(kStr); + + // // assert + // await assertRejects( + // () => suite.importKey("raw", k), + // DeserializeError, + // ); + }); + }); + + describe("importKey with invalid x448 private key", () => { + it("should throw DeserializeError", async () => { + // assert + await assertThrows( + () => + new CipherSuite({ + kem: KemId.DhkemX448HkdfSha512, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }), + InvalidParamError, + "The KEM (33) cannot be specified by KemId. Use submodule for the KEM", + ); + + // // setup + // const suite = new CipherSuite({ + // kem: KemId.DhkemX448HkdfSha512, + // kdf: KdfId.HkdfSha256, + // aead: AeadId.Aes128Gcm, + // }); + + // const kStr = "aabbccddeeff"; + // const k = hexStringToBytes(kStr); + + // // assert + // await assertRejects( + // () => suite.importKey("raw", k, false), + // DeserializeError, + // ); + }); + }); + + describe("A README example of Oblivious HTTP (HKDF-SHA256)", () => { + it("throw InvalidParamError bacause of the invalid hashSize", async () => { + const te = new TextEncoder(); + const cryptoApi = await loadCrypto(); + + const suite = new CipherSuite({ + kem: KemId.DhkemP256HkdfSha256, + kdf: KdfId.HkdfSha256, + aead: AeadId.Aes128Gcm, + }); + const rkp = await suite.generateKeyPair(); + + // The sender (OHTTP client) side: + const _response = te.encode("This is the response."); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const secretS = await sender.export( + te.encode("message/bhttp response"), + suite.aead.keySize, + ); + + const responseNonce = new Uint8Array(suite.aead.keySize); + cryptoApi.getRandomValues(responseNonce); + const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + const kdfS = suite.kdf; + await assertRejects( + () => kdfS.extract(saltS, new Uint8Array(secretS)), + InvalidParamError, + ); + // const prkS = await kdfS.extract(saltS, new Uint8Array(secretS)); + // const keyS = await kdfS.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await kdfS.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const kdfR = suite.kdf; + // const prkR = await kdfR.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await kdfR.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await kdfR.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + }); + }); + + describe("A README example of Oblivious HTTP (HKDF-SHA384)", () => { + it("throw InvalidParamError bacause of the invalid hashSize", async () => { + const te = new TextEncoder(); + const cryptoApi = await loadCrypto(); + + const suite = new CipherSuite({ + kem: KemId.DhkemP384HkdfSha384, + kdf: KdfId.HkdfSha384, + aead: AeadId.Aes256Gcm, + }); + const rkp = await suite.generateKeyPair(); + + // The sender (OHTTP client) side: + const _response = te.encode("This is the response."); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const secretS = await sender.export( + te.encode("message/bhttp response"), + suite.aead.keySize, + ); + + const responseNonce = new Uint8Array(suite.aead.keySize); + cryptoApi.getRandomValues(responseNonce); + const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + const kdfS = suite.kdf; + await assertRejects( + () => kdfS.extract(saltS, new Uint8Array(secretS)), + InvalidParamError, + ); + // const prkS = await kdfS.extract(saltS, new Uint8Array(secretS)); + // const keyS = await kdfS.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await kdfS.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const kdfR = suite.kdf; + // const prkR = await kdfR.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await kdfR.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await kdfR.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + }); + }); + + describe("A README example of Oblivious HTTP (HKDF-SHA512)", () => { + it("throw InvalidParamError bacause of the invalid hashSize", async () => { + if (isDeno()) { + return; + } + const te = new TextEncoder(); + const cryptoApi = await loadCrypto(); + + const suite = new CipherSuite({ + kem: KemId.DhkemP521HkdfSha512, + kdf: KdfId.HkdfSha512, + aead: AeadId.Aes256Gcm, + }); + const rkp = await suite.generateKeyPair(); + + // The sender (OHTTP client) side: + const _response = te.encode("This is the response."); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + + const secretS = await sender.export( + te.encode("message/bhttp response"), + suite.aead.keySize, + ); + + const responseNonce = new Uint8Array(suite.aead.keySize); + cryptoApi.getRandomValues(responseNonce); + const saltS = concat(new Uint8Array(sender.enc), responseNonce); + + const kdfS = suite.kdf; + await assertRejects( + () => kdfS.extract(saltS, new Uint8Array(secretS)), + InvalidParamError, + ); + // const prkS = await kdfS.extract(saltS, new Uint8Array(secretS)); + // const keyS = await kdfS.expand( + // prkS, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceS = await kdfS.expand( + // prkS, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + + // const aeadKeyS = await suite.aead.createEncryptionContext(keyS); + // const ct = await aeadKeyS.seal(nonceS, response, te.encode("")); + // const encResponse = concat(responseNonce, new Uint8Array(ct)); + + // // The recipient (OHTTP server) side: + // const recipient = await suite.createRecipientContext({ + // recipientKey: rkp.privateKey, + // enc: sender.enc, + // }); + + // const secretR = await recipient.export( + // te.encode("message/bhttp response"), + // suite.aead.keySize, + // ); + + // const saltR = concat( + // new Uint8Array(sender.enc), + // encResponse.slice(0, suite.aead.keySize), + // ); + // const kdfR = suite.kdf; + // const prkR = await kdfR.extract( + // saltR, + // new Uint8Array(secretR), + // ); + // const keyR = await kdfR.expand( + // prkR, + // te.encode("key"), + // suite.aead.keySize, + // ); + // const nonceR = await kdfR.expand( + // prkR, + // te.encode("nonce"), + // suite.aead.nonceSize, + // ); + // const aeadKeyR = await suite.aead.createEncryptionContext(keyR); + // const pt = await aeadKeyR.open( + // nonceR, + // encResponse.slice(suite.aead.keySize), + // te.encode(""), + // ); + + // // pt === "This is the response." + // assertEquals(response, new Uint8Array(pt)); + }); + }); +}); diff --git a/core/test/runtimes/browsers/core.spec.ts b/core/test/runtimes/browsers/core.spec.ts new file mode 100644 index 000000000..ef8738e58 --- /dev/null +++ b/core/test/runtimes/browsers/core.spec.ts @@ -0,0 +1,9 @@ +import { expect, test } from "@playwright/test"; + +test("basic test", async ({ page }) => { + await page.goto("https://dajiaji.github.io/hpke-js/core"); + await page.click("text=run"); + await page.waitForTimeout(5000); + await expect(page.locator("id=pass")).toHaveText("18"); + await expect(page.locator("id=fail")).toHaveText("0"); +}); diff --git a/core/test/runtimes/browsers/package.json b/core/test/runtimes/browsers/package.json new file mode 100644 index 000000000..22ec1425b --- /dev/null +++ b/core/test/runtimes/browsers/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@playwright/test": "^1.36.1" + } +} diff --git a/core/test/runtimes/browsers/pages/index.html b/core/test/runtimes/browsers/pages/index.html new file mode 100644 index 000000000..867629be5 --- /dev/null +++ b/core/test/runtimes/browsers/pages/index.html @@ -0,0 +1,95 @@ + + @hpke/core test + + + +

@hpke/core test

+ +
+ + +
+ +
+ +
+ + + + + + + + + +
pass: -
fail: -
+
+ + diff --git a/core/test/runtimes/browsers/pages/src/.gitkeep b/core/test/runtimes/browsers/pages/src/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/core/test/runtimes/browsers/playwright.config.ts b/core/test/runtimes/browsers/playwright.config.ts new file mode 100644 index 000000000..ec692f847 --- /dev/null +++ b/core/test/runtimes/browsers/playwright.config.ts @@ -0,0 +1,20 @@ +// playwright.config.ts +import { devices, PlaywrightTestConfig } from "@playwright/test"; + +const config: PlaywrightTestConfig = { + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ], +}; +export default config; diff --git a/core/test/runtimes/bun/core.spec.ts b/core/test/runtimes/bun/core.spec.ts new file mode 100644 index 000000000..a975d8289 --- /dev/null +++ b/core/test/runtimes/bun/core.spec.ts @@ -0,0 +1,69 @@ +import { assertEquals } from "testing/asserts.ts"; +import { describe, it } from "testing/bdd.ts"; + +describe("Bun", () => { + describe("GET /test?kem=0x0010", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:3002/test?kem=0x0010&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + describe("GET /test?kem=0x0011", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:3002/test?kem=0x0011&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + describe("GET /test?kem=0x0012", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:3002/test?kem=0x0012&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + // describe("GET /test?kem=0x0020", () => { + // it("should return ok", async () => { + // for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + // for (const aead of ["0x0001", "0x0002"]) { + // const res = await fetch( + // `http://localhost:3002/test?kem=0x0020&kdf=${kdf}&aead=${aead}`, + // ); + // assertEquals("ok", await res.text()); + // } + // } + // }); + // }); + + // describe("GET /test?kem=0x0021", () => { + // it("should return ok", async () => { + // for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + // for (const aead of ["0x0001", "0x0002"]) { + // const res = await fetch( + // `http://localhost:3002/test?kem=0x0021&kdf=${kdf}&aead=${aead}`, + // ); + // assertEquals("ok", await res.text()); + // } + // } + // }); + // }); +}); diff --git a/core/test/runtimes/bun/src/index.js b/core/test/runtimes/bun/src/index.js new file mode 100644 index 000000000..b765e76ea --- /dev/null +++ b/core/test/runtimes/bun/src/index.js @@ -0,0 +1,8 @@ +import { testServer } from "../../server.js"; + +export default { + port: 3002, + async fetch(request) { + return await testServer(request); + }, +}; diff --git a/core/test/runtimes/cloudflare/core.spec.ts b/core/test/runtimes/cloudflare/core.spec.ts new file mode 100644 index 000000000..ba8a0442b --- /dev/null +++ b/core/test/runtimes/cloudflare/core.spec.ts @@ -0,0 +1,69 @@ +import { assertEquals } from "testing/asserts.ts"; +import { describe, it } from "testing/bdd.ts"; + +describe("Cloudflare Workers", () => { + describe("GET /test?kem=0x0010", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:8789/test?kem=0x0010&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + describe("GET /test?kem=0x0011", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:8789/test?kem=0x0011&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + describe("GET /test?kem=0x0012", () => { + it("should return ok", async () => { + for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + for (const aead of ["0x0001", "0x0002"]) { + const res = await fetch( + `http://localhost:8789/test?kem=0x0012&kdf=${kdf}&aead=${aead}`, + ); + assertEquals("ok", await res.text()); + } + } + }); + }); + + // describe("GET /test?kem=0x0020", () => { + // it("should return ok", async () => { + // for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + // for (const aead of ["0x0001", "0x0002"]) { + // const res = await fetch( + // `http://localhost:8789/test?kem=0x0020&kdf=${kdf}&aead=${aead}`, + // ); + // assertEquals("ok", await res.text()); + // } + // } + // }); + // }); + + // describe("GET /test?kem=0x0021", () => { + // it("should return ok", async () => { + // for (const kdf of ["0x0001", "0x0002", "0x0003"]) { + // for (const aead of ["0x0001", "0x0002"]) { + // const res = await fetch( + // `http://localhost:8789/test?kem=0x0021&kdf=${kdf}&aead=${aead}`, + // ); + // assertEquals("ok", await res.text()); + // } + // } + // }); + // }); +}); diff --git a/core/test/runtimes/cloudflare/package.json b/core/test/runtimes/cloudflare/package.json new file mode 100644 index 000000000..29979386d --- /dev/null +++ b/core/test/runtimes/cloudflare/package.json @@ -0,0 +1,12 @@ +{ + "name": "wrangler", + "version": "0.1.0", + "devDependencies": { + "wrangler": "^3.3.0" + }, + "private": true, + "scripts": { + "start": "wrangler dev --port 8789", + "deploy": "wrangler publish" + } +} diff --git a/core/test/runtimes/cloudflare/src/index.js b/core/test/runtimes/cloudflare/src/index.js new file mode 100644 index 000000000..2b1b697ea --- /dev/null +++ b/core/test/runtimes/cloudflare/src/index.js @@ -0,0 +1,7 @@ +import { testServer } from "../../server.js"; + +export default { + async fetch(request) { + return await testServer(request); + }, +}; diff --git a/core/test/runtimes/cloudflare/wrangler.toml b/core/test/runtimes/cloudflare/wrangler.toml new file mode 100644 index 000000000..ee79b9b87 --- /dev/null +++ b/core/test/runtimes/cloudflare/wrangler.toml @@ -0,0 +1,4 @@ +name = "wrangler" +main = "src/index.js" +compatibility_date = "2022-07-01" +node_compat = true diff --git a/core/test/runtimes/server.js b/core/test/runtimes/server.js new file mode 100644 index 000000000..30f1cf20f --- /dev/null +++ b/core/test/runtimes/server.js @@ -0,0 +1,41 @@ +import * as hpke from "./hpke-core.js"; + +export async function testServer(request) { + const url = new URL(request.url); + if (url.pathname !== "/test") { + return new Response("ng: invalid path"); + } + const params = url.searchParams; + const kemStr = params.get("kem"); + const kdfStr = params.get("kdf"); + const aeadStr = params.get("aead"); + if (kemStr === null || kdfStr === null || aeadStr === null) { + return new Response("ng: invalid params"); + } + const kem = Number.parseInt(kemStr); + const kdf = Number.parseInt(kdfStr); + const aead = Number.parseInt(aeadStr); + if (Number.isNaN(kem) || Number.isNaN(kdf) || Number.isNaN(aead)) { + return new Response("ng: invalid params"); + } + + try { + const suite = new hpke.CipherSuite({ kem: kem, kdf: kdf, aead: aead }); + const rkp = await suite.generateKeyPair(); + const sender = await suite.createSenderContext({ + recipientPublicKey: rkp.publicKey, + }); + const recipient = await suite.createRecipientContext({ + recipientKey: rkp, + enc: sender.enc, + }); + const ct = await sender.seal(new TextEncoder().encode("hello world!")); + const pt = await recipient.open(ct); + if ("hello world!" !== new TextDecoder().decode(pt)) { + return new Response("ng"); + } + } catch (e) { + return new Response("ng: " + e.message); + } + return new Response("ok"); +} diff --git a/deno.json b/deno.json index 1eed82689..fcc3dc593 100644 --- a/deno.json +++ b/deno.json @@ -15,6 +15,7 @@ "test/" ], "exclude": [ + "core", "x", "samples/node/node_modules", "samples/ts-node/node_modules", @@ -26,6 +27,7 @@ "lint": { "include": ["mod.ts", "samples/", "src/", "test/"], "exclude": [ + "core", "x", "samples/node/node_modules", "samples/ts-node/node_modules", diff --git a/dnt.ts b/dnt.ts index 4e95cd377..9ced28770 100644 --- a/dnt.ts +++ b/dnt.ts @@ -1,6 +1,7 @@ import { build, emptyDir } from "dnt"; await emptyDir("./npm"); +await emptyDir("./core/npm"); await emptyDir("./x/dhkem-secp256k1/npm"); await build({