From d3b05882a9509f36996d39ac694c4c4c24581a35 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Fri, 20 Sep 2024 17:03:35 -0500 Subject: [PATCH 01/67] factoring --- package-lock.json | 202 ++- package.json | 6 +- scripts/data/algorithms.csv | 82 ++ scripts/data/elliptic-curves.csv | 17 + scripts/data/key-common-parameters.csv | 10 + scripts/data/key-type-parameters.csv | 29 + scripts/data/key-type.csv | 8 + scripts/make-iana-assignments.ts | 394 +++++ scripts/make-iana-cose-algorithms.js | 19 - scripts/make-iana-cose-elliptic-curves.js | 24 - scripts/make-iana-cose-key-type-parameters.js | 20 - scripts/make-iana-cose-key-type.js | 22 - scripts/make-iana-key-common-parameters.js | 21 - src/cose/algorithms.ts | 740 ---------- src/cose/elliptic-curves.ts | 155 -- src/cose/key-common-parameters.ts | 74 - src/cose/key-type-parameters.ts | 226 --- src/cose/key-type.ts | 58 - src/cose/key/convertCoseKeyToJsonWebKey.ts | 28 +- src/cose/key/convertJsonWebKeyToCoseKey.ts | 155 +- src/cose/key/generate.ts | 21 +- src/cose/sign1/verifier.ts | 6 +- src/iana/assignments/cose.ts | 1286 +++++++++++++++++ src/iana/assignments/media-types.ts | 4 + src/iana/index.ts | 64 - src/iana/requested/cose.ts | 19 + src/index.ts | 3 +- src/x509/certificate.ts | 14 +- test/fully-specified.test.ts | 5 +- test/x509.test.ts | 12 +- 30 files changed, 2075 insertions(+), 1649 deletions(-) create mode 100644 scripts/data/algorithms.csv create mode 100644 scripts/data/elliptic-curves.csv create mode 100644 scripts/data/key-common-parameters.csv create mode 100644 scripts/data/key-type-parameters.csv create mode 100644 scripts/data/key-type.csv create mode 100644 scripts/make-iana-assignments.ts delete mode 100644 scripts/make-iana-cose-algorithms.js delete mode 100644 scripts/make-iana-cose-elliptic-curves.js delete mode 100644 scripts/make-iana-cose-key-type-parameters.js delete mode 100644 scripts/make-iana-cose-key-type.js delete mode 100644 scripts/make-iana-key-common-parameters.js delete mode 100644 src/cose/algorithms.ts delete mode 100644 src/cose/elliptic-curves.ts delete mode 100644 src/cose/key-common-parameters.ts delete mode 100644 src/cose/key-type-parameters.ts delete mode 100644 src/cose/key-type.ts create mode 100644 src/iana/assignments/cose.ts create mode 100644 src/iana/assignments/media-types.ts delete mode 100644 src/iana/index.ts create mode 100644 src/iana/requested/cose.ts diff --git a/package-lock.json b/package-lock.json index cee6740..bae408c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "jest": "^29.3.1", "moment": "^2.30.1", "ts-jest": "^29.0.3", + "ts-node": "^10.9.2", "typescript": "^4.9.4" } }, @@ -556,6 +557,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -1044,39 +1069,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@noble/ciphers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", - "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -1302,6 +1294,34 @@ "version": "0.0.5", "license": "Apache-2.0" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.0", "dev": true, @@ -1596,7 +1616,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "license": "MIT", "bin": { @@ -1614,6 +1636,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -1688,6 +1723,13 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -2042,6 +2084,13 @@ "dev": true, "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "dev": true, @@ -2119,6 +2168,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.4.3", "dev": true, @@ -2717,20 +2776,6 @@ "node": ">=8" } }, - "node_modules/hpke-js": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/hpke-js/-/hpke-js-1.2.9.tgz", - "integrity": "sha512-rPTJshLtt3sSObIs74LAQt1os59gHJav81DEMU2j3S5zN+kctGvfxR0i8U5EVmfH9OsgxSFBNl7ao/0jFE1f6g==", - "license": "MIT", - "dependencies": { - "@noble/ciphers": "0.5.3", - "@noble/curves": "1.4.2", - "@noble/hashes": "1.4.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, @@ -4490,6 +4535,50 @@ } } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "1.14.1", "license": "0BSD" @@ -4597,6 +4686,13 @@ "punycode": "^2.1.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.1.0", "dev": true, @@ -4716,6 +4812,16 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "dev": true, diff --git a/package.json b/package.json index 8971de2..8b62941 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "coverage": "jest --ci --coverage", "lint": "eslint ./src ./test --fix", "shove": "git add -A; git commit -m ':rocket:'; git push origin main", - "generate:iana:ts": "./scripts/make-iana.sh" + "generate:iana:ts": "./scripts/make-iana.sh", + "iana:assignments": "npx ts-node ./scripts/make-iana-assignments.ts" }, "repository": { "type": "git", @@ -41,6 +42,7 @@ "jest": "^29.3.1", "moment": "^2.30.1", "ts-jest": "^29.0.3", + "ts-node": "^10.9.2", "typescript": "^4.9.4" }, "dependencies": { @@ -49,4 +51,4 @@ "cbor-web": "^9.0.2", "jose": "^4.14.4" } -} +} \ No newline at end of file diff --git a/scripts/data/algorithms.csv b/scripts/data/algorithms.csv new file mode 100644 index 0000000..3028b11 --- /dev/null +++ b/scripts/data/algorithms.csv @@ -0,0 +1,82 @@ +Name,Value,Description,Capabilities,Change Controller,Reference,Recommended +Reserved for Private Use,less than -65536,,,,[RFC9053],No +Unassigned,-65536,,,,, +RS1,-65535,RSASSA-PKCS1-v1_5 using SHA-1,[kty],IESG,[RFC8812][RFC9053],Deprecated +A128CTR,-65534,AES-CTR w/ 128-bit key,[kty],IETF,[RFC9459],Deprecated +A192CTR,-65533,AES-CTR w/ 192-bit key,[kty],IETF,[RFC9459],Deprecated +A256CTR,-65532,AES-CTR w/ 256-bit key,[kty],IETF,[RFC9459],Deprecated +A128CBC,-65531,AES-CBC w/ 128-bit key,[kty],IETF,[RFC9459],Deprecated +A192CBC,-65530,AES-CBC w/ 192-bit key,[kty],IETF,[RFC9459],Deprecated +A256CBC,-65529,AES-CBC w/ 256-bit key,[kty],IETF,[RFC9459],Deprecated +Unassigned,-65528 to -261,,,,, +WalnutDSA,-260,WalnutDSA signature,[kty],,[RFC9021][RFC9053],No +RS512,-259,RSASSA-PKCS1-v1_5 using SHA-512,[kty],IESG,[RFC8812][RFC9053],No +RS384,-258,RSASSA-PKCS1-v1_5 using SHA-384,[kty],IESG,[RFC8812][RFC9053],No +RS256,-257,RSASSA-PKCS1-v1_5 using SHA-256,[kty],IESG,[RFC8812][RFC9053],No +Unassigned,-256 to -48,,,,, +ES256K,-47,ECDSA using secp256k1 curve and SHA-256,[kty],IESG,[RFC8812][RFC9053],No +HSS-LMS,-46,HSS/LMS hash-based digital signature,[kty],,[RFC8778][RFC9053],Yes +SHAKE256,-45,SHAKE-256 512-bit Hash Value,[kty],,[RFC9054][RFC9053],Yes +SHA-512,-44,SHA-2 512-bit Hash,[kty],,[RFC9054][RFC9053],Yes +SHA-384,-43,SHA-2 384-bit Hash,[kty],,[RFC9054][RFC9053],Yes +RSAES-OAEP w/ SHA-512,-42,RSAES-OAEP w/ SHA-512,[kty],,[RFC8230][RFC9053],Yes +RSAES-OAEP w/ SHA-256,-41,RSAES-OAEP w/ SHA-256,[kty],,[RFC8230][RFC9053],Yes +RSAES-OAEP w/ RFC 8017 default parameters,-40,RSAES-OAEP w/ SHA-1,[kty],,[RFC8230][RFC9053],Yes +PS512,-39,RSASSA-PSS w/ SHA-512,[kty],,[RFC8230][RFC9053],Yes +PS384,-38,RSASSA-PSS w/ SHA-384,[kty],,[RFC8230][RFC9053],Yes +PS256,-37,RSASSA-PSS w/ SHA-256,[kty],,[RFC8230][RFC9053],Yes +ES512,-36,ECDSA w/ SHA-512,[kty],,[RFC9053],Yes +ES384,-35,ECDSA w/ SHA-384,[kty],,[RFC9053],Yes +ECDH-SS + A256KW,-34,ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key,[kty],,[RFC9053],Yes +ECDH-SS + A192KW,-33,ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key,[kty],,[RFC9053],Yes +ECDH-SS + A128KW,-32,ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key,[kty],,[RFC9053],Yes +ECDH-ES + A256KW,-31,ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key,[kty],,[RFC9053],Yes +ECDH-ES + A192KW,-30,ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key,[kty],,[RFC9053],Yes +ECDH-ES + A128KW,-29,ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key,[kty],,[RFC9053],Yes +ECDH-SS + HKDF-512,-28,ECDH SS w/ HKDF - generate key directly,[kty],,[RFC9053],Yes +ECDH-SS + HKDF-256,-27,ECDH SS w/ HKDF - generate key directly,[kty],,[RFC9053],Yes +ECDH-ES + HKDF-512,-26,ECDH ES w/ HKDF - generate key directly,[kty],,[RFC9053],Yes +ECDH-ES + HKDF-256,-25,ECDH ES w/ HKDF - generate key directly,[kty],,[RFC9053],Yes +Unassigned,-24 to -19,,,,, +SHAKE128,-18,SHAKE-128 256-bit Hash Value,[kty],,[RFC9054][RFC9053],Yes +SHA-512/256,-17,SHA-2 512-bit Hash truncated to 256-bits,[kty],,[RFC9054][RFC9053],Yes +SHA-256,-16,SHA-2 256-bit Hash,[kty],,[RFC9054][RFC9053],Yes +SHA-256/64,-15,SHA-2 256-bit Hash truncated to 64-bits,[kty],,[RFC9054][RFC9053],Filter Only +SHA-1,-14,SHA-1 Hash,[kty],,[RFC9054][RFC9053],Filter Only +direct+HKDF-AES-256,-13,Shared secret w/ AES-MAC 256-bit key,[kty],,[RFC9053],Yes +direct+HKDF-AES-128,-12,Shared secret w/ AES-MAC 128-bit key,[kty],,[RFC9053],Yes +direct+HKDF-SHA-512,-11,Shared secret w/ HKDF and SHA-512,[kty],,[RFC9053],Yes +direct+HKDF-SHA-256,-10,Shared secret w/ HKDF and SHA-256,[kty],,[RFC9053],Yes +Unassigned,-9,,,,, +EdDSA,-8,EdDSA,[kty],,[RFC9053],Yes +ES256,-7,ECDSA w/ SHA-256,[kty],,[RFC9053],Yes +direct,-6,Direct use of CEK,[kty],,[RFC9053],Yes +A256KW,-5,AES Key Wrap w/ 256-bit key,[kty],,[RFC9053],Yes +A192KW,-4,AES Key Wrap w/ 192-bit key,[kty],,[RFC9053],Yes +A128KW,-3,AES Key Wrap w/ 128-bit key,[kty],,[RFC9053],Yes +Unassigned,-2 to -1,,,,, +Reserved,0,,,,[RFC9053],No +A128GCM,1,"AES-GCM mode w/ 128-bit key, 128-bit tag",[kty],,[RFC9053],Yes +A192GCM,2,"AES-GCM mode w/ 192-bit key, 128-bit tag",[kty],,[RFC9053],Yes +A256GCM,3,"AES-GCM mode w/ 256-bit key, 128-bit tag",[kty],,[RFC9053],Yes +HMAC 256/64,4,HMAC w/ SHA-256 truncated to 64 bits,[kty],,[RFC9053],Yes +HMAC 256/256,5,HMAC w/ SHA-256,[kty],,[RFC9053],Yes +HMAC 384/384,6,HMAC w/ SHA-384,[kty],,[RFC9053],Yes +HMAC 512/512,7,HMAC w/ SHA-512,[kty],,[RFC9053],Yes +Unassigned,8-9,,,,, +AES-CCM-16-64-128,10,"AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-16-64-256,11,"AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-64-64-128,12,"AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-64-64-256,13,"AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce",[kty],,[RFC9053],Yes +AES-MAC 128/64,14,"AES-MAC 128-bit key, 64-bit tag",[kty],,[RFC9053],Yes +AES-MAC 256/64,15,"AES-MAC 256-bit key, 64-bit tag",[kty],,[RFC9053],Yes +Unassigned,16-23,,,,, +ChaCha20/Poly1305,24,"ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag",[kty],,[RFC9053],Yes +AES-MAC 128/128,25,"AES-MAC 128-bit key, 128-bit tag",[kty],,[RFC9053],Yes +AES-MAC 256/128,26,"AES-MAC 256-bit key, 128-bit tag",[kty],,[RFC9053],Yes +Unassigned,27-29,,,,, +AES-CCM-16-128-128,30,"AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-16-128-256,31,"AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-64-128-128,32,"AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce",[kty],,[RFC9053],Yes +AES-CCM-64-128-256,33,"AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce",[kty],,[RFC9053],Yes +IV-GENERATION,34,For doing IV generation for symmetric algorithms.,,,[RFC9053],No diff --git a/scripts/data/elliptic-curves.csv b/scripts/data/elliptic-curves.csv new file mode 100644 index 0000000..fce3d68 --- /dev/null +++ b/scripts/data/elliptic-curves.csv @@ -0,0 +1,17 @@ +Name,Value,Key Type,Description,Change Controller,Reference,Recommended +Reserved for Private Use,Integer values less than -65536,,,,[RFC9053],No +Unassigned,-65536 to -1,,,,, +Reserved,0,,,,[RFC9053],No +P-256,1,EC2,NIST P-256 also known as secp256r1,,[RFC9053],Yes +P-384,2,EC2,NIST P-384 also known as secp384r1,,[RFC9053],Yes +P-521,3,EC2,NIST P-521 also known as secp521r1,,[RFC9053],Yes +X25519,4,OKP,X25519 for use w/ ECDH only,,[RFC9053],Yes +X448,5,OKP,X448 for use w/ ECDH only,,[RFC9053],Yes +Ed25519,6,OKP,Ed25519 for use w/ EdDSA only,,[RFC9053],Yes +Ed448,7,OKP,Ed448 for use w/ EdDSA only,,[RFC9053],Yes +secp256k1,8,EC2,SECG secp256k1 curve,IESG,[RFC8812],No +Unassigned,9-255,,,,, +brainpoolP256r1,256,EC2,BrainpoolP256r1,[ISO/IEC JTC 1/SC 17/WG 10],"[ISO/IEC 18013-5:2021, 9.1.5.2]",No +brainpoolP320r1,257,EC2,BrainpoolP320r1,[ISO/IEC JTC 1/SC 17/WG 10],"[ISO/IEC 18013-5:2021, 9.1.5.2]",No +brainpoolP384r1,258,EC2,BrainpoolP384r1,[ISO/IEC JTC 1/SC 17/WG 10],"[ISO/IEC 18013-5:2021, 9.1.5.2]",No +brainpoolP512r1,259,EC2,BrainpoolP512r1,[ISO/IEC JTC 1/SC 17/WG 10],"[ISO/IEC 18013-5:2021, 9.1.5.2]",No diff --git a/scripts/data/key-common-parameters.csv b/scripts/data/key-common-parameters.csv new file mode 100644 index 0000000..14cd9c5 --- /dev/null +++ b/scripts/data/key-common-parameters.csv @@ -0,0 +1,10 @@ +Name,Label,CBOR Type,Value Registry,Description,Reference +Reserved for Private Use,less than -65536,,,,[RFC9052] +"used for key parameters specific to a single algorithm + delegated to the COSE Key Type Parameters registry",-65536 to -1,,,,[RFC9052] +Reserved,0,,,,[RFC9052] +kty,1,tstr / int,COSE Key Types,Identification of the key type,[RFC9052] +kid,2,bstr,,Key identification value - match to kid in message,[RFC9052] +alg,3,tstr / int,COSE Algorithms,Key usage restriction to this algorithm,[RFC9052] +key_ops,4,[+ (tstr/int)],,Restrict set of permissible operations,[RFC9052] +Base IV,5,bstr,,Base IV to be XORed with Partial IVs,[RFC9052] diff --git a/scripts/data/key-type-parameters.csv b/scripts/data/key-type-parameters.csv new file mode 100644 index 0000000..e34e265 --- /dev/null +++ b/scripts/data/key-type-parameters.csv @@ -0,0 +1,29 @@ +Key Type,Name,Label,CBOR Type,Description,Reference +1,crv,-1,int / tstr,"EC identifier -- Taken from the ""COSE Elliptic Curves"" registry",[RFC9053] +1,x,-2,bstr,Public Key,[RFC9053] +1,d,-4,bstr,Private key,[RFC9053] +2,crv,-1,int / tstr,"EC identifier -- Taken from the ""COSE Elliptic Curves"" registry",[RFC9053] +2,x,-2,bstr,x-coordinate,[RFC9053] +2,y,-3,bstr / bool,y-coordinate,[RFC9053] +2,d,-4,bstr,Private key,[RFC9053] +3,n,-1,bstr,the RSA modulus n,[RFC8230] +3,e,-2,bstr,the RSA public exponent e,[RFC8230] +3,d,-3,bstr,the RSA private exponent d,[RFC8230] +3,p,-4,bstr,the prime factor p of n,[RFC8230] +3,q,-5,bstr,the prime factor q of n,[RFC8230] +3,dP,-6,bstr,dP is d mod (p - 1),[RFC8230] +3,dQ,-7,bstr,dQ is d mod (q - 1),[RFC8230] +3,qInv,-8,bstr,qInv is the CRT coefficient q^(-1) mod p,[RFC8230] +3,other,-9,array,"other prime infos, an array",[RFC8230] +3,r_i,-10,bstr,"a prime factor r_i of n, where i >= 3",[RFC8230] +3,d_i,-11,bstr,d_i = d mod (r_i - 1),[RFC8230] +3,t_i,-12,bstr,"the CRT coefficient t_i = (r_1 * r_2 * ... * + r_(i-1))^(-1) mod r_i",[RFC8230] +4,k,-1,bstr,Key Value,[RFC9053] +5,pub,-1,bstr,Public key for HSS/LMS hash-based digital signature,[RFC8778] +6,N,-1,uint,Group and Matrix (NxN) size,[RFC9021] +6,q,-2,uint,Finite field F_q,[RFC9021] +6,t-values,-3,array (of uint),"List of T-values, entries in F_q",[RFC9021] +6,matrix 1,-4,array (of array of uint),NxN Matrix of entries in F_q in column-major form,[RFC9021] +6,permutation 1,-5,array (of uint),Permutation associated with matrix 1,[RFC9021] +6,matrix 2,-6,array (of array of uint),NxN Matrix of entries in F_q in column-major form,[RFC9021] diff --git a/scripts/data/key-type.csv b/scripts/data/key-type.csv new file mode 100644 index 0000000..6f75f7b --- /dev/null +++ b/scripts/data/key-type.csv @@ -0,0 +1,8 @@ +Name,Value,Description,Capabilities,Reference +Reserved,0,This value is reserved,,[RFC9053] +OKP,1,Octet Key Pair,"[kty(1), crv]",[RFC9053] +EC2,2,Elliptic Curve Keys w/ x- and y-coordinate pair,"[kty(2), crv]",[RFC9053] +RSA,3,RSA Key,[kty(3)],[RFC8230][RFC9053] +Symmetric,4,Symmetric Keys,[kty(4)],[RFC9053] +HSS-LMS,5,Public key for HSS/LMS hash-based digital signature,"[kty(5), hash algorithm]",[RFC8778][RFC9053] +WalnutDSA,6,WalnutDSA public key,[kty(6)],[RFC9021][RFC9053] diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts new file mode 100644 index 0000000..d050d7b --- /dev/null +++ b/scripts/make-iana-assignments.ts @@ -0,0 +1,394 @@ +/* eslint-disable no-async-promise-executor */ + +import fs from 'fs'; + +import csv from 'csv-parser' + +const cddlToType = (cddl: string) => { + // invalid cddl in iana registry + if (cddl.includes('array')) { + return cddl + .replace("array (of array of uint)", "Array>") + .replace("array (of uint)", "Array") + .replace("array (of number)", "Array") + .replace("array", "Array") + } + if (cddl.length) { + return cddl + .replace(/\//g, '|') + .replace(/tstr/g, 'string') + .replace(/bstr/g, 'Buffer') + .replace(/uint/g, 'number') + .replace(/int/g, 'number') + .replace(/bool/g, 'boolean') + .replace(/\+/g, '') + } + return 'string' +} + +const curveLabels = [] as any +const commonKeyParamsMap = new Map() + +const getCommonKeyParams = () => { + return new Promise(async (resolve) => { + // https://www.iana.org/assignments/cose/key-common-parameters.csv + const stream = fs.createReadStream('./scripts/data/key-common-parameters.csv') + .pipe(csv()) + const commonKeyParams = {} as any + let commonKeyParamDefinitions = `\n` + stream.on('data', (row: any) => { + if (row.Label.includes('-')) { + return + } + if (row.Reference.startsWith('[RFC')) { + row.Reference = `https://datatracker.ietf.org/doc/${row.Reference.substring(1, row.Reference.length - 1)}` + } + const valueName = row.Name.replace(/ /g, '_').toLowerCase() + const valueType = cddlToType(row['CBOR Type']) + commonKeyParams[row['Label']] = [valueName, valueType] + commonKeyParamDefinitions += ` +export const ${valueName}_key_parameter = { + 'Name': '${row['Name']}', + 'Label': ${row['Label']}, + 'CBOR Type': '${row['CBOR Type']}', + 'Value Registry': '${row['Value Registry']}', + 'Description': '${row['Description'].replace(/\\n/g, ' ')}', + 'Reference': '${row['Reference']}' +} + `.trim() + '\n' + }); + stream.on('end', () => { + commonKeyParamDefinitions += `export enum cose_key {\n` + for (const entry of Object.entries(commonKeyParams)) { + const [label, [name, type]] = entry as any + if (label <= 0) { + continue + } + commonKeyParamDefinitions += ` ${name} = ${label},\n` + commonKeyParamsMap.set(parseInt(label, 10), name) + } + commonKeyParamDefinitions += '}\n' + commonKeyParamDefinitions = commonKeyParamDefinitions.trim() + resolve({ commonKeyParams, commonKeyParamDefinitions }) + }); + }) +} + +const getKeyTypes = async () => { + return new Promise(async (resolve) => { + // https://www.iana.org/assignments/cose/key-type.csv + const stream = fs.createReadStream('./scripts/data/key-type.csv') + .pipe(csv()) + let keyTypeDefinitions = `\n` + const keyTypes = new Map() + stream.on('data', (row: any) => { + if (row.Reference.startsWith('[RFC')) { + const matches = row.Reference.match(/[(RFC\d+)]+/g) + if (matches.length) { + row.Reference = matches.map((rfc: any) => { + return `https://datatracker.ietf.org/doc/${rfc.toLowerCase()}` + }).join(' ') + } + } + row['Value'] = parseInt(row['Value'], 10) + const valueName = row['Name'].replace(/ /g, '_').replace(/-/g, '_').toLowerCase() + keyTypeDefinitions += ` +export const ${valueName}_key_type = { + 'Name': '${row['Name']}', + 'Value': ${row['Value']}, + 'Description': '${row['Description'].replace(/\n /g, ' ')}', + 'Capabilities': '${row['Capabilities']}', + 'Reference': '${row['Reference']}' +} + `.trim() + '\n' + const k = new Map() + k.set(1, ['kty', row['Value']]) + keyTypes.set(row['Value'], [valueName, k]) + }); + stream.on('end', () => { + keyTypeDefinitions += `export enum cose_key_type {\n` + for (const [label, [name, type]] of keyTypes.entries()) { + if (label <= 0) { + continue + } + keyTypeDefinitions += ` ${name} = ${label},\n` + } + keyTypeDefinitions += '}\n' + return resolve({ keyTypes, keyTypeDefinitions }) + }); + }) +} + +const getCurves = async () => { + return new Promise(async (resolve) => { + // https://www.iana.org/assignments/cose/elliptic-curves.csv + const stream = fs.createReadStream('./scripts/data/elliptic-curves.csv') + .pipe(csv()) + let curveDefinitions = `\n` + const curvesByKeyType = new Map() + stream.on('data', (row: any) => { + if (row.Value.includes('-')) { + return + } + if (row.Reference.startsWith('[RFC')) { + const matches = row.Reference.match(/[(RFC\d+)]+/g) + if (matches.length) { + row.Reference = matches.map((rfc: any) => { + return `https://datatracker.ietf.org/doc/${rfc.toLowerCase()}` + }).join(' ') + } + } + row['Value'] = parseInt(row['Value'], 10) + const valueName = row['Name'].replace(/ /g, '_').replace(/-/g, '_').toLowerCase() + if (curvesByKeyType.get(row['Key Type']) === undefined) { + curvesByKeyType.set(row['Key Type'], new Map()) + } + const curvesForKeyType = curvesByKeyType.get(row['Key Type']) + curvesForKeyType.set(valueName, row['Value']) + curvesByKeyType.set(row['Key Type'], curvesForKeyType) + curveDefinitions += ` +export const ${valueName}_curve = { + 'Name': '${row['Name']}', + 'Value': ${row['Value']}, + 'Key Type': '${row['Key Type']}', + 'Description': '${row['Description'].replace(/\n /g, ' ')}', + 'Change Controller': '${row['Change Controller']}', + 'Reference': '${row['Reference']}', + 'Recommended': '${row['Recommended']}' +} + `.trim() + '\n' + }); + stream.on('end', () => { + // create enums for key types + + for (const [kty, curves] of curvesByKeyType.entries()) { + if (kty === '') { + continue + } + curveDefinitions += `export enum ${kty.toLowerCase()}_curves {\n` + for (const [curveName, curveLabel] of curves.entries()) { + curveDefinitions += ` ${curveName} = ${curveLabel},\n` + let joseCurveName = curveName.replace('_', '-') + if (joseCurveName.includes('-')) { + joseCurveName = joseCurveName.toUpperCase() + } + if (joseCurveName.startsWith('x') || joseCurveName.startsWith('e')) { + joseCurveName = joseCurveName.charAt(0).toUpperCase() + joseCurveName.slice(1); + } + curveLabels.push([curveLabel, joseCurveName]) + + } + curveDefinitions += '}\n' + } + return resolve({ curvesByKeyType, curveDefinitions }) + }); + }) +} + +const getKeyTypeParams = async ({ commonKeyParams, keyTypes, curvesByKeyType }: any) => { + const paramsByKeyType = new Map() + // https://www.iana.org/assignments/cose/key-type-parameters.csv + return new Promise(async (resolve) => { + const stream = fs.createReadStream('./scripts/data/key-type-parameters.csv') + .pipe(csv()) + let keyParams = `\n` + stream.on('data', row => { + if (row.Reference.startsWith('[RFC')) { + row.Reference = `https://datatracker.ietf.org/doc/${row.Reference.substring(1, row.Reference.length - 1)}` + } + row['Key Type'] = parseInt(row['Key Type'], 10) + row['Label'] = parseInt(row['Label'], 10) + // this is later used to produce fully specified key types + const [ktyName, ktyParams] = keyTypes.get(row['Key Type']) + const paramName = row['Name'].replace(/ /g, '_').replace(/-/g, '_').toLowerCase() + const valueType = cddlToType(row['CBOR Type']) + ktyParams.set(row['Label'], [paramName, valueType]) + keyParams += ` +export const ${ktyName}_${paramName}_parameter = { + 'Key Type': ${row['Key Type']}, + 'Label': ${row['Label']}, + 'Name': '${row['Name']}', + 'CBOR Type': '${row['CBOR Type']}', + 'Description': '${row['Description'].replace(/\n /g, ' ')}', + 'Reference': '${row['Reference']}' +} +`.trim() + '\n' + + }); + stream.on('end', () => { + + + + // create enums for key types + for (const [kty, [name, params]] of keyTypes.entries()) { + // skip reserved + if (kty === 0) { + continue + } + const keyName = `${name}` + keyParams += `export enum ${keyName} {\n` + // merge enums here. + for (const entry of Object.entries(commonKeyParams)) { + const [label, [name, type]] = entry as any + if (label === '0') { + continue + } + keyParams += ` ${name} = ${label},\n` + } + for (const [paramLabel, [paramName]] of params.entries()) { + if (paramName === 'kty') { + continue + } + keyParams += ` ${paramName} = ${paramLabel},\n` + } + keyParams += '}\n\n' + } + + keyParams += `\nexport type any_cose_key = {\n` + for (const entry of Object.entries(commonKeyParams)) { + const [label, [tag, type]] = entry as any + if (label === '0') { + continue + } + // skip kty + if (label === '1') { + continue + } + keyParams += ` get(k: cose_key.${tag}): ${type}\n` + } + keyParams += '}\n\n' + for (const [kty, [name, params]] of keyTypes.entries()) { + // skip reserved + if (kty === 0) { + continue + } + const keyName = `${name}_key` + const keyTypeParams = new Map(commonKeyParamsMap.entries()) + + + keyParams += `export type ${keyName} = any_cose_key & {\n` + for (const [paramLabel, [paramTag, paramValue]] of params.entries()) { + if (paramLabel === 1) { + const [keyType] = keyTypes.get(paramValue) + keyParams += ` get(k: ${name}.${paramTag}): cose_key_type.${keyType}\n` + } else { + if (paramTag === 'crv') { + keyParams += ` get(k: ${name}.${paramTag}): ${name}_curves\n` + } else { + keyParams += ` get(k: ${name}.${paramTag}): ${paramValue}\n` + } + } + + keyTypeParams.set(paramLabel, paramTag) + } + keyParams += '}\n\n' + paramsByKeyType.set(kty, keyTypeParams) + } + keyParams = keyParams.trim() + + return resolve({ keyParams, paramsByKeyType }) + }); + }) +} + + + + +const createAlgorithmDefinitions = () => { + const labelToAlgorithm = new Map() as Map + return new Promise(async (resolve) => { + const stream = fs.createReadStream('./scripts/data/algorithms.csv') + .pipe(csv()) + let algorithmDefinitions = `\n` + stream.on('data', (row: any) => { + if (row.Name === 'Unassigned') { + return + } + const valueName = row.Name + .replace(/ /g, '_') + .replace(/-/g, '_') + .replace(/\//g, '_') + .replace(/\+/g, '_') + .toLowerCase() + if (row.Reference.startsWith('[RFC')) { + row.Reference = `https://datatracker.ietf.org/doc/${row.Reference.substring(1, row.Reference.length - 1)}` + } + row['Value'] = parseInt(row['Value'], 10) + algorithmDefinitions += ` +export const ${valueName}_algorithm = { + 'Name': '${row['Name']}', + 'Value': ${row['Value']}, + 'Description': '${row['Description'].replace(/\\n/g, ' ')}', + 'Capabilities': '${row['Capabilities']}', + 'Change Controller': '${row['Change Controller']}', + 'Reference': '${row['Reference']}', + 'Recommended': '${row['Recommended']}' +} + `.trim() + '\n' + labelToAlgorithm.set(row['Value'], row['Name']) + }); + stream.on('end', () => { + resolve({ labelToAlgorithm, algorithmDefinitions }) + }); + }) +} + +const createMappings = (curveLabels: [number, string][], paramsByKeyType: Map, keyTypes: Map, labelToAlgorithm: Map) => { + + const ktyMap = { + 'okp': 'OKP', + 'ec2': 'EC', + 'rsa': 'RSA', + 'symmetric': 'oct' + } as Record + + const ktyNames = Array.from(keyTypes.entries()).map(([label, [name]]) => { + const betterName = ktyMap[name] ? ktyMap[name] : name + return [label, betterName] + }) + let mappings = ` + +// kty +export const label_to_key_type = new Map(${JSON.stringify(ktyNames)}) as Map +export const key_type_to_label = new Map([...label_to_key_type.entries()].map((e: any) => e.reverse())) as Map + +// crv +export const label_to_curve = new Map(${JSON.stringify(curveLabels)}) as Map +export const curve_to_label = new Map([...label_to_curve.entries()].map((e: any) => e.reverse())) as Map + +// alg +export const labels_to_algorithms = new Map(${JSON.stringify(Array.from(labelToAlgorithm.entries()))}) as Map +export const algorithms_to_labels = new Map([...${`labels_to_algorithms`}.entries()].map((e: any) => e.reverse())) as Map +` + + for (const [kty, params] of paramsByKeyType.entries()) { + const [ktyName] = keyTypes.get(kty) + mappings += ` +// ${ktyName} +export const labels_to_${ktyName}_params = new Map(${JSON.stringify(Array.from(params.entries()))}) as Map +export const ${ktyName}_params_to_labels = new Map([...${`labels_to_${ktyName}_params`}.entries()].map((e: any) => e.reverse())) as Map +` + } + return mappings +} + +(async () => { + const { commonKeyParams, commonKeyParamDefinitions } = await getCommonKeyParams() as any + const { keyTypes, keyTypeDefinitions } = await getKeyTypes() as any + const { curvesByKeyType, curveDefinitions } = await getCurves() as any + const { keyParams, paramsByKeyType } = await getKeyTypeParams({ commonKeyParams, keyTypes, curvesByKeyType }) as any + + const { labelToAlgorithm, algorithmDefinitions } = await createAlgorithmDefinitions() as any + const mappings = createMappings(curveLabels, paramsByKeyType, keyTypes, labelToAlgorithm) + const final = ` +// DO NOT edit this file, it is generated automatically +${commonKeyParamDefinitions} +${keyTypeDefinitions} +${curveDefinitions} +${algorithmDefinitions} +${keyParams} +${mappings} + +`.trim() + fs.writeFileSync('./src/iana/assignments/cose.ts', final) +})() \ No newline at end of file diff --git a/scripts/make-iana-cose-algorithms.js b/scripts/make-iana-cose-algorithms.js deleted file mode 100644 index 43a21ce..0000000 --- a/scripts/make-iana-cose-algorithms.js +++ /dev/null @@ -1,19 +0,0 @@ -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/algorithms.csv') - const file = ` - export type IANACOSEAlgorithm = { - Name: string - Value: string - Description: string - Capabilities: string - 'Change Controller': string - Recommended: string - Reference: string - } - export const IANACOSEAlgorithms: Record = ${JSON.stringify(record, null, 2)}; - ` - fs.writeFileSync('./src/cose/algorithms.ts', file.trim()) -})() \ No newline at end of file diff --git a/scripts/make-iana-cose-elliptic-curves.js b/scripts/make-iana-cose-elliptic-curves.js deleted file mode 100644 index 96b0ab3..0000000 --- a/scripts/make-iana-cose-elliptic-curves.js +++ /dev/null @@ -1,24 +0,0 @@ - - - -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/elliptic-curves.csv') - const file = ` - - -export type IANACOSEEllipticCurve = { - Name: string - Value: string - 'Key Type': string - Description: string - 'Change Controller': string - Reference: string - Recommended: string -} -export const IANACOSEEllipticCurves: Record = ${JSON.stringify(record, null, 2)}; -` - fs.writeFileSync('./src/cose/elliptic-curves.ts', file.trim()) -})() \ No newline at end of file diff --git a/scripts/make-iana-cose-key-type-parameters.js b/scripts/make-iana-cose-key-type-parameters.js deleted file mode 100644 index bb4c81e..0000000 --- a/scripts/make-iana-cose-key-type-parameters.js +++ /dev/null @@ -1,20 +0,0 @@ - - -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/key-type-parameters.csv') - const file = ` -export type IANACOSEKeyTypeParameter = { - 'Key Type': string - 'Name': string - 'Label': string - 'CBOR Type': string - Description: string - Reference: string -} -export const IANACOSEKeyTypeParameters: Record = ${JSON.stringify(record, null, 2)}; - ` - fs.writeFileSync('./src/cose/key-type-parameters.ts', file.trim()) -})() \ No newline at end of file diff --git a/scripts/make-iana-cose-key-type.js b/scripts/make-iana-cose-key-type.js deleted file mode 100644 index 51f6afc..0000000 --- a/scripts/make-iana-cose-key-type.js +++ /dev/null @@ -1,22 +0,0 @@ - - - - - -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/key-type.csv') - const file = ` -export type IANACOSEKeyType = { - Name: string - Value: string - Description: string - Capabilities: string - Reference: string -} -export const IANACOSEKeyTypes: Record = ${JSON.stringify(record, null, 2)}; -` - fs.writeFileSync('./src/cose/key-type.ts', file.trim()) -})() \ No newline at end of file diff --git a/scripts/make-iana-key-common-parameters.js b/scripts/make-iana-key-common-parameters.js deleted file mode 100644 index 8a8ff4c..0000000 --- a/scripts/make-iana-key-common-parameters.js +++ /dev/null @@ -1,21 +0,0 @@ - - - -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/key-common-parameters.csv') - const file = ` -export type IANACOSEKeyCommonParameter = { - Name: string - Label: string - 'CBOR Type': string - 'Value Registry': string - Description: string - Reference: string -} -export const IANACOSEKeyCommonParameters: Record = ${JSON.stringify(record, null, 2)}; -` - fs.writeFileSync('./src/cose/key-common-parameters.ts', file.trim()) -})() \ No newline at end of file diff --git a/src/cose/algorithms.ts b/src/cose/algorithms.ts deleted file mode 100644 index 8cccc9d..0000000 --- a/src/cose/algorithms.ts +++ /dev/null @@ -1,740 +0,0 @@ -export type IANACOSEAlgorithm = { - Name: string - Value: string - Description: string - Capabilities: string - 'Change Controller': string - Recommended: string - Reference: string - } - export const IANACOSEAlgorithms: Record = { - "0": { - "Name": "Reserved", - "Value": "0", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "No" - }, - "1": { - "Name": "A128GCM", - "Value": "1", - "Description": "AES-GCM mode w/ 128-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "2": { - "Name": "A192GCM", - "Value": "2", - "Description": "AES-GCM mode w/ 192-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "3": { - "Name": "A256GCM", - "Value": "3", - "Description": "AES-GCM mode w/ 256-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "4": { - "Name": "HMAC 256/64", - "Value": "4", - "Description": "HMAC w/ SHA-256 truncated to 64 bits", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "5": { - "Name": "HMAC 256/256", - "Value": "5", - "Description": "HMAC w/ SHA-256", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "6": { - "Name": "HMAC 384/384", - "Value": "6", - "Description": "HMAC w/ SHA-384", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "7": { - "Name": "HMAC 512/512", - "Value": "7", - "Description": "HMAC w/ SHA-512", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "10": { - "Name": "AES-CCM-16-64-128", - "Value": "10", - "Description": "AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "11": { - "Name": "AES-CCM-16-64-256", - "Value": "11", - "Description": "AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "12": { - "Name": "AES-CCM-64-64-128", - "Value": "12", - "Description": "AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "13": { - "Name": "AES-CCM-64-64-256", - "Value": "13", - "Description": "AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "14": { - "Name": "AES-MAC 128/64", - "Value": "14", - "Description": "AES-MAC 128-bit key, 64-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "15": { - "Name": "AES-MAC 256/64", - "Value": "15", - "Description": "AES-MAC 256-bit key, 64-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "24": { - "Name": "ChaCha20/Poly1305", - "Value": "24", - "Description": "ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "25": { - "Name": "AES-MAC 128/128", - "Value": "25", - "Description": "AES-MAC 128-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "26": { - "Name": "AES-MAC 256/128", - "Value": "26", - "Description": "AES-MAC 256-bit key, 128-bit tag", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "30": { - "Name": "AES-CCM-16-128-128", - "Value": "30", - "Description": "AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "31": { - "Name": "AES-CCM-16-128-256", - "Value": "31", - "Description": "AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "32": { - "Name": "AES-CCM-64-128-128", - "Value": "32", - "Description": "AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "33": { - "Name": "AES-CCM-64-128-256", - "Value": "33", - "Description": "AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "34": { - "Name": "IV-GENERATION", - "Value": "34", - "Description": "For doing IV generation for symmetric algorithms.", - "Capabilities": "", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "No" - }, - "less than -65536": { - "Name": "Reserved for Private Use", - "Value": "less than -65536", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "No" - }, - "-65536": { - "Name": "Unassigned", - "Value": "-65536", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "-65535": { - "Name": "RS1", - "Value": "-65535", - "Description": "RSASSA-PKCS1-v1_5 using SHA-1", - "Capabilities": "[kty]", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812][RFC9053", - "Recommended": "Deprecated" - }, - "-65534": { - "Name": "A128CTR", - "Value": "-65534", - "Description": "AES-CTR w/ 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65533": { - "Name": "A192CTR", - "Value": "-65533", - "Description": "AES-CTR w/ 192-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65532": { - "Name": "A256CTR", - "Value": "-65532", - "Description": "AES-CTR w/ 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65531": { - "Name": "A128CBC", - "Value": "-65531", - "Description": "AES-CBC w/ 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65530": { - "Name": "A192CBC", - "Value": "-65530", - "Description": "AES-CBC w/ 192-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65529": { - "Name": "A256CBC", - "Value": "-65529", - "Description": "AES-CBC w/ 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "IETF", - "Reference": "https://datatracker.ietf.org/doc/RFC9459", - "Recommended": "Deprecated" - }, - "-65528 to -261": { - "Name": "Unassigned", - "Value": "-65528 to -261", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "-260": { - "Name": "WalnutDSA", - "Value": "-260", - "Description": "WalnutDSA signature", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9021][RFC9053", - "Recommended": "No" - }, - "-259": { - "Name": "RS512", - "Value": "-259", - "Description": "RSASSA-PKCS1-v1_5 using SHA-512", - "Capabilities": "[kty]", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812][RFC9053", - "Recommended": "No" - }, - "-258": { - "Name": "RS384", - "Value": "-258", - "Description": "RSASSA-PKCS1-v1_5 using SHA-384", - "Capabilities": "[kty]", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812][RFC9053", - "Recommended": "No" - }, - "-257": { - "Name": "RS256", - "Value": "-257", - "Description": "RSASSA-PKCS1-v1_5 using SHA-256", - "Capabilities": "[kty]", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812][RFC9053", - "Recommended": "No" - }, - "-256 to -48": { - "Name": "Unassigned", - "Value": "-256 to -48", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "-47": { - "Name": "ES256K", - "Value": "-47", - "Description": "ECDSA using secp256k1 curve and SHA-256", - "Capabilities": "[kty]", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812][RFC9053", - "Recommended": "No" - }, - "-46": { - "Name": "HSS-LMS", - "Value": "-46", - "Description": "HSS/LMS hash-based digital signature", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8778][RFC9053", - "Recommended": "Yes" - }, - "-45": { - "Name": "SHAKE256", - "Value": "-45", - "Description": "SHAKE-256 512-bit Hash Value", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-44": { - "Name": "SHA-512", - "Value": "-44", - "Description": "SHA-2 512-bit Hash", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-43": { - "Name": "SHA-384", - "Value": "-43", - "Description": "SHA-2 384-bit Hash", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-42": { - "Name": "RSAES-OAEP w/ SHA-512", - "Value": "-42", - "Description": "RSAES-OAEP w/ SHA-512", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-41": { - "Name": "RSAES-OAEP w/ SHA-256", - "Value": "-41", - "Description": "RSAES-OAEP w/ SHA-256", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-40": { - "Name": "RSAES-OAEP w/ RFC 8017 default parameters", - "Value": "-40", - "Description": "RSAES-OAEP w/ SHA-1", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-39": { - "Name": "PS512", - "Value": "-39", - "Description": "RSASSA-PSS w/ SHA-512", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-38": { - "Name": "PS384", - "Value": "-38", - "Description": "RSASSA-PSS w/ SHA-384", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-37": { - "Name": "PS256", - "Value": "-37", - "Description": "RSASSA-PSS w/ SHA-256", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053", - "Recommended": "Yes" - }, - "-36": { - "Name": "ES512", - "Value": "-36", - "Description": "ECDSA w/ SHA-512", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-35": { - "Name": "ES384", - "Value": "-35", - "Description": "ECDSA w/ SHA-384", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-34": { - "Name": "ECDH-SS + A256KW", - "Value": "-34", - "Description": "ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-33": { - "Name": "ECDH-SS + A192KW", - "Value": "-33", - "Description": "ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-32": { - "Name": "ECDH-SS + A128KW", - "Value": "-32", - "Description": "ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-31": { - "Name": "ECDH-ES + A256KW", - "Value": "-31", - "Description": "ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-30": { - "Name": "ECDH-ES + A192KW", - "Value": "-30", - "Description": "ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-29": { - "Name": "ECDH-ES + A128KW", - "Value": "-29", - "Description": "ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-28": { - "Name": "ECDH-SS + HKDF-512", - "Value": "-28", - "Description": "ECDH SS w/ HKDF - generate key directly", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-27": { - "Name": "ECDH-SS + HKDF-256", - "Value": "-27", - "Description": "ECDH SS w/ HKDF - generate key directly", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-26": { - "Name": "ECDH-ES + HKDF-512", - "Value": "-26", - "Description": "ECDH ES w/ HKDF - generate key directly", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-25": { - "Name": "ECDH-ES + HKDF-256", - "Value": "-25", - "Description": "ECDH ES w/ HKDF - generate key directly", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-24 to -19": { - "Name": "Unassigned", - "Value": "-24 to -19", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "-18": { - "Name": "SHAKE128", - "Value": "-18", - "Description": "SHAKE-128 256-bit Hash Value", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-17": { - "Name": "SHA-512/256", - "Value": "-17", - "Description": "SHA-2 512-bit Hash truncated to 256-bits", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-16": { - "Name": "SHA-256", - "Value": "-16", - "Description": "SHA-2 256-bit Hash", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Yes" - }, - "-15": { - "Name": "SHA-256/64", - "Value": "-15", - "Description": "SHA-2 256-bit Hash truncated to 64-bits", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Filter Only" - }, - "-14": { - "Name": "SHA-1", - "Value": "-14", - "Description": "SHA-1 Hash", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9054][RFC9053", - "Recommended": "Filter Only" - }, - "-13": { - "Name": "direct+HKDF-AES-256", - "Value": "-13", - "Description": "Shared secret w/ AES-MAC 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-12": { - "Name": "direct+HKDF-AES-128", - "Value": "-12", - "Description": "Shared secret w/ AES-MAC 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-11": { - "Name": "direct+HKDF-SHA-512", - "Value": "-11", - "Description": "Shared secret w/ HKDF and SHA-512", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-10": { - "Name": "direct+HKDF-SHA-256", - "Value": "-10", - "Description": "Shared secret w/ HKDF and SHA-256", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-9": { - "Name": "Unassigned", - "Value": "-9", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "-8": { - "Name": "EdDSA", - "Value": "-8", - "Description": "EdDSA", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-7": { - "Name": "ES256", - "Value": "-7", - "Description": "ECDSA w/ SHA-256", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-6": { - "Name": "direct", - "Value": "-6", - "Description": "Direct use of CEK", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-5": { - "Name": "A256KW", - "Value": "-5", - "Description": "AES Key Wrap w/ 256-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-4": { - "Name": "A192KW", - "Value": "-4", - "Description": "AES Key Wrap w/ 192-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-3": { - "Name": "A128KW", - "Value": "-3", - "Description": "AES Key Wrap w/ 128-bit key", - "Capabilities": "[kty]", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "-2 to -1": { - "Name": "Unassigned", - "Value": "-2 to -1", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "8-9": { - "Name": "Unassigned", - "Value": "8-9", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "16-23": { - "Name": "Unassigned", - "Value": "16-23", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "27-29": { - "Name": "Unassigned", - "Value": "27-29", - "Description": "", - "Capabilities": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - } -}; \ No newline at end of file diff --git a/src/cose/elliptic-curves.ts b/src/cose/elliptic-curves.ts deleted file mode 100644 index a78e905..0000000 --- a/src/cose/elliptic-curves.ts +++ /dev/null @@ -1,155 +0,0 @@ -export type IANACOSEEllipticCurve = { - Name: string - Value: string - 'Key Type': string - Description: string - 'Change Controller': string - Reference: string - Recommended: string -} -export const IANACOSEEllipticCurves: Record = { - "0": { - "Name": "Reserved", - "Value": "0", - "Key Type": "", - "Description": "", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "No" - }, - "Integer values less than -65536": { - "Name": "Reserved for Private Use", - "Value": "Integer values less than -65536", - "Key Type": "", - "Description": "", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "No" - }, - "-65536 to -1": { - "Name": "Unassigned", - "Value": "-65536 to -1", - "Key Type": "", - "Description": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "EC2-P-256": { - "Name": "P-256", - "Value": "1", - "Key Type": "EC2", - "Description": "NIST P-256 also known as secp256r1", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "EC2-P-384": { - "Name": "P-384", - "Value": "2", - "Key Type": "EC2", - "Description": "NIST P-384 also known as secp384r1", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "EC2-P-521": { - "Name": "P-521", - "Value": "3", - "Key Type": "EC2", - "Description": "NIST P-521 also known as secp521r1", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "OKP-X25519": { - "Name": "X25519", - "Value": "4", - "Key Type": "OKP", - "Description": "X25519 for use w/ ECDH only", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "OKP-X448": { - "Name": "X448", - "Value": "5", - "Key Type": "OKP", - "Description": "X448 for use w/ ECDH only", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "OKP-Ed25519": { - "Name": "Ed25519", - "Value": "6", - "Key Type": "OKP", - "Description": "Ed25519 for use w/ EdDSA only", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "OKP-Ed448": { - "Name": "Ed448", - "Value": "7", - "Key Type": "OKP", - "Description": "Ed448 for use w/ EdDSA only", - "Change Controller": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053", - "Recommended": "Yes" - }, - "EC2-secp256k1": { - "Name": "secp256k1", - "Value": "8", - "Key Type": "EC2", - "Description": "SECG secp256k1 curve", - "Change Controller": "IESG", - "Reference": "https://datatracker.ietf.org/doc/RFC8812", - "Recommended": "No" - }, - "9-255": { - "Name": "Unassigned", - "Value": "9-255", - "Key Type": "", - "Description": "", - "Change Controller": "", - "Reference": "", - "Recommended": "" - }, - "EC2-brainpoolP256r1": { - "Name": "brainpoolP256r1", - "Value": "256", - "Key Type": "EC2", - "Description": "BrainpoolP256r1", - "Change Controller": "[ISO/IEC JTC 1/SC 17/WG 10]", - "Reference": "[ISO/IEC 18013-5:2021, 9.1.5.2]", - "Recommended": "No" - }, - "EC2-brainpoolP320r1": { - "Name": "brainpoolP320r1", - "Value": "257", - "Key Type": "EC2", - "Description": "BrainpoolP320r1", - "Change Controller": "[ISO/IEC JTC 1/SC 17/WG 10]", - "Reference": "[ISO/IEC 18013-5:2021, 9.1.5.2]", - "Recommended": "No" - }, - "EC2-brainpoolP384r1": { - "Name": "brainpoolP384r1", - "Value": "258", - "Key Type": "EC2", - "Description": "BrainpoolP384r1", - "Change Controller": "[ISO/IEC JTC 1/SC 17/WG 10]", - "Reference": "[ISO/IEC 18013-5:2021, 9.1.5.2]", - "Recommended": "No" - }, - "EC2-brainpoolP512r1": { - "Name": "brainpoolP512r1", - "Value": "259", - "Key Type": "EC2", - "Description": "BrainpoolP512r1", - "Change Controller": "[ISO/IEC JTC 1/SC 17/WG 10]", - "Reference": "[ISO/IEC 18013-5:2021, 9.1.5.2]", - "Recommended": "No" - } -}; \ No newline at end of file diff --git a/src/cose/key-common-parameters.ts b/src/cose/key-common-parameters.ts deleted file mode 100644 index 85fcc6e..0000000 --- a/src/cose/key-common-parameters.ts +++ /dev/null @@ -1,74 +0,0 @@ -export type IANACOSEKeyCommonParameter = { - Name: string - Label: string - 'CBOR Type': string - 'Value Registry': string - Description: string - Reference: string -} -export const IANACOSEKeyCommonParameters: Record = { - "0": { - "Name": "Reserved", - "Label": "0", - "CBOR Type": "", - "Value Registry": "", - "Description": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "1": { - "Name": "kty", - "Label": "1", - "CBOR Type": "tstr / int", - "Value Registry": "COSE Key Types", - "Description": "Identification of the key type", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "2": { - "Name": "kid", - "Label": "2", - "CBOR Type": "bstr", - "Value Registry": "", - "Description": "Key identification value - match to kid in message", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "3": { - "Name": "alg", - "Label": "3", - "CBOR Type": "tstr / int", - "Value Registry": "COSE Algorithms", - "Description": "Key usage restriction to this algorithm", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "4": { - "Name": "key_ops", - "Label": "4", - "CBOR Type": "[+ (tstr/int)]", - "Value Registry": "", - "Description": "Restrict set of permissible operations", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "5": { - "Name": "Base IV", - "Label": "5", - "CBOR Type": "bstr", - "Value Registry": "", - "Description": "Base IV to be XORed with Partial IVs", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "less than -65536": { - "Name": "Reserved for Private Use", - "Label": "less than -65536", - "CBOR Type": "", - "Value Registry": "", - "Description": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "-65536 to -1": { - "Name": "used for key parameters specific to a single algorithm\n delegated to the COSE Key Type Parameters registry", - "Label": "-65536 to -1", - "CBOR Type": "", - "Value Registry": "", - "Description": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - } -}; \ No newline at end of file diff --git a/src/cose/key-type-parameters.ts b/src/cose/key-type-parameters.ts deleted file mode 100644 index 5db0c3e..0000000 --- a/src/cose/key-type-parameters.ts +++ /dev/null @@ -1,226 +0,0 @@ -export type IANACOSEKeyTypeParameter = { - 'Key Type': string - 'Name': string - 'Label': string - 'CBOR Type': string - Description: string - Reference: string -} -export const IANACOSEKeyTypeParameters: Record = { - "1-crv": { - "Key Type": "1", - "Name": "crv", - "Label": "-1", - "CBOR Type": "int / tstr", - "Description": "EC identifier -- Taken from the \"COSE Elliptic Curves\" registry", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "1-x": { - "Key Type": "1", - "Name": "x", - "Label": "-2", - "CBOR Type": "bstr", - "Description": "Public Key", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "1-d": { - "Key Type": "1", - "Name": "d", - "Label": "-4", - "CBOR Type": "bstr", - "Description": "Private key", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "2-crv": { - "Key Type": "2", - "Name": "crv", - "Label": "-1", - "CBOR Type": "int / tstr", - "Description": "EC identifier -- Taken from the \"COSE Elliptic Curves\" registry", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "2-x": { - "Key Type": "2", - "Name": "x", - "Label": "-2", - "CBOR Type": "bstr", - "Description": "x-coordinate", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "2-y": { - "Key Type": "2", - "Name": "y", - "Label": "-3", - "CBOR Type": "bstr / bool", - "Description": "y-coordinate", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "2-d": { - "Key Type": "2", - "Name": "d", - "Label": "-4", - "CBOR Type": "bstr", - "Description": "Private key", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "3-n": { - "Key Type": "3", - "Name": "n", - "Label": "-1", - "CBOR Type": "bstr", - "Description": "the RSA modulus n", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-e": { - "Key Type": "3", - "Name": "e", - "Label": "-2", - "CBOR Type": "bstr", - "Description": "the RSA public exponent e", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-d": { - "Key Type": "3", - "Name": "d", - "Label": "-3", - "CBOR Type": "bstr", - "Description": "the RSA private exponent d", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-p": { - "Key Type": "3", - "Name": "p", - "Label": "-4", - "CBOR Type": "bstr", - "Description": "the prime factor p of n", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-q": { - "Key Type": "3", - "Name": "q", - "Label": "-5", - "CBOR Type": "bstr", - "Description": "the prime factor q of n", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-dP": { - "Key Type": "3", - "Name": "dP", - "Label": "-6", - "CBOR Type": "bstr", - "Description": "dP is d mod (p - 1)", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-dQ": { - "Key Type": "3", - "Name": "dQ", - "Label": "-7", - "CBOR Type": "bstr", - "Description": "dQ is d mod (q - 1)", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-qInv": { - "Key Type": "3", - "Name": "qInv", - "Label": "-8", - "CBOR Type": "bstr", - "Description": "qInv is the CRT coefficient q^(-1) mod p", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-other": { - "Key Type": "3", - "Name": "other", - "Label": "-9", - "CBOR Type": "array", - "Description": "other prime infos, an array", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-r_i": { - "Key Type": "3", - "Name": "r_i", - "Label": "-10", - "CBOR Type": "bstr", - "Description": "a prime factor r_i of n, where i >= 3", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-d_i": { - "Key Type": "3", - "Name": "d_i", - "Label": "-11", - "CBOR Type": "bstr", - "Description": "d_i = d mod (r_i - 1)", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "3-t_i": { - "Key Type": "3", - "Name": "t_i", - "Label": "-12", - "CBOR Type": "bstr", - "Description": "the CRT coefficient t_i = (r_1 * r_2 * ... *\n r_(i-1))^(-1) mod r_i", - "Reference": "https://datatracker.ietf.org/doc/RFC8230" - }, - "4-k": { - "Key Type": "4", - "Name": "k", - "Label": "-1", - "CBOR Type": "bstr", - "Description": "Key Value", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "5-pub": { - "Key Type": "5", - "Name": "pub", - "Label": "-1", - "CBOR Type": "bstr", - "Description": "Public key for HSS/LMS hash-based digital signature", - "Reference": "https://datatracker.ietf.org/doc/RFC8778" - }, - "6-N": { - "Key Type": "6", - "Name": "N", - "Label": "-1", - "CBOR Type": "uint", - "Description": "Group and Matrix (NxN) size", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - }, - "6-q": { - "Key Type": "6", - "Name": "q", - "Label": "-2", - "CBOR Type": "uint", - "Description": "Finite field F_q", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - }, - "6-t-values": { - "Key Type": "6", - "Name": "t-values", - "Label": "-3", - "CBOR Type": "array (of uint)", - "Description": "List of T-values, entries in F_q", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - }, - "6-matrix 1": { - "Key Type": "6", - "Name": "matrix 1", - "Label": "-4", - "CBOR Type": "array (of array of uint)", - "Description": "NxN Matrix of entries in F_q in column-major form", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - }, - "6-permutation 1": { - "Key Type": "6", - "Name": "permutation 1", - "Label": "-5", - "CBOR Type": "array (of uint)", - "Description": "Permutation associated with matrix 1", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - }, - "6-matrix 2": { - "Key Type": "6", - "Name": "matrix 2", - "Label": "-6", - "CBOR Type": "array (of array of uint)", - "Description": "NxN Matrix of entries in F_q in column-major form", - "Reference": "https://datatracker.ietf.org/doc/RFC9021" - } -}; \ No newline at end of file diff --git a/src/cose/key-type.ts b/src/cose/key-type.ts deleted file mode 100644 index 0d3041b..0000000 --- a/src/cose/key-type.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type IANACOSEKeyType = { - Name: string - Value: string - Description: string - Capabilities: string - Reference: string -} -export const IANACOSEKeyTypes: Record = { - "0": { - "Name": "Reserved", - "Value": "0", - "Description": "This value is reserved", - "Capabilities": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "1": { - "Name": "OKP", - "Value": "1", - "Description": "Octet Key Pair", - "Capabilities": "[kty(1), crv]", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "2": { - "Name": "EC2", - "Value": "2", - "Description": "Elliptic Curve Keys w/ x- and y-coordinate pair", - "Capabilities": "[kty(2), crv]", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "3": { - "Name": "RSA", - "Value": "3", - "Description": "RSA Key", - "Capabilities": "[kty(3)]", - "Reference": "https://datatracker.ietf.org/doc/RFC8230][RFC9053" - }, - "4": { - "Name": "Symmetric", - "Value": "4", - "Description": "Symmetric Keys", - "Capabilities": "[kty(4)]", - "Reference": "https://datatracker.ietf.org/doc/RFC9053" - }, - "5": { - "Name": "HSS-LMS", - "Value": "5", - "Description": "Public key for HSS/LMS hash-based digital signature", - "Capabilities": "[kty(5), hash algorithm]", - "Reference": "https://datatracker.ietf.org/doc/RFC8778][RFC9053" - }, - "6": { - "Name": "WalnutDSA", - "Value": "6", - "Description": "WalnutDSA public key", - "Capabilities": "[kty(6)]", - "Reference": "https://datatracker.ietf.org/doc/RFC9021][RFC9053" - } -}; \ No newline at end of file diff --git a/src/cose/key/convertCoseKeyToJsonWebKey.ts b/src/cose/key/convertCoseKeyToJsonWebKey.ts index e251d99..58c9ab0 100644 --- a/src/cose/key/convertCoseKeyToJsonWebKey.ts +++ b/src/cose/key/convertCoseKeyToJsonWebKey.ts @@ -1,37 +1,33 @@ import { base64url, calculateJwkThumbprint } from "jose"; import { CoseKey } from "."; -import { IANACOSEEllipticCurves } from '../elliptic-curves'; - -const curves = Object.values(IANACOSEEllipticCurves) - import { formatJwk } from "./formatJwk"; -import { iana } from "../../iana"; + import { EC2, Key, KeyTypes } from "../Params"; +import * as iana2 from '../../iana/assignments/cose' + +import { labels_to_algorithms } from "../../iana/requested/cose"; + export const convertCoseKeyToJsonWebKey = async (coseKey: CoseKey): Promise => { + // todo refactor... const kty = coseKey.get(Key.Kty) as number // kty EC2 if (![KeyTypes.EC2].includes(kty)) { throw new Error('This library requires does not support the given key type') } const kid = coseKey.get(Key.Kid) - const alg = coseKey.get(Key.Alg) + const algLabel = coseKey.get(Key.Alg) const crv = coseKey.get(EC2.Crv) - const foundAlgorithm = iana["COSE Algorithms"].getByValue(alg as number) - if (!foundAlgorithm) { - throw new Error('This library requires keys to use fully specified algorithms') - } - const foundCurve = curves.find((param) => { - return param.Value === `${crv}` - }) - if (!foundCurve) { + const algName = labels_to_algorithms.get(algLabel as number) + const crv2 = iana2.label_to_curve.get(crv as number) + if (!crv2) { throw new Error('This library requires does not support the given curve') } const jwk = { kty: 'EC', - alg: foundAlgorithm.Name, - crv: foundCurve.Name + alg: algName, + crv: crv2 } as any const x = coseKey.get(EC2.X) as any const y = coseKey.get(EC2.Y) as any diff --git a/src/cose/key/convertJsonWebKeyToCoseKey.ts b/src/cose/key/convertJsonWebKeyToCoseKey.ts index b3fa13d..425f93e 100644 --- a/src/cose/key/convertJsonWebKeyToCoseKey.ts +++ b/src/cose/key/convertJsonWebKeyToCoseKey.ts @@ -1,137 +1,54 @@ import { base64url } from 'jose' -import { toArrayBuffer } from '../../cbor' -import { IANACOSEKeyCommonParameters } from '../key-common-parameters'; -import { IANACOSEKeyTypeParameters, IANACOSEKeyTypeParameter } from '../key-type-parameters'; -import { IANACOSEKeyTypes } from '../key-type'; -import { IANACOSEEllipticCurves } from '../elliptic-curves'; -import { PublicKeyJwk, PrivateKeyJwk } from '../sign1'; -import { iana } from '../../iana'; +import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' -const commonParams = Object.values(IANACOSEKeyCommonParameters) -const keyTypeParams = Object.values(IANACOSEKeyTypeParameters) -const keyTypes = Object.values(IANACOSEKeyTypes) -const curves = Object.values(IANACOSEEllipticCurves) +import { algorithms_to_labels } from '../../iana/requested/cose' - -const keyTypeParamsByKty = { - 'OKP': keyTypeParams.filter((p) => p['Key Type'] === '1'), - 'EC2': keyTypeParams.filter((p) => p['Key Type'] === '2') -} as Record<'OKP' | 'EC2', IANACOSEKeyTypeParameter[]> - -const getKeyTypeSpecificLabel = (keyType: 'EC2' | 'OKP', keyTypeParam: string) => { - let label: string | number = keyTypeParam; - let foundKeyTypeParam = keyTypeParamsByKty[keyType].find((param) => { - return param.Name === keyTypeParam - }) - if (!foundKeyTypeParam) { - foundKeyTypeParam = keyTypeParamsByKty[keyType].find((param) => { - return param.Name === keyTypeParam - }) - } - if (foundKeyTypeParam) { - label = parseInt(foundKeyTypeParam.Label, 10) - } else { - throw new Error(`Unable to find a label for this param (${keyTypeParam}) for the given key type ${keyType}`) - } - return label -} - -export const convertJsonWebKeyToCoseKey = async (jwk: PublicKeyJwk | PrivateKeyJwk): Promise => { - - const { kty } = jwk - let coseKty = `${kty}` as 'OKP' | 'EC' | 'EC2'; // evidence of terrible design. - if (coseKty === 'EC') { - coseKty = 'EC2' - } - if (!keyTypeParamsByKty[coseKty]) { - throw new Error('Unsupported key type') - } +export const convertJsonWebKeyToCoseKey = async (jwk: any): Promise => { const coseKey = new Map(); - + const { kty } = jwk for (const [key, value] of Object.entries(jwk)) { - const foundCommonParam = commonParams.find((param) => { - return param.Name === key - }) - let label: string | number = key - if (foundCommonParam) { - label = parseInt(foundCommonParam.Label, 10) - } - switch (key) { - case 'kty': { - const foundKeyType = keyTypes.find((param) => { - return param.Name === coseKty - }) - if (foundKeyType) { - coseKey.set(label, parseInt(foundKeyType.Value, 10)) - } else { - throw new Error('Unsupported key type: ' + value) - } - break - } - case 'kid': { - if (foundCommonParam) { - coseKey.set(label, value) - } else { - throw new Error('Expected common parameter was not found in iana registry.') - } - break - } - case 'alg': { - if (foundCommonParam) { - const foundAlgorithm = iana['COSE Algorithms'].getByName(value) - if (foundAlgorithm) { - coseKey.set(label, parseInt(foundAlgorithm.Value, 10)) - } else { - throw new Error('Expected algorithm was not found in iana registry.') + switch (kty) { + case 'EC': { + switch (key) { + case 'kty': { + coseKey.set(ec2_params_to_labels.get(key), key_type_to_label.get(value as string)) + break; + } + case 'crv': { + coseKey.set(ec2_params_to_labels.get(key), curve_to_label.get(value as string)) + break; + } + case 'alg': { + const maybeUnknown = algorithms_to_labels.get(value as string) || value as string + coseKey.set(ec2_params_to_labels.get(key), maybeUnknown) + break; + } + case 'kid': { + coseKey.set(ec2_params_to_labels.get(key), value as string) + break; + } + case 'x': + case 'y': + case 'd': { + // todo check lengths based on curves + coseKey.set(ec2_params_to_labels.get(key), Buffer.from(base64url.decode(value as string))) + break; + } + default: { + coseKey.set(key, value) } - } else { - throw new Error('Expected common parameter was not found in iana registry.') - } - break - } - case 'crv': { - label = getKeyTypeSpecificLabel(coseKty, 'crv') - const foundCurve = curves.find((param) => { - return param.Name === value - }) - if (foundCurve) { - coseKey.set(label, parseInt(foundCurve.Value, 10)) - } else { - throw new Error('Expected curve was not found in iana registry.') } - break - } - case 'x': - case 'y': - case 'd': { - label = getKeyTypeSpecificLabel(coseKty, key) - coseKey.set(label, toArrayBuffer(base64url.decode(value as string))) - break - } - case 'x5c': { - const items = (value as string[] || []).map((item: string) => { - return toArrayBuffer(base64url.decode(item as string)) - }) - coseKey.set(label, items) - break - } - case 'x5t#S256': { - coseKey.set(label, toArrayBuffer(base64url.decode(value as string))) - break + break; } default: { - // by default we assume a text label - coseKey.set(label, value) + coseKey.set(key, value) } } } - - - - // TODO: Length checks on x, y, d return coseKey as T } +// coseKey.set(label, Buffer.from(base64url.decode(value as string))) diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts index 69f155b..ac50464 100644 --- a/src/cose/key/generate.ts +++ b/src/cose/key/generate.ts @@ -2,9 +2,6 @@ import { generateKeyPair, exportJWK, calculateJwkThumbprint } from "jose" -import { IANACOSEAlgorithms } from "../algorithms" - - import { CoseKey } from '.' export type CoseKeyAgreementAlgorithms = 'ECDH-ES+A128KW' @@ -14,28 +11,14 @@ export type ContentTypeOfCoseKey = 'application/cose-key' export type PrivateKeyContentType = ContentTypeOfCoseKey | ContentTypeOfJsonWebKey import { convertJsonWebKeyToCoseKey } from './convertJsonWebKeyToCoseKey' - import { thumbprint } from "./thumbprint" - import { formatJwk } from './formatJwk' - -import { iana } from '../../iana' import { Key } from "../Params" +import { less_specified } from "../../iana/requested/cose" export const generate = async (alg: CoseSignatureAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise => { - let knownAlgorithm = Object.values(IANACOSEAlgorithms).find(( - entry - ) => { - return entry.Name === alg - }) - if (!knownAlgorithm) { - knownAlgorithm = iana["COSE Algorithms"].getByName(alg) - } - if (!knownAlgorithm) { - throw new Error('Algorithm is not supported.') - } const cryptoKeyPair = await generateKeyPair( - iana["COSE Algorithms"]["less-specified"](knownAlgorithm.Name), + less_specified[alg], { extractable: true } ); const privateKeyJwk = await exportJWK(cryptoKeyPair.privateKey) diff --git a/src/cose/sign1/verifier.ts b/src/cose/sign1/verifier.ts index 7bfc309..df09109 100644 --- a/src/cose/sign1/verifier.ts +++ b/src/cose/sign1/verifier.ts @@ -5,14 +5,16 @@ import { RequestCoseSign1Verifier, RequestCoseSign1Verify } from './types' import { DecodedToBeSigned, ProtectedHeaderMap } from './types' import rawVerifier from '../../crypto/verifier' -import { iana } from '../../iana' + import { Protected } from '../Params' +import { algorithms_to_labels } from '../../iana/requested/cose' + const verifier = ({ resolver }: RequestCoseSign1Verifier) => { return { verify: async ({ coseSign1, externalAAD }: RequestCoseSign1Verify): Promise => { const publicKeyJwk = await resolver.resolve(coseSign1) - const algInPublicKey = parseInt(`${iana['COSE Algorithms'].getByName(`${publicKeyJwk.alg}`)?.Value}`, 10) + const algInPublicKey = algorithms_to_labels.get(publicKeyJwk.alg as string) const ecdsa = rawVerifier({ publicKeyJwk }) const obj = await decodeFirst(coseSign1); const signatureStructure = obj.value; diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts new file mode 100644 index 0000000..5276c16 --- /dev/null +++ b/src/iana/assignments/cose.ts @@ -0,0 +1,1286 @@ +// DO NOT edit this file, it is generated automatically +export const reserved_key_parameter = { + 'Name': 'Reserved', + 'Label': 0, + 'CBOR Type': '', + 'Value Registry': '', + 'Description': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const kty_key_parameter = { + 'Name': 'kty', + 'Label': 1, + 'CBOR Type': 'tstr / int', + 'Value Registry': 'COSE Key Types', + 'Description': 'Identification of the key type', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const kid_key_parameter = { + 'Name': 'kid', + 'Label': 2, + 'CBOR Type': 'bstr', + 'Value Registry': '', + 'Description': 'Key identification value - match to kid in message', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const alg_key_parameter = { + 'Name': 'alg', + 'Label': 3, + 'CBOR Type': 'tstr / int', + 'Value Registry': 'COSE Algorithms', + 'Description': 'Key usage restriction to this algorithm', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const key_ops_key_parameter = { + 'Name': 'key_ops', + 'Label': 4, + 'CBOR Type': '[+ (tstr/int)]', + 'Value Registry': '', + 'Description': 'Restrict set of permissible operations', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const base_iv_key_parameter = { + 'Name': 'Base IV', + 'Label': 5, + 'CBOR Type': 'bstr', + 'Value Registry': '', + 'Description': 'Base IV to be XORed with Partial IVs', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export enum cose_key { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, +} + +export const reserved_key_type = { + 'Name': 'Reserved', + 'Value': 0, + 'Description': 'This value is reserved', + 'Capabilities': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053' +} +export const okp_key_type = { + 'Name': 'OKP', + 'Value': 1, + 'Description': 'Octet Key Pair', + 'Capabilities': '[kty(1), crv]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053' +} +export const ec2_key_type = { + 'Name': 'EC2', + 'Value': 2, + 'Description': 'Elliptic Curve Keys w/ x- and y-coordinate pair', + 'Capabilities': '[kty(2), crv]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053' +} +export const rsa_key_type = { + 'Name': 'RSA', + 'Value': 3, + 'Description': 'RSA Key', + 'Capabilities': '[kty(3)]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc8230 https://datatracker.ietf.org/doc/rfc9053' +} +export const symmetric_key_type = { + 'Name': 'Symmetric', + 'Value': 4, + 'Description': 'Symmetric Keys', + 'Capabilities': '[kty(4)]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053' +} +export const hss_lms_key_type = { + 'Name': 'HSS-LMS', + 'Value': 5, + 'Description': 'Public key for HSS/LMS hash-based digital signature', + 'Capabilities': '[kty(5), hash algorithm]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc8778 https://datatracker.ietf.org/doc/rfc9053' +} +export const walnutdsa_key_type = { + 'Name': 'WalnutDSA', + 'Value': 6, + 'Description': 'WalnutDSA public key', + 'Capabilities': '[kty(6)]', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9021 https://datatracker.ietf.org/doc/rfc9053' +} +export enum cose_key_type { + okp = 1, + ec2 = 2, + rsa = 3, + symmetric = 4, + hss_lms = 5, + walnutdsa = 6, +} + + +export const reserved_curve = { + 'Name': 'Reserved', + 'Value': 0, + 'Key Type': '', + 'Description': '', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'No' +} +export const p_256_curve = { + 'Name': 'P-256', + 'Value': 1, + 'Key Type': 'EC2', + 'Description': 'NIST P-256 also known as secp256r1', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const p_384_curve = { + 'Name': 'P-384', + 'Value': 2, + 'Key Type': 'EC2', + 'Description': 'NIST P-384 also known as secp384r1', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const p_521_curve = { + 'Name': 'P-521', + 'Value': 3, + 'Key Type': 'EC2', + 'Description': 'NIST P-521 also known as secp521r1', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const x25519_curve = { + 'Name': 'X25519', + 'Value': 4, + 'Key Type': 'OKP', + 'Description': 'X25519 for use w/ ECDH only', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const x448_curve = { + 'Name': 'X448', + 'Value': 5, + 'Key Type': 'OKP', + 'Description': 'X448 for use w/ ECDH only', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const ed25519_curve = { + 'Name': 'Ed25519', + 'Value': 6, + 'Key Type': 'OKP', + 'Description': 'Ed25519 for use w/ EdDSA only', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const ed448_curve = { + 'Name': 'Ed448', + 'Value': 7, + 'Key Type': 'OKP', + 'Description': 'Ed448 for use w/ EdDSA only', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/rfc9053', + 'Recommended': 'Yes' +} +export const secp256k1_curve = { + 'Name': 'secp256k1', + 'Value': 8, + 'Key Type': 'EC2', + 'Description': 'SECG secp256k1 curve', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/rfc8812', + 'Recommended': 'No' +} +export const brainpoolp256r1_curve = { + 'Name': 'brainpoolP256r1', + 'Value': 256, + 'Key Type': 'EC2', + 'Description': 'BrainpoolP256r1', + 'Change Controller': '[ISO/IEC JTC 1/SC 17/WG 10]', + 'Reference': '[ISO/IEC 18013-5:2021, 9.1.5.2]', + 'Recommended': 'No' +} +export const brainpoolp320r1_curve = { + 'Name': 'brainpoolP320r1', + 'Value': 257, + 'Key Type': 'EC2', + 'Description': 'BrainpoolP320r1', + 'Change Controller': '[ISO/IEC JTC 1/SC 17/WG 10]', + 'Reference': '[ISO/IEC 18013-5:2021, 9.1.5.2]', + 'Recommended': 'No' +} +export const brainpoolp384r1_curve = { + 'Name': 'brainpoolP384r1', + 'Value': 258, + 'Key Type': 'EC2', + 'Description': 'BrainpoolP384r1', + 'Change Controller': '[ISO/IEC JTC 1/SC 17/WG 10]', + 'Reference': '[ISO/IEC 18013-5:2021, 9.1.5.2]', + 'Recommended': 'No' +} +export const brainpoolp512r1_curve = { + 'Name': 'brainpoolP512r1', + 'Value': 259, + 'Key Type': 'EC2', + 'Description': 'BrainpoolP512r1', + 'Change Controller': '[ISO/IEC JTC 1/SC 17/WG 10]', + 'Reference': '[ISO/IEC 18013-5:2021, 9.1.5.2]', + 'Recommended': 'No' +} +export enum ec2_curves { + p_256 = 1, + p_384 = 2, + p_521 = 3, + secp256k1 = 8, + brainpoolp256r1 = 256, + brainpoolp320r1 = 257, + brainpoolp384r1 = 258, + brainpoolp512r1 = 259, +} +export enum okp_curves { + x25519 = 4, + x448 = 5, + ed25519 = 6, + ed448 = 7, +} + + +export const reserved_for_private_use_algorithm = { + 'Name': 'Reserved for Private Use', + 'Value': NaN, + 'Description': '', + 'Capabilities': '', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'No' +} +export const rs1_algorithm = { + 'Name': 'RS1', + 'Value': -65535, + 'Description': 'RSASSA-PKCS1-v1_5 using SHA-1', + 'Capabilities': '[kty]', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8812][RFC9053', + 'Recommended': 'Deprecated' +} +export const a128ctr_algorithm = { + 'Name': 'A128CTR', + 'Value': -65534, + 'Description': 'AES-CTR w/ 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const a192ctr_algorithm = { + 'Name': 'A192CTR', + 'Value': -65533, + 'Description': 'AES-CTR w/ 192-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const a256ctr_algorithm = { + 'Name': 'A256CTR', + 'Value': -65532, + 'Description': 'AES-CTR w/ 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const a128cbc_algorithm = { + 'Name': 'A128CBC', + 'Value': -65531, + 'Description': 'AES-CBC w/ 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const a192cbc_algorithm = { + 'Name': 'A192CBC', + 'Value': -65530, + 'Description': 'AES-CBC w/ 192-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const a256cbc_algorithm = { + 'Name': 'A256CBC', + 'Value': -65529, + 'Description': 'AES-CBC w/ 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': 'IETF', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9459', + 'Recommended': 'Deprecated' +} +export const walnutdsa_algorithm = { + 'Name': 'WalnutDSA', + 'Value': -260, + 'Description': 'WalnutDSA signature', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021][RFC9053', + 'Recommended': 'No' +} +export const rs512_algorithm = { + 'Name': 'RS512', + 'Value': -259, + 'Description': 'RSASSA-PKCS1-v1_5 using SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8812][RFC9053', + 'Recommended': 'No' +} +export const rs384_algorithm = { + 'Name': 'RS384', + 'Value': -258, + 'Description': 'RSASSA-PKCS1-v1_5 using SHA-384', + 'Capabilities': '[kty]', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8812][RFC9053', + 'Recommended': 'No' +} +export const rs256_algorithm = { + 'Name': 'RS256', + 'Value': -257, + 'Description': 'RSASSA-PKCS1-v1_5 using SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8812][RFC9053', + 'Recommended': 'No' +} +export const es256k_algorithm = { + 'Name': 'ES256K', + 'Value': -47, + 'Description': 'ECDSA using secp256k1 curve and SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': 'IESG', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8812][RFC9053', + 'Recommended': 'No' +} +export const hss_lms_algorithm = { + 'Name': 'HSS-LMS', + 'Value': -46, + 'Description': 'HSS/LMS hash-based digital signature', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8778][RFC9053', + 'Recommended': 'Yes' +} +export const shake256_algorithm = { + 'Name': 'SHAKE256', + 'Value': -45, + 'Description': 'SHAKE-256 512-bit Hash Value', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const sha_512_algorithm = { + 'Name': 'SHA-512', + 'Value': -44, + 'Description': 'SHA-2 512-bit Hash', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const sha_384_algorithm = { + 'Name': 'SHA-384', + 'Value': -43, + 'Description': 'SHA-2 384-bit Hash', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const rsaes_oaep_w__sha_512_algorithm = { + 'Name': 'RSAES-OAEP w/ SHA-512', + 'Value': -42, + 'Description': 'RSAES-OAEP w/ SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const rsaes_oaep_w__sha_256_algorithm = { + 'Name': 'RSAES-OAEP w/ SHA-256', + 'Value': -41, + 'Description': 'RSAES-OAEP w/ SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const rsaes_oaep_w__rfc_8017_default_parameters_algorithm = { + 'Name': 'RSAES-OAEP w/ RFC 8017 default parameters', + 'Value': -40, + 'Description': 'RSAES-OAEP w/ SHA-1', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const ps512_algorithm = { + 'Name': 'PS512', + 'Value': -39, + 'Description': 'RSASSA-PSS w/ SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const ps384_algorithm = { + 'Name': 'PS384', + 'Value': -38, + 'Description': 'RSASSA-PSS w/ SHA-384', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const ps256_algorithm = { + 'Name': 'PS256', + 'Value': -37, + 'Description': 'RSASSA-PSS w/ SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230][RFC9053', + 'Recommended': 'Yes' +} +export const es512_algorithm = { + 'Name': 'ES512', + 'Value': -36, + 'Description': 'ECDSA w/ SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const es384_algorithm = { + 'Name': 'ES384', + 'Value': -35, + 'Description': 'ECDSA w/ SHA-384', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_ss___a256kw_algorithm = { + 'Name': 'ECDH-SS + A256KW', + 'Value': -34, + 'Description': 'ECDH SS w/ Concat KDF and AES Key Wrap w/ 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_ss___a192kw_algorithm = { + 'Name': 'ECDH-SS + A192KW', + 'Value': -33, + 'Description': 'ECDH SS w/ Concat KDF and AES Key Wrap w/ 192-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_ss___a128kw_algorithm = { + 'Name': 'ECDH-SS + A128KW', + 'Value': -32, + 'Description': 'ECDH SS w/ Concat KDF and AES Key Wrap w/ 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_es___a256kw_algorithm = { + 'Name': 'ECDH-ES + A256KW', + 'Value': -31, + 'Description': 'ECDH ES w/ Concat KDF and AES Key Wrap w/ 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_es___a192kw_algorithm = { + 'Name': 'ECDH-ES + A192KW', + 'Value': -30, + 'Description': 'ECDH ES w/ Concat KDF and AES Key Wrap w/ 192-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_es___a128kw_algorithm = { + 'Name': 'ECDH-ES + A128KW', + 'Value': -29, + 'Description': 'ECDH ES w/ Concat KDF and AES Key Wrap w/ 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_ss___hkdf_512_algorithm = { + 'Name': 'ECDH-SS + HKDF-512', + 'Value': -28, + 'Description': 'ECDH SS w/ HKDF - generate key directly', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_ss___hkdf_256_algorithm = { + 'Name': 'ECDH-SS + HKDF-256', + 'Value': -27, + 'Description': 'ECDH SS w/ HKDF - generate key directly', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_es___hkdf_512_algorithm = { + 'Name': 'ECDH-ES + HKDF-512', + 'Value': -26, + 'Description': 'ECDH ES w/ HKDF - generate key directly', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const ecdh_es___hkdf_256_algorithm = { + 'Name': 'ECDH-ES + HKDF-256', + 'Value': -25, + 'Description': 'ECDH ES w/ HKDF - generate key directly', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const shake128_algorithm = { + 'Name': 'SHAKE128', + 'Value': -18, + 'Description': 'SHAKE-128 256-bit Hash Value', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const sha_512_256_algorithm = { + 'Name': 'SHA-512/256', + 'Value': -17, + 'Description': 'SHA-2 512-bit Hash truncated to 256-bits', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const sha_256_algorithm = { + 'Name': 'SHA-256', + 'Value': -16, + 'Description': 'SHA-2 256-bit Hash', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Yes' +} +export const sha_256_64_algorithm = { + 'Name': 'SHA-256/64', + 'Value': -15, + 'Description': 'SHA-2 256-bit Hash truncated to 64-bits', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Filter Only' +} +export const sha_1_algorithm = { + 'Name': 'SHA-1', + 'Value': -14, + 'Description': 'SHA-1 Hash', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9054][RFC9053', + 'Recommended': 'Filter Only' +} +export const direct_hkdf_aes_256_algorithm = { + 'Name': 'direct+HKDF-AES-256', + 'Value': -13, + 'Description': 'Shared secret w/ AES-MAC 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const direct_hkdf_aes_128_algorithm = { + 'Name': 'direct+HKDF-AES-128', + 'Value': -12, + 'Description': 'Shared secret w/ AES-MAC 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const direct_hkdf_sha_512_algorithm = { + 'Name': 'direct+HKDF-SHA-512', + 'Value': -11, + 'Description': 'Shared secret w/ HKDF and SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const direct_hkdf_sha_256_algorithm = { + 'Name': 'direct+HKDF-SHA-256', + 'Value': -10, + 'Description': 'Shared secret w/ HKDF and SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const eddsa_algorithm = { + 'Name': 'EdDSA', + 'Value': -8, + 'Description': 'EdDSA', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const es256_algorithm = { + 'Name': 'ES256', + 'Value': -7, + 'Description': 'ECDSA w/ SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const direct_algorithm = { + 'Name': 'direct', + 'Value': -6, + 'Description': 'Direct use of CEK', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const a256kw_algorithm = { + 'Name': 'A256KW', + 'Value': -5, + 'Description': 'AES Key Wrap w/ 256-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const a192kw_algorithm = { + 'Name': 'A192KW', + 'Value': -4, + 'Description': 'AES Key Wrap w/ 192-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const a128kw_algorithm = { + 'Name': 'A128KW', + 'Value': -3, + 'Description': 'AES Key Wrap w/ 128-bit key', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const reserved_algorithm = { + 'Name': 'Reserved', + 'Value': 0, + 'Description': '', + 'Capabilities': '', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'No' +} +export const a128gcm_algorithm = { + 'Name': 'A128GCM', + 'Value': 1, + 'Description': 'AES-GCM mode w/ 128-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const a192gcm_algorithm = { + 'Name': 'A192GCM', + 'Value': 2, + 'Description': 'AES-GCM mode w/ 192-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const a256gcm_algorithm = { + 'Name': 'A256GCM', + 'Value': 3, + 'Description': 'AES-GCM mode w/ 256-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const hmac_256_64_algorithm = { + 'Name': 'HMAC 256/64', + 'Value': 4, + 'Description': 'HMAC w/ SHA-256 truncated to 64 bits', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const hmac_256_256_algorithm = { + 'Name': 'HMAC 256/256', + 'Value': 5, + 'Description': 'HMAC w/ SHA-256', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const hmac_384_384_algorithm = { + 'Name': 'HMAC 384/384', + 'Value': 6, + 'Description': 'HMAC w/ SHA-384', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const hmac_512_512_algorithm = { + 'Name': 'HMAC 512/512', + 'Value': 7, + 'Description': 'HMAC w/ SHA-512', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_16_64_128_algorithm = { + 'Name': 'AES-CCM-16-64-128', + 'Value': 10, + 'Description': 'AES-CCM mode 128-bit key, 64-bit tag, 13-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_16_64_256_algorithm = { + 'Name': 'AES-CCM-16-64-256', + 'Value': 11, + 'Description': 'AES-CCM mode 256-bit key, 64-bit tag, 13-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_64_64_128_algorithm = { + 'Name': 'AES-CCM-64-64-128', + 'Value': 12, + 'Description': 'AES-CCM mode 128-bit key, 64-bit tag, 7-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_64_64_256_algorithm = { + 'Name': 'AES-CCM-64-64-256', + 'Value': 13, + 'Description': 'AES-CCM mode 256-bit key, 64-bit tag, 7-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_mac_128_64_algorithm = { + 'Name': 'AES-MAC 128/64', + 'Value': 14, + 'Description': 'AES-MAC 128-bit key, 64-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_mac_256_64_algorithm = { + 'Name': 'AES-MAC 256/64', + 'Value': 15, + 'Description': 'AES-MAC 256-bit key, 64-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const chacha20_poly1305_algorithm = { + 'Name': 'ChaCha20/Poly1305', + 'Value': 24, + 'Description': 'ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_mac_128_128_algorithm = { + 'Name': 'AES-MAC 128/128', + 'Value': 25, + 'Description': 'AES-MAC 128-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_mac_256_128_algorithm = { + 'Name': 'AES-MAC 256/128', + 'Value': 26, + 'Description': 'AES-MAC 256-bit key, 128-bit tag', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_16_128_128_algorithm = { + 'Name': 'AES-CCM-16-128-128', + 'Value': 30, + 'Description': 'AES-CCM mode 128-bit key, 128-bit tag, 13-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_16_128_256_algorithm = { + 'Name': 'AES-CCM-16-128-256', + 'Value': 31, + 'Description': 'AES-CCM mode 256-bit key, 128-bit tag, 13-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_64_128_128_algorithm = { + 'Name': 'AES-CCM-64-128-128', + 'Value': 32, + 'Description': 'AES-CCM mode 128-bit key, 128-bit tag, 7-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const aes_ccm_64_128_256_algorithm = { + 'Name': 'AES-CCM-64-128-256', + 'Value': 33, + 'Description': 'AES-CCM mode 256-bit key, 128-bit tag, 7-byte nonce', + 'Capabilities': '[kty]', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'Yes' +} +export const iv_generation_algorithm = { + 'Name': 'IV-GENERATION', + 'Value': 34, + 'Description': 'For doing IV generation for symmetric algorithms.', + 'Capabilities': '', + 'Change Controller': '', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', + 'Recommended': 'No' +} + +export const okp_crv_parameter = { + 'Key Type': 1, + 'Label': -1, + 'Name': 'crv', + 'CBOR Type': 'int / tstr', + 'Description': 'EC identifier -- Taken from the "COSE Elliptic Curves" registry', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const okp_x_parameter = { + 'Key Type': 1, + 'Label': -2, + 'Name': 'x', + 'CBOR Type': 'bstr', + 'Description': 'Public Key', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const okp_d_parameter = { + 'Key Type': 1, + 'Label': -4, + 'Name': 'd', + 'CBOR Type': 'bstr', + 'Description': 'Private key', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const ec2_crv_parameter = { + 'Key Type': 2, + 'Label': -1, + 'Name': 'crv', + 'CBOR Type': 'int / tstr', + 'Description': 'EC identifier -- Taken from the "COSE Elliptic Curves" registry', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const ec2_x_parameter = { + 'Key Type': 2, + 'Label': -2, + 'Name': 'x', + 'CBOR Type': 'bstr', + 'Description': 'x-coordinate', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const ec2_y_parameter = { + 'Key Type': 2, + 'Label': -3, + 'Name': 'y', + 'CBOR Type': 'bstr / bool', + 'Description': 'y-coordinate', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const ec2_d_parameter = { + 'Key Type': 2, + 'Label': -4, + 'Name': 'd', + 'CBOR Type': 'bstr', + 'Description': 'Private key', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const rsa_n_parameter = { + 'Key Type': 3, + 'Label': -1, + 'Name': 'n', + 'CBOR Type': 'bstr', + 'Description': 'the RSA modulus n', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_e_parameter = { + 'Key Type': 3, + 'Label': -2, + 'Name': 'e', + 'CBOR Type': 'bstr', + 'Description': 'the RSA public exponent e', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_d_parameter = { + 'Key Type': 3, + 'Label': -3, + 'Name': 'd', + 'CBOR Type': 'bstr', + 'Description': 'the RSA private exponent d', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_p_parameter = { + 'Key Type': 3, + 'Label': -4, + 'Name': 'p', + 'CBOR Type': 'bstr', + 'Description': 'the prime factor p of n', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_q_parameter = { + 'Key Type': 3, + 'Label': -5, + 'Name': 'q', + 'CBOR Type': 'bstr', + 'Description': 'the prime factor q of n', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_dp_parameter = { + 'Key Type': 3, + 'Label': -6, + 'Name': 'dP', + 'CBOR Type': 'bstr', + 'Description': 'dP is d mod (p - 1)', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_dq_parameter = { + 'Key Type': 3, + 'Label': -7, + 'Name': 'dQ', + 'CBOR Type': 'bstr', + 'Description': 'dQ is d mod (q - 1)', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_qinv_parameter = { + 'Key Type': 3, + 'Label': -8, + 'Name': 'qInv', + 'CBOR Type': 'bstr', + 'Description': 'qInv is the CRT coefficient q^(-1) mod p', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_other_parameter = { + 'Key Type': 3, + 'Label': -9, + 'Name': 'other', + 'CBOR Type': 'array', + 'Description': 'other prime infos, an array', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_r_i_parameter = { + 'Key Type': 3, + 'Label': -10, + 'Name': 'r_i', + 'CBOR Type': 'bstr', + 'Description': 'a prime factor r_i of n, where i >= 3', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_d_i_parameter = { + 'Key Type': 3, + 'Label': -11, + 'Name': 'd_i', + 'CBOR Type': 'bstr', + 'Description': 'd_i = d mod (r_i - 1)', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const rsa_t_i_parameter = { + 'Key Type': 3, + 'Label': -12, + 'Name': 't_i', + 'CBOR Type': 'bstr', + 'Description': 'the CRT coefficient t_i = (r_1 * r_2 * ... * r_(i-1))^(-1) mod r_i', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8230' +} +export const symmetric_k_parameter = { + 'Key Type': 4, + 'Label': -1, + 'Name': 'k', + 'CBOR Type': 'bstr', + 'Description': 'Key Value', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9053' +} +export const hss_lms_pub_parameter = { + 'Key Type': 5, + 'Label': -1, + 'Name': 'pub', + 'CBOR Type': 'bstr', + 'Description': 'Public key for HSS/LMS hash-based digital signature', + 'Reference': 'https://datatracker.ietf.org/doc/RFC8778' +} +export const walnutdsa_n_parameter = { + 'Key Type': 6, + 'Label': -1, + 'Name': 'N', + 'CBOR Type': 'uint', + 'Description': 'Group and Matrix (NxN) size', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export const walnutdsa_q_parameter = { + 'Key Type': 6, + 'Label': -2, + 'Name': 'q', + 'CBOR Type': 'uint', + 'Description': 'Finite field F_q', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export const walnutdsa_t_values_parameter = { + 'Key Type': 6, + 'Label': -3, + 'Name': 't-values', + 'CBOR Type': 'array (of uint)', + 'Description': 'List of T-values, entries in F_q', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export const walnutdsa_matrix_1_parameter = { + 'Key Type': 6, + 'Label': -4, + 'Name': 'matrix 1', + 'CBOR Type': 'array (of array of uint)', + 'Description': 'NxN Matrix of entries in F_q in column-major form', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export const walnutdsa_permutation_1_parameter = { + 'Key Type': 6, + 'Label': -5, + 'Name': 'permutation 1', + 'CBOR Type': 'array (of uint)', + 'Description': 'Permutation associated with matrix 1', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export const walnutdsa_matrix_2_parameter = { + 'Key Type': 6, + 'Label': -6, + 'Name': 'matrix 2', + 'CBOR Type': 'array (of array of uint)', + 'Description': 'NxN Matrix of entries in F_q in column-major form', + 'Reference': 'https://datatracker.ietf.org/doc/RFC9021' +} +export enum okp { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + crv = -1, + x = -2, + d = -4, +} + +export enum ec2 { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + crv = -1, + x = -2, + y = -3, + d = -4, +} + +export enum rsa { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + n = -1, + e = -2, + d = -3, + p = -4, + q = -5, + dp = -6, + dq = -7, + qinv = -8, + other = -9, + r_i = -10, + d_i = -11, + t_i = -12, +} + +export enum symmetric { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + k = -1, +} + +export enum hss_lms { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + pub = -1, +} + +export enum walnutdsa { + kty = 1, + kid = 2, + alg = 3, + key_ops = 4, + base_iv = 5, + n = -1, + q = -2, + t_values = -3, + matrix_1 = -4, + permutation_1 = -5, + matrix_2 = -6, +} + + +export type any_cose_key = { + get(k: cose_key.kid): Buffer + get(k: cose_key.alg): string | number + get(k: cose_key.key_ops): [ (string|number)] + get(k: cose_key.base_iv): Buffer +} + +export type okp_key = any_cose_key & { + get(k: okp.kty): cose_key_type.okp + get(k: okp.crv): okp_curves + get(k: okp.x): Buffer + get(k: okp.d): Buffer +} + +export type ec2_key = any_cose_key & { + get(k: ec2.kty): cose_key_type.ec2 + get(k: ec2.crv): ec2_curves + get(k: ec2.x): Buffer + get(k: ec2.y): Buffer | boolean + get(k: ec2.d): Buffer +} + +export type rsa_key = any_cose_key & { + get(k: rsa.kty): cose_key_type.rsa + get(k: rsa.n): Buffer + get(k: rsa.e): Buffer + get(k: rsa.d): Buffer + get(k: rsa.p): Buffer + get(k: rsa.q): Buffer + get(k: rsa.dp): Buffer + get(k: rsa.dq): Buffer + get(k: rsa.qinv): Buffer + get(k: rsa.other): Array + get(k: rsa.r_i): Buffer + get(k: rsa.d_i): Buffer + get(k: rsa.t_i): Buffer +} + +export type symmetric_key = any_cose_key & { + get(k: symmetric.kty): cose_key_type.symmetric + get(k: symmetric.k): Buffer +} + +export type hss_lms_key = any_cose_key & { + get(k: hss_lms.kty): cose_key_type.hss_lms + get(k: hss_lms.pub): Buffer +} + +export type walnutdsa_key = any_cose_key & { + get(k: walnutdsa.kty): cose_key_type.walnutdsa + get(k: walnutdsa.n): number + get(k: walnutdsa.q): number + get(k: walnutdsa.t_values): Array + get(k: walnutdsa.matrix_1): Array> + get(k: walnutdsa.permutation_1): Array + get(k: walnutdsa.matrix_2): Array> +} + + +// kty +export const label_to_key_type = new Map([[0,"reserved"],[1,"OKP"],[2,"EC"],[3,"RSA"],[4,"oct"],[5,"hss_lms"],[6,"walnutdsa"]]) as Map +export const key_type_to_label = new Map([...label_to_key_type.entries()].map((e: any) => e.reverse())) as Map + +// crv +export const label_to_curve = new Map([[1,"P-256"],[2,"P-384"],[3,"P-521"],[8,"secp256k1"],[256,"brainpoolp256r1"],[257,"brainpoolp320r1"],[258,"brainpoolp384r1"],[259,"brainpoolp512r1"],[4,"X25519"],[5,"X448"],[6,"Ed25519"],[7,"Ed448"]]) as Map +export const curve_to_label = new Map([...label_to_curve.entries()].map((e: any) => e.reverse())) as Map + +// alg +export const labels_to_algorithms = new Map([[null,"Reserved for Private Use"],[-65535,"RS1"],[-65534,"A128CTR"],[-65533,"A192CTR"],[-65532,"A256CTR"],[-65531,"A128CBC"],[-65530,"A192CBC"],[-65529,"A256CBC"],[-260,"WalnutDSA"],[-259,"RS512"],[-258,"RS384"],[-257,"RS256"],[-47,"ES256K"],[-46,"HSS-LMS"],[-45,"SHAKE256"],[-44,"SHA-512"],[-43,"SHA-384"],[-42,"RSAES-OAEP w/ SHA-512"],[-41,"RSAES-OAEP w/ SHA-256"],[-40,"RSAES-OAEP w/ RFC 8017 default parameters"],[-39,"PS512"],[-38,"PS384"],[-37,"PS256"],[-36,"ES512"],[-35,"ES384"],[-34,"ECDH-SS + A256KW"],[-33,"ECDH-SS + A192KW"],[-32,"ECDH-SS + A128KW"],[-31,"ECDH-ES + A256KW"],[-30,"ECDH-ES + A192KW"],[-29,"ECDH-ES + A128KW"],[-28,"ECDH-SS + HKDF-512"],[-27,"ECDH-SS + HKDF-256"],[-26,"ECDH-ES + HKDF-512"],[-25,"ECDH-ES + HKDF-256"],[-18,"SHAKE128"],[-17,"SHA-512/256"],[-16,"SHA-256"],[-15,"SHA-256/64"],[-14,"SHA-1"],[-13,"direct+HKDF-AES-256"],[-12,"direct+HKDF-AES-128"],[-11,"direct+HKDF-SHA-512"],[-10,"direct+HKDF-SHA-256"],[-8,"EdDSA"],[-7,"ES256"],[-6,"direct"],[-5,"A256KW"],[-4,"A192KW"],[-3,"A128KW"],[0,"Reserved"],[1,"A128GCM"],[2,"A192GCM"],[3,"A256GCM"],[4,"HMAC 256/64"],[5,"HMAC 256/256"],[6,"HMAC 384/384"],[7,"HMAC 512/512"],[10,"AES-CCM-16-64-128"],[11,"AES-CCM-16-64-256"],[12,"AES-CCM-64-64-128"],[13,"AES-CCM-64-64-256"],[14,"AES-MAC 128/64"],[15,"AES-MAC 256/64"],[24,"ChaCha20/Poly1305"],[25,"AES-MAC 128/128"],[26,"AES-MAC 256/128"],[30,"AES-CCM-16-128-128"],[31,"AES-CCM-16-128-256"],[32,"AES-CCM-64-128-128"],[33,"AES-CCM-64-128-256"],[34,"IV-GENERATION"]]) as Map +export const algorithms_to_labels = new Map([...labels_to_algorithms.entries()].map((e: any) => e.reverse())) as Map + +// okp +export const labels_to_okp_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"crv"],[-2,"x"],[-4,"d"]]) as Map +export const okp_params_to_labels = new Map([...labels_to_okp_params.entries()].map((e: any) => e.reverse())) as Map + +// ec2 +export const labels_to_ec2_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"crv"],[-2,"x"],[-3,"y"],[-4,"d"]]) as Map +export const ec2_params_to_labels = new Map([...labels_to_ec2_params.entries()].map((e: any) => e.reverse())) as Map + +// rsa +export const labels_to_rsa_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"n"],[-2,"e"],[-3,"d"],[-4,"p"],[-5,"q"],[-6,"dp"],[-7,"dq"],[-8,"qinv"],[-9,"other"],[-10,"r_i"],[-11,"d_i"],[-12,"t_i"]]) as Map +export const rsa_params_to_labels = new Map([...labels_to_rsa_params.entries()].map((e: any) => e.reverse())) as Map + +// symmetric +export const labels_to_symmetric_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"k"]]) as Map +export const symmetric_params_to_labels = new Map([...labels_to_symmetric_params.entries()].map((e: any) => e.reverse())) as Map + +// hss_lms +export const labels_to_hss_lms_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"pub"]]) as Map +export const hss_lms_params_to_labels = new Map([...labels_to_hss_lms_params.entries()].map((e: any) => e.reverse())) as Map + +// walnutdsa +export const labels_to_walnutdsa_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"n"],[-2,"q"],[-3,"t_values"],[-4,"matrix_1"],[-5,"permutation_1"],[-6,"matrix_2"]]) as Map +export const walnutdsa_params_to_labels = new Map([...labels_to_walnutdsa_params.entries()].map((e: any) => e.reverse())) as Map \ No newline at end of file diff --git a/src/iana/assignments/media-types.ts b/src/iana/assignments/media-types.ts new file mode 100644 index 0000000..4c92414 --- /dev/null +++ b/src/iana/assignments/media-types.ts @@ -0,0 +1,4 @@ +export type application_cose = 'application/cose' +export type application_cose_key = 'application/cose-key' + +export type diagnostic_types = application_cose | application_cose_key \ No newline at end of file diff --git a/src/iana/index.ts b/src/iana/index.ts deleted file mode 100644 index 75adc0b..0000000 --- a/src/iana/index.ts +++ /dev/null @@ -1,64 +0,0 @@ - - -import { IANACOSEAlgorithms, IANACOSEAlgorithm } from '../cose/algorithms'; - -const algorithms = Object.values(IANACOSEAlgorithms) - -const ESP256 = { - Name: 'ESP256', - Value: '-9' -} as IANACOSEAlgorithm - -const ESP384 = { - Name: 'ESP384', - Value: '-48' -} as IANACOSEAlgorithm - -const fullySpecifiedByName = { - ESP256, - ESP384 -} as Record - -const fullySpecifiedByLabel = { - [ESP256.Value]: ESP256, - [ESP384.Value]: ESP384, -} as Record - -export const iana = { - 'COSE Algorithms': { - 'less-specified': (alg: string) => { - if (alg === 'ESP256') { - return 'ES256' - } - if (alg === 'ESP384') { - return 'ES384' - } - return alg - }, - getByName: (name: string) => { - const foundAlgorithm = algorithms.find((param) => { - return param.Name === name - }) - if (foundAlgorithm && foundAlgorithm.Name !== 'Unassigned') { - return foundAlgorithm - } - // extensions - if (fullySpecifiedByName[name]) { - return fullySpecifiedByName[name] - } - }, - getByValue: (value: number) => { - const foundAlgorithm = algorithms.find((param) => { - return param.Value === `${value}` - }) - if (foundAlgorithm && foundAlgorithm.Name !== 'Unassigned') { - return foundAlgorithm - } - // extensions - if (fullySpecifiedByLabel[`${value}`]) { - return fullySpecifiedByLabel[`${value}`] - } - } - } -} - diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts new file mode 100644 index 0000000..3e20314 --- /dev/null +++ b/src/iana/requested/cose.ts @@ -0,0 +1,19 @@ +import { algorithms_to_labels, labels_to_algorithms } from "../assignments/cose"; + + +algorithms_to_labels.set('ESP256', -9) +labels_to_algorithms.set(-9, 'ESP256') + +algorithms_to_labels.set('ESP384', -48) +labels_to_algorithms.set(-48, 'ESP384') + +export const less_specified = { + 'ESP384': 'ES384', + 'ESP256': 'ES256', + 'ES256': 'ES256', + 'ES384': 'ES384', + 'ES512': 'ES512' +} + + +export { algorithms_to_labels, labels_to_algorithms } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2741bae..c401861 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,8 @@ -export * from './cose/algorithms' + export * from './cose/header-parameters' -export * from './cose/key-common-parameters' import * as key from './cose/key' import * as attached from './cose/attached' diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 5be4798..3ed929b 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -2,8 +2,9 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; import { PublicKeyJwk } from "../cose/sign1" import * as x509 from "@peculiar/x509"; import { CoseSignatureAlgorithms } from '../cose/key'; -import { IANACOSEAlgorithms, PrivateKeyJwk, detached, RequestCoseSign1VerifyDetached, Hash } from '..'; +import { PrivateKeyJwk, detached, RequestCoseSign1VerifyDetached, Hash } from '..'; import { crypto } from '..'; +import { labels_to_algorithms } from '../iana/requested/cose'; // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) @@ -97,14 +98,9 @@ const root = async (req: RequestRootCertificate): Promise { - const foundAlgorithm = Object.values(IANACOSEAlgorithms).find((entry) => { - return entry.Value === `${alg}` - }) - if (!foundAlgorithm) { - throw new Error('Could not find algorithm in registry for: ' + alg) - } - const privateKeyJwk = await exportJWK(await importPKCS8(privateKeyPKCS8, `${foundAlgorithm.Name}`)) as PrivateKeyJwk - privateKeyJwk.alg = foundAlgorithm.Name; + const algName = labels_to_algorithms.get(alg) + const privateKeyJwk = await exportJWK(await importPKCS8(privateKeyPKCS8, `${algName}`)) as PrivateKeyJwk + privateKeyJwk.alg = algName; return detached.signer({ remote: crypto.signer({ privateKeyJwk diff --git a/test/fully-specified.test.ts b/test/fully-specified.test.ts index 21421cb..6b00f0c 100644 --- a/test/fully-specified.test.ts +++ b/test/fully-specified.test.ts @@ -32,7 +32,10 @@ const helpTestSignAndVerify = async (privateKey: cose.key.CoseKey) => { // https://datatracker.ietf.org/doc/draft-ietf-jose-fully-specified-algorithms/ -const algorithms = ["ESP256", "ESP384"] as CoseSignatureAlgorithms[] +const algorithms = [ + "ESP256", + "ESP384" +] as CoseSignatureAlgorithms[] algorithms.forEach((alg) => { it(alg, async () => { diff --git a/test/x509.test.ts b/test/x509.test.ts index a9da368..46990c0 100644 --- a/test/x509.test.ts +++ b/test/x509.test.ts @@ -2,6 +2,7 @@ import fs from 'fs' import moment from 'moment' import * as jose from 'jose' import * as cose from '../src' +import { labels_to_algorithms } from '../src/iana/requested/cose' it('sign and verify with x5t and key resolver', async () => { const cert = await cose.certificate.root({ @@ -46,15 +47,10 @@ it('sign and verify with x5t and key resolver', async () => { // normally this would be a trust store lookup if (hashAlgorith === rootCertificateThumbprint[0]) { if (Buffer.from(hash).toString('hex') === Buffer.from(rootCertificateThumbprint[1]).toString('hex')) { - const foundAlgorithm = Object.values(cose.IANACOSEAlgorithms).find((entry) => { - return entry.Value === `${alg}` - }) - if (!foundAlgorithm) { - throw new Error('Could not find algorithm in registry for: ' + alg) - } + const algName = labels_to_algorithms.get(alg) as any // could do extra certificate policy validation here... - const publicKeyJwk = await jose.exportJWK(await jose.importX509(cert.public, foundAlgorithm.Name)) - publicKeyJwk.alg = foundAlgorithm.Name + const publicKeyJwk = await jose.exportJWK(await jose.importX509(cert.public, algName)) + publicKeyJwk.alg = algName return publicKeyJwk } } From 168b447a269ed646ce111a5938103eb275f541cc Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Fri, 20 Sep 2024 17:08:21 -0500 Subject: [PATCH 02/67] cleaning --- package.json | 1 - scripts/make-iana-cose-header-parameters.js | 18 ----------- scripts/make-iana.js | 34 --------------------- scripts/make-iana.sh | 7 ----- 4 files changed, 60 deletions(-) delete mode 100644 scripts/make-iana-cose-header-parameters.js delete mode 100644 scripts/make-iana.js delete mode 100755 scripts/make-iana.sh diff --git a/package.json b/package.json index 8b62941..282c6d6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "coverage": "jest --ci --coverage", "lint": "eslint ./src ./test --fix", "shove": "git add -A; git commit -m ':rocket:'; git push origin main", - "generate:iana:ts": "./scripts/make-iana.sh", "iana:assignments": "npx ts-node ./scripts/make-iana-assignments.ts" }, "repository": { diff --git a/scripts/make-iana-cose-header-parameters.js b/scripts/make-iana-cose-header-parameters.js deleted file mode 100644 index 2bc2207..0000000 --- a/scripts/make-iana-cose-header-parameters.js +++ /dev/null @@ -1,18 +0,0 @@ -const fs = require('fs'); -const { csvUrlToMap } = require('./make-iana'); - -(async () => { - const record = await csvUrlToMap('https://www.iana.org/assignments/cose/header-parameters.csv') - const file = ` - export type IANACOSEHeaderParameter = { - Name: string - Label: string - 'Value Type': string - 'Value Registry': string - Description: string - Reference: string - } - export const IANACOSEHeaderParameters: Record = ${JSON.stringify(record, null, 2)}; - ` - fs.writeFileSync('./src/cose/header-parameters.ts', file.trim()) -})() \ No newline at end of file diff --git a/scripts/make-iana.js b/scripts/make-iana.js deleted file mode 100644 index 7f8e995..0000000 --- a/scripts/make-iana.js +++ /dev/null @@ -1,34 +0,0 @@ - -const axios = require('axios'); -const csv = require('csv-parser'); - -const csvUrlToMap = async (url) => { - const response = await axios.get(url, { - // headers: {Authorization: `Bearer ${token}`, - responseType: 'stream' - }); - return new Promise((resolve, reject) => { - - const stream = response.data.pipe(csv()); - const map = {} - stream.on('data', row => { - if (row.Reference.startsWith('[RFC')) { - row.Reference = `https://datatracker.ietf.org/doc/${row.Reference.substring(1, row.Reference.length - 1)}` - } - let index = row.Label || row.Value || row.Name - - if (row['Key Type']) { - index = row['Key Type'] + '-' + row['Name'] - } - - map[index] = row - // console.log(row) - }); - stream.on('end', () => { - resolve(map) - }); - }) -} - - -module.exports = { csvUrlToMap } diff --git a/scripts/make-iana.sh b/scripts/make-iana.sh deleted file mode 100755 index 771f76a..0000000 --- a/scripts/make-iana.sh +++ /dev/null @@ -1,7 +0,0 @@ - -node scripts/make-iana-cose-algorithms.js; -node scripts/make-iana-cose-header-parameters.js; -node scripts/make-iana-cose-key-common-parameters.js; -node scripts/make-iana-cose-key-type-parameters.js; -node scripts/make-iana-cose-key-type.js; -node scripts/make-iana-cose-elliptic-curves.js; \ No newline at end of file From 9858b54006d049126d47bf624697c198eb49cd26 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Fri, 20 Sep 2024 17:20:42 -0500 Subject: [PATCH 03/67] factoring --- scripts/data/header-parameters.csv | 33 ++++ scripts/make-iana-assignments.ts | 62 ++++++- src/cose/header-parameters.ts | 218 ------------------------ src/iana/assignments/cose.ts | 260 ++++++++++++++++++++++++++--- src/index.ts | 5 - 5 files changed, 333 insertions(+), 245 deletions(-) create mode 100644 scripts/data/header-parameters.csv delete mode 100644 src/cose/header-parameters.ts diff --git a/scripts/data/header-parameters.csv b/scripts/data/header-parameters.csv new file mode 100644 index 0000000..44899de --- /dev/null +++ b/scripts/data/header-parameters.csv @@ -0,0 +1,33 @@ +Name,Label,Value Type,Value Registry,Description,Reference +Reserved for Private Use,less than -65536,,,,[RFC9052] +delegated to the COSE Header Algorithm Parameters registry,-65536 to -1,,,, +Reserved,0,,,,[RFC9052] +alg,1,int / tstr,COSE Algorithms registry,Cryptographic algorithm to use,[RFC9052] +crit,2,[+ label],COSE Header Parameters registry,Critical headers to be understood,[RFC9052] +content type,3,tstr / uint,[COAP Content-Formats] or [Media Types] registry,Content type of the payload,[RFC9052] +kid,4,bstr,,Key identifier,[RFC9052] +IV,5,bstr,,Full Initialization Vector,[RFC9052] +Partial IV,6,bstr,,Partial Initialization Vector,[RFC9052] +counter signature,7,COSE_Signature / [+ COSE_Signature ],,CBOR-encoded signature structure (Deprecated by [RFC9338]),[RFC8152] +Unassigned,8,,,, +CounterSignature0,9,bstr,,Counter signature with implied signer and headers (Deprecated by [RFC9338]),[RFC8152] +kid context,10,bstr,,Identifies the context for the key identifier,"[RFC8613, Section 5.1]" +Countersignature version 2,11,COSE_Countersignature / [+ COSE_Countersignature],,V2 countersignature attribute,[RFC9338] +Countersignature0 version 2,12,COSE_Countersignature0,,V2 Abbreviated Countersignature,[RFC9338] +kcwt,13,COSE_Messages,,A CBOR Web Token (CWT) containing a COSE_Key in a 'cnf' claim and possibly other claims. CWT is defined in [RFC8392]. COSE_Messages is defined in [RFC9052].,[RFC9528] +kccs,14,map,,A CWT Claims Set (CCS) containing a COSE_Key in a 'cnf' claim and possibly other claims. CCS is defined in [RFC8392].,[RFC9528] +CWT Claims,15,map,map keys in [CWT Claims],Location for CWT Claims in COSE Header Parameters.,"[RFC9597, Section 2]" +typ (type),16,uint / tstr,[COAP Content-Formats] or [Media Types] registry,Content type of the complete COSE object,"[RFC9596, Section 2]" +Unassigned,17-21,,,, +c5t,22,COSE_CertHash,,"Hash of a C509Certificate (TEMPORARY - registered 2024-03-11, expires 2025-03-11)",[draft-ietf-cose-cbor-encoded-cert-09] +c5u,23,uri,,"URI pointing to a COSE_C509 containing a ordered chain of certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)",[draft-ietf-cose-cbor-encoded-cert-09] +c5b,24,COSE_C509,,"An unordered bag of C509 certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)",[draft-ietf-cose-cbor-encoded-cert-09] +c5c,25,COSE_C509,,"An ordered chain of C509 certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)",[draft-ietf-cose-cbor-encoded-cert-09] +Unassigned,26-31,,,, +x5bag,32,COSE_X509,,An unordered bag of X.509 certificates,[RFC9360] +x5chain,33,COSE_X509,,An ordered chain of X.509 certificates,[RFC9360] +x5t,34,COSE_CertHash,,Hash of an X.509 certificate,[RFC9360] +x5u,35,uri,,URI pointing to an X.509 certificate,[RFC9360] +Unassigned,36-255,,,, +CUPHNonce,256,bstr,,Challenge Nonce,[FIDO Device Onboard Specification] +CUPHOwnerPubKey,257,array,,Public Key,[FIDO Device Onboard Specification] diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index d050d7b..ca6b678 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -301,7 +301,7 @@ const createAlgorithmDefinitions = () => { .pipe(csv()) let algorithmDefinitions = `\n` stream.on('data', (row: any) => { - if (row.Name === 'Unassigned') { + if (row.Name === 'Unassigned' || row.Name.includes('Reserved')) { return } const valueName = row.Name @@ -333,7 +333,55 @@ export const ${valueName}_algorithm = { }) } -const createMappings = (curveLabels: [number, string][], paramsByKeyType: Map, keyTypes: Map, labelToAlgorithm: Map) => { + +const createHeaderParamesters = () => { + const labelToHeaderParam = new Map() as Map + return new Promise(async (resolve) => { + const stream = fs.createReadStream('./scripts/data/header-parameters.csv') + .pipe(csv()) + let headerParameterDefinitions = `\n` + stream.on('data', (row: any) => { + if (row.Name === 'Unassigned' || row.Name.includes('Reserved')) { + return + } + const valueName = row.Name + .replace(/ /g, '_') + .replace(/-/g, '_') + .replace(/\//g, '_') + .replace(/\(/g, '_') + .replace(/\)/g, '_') + .replace(/\+/g, '_') + .toLowerCase() + if (row.Reference.startsWith('[RFC')) { + row.Reference = `https://datatracker.ietf.org/doc/${row.Reference.substring(1, row.Reference.length - 1)}` + } + row['Label'] = parseInt(row['Label'], 10) + headerParameterDefinitions += ` +export const ${valueName}_algorithm = { + 'Name': '${row['Name']}', + 'Label': ${row['Label']}, + 'Value Type': '${row['Value Type'].replace(/\\n/g, ' ')}', + 'Value Registry': '${row['Value Registry']}', + 'Change Controller': '${row['Change Controller']}', + 'Description': "${row['Description']}", + 'Reference': '${row['Reference']}' +} + `.trim() + '\n' + labelToHeaderParam.set(row['Label'], row['Name']) + }); + stream.on('end', () => { + resolve({ labelToHeaderParam, headerParameterDefinitions }) + }); + }) +} + +const createMappings = ( + curveLabels: [number, string][], + paramsByKeyType: Map, + keyTypes: Map, + labelToAlgorithm: Map, + labelToHeaderParam: Map +) => { const ktyMap = { 'okp': 'OKP', @@ -359,6 +407,10 @@ export const curve_to_label = new Map([...label_to_curve.entries()].map((e: any) // alg export const labels_to_algorithms = new Map(${JSON.stringify(Array.from(labelToAlgorithm.entries()))}) as Map export const algorithms_to_labels = new Map([...${`labels_to_algorithms`}.entries()].map((e: any) => e.reverse())) as Map + +// headers +export const labels_to_headers = new Map(${JSON.stringify(Array.from(labelToHeaderParam.entries()))}) as Map +export const headers_to_labels = new Map([...${`labels_to_headers`}.entries()].map((e: any) => e.reverse())) as Map ` for (const [kty, params] of paramsByKeyType.entries()) { @@ -372,6 +424,8 @@ export const ${ktyName}_params_to_labels = new Map([...${`labels_to_${ktyName}_p return mappings } + + (async () => { const { commonKeyParams, commonKeyParamDefinitions } = await getCommonKeyParams() as any const { keyTypes, keyTypeDefinitions } = await getKeyTypes() as any @@ -379,13 +433,15 @@ export const ${ktyName}_params_to_labels = new Map([...${`labels_to_${ktyName}_p const { keyParams, paramsByKeyType } = await getKeyTypeParams({ commonKeyParams, keyTypes, curvesByKeyType }) as any const { labelToAlgorithm, algorithmDefinitions } = await createAlgorithmDefinitions() as any - const mappings = createMappings(curveLabels, paramsByKeyType, keyTypes, labelToAlgorithm) + const { labelToHeaderParam, headerParameterDefinitions } = await createHeaderParamesters() as any; + const mappings = createMappings(curveLabels, paramsByKeyType, keyTypes, labelToAlgorithm, labelToHeaderParam) const final = ` // DO NOT edit this file, it is generated automatically ${commonKeyParamDefinitions} ${keyTypeDefinitions} ${curveDefinitions} ${algorithmDefinitions} +${headerParameterDefinitions} ${keyParams} ${mappings} diff --git a/src/cose/header-parameters.ts b/src/cose/header-parameters.ts deleted file mode 100644 index 78c140f..0000000 --- a/src/cose/header-parameters.ts +++ /dev/null @@ -1,218 +0,0 @@ -export type IANACOSEHeaderParameter = { - Name: string - Label: string - 'Value Type': string - 'Value Registry': string - Description: string - Reference: string - } - export const IANACOSEHeaderParameters: Record = { - "0": { - "Name": "Reserved", - "Label": "0", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "1": { - "Name": "alg", - "Label": "1", - "Value Type": "int / tstr", - "Value Registry": "COSE Algorithms registry", - "Description": "Cryptographic algorithm to use", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "2": { - "Name": "crit", - "Label": "2", - "Value Type": "[+ label]", - "Value Registry": "COSE Header Parameters registry", - "Description": "Critical headers to be understood", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "3": { - "Name": "content type", - "Label": "3", - "Value Type": "tstr / uint", - "Value Registry": "[COAP Content-Formats] or [Media Types] registry", - "Description": "Content type of the payload", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "4": { - "Name": "kid", - "Label": "4", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Key identifier", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "5": { - "Name": "IV", - "Label": "5", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Full Initialization Vector", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "6": { - "Name": "Partial IV", - "Label": "6", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Partial Initialization Vector", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "7": { - "Name": "counter signature", - "Label": "7", - "Value Type": "COSE_Signature / [+ COSE_Signature ]", - "Value Registry": "", - "Description": "CBOR-encoded signature structure (Deprecated by [RFC9338])", - "Reference": "https://datatracker.ietf.org/doc/RFC8152" - }, - "8": { - "Name": "Unassigned", - "Label": "8", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "" - }, - "9": { - "Name": "CounterSignature0", - "Label": "9", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Counter signature with implied signer and headers (Deprecated by [RFC9338])", - "Reference": "https://datatracker.ietf.org/doc/RFC8152" - }, - "10": { - "Name": "kid context", - "Label": "10", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Identifies the context for the key identifier", - "Reference": "https://datatracker.ietf.org/doc/RFC8613, Section 5.1" - }, - "11": { - "Name": "Countersignature version 2", - "Label": "11", - "Value Type": "COSE_Countersignature / [+ COSE_Countersignature]", - "Value Registry": "", - "Description": "V2 countersignature attribute", - "Reference": "https://datatracker.ietf.org/doc/RFC9338" - }, - "12": { - "Name": "Countersignature0 version 2", - "Label": "12", - "Value Type": "COSE_Countersignature0", - "Value Registry": "", - "Description": "V2 Abbreviated Countersignature", - "Reference": "https://datatracker.ietf.org/doc/RFC9338" - }, - "13": { - "Name": "kcwt", - "Label": "13", - "Value Type": "COSE_Messages", - "Value Registry": "", - "Description": "A CBOR Web Token (CWT) containing a COSE_Key in a 'cnf' claim and possibly other claims. CWT is defined in [RFC8392]. COSE_Messages is defined in [RFC9052].", - "Reference": "https://datatracker.ietf.org/doc/RFC-ietf-lake-edhoc-22" - }, - "14": { - "Name": "kccs", - "Label": "14", - "Value Type": "map", - "Value Registry": "", - "Description": "A CWT Claims Set (CCS) containing a COSE_Key in a 'cnf' claim and possibly other claims. CCS is defined in [RFC8392].", - "Reference": "https://datatracker.ietf.org/doc/RFC-ietf-lake-edhoc-22" - }, - "15": { - "Name": "CWT Claims", - "Label": "15", - "Value Type": "map", - "Value Registry": "", - "Description": "Location for CWT Claims in COSE Header Parameters.", - "Reference": "https://datatracker.ietf.org/doc/RFC-ietf-cose-cwt-claims-in-headers-10" - }, - "32": { - "Name": "x5bag", - "Label": "32", - "Value Type": "COSE_X509", - "Value Registry": "", - "Description": "An unordered bag of X.509 certificates", - "Reference": "https://datatracker.ietf.org/doc/RFC9360" - }, - "33": { - "Name": "x5chain", - "Label": "33", - "Value Type": "COSE_X509", - "Value Registry": "", - "Description": "An ordered chain of X.509 certificates", - "Reference": "https://datatracker.ietf.org/doc/RFC9360" - }, - "34": { - "Name": "x5t", - "Label": "34", - "Value Type": "COSE_CertHash", - "Value Registry": "", - "Description": "Hash of an X.509 certificate", - "Reference": "https://datatracker.ietf.org/doc/RFC9360" - }, - "35": { - "Name": "x5u", - "Label": "35", - "Value Type": "uri", - "Value Registry": "", - "Description": "URI pointing to an X.509 certificate", - "Reference": "https://datatracker.ietf.org/doc/RFC9360" - }, - "256": { - "Name": "CUPHNonce", - "Label": "256", - "Value Type": "bstr", - "Value Registry": "", - "Description": "Challenge Nonce", - "Reference": "[FIDO Device Onboard Specification]" - }, - "257": { - "Name": "CUPHOwnerPubKey", - "Label": "257", - "Value Type": "array", - "Value Registry": "", - "Description": "Public Key", - "Reference": "[FIDO Device Onboard Specification]" - }, - "less than -65536": { - "Name": "Reserved for Private Use", - "Label": "less than -65536", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "https://datatracker.ietf.org/doc/RFC9052" - }, - "-65536 to -1": { - "Name": "delegated to the COSE Header Algorithm Parameters registry", - "Label": "-65536 to -1", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "" - }, - "16-31": { - "Name": "Unassigned", - "Label": "16-31", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "" - }, - "36-255": { - "Name": "Unassigned", - "Label": "36-255", - "Value Type": "", - "Value Registry": "", - "Description": "", - "Reference": "" - } -}; \ No newline at end of file diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index 5276c16..45684f2 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -249,15 +249,6 @@ export enum okp_curves { } -export const reserved_for_private_use_algorithm = { - 'Name': 'Reserved for Private Use', - 'Value': NaN, - 'Description': '', - 'Capabilities': '', - 'Change Controller': '', - 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', - 'Recommended': 'No' -} export const rs1_algorithm = { 'Name': 'RS1', 'Value': -65535, @@ -699,15 +690,6 @@ export const a128kw_algorithm = { 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', 'Recommended': 'Yes' } -export const reserved_algorithm = { - 'Name': 'Reserved', - 'Value': 0, - 'Description': '', - 'Capabilities': '', - 'Change Controller': '', - 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', - 'Recommended': 'No' -} export const a128gcm_algorithm = { 'Name': 'A128GCM', 'Value': 1, @@ -898,6 +880,242 @@ export const iv_generation_algorithm = { 'Recommended': 'No' } + +export const delegated_to_the_cose_header_algorithm_parameters_registry_algorithm = { + 'Name': 'delegated to the COSE Header Algorithm Parameters registry', + 'Label': -65536, + 'Value Type': '', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "", + 'Reference': '' +} +export const alg_algorithm = { + 'Name': 'alg', + 'Label': 1, + 'Value Type': 'int / tstr', + 'Value Registry': 'COSE Algorithms registry', + 'Change Controller': 'undefined', + 'Description': "Cryptographic algorithm to use", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const crit_algorithm = { + 'Name': 'crit', + 'Label': 2, + 'Value Type': '[+ label]', + 'Value Registry': 'COSE Header Parameters registry', + 'Change Controller': 'undefined', + 'Description': "Critical headers to be understood", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const content_type_algorithm = { + 'Name': 'content type', + 'Label': 3, + 'Value Type': 'tstr / uint', + 'Value Registry': '[COAP Content-Formats] or [Media Types] registry', + 'Change Controller': 'undefined', + 'Description': "Content type of the payload", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const kid_algorithm = { + 'Name': 'kid', + 'Label': 4, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Key identifier", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const iv_algorithm = { + 'Name': 'IV', + 'Label': 5, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Full Initialization Vector", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const partial_iv_algorithm = { + 'Name': 'Partial IV', + 'Label': 6, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Partial Initialization Vector", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9052' +} +export const counter_signature_algorithm = { + 'Name': 'counter signature', + 'Label': 7, + 'Value Type': 'COSE_Signature / [+ COSE_Signature ]', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "CBOR-encoded signature structure (Deprecated by [RFC9338])", + 'Reference': 'https://datatracker.ietf.org/doc/RFC8152' +} +export const countersignature0_algorithm = { + 'Name': 'CounterSignature0', + 'Label': 9, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Counter signature with implied signer and headers (Deprecated by [RFC9338])", + 'Reference': 'https://datatracker.ietf.org/doc/RFC8152' +} +export const kid_context_algorithm = { + 'Name': 'kid context', + 'Label': 10, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Identifies the context for the key identifier", + 'Reference': 'https://datatracker.ietf.org/doc/RFC8613, Section 5.1' +} +export const countersignature_version_2_algorithm = { + 'Name': 'Countersignature version 2', + 'Label': 11, + 'Value Type': 'COSE_Countersignature / [+ COSE_Countersignature]', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "V2 countersignature attribute", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9338' +} +export const countersignature0_version_2_algorithm = { + 'Name': 'Countersignature0 version 2', + 'Label': 12, + 'Value Type': 'COSE_Countersignature0', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "V2 Abbreviated Countersignature", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9338' +} +export const kcwt_algorithm = { + 'Name': 'kcwt', + 'Label': 13, + 'Value Type': 'COSE_Messages', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "A CBOR Web Token (CWT) containing a COSE_Key in a 'cnf' claim and possibly other claims. CWT is defined in [RFC8392]. COSE_Messages is defined in [RFC9052].", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9528' +} +export const kccs_algorithm = { + 'Name': 'kccs', + 'Label': 14, + 'Value Type': 'map', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "A CWT Claims Set (CCS) containing a COSE_Key in a 'cnf' claim and possibly other claims. CCS is defined in [RFC8392].", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9528' +} +export const cwt_claims_algorithm = { + 'Name': 'CWT Claims', + 'Label': 15, + 'Value Type': 'map', + 'Value Registry': 'map keys in [CWT Claims]', + 'Change Controller': 'undefined', + 'Description': "Location for CWT Claims in COSE Header Parameters.", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9597, Section 2' +} +export const typ__type__algorithm = { + 'Name': 'typ (type)', + 'Label': 16, + 'Value Type': 'uint / tstr', + 'Value Registry': '[COAP Content-Formats] or [Media Types] registry', + 'Change Controller': 'undefined', + 'Description': "Content type of the complete COSE object", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9596, Section 2' +} +export const c5t_algorithm = { + 'Name': 'c5t', + 'Label': 22, + 'Value Type': 'COSE_CertHash', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Hash of a C509Certificate (TEMPORARY - registered 2024-03-11, expires 2025-03-11)", + 'Reference': '[draft-ietf-cose-cbor-encoded-cert-09]' +} +export const c5u_algorithm = { + 'Name': 'c5u', + 'Label': 23, + 'Value Type': 'uri', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "URI pointing to a COSE_C509 containing a ordered chain of certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)", + 'Reference': '[draft-ietf-cose-cbor-encoded-cert-09]' +} +export const c5b_algorithm = { + 'Name': 'c5b', + 'Label': 24, + 'Value Type': 'COSE_C509', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "An unordered bag of C509 certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)", + 'Reference': '[draft-ietf-cose-cbor-encoded-cert-09]' +} +export const c5c_algorithm = { + 'Name': 'c5c', + 'Label': 25, + 'Value Type': 'COSE_C509', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "An ordered chain of C509 certificates (TEMPORARY - registered 2024-03-11, expires 2025-03-11)", + 'Reference': '[draft-ietf-cose-cbor-encoded-cert-09]' +} +export const x5bag_algorithm = { + 'Name': 'x5bag', + 'Label': 32, + 'Value Type': 'COSE_X509', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "An unordered bag of X.509 certificates", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9360' +} +export const x5chain_algorithm = { + 'Name': 'x5chain', + 'Label': 33, + 'Value Type': 'COSE_X509', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "An ordered chain of X.509 certificates", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9360' +} +export const x5t_algorithm = { + 'Name': 'x5t', + 'Label': 34, + 'Value Type': 'COSE_CertHash', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Hash of an X.509 certificate", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9360' +} +export const x5u_algorithm = { + 'Name': 'x5u', + 'Label': 35, + 'Value Type': 'uri', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "URI pointing to an X.509 certificate", + 'Reference': 'https://datatracker.ietf.org/doc/RFC9360' +} +export const cuphnonce_algorithm = { + 'Name': 'CUPHNonce', + 'Label': 256, + 'Value Type': 'bstr', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Challenge Nonce", + 'Reference': '[FIDO Device Onboard Specification]' +} +export const cuphownerpubkey_algorithm = { + 'Name': 'CUPHOwnerPubKey', + 'Label': 257, + 'Value Type': 'array', + 'Value Registry': '', + 'Change Controller': 'undefined', + 'Description': "Public Key", + 'Reference': '[FIDO Device Onboard Specification]' +} + export const okp_crv_parameter = { 'Key Type': 1, 'Label': -1, @@ -1258,9 +1476,13 @@ export const label_to_curve = new Map([[1,"P-256"],[2,"P-384"],[3,"P-521"],[8,"s export const curve_to_label = new Map([...label_to_curve.entries()].map((e: any) => e.reverse())) as Map // alg -export const labels_to_algorithms = new Map([[null,"Reserved for Private Use"],[-65535,"RS1"],[-65534,"A128CTR"],[-65533,"A192CTR"],[-65532,"A256CTR"],[-65531,"A128CBC"],[-65530,"A192CBC"],[-65529,"A256CBC"],[-260,"WalnutDSA"],[-259,"RS512"],[-258,"RS384"],[-257,"RS256"],[-47,"ES256K"],[-46,"HSS-LMS"],[-45,"SHAKE256"],[-44,"SHA-512"],[-43,"SHA-384"],[-42,"RSAES-OAEP w/ SHA-512"],[-41,"RSAES-OAEP w/ SHA-256"],[-40,"RSAES-OAEP w/ RFC 8017 default parameters"],[-39,"PS512"],[-38,"PS384"],[-37,"PS256"],[-36,"ES512"],[-35,"ES384"],[-34,"ECDH-SS + A256KW"],[-33,"ECDH-SS + A192KW"],[-32,"ECDH-SS + A128KW"],[-31,"ECDH-ES + A256KW"],[-30,"ECDH-ES + A192KW"],[-29,"ECDH-ES + A128KW"],[-28,"ECDH-SS + HKDF-512"],[-27,"ECDH-SS + HKDF-256"],[-26,"ECDH-ES + HKDF-512"],[-25,"ECDH-ES + HKDF-256"],[-18,"SHAKE128"],[-17,"SHA-512/256"],[-16,"SHA-256"],[-15,"SHA-256/64"],[-14,"SHA-1"],[-13,"direct+HKDF-AES-256"],[-12,"direct+HKDF-AES-128"],[-11,"direct+HKDF-SHA-512"],[-10,"direct+HKDF-SHA-256"],[-8,"EdDSA"],[-7,"ES256"],[-6,"direct"],[-5,"A256KW"],[-4,"A192KW"],[-3,"A128KW"],[0,"Reserved"],[1,"A128GCM"],[2,"A192GCM"],[3,"A256GCM"],[4,"HMAC 256/64"],[5,"HMAC 256/256"],[6,"HMAC 384/384"],[7,"HMAC 512/512"],[10,"AES-CCM-16-64-128"],[11,"AES-CCM-16-64-256"],[12,"AES-CCM-64-64-128"],[13,"AES-CCM-64-64-256"],[14,"AES-MAC 128/64"],[15,"AES-MAC 256/64"],[24,"ChaCha20/Poly1305"],[25,"AES-MAC 128/128"],[26,"AES-MAC 256/128"],[30,"AES-CCM-16-128-128"],[31,"AES-CCM-16-128-256"],[32,"AES-CCM-64-128-128"],[33,"AES-CCM-64-128-256"],[34,"IV-GENERATION"]]) as Map +export const labels_to_algorithms = new Map([[-65535,"RS1"],[-65534,"A128CTR"],[-65533,"A192CTR"],[-65532,"A256CTR"],[-65531,"A128CBC"],[-65530,"A192CBC"],[-65529,"A256CBC"],[-260,"WalnutDSA"],[-259,"RS512"],[-258,"RS384"],[-257,"RS256"],[-47,"ES256K"],[-46,"HSS-LMS"],[-45,"SHAKE256"],[-44,"SHA-512"],[-43,"SHA-384"],[-42,"RSAES-OAEP w/ SHA-512"],[-41,"RSAES-OAEP w/ SHA-256"],[-40,"RSAES-OAEP w/ RFC 8017 default parameters"],[-39,"PS512"],[-38,"PS384"],[-37,"PS256"],[-36,"ES512"],[-35,"ES384"],[-34,"ECDH-SS + A256KW"],[-33,"ECDH-SS + A192KW"],[-32,"ECDH-SS + A128KW"],[-31,"ECDH-ES + A256KW"],[-30,"ECDH-ES + A192KW"],[-29,"ECDH-ES + A128KW"],[-28,"ECDH-SS + HKDF-512"],[-27,"ECDH-SS + HKDF-256"],[-26,"ECDH-ES + HKDF-512"],[-25,"ECDH-ES + HKDF-256"],[-18,"SHAKE128"],[-17,"SHA-512/256"],[-16,"SHA-256"],[-15,"SHA-256/64"],[-14,"SHA-1"],[-13,"direct+HKDF-AES-256"],[-12,"direct+HKDF-AES-128"],[-11,"direct+HKDF-SHA-512"],[-10,"direct+HKDF-SHA-256"],[-8,"EdDSA"],[-7,"ES256"],[-6,"direct"],[-5,"A256KW"],[-4,"A192KW"],[-3,"A128KW"],[1,"A128GCM"],[2,"A192GCM"],[3,"A256GCM"],[4,"HMAC 256/64"],[5,"HMAC 256/256"],[6,"HMAC 384/384"],[7,"HMAC 512/512"],[10,"AES-CCM-16-64-128"],[11,"AES-CCM-16-64-256"],[12,"AES-CCM-64-64-128"],[13,"AES-CCM-64-64-256"],[14,"AES-MAC 128/64"],[15,"AES-MAC 256/64"],[24,"ChaCha20/Poly1305"],[25,"AES-MAC 128/128"],[26,"AES-MAC 256/128"],[30,"AES-CCM-16-128-128"],[31,"AES-CCM-16-128-256"],[32,"AES-CCM-64-128-128"],[33,"AES-CCM-64-128-256"],[34,"IV-GENERATION"]]) as Map export const algorithms_to_labels = new Map([...labels_to_algorithms.entries()].map((e: any) => e.reverse())) as Map +// headers +export const labels_to_headers = new Map([[-65536,"delegated to the COSE Header Algorithm Parameters registry"],[1,"alg"],[2,"crit"],[3,"content type"],[4,"kid"],[5,"IV"],[6,"Partial IV"],[7,"counter signature"],[9,"CounterSignature0"],[10,"kid context"],[11,"Countersignature version 2"],[12,"Countersignature0 version 2"],[13,"kcwt"],[14,"kccs"],[15,"CWT Claims"],[16,"typ (type)"],[22,"c5t"],[23,"c5u"],[24,"c5b"],[25,"c5c"],[32,"x5bag"],[33,"x5chain"],[34,"x5t"],[35,"x5u"],[256,"CUPHNonce"],[257,"CUPHOwnerPubKey"]]) as Map +export const headers_to_labels = new Map([...labels_to_headers.entries()].map((e: any) => e.reverse())) as Map + // okp export const labels_to_okp_params = new Map([[1,"kty"],[2,"kid"],[3,"alg"],[4,"key_ops"],[5,"base_iv"],[-1,"crv"],[-2,"x"],[-4,"d"]]) as Map export const okp_params_to_labels = new Map([...labels_to_okp_params.entries()].map((e: any) => e.reverse())) as Map diff --git a/src/index.ts b/src/index.ts index c401861..44190a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,6 @@ -export * from './cose/header-parameters' import * as key from './cose/key' import * as attached from './cose/attached' @@ -21,10 +20,6 @@ export * from './cose/Params' // export * from './cose/encrypt' import * as cbor from './cbor' - import * as receipt from './cose/receipt' - - import * as crypto from './crypto' - export { crypto, cbor, key, attached, detached, receipt } \ No newline at end of file From a034ea6a35ba6276fd0157a09ca6169f6a06569b Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Fri, 20 Sep 2024 17:25:00 -0500 Subject: [PATCH 04/67] factoring --- src/cbor/diag.ts | 23 ++++++++++++ src/cbor/index.ts | 1 + src/cbor/pretty/ellideBytes.ts | 4 ++ src/cbor/pretty/prettyCose.ts | 11 ++++++ src/cbor/pretty/prettyCoseKey.ts | 47 ++++++++++++++++++++++++ src/cbor/pretty/prettyCoseSign1.ts | 7 ++++ tests/__fixtures__/cose-key.cbor | 1 + tests/__fixtures__/cose-key.diag | 9 +++++ tests/__fixtures__/detached-payload.cbor | 1 + tests/edn.test.ts | 21 +++++++++++ {test => tests}/fully-specified.test.ts | 0 {test => tests}/key.test.ts | 0 {test => tests}/readme.test.ts | 0 {test => tests}/receipt.test.ts | 0 {test => tests}/sign1.attached.test.ts | 0 {test => tests}/sign1.detached.test.ts | 0 {test => tests}/signer.test.ts | 0 {test => tests}/verifiers.test.ts | 0 {test => tests}/x509.test.ts | 0 tsconfig.json | 2 +- 20 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/cbor/diag.ts create mode 100644 src/cbor/pretty/ellideBytes.ts create mode 100644 src/cbor/pretty/prettyCose.ts create mode 100644 src/cbor/pretty/prettyCoseKey.ts create mode 100644 src/cbor/pretty/prettyCoseSign1.ts create mode 100644 tests/__fixtures__/cose-key.cbor create mode 100644 tests/__fixtures__/cose-key.diag create mode 100644 tests/__fixtures__/detached-payload.cbor create mode 100644 tests/edn.test.ts rename {test => tests}/fully-specified.test.ts (100%) rename {test => tests}/key.test.ts (100%) rename {test => tests}/readme.test.ts (100%) rename {test => tests}/receipt.test.ts (100%) rename {test => tests}/sign1.attached.test.ts (100%) rename {test => tests}/sign1.detached.test.ts (100%) rename {test => tests}/signer.test.ts (100%) rename {test => tests}/verifiers.test.ts (100%) rename {test => tests}/x509.test.ts (100%) diff --git a/src/cbor/diag.ts b/src/cbor/diag.ts new file mode 100644 index 0000000..d7d9366 --- /dev/null +++ b/src/cbor/diag.ts @@ -0,0 +1,23 @@ + +import * as cbor from 'cbor-web' + + +import { diagnostic_types } from '../iana/assignments/media-types' + +import { prettyCoseKey } from './pretty/prettyCoseKey' +import { prettyCose } from './pretty/prettyCose' + +export const diag = async (data: any, contentType: diagnostic_types) => { + try { + if (contentType === 'application/cose-key') { + return prettyCoseKey(data) + } + if (contentType === 'application/cose') { + return prettyCose(data) + } + } catch (e) { + return cbor.diagnose(data) + } +} + + diff --git a/src/cbor/index.ts b/src/cbor/index.ts index 21926da..805057c 100644 --- a/src/cbor/index.ts +++ b/src/cbor/index.ts @@ -1,3 +1,4 @@ +export * from './diag' import { encodeCanonical, encode, decode, encodeAsync, decodeFirst, decodeFirstSync, diagnose, Tagged } from 'cbor-web' diff --git a/src/cbor/pretty/ellideBytes.ts b/src/cbor/pretty/ellideBytes.ts new file mode 100644 index 0000000..d9bc7b7 --- /dev/null +++ b/src/cbor/pretty/ellideBytes.ts @@ -0,0 +1,4 @@ +export const ellideBytes = (bytes: Buffer) => { + const line = `h'${bytes.toString('hex').toLowerCase()}'` + return line.replace(/h'(.{8}).+(.{8})'/g, `h'$1...$2'`).trim() +} diff --git a/src/cbor/pretty/prettyCose.ts b/src/cbor/pretty/prettyCose.ts new file mode 100644 index 0000000..33be1ad --- /dev/null +++ b/src/cbor/pretty/prettyCose.ts @@ -0,0 +1,11 @@ +import * as cbor from 'cbor-web' +import { prettyCoseSign1 } from './prettyCoseSign1' + +export const prettyCose = (data: Buffer) => { + const decoded = cbor.decode(data) + if (decoded.tag === 18) { + return prettyCoseSign1(data) + } + return cbor.diagnose(data) +} + diff --git a/src/cbor/pretty/prettyCoseKey.ts b/src/cbor/pretty/prettyCoseKey.ts new file mode 100644 index 0000000..2e86aa4 --- /dev/null +++ b/src/cbor/pretty/prettyCoseKey.ts @@ -0,0 +1,47 @@ + +import * as cbor from 'cbor-web' + +import { ellideBytes } from './ellideBytes' + +import { ec2_key, cose_key, cose_key_type, ec2, any_cose_key } from '../../iana/assignments/cose' + +export const prettyCoseKey = (data: Buffer) => { + const decoded = cbor.decode(data) as any_cose_key + const kty = decoded.get(cose_key.kty as any) as any + if (kty === cose_key_type.ec2) { + const k = decoded as ec2_key + let diag = `/ cose key / {\n` + const kid = k.get(cose_key.kid) + if (kid !== undefined) { + diag += ` / kid / ${cose_key.kid} : ${ellideBytes(kid)},\n` + } + const kty = k.get(ec2.kty) + if (kty !== undefined) { + diag += ` / kty / ${cose_key.kty} : ${kty},\n` + } + const alg = k.get(cose_key.alg) + if (kty !== undefined) { + diag += ` / alg / ${cose_key.alg} : ${alg},\n` + } + const crv = k.get(ec2.crv) + if (crv !== undefined) { + diag += ` / crv / ${ec2.crv} : ${crv},\n` + } + const x = k.get(ec2.x) + if (x !== undefined) { + diag += ` / x / ${ec2.x} : ${ellideBytes(x as any)},\n` + } + const y = k.get(ec2.y) + if (y && (typeof y !== "boolean")) { + diag += ` / y / ${ec2.y} : ${ellideBytes(y as any)},\n` + } + const d = k.get(ec2.d) + if (d !== undefined) { + diag += ` / d / ${ec2.d} : ${ellideBytes(d as any)},\n` + } + diag += `}` + return diag.trim() + } + return cbor.diagnose(data) +} + diff --git a/src/cbor/pretty/prettyCoseSign1.ts b/src/cbor/pretty/prettyCoseSign1.ts new file mode 100644 index 0000000..a84d2bb --- /dev/null +++ b/src/cbor/pretty/prettyCoseSign1.ts @@ -0,0 +1,7 @@ + +import * as cbor from 'cbor-web' + +export const prettyCoseSign1 = (data: Buffer) => { + return cbor.diagnose(data) +} + diff --git a/tests/__fixtures__/cose-key.cbor b/tests/__fixtures__/cose-key.cbor new file mode 100644 index 0000000..fc442a2 --- /dev/null +++ b/tests/__fixtures__/cose-key.cbor @@ -0,0 +1 @@ +§!X Ç(+Ë]Ók3G°õ»ŽKßg ýJ*;Uùô.3(Ý}"X ãz×/Õ´6ë?’5§2 a£¶0Igz(Þ#¬1öí #X ˆª†$ðjí/ŠŸlȃ%pòñÉ—þ´Ú­¸ÎV(X GAWŸ–tëÊE¡ûp:xù›‘D%‡‚"·¾´†æÌBï \ No newline at end of file diff --git a/tests/__fixtures__/cose-key.diag b/tests/__fixtures__/cose-key.diag new file mode 100644 index 0000000..90a69e6 --- /dev/null +++ b/tests/__fixtures__/cose-key.diag @@ -0,0 +1,9 @@ +/ cose key / { + / kid / 2 : h'4741579f...e6cc42ef', + / kty / 1 : 2, + / alg / 3 : -9, + / crv / -1 : 1, + / x / -2 : h'c7282bcb...3328dd7d', + / y / -3 : h'e37ad72f...31f6edc2', + / d / -4 : h'88aa8624...b8ce1856', +} \ No newline at end of file diff --git a/tests/__fixtures__/detached-payload.cbor b/tests/__fixtures__/detached-payload.cbor new file mode 100644 index 0000000..d493085 --- /dev/null +++ b/tests/__fixtures__/detached-payload.cbor @@ -0,0 +1 @@ +Ò„D¡8" öX`´àÕ1Èf ÙJŠg¡<Ÿ&Ì{N2äk¨•ì<*15‚Ï‚ò—G• m/qݹ—¼‹]üwJ5éáÍÏPrâ^%I†¥™Z÷»æ±œ©ë¢.ˆ^°¤Š: \ No newline at end of file diff --git a/tests/edn.test.ts b/tests/edn.test.ts new file mode 100644 index 0000000..65b1c41 --- /dev/null +++ b/tests/edn.test.ts @@ -0,0 +1,21 @@ +import fs from 'fs' +import * as cose from '../src' + +it('cose key', async () => { + const input = fs.readFileSync('./tests/__fixtures__/cose-key.cbor') + const output = fs.readFileSync('./tests/__fixtures__/cose-key.diag') + const diag = await cose.cbor.diag(input, "application/cose-key") + expect(diag).toBe(output.toString()) +}) + + +it('detached payload cose sign1', async () => { + const input = fs.readFileSync('./tests/__fixtures__/detached-payload.cbor') + // const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') + const diag = await cose.cbor.diag(input, "application/cose") + // console.log(diag) + // expect(diag).toBe(output.toString()) +}) + + +// Move this to transmute/cose \ No newline at end of file diff --git a/test/fully-specified.test.ts b/tests/fully-specified.test.ts similarity index 100% rename from test/fully-specified.test.ts rename to tests/fully-specified.test.ts diff --git a/test/key.test.ts b/tests/key.test.ts similarity index 100% rename from test/key.test.ts rename to tests/key.test.ts diff --git a/test/readme.test.ts b/tests/readme.test.ts similarity index 100% rename from test/readme.test.ts rename to tests/readme.test.ts diff --git a/test/receipt.test.ts b/tests/receipt.test.ts similarity index 100% rename from test/receipt.test.ts rename to tests/receipt.test.ts diff --git a/test/sign1.attached.test.ts b/tests/sign1.attached.test.ts similarity index 100% rename from test/sign1.attached.test.ts rename to tests/sign1.attached.test.ts diff --git a/test/sign1.detached.test.ts b/tests/sign1.detached.test.ts similarity index 100% rename from test/sign1.detached.test.ts rename to tests/sign1.detached.test.ts diff --git a/test/signer.test.ts b/tests/signer.test.ts similarity index 100% rename from test/signer.test.ts rename to tests/signer.test.ts diff --git a/test/verifiers.test.ts b/tests/verifiers.test.ts similarity index 100% rename from test/verifiers.test.ts rename to tests/verifiers.test.ts diff --git a/test/x509.test.ts b/tests/x509.test.ts similarity index 100% rename from test/x509.test.ts rename to tests/x509.test.ts diff --git a/tsconfig.json b/tsconfig.json index 4f251e9..aef3552 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "exclude": [ - "test", + "tests", "attic", "examples" ], From 60ff4e324047448e57e55024e45318a48c8ca955 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 08:43:57 -0500 Subject: [PATCH 05/67] factoring --- scripts/make-iana-assignments.ts | 2 +- src/cose/key/convertCoseKeyToJsonWebKey.ts | 86 +++++++++++----------- src/cose/key/convertJsonWebKeyToCoseKey.ts | 8 +- src/cose/key/generate.ts | 8 +- src/cose/key/publicFromPrivate.ts | 18 ++--- src/iana/assignments/cose.ts | 2 +- src/index.ts | 4 + tests/edn.test.ts | 2 - tests/fully-specified.test.ts | 6 +- tests/key.test.ts | 61 ++++++++------- tests/verifiers.test.ts | 6 +- 11 files changed, 100 insertions(+), 103 deletions(-) diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index ca6b678..54d8afc 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -244,7 +244,7 @@ export const ${ktyName}_${paramName}_parameter = { keyParams += '}\n\n' } - keyParams += `\nexport type any_cose_key = {\n` + keyParams += `\nexport type any_cose_key = Map & {\n` for (const entry of Object.entries(commonKeyParams)) { const [label, [tag, type]] = entry as any if (label === '0') { diff --git a/src/cose/key/convertCoseKeyToJsonWebKey.ts b/src/cose/key/convertCoseKeyToJsonWebKey.ts index 58c9ab0..5ffc3f6 100644 --- a/src/cose/key/convertCoseKeyToJsonWebKey.ts +++ b/src/cose/key/convertCoseKeyToJsonWebKey.ts @@ -1,51 +1,49 @@ -import { base64url, calculateJwkThumbprint } from "jose"; -import { CoseKey } from "."; - +import { base64url } from "jose"; import { formatJwk } from "./formatJwk"; - -import { EC2, Key, KeyTypes } from "../Params"; - -import * as iana2 from '../../iana/assignments/cose' - +import { labels_to_ec2_params, ec2, label_to_key_type, any_cose_key, label_to_curve } from '../../iana/assignments/cose' import { labels_to_algorithms } from "../../iana/requested/cose"; -export const convertCoseKeyToJsonWebKey = async (coseKey: CoseKey): Promise => { - // todo refactor... - const kty = coseKey.get(Key.Kty) as number - // kty EC2 - if (![KeyTypes.EC2].includes(kty)) { - throw new Error('This library requires does not support the given key type') - } - const kid = coseKey.get(Key.Kid) - const algLabel = coseKey.get(Key.Alg) - const crv = coseKey.get(EC2.Crv) - const algName = labels_to_algorithms.get(algLabel as number) - const crv2 = iana2.label_to_curve.get(crv as number) - if (!crv2) { - throw new Error('This library requires does not support the given curve') - } - const jwk = { - kty: 'EC', - alg: algName, - crv: crv2 - } as any - const x = coseKey.get(EC2.X) as any - const y = coseKey.get(EC2.Y) as any - const d = coseKey.get(EC2.D) as any - if (x) { - jwk.x = base64url.encode(x) - } - if (y) { - jwk.y = base64url.encode(y) - } - if (d) { - jwk.d = base64url.encode(d) +export const convertCoseKeyToJsonWebKey = async (key: any_cose_key): Promise => { + + const jwk = {} as Record + const ktyLabel = key.get(ec2.kty) + const kty = labels_to_ec2_params.get(ktyLabel) + if (!kty) { + throw new Error('Unknown cose key type: ' + ktyLabel) } - // TODO check lengths for x, y, d - if (kid && typeof kid === 'string') { - jwk.kid = kid - } else { - jwk.kid = await calculateJwkThumbprint(jwk) + for (const [label, value] of key.entries()) { + switch (label) { + case ec2.kty: { + const key = labels_to_ec2_params.get(label) + jwk[`${key}`] = label_to_key_type.get(value) + break + } + case ec2.kid: { + const key = labels_to_ec2_params.get(label) + jwk[`${key}`] = value + break + } + case ec2.alg: { + const key = labels_to_ec2_params.get(label) + jwk[`${key}`] = labels_to_algorithms.get(value) + break + } + case ec2.crv: { + const key = labels_to_ec2_params.get(label) + jwk[`${key}`] = label_to_curve.get(value) + break + } + case ec2.x: + case ec2.y: + case ec2.d: { + const key = labels_to_ec2_params.get(label) + jwk[`${key}`] = base64url.encode(value) + break + } + default: { + jwk[label] = value + } + } } return formatJwk(jwk) as T } \ No newline at end of file diff --git a/src/cose/key/convertJsonWebKeyToCoseKey.ts b/src/cose/key/convertJsonWebKeyToCoseKey.ts index 425f93e..f77e920 100644 --- a/src/cose/key/convertJsonWebKeyToCoseKey.ts +++ b/src/cose/key/convertJsonWebKeyToCoseKey.ts @@ -1,15 +1,15 @@ import { base64url } from 'jose' - import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' - import { algorithms_to_labels } from '../../iana/requested/cose' -export const convertJsonWebKeyToCoseKey = async (jwk: any): Promise => { +export const convertJsonWebKeyToCoseKey = async (jwk: Record): Promise => { const coseKey = new Map(); const { kty } = jwk for (const [key, value] of Object.entries(jwk)) { switch (kty) { + // we should be used iana namespace here + // instead of magic strings case 'EC': { switch (key) { case 'kty': { @@ -50,5 +50,3 @@ export const convertJsonWebKeyToCoseKey = async (jwk: any): Promise => { return coseKey as T } -// coseKey.set(label, Buffer.from(base64url.decode(value as string))) - diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts index ac50464..2d5d229 100644 --- a/src/cose/key/generate.ts +++ b/src/cose/key/generate.ts @@ -30,10 +30,10 @@ export const generate = async (alg: CoseSignatureAlgorithms, contentType: Pri } if (contentType === 'application/cose-key') { delete privateKeyJwk.kid; - const secretKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) - const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(secretKeyCoseKey) - secretKeyCoseKey.set(Key.Kid, coseKeyThumbprint) - return secretKeyCoseKey as T + const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) + const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(privateKeyCoseKey) + privateKeyCoseKey.set(Key.Kid, coseKeyThumbprint) + return privateKeyCoseKey as T } throw new Error('Unsupported content type.') } diff --git a/src/cose/key/publicFromPrivate.ts b/src/cose/key/publicFromPrivate.ts index a3eae73..59269ca 100644 --- a/src/cose/key/publicFromPrivate.ts +++ b/src/cose/key/publicFromPrivate.ts @@ -1,8 +1,8 @@ -import { CoseKey } from "."; + +import { any_cose_key, ec2_key } from "../../iana/assignments/cose"; import { EC2, Key, KeyTypes } from "../Params"; import { PrivateKeyJwk } from "../sign1"; - export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => { if (privateKeyJwk.kty !== 'EC') { throw new Error('Only EC keys are supported') @@ -12,8 +12,8 @@ export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => { return publicKeyJwk } -export const extractPublicCoseKey = (secretKey: CoseKey) => { - const publicCoseKeyMap = new Map(secretKey) +export const extractPublicCoseKey = (privateKey: any_cose_key) => { + const publicCoseKeyMap = new Map(privateKey) if (publicCoseKeyMap.get(Key.Kty) !== KeyTypes.EC2) { throw new Error('Only EC2 keys are supported') } @@ -21,12 +21,12 @@ export const extractPublicCoseKey = (secretKey: CoseKey) => { throw new Error('privateKey is not a secret / private key (has no d / -4)') } publicCoseKeyMap.delete(EC2.D); - return publicCoseKeyMap + return publicCoseKeyMap as T } -export const publicFromPrivate = (secretKey: PrivateKeyJwk | CoseKey) => { - if ((secretKey as PrivateKeyJwk).kty) { - return extractPublicKeyJwk(secretKey as PrivateKeyJwk) as T +export const publicFromPrivate = (privateKey: PrivateKeyJwk | any_cose_key) => { + if ((privateKey as PrivateKeyJwk).kty) { + return extractPublicKeyJwk(privateKey as PrivateKeyJwk) as T } - return extractPublicCoseKey(secretKey as CoseKey) as T + return extractPublicCoseKey(privateKey as any_cose_key) as T } \ No newline at end of file diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index 45684f2..7be26d2 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -1408,7 +1408,7 @@ export enum walnutdsa { } -export type any_cose_key = { +export type any_cose_key = Map & { get(k: cose_key.kid): Buffer get(k: cose_key.alg): string | number get(k: cose_key.key_ops): [ (string|number)] diff --git a/src/index.ts b/src/index.ts index 44190a5..cff4bad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import * as detached from './cose/detached' export * from './cose/sign1' export * from './x509' + export * from './cose/Params' // https://github.com/dajiaji/hpke-js/issues/302 @@ -19,6 +20,9 @@ export * from './cose/Params' // a better fix would be to move hpke stuff to its won package. // export * from './cose/encrypt' +export * from './iana/assignments/cose' +export * from './iana/requested/cose' + import * as cbor from './cbor' import * as receipt from './cose/receipt' import * as crypto from './crypto' diff --git a/tests/edn.test.ts b/tests/edn.test.ts index 65b1c41..e2afeea 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -17,5 +17,3 @@ it('detached payload cose sign1', async () => { // expect(diag).toBe(output.toString()) }) - -// Move this to transmute/cose \ No newline at end of file diff --git a/tests/fully-specified.test.ts b/tests/fully-specified.test.ts index 6b00f0c..3ca3161 100644 --- a/tests/fully-specified.test.ts +++ b/tests/fully-specified.test.ts @@ -4,8 +4,8 @@ import { CoseSignatureAlgorithms } from '../src/cose/key' const message = '💣 test ✨ mesage 🔥' -const helpTestSignAndVerify = async (privateKey: cose.key.CoseKey) => { - const publicKey = await cose.key.extractPublicCoseKey(privateKey) +const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { + const publicKey = await cose.key.extractPublicCoseKey(privateKey) expect(new TextDecoder().decode(await cose.attached .verifier({ resolver: { @@ -39,7 +39,7 @@ const algorithms = [ algorithms.forEach((alg) => { it(alg, async () => { - const privateKey = await cose.key.generate(alg, 'application/cose-key') + const privateKey = await cose.key.generate(alg, 'application/cose-key') await helpTestSignAndVerify(privateKey) }) }) diff --git a/tests/key.test.ts b/tests/key.test.ts index 3fec1a9..38a6a2e 100644 --- a/tests/key.test.ts +++ b/tests/key.test.ts @@ -1,27 +1,26 @@ import { base64url } from 'jose' -import * as transmute from '../src' +import * as cose from '../src' -it('generate cose key', async () => { - const secretKeyJwk1 = await transmute.key.generate('ES256', 'application/jwk+json') - const secretKeyCose1 = await transmute.key.convertJsonWebKeyToCoseKey(secretKeyJwk1) - expect(secretKeyCose1.get(transmute.EC2.Crv)).toBe(transmute.Curves.P256) // crv : P-256 - const secretKeyCose2 = await transmute.key.generate('ES256', 'application/cose-key') - expect(secretKeyCose2.get(transmute.EC2.Crv)).toBe(transmute.Curves.P256) // crv : P-256 - const secretKeyJwk2 = await transmute.key.convertCoseKeyToJsonWebKey(secretKeyCose1) - expect(secretKeyJwk2.kid).toBe(secretKeyJwk1.kid) // text identifiers survive key conversion - expect(secretKeyJwk2.alg).toBe(secretKeyJwk1.alg) - expect(secretKeyJwk2.kty).toBe(secretKeyJwk1.kty) - expect(secretKeyJwk2.crv).toBe(secretKeyJwk1.crv) - expect(secretKeyJwk2.x).toBe(secretKeyJwk1.x) - expect(secretKeyJwk2.y).toBe(secretKeyJwk1.y) - expect(secretKeyJwk2.d).toBe(secretKeyJwk1.d) - const secretKeyJwk3 = await transmute.key.convertCoseKeyToJsonWebKey(secretKeyCose1) - const secretKeyCose3 = await transmute.key.convertJsonWebKeyToCoseKey(secretKeyJwk3) - const secretKeyJwk4 = await transmute.key.convertCoseKeyToJsonWebKey(secretKeyCose3) - expect(secretKeyJwk4.kid).toBe(secretKeyJwk3.kid) // text identifiers survive key conversion +it('ES256', async () => { + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + expect(privateKeyJwk.alg).toBe('ES256') +}) + +it('conversion', async () => { + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) + const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) + expect(privateKey.alg).toBe('ES256') +}) +it('thumbprint', async () => { + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) + const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) + expect(privateKey.alg).toBe('ES256') }) + it('generate thumbprints', async () => { const k1 = { kty: 'EC', @@ -32,27 +31,27 @@ it('generate thumbprints', async () => { y: 'YBFDrB8IROK1G_mu5FceqQnEk4CoFbcz6MyhuQWkCTE', d: 'FLvNjn-z8HOvl0eGcH8eBYnxZ4xoEKVvCYIB0ibqkfs' } - const jkt = await transmute.key.thumbprint.calculateJwkThumbprint(k1) - const jktUri = await transmute.key.thumbprint.calculateJwkThumbprintUri(k1) + const jkt = await cose.key.thumbprint.calculateJwkThumbprint(k1) + const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(k1) expect(jktUri).toBe('urn:ietf:params:oauth:jwk-thumbprint:sha-256:6hnb34De4biE17mQd46iSzxMnYPtqy3UaUd22KYZ0xg') expect(jkt).toBe(k1.kid) - const k2 = await transmute.key.convertJsonWebKeyToCoseKey(k1) - const ckt = await transmute.key.thumbprint.calculateCoseKeyThumbprint(k2) - const cktUri = await transmute.key.thumbprint.calculateCoseKeyThumbprintUri(k2) + const k2 = await cose.key.convertJsonWebKeyToCoseKey(k1) + const ckt = await cose.key.thumbprint.calculateCoseKeyThumbprint(k2) + const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(k2) expect(cktUri).toBe('urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJDPXpM4qfsGD_EyGfMa0JZsZNmvYK1lY') const decoded = base64url.decode(cktUri.split(':').pop() as string) expect(Buffer.from(decoded)).toEqual(Buffer.from(ckt)) }) -it('public from private for JWK and cose key', async () => { - const privateKeyJwk = await transmute.key.generate('ES256', 'application/jwk+json') +it('public from private', async () => { + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...expectedPublicKeyJwk } = privateKeyJwk - const publicKeyJwk = transmute.key.publicFromPrivate(privateKeyJwk) + const publicKeyJwk = cose.key.publicFromPrivate(privateKeyJwk) expect(publicKeyJwk).toEqual(expectedPublicKeyJwk) - const secretKeyCose = await transmute.key.generate('ES256', 'application/cose-key') - const expectedPublicKeyCose = new Map(secretKeyCose.entries()) - expectedPublicKeyCose.delete(transmute.EC2.D) - const publicKeyCose = transmute.key.publicFromPrivate(secretKeyCose) + const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') + const expectedPublicKeyCose = new Map(privateKeyCose.entries()) + expectedPublicKeyCose.delete(cose.EC2.D) + const publicKeyCose = cose.key.publicFromPrivate(privateKeyCose) expect(publicKeyCose).toEqual(expectedPublicKeyCose) }) \ No newline at end of file diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 82b0ce4..fd40fec 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -3,9 +3,9 @@ import fs from 'fs' import * as cose from '../src' it('verify multiple receipts', async () => { - const issuerSecretKey = await cose.key.generate('ES256', 'application/cose-key') - const notary1SecretKey = await cose.key.generate('ES256', 'application/cose-key') - const notary2SecretKey = await cose.key.generate('ES256', 'application/cose-key') + const issuerSecretKey = await cose.key.generate('ES256', 'application/cose-key') + const notary1SecretKey = await cose.key.generate('ES256', 'application/cose-key') + const notary2SecretKey = await cose.key.generate('ES256', 'application/cose-key') const issuerSigner = cose.detached.signer({ remote: cose.crypto.signer({ privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(issuerSecretKey) From b9e2612ca2ae9e104ff56c9727bee054c460f7c9 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 08:53:22 -0500 Subject: [PATCH 06/67] factoring wip --- scripts/make-iana-assignments.ts | 10 ++++++++- src/cose/Params.ts | 11 ++------- src/iana/assignments/cose.ts | 38 +++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index 54d8afc..3237d47 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -341,7 +341,7 @@ const createHeaderParamesters = () => { .pipe(csv()) let headerParameterDefinitions = `\n` stream.on('data', (row: any) => { - if (row.Name === 'Unassigned' || row.Name.includes('Reserved')) { + if (row.Name === 'Unassigned' || row.Name.includes('Reserved') || row.Name.includes('delegated')) { return } const valueName = row.Name @@ -370,6 +370,14 @@ export const ${valueName}_algorithm = { labelToHeaderParam.set(row['Label'], row['Name']) }); stream.on('end', () => { + headerParameterDefinitions += `export enum headers {\n` + for (const [label, name] of labelToHeaderParam.entries()) { + let betterName = name.split(' (')[0] + betterName = betterName.replace(/ /g, '_') + betterName = betterName.toLowerCase() + headerParameterDefinitions += ` ${betterName} = ${label},\n` + } + headerParameterDefinitions += `}\n` resolve({ labelToHeaderParam, headerParameterDefinitions }) }); }) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index 637134a..1f02fa0 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -15,13 +15,6 @@ export const UnprotectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } -export const HeaderParameters = { - Alg: 1, - Epk: -1, - Kid: 4, - X5t: 34 -} - export const PartyUIdentity = -21 export const PartyUNonce = -22 export const PartyUOther = -23 @@ -56,7 +49,7 @@ export const VerifiableDataProofTypes = { } export const Protected = { - ...HeaderParameters, + PartyUIdentity, PartyUNonce, PartyUOther, @@ -78,7 +71,7 @@ export const Protected = { export const Unprotected = { - ...HeaderParameters, + Iv: 5, Ek: -4, // new from COSE HPKE diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index 7be26d2..b24e2ca 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -881,15 +881,6 @@ export const iv_generation_algorithm = { } -export const delegated_to_the_cose_header_algorithm_parameters_registry_algorithm = { - 'Name': 'delegated to the COSE Header Algorithm Parameters registry', - 'Label': -65536, - 'Value Type': '', - 'Value Registry': '', - 'Change Controller': 'undefined', - 'Description': "", - 'Reference': '' -} export const alg_algorithm = { 'Name': 'alg', 'Label': 1, @@ -1115,6 +1106,33 @@ export const cuphownerpubkey_algorithm = { 'Description': "Public Key", 'Reference': '[FIDO Device Onboard Specification]' } +export enum headers { + alg = 1, + crit = 2, + content_type = 3, + kid = 4, + iv = 5, + partial_iv = 6, + counter_signature = 7, + countersignature0 = 9, + kid_context = 10, + countersignature_version_2 = 11, + countersignature0_version_2 = 12, + kcwt = 13, + kccs = 14, + cwt_claims = 15, + typ = 16, + c5t = 22, + c5u = 23, + c5b = 24, + c5c = 25, + x5bag = 32, + x5chain = 33, + x5t = 34, + x5u = 35, + cuphnonce = 256, + cuphownerpubkey = 257, +} export const okp_crv_parameter = { 'Key Type': 1, @@ -1480,7 +1498,7 @@ export const labels_to_algorithms = new Map([[-65535,"RS1"],[-65534,"A128CTR"],[ export const algorithms_to_labels = new Map([...labels_to_algorithms.entries()].map((e: any) => e.reverse())) as Map // headers -export const labels_to_headers = new Map([[-65536,"delegated to the COSE Header Algorithm Parameters registry"],[1,"alg"],[2,"crit"],[3,"content type"],[4,"kid"],[5,"IV"],[6,"Partial IV"],[7,"counter signature"],[9,"CounterSignature0"],[10,"kid context"],[11,"Countersignature version 2"],[12,"Countersignature0 version 2"],[13,"kcwt"],[14,"kccs"],[15,"CWT Claims"],[16,"typ (type)"],[22,"c5t"],[23,"c5u"],[24,"c5b"],[25,"c5c"],[32,"x5bag"],[33,"x5chain"],[34,"x5t"],[35,"x5u"],[256,"CUPHNonce"],[257,"CUPHOwnerPubKey"]]) as Map +export const labels_to_headers = new Map([[1,"alg"],[2,"crit"],[3,"content type"],[4,"kid"],[5,"IV"],[6,"Partial IV"],[7,"counter signature"],[9,"CounterSignature0"],[10,"kid context"],[11,"Countersignature version 2"],[12,"Countersignature0 version 2"],[13,"kcwt"],[14,"kccs"],[15,"CWT Claims"],[16,"typ (type)"],[22,"c5t"],[23,"c5u"],[24,"c5b"],[25,"c5c"],[32,"x5bag"],[33,"x5chain"],[34,"x5t"],[35,"x5u"],[256,"CUPHNonce"],[257,"CUPHOwnerPubKey"]]) as Map export const headers_to_labels = new Map([...labels_to_headers.entries()].map((e: any) => e.reverse())) as Map // okp From 83955138ec9bee8b274895ee64552feb20a7238a Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 08:57:16 -0500 Subject: [PATCH 07/67] factoring --- attic/encrypt.aad.test.ts | 4 ++-- attic/encrypt/direct.ts | 6 +++--- attic/encrypt/hpke/direct.ts | 2 +- attic/encrypt/hpke/wrap.ts | 2 +- attic/encrypt/wrap.ts | 8 ++++---- attic/hpke.test.ts | 4 ++-- attic/kdf.context.test.ts | 2 +- scripts/make-iana-assignments.ts | 2 +- src/cose/sign1/verifier.ts | 4 ++-- src/iana/assignments/cose.ts | 2 +- tests/fully-specified.test.ts | 2 +- tests/key.test.ts | 6 +++--- tests/readme.test.ts | 10 +++++----- tests/receipt.test.ts | 8 ++++---- tests/sign1.attached.test.ts | 4 ++-- tests/sign1.detached.test.ts | 4 ++-- tests/signer.test.ts | 2 +- tests/verifiers.test.ts | 14 +++++++------- tests/x509.test.ts | 8 ++++---- 19 files changed, 47 insertions(+), 47 deletions(-) diff --git a/attic/encrypt.aad.test.ts b/attic/encrypt.aad.test.ts index d5d03ae..55e2959 100644 --- a/attic/encrypt.aad.test.ts +++ b/attic/encrypt.aad.test.ts @@ -42,7 +42,7 @@ it('wrap with external aad', async () => { const ciphertext = await encrypt.wrap({ aad, protectedHeader: ProtectedHeader([ - [Protected.Alg, Aead.A128GCM], + [cose.header.alg, Aead.A128GCM], ]), plaintext, recipients: encryptionKeys @@ -60,7 +60,7 @@ it('direct', async () => { const ciphertext = await encrypt.direct({ aad, protectedHeader: ProtectedHeader([ - [Protected.Alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], + [cose.header.alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], ]), plaintext, recipients: encryptionKeys diff --git a/attic/encrypt/direct.ts b/attic/encrypt/direct.ts index 502e586..5a03c40 100644 --- a/attic/encrypt/direct.ts +++ b/attic/encrypt/direct.ts @@ -39,7 +39,7 @@ export const encrypt = async (req: RequestDirectEncryption) => { if (recipientPublicKeyJwk.alg === hpke.primaryAlgorithm.label) { return hpke.encrypt.direct(req) } - const alg = req.protectedHeader.get(Protected.Alg) + const alg = req.protectedHeader.get(cose.header.alg) const protectedHeader = await encodeAsync(req.protectedHeader) const unprotectedHeader = req.unprotectedHeader; const directAgreementAlgorithm = getCoseAlgFromRecipientJwk(recipientPublicKeyJwk) @@ -88,7 +88,7 @@ export const decrypt = async (req: RequestDirectDecryption) => { throw new Error('Expected recipient cipher text length to the be zero') } const decodedRecipientProtectedHeader = decode(recipientProtectedHeader) - const recipientAlgorithm = decodedRecipientProtectedHeader.get(Protected.Alg) + const recipientAlgorithm = decodedRecipientProtectedHeader.get(cose.header.alg) const epk = recipientUnprotectedHeader.get(Unprotected.Epk) // ensure the epk has the algorithm that is set in the protected header epk.set(Epk.Alg, recipientAlgorithm) @@ -98,6 +98,6 @@ export const decrypt = async (req: RequestDirectDecryption) => { const aad = await createAAD(protectedHeader, 'Encrypt', externalAad) const iv = unprotectedHeader.get(Unprotected.Iv) const decodedProtectedHeader = decode(protectedHeader) - const alg = decodedProtectedHeader.get(Protected.Alg) + const alg = decodedProtectedHeader.get(cose.header.alg) return aes.decrypt(alg, ciphertext, new Uint8Array(iv), new Uint8Array(aad), new Uint8Array(cek)) } \ No newline at end of file diff --git a/attic/encrypt/hpke/direct.ts b/attic/encrypt/hpke/direct.ts index f552be3..dc7e9da 100644 --- a/attic/encrypt/hpke/direct.ts +++ b/attic/encrypt/hpke/direct.ts @@ -13,7 +13,7 @@ export const encryptDirect = async (req: RequestDirectEncryption) => { if (req.unprotectedHeader === undefined) { req.unprotectedHeader = UnprotectedHeader([]) } - const alg = req.protectedHeader.get(Protected.Alg) + const alg = req.protectedHeader.get(cose.header.alg) if (alg !== Direct['HPKE-Base-P256-SHA256-AES128GCM']) { throw new Error('Only alg 35 is supported') } diff --git a/attic/encrypt/hpke/wrap.ts b/attic/encrypt/hpke/wrap.ts index 181a3af..12763e2 100644 --- a/attic/encrypt/hpke/wrap.ts +++ b/attic/encrypt/hpke/wrap.ts @@ -106,6 +106,6 @@ export const decryptWrap = async (req: RequestWrapDecryption) => { const externalAad = req.aad ? toArrayBuffer(req.aad) : EMPTY_BUFFER const aad = await createAAD(protectedHeader, 'Encrypt', externalAad) const decodedProtectedHeader = await decodeFirst(protectedHeader) - const alg = decodedProtectedHeader.get(Protected.Alg) + const alg = decodedProtectedHeader.get(cose.header.alg) return aes.decrypt(alg, ciphertext, new Uint8Array(iv), new Uint8Array(aad), new Uint8Array(contentEncryptionKey)) } diff --git a/attic/encrypt/wrap.ts b/attic/encrypt/wrap.ts index 1a1a260..439bb20 100644 --- a/attic/encrypt/wrap.ts +++ b/attic/encrypt/wrap.ts @@ -32,7 +32,7 @@ export const decrypt = async (req: RequestWrapDecryption) => { throw new Error('Only tag 96 cose encrypt are supported') } const decodedRecipientProtectedHeader = await decodeFirst(recipientProtectedHeader) - const recipientAlgorithm = decodedRecipientProtectedHeader.get(Protected.Alg) + const recipientAlgorithm = decodedRecipientProtectedHeader.get(cose.header.alg) const epk = recipientUnprotectedHeader.get(Unprotected.Epk) // ensure the epk has the algorithm that is set in the protected header epk.set(Epk.Alg, recipientAlgorithm) @@ -47,7 +47,7 @@ export const decrypt = async (req: RequestWrapDecryption) => { } const cek = await aes.unwrap(kwAlg, recipientCipherText, new Uint8Array(kek)) const decodedProtectedHeader = await decodeFirst(protectedHeader) - const alg = decodedProtectedHeader.get(Protected.Alg) + const alg = decodedProtectedHeader.get(cose.header.alg) return aes.decrypt(alg, ciphertext, new Uint8Array(iv), new Uint8Array(aad), new Uint8Array(cek)) } @@ -75,7 +75,7 @@ export const encrypt = async (req: RequestWrapEncryption) => { if (recipientPublicKeyJwk.alg === 'HPKE-Base-P256-SHA256-AES128GCM') { return hpke.encrypt.wrap(req) } - const alg = req.protectedHeader.get(Protected.Alg) + const alg = req.protectedHeader.get(cose.header.alg) if (alg !== Aead.A128GCM) { throw new Error('Only A128GCM is supported currently') } @@ -83,7 +83,7 @@ export const encrypt = async (req: RequestWrapEncryption) => { const unprotectedHeader = req.unprotectedHeader; const keyAgreementWithKeyWrappingAlgorithm = getCoseAlgFromRecipientJwk(recipientPublicKeyJwk) const recipientProtectedHeader = await encodeAsync(ProtectedHeader([ - [Protected.Alg, KeyAgreementWithKeyWrap["ECDH-ES+A128KW"]], + [cose.header.alg, KeyAgreementWithKeyWrap["ECDH-ES+A128KW"]], ])) const senderPrivateKeyJwk = await generate('ES256', "application/jwk+json") const kek = await ecdh.deriveKey(protectedHeader, recipientProtectedHeader, recipientPublicKeyJwk, senderPrivateKeyJwk) diff --git a/attic/hpke.test.ts b/attic/hpke.test.ts index 04004c9..200f1ad 100644 --- a/attic/hpke.test.ts +++ b/attic/hpke.test.ts @@ -41,7 +41,7 @@ const decryptionKeys = { it('wrap', async () => { const ciphertext = await encrypt.wrap({ protectedHeader: ProtectedHeader([ - [Protected.Alg, Aead.A128GCM], + [cose.header.alg, Aead.A128GCM], ]), unprotectedHeader: UnprotectedHeader([]), plaintext, @@ -60,7 +60,7 @@ it('wrap', async () => { it('direct', async () => { const ciphertext = await encrypt.direct({ protectedHeader: ProtectedHeader([ - [Protected.Alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], + [cose.header.alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], ]), unprotectedHeader: UnprotectedHeader([]), plaintext, diff --git a/attic/kdf.context.test.ts b/attic/kdf.context.test.ts index 0ffb3fa..a32a56c 100644 --- a/attic/kdf.context.test.ts +++ b/attic/kdf.context.test.ts @@ -41,7 +41,7 @@ const decryptionKeys = { it('direct with party info', async () => { const ciphertext = await encrypt.direct({ protectedHeader: ProtectedHeader([ - [Protected.Alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], + [cose.header.alg, Direct['HPKE-Base-P256-SHA256-AES128GCM']], [Protected.PartyUIdentity, Buffer.from(new TextEncoder().encode('did:example:party-u'))], [Protected.PartyVIdentity, Buffer.from(new TextEncoder().encode('did:example:party-v'))] ]), diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index 3237d47..dad1466 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -370,7 +370,7 @@ export const ${valueName}_algorithm = { labelToHeaderParam.set(row['Label'], row['Name']) }); stream.on('end', () => { - headerParameterDefinitions += `export enum headers {\n` + headerParameterDefinitions += `export enum header {\n` for (const [label, name] of labelToHeaderParam.entries()) { let betterName = name.split(' (')[0] betterName = betterName.replace(/ /g, '_') diff --git a/src/cose/sign1/verifier.ts b/src/cose/sign1/verifier.ts index df09109..28c0a51 100644 --- a/src/cose/sign1/verifier.ts +++ b/src/cose/sign1/verifier.ts @@ -6,8 +6,8 @@ import { DecodedToBeSigned, ProtectedHeaderMap } from './types' import rawVerifier from '../../crypto/verifier' -import { Protected } from '../Params' +import * as cose from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' const verifier = ({ resolver }: RequestCoseSign1Verifier) => { @@ -27,7 +27,7 @@ const verifier = ({ resolver }: RequestCoseSign1Verifier) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [protectedHeaderBytes, _, payload, signature] = signatureStructure; const protectedHeaderMap: ProtectedHeaderMap = (!protectedHeaderBytes.length) ? new Map() : decodeFirstSync(protectedHeaderBytes); - const algInHeader = protectedHeaderMap.get(Protected.Alg) + const algInHeader = protectedHeaderMap.get(cose.header.alg) if (algInHeader !== algInPublicKey) { throw new Error('Verification key does not support algorithm: ' + algInHeader); } diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index b24e2ca..12c9d9e 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -1106,7 +1106,7 @@ export const cuphownerpubkey_algorithm = { 'Description': "Public Key", 'Reference': '[FIDO Device Onboard Specification]' } -export enum headers { +export enum header { alg = 1, crit = 2, content_type = 3, diff --git a/tests/fully-specified.test.ts b/tests/fully-specified.test.ts index 3ca3161..11f0944 100644 --- a/tests/fully-specified.test.ts +++ b/tests/fully-specified.test.ts @@ -23,7 +23,7 @@ const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { }) .sign({ protectedHeader: new Map([ - [cose.Protected.Alg, privateKey.get(cose.Key.Alg)] + [cose.header.alg, privateKey.get(cose.Key.Alg)] ]), payload: new TextEncoder().encode(message) }) diff --git a/tests/key.test.ts b/tests/key.test.ts index 38a6a2e..f2897b7 100644 --- a/tests/key.test.ts +++ b/tests/key.test.ts @@ -35,7 +35,7 @@ it('generate thumbprints', async () => { const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(k1) expect(jktUri).toBe('urn:ietf:params:oauth:jwk-thumbprint:sha-256:6hnb34De4biE17mQd46iSzxMnYPtqy3UaUd22KYZ0xg') expect(jkt).toBe(k1.kid) - const k2 = await cose.key.convertJsonWebKeyToCoseKey(k1) + const k2 = await cose.key.convertJsonWebKeyToCoseKey(k1) const ckt = await cose.key.thumbprint.calculateCoseKeyThumbprint(k2) const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(k2) expect(cktUri).toBe('urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJDPXpM4qfsGD_EyGfMa0JZsZNmvYK1lY') @@ -49,9 +49,9 @@ it('public from private', async () => { const { d, ...expectedPublicKeyJwk } = privateKeyJwk const publicKeyJwk = cose.key.publicFromPrivate(privateKeyJwk) expect(publicKeyJwk).toEqual(expectedPublicKeyJwk) - const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') + const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') const expectedPublicKeyCose = new Map(privateKeyCose.entries()) expectedPublicKeyCose.delete(cose.EC2.D) - const publicKeyCose = cose.key.publicFromPrivate(privateKeyCose) + const publicKeyCose = cose.key.publicFromPrivate(privateKeyCose) expect(publicKeyCose).toEqual(expectedPublicKeyCose) }) \ No newline at end of file diff --git a/tests/readme.test.ts b/tests/readme.test.ts index 69e95c7..786b210 100644 --- a/tests/readme.test.ts +++ b/tests/readme.test.ts @@ -21,9 +21,9 @@ it('readme', async () => { const content = fs.readFileSync('./examples/image.png') const signatureForImage = await issuer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // signing algorithm ES256 + [cose.header.alg, cose.Signature.ES256], // signing algorithm ES256 [cose.Protected.ContentType, "image/png"], // content type image/png - [cose.Protected.Kid, issuerPublicKeyJwk.kid] // issuer key identifier + [cose.header.kid, issuerPublicKeyJwk.kid] // issuer key identifier ]), payload: content }) @@ -32,9 +32,9 @@ it('readme', async () => { ] const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], + [cose.header.alg, cose.Signature.ES256], [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']], - [cose.Protected.Kid, notaryPublicKeyJwk.kid] + [cose.header.kid, notaryPublicKeyJwk.kid] ]), entry: 0, entries: transparencyLogContainingImageSignatures, @@ -48,7 +48,7 @@ it('readme', async () => { } const [protectedHeaderBytes] = value; const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) - const kid = protectedHeaderMap.get(cose.Protected.Kid); + const kid = protectedHeaderMap.get(cose.header.kid); if (kid === issuerPublicKeyJwk.kid) { return issuerPublicKeyJwk } diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 6e93333..363d2d9 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -34,7 +34,7 @@ it('issue & verify', async () => { }) const inclusion = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 1, @@ -54,7 +54,7 @@ it('issue & verify', async () => { // based on a previous receipt const { root, receipt } = await cose.receipt.consistency.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), receipt: inclusion, @@ -81,7 +81,7 @@ it("add / remove from receipts", async () => { const content = fs.readFileSync('./examples/image.png') const signatureForImage = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content @@ -90,7 +90,7 @@ it("add / remove from receipts", async () => { // inclusion proof receipt for image signature const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index 4ac94a9..253e7a0 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -12,7 +12,7 @@ it('sign and verify', async () => { }) const message = '💣 test ✨ mesage 🔥' const coseSign1 = await signer.sign({ - protectedHeader: new Map([[cose.Protected.Alg, cose.Signature.ES256]]), + protectedHeader: new Map([[cose.header.alg, cose.Signature.ES256]]), unprotectedHeader: new Map(), payload: new TextEncoder().encode(message) }) @@ -45,7 +45,7 @@ it('sign and verify large image from file system', async () => { const coseSign1 = await signer.sign({ protectedHeader: new Map([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), unprotectedHeader: new Map(), diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index ae23b1c..c98eb6d 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -14,7 +14,7 @@ it('sign and verify', async () => { const payload = new TextEncoder().encode(message) const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 ]), payload }) @@ -48,7 +48,7 @@ it('sign and verify large image from file system', async () => { const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 1a63c37..fc1b427 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -15,7 +15,7 @@ it('sign and verify large image from file system', async () => { const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index fd40fec..c93e6dc 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -30,8 +30,8 @@ it('verify multiple receipts', async () => { const content = fs.readFileSync('./examples/image.png') const signatureForImage = await issuerSigner.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Kid, issuerCkt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.kid, issuerCkt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content @@ -40,8 +40,8 @@ it('verify multiple receipts', async () => { // inclusion proof receipt for image signature const receiptForImageSignature1 = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Kid, notary1Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.kid, notary1Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, @@ -50,8 +50,8 @@ it('verify multiple receipts', async () => { }) const receiptForImageSignature2 = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Kid, notary2Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 + [cose.header.kid, notary2Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... + [cose.header.alg, cose.Signature.ES256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, @@ -67,7 +67,7 @@ it('verify multiple receipts', async () => { } const [protectedHeaderBytes] = value; const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) - const kid = protectedHeaderMap.get(cose.Protected.Kid); + const kid = protectedHeaderMap.get(cose.header.kid); if (kid === issuerCkt) { return cose.key.convertCoseKeyToJsonWebKey( await cose.key.publicFromPrivate(issuerSecretKey) diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 46990c0..8518dff 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -25,8 +25,8 @@ it('sign and verify with x5t and key resolver', async () => { const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // alg ES256 - [cose.Protected.X5t, rootCertificateThumbprint], // xt5 thumbprint + [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.x5t, rootCertificateThumbprint], // xt5 thumbprint [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content @@ -38,8 +38,8 @@ it('sign and verify with x5t and key resolver', async () => { } const [protectedHeaderBytes] = value; const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) - const alg = protectedHeaderMap.get(cose.Protected.Alg) - const x5t = protectedHeaderMap.get(cose.Protected.X5t) // get x5t + const alg = protectedHeaderMap.get(cose.header.alg) + const x5t = protectedHeaderMap.get(cose.header.x5t) // get x5t if (!x5t) { throw new Error('x5t is required in protected header to use the certificate verifer exposed by this library') } From f916aa89a9e7304d17efc6425895869d7872cb5a Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:04:16 -0500 Subject: [PATCH 08/67] factoring --- src/cose/Params.ts | 10 ---------- src/cose/key/generate.ts | 4 ++-- src/cose/key/publicFromPrivate.ts | 18 ++++++++++-------- src/cose/key/thumbprint.ts | 11 +++++------ tests/key.test.ts | 2 +- tests/receipt.test.ts | 4 ++-- 6 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index 1f02fa0..221b1d6 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -131,17 +131,7 @@ export const KeyTypes = { EC2: 2 } -export const EC2 = { - ...Key, - Crv: -1, - X: -2, - Y: -3, - D: -4 -} -export const Curves = { - P256: 1, -} export const COSE_Encrypt0 = 16 export const COSE_Sign1 = 18 diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts index 2d5d229..dd0f2a8 100644 --- a/src/cose/key/generate.ts +++ b/src/cose/key/generate.ts @@ -2,7 +2,6 @@ import { generateKeyPair, exportJWK, calculateJwkThumbprint } from "jose" -import { CoseKey } from '.' export type CoseKeyAgreementAlgorithms = 'ECDH-ES+A128KW' export type CoseSignatureAlgorithms = 'ES256' | 'ES384' | 'ES512' | 'ESP256' | 'ESP384' @@ -15,6 +14,7 @@ import { thumbprint } from "./thumbprint" import { formatJwk } from './formatJwk' import { Key } from "../Params" import { less_specified } from "../../iana/requested/cose" +import { any_cose_key } from "../../iana/assignments/cose" export const generate = async (alg: CoseSignatureAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise => { const cryptoKeyPair = await generateKeyPair( @@ -30,7 +30,7 @@ export const generate = async (alg: CoseSignatureAlgorithms, contentType: Pri } if (contentType === 'application/cose-key') { delete privateKeyJwk.kid; - const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) + const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(privateKeyCoseKey) privateKeyCoseKey.set(Key.Kid, coseKeyThumbprint) return privateKeyCoseKey as T diff --git a/src/cose/key/publicFromPrivate.ts b/src/cose/key/publicFromPrivate.ts index 59269ca..8ed5734 100644 --- a/src/cose/key/publicFromPrivate.ts +++ b/src/cose/key/publicFromPrivate.ts @@ -1,8 +1,10 @@ -import { any_cose_key, ec2_key } from "../../iana/assignments/cose"; -import { EC2, Key, KeyTypes } from "../Params"; +import * as cose from "../../iana/assignments/cose"; + import { PrivateKeyJwk } from "../sign1"; + + export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => { if (privateKeyJwk.kty !== 'EC') { throw new Error('Only EC keys are supported') @@ -12,21 +14,21 @@ export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => { return publicKeyJwk } -export const extractPublicCoseKey = (privateKey: any_cose_key) => { +export const extractPublicCoseKey = (privateKey: cose.any_cose_key) => { const publicCoseKeyMap = new Map(privateKey) - if (publicCoseKeyMap.get(Key.Kty) !== KeyTypes.EC2) { + if (publicCoseKeyMap.get(cose.cose_key.kty) !== cose.cose_key_type.ec2) { throw new Error('Only EC2 keys are supported') } - if (!publicCoseKeyMap.get(EC2.D)) { + if (!publicCoseKeyMap.get(cose.ec2.d)) { throw new Error('privateKey is not a secret / private key (has no d / -4)') } - publicCoseKeyMap.delete(EC2.D); + publicCoseKeyMap.delete(cose.ec2.d); return publicCoseKeyMap as T } -export const publicFromPrivate = (privateKey: PrivateKeyJwk | any_cose_key) => { +export const publicFromPrivate = (privateKey: PrivateKeyJwk | cose.any_cose_key) => { if ((privateKey as PrivateKeyJwk).kty) { return extractPublicKeyJwk(privateKey as PrivateKeyJwk) as T } - return extractPublicCoseKey(privateKey as any_cose_key) as T + return extractPublicCoseKey(privateKey as cose.any_cose_key) as T } \ No newline at end of file diff --git a/src/cose/key/thumbprint.ts b/src/cose/key/thumbprint.ts index c68b6b0..490d257 100644 --- a/src/cose/key/thumbprint.ts +++ b/src/cose/key/thumbprint.ts @@ -3,16 +3,15 @@ import { calculateJwkThumbprint, calculateJwkThumbprintUri, base64url } from "jo import { encodeCanonical } from "../../cbor"; import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; -import { EC2, Key, KeyTypes } from "../Params"; -import { CoseKey } from "."; +import * as cose from '../../iana/assignments/cose' // https://www.ietf.org/archive/id/draft-ietf-cose-key-thumbprint-01.html#section-6 -const calculateCoseKeyThumbprint = async (coseKey: CoseKey): Promise => { - if (coseKey.get(Key.Kty) !== KeyTypes.EC2) { +const calculateCoseKeyThumbprint = async (coseKey: cose.any_cose_key): Promise => { + if (coseKey.get(cose.cose_key.kty) !== cose.cose_key_type.ec2) { throw new Error('Unsupported key type (Only EC2 are supported') } const onlyRequiredMap = new Map() - const requiredKeys = [EC2.Kty, EC2.Crv, EC2.X, EC2.Y] + const requiredKeys = [cose.ec2.kty, cose.ec2.crv, cose.ec2.x, cose.ec2.y] for (const [key, value] of coseKey.entries()) { if (requiredKeys.includes(key as number)) { onlyRequiredMap.set(key, value) @@ -24,7 +23,7 @@ const calculateCoseKeyThumbprint = async (coseKey: CoseKey): Promise => { +const calculateCoseKeyThumbprintUri = async (coseKey: cose.any_cose_key): Promise => { const prefix = `urn:ietf:params:oauth:ckt:sha-256` const digest = await calculateCoseKeyThumbprint(coseKey) return `${prefix}:${base64url.encode(new Uint8Array(digest))}` diff --git a/tests/key.test.ts b/tests/key.test.ts index f2897b7..043c2a1 100644 --- a/tests/key.test.ts +++ b/tests/key.test.ts @@ -51,7 +51,7 @@ it('public from private', async () => { expect(publicKeyJwk).toEqual(expectedPublicKeyJwk) const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') const expectedPublicKeyCose = new Map(privateKeyCose.entries()) - expectedPublicKeyCose.delete(cose.EC2.D) + expectedPublicKeyCose.delete(cose.ec2.d) const publicKeyCose = cose.key.publicFromPrivate(privateKeyCose) expect(publicKeyCose).toEqual(expectedPublicKeyCose) }) \ No newline at end of file diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 363d2d9..2d0f4f4 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -102,8 +102,8 @@ it("add / remove from receipts", async () => { expect(value[1].get(cose.Unprotected.Receipts).length).toBe(1) // expect 1 receipt const receipts = await cose.receipt.get(transparentSignature) expect(receipts.length).toBe(1) // expect 1 receipt - const coseKey = await cose.key.convertJsonWebKeyToCoseKey(publicKeyJwk) - coseKey.set(cose.EC2.Kid, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey)) + const coseKey = await cose.key.convertJsonWebKeyToCoseKey(publicKeyJwk) + coseKey.set(cose.ec2.kid, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey)) const publicKey = cose.key.serialize(coseKey) expect(publicKey).toBeDefined(); // fs.writeFileSync('./examples/image.ckt.signature.cbor', Buffer.from(transparentSignature)) From ef1bc945fc8120009aa50340eaf8abe43abdfa92 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:05:58 -0500 Subject: [PATCH 09/67] factoring --- src/cose/Params.ts | 15 --------------- src/cose/key/generate.ts | 8 ++++---- tests/fully-specified.test.ts | 2 +- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index 221b1d6..71eff70 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -117,21 +117,6 @@ export const KeyType = 1 export const KeyAlg = 3 export const KeyId = 2 -export const Key = { - Kty: KeyType, - Alg: KeyAlg, - Kid: KeyId -} - -export const Epk = { - ...Key -} - -export const KeyTypes = { - EC2: 2 -} - - export const COSE_Encrypt0 = 16 export const COSE_Sign1 = 18 diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts index dd0f2a8..123d7d5 100644 --- a/src/cose/key/generate.ts +++ b/src/cose/key/generate.ts @@ -12,9 +12,9 @@ export type PrivateKeyContentType = ContentTypeOfCoseKey | ContentTypeOfJsonWebK import { convertJsonWebKeyToCoseKey } from './convertJsonWebKeyToCoseKey' import { thumbprint } from "./thumbprint" import { formatJwk } from './formatJwk' -import { Key } from "../Params" + import { less_specified } from "../../iana/requested/cose" -import { any_cose_key } from "../../iana/assignments/cose" +import * as cose from "../../iana/assignments/cose" export const generate = async (alg: CoseSignatureAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise => { const cryptoKeyPair = await generateKeyPair( @@ -30,9 +30,9 @@ export const generate = async (alg: CoseSignatureAlgorithms, contentType: Pri } if (contentType === 'application/cose-key') { delete privateKeyJwk.kid; - const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) + const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(privateKeyCoseKey) - privateKeyCoseKey.set(Key.Kid, coseKeyThumbprint) + privateKeyCoseKey.set(cose.ec2.kid, coseKeyThumbprint) return privateKeyCoseKey as T } throw new Error('Unsupported content type.') diff --git a/tests/fully-specified.test.ts b/tests/fully-specified.test.ts index 11f0944..654d086 100644 --- a/tests/fully-specified.test.ts +++ b/tests/fully-specified.test.ts @@ -23,7 +23,7 @@ const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { }) .sign({ protectedHeader: new Map([ - [cose.header.alg, privateKey.get(cose.Key.Alg)] + [cose.header.alg, privateKey.get(cose.cose_key.alg)] ]), payload: new TextEncoder().encode(message) }) From 9f61383baf32e3de755aa79cb0330491fd3e1dfc Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:11:25 -0500 Subject: [PATCH 10/67] factoring --- scripts/make-iana-assignments.ts | 12 ++++++ src/cose/Params.ts | 24 ----------- src/iana/assignments/cose.ts | 72 ++++++++++++++++++++++++++++++++ tests/readme.test.ts | 4 +- tests/receipt.test.ts | 8 ++-- tests/sign1.attached.test.ts | 4 +- tests/sign1.detached.test.ts | 4 +- tests/signer.test.ts | 2 +- tests/verifiers.test.ts | 6 +-- tests/x509.test.ts | 4 +- 10 files changed, 100 insertions(+), 40 deletions(-) diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index dad1466..e260814 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -328,6 +328,18 @@ export const ${valueName}_algorithm = { labelToAlgorithm.set(row['Value'], row['Name']) }); stream.on('end', () => { + algorithmDefinitions += `export enum algorithm {\n` + for (const [label, name] of labelToAlgorithm.entries()) { + let betterName = name.split(' (')[0] + betterName = betterName + .replace(/ /g, '_') + .replace(/-/g, '_') + .replace(/\+/g, '_') + .replace(/\//g, '_') + betterName = betterName.toLowerCase() + algorithmDefinitions += ` ${betterName} = ${label},\n` + } + algorithmDefinitions += `}\n` resolve({ labelToAlgorithm, algorithmDefinitions }) }); }) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index 71eff70..f77341f 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -90,32 +90,8 @@ export const Hash = { 'SHA256': -16 } -export const Signature = { - 'ES256': -7, - 'ES384': -35 -} - - -export const KeyAgreement = { - 'ECDH-ES+HKDF-256': -25 -} -export const KeyAgreementWithKeyWrap = { - 'ECDH-ES+A128KW': -29 -} - -export const KeyWrap = { - A128KW: -3 -} - -export const Direct = { - 'HPKE-Base-P256-SHA256-AES128GCM': 35 -} - -export const KeyType = 1 -export const KeyAlg = 3 -export const KeyId = 2 export const COSE_Encrypt0 = 16 diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index 12c9d9e..b1b951d 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -879,6 +879,78 @@ export const iv_generation_algorithm = { 'Reference': 'https://datatracker.ietf.org/doc/RFC9053', 'Recommended': 'No' } +export enum algorithm { + rs1 = -65535, + a128ctr = -65534, + a192ctr = -65533, + a256ctr = -65532, + a128cbc = -65531, + a192cbc = -65530, + a256cbc = -65529, + walnutdsa = -260, + rs512 = -259, + rs384 = -258, + rs256 = -257, + es256k = -47, + hss_lms = -46, + shake256 = -45, + sha_512 = -44, + sha_384 = -43, + rsaes_oaep_w__sha_512 = -42, + rsaes_oaep_w__sha_256 = -41, + rsaes_oaep_w__rfc_8017_default_parameters = -40, + ps512 = -39, + ps384 = -38, + ps256 = -37, + es512 = -36, + es384 = -35, + ecdh_ss___a256kw = -34, + ecdh_ss___a192kw = -33, + ecdh_ss___a128kw = -32, + ecdh_es___a256kw = -31, + ecdh_es___a192kw = -30, + ecdh_es___a128kw = -29, + ecdh_ss___hkdf_512 = -28, + ecdh_ss___hkdf_256 = -27, + ecdh_es___hkdf_512 = -26, + ecdh_es___hkdf_256 = -25, + shake128 = -18, + sha_512_256 = -17, + sha_256 = -16, + sha_256_64 = -15, + sha_1 = -14, + direct_hkdf_aes_256 = -13, + direct_hkdf_aes_128 = -12, + direct_hkdf_sha_512 = -11, + direct_hkdf_sha_256 = -10, + eddsa = -8, + es256 = -7, + direct = -6, + a256kw = -5, + a192kw = -4, + a128kw = -3, + a128gcm = 1, + a192gcm = 2, + a256gcm = 3, + hmac_256_64 = 4, + hmac_256_256 = 5, + hmac_384_384 = 6, + hmac_512_512 = 7, + aes_ccm_16_64_128 = 10, + aes_ccm_16_64_256 = 11, + aes_ccm_64_64_128 = 12, + aes_ccm_64_64_256 = 13, + aes_mac_128_64 = 14, + aes_mac_256_64 = 15, + chacha20_poly1305 = 24, + aes_mac_128_128 = 25, + aes_mac_256_128 = 26, + aes_ccm_16_128_128 = 30, + aes_ccm_16_128_256 = 31, + aes_ccm_64_128_128 = 32, + aes_ccm_64_128_256 = 33, + iv_generation = 34, +} export const alg_algorithm = { diff --git a/tests/readme.test.ts b/tests/readme.test.ts index 786b210..b338e88 100644 --- a/tests/readme.test.ts +++ b/tests/readme.test.ts @@ -21,7 +21,7 @@ it('readme', async () => { const content = fs.readFileSync('./examples/image.png') const signatureForImage = await issuer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // signing algorithm ES256 + [cose.header.alg, cose.algorithm.es256], // signing algorithm ES256 [cose.Protected.ContentType, "image/png"], // content type image/png [cose.header.kid, issuerPublicKeyJwk.kid] // issuer key identifier ]), @@ -32,7 +32,7 @@ it('readme', async () => { ] const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], + [cose.header.alg, cose.algorithm.es256], [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']], [cose.header.kid, notaryPublicKeyJwk.kid] ]), diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 2d0f4f4..d605bc1 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -34,7 +34,7 @@ it('issue & verify', async () => { }) const inclusion = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 1, @@ -54,7 +54,7 @@ it('issue & verify', async () => { // based on a previous receipt const { root, receipt } = await cose.receipt.consistency.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), receipt: inclusion, @@ -81,7 +81,7 @@ it("add / remove from receipts", async () => { const content = fs.readFileSync('./examples/image.png') const signatureForImage = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content @@ -90,7 +90,7 @@ it("add / remove from receipts", async () => { // inclusion proof receipt for image signature const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index 253e7a0..37e5511 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -12,7 +12,7 @@ it('sign and verify', async () => { }) const message = '💣 test ✨ mesage 🔥' const coseSign1 = await signer.sign({ - protectedHeader: new Map([[cose.header.alg, cose.Signature.ES256]]), + protectedHeader: new Map([[cose.header.alg, cose.algorithm.es256]]), unprotectedHeader: new Map(), payload: new TextEncoder().encode(message) }) @@ -45,7 +45,7 @@ it('sign and verify large image from file system', async () => { const coseSign1 = await signer.sign({ protectedHeader: new Map([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), unprotectedHeader: new Map(), diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index c98eb6d..c2b849e 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -14,7 +14,7 @@ it('sign and verify', async () => { const payload = new TextEncoder().encode(message) const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 ]), payload }) @@ -48,7 +48,7 @@ it('sign and verify large image from file system', async () => { const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content diff --git a/tests/signer.test.ts b/tests/signer.test.ts index fc1b427..e53d996 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -15,7 +15,7 @@ it('sign and verify large image from file system', async () => { const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index c93e6dc..0e64623 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -31,7 +31,7 @@ it('verify multiple receipts', async () => { const signatureForImage = await issuerSigner.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.kid, issuerCkt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.ContentType, "image/png"], // content_type image/png ]), payload: content @@ -41,7 +41,7 @@ it('verify multiple receipts', async () => { const receiptForImageSignature1 = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.kid, notary1Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, @@ -51,7 +51,7 @@ it('verify multiple receipts', async () => { const receiptForImageSignature2 = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.kid, notary2Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 8518dff..7946131 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -19,13 +19,13 @@ it('sign and verify with x5t and key resolver', async () => { // } const rootCertificateThumbprint = await cose.certificate.thumbprint(cert.public) const signer = await cose.certificate.pkcs8Signer({ - alg: cose.Signature.ES256, + alg: cose.algorithm.es256, privateKeyPKCS8: cert.private }) const content = fs.readFileSync('./examples/image.png') const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.Signature.ES256], // alg ES256 + [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.header.x5t, rootCertificateThumbprint], // xt5 thumbprint [cose.Protected.ContentType, "image/png"], // content_type image/png ]), From edc950276055383bc0fb6deb760463b73c3bd5fa Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:17:11 -0500 Subject: [PATCH 11/67] factoring --- src/cose/Params.ts | 13 +------------ src/cose/sign1/hashEnvelopeSigner.ts | 9 ++++----- src/iana/requested/cose.ts | 7 +++++++ src/x509/certificate.ts | 8 ++++---- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index f77341f..e41f9c8 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -25,9 +25,7 @@ export const PartyVOther = -26 export const ContentType = 3 -export const PayloadLocation = -6801; -export const PayloadPreImageContentType = -6802; -export const PayloadHashAlgorithm = -6800; + export const CWTClaims = 15 @@ -61,10 +59,6 @@ export const Protected = { Type, // https://datatracker.ietf.org/doc/html/rfc9596 CWTClaims, // https://datatracker.ietf.org/doc/html/rfc9597 - PayloadHashAlgorithm, // new COSE Hash Envelop - PayloadPreImageContentType, - PayloadLocation, - VerifiableDataStructure, } @@ -86,11 +80,6 @@ export const Aead = { } -export const Hash = { - 'SHA256': -16 -} - - diff --git a/src/cose/sign1/hashEnvelopeSigner.ts b/src/cose/sign1/hashEnvelopeSigner.ts index 975d877..a56ecca 100644 --- a/src/cose/sign1/hashEnvelopeSigner.ts +++ b/src/cose/sign1/hashEnvelopeSigner.ts @@ -4,17 +4,16 @@ import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; import { RequestCoseSign1Signer, RequestCoseSign1 } from "./types" -// https://datatracker.ietf.org/doc/draft-steele-cose-hash-envelope/ - -import { Hash, Protected } from "../Params"; +import * as cose from '../../iana/assignments/cose' +import { draft_headers } from '../../iana/requested/cose' export const hash = { signer: ({ remote }: RequestCoseSign1Signer) => { return { sign: async ({ protectedHeader, unprotectedHeader, payload }: RequestCoseSign1): Promise => { const subtle = await subtleCryptoProvider(); - const hashEnvelopeAlgorithm = protectedHeader.get(Protected.PayloadHashAlgorithm) - if (hashEnvelopeAlgorithm !== Hash.SHA256) { + const hashEnvelopeAlgorithm = protectedHeader.get(draft_headers.payload_hash_algorithm) + if (hashEnvelopeAlgorithm !== cose.algorithm.sha_256) { throw new Error('Unsupported hash envelope algorithm (-16 is only one supported)') } const payloadHash = await subtle.digest("SHA-256", payload) diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts index 3e20314..42bb6d6 100644 --- a/src/iana/requested/cose.ts +++ b/src/iana/requested/cose.ts @@ -15,5 +15,12 @@ export const less_specified = { 'ES512': 'ES512' } +export enum draft_headers { + // https://datatracker.ietf.org/doc/draft-ietf-cose-hash-envelope/ + payload_hash_algorithm = -6800, + payload_location = -6801, + payload_preimage_content_type = -6802 +} + export { algorithms_to_labels, labels_to_algorithms } \ No newline at end of file diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 3ed929b..0f9f08c 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -2,14 +2,15 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; import { PublicKeyJwk } from "../cose/sign1" import * as x509 from "@peculiar/x509"; import { CoseSignatureAlgorithms } from '../cose/key'; -import { PrivateKeyJwk, detached, RequestCoseSign1VerifyDetached, Hash } from '..'; +import { PrivateKeyJwk, detached, RequestCoseSign1VerifyDetached } from '..'; import { crypto } from '..'; + +import * as cose from '../iana/assignments/cose'; import { labels_to_algorithms } from '../iana/requested/cose'; // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) - const extractable = true; const provide = async () => { @@ -20,7 +21,6 @@ const provide = async () => { } } - const algTowebCryptoParams: Record = { 'ESP256': { @@ -62,7 +62,7 @@ export type RequestRootCertificate = { // https://datatracker.ietf.org/doc/html/rfc9360#section-2-5.6.1 const thumbprint = async (cert: string): Promise<[number, ArrayBuffer]> => { const current = new x509.X509Certificate(cert) - return [Hash.SHA256, await current.getThumbprint('SHA-256')] + return [cose.algorithm.sha_256, await current.getThumbprint('SHA-256')] } export type RootCertificateResponse = { public: string, private: string } From 4a6bb9a2d298c8485def51c95a2a63dd6d93cbd0 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:24:43 -0500 Subject: [PATCH 12/67] factoring --- src/cose/Params.ts | 27 -------------------------- src/cose/receipt/add.ts | 8 +++++--- src/cose/receipt/consistency/issue.ts | 13 ++++++++----- src/cose/receipt/consistency/verify.ts | 9 ++++++--- src/cose/receipt/get.ts | 6 ++++-- src/cose/receipt/inclusion/issue.ts | 8 +++++--- src/cose/receipt/inclusion/verify.ts | 10 +++++++--- src/iana/requested/cose.ts | 10 ++++++++-- tests/readme.test.ts | 4 ++-- tests/receipt.test.ts | 10 +++++----- tests/sign1.attached.test.ts | 2 +- tests/sign1.detached.test.ts | 2 +- tests/signer.test.ts | 2 +- tests/verifiers.test.ts | 6 +++--- tests/x509.test.ts | 2 +- 15 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/cose/Params.ts b/src/cose/Params.ts index e41f9c8..3ad4609 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -32,9 +32,6 @@ export const CWTClaims = 15 export const Type = 16 // https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/ -export const Receipts = 394 -export const VerifiableDataStructure = 395 -export const VerifiableDataProofs = 396 export const VerifiableDataStructures = { 'RFC9162-Binary-Merkle-Tree': 1 @@ -46,31 +43,11 @@ export const VerifiableDataProofTypes = { 'RFC9162-Consistency-Proof': -2 } -export const Protected = { - - PartyUIdentity, - PartyUNonce, - PartyUOther, - PartyVIdentity, - PartyVNonce, - PartyVOther, - ContentType, - - Type, // https://datatracker.ietf.org/doc/html/rfc9596 - CWTClaims, // https://datatracker.ietf.org/doc/html/rfc9597 - - VerifiableDataStructure, - -} - export const Unprotected = { Iv: 5, Ek: -4, // new from COSE HPKE - - Receipts, - VerifiableDataProofs } export const A128GCM = 1 @@ -79,10 +56,6 @@ export const Aead = { A128GCM } - - - - export const COSE_Encrypt0 = 16 export const COSE_Sign1 = 18 export const COSE_Encrypt = 96 \ No newline at end of file diff --git a/src/cose/receipt/add.ts b/src/cose/receipt/add.ts index 07468c4..2ba1fa4 100644 --- a/src/cose/receipt/add.ts +++ b/src/cose/receipt/add.ts @@ -1,7 +1,9 @@ import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged, Sign1Tag } from '../../cbor' -import { Receipts } from '../Params'; + import { CoseSign1Bytes } from "../sign1"; +import { draft_headers } from '../../iana/requested/cose'; + export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) if (tag !== Sign1Tag) { @@ -11,8 +13,8 @@ export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): P value[1] = new Map(); } // unprotected header - const receipts = value[1].get(Receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ + const receipts = value[1].get(draft_headers.receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ receipts.push(receipt) - value[1].set(Receipts, receipts) + value[1].set(draft_headers.receipts, receipts) return toArrayBuffer(await encodeAsync(new Tagged(Sign1Tag, value), { canonical: true })); } \ No newline at end of file diff --git a/src/cose/receipt/consistency/issue.ts b/src/cose/receipt/consistency/issue.ts index 2142217..7cdf0ee 100644 --- a/src/cose/receipt/consistency/issue.ts +++ b/src/cose/receipt/consistency/issue.ts @@ -1,11 +1,14 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, Protected, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' import { CoseSign1Bytes, CoseSign1Signer, ProtectedHeaderMap } from "../../sign1" import { toArrayBuffer } from '../../../cbor' +import { draft_headers } from '../../..' + + export type RequestIssueConsistencyReceipt = { protectedHeader: ProtectedHeaderMap receipt: CoseSign1Bytes, @@ -15,7 +18,7 @@ export type RequestIssueConsistencyReceipt = { export const issue = async (req: RequestIssueConsistencyReceipt) => { const { protectedHeader, receipt, entries, signer } = req; - const consistencyVds = protectedHeader.get(Protected.VerifiableDataStructure) + const consistencyVds = protectedHeader.get(draft_headers.verifiable_data_structure) if (consistencyVds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') } @@ -27,12 +30,12 @@ export const issue = async (req: RequestIssueConsistencyReceipt) => { const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value const receiptProtectedHeader = cbor.decode(protectedHeaderBytes) - const inclusionVds = receiptProtectedHeader.get(Protected.VerifiableDataStructure); + const inclusionVds = receiptProtectedHeader.get(draft_headers.verifiable_data_structure); if (inclusionVds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') } - const [inclusion] = unprotectedHeaderMap.get(Unprotected.VerifiableDataProofs) + const [inclusion] = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) .get(VerifiableDataProofTypes['RFC9162-Inclusion-Proof']) // get first inclusion proof if (payload !== null) { throw new Error('payload must be null for this type of proof') @@ -61,7 +64,7 @@ export const issue = async (req: RequestIssueConsistencyReceipt) => { ]) const unprotectedHeader = new Map(); - unprotectedHeader.set(Unprotected.VerifiableDataProofs, proofs) + unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) const consistency = await signer.sign({ protectedHeader, diff --git a/src/cose/receipt/consistency/verify.ts b/src/cose/receipt/consistency/verify.ts index 29f57c7..48249f7 100644 --- a/src/cose/receipt/consistency/verify.ts +++ b/src/cose/receipt/consistency/verify.ts @@ -1,10 +1,13 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, Protected, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../sign1" +import { draft_headers } from '../../..' + + export type RequestVerifyConsistencyReceipt = { oldRoot: ArrayBuffer, newRoot: ArrayBuffer, @@ -20,11 +23,11 @@ export const verify = async (req: RequestVerifyConsistencyReceipt) => { } const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value const protectedHeader = cbor.decode(protectedHeaderBytes) - const vds = protectedHeader.get(Protected.VerifiableDataStructure); + const vds = protectedHeader.get(draft_headers.verifiable_data_structure); if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') } - const proofs = unprotectedHeaderMap.get(Unprotected.VerifiableDataProofs) + const proofs = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) const [consistency] = proofs.get(VerifiableDataProofTypes['RFC9162-Consistency-Proof']) // get first consistency proof if (payload !== null) { throw new Error('payload must be null for this type of proof') diff --git a/src/cose/receipt/get.ts b/src/cose/receipt/get.ts index 7ae264b..1174fc5 100644 --- a/src/cose/receipt/get.ts +++ b/src/cose/receipt/get.ts @@ -1,7 +1,9 @@ import { decodeFirstSync, Sign1Tag } from '../../cbor' -import { Receipts } from '../Params'; + import { CoseSign1Bytes } from "../sign1"; +import { draft_headers } from '../../iana/requested/cose'; + export const get = async (signature: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) if (tag !== Sign1Tag) { @@ -11,6 +13,6 @@ export const get = async (signature: CoseSign1Bytes): Promise return [] } // unprotected header - const receipts = value[1].get(Receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ + const receipts = value[1].get(draft_headers.receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ return receipts } \ No newline at end of file diff --git a/src/cose/receipt/inclusion/issue.ts b/src/cose/receipt/inclusion/issue.ts index 56fdda5..bb6af87 100644 --- a/src/cose/receipt/inclusion/issue.ts +++ b/src/cose/receipt/inclusion/issue.ts @@ -1,10 +1,12 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, Protected, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' import { CoseSign1Signer, ProtectedHeaderMap } from "../../sign1" +import { draft_headers } from '../../..' + export type RequestIssueInclusionReceipt = { protectedHeader: ProtectedHeaderMap entry: number, @@ -14,7 +16,7 @@ export type RequestIssueInclusionReceipt = { export const issue = async (req: RequestIssueInclusionReceipt) => { const { protectedHeader, entry, entries, signer } = req; - const vds = protectedHeader.get(Protected.VerifiableDataStructure) + const vds = protectedHeader.get(draft_headers.verifiable_data_structure) if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') } @@ -32,7 +34,7 @@ export const issue = async (req: RequestIssueInclusionReceipt) => { ]) ]) const unprotectedHeader = new Map(); - unprotectedHeader.set(Unprotected.VerifiableDataProofs, proofs) + unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) return signer.sign({ protectedHeader, unprotectedHeader, diff --git a/src/cose/receipt/inclusion/verify.ts b/src/cose/receipt/inclusion/verify.ts index 4fd14d2..e94daaf 100644 --- a/src/cose/receipt/inclusion/verify.ts +++ b/src/cose/receipt/inclusion/verify.ts @@ -1,10 +1,14 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, Protected, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../sign1" + +import { draft_headers } from '../../..' + + export type RequestVerifyInclusionReceipt = { entry: Uint8Array, receipt: CoseSign1Bytes, @@ -19,11 +23,11 @@ export const verify = async (req: RequestVerifyInclusionReceipt) => { } const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value const protectedHeader = cbor.decode(protectedHeaderBytes) - const vds = protectedHeader.get(Protected.VerifiableDataStructure); + const vds = protectedHeader.get(draft_headers.verifiable_data_structure); if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') } - const proofs = unprotectedHeaderMap.get(Unprotected.VerifiableDataProofs) + const proofs = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) const [inclusion] = proofs.get(VerifiableDataProofTypes['RFC9162-Inclusion-Proof']) // get first inclusion proof if (payload !== null) { throw new Error('payload must be null for this type of proof') diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts index 42bb6d6..900ac11 100644 --- a/src/iana/requested/cose.ts +++ b/src/iana/requested/cose.ts @@ -19,8 +19,14 @@ export enum draft_headers { // https://datatracker.ietf.org/doc/draft-ietf-cose-hash-envelope/ payload_hash_algorithm = -6800, payload_location = -6801, - payload_preimage_content_type = -6802 -} + payload_preimage_content_type = -6802, + + // https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/ + receipts = 394, + verifiable_data_structure = 395, + verifiable_data_proofs = 395 +} + export { algorithms_to_labels, labels_to_algorithms } \ No newline at end of file diff --git a/tests/readme.test.ts b/tests/readme.test.ts index b338e88..d062001 100644 --- a/tests/readme.test.ts +++ b/tests/readme.test.ts @@ -22,7 +22,7 @@ it('readme', async () => { const signatureForImage = await issuer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // signing algorithm ES256 - [cose.Protected.ContentType, "image/png"], // content type image/png + [cose.header.content_type, "image/png"], // content type image/png [cose.header.kid, issuerPublicKeyJwk.kid] // issuer key identifier ]), payload: content @@ -33,7 +33,7 @@ it('readme', async () => { const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']], + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']], [cose.header.kid, notaryPublicKeyJwk.kid] ]), entry: 0, diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index d605bc1..bf7075a 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -35,7 +35,7 @@ it('issue & verify', async () => { const inclusion = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 1, entries, @@ -55,7 +55,7 @@ it('issue & verify', async () => { const { root, receipt } = await cose.receipt.consistency.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), receipt: inclusion, entries, @@ -82,7 +82,7 @@ it("add / remove from receipts", async () => { const signatureForImage = await signer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), payload: content }) @@ -91,7 +91,7 @@ it("add / remove from receipts", async () => { const receiptForImageSignature = await cose.receipt.inclusion.issue({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, entries: transparencyLogContainingImageSignatures, @@ -99,7 +99,7 @@ it("add / remove from receipts", async () => { }) const transparentSignature = await cose.receipt.add(signatureForImage, receiptForImageSignature) const { value } = cose.cbor.decode(transparentSignature) - expect(value[1].get(cose.Unprotected.Receipts).length).toBe(1) // expect 1 receipt + expect(value[1].get(cose.draft_headers.receipts).length).toBe(1) // expect 1 receipt const receipts = await cose.receipt.get(transparentSignature) expect(receipts.length).toBe(1) // expect 1 receipt const coseKey = await cose.key.convertJsonWebKeyToCoseKey(publicKeyJwk) diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index 37e5511..04c95bd 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -46,7 +46,7 @@ it('sign and verify large image from file system', async () => { const coseSign1 = await signer.sign({ protectedHeader: new Map([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), unprotectedHeader: new Map(), payload: content diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index c2b849e..c9baca3 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -49,7 +49,7 @@ it('sign and verify large image from file system', async () => { const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), payload: content }) diff --git a/tests/signer.test.ts b/tests/signer.test.ts index e53d996..6082f8f 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -16,7 +16,7 @@ it('sign and verify large image from file system', async () => { const coseSign1 = await signer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), payload: content }) diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 0e64623..67037c8 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -32,7 +32,7 @@ it('verify multiple receipts', async () => { protectedHeader: cose.ProtectedHeader([ [cose.header.kid, issuerCkt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), payload: content }) @@ -42,7 +42,7 @@ it('verify multiple receipts', async () => { protectedHeader: cose.ProtectedHeader([ [cose.header.kid, notary1Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, entries: transparencyLogContainingImageSignatures, @@ -52,7 +52,7 @@ it('verify multiple receipts', async () => { protectedHeader: cose.ProtectedHeader([ [cose.header.kid, notary2Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.Protected.VerifiableDataStructure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 + [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 ]), entry: 0, entries: transparencyLogContainingImageSignatures, diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 7946131..88ed872 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -27,7 +27,7 @@ it('sign and verify with x5t and key resolver', async () => { protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], // alg ES256 [cose.header.x5t, rootCertificateThumbprint], // xt5 thumbprint - [cose.Protected.ContentType, "image/png"], // content_type image/png + [cose.header.content_type, "image/png"], // content_type image/png ]), payload: content }) From 74f0810ed2b5382f919f418610eaf4792e7503d2 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:27:57 -0500 Subject: [PATCH 13/67] factoring --- attic/encrypt/hpke/direct.ts | 2 +- attic/encrypt/hpke/wrap.ts | 2 +- attic/encrypt/wrap.ts | 2 +- src/cose/Params.ts | 48 ++------------------------ src/cose/receipt/consistency/verify.ts | 2 +- src/iana/requested/cose.ts | 8 +++++ 6 files changed, 15 insertions(+), 49 deletions(-) diff --git a/attic/encrypt/hpke/direct.ts b/attic/encrypt/hpke/direct.ts index dc7e9da..6b6d9bc 100644 --- a/attic/encrypt/hpke/direct.ts +++ b/attic/encrypt/hpke/direct.ts @@ -1,6 +1,6 @@ -import { COSE_Encrypt0, Direct, Protected, Unprotected, UnprotectedHeader } from '../../Params' +import { COSE_Encrypt0, Direct, Protected, UnprotectedHeader } from '../../Params' import { RequestDirectEncryption, RequestDirectDecryption } from '../types' import { Tagged, decodeFirst, encodeAsync } from "cbor-web" diff --git a/attic/encrypt/hpke/wrap.ts b/attic/encrypt/hpke/wrap.ts index 12763e2..89552cb 100644 --- a/attic/encrypt/hpke/wrap.ts +++ b/attic/encrypt/hpke/wrap.ts @@ -1,6 +1,6 @@ import { createAAD } from '../utils' -import { COSE_Encrypt, Protected, Unprotected, UnprotectedHeader } from '../../Params' +import { COSE_Encrypt, Protected, UnprotectedHeader } from '../../Params' import { RequestWrapDecryption, RequestWrapEncryption, } from '../types' import { EMPTY_BUFFER } from "../../../cbor" import { Tagged, decodeFirst, encodeAsync } from "cbor-web" diff --git a/attic/encrypt/wrap.ts b/attic/encrypt/wrap.ts index 439bb20..4492d96 100644 --- a/attic/encrypt/wrap.ts +++ b/attic/encrypt/wrap.ts @@ -12,7 +12,7 @@ import { RequestWrapDecryption, RequestWrapEncryption } from './types' import { EMPTY_BUFFER } from "../../cbor" import * as hpke from './hpke' -import { UnprotectedHeader, COSE_Encrypt, Unprotected, KeyWrap, KeyAgreementWithKeyWrap, Aead, ProtectedHeader, Protected, Epk } from "../Params" +import { UnprotectedHeader, COSE_Encrypt, KeyWrap, KeyAgreementWithKeyWrap, Aead, ProtectedHeader, Protected, Epk } from "../Params" import { toArrayBuffer } from "../../cbor" diff --git a/src/cose/Params.ts b/src/cose/Params.ts index 3ad4609..e413e46 100644 --- a/src/cose/Params.ts +++ b/src/cose/Params.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -// This module is just just a limited set of the IANA registries, -// exposed to make Map initialization more readable - +// This module has some helper functions +// that reduce clutter / verbosity export type HeaderMapEntry = [number, any] export type HeaderMap = Map @@ -10,52 +9,11 @@ export const ProtectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } - export const UnprotectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } -export const PartyUIdentity = -21 -export const PartyUNonce = -22 -export const PartyUOther = -23 - -export const PartyVIdentity = -24 -export const PartyVNonce = -25 -export const PartyVOther = -26 - -export const ContentType = 3 - - - - -export const CWTClaims = 15 -export const Type = 16 - -// https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/ - -export const VerifiableDataStructures = { - 'RFC9162-Binary-Merkle-Tree': 1 -} - -// only for RFC9162-Binary-Merkle-Tree -export const VerifiableDataProofTypes = { - 'RFC9162-Inclusion-Proof': -1, - 'RFC9162-Consistency-Proof': -2 -} - - -export const Unprotected = { - - Iv: 5, - Ek: -4, // new from COSE HPKE -} - -export const A128GCM = 1 - -export const Aead = { - A128GCM -} - +// move to iana cbor export const COSE_Encrypt0 = 16 export const COSE_Sign1 = 18 export const COSE_Encrypt = 96 \ No newline at end of file diff --git a/src/cose/receipt/consistency/verify.ts b/src/cose/receipt/consistency/verify.ts index 48249f7..64d6292 100644 --- a/src/cose/receipt/consistency/verify.ts +++ b/src/cose/receipt/consistency/verify.ts @@ -1,7 +1,7 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, Unprotected, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../sign1" diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts index 900ac11..b5d2e19 100644 --- a/src/iana/requested/cose.ts +++ b/src/iana/requested/cose.ts @@ -25,8 +25,16 @@ export enum draft_headers { receipts = 394, verifiable_data_structure = 395, verifiable_data_proofs = 395 +} +export const VerifiableDataStructures = { + 'RFC9162-Binary-Merkle-Tree': 1 +} +// only for RFC9162-Binary-Merkle-Tree +export const VerifiableDataProofTypes = { + 'RFC9162-Inclusion-Proof': -1, + 'RFC9162-Consistency-Proof': -2 } export { algorithms_to_labels, labels_to_algorithms } \ No newline at end of file From ec1de35444a3793101e798677bae4f3cff594723 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 09:43:24 -0500 Subject: [PATCH 14/67] save --- scripts/data/tags.csv | 265 ++++++++++++++++++++++++++++ scripts/make-iana-assignments.ts | 52 +++++- src/cose/ExperimentalCoseHeaders.ts | 5 - src/cose/detached/index.ts | 2 +- src/{cose/Params.ts => desugar.ts} | 4 - src/iana/assignments/cbor.ts | 10 ++ src/index.ts | 7 +- tests/readme.test.ts | 2 +- tests/verifiers.test.ts | 2 +- tests/x509.test.ts | 2 +- 10 files changed, 332 insertions(+), 19 deletions(-) create mode 100644 scripts/data/tags.csv delete mode 100644 src/cose/ExperimentalCoseHeaders.ts rename src/{cose/Params.ts => desugar.ts} (79%) create mode 100644 src/iana/assignments/cbor.ts diff --git a/scripts/data/tags.csv b/scripts/data/tags.csv new file mode 100644 index 0000000..5b76ca9 --- /dev/null +++ b/scripts/data/tags.csv @@ -0,0 +1,265 @@ +Tag,Data Item,Semantics,Reference,Template +0,text string,Standard date/time string; see Section 3.4.1,[RFC8949], +1,integer or float,Epoch-based date/time; see Section 3.4.2,[RFC8949], +2,byte string,Unsigned bignum; see Section 3.4.3,[RFC8949], +3,byte string,Negative bignum; see Section 3.4.3,[RFC8949], +4,array,Decimal fraction; see Section 3.4.4,[RFC8949], +5,array,Bigfloat; see Section 3.4.4,[RFC8949], +6-15,Unassigned,,, +16,COSE_Encrypt0,COSE Single Recipient Encrypted Data Object,[RFC9052], +17,COSE_Mac0,COSE Mac w/o Recipients Object,[RFC9052], +18,COSE_Sign1,COSE Single Signer Data Object,[RFC9052], +19,COSE_Countersignature,COSE standalone V2 countersignature,[RFC9338], +20,Unassigned,,, +21,(any),Expected conversion to base64url encoding; see Section 3.4.5.2,[RFC8949], +22,(any),Expected conversion to base64 encoding; see Section 3.4.5.2,[RFC8949], +23,(any),Expected conversion to base16 encoding; see Section 3.4.5.2,[RFC8949], +24,byte string,Encoded CBOR data item; see Section 3.4.5.1,[RFC8949], +25,unsigned integer,reference the nth previously seen string,[http://cbor.schmorp.de/stringref][Marc_A._Lehmann], +26,array,Serialised Perl object with classname and constructor arguments,[http://cbor.schmorp.de/perl-object][Marc_A._Lehmann], +27,array,Serialised language-independent object with type name and constructor arguments,[http://cbor.schmorp.de/generic-object][Marc_A._Lehmann], +28,multiple,mark value as (potentially) shared,[http://cbor.schmorp.de/value-sharing][Marc_A._Lehmann], +29,unsigned integer,reference nth marked value,[http://cbor.schmorp.de/value-sharing][Marc_A._Lehmann], +30,array,Rational number,[http://peteroupc.github.io/CBOR/rational.html][Peter_Occil], +31,Undefined (0xf7),Absent value in a CBOR Array,[https://github.com/svaarala/cbor-specs/blob/master/cbor-absent-tag.rst][Sami_Vaarala], +32,text string,URI; see Section 3.4.5.3,[RFC8949], +33,text string,base64url; see Section 3.4.5.3,[RFC8949], +34,text string,base64; see Section 3.4.5.3,[RFC8949], +35,UTF-8 string,Regular expression; see Section 2.4.4.3,[RFC7049], +36,text string,MIME message; see Section 3.4.5.3,[RFC8949], +37,byte string,"Binary UUID ([RFC9562, Section 4])",[https://github.com/lucas-clemente/cbor-specs/blob/master/uuid.md][Lucas_Clemente], +38,array,Language-tagged string,"[RFC9290, Appendix A]", +39,multiple,Identifier,[https://github.com/lucas-clemente/cbor-specs/blob/master/id.md][Lucas_Clemente], +40,array of two arrays [1],"Multi-dimensional Array, row-major order",[RFC8746], +41,array,Homogeneous Array,[RFC8746], +42,byte string,IPLD content identifier,[https://github.com/ipld/cid-cbor/][Volker_Mische], +43,text string,YANG bits datatype; see Section 6.7.,[RFC9254], +44,text string,YANG enumeration datatype; see Section 6.6.,[RFC9254], +45,unsigned integer or text string,YANG identityref datatype; see Section 6.10.,[RFC9254], +46,unsigned integer or text string or array,YANG instance-identifier datatype; see Section 6.13.,[RFC9254], +47,unsigned integer,YANG Schema Item iDentifier (sid); see Section 3.2.,[RFC9254], +48,byte string,IEEE MAC Address,[RFC9542], +49-51,Unassigned,,, +52,byte string or array,"IPv4, [prefixlen,IPv4], [IPv4,prefixpart]",[RFC9164], +53,Unassigned,,, +54,byte string or array,"IPv6, [prefixlen,IPv6], [IPv6,prefixpart]",[RFC9164], +55-60,Unassigned,,, +61,CBOR Web Token (CWT),CBOR Web Token (CWT),[RFC8392][Michael_B._Jones], +62,Unassigned,,, +63,byte string,Encoded CBOR Sequence [RFC8742],"[draft-bormann-cbor-notable-tags-02, Section 2.1]", +64,byte string,uint8 Typed Array,[RFC8746], +65,byte string,"uint16, big endian, Typed Array",[RFC8746], +66,byte string,"uint32, big endian, Typed Array",[RFC8746], +67,byte string,"uint64, big endian, Typed Array",[RFC8746], +68,byte string,"uint8 Typed Array, clamped arithmetic",[RFC8746], +69,byte string,"uint16, little endian, Typed Array",[RFC8746], +70,byte string,"uint32, little endian, Typed Array",[RFC8746], +71,byte string,"uint64, little endian, Typed Array",[RFC8746], +72,byte string,sint8 Typed Array,[RFC8746], +73,byte string,"sint16, big endian, Typed Array",[RFC8746], +74,byte string,"sint32, big endian, Typed Array",[RFC8746], +75,byte string,"sint64, big endian, Typed Array",[RFC8746], +76,byte string,(reserved),[RFC8746], +77,byte string,"sint16, little endian, Typed Array",[RFC8746], +78,byte string,"sint32, little endian, Typed Array",[RFC8746], +79,byte string,"sint64, little endian, Typed Array",[RFC8746], +80,byte string,"IEEE 754 binary16, big endian, Typed Array",[RFC8746], +81,byte string,"IEEE 754 binary32, big endian, Typed Array",[RFC8746], +82,byte string,"IEEE 754 binary64, big endian, Typed Array",[RFC8746], +83,byte string,"IEEE 754 binary128, big endian, Typed Array",[RFC8746], +84,byte string,"IEEE 754 binary16, little endian, Typed Array",[RFC8746], +85,byte string,"IEEE 754 binary32, little endian, Typed Array",[RFC8746], +86,byte string,"IEEE 754 binary64, little endian, Typed Array",[RFC8746], +87,byte string,"IEEE 754 binary128, little endian, Typed Array",[RFC8746], +88-95,Unassigned,,, +96,COSE_Encrypt,COSE Encrypted Data Object,[RFC9052], +97,COSE_Mac,COSE MACed Data Object,[RFC9052], +98,COSE_Sign,COSE Signed Data Object,[RFC9052], +99,Unassigned,,, +100,Unsigned or negative integer,Number of days since the epoch date 1970-01-01,[RFC8943], +101,"array [uint, any]",alternatives as given by the uint + 128; see Section 9.1,[draft-bormann-cbor-notable-tags-07], +102,Unassigned,,, +103,array,Geographic Coordinates,[https://github.com/allthingstalk/cbor/blob/master/CBOR-Tag103-Geographic-Coordinates.md][Danilo_Vidovic], +104,multiple,Geographic Coordinate Reference System WKT or EPSG number,[draft-clarke-cbor-crs], +105-109,Unassigned,,, +110,"byte string, array, or map",relative object identifier (BER encoding); SDNV [RFC6256] sequence,[RFC9090], +111,"byte string, array, or map",object identifier (BER encoding),[RFC9090], +112,"byte string, array, or map","object identifier (BER encoding), relative to 1.3.6.1.4.1",[RFC9090], +113-119,Unassigned,,, +120,multiple,Internet of Things Data Point,[https://github.com/allthingstalk/cbor/blob/master/CBOR-Tag120-Internet-of-Things-Data-Points.md][Danilo_Vidovic], +121-127,any,"alternatives 0..6, 1+1 encoding; see Section 9.1",[draft-bormann-cbor-notable-tags-07], +128-199,Unassigned,,, +200,multiple,Gordian Envelope,[draft-mcnally-envelope-05], +201,any,enclosed dCBOR,[draft-mcnally-deterministic-cbor-10], +202-255,Unassigned,,, +256,multiple,mark value as having string references,[http://cbor.schmorp.de/stringref][Marc_A._Lehmann], +257,byte string,Binary MIME message,[http://peteroupc.github.io/CBOR/binarymime.html][Peter_Occil], +258,array,Mathematical finite set,[https://github.com/input-output-hk/cbor-sets-spec/blob/master/CBOR_SETS.md][Alfredo_Di_Napoli], +259,map,Map datatype with key-value operations (e.g. `.get()/.set()/.delete()`),[https://github.com/shanewholloway/js-cbor-codec/blob/master/docs/CBOR-259-spec--explicit-maps.md][Shane_Holloway], +260,byte string,"Network Address (IPv4 or IPv6 or MAC Address) (DEPRECATED in favor of 52 and 54 + for IP addresses)",[http://www.employees.org/~ravir/cbor-network.txt][Ravi_Raju][RFC9164], +261,map (IPAddress + Mask Length),"Network Address Prefix (IPv4 or IPv6 Address + Mask Length) (DEPRECATED in favor of 52 and 54 + for IP addresses)",[https://github.com/toravir/CBOR-Tag-Specs/blob/master/networkPrefix.md][Ravi_Raju][RFC9164], +262,byte string,Embedded JSON Object,[https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md][Ravi_Raju], +263,byte string,Hexadecimal string,[https://github.com/toravir/CBOR-Tag-Specs/blob/master/hexString.md][Ravi_Raju], +264,array,Decimal fraction with arbitrary exponent,[http://peteroupc.github.io/CBOR/bigfrac.html][Peter_Occil], +265,array,Bigfloat with arbitrary exponent,[http://peteroupc.github.io/CBOR/bigfrac.html][Peter_Occil], +266,text string,Internationalized resource identifier (IRI),[https://peteroupc.github.io/CBOR/iri.html][Peter_Occil], +267,text string,Internationalized resource identifier reference (IRI reference),[https://peteroupc.github.io/CBOR/iri.html][Peter_Occil], +268,array,Extended decimal fraction,[https://peteroupc.github.io/CBOR/extended.html][Peter_Occil], +269,array,Extended bigfloat,[https://peteroupc.github.io/CBOR/extended.html][Peter_Occil], +270,array,Extended rational number,[https://peteroupc.github.io/CBOR/extended.html][Peter_Occil], +271,DDoS Open Threat Signaling (DOTS) signal channel object,"DDoS Open Threat Signaling (DOTS) signal channel object, as defined in [RFC9132]",[RFC9132], +272,byte string,Non-UTF-8 CESU-8 string,[https://github.com/svaarala/cbor-specs/blob/master/cbor-nonutf8-string-tags.rst][Sami_Vaarala], +273,byte string,Non-UTF-8 WTF-8 string,[https://github.com/svaarala/cbor-specs/blob/master/cbor-nonutf8-string-tags.rst][Sami_Vaarala], +274,byte string,Non-UTF-8 MUTF-8 string,[https://github.com/svaarala/cbor-specs/blob/master/cbor-nonutf8-string-tags.rst][Sami_Vaarala], +275,map (major type 5),Map contains only keys that are of type Text String (major type 3),[https://github.com/ecorm/cbor-tag-text-key-map][Emile_Cormier], +276,byte string,ERIS binary read capability,[http://purl.org/eris], +277,byte string,Universal Geographical Area Description (GAD) shape; see Section 5,[TS 23.032][Mathew_Meins], +278,byte string,Universal Geographical Area Description (GAD) description of velocity; see Section 8,[TS 23.032][Mathew_Meins], +279,array,Coordinate Reference System Wrapper,[Fast and Readable Geographical Hashing (CTA-5009-A)][Consumer_Technology_Association], +280-300,Unassigned,,, +301,text string or array,Geohash String,[Fast and Readable Geographical Hashing (CTA-5009-A)][Consumer_Technology_Association], +302-502,Unassigned,,, +503-504,any,Earmarked for CoRIM,[draft-ietf-rats-corim], +505-508,Unassigned,,, +509-549,any,Earmarked for CoRIM,[draft-ietf-rats-corim], +550-561,Unassigned,,, +562-569,any,Earmarked for CoRIM,[draft-ietf-rats-corim], +570,"map (spdm-toc-map, see CDDL)",spdm-toc-map,[TCG DICE Concise Evidence Binding for SPDM][TCG], +571,"map (concise-evidence-map, see CDDL)",concise-evidence-map,[TCG DICE Concise Evidence Binding for SPDM][TCG], +572-599,any,Earmarked for CoRIM,[draft-ietf-rats-corim], +600-601,Unassigned,,, +602,array,"Detached EAT Bundle [RFC-ietf-rats-eat-30, Section 5]",[RFC-ietf-rats-eat-30], +603-1000,Unassigned,,, +1001,map,extended time,"[RFC9581, Section 3]", +1002,map,duration,"[RFC9581, Section 4]", +1003,array,period,"[RFC9581, Section 5]", +1004,UTF-8 text string,[RFC3339] full-date string,[RFC8943], +1005-1009,Unassigned,,, +1010,"array: [id: text string, obj: any]",Object type identifier,[draft-rundgren-cotx-04], +1011-1039,Unassigned,,, +1040,array of two arrays [1],"Multi-dimensional Array, column-major order",[RFC8746], +1041-1047,Unassigned,,, +1048,byte string,IEEE OUI/CID,[RFC9542], +1049-1279,Unassigned,,, +1280-1400,any,"alternatives 7..127, 1+2 encoding; see Section 9.1",[draft-bormann-cbor-notable-tags-07], +1401-18299,Unassigned,,, +18300-18555,byte string,Bare Hash value (COSE algorithm -256 to -1),"[draft-bormann-cbor-notable-tags-09, Section 3.1.1]", +18556,array,"[COSE algorithm identifier, Base Hash value]","[draft-bormann-cbor-notable-tags-09, Section 3.1.1]", +18557-18811,byte string,Bare Hash value (COSE algorithm 1 to 255),"[draft-bormann-cbor-notable-tags-09, Section 3.1.1]", +18812-21064,Unassigned,,, +21065,text string,I-Regexp,"[draft-bormann-cbor-notable-tags-09, Section 2.1][draft-ietf-jsonpath-iregexp-08]", +21066,"Array[UTF8string, UTF8string?]",ECMAScript RegExp [https://262.ecma-international.org/14.0/#sec-regexp-regular-expression-objects],[https://github.com/hildjj/cbor-specs/blob/main/regexp.md][Joe_Hildebrand], +21067-22097,Unassigned,,, +22098,multiple,hint that indicates an additional level of indirection,[http://cbor.schmorp.de/indirection][Marc_A._Lehmann], +22099-25440,Unassigned,,, +25441,Array containing at most one array followed by at most one map,Capture [3],[https://github.com/japhb/cbor-specs/blob/main/capture.md][Geoffrey_Broadwell], +25442-32767,Unassigned,,, +32768,unsigned integer,Identifier for a FHIR constant,[Stefan_Genchev],template/32768 +32769,multiple,External reference,[https://gitlab.com/Hawk777/cbor-specs/-/blob/main/external-reference.md][Christopher_Head], +32770-32779,unsigned integer,Used to mark pointers in PSA Crypto API IPC implementation,[Ole_Saether],template/32770-32779 +32780-39999,Unassigned,,, +40000,unsigned integer,"ur:known-value, Semantic signifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40001,byte string,"ur:digest, 32-byte SHA-256 digest",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40002,array,"ur:encrypted, IETF ChaCha20-Poly1305 ([RFC8439]) encrypted message",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40003,array,"ur:compressed, [RFC1951] DEFLATE-compressed message",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40004,multiple,"ur:request, Transaction Request identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40005,multiple,"ur:response, Transaction response identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40006,unsigned integer or text string,"ur:function, Envelope expression function identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40007,unsigned integer or text string,"ur:parameter, Envelope expression parameter identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40008,unsigned integer or text string,"ur:placeholder, Envelope expression placeholder identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40009,unsigned integer or text string,"ur:replacement, Envelope expression replacement identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40010,byte string,"ur:agreement-private-key, Curve25519 private key for X25519 key agreement",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40011,byte string,"ur:agreement-public-key, Curve25519 public key for X25519 key agreement",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40012,byte string,"ur:arid, Apparently Random Identifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40013,Unassigned,,, +40014,byte string,"ur:nonce, Cryptographic nonce",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40015,array,"ur:password, Scrypt-hashed password",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40016,byte string,"ur:crypto-prvkeys, Private key base (key material)",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40017,array,"ur:crypto-pubkeys, Public key base (signing and agreement public key bundle)",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40018,byte string,"ur:salt, Random salt used for hash tree decorrelation",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40019,array,"ur:crypto-sealed, Encrypted message and ephemeral public key",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40020,multiple,"ur:signature, Cryptographic signature",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40021,byte string,"ur:signing-private-key, Cryptographic private key used for signing",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40022,multiple,"ur:signing-public-key, Cryptographic public key used for signing",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40023,byte string,"ur:crypto-key, Cryptographic key used for symmetric encryption",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40024-40299,Unassigned,,, +40300,map,"ur:seed, Cryptographic seed",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40301-40302,Unassigned,,, +40303,map,"ur:hdkey, Bitcoin BIP-32 HD key",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40304,map,"ur:keypath, Bitcoin BIP-32 key derivation path",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40305,map,"ur:coin-info, Cryptographic asset and network specifier",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40306,map,"ur:eckey, Bitcoin elliptic curve key (private or public)",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40307,map,"ur:address, Cryptocurrency address",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40308,map,"ur:output-descriptor, Bitcoin output descriptor",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40309,byte string,"ur:sskr, Sharded Secret Key Reconstruction (SSKR) shear",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40310,byte string,"ur:psbt, Partially Signed Bitcoin Transaction",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40311,map,"ur:account, Bitcoin output descriptor bundle",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40312-40799,Unassigned,,, +40800,text string,"ur:ssh-private, Text format SSH private key",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40801,text string,"ur:ssh-public, Text format SSH public key",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40802,text string,"ur:ssh-signature, Text format SSH signature",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40803,text string,"ur:ssh-certificate, Text format SSH certificate",[https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-006-urtypes.md][Wolf_McNally], +40804-42599,Unassigned,,, +42600,map,A confidentiality clearance. The key value pairs of the map are defined in ADatP-4774.4,[Aidan_Murdock],template/42600 +42601,array,A metadata binding. The elements of the array are defined in AdatP-4778.5. The tag is also used as part of the magic number in on-disk detached and encapsulating bindings.,[Aidan_Murdock],template/42601 +42602,map,A collection of NCMS metadata elements. The key value pairs of the map are defined in AdatP-5636.4,[Aidan_Murdock],template/42602 +42603-42999,Unassigned,,, +43000,array,Single complex number: array elements are real (I) and imaginary (Q) components,[Saajan_Chana],template/43000 +43001,array,"Array of complex numbers in interleaved form: complex value k is stored with real (I) part +at array index 2k and imaginary (Q) part at index (2k + 1)",[Saajan_Chana],template/43001 +43002-49999,Unassigned,,, +50000,integer,PlatformV_IS_ID,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50001,text string,PlatformV_IS_NAME,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50002,any,PlatformV_IS_VALUE,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50003,array,PlatformV_HAS_COMPOSITE_VALUE,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50004,array,PlatformV_HAS_MAPPED_VALUE,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50005,array,PlatformV_HAS_OBJ_ID,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50006,array,PlatformV_HAS_OBJ_TAG,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50007,array,PlatformV_HAS_CHILD,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50008,array,PlatformV_HAS_PROPERTY,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50009,array,PlatformV_HAS_META,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50010,array,PlatformV_HAS_EVENT,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50011,array,PlatformV_HAS_ACTION,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50012,integer,PlatformV_IS_TYPE,[https://github.com/arthurwangtz/platformv-cbor][Wang_Tongzhou], +50013-55798,Unassigned,,, +55799,(any),Self-described CBOR; see Section 3.4.6,[RFC8949], +55800,tagged byte string,indicates that the file contains CBOR Sequences,[RFC9277], +55801,tagged byte string,indicates that the file starts with a CBOR-Labeled Non-CBOR Data label.,[RFC9277], +55802-56499,Unassigned,,, +56500,array (major type 4),Compressed byte string,[https://github.com/dectris/documentation/blob/main/cbor/dectris-compression-tag.md][Dirk_Boye], +56501-57341,Unassigned,,, +57342,array,Identify and define a set of record structures (each a sequence of property names) that can be referenced as tags in the included value (and the scope for the record tag definitions),[https://github.com/kriszyp/cbor-records][Kris_Zyp], +57343,array,"Identify and define a record structure (a sequence of property names), and use that record structure definition to interpret the included values.",[https://github.com/kriszyp/cbor-records][Kris_Zyp], +57344-57599,array,"References a defined record structure, using that referenced record definition to interpret the included values.",[https://github.com/kriszyp/cbor-records][Kris_Zyp], +57600-59999,Unassigned,,, +60000,array,The tagged CBOR array contains attestation evidence data with an Intel TEE quote.,[Shanwei_Cen],template/60000 +60001,array,The tagged CBOR array contains attestation evidence data with an Intel TEE report.,[Shanwei_Cen],template/60001 +60002,array,The tagged CBOR array contains attestation evidence data with an Intel SGX report.,[Shanwei_Cen],template/60002 +60003-65534,Unassigned,,, +65535,(none valid),always invalid; see Section 10.1,[draft-bormann-cbor-notable-tags-02], +65536-79999,Unassigned,,, +80000-80099,"byte string, array or map",Private tags as suggested in [https://mailarchive.ietf.org/arch/msg/cbor/NJlskB63pjXFt5a6S37tiwJBbCM].,[Tony_Putman],template/80000-80099 +80100-14245119,Unassigned,,, +14245120-14245220,array or map,"A tag within this range will indicate that a CBOR-encoded payload +contains a W3C verifiable credential, data integrity proof value. The additional +specificity of the tag in this range is use to identify a particular cryptographic +suite, cryptographic feature, or proof role (base or derived).",[Greg_Bernstein],template/14245120-14245220 +14245221-15309735,Unassigned,,, +15309736,map (major type 5),RAINS Message,[https://britram.github.io/rains-prototype][Brian_Trammell], +15309737-1330664269,Unassigned,,, +1330664270,byte-string,"A CBOR encoded Openswan configuration file, as stored on disk for +unit test cases.",[Michael_Richardson][Samir_Hussain],template/1330664270 +1330664271-1398229315,Unassigned,,, +1398229316,map,Concise Software Identifier (CoSWID),[RFC9393], +1398229317-1668546816,Unassigned,,, +1668546817-1668612095,byte string or any CBOR data item (see Appendix B of [RFC9277]),the representation of content-format ct < 65025 is indicated by tag number TN(ct) = 0x63740101 + (ct / 255) * 256 + ct % 255,[RFC9277], +1668612096-1701996914,Unassigned,,, +1701996915,array,Array of content-addressed blocks and ERIS read capabilities,[Endo_Renberg],template/1701996915 +1701996916,array,ERIS-FS image header,[Endo_Renberg],template/1701996916 +1701996917-4294967294,Unassigned,,, +4294967295,(none valid),always invalid; see Section 10.1,[draft-bormann-cbor-notable-tags-02], +4294967296,map,Intel FPGA SPDM Manifest,[Andrew_Draper],template/4294967296 +4294967297-18446744073709551614,Unassigned,,, +18446744073709551615,(none valid),always invalid; Section 10.1,[draft-bormann-cbor-notable-tags-02], diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index e260814..c2f6340 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -446,7 +446,8 @@ export const ${ktyName}_params_to_labels = new Map([...${`labels_to_${ktyName}_p -(async () => { + +const makeIanaCoseAssignments = async () => { const { commonKeyParams, commonKeyParamDefinitions } = await getCommonKeyParams() as any const { keyTypes, keyTypeDefinitions } = await getKeyTypes() as any const { curvesByKeyType, curveDefinitions } = await getCurves() as any @@ -467,4 +468,53 @@ ${mappings} `.trim() fs.writeFileSync('./src/iana/assignments/cose.ts', final) +} + + +const getRows = (path_to_csv: string): Promise => { + return new Promise(async (resolve) => { + const rows = [] as any[] + const stream = fs.createReadStream(path_to_csv) + .pipe(csv()) + stream.on('data', (row: any) => { + rows.push(row) + }); + stream.on('end', () => { + resolve(rows) + }); + }) +} + +type CborTagRow = { + Tag: string, + 'Data Item': string, + Semantics: string, + Reference: string, + Template: string +} + +const getCborTags = async () => { + const rows = await getRows('./scripts/data/tags.csv') + let tags = `export enum tag {\n` + for (const row of rows) { + if (row['Data Item'].startsWith('COSE')) { + tags += ` ${row['Data Item']} = ${row['Tag']},\n` + } + } + tags += `}\n` + return tags +} + +const makeIanaCborAssignments = async () => { + const tags = await getCborTags(); + const final = ` +// DO NOT edit this file, it is generated automatically +${tags} +`.trim() + fs.writeFileSync('./src/iana/assignments/cbor.ts', final) +} + +(async () => { + await makeIanaCborAssignments() + await makeIanaCoseAssignments() })() \ No newline at end of file diff --git a/src/cose/ExperimentalCoseHeaders.ts b/src/cose/ExperimentalCoseHeaders.ts deleted file mode 100644 index f70cf67..0000000 --- a/src/cose/ExperimentalCoseHeaders.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { IANACOSEHeaderParameters } from './header-parameters' - -const ExperimentalCoseHeaders = IANACOSEHeaderParameters - -export { ExperimentalCoseHeaders } \ No newline at end of file diff --git a/src/cose/detached/index.ts b/src/cose/detached/index.ts index 068ed34..f62fef4 100644 --- a/src/cose/detached/index.ts +++ b/src/cose/detached/index.ts @@ -1,7 +1,7 @@ import * as sign1 from "../sign1" import { decodeFirstSync, encodeAsync, Sign1Tag, Tagged, toArrayBuffer } from '../../cbor' -import { UnprotectedHeader } from "../Params" +import { UnprotectedHeader } from "../../desugar" export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { const coseSign1Signer = sign1.signer({ remote }) diff --git a/src/cose/Params.ts b/src/desugar.ts similarity index 79% rename from src/cose/Params.ts rename to src/desugar.ts index e413e46..3abf6b5 100644 --- a/src/cose/Params.ts +++ b/src/desugar.ts @@ -13,7 +13,3 @@ export const UnprotectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } -// move to iana cbor -export const COSE_Encrypt0 = 16 -export const COSE_Sign1 = 18 -export const COSE_Encrypt = 96 \ No newline at end of file diff --git a/src/iana/assignments/cbor.ts b/src/iana/assignments/cbor.ts new file mode 100644 index 0000000..40dcfc9 --- /dev/null +++ b/src/iana/assignments/cbor.ts @@ -0,0 +1,10 @@ +// DO NOT edit this file, it is generated automatically +export enum tag { + COSE_Encrypt0 = 16, + COSE_Mac0 = 17, + COSE_Sign1 = 18, + COSE_Countersignature = 19, + COSE_Encrypt = 96, + COSE_Mac = 97, + COSE_Sign = 98, +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index cff4bad..6d18d16 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,6 @@ - - - - import * as key from './cose/key' import * as attached from './cose/attached' import * as detached from './cose/detached' @@ -13,13 +9,14 @@ export * from './cose/sign1' export * from './x509' -export * from './cose/Params' +export * from './desugar' // https://github.com/dajiaji/hpke-js/issues/302 // this issue also effect vercel ncc // a better fix would be to move hpke stuff to its won package. // export * from './cose/encrypt' +export * from './iana/assignments/cbor' export * from './iana/assignments/cose' export * from './iana/requested/cose' diff --git a/tests/readme.test.ts b/tests/readme.test.ts index d062001..c812e60 100644 --- a/tests/readme.test.ts +++ b/tests/readme.test.ts @@ -43,7 +43,7 @@ it('readme', async () => { const transparentSignature = await cose.receipt.add(signatureForImage, receiptForImageSignature) const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) - if (tag !== cose.COSE_Sign1) { + if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') } const [protectedHeaderBytes] = value; diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 67037c8..364121d 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -62,7 +62,7 @@ it('verify multiple receipts', async () => { const transparentSignature = await cose.receipt.add(transparentSignature1, receiptForImageSignature2) const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) - if (tag !== cose.COSE_Sign1) { + if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') } const [protectedHeaderBytes] = value; diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 88ed872..8dca2a4 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -33,7 +33,7 @@ it('sign and verify with x5t and key resolver', async () => { }) const certificateFromThumbprint = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) - if (tag !== cose.COSE_Sign1) { + if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') } const [protectedHeaderBytes] = value; From 67a281b407aebe7a5e48fe8c84dda489b6628827 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 12:39:53 -0500 Subject: [PATCH 15/67] factoring --- scripts/data/web-key-parameters.csv | 32 ++++++ scripts/make-iana-assignments.ts | 125 ++++++++++++++++++++++ src/draft.ts | 81 ++++++++++++++ src/iana/assignments/jose.ts | 157 ++++++++++++++++++++++++++++ src/index.ts | 1 + tests/jose.type.sanity.test.ts | 32 ++++++ 6 files changed, 428 insertions(+) create mode 100644 scripts/data/web-key-parameters.csv create mode 100644 src/draft.ts create mode 100644 src/iana/assignments/jose.ts create mode 100644 tests/jose.type.sanity.test.ts diff --git a/scripts/data/web-key-parameters.csv b/scripts/data/web-key-parameters.csv new file mode 100644 index 0000000..4501a73 --- /dev/null +++ b/scripts/data/web-key-parameters.csv @@ -0,0 +1,32 @@ +Parameter Name,Parameter Description,"Used with ""kty"" Value(s)",Parameter Information Class,Change Controller,Reference +kty,Key Type,*,Public,[IESG],"[RFC7517, Section 4.1]" +use,Public Key Use,*,Public,[IESG],"[RFC7517, Section 4.2]" +key_ops,Key Operations,*,Public,[IESG],"[RFC7517, Section 4.3]" +alg,Algorithm,*,Public,[IESG],"[RFC7517, Section 4.4]" +kid,Key ID,*,Public,[IESG],"[RFC7517, Section 4.5]" +x5u,X.509 URL,*,Public,[IESG],"[RFC7517, Section 4.6]" +x5c,X.509 Certificate Chain,*,Public,[IESG],"[RFC7517, Section 4.7]" +x5t,X.509 Certificate SHA-1 Thumbprint,*,Public,[IESG],"[RFC7517, Section 4.8]" +x5t#S256,X.509 Certificate SHA-256 Thumbprint,*,Public,[IESG],"[RFC7517, Section 4.9]" +crv,Curve,EC,Public,[IESG],"[RFC7518, Section 6.2.1.1]" +x,X Coordinate,EC,Public,[IESG],"[RFC7518, Section 6.2.1.2]" +y,Y Coordinate,EC,Public,[IESG],"[RFC7518, Section 6.2.1.3]" +d,ECC Private Key,EC,Private,[IESG],"[RFC7518, Section 6.2.2.1]" +n,Modulus,RSA,Public,[IESG],"[RFC7518, Section 6.3.1.1]" +e,Exponent,RSA,Public,[IESG],"[RFC7518, Section 6.3.1.2]" +d,Private Exponent,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.1]" +p,First Prime Factor,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.2]" +q,Second Prime Factor,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.3]" +dp,First Factor CRT Exponent,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.4]" +dq,Second Factor CRT Exponent,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.5]" +qi,First CRT Coefficient,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.6]" +oth,Other Primes Info,RSA,Private,[IESG],"[RFC7518, Section 6.3.2.7]" +k,Key Value,oct,Private,[IESG],"[RFC7518, Section 6.4.1]" +crv,The subtype of key pair,OKP,Public,[IESG],"[RFC8037, Section 2]" +d,The private key,OKP,Private,[IESG],"[RFC8037, Section 2]" +x,The public key,OKP,Public,[IESG],"[RFC8037, Section 2]" +ext,Extractable,*,Public,[W3C_Web_Cryptography_Working_Group],[https://www.w3.org/TR/WebCryptoAPI] +iat,"Issued At, as defined in [RFC7519]",*,Public,[OpenID_Foundation_Artifact_Binding_Working_Group],"[OpenID Federation 1.0, Section 8.7.2]" +nbf,"Not Before, as defined in [RFC7519]",*,Public,[OpenID_Foundation_Artifact_Binding_Working_Group],"[OpenID Federation 1.0, Section 8.7.2]" +exp,"Expiration Time, as defined in [RFC7519]",*,Public,[OpenID_Foundation_Artifact_Binding_Working_Group],"[OpenID Federation 1.0, Section 8.7.2]" +revoked,Revoked Key Properties,*,Public,[OpenID_Foundation_Artifact_Binding_Working_Group],"[OpenID Federation 1.0, Section 8.7.2]" diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index c2f6340..a32e241 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -514,7 +514,132 @@ ${tags} fs.writeFileSync('./src/iana/assignments/cbor.ts', final) } + +// type WebKeyParameter = { + +// } + +const getJose = async () => { + const rows = await getRows<{ + 'Parameter Name': string, + 'Parameter Description': string, + 'Used with "kty" Value(s)': string, + 'Parameter Information Class': string, + 'Change Controller': string, + Reference: string, + }>('./scripts/data/web-key-parameters.csv') + + const byKty = {} as Record + + for (const row of rows) { + if (byKty[row['Used with "kty" Value(s)']] === undefined) { + byKty[row['Used with "kty" Value(s)']] = {} + } + byKty[row['Used with "kty" Value(s)']] = { + ...byKty[row['Used with "kty" Value(s)']], + [row['Parameter Name']]: row + } + } + let definitions = ` + +export type key_type = 'EC' | 'OKP' | 'RSA' | 'oct' +export type key_use = 'enc' | 'sig'; +export type key_ops = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey' | 'deriveBits'; + +/** + * Common JSON Web Key Parameters + * @enum {string} + */ +export const web_key = {\n` + for (const param of Object.keys(byKty['*'])) { + definitions += ` '${param}' : '${param}',\n` + } + definitions += `} as const + + +export type web_key_type = { + 'kty' : key_type, + 'use' ?: key_use, + 'key_ops' ?: key_ops, + 'alg' ?: string, + 'kid' ?: string, + 'x5u' ?: string, + 'x5c' ?: string[], + 'x5t' ?: string, + 'x5t#S256' ?: string, + 'ext' ?: boolean, + 'iat' ?: number, + 'nbf' ?: number, + 'exp' ?: number, + 'revoked' ?: { + revoked_at: number, + reason?: string, + }, +} +` + for (const kty of Object.keys(byKty)) { + if (kty === '*') { + continue + } + definitions += ` +/** + * ${kty} Parameters + * @enum {string} + */ +export const ${kty.toLocaleLowerCase()}_web_key = { + ...web_key, +` + const publicParams = [] + const privateParams = [] + for (const key of Object.keys(byKty[kty])) { + const param = byKty[kty][key] + definitions += ` '${key}' : '${key}',\n` + if (param['Parameter Information Class'].includes('Public')) { + publicParams.push(key) + } else { + privateParams.push(key) + } + + } + definitions += `} as const + +` + if (publicParams.length) { + definitions += `export type public_${kty.toLocaleLowerCase()}_web_key_type = web_key_type & { +${publicParams.map((p) => ` ${p}: string,`).join('\n')} +} +` + } + const extendsKey = publicParams.length ? `public_${kty.toLocaleLowerCase()}_web_key_type` : 'web_key_type' + definitions += `export type private_${kty.toLocaleLowerCase()}_web_key_type = ${extendsKey} & { +${privateParams.map((p) => ` ${p}: string,`).join('\n')} +} + +export const private_${kty.toLocaleLowerCase()}_web_key_params = { +${privateParams.map((p) => ` ${p}: "p",`).join('\n')} +} +` + } + + return definitions +} + + +const makeIanaJoseAssignments = async () => { + const definitions = await getJose(); + const final = ` +// DO NOT edit this file, it is generated automatically +/** + * @see {@link https://www.iana.org/assignments/jose} + */ +${definitions} +`.trim() + fs.writeFileSync('./src/iana/assignments/jose.ts', final) +} + + (async () => { + await makeIanaJoseAssignments() await makeIanaCborAssignments() await makeIanaCoseAssignments() })() \ No newline at end of file diff --git a/src/draft.ts b/src/draft.ts new file mode 100644 index 0000000..3e8c29b --- /dev/null +++ b/src/draft.ts @@ -0,0 +1,81 @@ + +import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' + +import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params } from './iana/assignments/jose' + +type algorithm_specified_key_params = { + 'ES256': { + kty: 'EC' + crv: 'P-256' + alg: 'ES256', + x: string + y: string + d?: string + } +} + +export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] + +export const format_web_key = (jwk: JWK) => { + const { kid, alg, kty, crv, x, y, d, ext, ...rest } = jwk + return JSON.parse(JSON.stringify({ + kid, alg, kty, crv, x, y, d, ext, ...rest + })) +} + +const without_private_information = (jwk: JWK, private_params: Record) => { + const public_information = {} as Record + for (const [key, value] of Object.entries(jwk)) { + if (key in private_params) { + continue + } + public_information[key] = value + } + return format_web_key(public_information) as T +} + +export const export_public_web_key_with_algorithm = async (k: KeyLike, alg: 'ES256', ext: boolean, kid?: string): Promise> => { + const jwk = await exportJWK(k); + jwk.alg = alg + jwk.ext = ext + jwk.kid = kid || await calculateJwkThumbprint(jwk) + const { kty } = jwk + switch (kty) { + case 'RSA': { + return without_private_information(jwk, private_rsa_web_key_params) + } + case 'EC': { + return without_private_information(jwk, private_ec_web_key_params) + } + case 'OKP': { + return without_private_information(jwk, private_okp_web_key_params) + } + case 'oct': { + return without_private_information(jwk, private_oct_web_key_params) + } + default: { + throw new Error('Unknown key type: ' + kty) + } + } +} + +export const export_private_web_key_with_algorithm = async (k: KeyLike, alg: 'ES256'): Promise> => { + const privateKey = await exportJWK(k); + privateKey.alg = alg + privateKey.ext = true; // impossible to export otherwise. + privateKey.kid = await calculateJwkThumbprint(privateKey) + return format_web_key(privateKey) as fully_specified_web_key +} + +export type RequestFullySpecifiedWebKey = { + alg: 'ES256', + ext: boolean, + kid?: string +} +export const generate_web_key = async ({ alg, ext, kid }: RequestFullySpecifiedWebKey) => { + const k = await generateKeyPair(alg, { extractable: ext }) + return { + publicKey: await export_public_web_key_with_algorithm(k.publicKey, alg, ext, kid), + privateKey: await export_private_web_key_with_algorithm(k.privateKey, alg), + } +} diff --git a/src/iana/assignments/jose.ts b/src/iana/assignments/jose.ts new file mode 100644 index 0000000..91a7027 --- /dev/null +++ b/src/iana/assignments/jose.ts @@ -0,0 +1,157 @@ +// DO NOT edit this file, it is generated automatically +/** + * @see {@link https://www.iana.org/assignments/jose} + */ + + +export type key_type = 'EC' | 'OKP' | 'RSA' | 'oct' +export type key_use = 'enc' | 'sig'; +export type key_ops = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey' | 'deriveBits'; + +/** + * Common JSON Web Key Parameters + * @enum {string} + */ +export const web_key = { + 'kty' : 'kty', + 'use' : 'use', + 'key_ops' : 'key_ops', + 'alg' : 'alg', + 'kid' : 'kid', + 'x5u' : 'x5u', + 'x5c' : 'x5c', + 'x5t' : 'x5t', + 'x5t#S256' : 'x5t#S256', + 'ext' : 'ext', + 'iat' : 'iat', + 'nbf' : 'nbf', + 'exp' : 'exp', + 'revoked' : 'revoked', +} as const + + +export type web_key_type = { + 'kty' : key_type, + 'use' ?: key_use, + 'key_ops' ?: key_ops, + 'alg' ?: string, + 'kid' ?: string, + 'x5u' ?: string, + 'x5c' ?: string[], + 'x5t' ?: string, + 'x5t#S256' ?: string, + 'ext' ?: boolean, + 'iat' ?: number, + 'nbf' ?: number, + 'exp' ?: number, + 'revoked' ?: { + revoked_at: number, + reason?: string, + }, +} + +/** + * EC Parameters + * @enum {string} + */ +export const ec_web_key = { + ...web_key, + 'crv' : 'crv', + 'x' : 'x', + 'y' : 'y', + 'd' : 'd', +} as const + +export type public_ec_web_key_type = web_key_type & { + crv: string, + x: string, + y: string, +} +export type private_ec_web_key_type = public_ec_web_key_type & { + d: string, +} + +export const private_ec_web_key_params = { + d: "p", +} + +/** + * RSA Parameters + * @enum {string} + */ +export const rsa_web_key = { + ...web_key, + 'n' : 'n', + 'e' : 'e', + 'd' : 'd', + 'p' : 'p', + 'q' : 'q', + 'dp' : 'dp', + 'dq' : 'dq', + 'qi' : 'qi', + 'oth' : 'oth', +} as const + +export type public_rsa_web_key_type = web_key_type & { + n: string, + e: string, +} +export type private_rsa_web_key_type = public_rsa_web_key_type & { + d: string, + p: string, + q: string, + dp: string, + dq: string, + qi: string, + oth: string, +} + +export const private_rsa_web_key_params = { + d: "p", + p: "p", + q: "p", + dp: "p", + dq: "p", + qi: "p", + oth: "p", +} + +/** + * oct Parameters + * @enum {string} + */ +export const oct_web_key = { + ...web_key, + 'k' : 'k', +} as const + +export type private_oct_web_key_type = web_key_type & { + k: string, +} + +export const private_oct_web_key_params = { + k: "p", +} + +/** + * OKP Parameters + * @enum {string} + */ +export const okp_web_key = { + ...web_key, + 'crv' : 'crv', + 'd' : 'd', + 'x' : 'x', +} as const + +export type public_okp_web_key_type = web_key_type & { + crv: string, + x: string, +} +export type private_okp_web_key_type = public_okp_web_key_type & { + d: string, +} + +export const private_okp_web_key_params = { + d: "p", +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 6d18d16..269f37d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ export * from './x509' export * from './desugar' +export * from './draft' // https://github.com/dajiaji/hpke-js/issues/302 // this issue also effect vercel ncc diff --git a/tests/jose.type.sanity.test.ts b/tests/jose.type.sanity.test.ts new file mode 100644 index 0000000..213f30c --- /dev/null +++ b/tests/jose.type.sanity.test.ts @@ -0,0 +1,32 @@ +import * as jose from 'jose' + +import { fully_specified_web_key, generate_web_key } from '../src/draft' + +it('without type safety', async () => { + const k = await jose.generateKeyPair('ES256') + const publicKey = await jose.exportJWK(k.publicKey) + const { kty, crv, alg, x } = publicKey + expect(alg === undefined).toBe(true) // bad but legal + expect(kty).toBe('EC') + expect(crv).toBe('P-256') // string, but we know it must be P-256 + expect(x).toBeDefined() +}) + +it('fully specified key', async () => { + const { publicKey } = await generate_web_key({ alg: 'ES256', ext: true }) + const { kid, kty, crv, alg, x } = publicKey + expect(kid).toBeDefined() // default key identifier + expect(alg === 'ES256').toBe(true) // good (and with type checking) + expect(kty).toBe('EC') + expect(crv).toBe('P-256') // type specifies the curve and algorithm fully + expect(x).toBeDefined() +}) + +it('custom key identifier', async () => { + const { publicKey } = await generate_web_key({ alg: 'ES256', ext: true, kid: 'magic-key-42' }) + type extended_web_key_type = fully_specified_web_key<'ES256'> & Required<{ kid: 'magic-key-42' }> + const k = publicKey as extended_web_key_type + expect(k.kid).toBe('magic-key-42') // type safe key identifier +}) + + From f340f03e87d47a414128bfefd353cbefc528218e Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 13:04:51 -0500 Subject: [PATCH 16/67] factoring --- scripts/make-iana-assignments.ts | 7 +++++ src/cose/key/convertJsonWebKeyToCoseKey.ts | 25 +++++++++-------- src/cose/key/publicFromPrivate.ts | 12 ++++---- src/crypto/signer.ts | 3 +- ...t-ietf-jose-fully-specified-algorithms.ts} | 20 +++++++------ src/iana/assignments/jose.ts | 7 +++++ src/index.ts | 2 +- tests/jose.type.sanity.test.ts | 28 +++++++++---------- tests/key.test.ts | 16 +++++------ tests/readme.test.ts | 11 ++++---- tests/receipt.test.ts | 8 +++--- 11 files changed, 77 insertions(+), 62 deletions(-) rename src/{draft.ts => drafts/draft-ietf-jose-fully-specified-algorithms.ts} (79%) diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index a32e241..47d215b 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -542,6 +542,13 @@ const getJose = async () => { } let definitions = ` +export const jose_key_type = { + RSA: 'RSA', + EC: 'EC', + OKP: 'OKP', + oct: 'oct' +} as const + export type key_type = 'EC' | 'OKP' | 'RSA' | 'oct' export type key_use = 'enc' | 'sig'; export type key_ops = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey' | 'deriveBits'; diff --git a/src/cose/key/convertJsonWebKeyToCoseKey.ts b/src/cose/key/convertJsonWebKeyToCoseKey.ts index f77e920..81c7948 100644 --- a/src/cose/key/convertJsonWebKeyToCoseKey.ts +++ b/src/cose/key/convertJsonWebKeyToCoseKey.ts @@ -1,37 +1,38 @@ -import { base64url } from 'jose' +import { base64url, JWK } from 'jose' import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' -export const convertJsonWebKeyToCoseKey = async (jwk: Record): Promise => { +import { jose_key_type, ec_web_key } from '../../iana/assignments/jose' + +export const convertJsonWebKeyToCoseKey = async (jwk: JWK): Promise => { const coseKey = new Map(); const { kty } = jwk for (const [key, value] of Object.entries(jwk)) { switch (kty) { - // we should be used iana namespace here - // instead of magic strings - case 'EC': { + // todo key use and key_ops + case jose_key_type.EC: { switch (key) { - case 'kty': { + case ec_web_key.kty: { coseKey.set(ec2_params_to_labels.get(key), key_type_to_label.get(value as string)) break; } - case 'crv': { + case ec_web_key.crv: { coseKey.set(ec2_params_to_labels.get(key), curve_to_label.get(value as string)) break; } - case 'alg': { + case ec_web_key.alg: { const maybeUnknown = algorithms_to_labels.get(value as string) || value as string coseKey.set(ec2_params_to_labels.get(key), maybeUnknown) break; } - case 'kid': { + case ec_web_key.kid: { coseKey.set(ec2_params_to_labels.get(key), value as string) break; } - case 'x': - case 'y': - case 'd': { + case ec_web_key.x: + case ec_web_key.y: + case ec_web_key.d: { // todo check lengths based on curves coseKey.set(ec2_params_to_labels.get(key), Buffer.from(base64url.decode(value as string))) break; diff --git a/src/cose/key/publicFromPrivate.ts b/src/cose/key/publicFromPrivate.ts index 8ed5734..e116b7a 100644 --- a/src/cose/key/publicFromPrivate.ts +++ b/src/cose/key/publicFromPrivate.ts @@ -1,11 +1,9 @@ import * as cose from "../../iana/assignments/cose"; -import { PrivateKeyJwk } from "../sign1"; +import { JWK } from 'jose' - - -export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => { +export const extractPublicKeyJwk = (privateKeyJwk: JWK) => { if (privateKeyJwk.kty !== 'EC') { throw new Error('Only EC keys are supported') } @@ -26,9 +24,9 @@ export const extractPublicCoseKey = return publicCoseKeyMap as T } -export const publicFromPrivate = (privateKey: PrivateKeyJwk | cose.any_cose_key) => { - if ((privateKey as PrivateKeyJwk).kty) { - return extractPublicKeyJwk(privateKey as PrivateKeyJwk) as T +export const publicFromPrivate = (privateKey: JWK | cose.any_cose_key) => { + if ((privateKey as JWK).kty) { + return extractPublicKeyJwk(privateKey as JWK) as T } return extractPublicCoseKey(privateKey as cose.any_cose_key) as T } \ No newline at end of file diff --git a/src/crypto/signer.ts b/src/crypto/signer.ts index c131cef..23e16d9 100644 --- a/src/crypto/signer.ts +++ b/src/crypto/signer.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +import { JWK } from 'jose' import { toArrayBuffer } from '../cbor' import { PrivateKeyJwk } from '../cose/sign1' @@ -7,7 +8,7 @@ import subtleCryptoProvider from './subtleCryptoProvider' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' -const signer = ({ privateKeyJwk }: { privateKeyJwk: PrivateKeyJwk }) => { +const signer = ({ privateKeyJwk }: { privateKeyJwk: JWK }) => { const digest = getDigestFromVerificationKey(`${privateKeyJwk.alg}`) const { alg, ...withoutAlg } = privateKeyJwk return { diff --git a/src/draft.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts similarity index 79% rename from src/draft.ts rename to src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 3e8c29b..3a2b0ef 100644 --- a/src/draft.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -1,7 +1,9 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' -import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params } from './iana/assignments/jose' +import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../iana/assignments/jose' + +type signature_algorithms = 'ES256' type algorithm_specified_key_params = { 'ES256': { @@ -14,7 +16,7 @@ type algorithm_specified_key_params = { } } -export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] +export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] export const format_web_key = (jwk: JWK) => { const { kid, alg, kty, crv, x, y, d, ext, ...rest } = jwk @@ -34,23 +36,23 @@ const without_private_information = (jwk: JWK, private_params: Record(k: KeyLike, alg: 'ES256', ext: boolean, kid?: string): Promise> => { +export const export_public_web_key_with_algorithm = async (k: KeyLike, alg: signature_algorithms, ext: boolean, kid?: string): Promise> => { const jwk = await exportJWK(k); jwk.alg = alg jwk.ext = ext jwk.kid = kid || await calculateJwkThumbprint(jwk) const { kty } = jwk switch (kty) { - case 'RSA': { + case jose_key_type.RSA: { return without_private_information(jwk, private_rsa_web_key_params) } - case 'EC': { + case jose_key_type.EC: { return without_private_information(jwk, private_ec_web_key_params) } - case 'OKP': { + case jose_key_type.OKP: { return without_private_information(jwk, private_okp_web_key_params) } - case 'oct': { + case jose_key_type.oct: { return without_private_information(jwk, private_oct_web_key_params) } default: { @@ -59,7 +61,7 @@ export const export_public_web_key_with_algorithm = async (k: } } -export const export_private_web_key_with_algorithm = async (k: KeyLike, alg: 'ES256'): Promise> => { +export const export_private_web_key_with_algorithm = async (k: KeyLike, alg: signature_algorithms): Promise> => { const privateKey = await exportJWK(k); privateKey.alg = alg privateKey.ext = true; // impossible to export otherwise. @@ -68,7 +70,7 @@ export const export_private_web_key_with_algorithm = async (k } export type RequestFullySpecifiedWebKey = { - alg: 'ES256', + alg: signature_algorithms, ext: boolean, kid?: string } diff --git a/src/iana/assignments/jose.ts b/src/iana/assignments/jose.ts index 91a7027..4bd4d7c 100644 --- a/src/iana/assignments/jose.ts +++ b/src/iana/assignments/jose.ts @@ -4,6 +4,13 @@ */ +export const jose_key_type = { + RSA: 'RSA', + EC: 'EC', + OKP: 'OKP', + oct: 'oct' +} as const + export type key_type = 'EC' | 'OKP' | 'RSA' | 'oct' export type key_use = 'enc' | 'sig'; export type key_ops = 'sign' | 'verify' | 'encrypt' | 'decrypt' | 'wrapKey' | 'unwrapKey' | 'deriveKey' | 'deriveBits'; diff --git a/src/index.ts b/src/index.ts index 269f37d..858bec9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,7 @@ export * from './x509' export * from './desugar' -export * from './draft' +export * from './drafts/draft-ietf-jose-fully-specified-algorithms' // https://github.com/dajiaji/hpke-js/issues/302 // this issue also effect vercel ncc diff --git a/tests/jose.type.sanity.test.ts b/tests/jose.type.sanity.test.ts index 213f30c..4b4d08c 100644 --- a/tests/jose.type.sanity.test.ts +++ b/tests/jose.type.sanity.test.ts @@ -1,24 +1,24 @@ import * as jose from 'jose' -import { fully_specified_web_key, generate_web_key } from '../src/draft' +import { fully_specified_web_key, generate_web_key } from '../src/drafts/draft-ietf-jose-fully-specified-algorithms' it('without type safety', async () => { - const k = await jose.generateKeyPair('ES256') - const publicKey = await jose.exportJWK(k.publicKey) - const { kty, crv, alg, x } = publicKey - expect(alg === undefined).toBe(true) // bad but legal - expect(kty).toBe('EC') - expect(crv).toBe('P-256') // string, but we know it must be P-256 + const { publicKey } = await jose.generateKeyPair('ES256') + const { kty, crv, alg, x } = await jose.exportJWK(publicKey) + expect(alg).toBeUndefined() // bad... but legal + // kty has type string, + expect(kty).toBe('EC') // ... but we know it must be EC + // crv has type string, + expect(crv).toBe('P-256') // ... but we know it must be P-256 expect(x).toBeDefined() }) it('fully specified key', async () => { - const { publicKey } = await generate_web_key({ alg: 'ES256', ext: true }) - const { kid, kty, crv, alg, x } = publicKey - expect(kid).toBeDefined() // default key identifier - expect(alg === 'ES256').toBe(true) // good (and with type checking) - expect(kty).toBe('EC') - expect(crv).toBe('P-256') // type specifies the curve and algorithm fully + // type specifies the curve and algorithm fully + const { publicKey: { kty, crv, alg, x } } = await generate_web_key({ alg: 'ES256', ext: true }) + expect(alg).toBe('ES256') // narrowed + expect(kty).toBe('EC') // narrowed + expect(crv).toBe('P-256') // narrowed expect(x).toBeDefined() }) @@ -28,5 +28,3 @@ it('custom key identifier', async () => { const k = publicKey as extended_web_key_type expect(k.kid).toBe('magic-key-42') // type safe key identifier }) - - diff --git a/tests/key.test.ts b/tests/key.test.ts index 043c2a1..abb7d42 100644 --- a/tests/key.test.ts +++ b/tests/key.test.ts @@ -1,23 +1,23 @@ -import { base64url } from 'jose' +import { base64url, JWK } from 'jose' import * as cose from '../src' it('ES256', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); expect(privateKeyJwk.alg).toBe('ES256') }) it('conversion', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) - const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) + const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) expect(privateKey.alg).toBe('ES256') }) it('thumbprint', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) - const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) + const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) expect(privateKey.alg).toBe('ES256') }) @@ -44,10 +44,10 @@ it('generate thumbprints', async () => { }) it('public from private', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...expectedPublicKeyJwk } = privateKeyJwk - const publicKeyJwk = cose.key.publicFromPrivate(privateKeyJwk) + const publicKeyJwk = cose.key.publicFromPrivate(privateKeyJwk) expect(publicKeyJwk).toEqual(expectedPublicKeyJwk) const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') const expectedPublicKeyCose = new Map(privateKeyCose.entries()) diff --git a/tests/readme.test.ts b/tests/readme.test.ts index c812e60..400fe72 100644 --- a/tests/readme.test.ts +++ b/tests/readme.test.ts @@ -1,12 +1,13 @@ import fs from 'fs' +import { JWK } from 'jose' import * as cose from '../src' it('readme', async () => { - const issuerSecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - const issuerPublicKeyJwk = await cose.key.publicFromPrivate(issuerSecretKeyJwk) + const issuerSecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const issuerPublicKeyJwk = await cose.key.publicFromPrivate(issuerSecretKeyJwk) - const notarySecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - const notaryPublicKeyJwk = await cose.key.publicFromPrivate(notarySecretKeyJwk) + const notarySecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const notaryPublicKeyJwk = await cose.key.publicFromPrivate(notarySecretKeyJwk) const issuer = cose.detached.signer({ remote: cose.crypto.signer({ @@ -41,7 +42,7 @@ it('readme', async () => { signer: notary }) const transparentSignature = await cose.receipt.add(signatureForImage, receiptForImageSignature) - const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { + const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index bf7075a..a3e06aa 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -2,7 +2,7 @@ import fs from 'fs' - +import { JWK } from 'jose' import * as cose from '../src' const encoder = new TextEncoder(); @@ -17,7 +17,7 @@ it('issue & verify', async () => { })) - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ @@ -71,8 +71,8 @@ it('issue & verify', async () => { }) it("add / remove from receipts", async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - const publicKeyJwk = await cose.key.publicFromPrivate(privateKeyJwk) + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const publicKeyJwk = await cose.key.publicFromPrivate(privateKeyJwk) const signer = cose.detached.signer({ remote: cose.crypto.signer({ privateKeyJwk From a671731e5defe356b5e790758cf4144202de1a34 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 13:11:40 -0500 Subject: [PATCH 17/67] cleaning --- src/crypto/verifier.ts | 5 +-- ...ft-ietf-jose-fully-specified-algorithms.ts | 41 ++++++++++++++----- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index a8640b2..1c4534d 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -1,13 +1,12 @@ - +import { JWK } from 'jose' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' import subtleCryptoProvider from './subtleCryptoProvider' -import { PublicKeyJwk } from '../cose/sign1' -const verifier = ({ publicKeyJwk }: { publicKeyJwk: PublicKeyJwk }) => { +const verifier = ({ publicKeyJwk }: { publicKeyJwk: JWK }) => { const digest = getDigestFromVerificationKey(`${publicKeyJwk.alg}`) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { alg, ...withoutAlg } = publicKeyJwk diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 3a2b0ef..3d5fe29 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -3,9 +3,7 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../iana/assignments/jose' -type signature_algorithms = 'ES256' - -type algorithm_specified_key_params = { +export type algorithm_specified_key_params = { 'ES256': { kty: 'EC' crv: 'P-256' @@ -13,10 +11,26 @@ type algorithm_specified_key_params = { x: string y: string d?: string + }, + 'ES384': { + kty: 'EC' + crv: 'P-384' + alg: 'ES384', + x: string + y: string + d?: string + }, + 'Ed25519': { + kty: 'OKP' + crv: 'Ed25519' + alg: 'Ed25519', + x: string + d?: string } } -export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] +export type fully_specified_signature_algorithms = keyof algorithm_specified_key_params +export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] export const format_web_key = (jwk: JWK) => { const { kid, alg, kty, crv, x, y, d, ext, ...rest } = jwk @@ -36,7 +50,12 @@ const without_private_information = (jwk: JWK, private_params: Record(k: KeyLike, alg: signature_algorithms, ext: boolean, kid?: string): Promise> => { +export const export_public_web_key_with_algorithm = async ( + k: KeyLike, + alg: fully_specified_signature_algorithms, + ext: boolean, + kid?: string +): Promise> => { const jwk = await exportJWK(k); jwk.alg = alg jwk.ext = ext @@ -61,7 +80,10 @@ export const export_public_web_key_with_algorithm = async (k: KeyLike, alg: signature_algorithms): Promise> => { +export const export_private_web_key_with_algorithm = async ( + k: KeyLike, + alg: fully_specified_signature_algorithms +): Promise> => { const privateKey = await exportJWK(k); privateKey.alg = alg privateKey.ext = true; // impossible to export otherwise. @@ -69,12 +91,11 @@ export const export_private_web_key_with_algorithm = async } -export type RequestFullySpecifiedWebKey = { - alg: signature_algorithms, +export const generate_web_key = async ({ alg, ext, kid }: { + alg: fully_specified_signature_algorithms, ext: boolean, kid?: string -} -export const generate_web_key = async ({ alg, ext, kid }: RequestFullySpecifiedWebKey) => { +}) => { const k = await generateKeyPair(alg, { extractable: ext }) return { publicKey: await export_public_web_key_with_algorithm(k.publicKey, alg, ext, kid), From bc27203fdbd415c0bcbe1089d27d7e765f5e7972 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 14:42:00 -0500 Subject: [PATCH 18/67] cleaning --- src/crypto/index.ts | 5 +- src/crypto/key.ts | 62 ++++++++++++++++ src/crypto/web_key_to_cose_key.ts | 52 ++++++++++++++ ...ft-ietf-jose-fully-specified-algorithms.ts | 62 ++++++++++++++-- src/iana/assignments/media-types.ts | 3 + src/x509/certificate.ts | 6 +- tests/crypto.test.ts | 72 +++++++++++++++++++ tests/fully-specified.test.ts | 5 +- tests/sign1.attached.test.ts | 6 +- tests/sign1.detached.test.ts | 6 +- tests/signer.test.ts | 3 +- tests/verifiers.test.ts | 8 ++- 12 files changed, 270 insertions(+), 20 deletions(-) create mode 100644 src/crypto/key.ts create mode 100644 src/crypto/web_key_to_cose_key.ts create mode 100644 tests/crypto.test.ts diff --git a/src/crypto/index.ts b/src/crypto/index.ts index ac9eebb..e5feb4e 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -2,4 +2,7 @@ import signer from "./signer"; import verifier from "./verifier"; -export { signer, verifier } \ No newline at end of file +import * as key from './key' + + +export { signer, verifier, key } \ No newline at end of file diff --git a/src/crypto/key.ts b/src/crypto/key.ts new file mode 100644 index 0000000..2b87972 --- /dev/null +++ b/src/crypto/key.ts @@ -0,0 +1,62 @@ + + + +import * as cbor from 'cbor-web' + +import { crypto_key_type } from '../iana/assignments/media-types' +import { fully_specified_signature_algorithms, generate_web_key } from "../drafts/draft-ietf-jose-fully-specified-algorithms" + +import { web_key_to_cose_key } from './web_key_to_cose_key' +import { parse } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; + +export { parse } +const encoder = new TextEncoder() +const decoder = new TextDecoder() + +export type request_crypto_key = { + + type: crypto_key_type, + algorithm: fully_specified_signature_algorithms + + id?: string, + extractable?: boolean +} + +export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key) => { + switch (type) { + case 'application/jwk+json': { + const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) + return encoder.encode(JSON.stringify(privateKey)) + } + case 'application/cose-key': { + const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) + return convert({ + key: encoder.encode(JSON.stringify(privateKey)), + from: 'application/jwk+json', + to: 'application/cose-key' + }) + } + default: { + throw new Error('Unsupported key type: ' + type) + } + } +} + +export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto_key_type, to: crypto_key_type }) => { + switch (from) { + case 'application/jwk+json': { + switch (to) { + case 'application/cose-key': { + const k = await web_key_to_cose_key(JSON.parse(decoder.decode(key))) + return cbor.encode(k) + } + default: { + throw new Error('Unknown key: ' + from) + } + } + } + default: { + throw new Error('Unknown key: ' + from) + } + } +} diff --git a/src/crypto/web_key_to_cose_key.ts b/src/crypto/web_key_to_cose_key.ts new file mode 100644 index 0000000..bce4875 --- /dev/null +++ b/src/crypto/web_key_to_cose_key.ts @@ -0,0 +1,52 @@ + +import { base64url, JWK } from 'jose' +import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../iana/assignments/cose' +import { algorithms_to_labels } from '../iana/requested/cose' +import { jose_key_type, ec_web_key } from '../iana/assignments/jose' + +export const web_key_to_cose_key = async (jwk: JWK): Promise => { + const coseKey = new Map(); + const { kty } = jwk + for (const [key, value] of Object.entries(jwk)) { + switch (kty) { + // todo key use and key_ops + case jose_key_type.EC: { + switch (key) { + case ec_web_key.kty: { + coseKey.set(ec2_params_to_labels.get(key), key_type_to_label.get(value as string)) + break; + } + case ec_web_key.crv: { + coseKey.set(ec2_params_to_labels.get(key), curve_to_label.get(value as string)) + break; + } + case ec_web_key.alg: { + const maybeUnknown = algorithms_to_labels.get(value as string) || value as string + coseKey.set(ec2_params_to_labels.get(key), maybeUnknown) + break; + } + case ec_web_key.kid: { + coseKey.set(ec2_params_to_labels.get(key), value as string) + break; + } + case ec_web_key.x: + case ec_web_key.y: + case ec_web_key.d: { + // todo check lengths based on curves + coseKey.set(ec2_params_to_labels.get(key), Buffer.from(base64url.decode(value as string))) + break; + } + default: { + coseKey.set(key, value) + } + } + break; + } + default: { + coseKey.set(key, value) + } + } + } + return coseKey as T +} + diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 3d5fe29..948feba 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -1,9 +1,23 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' - +import { crypto_key_type } from '../iana/assignments/media-types' import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../iana/assignments/jose' +import { ec2_key } from '../iana/assignments/cose' + +import * as cbor from 'cbor-web' + +const encoder = new TextEncoder() +const decoder = new TextDecoder() -export type algorithm_specified_key_params = { +export type algorithm_specified_web_key_params = { + 'ESP256': { + kty: 'EC' + crv: 'P-256' + alg: 'ES256', + x: string + y: string + d?: string + }, 'ES256': { kty: 'EC' crv: 'P-256' @@ -29,8 +43,16 @@ export type algorithm_specified_key_params = { } } -export type fully_specified_signature_algorithms = keyof algorithm_specified_key_params -export type fully_specified_web_key = web_key_type & algorithm_specified_key_params[T] +export type algorithm_specified_cose_key_params = { + 'ES256': ec2_key & { get(k: -1): 1 | 2 | 3 } + 'ESP256': ec2_key & { get(k: -1): 1 } +} + +export type fully_specified_signature_algorithms = keyof algorithm_specified_web_key_params +export type fully_specified_web_key = web_key_type & algorithm_specified_web_key_params[T] + +export type fully_specified_cose_signature_algorithms = keyof algorithm_specified_cose_key_params +export type fully_specified_cose_key = algorithm_specified_cose_key_params[T] export const format_web_key = (jwk: JWK) => { const { kid, alg, kty, crv, x, y, d, ext, ...rest } = jwk @@ -82,12 +104,13 @@ export const export_public_web_key_with_algorithm = async ( k: KeyLike, - alg: fully_specified_signature_algorithms + alg: fully_specified_signature_algorithms, + kid?: string ): Promise> => { const privateKey = await exportJWK(k); privateKey.alg = alg privateKey.ext = true; // impossible to export otherwise. - privateKey.kid = await calculateJwkThumbprint(privateKey) + privateKey.kid = kid || await calculateJwkThumbprint(privateKey) return format_web_key(privateKey) as fully_specified_web_key } @@ -99,6 +122,31 @@ export const generate_web_key = async ({ alg, ext, kid }: { const k = await generateKeyPair(alg, { extractable: ext }) return { publicKey: await export_public_web_key_with_algorithm(k.publicKey, alg, ext, kid), - privateKey: await export_private_web_key_with_algorithm(k.privateKey, alg), + privateKey: await export_private_web_key_with_algorithm(k.privateKey, alg, kid), } } + +export type parseable_fully_specified_signature_algorithms = 'ES256' | 'ESP256' +export type parsable_fully_specified_keys = + cty extends 'application/jwk+json' ? fully_specified_web_key : cty extends 'application/cose-key' ? fully_specified_cose_key : unknown + +export const parse = < + alg extends parseable_fully_specified_signature_algorithms, + cty extends crypto_key_type +>({ key, type }: { + key: Uint8Array, + type: cty +}): parsable_fully_specified_keys => { + switch (type) { + case 'application/jwk+json': { + const jwk = JSON.parse(decoder.decode(key)) + return jwk + } + case 'application/cose-key': { + return cbor.decode(key) + } + default: { + throw new Error('Unknown key: ' + type) + } + } +} \ No newline at end of file diff --git a/src/iana/assignments/media-types.ts b/src/iana/assignments/media-types.ts index 4c92414..dd9774a 100644 --- a/src/iana/assignments/media-types.ts +++ b/src/iana/assignments/media-types.ts @@ -1,4 +1,7 @@ export type application_cose = 'application/cose' export type application_cose_key = 'application/cose-key' +export type application_jwk = 'application/jwk+json' + +export type crypto_key_type = application_cose_key | application_jwk export type diagnostic_types = application_cose | application_cose_key \ No newline at end of file diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 0f9f08c..a940bd2 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -2,9 +2,9 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; import { PublicKeyJwk } from "../cose/sign1" import * as x509 from "@peculiar/x509"; import { CoseSignatureAlgorithms } from '../cose/key'; -import { PrivateKeyJwk, detached, RequestCoseSign1VerifyDetached } from '..'; +import { detached, RequestCoseSign1VerifyDetached } from '..'; import { crypto } from '..'; - +import { JWK } from 'jose' import * as cose from '../iana/assignments/cose'; import { labels_to_algorithms } from '../iana/requested/cose'; @@ -99,7 +99,7 @@ const root = async (req: RequestRootCertificate): Promise { const algName = labels_to_algorithms.get(alg) - const privateKeyJwk = await exportJWK(await importPKCS8(privateKeyPKCS8, `${algName}`)) as PrivateKeyJwk + const privateKeyJwk = await exportJWK(await importPKCS8(privateKeyPKCS8, `${algName}`)) as JWK privateKeyJwk.alg = algName; return detached.signer({ remote: crypto.signer({ diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts new file mode 100644 index 0000000..e38e0e5 --- /dev/null +++ b/tests/crypto.test.ts @@ -0,0 +1,72 @@ + +import { crypto } from '../src' +import * as cbor from 'cbor-web' +import * as cose from '../src/iana/assignments/cose' +import * as jose from '../src/iana/assignments/jose' + + +describe('web key', () => { + it('generate', async () => { + const key = new Map(Object.entries(JSON.parse(new TextDecoder().decode(await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'ES256' + }))))) + expect(key.get(jose.web_key.kid)).toBe('magic-key') + expect(key.get(jose.web_key.alg)).toBe('ES256') + }) + it('parse', async () => { + const jwk = crypto.key.parse<'ES256', 'application/jwk+json'>({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'ES256' + }), + type: 'application/jwk+json', + }) + const key = new Map(Object.entries(jwk)) + expect(key.get(jose.web_key.kid)).toBe('magic-key') + expect(key.get(jose.web_key.alg)).toBe('ES256') + }) +}) + +describe('cose key', () => { + it('generate', async () => { + const bytes = await crypto.key.generate({ + id: 'magic-key', + type: 'application/cose-key', + algorithm: 'ES256' + }) + const key = cbor.decode(bytes) + expect(key.get(cose.cose_key.kid)).toBe('magic-key') + expect(key.get(cose.cose_key.alg)).toBe(cose.algorithm.es256) + }) + + it('parse polymorphic', async () => { + const key = crypto.key.parse<'ES256', 'application/cose-key'>({ + key: await crypto.key.generate({ + type: 'application/cose-key', + algorithm: 'ES256' + }), + type: 'application/cose-key', + }) + const crv = key.get(cose.ec2.crv); + expect(crv).toBe(cose.ec2_curves.p_256) + // crv type can only narrow to 1, 2, 3 + }) + + it('parse fully specified', async () => { + const key = crypto.key.parse<'ESP256', 'application/cose-key'>({ + key: await crypto.key.generate({ + type: 'application/cose-key', + algorithm: 'ES256' + }), + type: 'application/cose-key', + }) + const crv = key.get(cose.ec2.crv); + expect(crv).toBe(cose.ec2_curves.p_256) + // crv type is narrowed to 1 + }) +}) + + diff --git a/tests/fully-specified.test.ts b/tests/fully-specified.test.ts index 654d086..72431eb 100644 --- a/tests/fully-specified.test.ts +++ b/tests/fully-specified.test.ts @@ -2,6 +2,9 @@ import * as cose from '../src' import { CoseSignatureAlgorithms } from '../src/cose/key' + +import { JWK } from 'jose' + const message = '💣 test ✨ mesage 🔥' const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { @@ -18,7 +21,7 @@ const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { coseSign1: await cose.attached .signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey) + privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey) }) }) .sign({ diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index 04c95bd..180eef5 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -1,8 +1,10 @@ import fs from 'fs' import * as cose from '../src' +import { JWK } from 'jose' + it('sign and verify', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.attached.signer({ @@ -32,7 +34,7 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.attached.signer({ diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index c9baca3..ee23356 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -1,8 +1,10 @@ import fs from 'fs' import * as cose from '../src' +import { JWK } from 'jose' + it('sign and verify', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ @@ -37,7 +39,7 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 6082f8f..860480f 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -1,9 +1,10 @@ import fs from 'fs' import * as cose from '../src' +import { JWK } from 'jose' it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 364121d..cc2ed2f 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -2,25 +2,27 @@ import fs from 'fs' import * as cose from '../src' +import { JWK } from 'jose' + it('verify multiple receipts', async () => { const issuerSecretKey = await cose.key.generate('ES256', 'application/cose-key') const notary1SecretKey = await cose.key.generate('ES256', 'application/cose-key') const notary2SecretKey = await cose.key.generate('ES256', 'application/cose-key') const issuerSigner = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(issuerSecretKey) + privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(issuerSecretKey) }) }) const notary1Signer = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary1SecretKey) + privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary1SecretKey) }) }) const notary2Signer = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary2SecretKey) + privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary2SecretKey) }) }) const issuerCkt = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(issuerSecretKey) From 0087fb9bad75cbb875aef7159c04f1dbfe822e42 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 21 Sep 2024 15:04:10 -0500 Subject: [PATCH 19/67] better examples --- ...ft-ietf-jose-fully-specified-algorithms.ts | 51 +++++++++++++++---- src/iana/requested/cose.ts | 5 +- tests/crypto.test.ts | 31 +++++++++-- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 948feba..4ca40ef 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -2,15 +2,15 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' import { crypto_key_type } from '../iana/assignments/media-types' import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../iana/assignments/jose' -import { ec2_key } from '../iana/assignments/cose' +import { ec2_curves, ec2_key, okp_key, ec2, okp, okp_curves } from '../iana/assignments/cose' import * as cbor from 'cbor-web' +import { less_specified } from '../iana/requested/cose' -const encoder = new TextEncoder() const decoder = new TextDecoder() export type algorithm_specified_web_key_params = { - 'ESP256': { + 'ESP256': web_key_type & { kty: 'EC' crv: 'P-256' alg: 'ES256', @@ -18,7 +18,7 @@ export type algorithm_specified_web_key_params = { y: string d?: string }, - 'ES256': { + 'ES256': web_key_type & { kty: 'EC' crv: 'P-256' alg: 'ES256', @@ -26,7 +26,7 @@ export type algorithm_specified_web_key_params = { y: string d?: string }, - 'ES384': { + 'ES384': web_key_type & { kty: 'EC' crv: 'P-384' alg: 'ES384', @@ -34,18 +34,48 @@ export type algorithm_specified_web_key_params = { y: string d?: string }, - 'Ed25519': { + 'ESP384': web_key_type & { + kty: 'EC' + crv: 'P-384' + alg: 'ES384', + x: string + y: string + d?: string + }, + 'EdDSA': web_key_type & { + kty: 'OKP' + crv: 'Ed25519' | 'Ed448' + alg: 'EdDSA', + x: string + d?: string + }, + 'Ed25519': web_key_type & { kty: 'OKP' crv: 'Ed25519' alg: 'Ed25519', x: string d?: string + }, + 'Ed448': web_key_type & { + kty: 'OKP' + crv: 'Ed448' + alg: 'Ed448', + x: string + d?: string } } export type algorithm_specified_cose_key_params = { - 'ES256': ec2_key & { get(k: -1): 1 | 2 | 3 } - 'ESP256': ec2_key & { get(k: -1): 1 } + 'ES256': ec2_key & { get(k: -1): ec2_curves.p_256 | ec2_curves.p_384 | ec2_curves.p_521 } + 'ESP256': ec2_key & { get(k: -1): ec2_curves.p_256 } + + 'ES384': ec2_key & { get(k: -1): ec2_curves.p_256 | ec2_curves.p_384 | ec2_curves.p_521 } + 'ESP384': ec2_key & { get(k: -1): ec2_curves.p_384 } + + 'EdDSA': okp_key & { get(k: -1): okp_curves.ed25519 | okp_curves.ed448 } + + 'Ed25519': ec2_key & { get(k: -1): okp_curves.ed25519 } + 'Ed448': ec2_key & { get(k: -1): okp_curves.ed448 } } export type fully_specified_signature_algorithms = keyof algorithm_specified_web_key_params @@ -119,14 +149,15 @@ export const generate_web_key = async ({ alg, ext, kid }: { ext: boolean, kid?: string }) => { - const k = await generateKeyPair(alg, { extractable: ext }) + const lessSpecific = less_specified[alg] + const k = await generateKeyPair(lessSpecific, { extractable: ext }) return { publicKey: await export_public_web_key_with_algorithm(k.publicKey, alg, ext, kid), privateKey: await export_private_web_key_with_algorithm(k.privateKey, alg, kid), } } -export type parseable_fully_specified_signature_algorithms = 'ES256' | 'ESP256' +export type parseable_fully_specified_signature_algorithms = keyof algorithm_specified_cose_key_params | keyof algorithm_specified_web_key_params export type parsable_fully_specified_keys = cty extends 'application/jwk+json' ? fully_specified_web_key : cty extends 'application/cose-key' ? fully_specified_cose_key : unknown diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts index b5d2e19..c6e8ba3 100644 --- a/src/iana/requested/cose.ts +++ b/src/iana/requested/cose.ts @@ -12,7 +12,10 @@ export const less_specified = { 'ESP256': 'ES256', 'ES256': 'ES256', 'ES384': 'ES384', - 'ES512': 'ES512' + 'ES512': 'ES512', + 'Ed25519': 'EdDSA', + 'Ed448': 'EdDSA', + 'EdDSA': 'EdDSA' } export enum draft_headers { diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts index e38e0e5..62ca9ca 100644 --- a/tests/crypto.test.ts +++ b/tests/crypto.test.ts @@ -4,7 +4,6 @@ import * as cbor from 'cbor-web' import * as cose from '../src/iana/assignments/cose' import * as jose from '../src/iana/assignments/jose' - describe('web key', () => { it('generate', async () => { const key = new Map(Object.entries(JSON.parse(new TextDecoder().decode(await crypto.key.generate({ @@ -28,6 +27,32 @@ describe('web key', () => { expect(key.get(jose.web_key.kid)).toBe('magic-key') expect(key.get(jose.web_key.alg)).toBe('ES256') }) + + it('parse polymorphic', async () => { + const jwk = crypto.key.parse<'EdDSA', 'application/jwk+json'>({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'EdDSA' + }), + type: 'application/jwk+json', + }) + const { crv } = jwk + expect(crv).toBe('Ed25519') + }) + + it('parse fully specified', async () => { + const jwk = crypto.key.parse<'Ed25519', 'application/jwk+json'>({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'Ed25519' + }), + type: 'application/jwk+json', + }) + const { crv } = jwk + expect(crv).toBe('Ed25519') + }) }) describe('cose key', () => { @@ -42,7 +67,7 @@ describe('cose key', () => { expect(key.get(cose.cose_key.alg)).toBe(cose.algorithm.es256) }) - it('parse polymorphic', async () => { + it('parse polymorphic EC2', async () => { const key = crypto.key.parse<'ES256', 'application/cose-key'>({ key: await crypto.key.generate({ type: 'application/cose-key', @@ -55,7 +80,7 @@ describe('cose key', () => { // crv type can only narrow to 1, 2, 3 }) - it('parse fully specified', async () => { + it('parse fully specified EC2', async () => { const key = crypto.key.parse<'ESP256', 'application/cose-key'>({ key: await crypto.key.generate({ type: 'application/cose-key', From 58f6acf5cf7507f15be164e35cf86a95294d8b2f Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:10:46 -0500 Subject: [PATCH 20/67] partial --- src/cose/key/thumbprint.ts | 1 - src/cose/sign1/types.ts | 2 +- src/crypto/key.ts | 59 +--------------- src/crypto/signer.ts | 2 +- ...ft-ietf-jose-fully-specified-algorithms.ts | 69 ++++++++++++++++++- src/x509/certificate.ts | 6 +- tests/crypto.test.ts | 2 + tests/edn.test.ts | 3 + tests/fully-specified.test.ts | 50 -------------- tests/key.test.ts | 57 --------------- tests/receipt.test.ts | 5 +- 11 files changed, 84 insertions(+), 172 deletions(-) delete mode 100644 tests/fully-specified.test.ts delete mode 100644 tests/key.test.ts diff --git a/src/cose/key/thumbprint.ts b/src/cose/key/thumbprint.ts index 490d257..0be74b5 100644 --- a/src/cose/key/thumbprint.ts +++ b/src/cose/key/thumbprint.ts @@ -34,5 +34,4 @@ export const thumbprint = { calculateJwkThumbprintUri, calculateCoseKeyThumbprint, calculateCoseKeyThumbprintUri, - uri: calculateCoseKeyThumbprintUri } \ No newline at end of file diff --git a/src/cose/sign1/types.ts b/src/cose/sign1/types.ts index fc4de08..ab268a0 100644 --- a/src/cose/sign1/types.ts +++ b/src/cose/sign1/types.ts @@ -32,7 +32,7 @@ export type CoseSign1Signer = { export type RequestCoseSign1Verifier = { resolver: { - resolve: (signature: ArrayBuffer) => Promise + resolve: (signature: ArrayBuffer) => Promise } } diff --git a/src/crypto/key.ts b/src/crypto/key.ts index 2b87972..50e2606 100644 --- a/src/crypto/key.ts +++ b/src/crypto/key.ts @@ -1,62 +1,7 @@ -import * as cbor from 'cbor-web' -import { crypto_key_type } from '../iana/assignments/media-types' -import { fully_specified_signature_algorithms, generate_web_key } from "../drafts/draft-ietf-jose-fully-specified-algorithms" +import { parse, generate, convert, gen } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; -import { web_key_to_cose_key } from './web_key_to_cose_key' -import { parse } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; - -export { parse } -const encoder = new TextEncoder() -const decoder = new TextDecoder() - -export type request_crypto_key = { - - type: crypto_key_type, - algorithm: fully_specified_signature_algorithms - - id?: string, - extractable?: boolean -} - -export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key) => { - switch (type) { - case 'application/jwk+json': { - const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) - return encoder.encode(JSON.stringify(privateKey)) - } - case 'application/cose-key': { - const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) - return convert({ - key: encoder.encode(JSON.stringify(privateKey)), - from: 'application/jwk+json', - to: 'application/cose-key' - }) - } - default: { - throw new Error('Unsupported key type: ' + type) - } - } -} - -export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto_key_type, to: crypto_key_type }) => { - switch (from) { - case 'application/jwk+json': { - switch (to) { - case 'application/cose-key': { - const k = await web_key_to_cose_key(JSON.parse(decoder.decode(key))) - return cbor.encode(k) - } - default: { - throw new Error('Unknown key: ' + from) - } - } - } - default: { - throw new Error('Unknown key: ' + from) - } - } -} +export { parse, generate, convert, gen } diff --git a/src/crypto/signer.ts b/src/crypto/signer.ts index 23e16d9..57e5d31 100644 --- a/src/crypto/signer.ts +++ b/src/crypto/signer.ts @@ -8,7 +8,7 @@ import subtleCryptoProvider from './subtleCryptoProvider' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' -const signer = ({ privateKeyJwk }: { privateKeyJwk: JWK }) => { +const signer = ({ privateKeyJwk }: { privateKeyJwk: JWK | any }) => { const digest = getDigestFromVerificationKey(`${privateKeyJwk.alg}`) const { alg, ...withoutAlg } = privateKeyJwk return { diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 4ca40ef..576ca7a 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -7,6 +7,9 @@ import { ec2_curves, ec2_key, okp_key, ec2, okp, okp_curves } from '../iana/assi import * as cbor from 'cbor-web' import { less_specified } from '../iana/requested/cose' +import { web_key_to_cose_key } from '../crypto/web_key_to_cose_key' + +const encoder = new TextEncoder() const decoder = new TextDecoder() export type algorithm_specified_web_key_params = { @@ -161,6 +164,18 @@ export type parseable_fully_specified_signature_algorithms = keyof algorithm_spe export type parsable_fully_specified_keys = cty extends 'application/jwk+json' ? fully_specified_web_key : cty extends 'application/cose-key' ? fully_specified_cose_key : unknown + + +export type request_crypto_key = { + + type: crypto_key_type, + algorithm: fully_specified_signature_algorithms + + id?: string, + extractable?: boolean +} + + export const parse = < alg extends parseable_fully_specified_signature_algorithms, cty extends crypto_key_type @@ -180,4 +195,56 @@ export const parse = < throw new Error('Unknown key: ' + type) } } -} \ No newline at end of file +} + + +export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key) => { + switch (type) { + case 'application/jwk+json': { + const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) + return encoder.encode(JSON.stringify(privateKey)) + } + case 'application/cose-key': { + const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) + return convert({ + key: encoder.encode(JSON.stringify(privateKey)), + from: 'application/jwk+json', + to: 'application/cose-key' + }) + } + default: { + throw new Error('Unsupported key type: ' + type) + } + } +} + +export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto_key_type, to: crypto_key_type }) => { + switch (from) { + case 'application/jwk+json': { + switch (to) { + case 'application/cose-key': { + const k = await web_key_to_cose_key(JSON.parse(decoder.decode(key))) + return cbor.encode(k) + } + default: { + throw new Error('Unknown key: ' + from) + } + } + } + default: { + throw new Error('Unknown key: ' + from) + } + } +} + +// generate parsed. +export const gen = async < + alg extends parseable_fully_specified_signature_algorithms, + cty extends crypto_key_type +>({ algorithm, type }: { + algorithm: alg, + type: cty +}): Promise> => { + const key = await generate({ algorithm, type }) + return parse({ key, type }) +} diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index a940bd2..3aab8bc 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -1,7 +1,7 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; import { PublicKeyJwk } from "../cose/sign1" import * as x509 from "@peculiar/x509"; -import { CoseSignatureAlgorithms } from '../cose/key'; + import { detached, RequestCoseSign1VerifyDetached } from '..'; import { crypto } from '..'; import { JWK } from 'jose' @@ -21,7 +21,7 @@ const provide = async () => { } } -const algTowebCryptoParams: Record +const algTowebCryptoParams: Record = { 'ESP256': { name: "ECDSA", @@ -51,7 +51,7 @@ const algTowebCryptoParams: Record { }) const { crv } = jwk expect(crv).toBe('Ed25519') + // crv is cannot narrow beyond Ed25519 | Ed448 }) it('parse fully specified', async () => { @@ -52,6 +53,7 @@ describe('web key', () => { }) const { crv } = jwk expect(crv).toBe('Ed25519') + // crv is narrowed to Ed25519 }) }) diff --git a/tests/edn.test.ts b/tests/edn.test.ts index e2afeea..aa89507 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -17,3 +17,6 @@ it('detached payload cose sign1', async () => { // expect(diag).toBe(output.toString()) }) +// Tomorrow... +// More EDN examples for SCITT stuff +// maybe HPKE... \ No newline at end of file diff --git a/tests/fully-specified.test.ts b/tests/fully-specified.test.ts deleted file mode 100644 index 72431eb..0000000 --- a/tests/fully-specified.test.ts +++ /dev/null @@ -1,50 +0,0 @@ - -import * as cose from '../src' -import { CoseSignatureAlgorithms } from '../src/cose/key' - - -import { JWK } from 'jose' - -const message = '💣 test ✨ mesage 🔥' - -const helpTestSignAndVerify = async (privateKey: cose.any_cose_key) => { - const publicKey = await cose.key.extractPublicCoseKey(privateKey) - expect(new TextDecoder().decode(await cose.attached - .verifier({ - resolver: { - resolve: async () => { - return cose.key.convertCoseKeyToJsonWebKey(publicKey) - } - } - }) - .verify({ - coseSign1: await cose.attached - .signer({ - remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey) - }) - }) - .sign({ - protectedHeader: new Map([ - [cose.header.alg, privateKey.get(cose.cose_key.alg)] - ]), - payload: new TextEncoder().encode(message) - }) - }))).toBe(message) -} - -// https://datatracker.ietf.org/doc/draft-ietf-jose-fully-specified-algorithms/ - -const algorithms = [ - "ESP256", - "ESP384" -] as CoseSignatureAlgorithms[] - -algorithms.forEach((alg) => { - it(alg, async () => { - const privateKey = await cose.key.generate(alg, 'application/cose-key') - await helpTestSignAndVerify(privateKey) - }) -}) - - diff --git a/tests/key.test.ts b/tests/key.test.ts deleted file mode 100644 index abb7d42..0000000 --- a/tests/key.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { base64url, JWK } from 'jose' - -import * as cose from '../src' - -it('ES256', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); - expect(privateKeyJwk.alg).toBe('ES256') -}) - -it('conversion', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); - const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) - const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) - expect(privateKey.alg).toBe('ES256') -}) - -it('thumbprint', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json'); - const privateKeyCose = await cose.key.convertJsonWebKeyToCoseKey(privateKeyJwk) - const privateKey = await cose.key.convertCoseKeyToJsonWebKey(privateKeyCose) - expect(privateKey.alg).toBe('ES256') -}) - -it('generate thumbprints', async () => { - const k1 = { - kty: 'EC', - kid: '6hnb34De4biE17mQd46iSzxMnYPtqy3UaUd22KYZ0xg', - alg: 'ES256', - crv: 'P-256', - x: '9YjGAfpSPQ9t8p9zc9eCqzkDGHu_j-0_tTkUvOk5U8E', - y: 'YBFDrB8IROK1G_mu5FceqQnEk4CoFbcz6MyhuQWkCTE', - d: 'FLvNjn-z8HOvl0eGcH8eBYnxZ4xoEKVvCYIB0ibqkfs' - } - const jkt = await cose.key.thumbprint.calculateJwkThumbprint(k1) - const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(k1) - expect(jktUri).toBe('urn:ietf:params:oauth:jwk-thumbprint:sha-256:6hnb34De4biE17mQd46iSzxMnYPtqy3UaUd22KYZ0xg') - expect(jkt).toBe(k1.kid) - const k2 = await cose.key.convertJsonWebKeyToCoseKey(k1) - const ckt = await cose.key.thumbprint.calculateCoseKeyThumbprint(k2) - const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(k2) - expect(cktUri).toBe('urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJDPXpM4qfsGD_EyGfMa0JZsZNmvYK1lY') - const decoded = base64url.decode(cktUri.split(':').pop() as string) - expect(Buffer.from(decoded)).toEqual(Buffer.from(ckt)) -}) - -it('public from private', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { d, ...expectedPublicKeyJwk } = privateKeyJwk - const publicKeyJwk = cose.key.publicFromPrivate(privateKeyJwk) - expect(publicKeyJwk).toEqual(expectedPublicKeyJwk) - const privateKeyCose = await cose.key.generate('ES256', 'application/cose-key') - const expectedPublicKeyCose = new Map(privateKeyCose.entries()) - expectedPublicKeyCose.delete(cose.ec2.d) - const publicKeyCose = cose.key.publicFromPrivate(privateKeyCose) - expect(publicKeyCose).toEqual(expectedPublicKeyCose) -}) \ No newline at end of file diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index a3e06aa..83fe476 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -17,7 +17,10 @@ it('issue & verify', async () => { })) - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ From c4e125267984184e13bdc6acca337908e5511805 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:14:47 -0500 Subject: [PATCH 21/67] usage --- src/cose/key/publicFromPrivate.ts | 2 +- tests/receipt.test.ts | 6 ++++-- tests/sign1.attached.test.ts | 10 ++++++++-- tests/sign1.detached.test.ts | 10 ++++++++-- tests/signer.test.ts | 7 ++++--- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/cose/key/publicFromPrivate.ts b/src/cose/key/publicFromPrivate.ts index e116b7a..d5db9e5 100644 --- a/src/cose/key/publicFromPrivate.ts +++ b/src/cose/key/publicFromPrivate.ts @@ -24,7 +24,7 @@ export const extractPublicCoseKey = return publicCoseKeyMap as T } -export const publicFromPrivate = (privateKey: JWK | cose.any_cose_key) => { +export const publicFromPrivate = (privateKey: any) => { if ((privateKey as JWK).kty) { return extractPublicKeyJwk(privateKey as JWK) as T } diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 83fe476..ffbf109 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -16,7 +16,6 @@ it('issue & verify', async () => { return cose.receipt.leaf(entry) })) - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" @@ -74,7 +73,10 @@ it('issue & verify', async () => { }) it("add / remove from receipts", async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) const publicKeyJwk = await cose.key.publicFromPrivate(privateKeyJwk) const signer = cose.detached.signer({ remote: cose.crypto.signer({ diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index 180eef5..f4fe39f 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -4,7 +4,10 @@ import * as cose from '../src' import { JWK } from 'jose' it('sign and verify', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.attached.signer({ @@ -34,7 +37,10 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.attached.signer({ diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index ee23356..5cefb5f 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -4,7 +4,10 @@ import * as cose from '../src' import { JWK } from 'jose' it('sign and verify', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ @@ -39,7 +42,10 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 860480f..493041f 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -1,10 +1,11 @@ import fs from 'fs' import * as cose from '../src' -import { JWK } from 'jose' - it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') + const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { d, ...publicKeyJwk } = privateKeyJwk const signer = cose.detached.signer({ From ccddd3ffc51d33f5aa842b61a4ced949d0478ed7 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:18:15 -0500 Subject: [PATCH 22/67] cleaning --- src/cose/key/generate.ts | 42 ------------------------ src/cose/key/index.ts | 2 +- tests/readme.test.ts | 70 ---------------------------------------- tests/verifiers.test.ts | 17 ++++++++-- 4 files changed, 15 insertions(+), 116 deletions(-) delete mode 100644 src/cose/key/generate.ts delete mode 100644 tests/readme.test.ts diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts deleted file mode 100644 index 123d7d5..0000000 --- a/src/cose/key/generate.ts +++ /dev/null @@ -1,42 +0,0 @@ - - -import { generateKeyPair, exportJWK, calculateJwkThumbprint } from "jose" - - -export type CoseKeyAgreementAlgorithms = 'ECDH-ES+A128KW' -export type CoseSignatureAlgorithms = 'ES256' | 'ES384' | 'ES512' | 'ESP256' | 'ESP384' -export type ContentTypeOfJsonWebKey = 'application/jwk+json' -export type ContentTypeOfCoseKey = 'application/cose-key' -export type PrivateKeyContentType = ContentTypeOfCoseKey | ContentTypeOfJsonWebKey - -import { convertJsonWebKeyToCoseKey } from './convertJsonWebKeyToCoseKey' -import { thumbprint } from "./thumbprint" -import { formatJwk } from './formatJwk' - -import { less_specified } from "../../iana/requested/cose" -import * as cose from "../../iana/assignments/cose" - -export const generate = async (alg: CoseSignatureAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise => { - const cryptoKeyPair = await generateKeyPair( - less_specified[alg], - { extractable: true } - ); - const privateKeyJwk = await exportJWK(cryptoKeyPair.privateKey) - const jwkThumbprint = await calculateJwkThumbprint(privateKeyJwk) - privateKeyJwk.kid = jwkThumbprint - privateKeyJwk.alg = alg - if (contentType === 'application/jwk+json') { - return formatJwk(privateKeyJwk) as T - } - if (contentType === 'application/cose-key') { - delete privateKeyJwk.kid; - const privateKeyCoseKey = await convertJsonWebKeyToCoseKey(privateKeyJwk) - const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(privateKeyCoseKey) - privateKeyCoseKey.set(cose.ec2.kid, coseKeyThumbprint) - return privateKeyCoseKey as T - } - throw new Error('Unsupported content type.') -} - - - diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index 800acc7..e666cbd 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -12,7 +12,7 @@ export type CoseKey = Map import { thumbprint } from './thumbprint' export { thumbprint } -export * from './generate' + export * from './convertJsonWebKeyToCoseKey' export * from './convertCoseKeyToJsonWebKey' export * from './publicFromPrivate' diff --git a/tests/readme.test.ts b/tests/readme.test.ts deleted file mode 100644 index 400fe72..0000000 --- a/tests/readme.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import fs from 'fs' -import { JWK } from 'jose' -import * as cose from '../src' - -it('readme', async () => { - const issuerSecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - const issuerPublicKeyJwk = await cose.key.publicFromPrivate(issuerSecretKeyJwk) - - const notarySecretKeyJwk = await cose.key.generate('ES256', 'application/jwk+json') - const notaryPublicKeyJwk = await cose.key.publicFromPrivate(notarySecretKeyJwk) - - const issuer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: issuerSecretKeyJwk - }) - }) - const notary = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: notarySecretKeyJwk - }) - }) - const content = fs.readFileSync('./examples/image.png') - const signatureForImage = await issuer.sign({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], // signing algorithm ES256 - [cose.header.content_type, "image/png"], // content type image/png - [cose.header.kid, issuerPublicKeyJwk.kid] // issuer key identifier - ]), - payload: content - }) - const transparencyLogContainingImageSignatures = [ - await cose.receipt.leaf(signatureForImage) - ] - const receiptForImageSignature = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']], - [cose.header.kid, notaryPublicKeyJwk.kid] - ]), - entry: 0, - entries: transparencyLogContainingImageSignatures, - signer: notary - }) - const transparentSignature = await cose.receipt.add(signatureForImage, receiptForImageSignature) - const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { - const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) - if (tag !== cose.tag.COSE_Sign1) { - throw new Error('Only tagged cose sign 1 are supported') - } - const [protectedHeaderBytes] = value; - const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) - const kid = protectedHeaderMap.get(cose.header.kid); - if (kid === issuerPublicKeyJwk.kid) { - return issuerPublicKeyJwk - } - if (kid === notaryPublicKeyJwk.kid) { - return notaryPublicKeyJwk - } - throw new Error('No verification key found in trust store.') - } - const verifier = await cose.receipt.verifier({ - resolve - }) - const verified = await verifier.verify({ - coseSign1: transparentSignature, - payload: content - }) - expect(verified.payload).toBeDefined() - expect(verified.receipts.length).toBe(1) -}) \ No newline at end of file diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index cc2ed2f..c844623 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -5,9 +5,20 @@ import * as cose from '../src' import { JWK } from 'jose' it('verify multiple receipts', async () => { - const issuerSecretKey = await cose.key.generate('ES256', 'application/cose-key') - const notary1SecretKey = await cose.key.generate('ES256', 'application/cose-key') - const notary2SecretKey = await cose.key.generate('ES256', 'application/cose-key') + + const issuerSecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + type: "application/cose-key", + algorithm: "ES256" + }) + const notary1SecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + type: "application/cose-key", + algorithm: "ES256" + }) + const notary2SecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + type: "application/cose-key", + algorithm: "ES256" + }) + const issuerSigner = cose.detached.signer({ remote: cose.crypto.signer({ privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(issuerSecretKey) From a9a3718af3d62eff5a709b6fcae96ff51befe634 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:25:55 -0500 Subject: [PATCH 23/67] cleaning --- src/cose/key/index.ts | 8 -------- src/cose/key/serialize.ts | 12 ------------ .../draft-ietf-jose-fully-specified-algorithms.ts | 14 ++++++++++++++ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index e666cbd..c079ba0 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -1,14 +1,6 @@ -import { PublicKeyJwk, PrivateKeyJwk } from '../sign1' - -export type JsonWebKey = PrivateKeyJwk | PublicKeyJwk - -export type CoseMapKey = string | number -export type CoseMapValue = Uint8Array | ArrayBuffer | string | number | Map - -export type CoseKey = Map import { thumbprint } from './thumbprint' export { thumbprint } diff --git a/src/cose/key/serialize.ts b/src/cose/key/serialize.ts index bc5e4e3..e69de29 100644 --- a/src/cose/key/serialize.ts +++ b/src/cose/key/serialize.ts @@ -1,12 +0,0 @@ - -import { encode } from '../../cbor' -import { JWK } from 'jose' -import { CoseKey } from '.' - -export const serialize = (key: JWK | CoseKey) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((key as JWK).kty) { - return JSON.stringify(key, null, 2) - } - return encode(key) -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 576ca7a..8525060 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -248,3 +248,17 @@ export const gen = async < const key = await generate({ algorithm, type }) return parse({ key, type }) } + + +export const serialize = < + alg extends parseable_fully_specified_signature_algorithms, + cty extends crypto_key_type +>({ key, type }: { key: fully_specified_cose_key | fully_specified_web_key, type: cty }) => { + if (type === 'application/jwk+json') { + return Buffer.from(encoder.encode(JSON.stringify(format_web_key(key as JWK), null, 2))) + } + if (type === 'application/cose-key') { + return cbor.encode(key) + } + throw new Error('Cannot serialize to unsupported media type: ' + type) +} \ No newline at end of file From 963b4bb309e52884d8336efcce80c8c92f0ed6d8 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:29:31 -0500 Subject: [PATCH 24/67] cleaning --- src/cose/key/index.ts | 1 - src/crypto/key.ts | 4 ++-- src/drafts/draft-ietf-jose-fully-specified-algorithms.ts | 4 ++-- tests/receipt.test.ts | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index c079ba0..976bb3e 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -8,4 +8,3 @@ export { thumbprint } export * from './convertJsonWebKeyToCoseKey' export * from './convertCoseKeyToJsonWebKey' export * from './publicFromPrivate' -export * from './serialize' \ No newline at end of file diff --git a/src/crypto/key.ts b/src/crypto/key.ts index 50e2606..8def59b 100644 --- a/src/crypto/key.ts +++ b/src/crypto/key.ts @@ -2,6 +2,6 @@ -import { parse, generate, convert, gen } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; +import { parse, generate, convert, gen, serialize } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; -export { parse, generate, convert, gen } +export { gen, generate, parse, serialize, convert } diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts index 8525060..0a00b21 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts @@ -198,11 +198,11 @@ export const parse = < } -export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key) => { +export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key): Promise => { switch (type) { case 'application/jwk+json': { const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) - return encoder.encode(JSON.stringify(privateKey)) + return Buffer.from(encoder.encode(JSON.stringify(privateKey))) } case 'application/cose-key': { const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index ffbf109..889ec34 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -109,7 +109,7 @@ it("add / remove from receipts", async () => { expect(receipts.length).toBe(1) // expect 1 receipt const coseKey = await cose.key.convertJsonWebKeyToCoseKey(publicKeyJwk) coseKey.set(cose.ec2.kid, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey)) - const publicKey = cose.key.serialize(coseKey) + const publicKey = cose.crypto.key.serialize<'ES256', 'application/cose-key'>({ key: coseKey as any, type: 'application/cose-key' }) expect(publicKey).toBeDefined(); // fs.writeFileSync('./examples/image.ckt.signature.cbor', Buffer.from(transparentSignature)) // fs.writeFileSync('./examples/image.ckt.public-key.cbor', publicKey) From f9cc3d68680705f00cc1b9aa42250b7fad05e646 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:37:25 -0500 Subject: [PATCH 25/67] cleaning --- src/cose/key/convertCoseKeyToJsonWebKey.ts | 7 ++- src/cose/key/convertJsonWebKeyToCoseKey.ts | 53 ------------------- src/cose/key/formatJwk.ts | 6 --- src/cose/key/index.ts | 1 - src/crypto/key.ts | 4 +- .../index.ts} | 12 +++-- .../web_key_to_cose_key.ts | 6 +-- tests/receipt.test.ts | 4 +- 8 files changed, 18 insertions(+), 75 deletions(-) delete mode 100644 src/cose/key/convertJsonWebKeyToCoseKey.ts delete mode 100644 src/cose/key/formatJwk.ts rename src/drafts/{draft-ietf-jose-fully-specified-algorithms.ts => draft-ietf-jose-fully-specified-algorithms/index.ts} (96%) rename src/{crypto => drafts/draft-ietf-jose-fully-specified-algorithms}/web_key_to_cose_key.ts (89%) diff --git a/src/cose/key/convertCoseKeyToJsonWebKey.ts b/src/cose/key/convertCoseKeyToJsonWebKey.ts index 5ffc3f6..3fbe34d 100644 --- a/src/cose/key/convertCoseKeyToJsonWebKey.ts +++ b/src/cose/key/convertCoseKeyToJsonWebKey.ts @@ -1,8 +1,11 @@ import { base64url } from "jose"; -import { formatJwk } from "./formatJwk"; + import { labels_to_ec2_params, ec2, label_to_key_type, any_cose_key, label_to_curve } from '../../iana/assignments/cose' import { labels_to_algorithms } from "../../iana/requested/cose"; + +import { format_web_key } from "../../drafts/draft-ietf-jose-fully-specified-algorithms"; + export const convertCoseKeyToJsonWebKey = async (key: any_cose_key): Promise => { const jwk = {} as Record @@ -45,5 +48,5 @@ export const convertCoseKeyToJsonWebKey = async (key: any_cose_key): Promise< } } } - return formatJwk(jwk) as T + return format_web_key(jwk) as T } \ No newline at end of file diff --git a/src/cose/key/convertJsonWebKeyToCoseKey.ts b/src/cose/key/convertJsonWebKeyToCoseKey.ts deleted file mode 100644 index 81c7948..0000000 --- a/src/cose/key/convertJsonWebKeyToCoseKey.ts +++ /dev/null @@ -1,53 +0,0 @@ - -import { base64url, JWK } from 'jose' -import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' -import { algorithms_to_labels } from '../../iana/requested/cose' - -import { jose_key_type, ec_web_key } from '../../iana/assignments/jose' - -export const convertJsonWebKeyToCoseKey = async (jwk: JWK): Promise => { - const coseKey = new Map(); - const { kty } = jwk - for (const [key, value] of Object.entries(jwk)) { - switch (kty) { - // todo key use and key_ops - case jose_key_type.EC: { - switch (key) { - case ec_web_key.kty: { - coseKey.set(ec2_params_to_labels.get(key), key_type_to_label.get(value as string)) - break; - } - case ec_web_key.crv: { - coseKey.set(ec2_params_to_labels.get(key), curve_to_label.get(value as string)) - break; - } - case ec_web_key.alg: { - const maybeUnknown = algorithms_to_labels.get(value as string) || value as string - coseKey.set(ec2_params_to_labels.get(key), maybeUnknown) - break; - } - case ec_web_key.kid: { - coseKey.set(ec2_params_to_labels.get(key), value as string) - break; - } - case ec_web_key.x: - case ec_web_key.y: - case ec_web_key.d: { - // todo check lengths based on curves - coseKey.set(ec2_params_to_labels.get(key), Buffer.from(base64url.decode(value as string))) - break; - } - default: { - coseKey.set(key, value) - } - } - break; - } - default: { - coseKey.set(key, value) - } - } - } - return coseKey as T -} - diff --git a/src/cose/key/formatJwk.ts b/src/cose/key/formatJwk.ts deleted file mode 100644 index 86ef76b..0000000 --- a/src/cose/key/formatJwk.ts +++ /dev/null @@ -1,6 +0,0 @@ - - -export const formatJwk = (jwk: any) => { - const { kid, alg, kty, crv, x, y, d, ...rest } = jwk - return JSON.parse(JSON.stringify({ kid, alg, kty, crv, x, y, d, ...rest })) as any -} \ No newline at end of file diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index 976bb3e..5225758 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -5,6 +5,5 @@ import { thumbprint } from './thumbprint' export { thumbprint } -export * from './convertJsonWebKeyToCoseKey' export * from './convertCoseKeyToJsonWebKey' export * from './publicFromPrivate' diff --git a/src/crypto/key.ts b/src/crypto/key.ts index 8def59b..7b39613 100644 --- a/src/crypto/key.ts +++ b/src/crypto/key.ts @@ -2,6 +2,4 @@ -import { parse, generate, convert, gen, serialize } from "../drafts/draft-ietf-jose-fully-specified-algorithms"; - -export { gen, generate, parse, serialize, convert } +export * from "../drafts/draft-ietf-jose-fully-specified-algorithms"; diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts similarity index 96% rename from src/drafts/draft-ietf-jose-fully-specified-algorithms.ts rename to src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 0a00b21..9d27c87 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -1,13 +1,15 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' -import { crypto_key_type } from '../iana/assignments/media-types' -import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../iana/assignments/jose' -import { ec2_curves, ec2_key, okp_key, ec2, okp, okp_curves } from '../iana/assignments/cose' +import { crypto_key_type } from '../../iana/assignments/media-types' +import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../../iana/assignments/jose' +import { ec2_curves, ec2_key, okp_key, ec2, okp, okp_curves } from '../../iana/assignments/cose' import * as cbor from 'cbor-web' -import { less_specified } from '../iana/requested/cose' +import { less_specified } from '../../iana/requested/cose' -import { web_key_to_cose_key } from '../crypto/web_key_to_cose_key' +import { web_key_to_cose_key } from './web_key_to_cose_key' + +export { web_key_to_cose_key } const encoder = new TextEncoder() const decoder = new TextDecoder() diff --git a/src/crypto/web_key_to_cose_key.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts similarity index 89% rename from src/crypto/web_key_to_cose_key.ts rename to src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts index bce4875..e7c7fbe 100644 --- a/src/crypto/web_key_to_cose_key.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts @@ -1,8 +1,8 @@ import { base64url, JWK } from 'jose' -import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../iana/assignments/cose' -import { algorithms_to_labels } from '../iana/requested/cose' -import { jose_key_type, ec_web_key } from '../iana/assignments/jose' +import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' +import { algorithms_to_labels } from '../../iana/requested/cose' +import { jose_key_type, ec_web_key } from '../../iana/assignments/jose' export const web_key_to_cose_key = async (jwk: JWK): Promise => { const coseKey = new Map(); diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 889ec34..ae148d9 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -107,9 +107,9 @@ it("add / remove from receipts", async () => { expect(value[1].get(cose.draft_headers.receipts).length).toBe(1) // expect 1 receipt const receipts = await cose.receipt.get(transparentSignature) expect(receipts.length).toBe(1) // expect 1 receipt - const coseKey = await cose.key.convertJsonWebKeyToCoseKey(publicKeyJwk) + const coseKey = await cose.crypto.key.web_key_to_cose_key>(publicKeyJwk) coseKey.set(cose.ec2.kid, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey)) - const publicKey = cose.crypto.key.serialize<'ES256', 'application/cose-key'>({ key: coseKey as any, type: 'application/cose-key' }) + const publicKey = cose.crypto.key.serialize<'ES256', 'application/cose-key'>({ key: coseKey, type: 'application/cose-key' }) expect(publicKey).toBeDefined(); // fs.writeFileSync('./examples/image.ckt.signature.cbor', Buffer.from(transparentSignature)) // fs.writeFileSync('./examples/image.ckt.public-key.cbor', publicKey) From b97769ba85b18701106a912ad188223fe9ec5c42 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 08:39:49 -0500 Subject: [PATCH 26/67] renaming --- attic/encrypt/direct.ts | 4 ++-- attic/encrypt/wrap.ts | 4 ++-- src/cose/key/index.ts | 1 - .../cose_key_to_web_key.ts} | 4 ++-- .../index.ts | 3 ++- tests/verifiers.test.ts | 12 ++++++------ 6 files changed, 14 insertions(+), 14 deletions(-) rename src/{cose/key/convertCoseKeyToJsonWebKey.ts => drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts} (88%) diff --git a/attic/encrypt/direct.ts b/attic/encrypt/direct.ts index 5a03c40..1491dea 100644 --- a/attic/encrypt/direct.ts +++ b/attic/encrypt/direct.ts @@ -1,5 +1,5 @@ -import { convertCoseKeyToJsonWebKey, convertJsonWebKeyToCoseKey, generate, publicFromPrivate } from "../key" +import { cose_key_to_web_key, convertJsonWebKeyToCoseKey, generate, publicFromPrivate } from "../key" import { Tagged, decode, decodeFirst, encodeAsync } from "cbor-web" @@ -92,7 +92,7 @@ export const decrypt = async (req: RequestDirectDecryption) => { const epk = recipientUnprotectedHeader.get(Unprotected.Epk) // ensure the epk has the algorithm that is set in the protected header epk.set(Epk.Alg, recipientAlgorithm) - const senderPublicKeyJwk = await convertCoseKeyToJsonWebKey(epk) + const senderPublicKeyJwk = await cose_key_to_web_key(epk) const cek = await ecdh.deriveKey(protectedHeader, recipientProtectedHeader, senderPublicKeyJwk, receiverPrivateKeyJwk) const externalAad = req.aad ? toArrayBuffer(req.aad) : EMPTY_BUFFER const aad = await createAAD(protectedHeader, 'Encrypt', externalAad) diff --git a/attic/encrypt/wrap.ts b/attic/encrypt/wrap.ts index 4492d96..2119d09 100644 --- a/attic/encrypt/wrap.ts +++ b/attic/encrypt/wrap.ts @@ -1,5 +1,5 @@ -import { convertCoseKeyToJsonWebKey, convertJsonWebKeyToCoseKey, generate, publicFromPrivate } from "../key" +import { cose_key_to_web_key, convertJsonWebKeyToCoseKey, generate, publicFromPrivate } from "../key" import { Tagged, decodeFirst, encodeAsync } from "cbor-web" @@ -36,7 +36,7 @@ export const decrypt = async (req: RequestWrapDecryption) => { const epk = recipientUnprotectedHeader.get(Unprotected.Epk) // ensure the epk has the algorithm that is set in the protected header epk.set(Epk.Alg, recipientAlgorithm) - const senderPublicKeyJwk = await convertCoseKeyToJsonWebKey(epk) + const senderPublicKeyJwk = await cose_key_to_web_key(epk) const kek = await ecdh.deriveKey(protectedHeader, recipientProtectedHeader, senderPublicKeyJwk, receiverPrivateKeyJwk) const iv = unprotectedHeader.get(Unprotected.Iv) const externalAad = req.aad ? toArrayBuffer(req.aad) : EMPTY_BUFFER diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index 5225758..028c3fb 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -5,5 +5,4 @@ import { thumbprint } from './thumbprint' export { thumbprint } -export * from './convertCoseKeyToJsonWebKey' export * from './publicFromPrivate' diff --git a/src/cose/key/convertCoseKeyToJsonWebKey.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts similarity index 88% rename from src/cose/key/convertCoseKeyToJsonWebKey.ts rename to src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts index 3fbe34d..daa643d 100644 --- a/src/cose/key/convertCoseKeyToJsonWebKey.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts @@ -4,9 +4,9 @@ import { labels_to_ec2_params, ec2, label_to_key_type, any_cose_key, label_to_cu import { labels_to_algorithms } from "../../iana/requested/cose"; -import { format_web_key } from "../../drafts/draft-ietf-jose-fully-specified-algorithms"; +import { format_web_key } from "."; -export const convertCoseKeyToJsonWebKey = async (key: any_cose_key): Promise => { +export const cose_key_to_web_key = async (key: any_cose_key): Promise => { const jwk = {} as Record const ktyLabel = key.get(ec2.kty) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 9d27c87..18b04ba 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -8,8 +8,9 @@ import * as cbor from 'cbor-web' import { less_specified } from '../../iana/requested/cose' import { web_key_to_cose_key } from './web_key_to_cose_key' +import { cose_key_to_web_key } from './cose_key_to_web_key' -export { web_key_to_cose_key } +export { web_key_to_cose_key, cose_key_to_web_key } const encoder = new TextEncoder() const decoder = new TextDecoder() diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index c844623..1ba78e6 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -21,19 +21,19 @@ it('verify multiple receipts', async () => { const issuerSigner = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(issuerSecretKey) + privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(issuerSecretKey) }) }) const notary1Signer = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary1SecretKey) + privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(notary1SecretKey) }) }) const notary2Signer = cose.detached.signer({ remote: cose.crypto.signer({ - privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(notary2SecretKey) + privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(notary2SecretKey) }) }) const issuerCkt = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(issuerSecretKey) @@ -82,17 +82,17 @@ it('verify multiple receipts', async () => { const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) const kid = protectedHeaderMap.get(cose.header.kid); if (kid === issuerCkt) { - return cose.key.convertCoseKeyToJsonWebKey( + return cose.crypto.key.cose_key_to_web_key( await cose.key.publicFromPrivate(issuerSecretKey) ) } if (kid === notary1Ckt) { - return cose.key.convertCoseKeyToJsonWebKey( + return cose.crypto.key.cose_key_to_web_key( await cose.key.publicFromPrivate(notary1SecretKey) ) } if (kid === notary2Ckt) { - return cose.key.convertCoseKeyToJsonWebKey( + return cose.crypto.key.cose_key_to_web_key( await cose.key.publicFromPrivate(notary2SecretKey) ) } From 109f4030ec1b116f7bef698efbfe11fb04e9cc43 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 09:32:59 -0500 Subject: [PATCH 27/67] cleaning --- src/cose/key/index.ts | 1 - src/cose/key/publicFromPrivate.ts | 32 -------- src/cose/key/serialize.ts | 0 .../cose_key_to_web_key.ts | 4 +- .../index.ts | 77 ++++++++----------- .../public_from_private.ts | 75 ++++++++++++++++++ .../web_key_to_cose_key.ts | 6 +- tests/receipt.test.ts | 5 +- tests/verifiers.test.ts | 15 +++- 9 files changed, 128 insertions(+), 87 deletions(-) delete mode 100644 src/cose/key/publicFromPrivate.ts delete mode 100644 src/cose/key/serialize.ts create mode 100644 src/drafts/draft-ietf-jose-fully-specified-algorithms/public_from_private.ts diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts index 028c3fb..e66f35d 100644 --- a/src/cose/key/index.ts +++ b/src/cose/key/index.ts @@ -5,4 +5,3 @@ import { thumbprint } from './thumbprint' export { thumbprint } -export * from './publicFromPrivate' diff --git a/src/cose/key/publicFromPrivate.ts b/src/cose/key/publicFromPrivate.ts deleted file mode 100644 index d5db9e5..0000000 --- a/src/cose/key/publicFromPrivate.ts +++ /dev/null @@ -1,32 +0,0 @@ - -import * as cose from "../../iana/assignments/cose"; - -import { JWK } from 'jose' - -export const extractPublicKeyJwk = (privateKeyJwk: JWK) => { - if (privateKeyJwk.kty !== 'EC') { - throw new Error('Only EC keys are supported') - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { d, p, q, dp, dq, qi, key_ops, ...publicKeyJwk } = privateKeyJwk - return publicKeyJwk -} - -export const extractPublicCoseKey = (privateKey: cose.any_cose_key) => { - const publicCoseKeyMap = new Map(privateKey) - if (publicCoseKeyMap.get(cose.cose_key.kty) !== cose.cose_key_type.ec2) { - throw new Error('Only EC2 keys are supported') - } - if (!publicCoseKeyMap.get(cose.ec2.d)) { - throw new Error('privateKey is not a secret / private key (has no d / -4)') - } - publicCoseKeyMap.delete(cose.ec2.d); - return publicCoseKeyMap as T -} - -export const publicFromPrivate = (privateKey: any) => { - if ((privateKey as JWK).kty) { - return extractPublicKeyJwk(privateKey as JWK) as T - } - return extractPublicCoseKey(privateKey as cose.any_cose_key) as T -} \ No newline at end of file diff --git a/src/cose/key/serialize.ts b/src/cose/key/serialize.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts index daa643d..fa4a96f 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/cose_key_to_web_key.ts @@ -1,4 +1,4 @@ -import { base64url } from "jose"; +import { base64url, JWK } from "jose"; import { labels_to_ec2_params, ec2, label_to_key_type, any_cose_key, label_to_curve } from '../../iana/assignments/cose' import { labels_to_algorithms } from "../../iana/requested/cose"; @@ -8,7 +8,7 @@ import { format_web_key } from "."; export const cose_key_to_web_key = async (key: any_cose_key): Promise => { - const jwk = {} as Record + const jwk = {} as JWK // this should error kty is mandatory const ktyLabel = key.get(ec2.kty) const kty = labels_to_ec2_params.get(ktyLabel) if (!kty) { diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 18b04ba..7c0296c 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -1,16 +1,19 @@ import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' import { crypto_key_type } from '../../iana/assignments/media-types' -import { web_key_type, private_rsa_web_key_params, private_oct_web_key_params, private_ec_web_key_params, private_okp_web_key_params, jose_key_type } from '../../iana/assignments/jose' -import { ec2_curves, ec2_key, okp_key, ec2, okp, okp_curves } from '../../iana/assignments/cose' +import { web_key_type, } from '../../iana/assignments/jose' +import { ec2_curves, ec2_key, okp_key, okp_curves } from '../../iana/assignments/cose' import * as cbor from 'cbor-web' import { less_specified } from '../../iana/requested/cose' import { web_key_to_cose_key } from './web_key_to_cose_key' import { cose_key_to_web_key } from './cose_key_to_web_key' +import { public_from_private } from './public_from_private' + +export { web_key_to_cose_key, cose_key_to_web_key, public_from_private } + -export { web_key_to_cose_key, cose_key_to_web_key } const encoder = new TextEncoder() const decoder = new TextDecoder() @@ -90,6 +93,20 @@ export type fully_specified_web_key = algorithm_specified_cose_key_params[T] +export type parseable_fully_specified_signature_algorithms = keyof algorithm_specified_cose_key_params | keyof algorithm_specified_web_key_params +export type parsable_fully_specified_keys = + cty extends 'application/jwk+json' ? fully_specified_web_key : cty extends 'application/cose-key' ? fully_specified_cose_key : unknown + +export type request_crypto_key = { + + type: crypto_key_type, + algorithm: fully_specified_signature_algorithms + + id?: string, + extractable?: boolean +} + + export const format_web_key = (jwk: JWK) => { const { kid, alg, kty, crv, x, y, d, ext, ...rest } = jwk return JSON.parse(JSON.stringify({ @@ -97,7 +114,7 @@ export const format_web_key = (jwk: JWK) => { })) } -const without_private_information = (jwk: JWK, private_params: Record) => { +export const without_private_information = (jwk: JWK, private_params: Record) => { const public_information = {} as Record for (const [key, value] of Object.entries(jwk)) { if (key in private_params) { @@ -108,46 +125,32 @@ const without_private_information = (jwk: JWK, private_params: Record( +export const export_public_web_key_with_algorithm = async ( k: KeyLike, - alg: fully_specified_signature_algorithms, + alg: alg, ext: boolean, kid?: string -): Promise> => { +): Promise> => { const jwk = await exportJWK(k); jwk.alg = alg jwk.ext = ext jwk.kid = kid || await calculateJwkThumbprint(jwk) - const { kty } = jwk - switch (kty) { - case jose_key_type.RSA: { - return without_private_information(jwk, private_rsa_web_key_params) - } - case jose_key_type.EC: { - return without_private_information(jwk, private_ec_web_key_params) - } - case jose_key_type.OKP: { - return without_private_information(jwk, private_okp_web_key_params) - } - case jose_key_type.oct: { - return without_private_information(jwk, private_oct_web_key_params) - } - default: { - throw new Error('Unknown key type: ' + kty) - } - } + return public_from_private({ + key: jwk as any, + type: 'application/jwk+json' + }) as fully_specified_web_key } -export const export_private_web_key_with_algorithm = async ( +export const export_private_web_key_with_algorithm = async ( k: KeyLike, - alg: fully_specified_signature_algorithms, + alg: alg, kid?: string -): Promise> => { +): Promise> => { const privateKey = await exportJWK(k); privateKey.alg = alg privateKey.ext = true; // impossible to export otherwise. privateKey.kid = kid || await calculateJwkThumbprint(privateKey) - return format_web_key(privateKey) as fully_specified_web_key + return format_web_key(privateKey) as fully_specified_web_key } export const generate_web_key = async ({ alg, ext, kid }: { @@ -163,22 +166,6 @@ export const generate_web_key = async ({ alg, ext, kid }: { } } -export type parseable_fully_specified_signature_algorithms = keyof algorithm_specified_cose_key_params | keyof algorithm_specified_web_key_params -export type parsable_fully_specified_keys = - cty extends 'application/jwk+json' ? fully_specified_web_key : cty extends 'application/cose-key' ? fully_specified_cose_key : unknown - - - -export type request_crypto_key = { - - type: crypto_key_type, - algorithm: fully_specified_signature_algorithms - - id?: string, - extractable?: boolean -} - - export const parse = < alg extends parseable_fully_specified_signature_algorithms, cty extends crypto_key_type diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/public_from_private.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/public_from_private.ts new file mode 100644 index 0000000..32cdfe0 --- /dev/null +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/public_from_private.ts @@ -0,0 +1,75 @@ +import { fully_specified_cose_key, fully_specified_web_key } from "."; + +import { JWK } from "jose"; +import { jose_key_type, private_ec_web_key_params, private_okp_web_key_params, private_oct_web_key_params } from "../../iana/assignments/jose"; + +import { without_private_information } from "."; +import { crypto_key_type } from "../../iana/assignments/media-types"; +import { any_cose_key, cose_key, cose_key_type, ec2, okp, symmetric } from "../../iana/assignments/cose"; + +import { parseable_fully_specified_signature_algorithms, parsable_fully_specified_keys } from "."; + +export const public_from_private = ({ key, type }: { + key: fully_specified_cose_key | fully_specified_web_key, + type: cty +}): parsable_fully_specified_keys => { + if (type === 'application/jwk+json') { + const jwk = key as JWK + const { kty } = jwk + switch (kty) { + // RSA not supported at this time + // case jose_key_type.RSA: { + // return without_private_information(key, private_rsa_web_key_params) + // } + case jose_key_type.EC: { + return without_private_information(jwk, private_ec_web_key_params) + } + case jose_key_type.OKP: { + return without_private_information(jwk, private_okp_web_key_params) + } + case jose_key_type.oct: { + return without_private_information(jwk, private_oct_web_key_params) + } + default: { + throw new Error('Unknown key type: ' + kty) + } + } + } + if (type === 'application/cose-key') { + // Unlike JOSE, COSE does not have private information class on key parameters + // So we are "guessing" regarding which parts of the IANA registry to omit. + const pub = new Map((key as any_cose_key).entries()) + const kty = pub.get(cose_key.kty) + switch (kty) { + // RSA not supported at this time + // case jose_key_type.RSA: { + // return without_private_information(key, private_rsa_web_key_params) + // } + case cose_key_type.ec2: { + const deleted = pub.delete(ec2.d) + if (!deleted) { + throw new Error('Malformed EC2 Private Key (no d)') + } + return pub as parsable_fully_specified_keys + } + case cose_key_type.okp: { + const deleted = pub.delete(okp.d) + if (!deleted) { + throw new Error('Malformed OKP Private Key (no d)') + } + return pub as parsable_fully_specified_keys + } + case cose_key_type.symmetric: { + const deleted = pub.delete(symmetric.k) + if (!deleted) { + throw new Error('Malformed oct key (no k)') + } + return pub as parsable_fully_specified_keys + } + default: { + throw new Error('Unknown key type: ' + kty) + } + } + } + throw new Error("Unsupported key type: " + type) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts index e7c7fbe..0385e72 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts @@ -1,10 +1,10 @@ -import { base64url, JWK } from 'jose' +import { base64url } from 'jose' import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' -import { jose_key_type, ec_web_key } from '../../iana/assignments/jose' +import { jose_key_type, ec_web_key, web_key_type } from '../../iana/assignments/jose' -export const web_key_to_cose_key = async (jwk: JWK): Promise => { +export const web_key_to_cose_key = async (jwk: web_key_type): Promise => { const coseKey = new Map(); const { kty } = jwk for (const [key, value] of Object.entries(jwk)) { diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index ae148d9..9fb39bb 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -77,7 +77,10 @@ it("add / remove from receipts", async () => { type: "application/jwk+json", algorithm: "ES256" }) - const publicKeyJwk = await cose.key.publicFromPrivate(privateKeyJwk) + const publicKeyJwk = await cose.crypto.key.public_from_private<'ES256', 'application/jwk+json'>({ + key: privateKeyJwk, + type: 'application/jwk+json' + }) const signer = cose.detached.signer({ remote: cose.crypto.signer({ privateKeyJwk diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 1ba78e6..e0128fb 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -83,17 +83,26 @@ it('verify multiple receipts', async () => { const kid = protectedHeaderMap.get(cose.header.kid); if (kid === issuerCkt) { return cose.crypto.key.cose_key_to_web_key( - await cose.key.publicFromPrivate(issuerSecretKey) + await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ + key: issuerSecretKey, + type: 'application/cose-key' + }) ) } if (kid === notary1Ckt) { return cose.crypto.key.cose_key_to_web_key( - await cose.key.publicFromPrivate(notary1SecretKey) + await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ + key: notary1SecretKey, + type: 'application/cose-key' + }) ) } if (kid === notary2Ckt) { return cose.crypto.key.cose_key_to_web_key( - await cose.key.publicFromPrivate(notary2SecretKey) + await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ + key: notary2SecretKey, + type: 'application/cose-key' + }) ) } throw new Error('No verification key found in trust store.') From 539617cb8c2a22cb5e3973e18627469d1ba65aaa Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 09:47:13 -0500 Subject: [PATCH 28/67] cleaning --- src/cose/key/index.ts | 7 --- src/cose/key/thumbprint.ts | 37 --------------- .../index.ts | 17 +++---- .../thumbprint.ts | 46 +++++++++++++++++++ src/index.ts | 4 +- tests/receipt.test.ts | 2 +- tests/verifiers.test.ts | 6 +-- 7 files changed, 61 insertions(+), 58 deletions(-) delete mode 100644 src/cose/key/index.ts delete mode 100644 src/cose/key/thumbprint.ts create mode 100644 src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts diff --git a/src/cose/key/index.ts b/src/cose/key/index.ts deleted file mode 100644 index e66f35d..0000000 --- a/src/cose/key/index.ts +++ /dev/null @@ -1,7 +0,0 @@ - - - - -import { thumbprint } from './thumbprint' -export { thumbprint } - diff --git a/src/cose/key/thumbprint.ts b/src/cose/key/thumbprint.ts deleted file mode 100644 index 0be74b5..0000000 --- a/src/cose/key/thumbprint.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { calculateJwkThumbprint, calculateJwkThumbprintUri, base64url } from "jose"; - -import { encodeCanonical } from "../../cbor"; - -import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; -import * as cose from '../../iana/assignments/cose' - -// https://www.ietf.org/archive/id/draft-ietf-cose-key-thumbprint-01.html#section-6 -const calculateCoseKeyThumbprint = async (coseKey: cose.any_cose_key): Promise => { - if (coseKey.get(cose.cose_key.kty) !== cose.cose_key_type.ec2) { - throw new Error('Unsupported key type (Only EC2 are supported') - } - const onlyRequiredMap = new Map() - const requiredKeys = [cose.ec2.kty, cose.ec2.crv, cose.ec2.x, cose.ec2.y] - for (const [key, value] of coseKey.entries()) { - if (requiredKeys.includes(key as number)) { - onlyRequiredMap.set(key, value) - } - } - const encoded = encodeCanonical(onlyRequiredMap) - const subtle = await subtleCryptoProvider() - const digest = subtle.digest("SHA-256", encoded) - return digest -} - -const calculateCoseKeyThumbprintUri = async (coseKey: cose.any_cose_key): Promise => { - const prefix = `urn:ietf:params:oauth:ckt:sha-256` - const digest = await calculateCoseKeyThumbprint(coseKey) - return `${prefix}:${base64url.encode(new Uint8Array(digest))}` -} - -export const thumbprint = { - calculateJwkThumbprint, - calculateJwkThumbprintUri, - calculateCoseKeyThumbprint, - calculateCoseKeyThumbprintUri, -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 7c0296c..7d3f8c3 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -1,5 +1,5 @@ -import { exportJWK, KeyLike, JWK, generateKeyPair, calculateJwkThumbprint } from 'jose' +import { exportJWK, KeyLike, JWK, generateKeyPair } from 'jose' import { crypto_key_type } from '../../iana/assignments/media-types' import { web_key_type, } from '../../iana/assignments/jose' import { ec2_curves, ec2_key, okp_key, okp_curves } from '../../iana/assignments/cose' @@ -10,10 +10,11 @@ import { less_specified } from '../../iana/requested/cose' import { web_key_to_cose_key } from './web_key_to_cose_key' import { cose_key_to_web_key } from './cose_key_to_web_key' import { public_from_private } from './public_from_private' +import { web_key_thumbprint } from './thumbprint' export { web_key_to_cose_key, cose_key_to_web_key, public_from_private } - +export * from './thumbprint' const encoder = new TextEncoder() const decoder = new TextDecoder() @@ -134,7 +135,7 @@ export const export_public_web_key_with_algorithm = async ({ key: jwk as any, type: 'application/jwk+json' @@ -146,11 +147,11 @@ export const export_private_web_key_with_algorithm = async > => { - const privateKey = await exportJWK(k); - privateKey.alg = alg - privateKey.ext = true; // impossible to export otherwise. - privateKey.kid = kid || await calculateJwkThumbprint(privateKey) - return format_web_key(privateKey) as fully_specified_web_key + const jwk = await exportJWK(k); + jwk.alg = alg + jwk.ext = true; // impossible to export otherwise. + jwk.kid = kid || await web_key_thumbprint(jwk as web_key_type) + return format_web_key(jwk) as fully_specified_web_key } export const generate_web_key = async ({ alg, ext, kid }: { diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts new file mode 100644 index 0000000..8226f36 --- /dev/null +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts @@ -0,0 +1,46 @@ +import { calculateJwkThumbprint, calculateJwkThumbprintUri, base64url, JWK } from "jose"; + +import { encodeCanonical } from "../../cbor"; + +import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; +import * as cose from '../../iana/assignments/cose' +import { web_key_type } from "../../iana/assignments/jose"; + +export type cose_key_thumbprint = ArrayBuffer +export type cose_key_thumbprint_base_encoded = string +export type cose_key_thumbprint_uri = `urn:ietf:params:oauth:ckt:sha-256:${cose_key_thumbprint_base_encoded}` + +export type web_key_thumbprint = string +export type web_key_thumbprint_uri = `urn:ietf:params:oauth:jwk-thumbprint:${web_key_thumbprint}` + +// https://www.ietf.org/archive/id/draft-ietf-cose-key-thumbprint-01.html#section-6 +const cose_key_thumbprint = async (coseKey: cose.any_cose_key): Promise => { + if (coseKey.get(cose.cose_key.kty) !== cose.cose_key_type.ec2) { + throw new Error('Unsupported key type (Only EC2 are supported') + } + const onlyRequiredMap = new Map() + const requiredKeys = [cose.ec2.kty, cose.ec2.crv, cose.ec2.x, cose.ec2.y] + for (const [key, value] of coseKey.entries()) { + if (requiredKeys.includes(key as number)) { + onlyRequiredMap.set(key, value) + } + } + const encoded = encodeCanonical(onlyRequiredMap) + const subtle = await subtleCryptoProvider() + const digest = subtle.digest("SHA-256", encoded) + return digest +} + +export const cose_key_thumbprint_uri = async (coseKey: cose.any_cose_key): Promise => { + const prefix = `urn:ietf:params:oauth:ckt:sha-256` + const digest = await cose_key_thumbprint(coseKey) + return `${prefix}:${base64url.encode(new Uint8Array(digest))}` +} + +export const web_key_thumbprint = async (jwk: web_key_type): Promise => { + return calculateJwkThumbprint(jwk as JWK) +} + +export const web_key_thumbprint_uri = async (jwk: web_key_type): Promise => { + return calculateJwkThumbprintUri(jwk as JWK) as Promise +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 858bec9..854db55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import * as key from './cose/key' + import * as attached from './cose/attached' import * as detached from './cose/detached' @@ -24,4 +24,4 @@ export * from './iana/requested/cose' import * as cbor from './cbor' import * as receipt from './cose/receipt' import * as crypto from './crypto' -export { crypto, cbor, key, attached, detached, receipt } \ No newline at end of file +export { crypto, cbor, attached, detached, receipt } \ No newline at end of file diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 9fb39bb..e4d1e12 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -111,7 +111,7 @@ it("add / remove from receipts", async () => { const receipts = await cose.receipt.get(transparentSignature) expect(receipts.length).toBe(1) // expect 1 receipt const coseKey = await cose.crypto.key.web_key_to_cose_key>(publicKeyJwk) - coseKey.set(cose.ec2.kid, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey)) + coseKey.set(cose.ec2.kid, await cose.crypto.key.cose_key_thumbprint_uri(coseKey)) const publicKey = cose.crypto.key.serialize<'ES256', 'application/cose-key'>({ key: coseKey, type: 'application/cose-key' }) expect(publicKey).toBeDefined(); // fs.writeFileSync('./examples/image.ckt.signature.cbor', Buffer.from(transparentSignature)) diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index e0128fb..ae4344b 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -36,9 +36,9 @@ it('verify multiple receipts', async () => { privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(notary2SecretKey) }) }) - const issuerCkt = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(issuerSecretKey) - const notary1Ckt = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(notary1SecretKey) - const notary2Ckt = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(notary2SecretKey) + const issuerCkt = await cose.crypto.key.cose_key_thumbprint_uri(issuerSecretKey) + const notary1Ckt = await cose.crypto.key.cose_key_thumbprint_uri(notary1SecretKey) + const notary2Ckt = await cose.crypto.key.cose_key_thumbprint_uri(notary2SecretKey) const content = fs.readFileSync('./examples/image.png') const signatureForImage = await issuerSigner.sign({ From bb8eb4df512e6e4a92b2ea8a0989ab73bd56ede0 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:20:57 -0500 Subject: [PATCH 29/67] cleaning --- src/cose/receipt/consistency/issue.ts | 6 ++++-- src/cose/receipt/inclusion/issue.ts | 6 ++++-- src/cose/receipt/verifier.ts | 8 +++++--- src/cose/sign1/types.ts | 12 ++++-------- src/cose/sign1/verifier.ts | 6 +++--- src/crypto/signer.ts | 2 +- src/x509/certificate.ts | 6 ++++-- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cose/receipt/consistency/issue.ts b/src/cose/receipt/consistency/issue.ts index 7cdf0ee..c1b066e 100644 --- a/src/cose/receipt/consistency/issue.ts +++ b/src/cose/receipt/consistency/issue.ts @@ -3,14 +3,16 @@ import { CoMETRE } from '@transmute/rfc9162' import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' -import { CoseSign1Bytes, CoseSign1Signer, ProtectedHeaderMap } from "../../sign1" +import { CoseSign1Bytes, CoseSign1Signer } from "../../sign1" import { toArrayBuffer } from '../../../cbor' import { draft_headers } from '../../..' +import { HeaderMap } from '../../..' + export type RequestIssueConsistencyReceipt = { - protectedHeader: ProtectedHeaderMap + protectedHeader: HeaderMap receipt: CoseSign1Bytes, entries: Uint8Array[] signer: CoseSign1Signer diff --git a/src/cose/receipt/inclusion/issue.ts b/src/cose/receipt/inclusion/issue.ts index bb6af87..51018af 100644 --- a/src/cose/receipt/inclusion/issue.ts +++ b/src/cose/receipt/inclusion/issue.ts @@ -3,12 +3,14 @@ import { CoMETRE } from '@transmute/rfc9162' import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' -import { CoseSign1Signer, ProtectedHeaderMap } from "../../sign1" +import { CoseSign1Signer } from "../../sign1" import { draft_headers } from '../../..' +import { HeaderMap } from '../../..' + export type RequestIssueInclusionReceipt = { - protectedHeader: ProtectedHeaderMap + protectedHeader: HeaderMap entry: number, entries: Uint8Array[] signer: CoseSign1Signer diff --git a/src/cose/receipt/verifier.ts b/src/cose/receipt/verifier.ts index 7be63da..bc2a641 100644 --- a/src/cose/receipt/verifier.ts +++ b/src/cose/receipt/verifier.ts @@ -1,6 +1,6 @@ -import { ProtectedHeaderMap, PublicKeyJwk, RequestCoseSign1DectachedVerify } from "../sign1" +import { RequestCoseSign1DectachedVerify } from "../sign1" + -import { decodeFirstSync } from '../../cbor' import { detached } from "../.." import { get } from "./get" @@ -9,8 +9,10 @@ import * as inclusion from './inclusion' import { leaf } from "./leaf" import { remove } from "./remove" +import { web_key_type } from "../../iana/assignments/jose" + export type RequestHeaderVerifier = { - resolve: (signature: ArrayBuffer) => Promise + resolve: (signature: ArrayBuffer) => Promise } const getVerifierForMessage = async (req: RequestCoseSign1DectachedVerify, resolver: RequestHeaderVerifier) => { diff --git a/src/cose/sign1/types.ts b/src/cose/sign1/types.ts index ab268a0..e1ac895 100644 --- a/src/cose/sign1/types.ts +++ b/src/cose/sign1/types.ts @@ -1,16 +1,12 @@ -export type ProtectedHeaderMap = Map -export type UnprotectedHeaderMap = Map +import { HeaderMap } from "../../desugar" -export type CoseSign1Structure = [Buffer, UnprotectedHeaderMap, Buffer, Buffer] +export type CoseSign1Structure = [Buffer, HeaderMap, Buffer, Buffer] export type DecodedToBeSigned = [string, Buffer, Buffer, Buffer] export type DecodedCoseSign1 = { value: CoseSign1Structure } -export type PrivateKeyJwk = JsonWebKey & { d: string, kid?: string } -export type PublicKeyJwk = Omit - export type RequestCoseSign1Signer = { remote: { sign: (toBeSigned: ArrayBuffer) => Promise @@ -18,8 +14,8 @@ export type RequestCoseSign1Signer = { } export type RequestCoseSign1 = { - protectedHeader: ProtectedHeaderMap, - unprotectedHeader?: UnprotectedHeaderMap, + protectedHeader: HeaderMap, + unprotectedHeader?: HeaderMap, payload: ArrayBuffer, externalAAD?: ArrayBuffer } diff --git a/src/cose/sign1/verifier.ts b/src/cose/sign1/verifier.ts index 28c0a51..7b24a0c 100644 --- a/src/cose/sign1/verifier.ts +++ b/src/cose/sign1/verifier.ts @@ -2,10 +2,10 @@ import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER } from '../../cbor' import { RequestCoseSign1Verifier, RequestCoseSign1Verify } from './types' -import { DecodedToBeSigned, ProtectedHeaderMap } from './types' +import { DecodedToBeSigned } from './types' import rawVerifier from '../../crypto/verifier' - +import { HeaderMap } from '../../desugar' import * as cose from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' @@ -26,7 +26,7 @@ const verifier = ({ resolver }: RequestCoseSign1Verifier) => { } // eslint-disable-next-line @typescript-eslint/no-unused-vars const [protectedHeaderBytes, _, payload, signature] = signatureStructure; - const protectedHeaderMap: ProtectedHeaderMap = (!protectedHeaderBytes.length) ? new Map() : decodeFirstSync(protectedHeaderBytes); + const protectedHeaderMap: HeaderMap = (!protectedHeaderBytes.length) ? new Map() : decodeFirstSync(protectedHeaderBytes); const algInHeader = protectedHeaderMap.get(cose.header.alg) if (algInHeader !== algInPublicKey) { throw new Error('Verification key does not support algorithm: ' + algInHeader); diff --git a/src/crypto/signer.ts b/src/crypto/signer.ts index 57e5d31..81e310c 100644 --- a/src/crypto/signer.ts +++ b/src/crypto/signer.ts @@ -2,7 +2,7 @@ import { JWK } from 'jose' import { toArrayBuffer } from '../cbor' -import { PrivateKeyJwk } from '../cose/sign1' + import subtleCryptoProvider from './subtleCryptoProvider' diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 3aab8bc..57230eb 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -1,5 +1,5 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; -import { PublicKeyJwk } from "../cose/sign1" + import * as x509 from "@peculiar/x509"; import { detached, RequestCoseSign1VerifyDetached } from '..'; @@ -8,6 +8,8 @@ import { JWK } from 'jose' import * as cose from '../iana/assignments/cose'; import { labels_to_algorithms } from '../iana/requested/cose'; +import { web_key_type } from '../iana/assignments/jose'; + // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) @@ -110,7 +112,7 @@ const pkcs8Signer = async ({ alg, privateKeyPKCS8 }: { alg: number, privateKeyPK export type RequestCertificateVerifier = { resolver: { - resolve: (signature: ArrayBuffer) => Promise + resolve: (signature: ArrayBuffer) => Promise } } From a4c6a55b5d557962fea973f89ec67e219d5bb835 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:30:28 -0500 Subject: [PATCH 30/67] cleaning --- src/cbor/index.ts | 2 +- src/cose/detached/index.ts | 9 ++++++--- src/cose/receipt/add.ts | 9 ++++++--- src/cose/receipt/get.ts | 5 +++-- src/cose/receipt/remove.ts | 8 +++++--- src/cose/sign1/signer.ts | 6 +++--- tests/receipt.test.ts | 2 +- tests/verifiers.test.ts | 3 ++- tests/x509.test.ts | 5 +++-- 9 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/cbor/index.ts b/src/cbor/index.ts index 805057c..33808b8 100644 --- a/src/cbor/index.ts +++ b/src/cbor/index.ts @@ -4,7 +4,7 @@ import { encodeCanonical, encode, decode, encodeAsync, decodeFirst, decodeFirstS import { toArrayBuffer } from './toArrayBuffer' -export const Sign1Tag = 18; + export const EMPTY_BUFFER = toArrayBuffer(new Uint8Array()) diff --git a/src/cose/detached/index.ts b/src/cose/detached/index.ts index f62fef4..f3e6126 100644 --- a/src/cose/detached/index.ts +++ b/src/cose/detached/index.ts @@ -1,8 +1,11 @@ import * as sign1 from "../sign1" -import { decodeFirstSync, encodeAsync, Sign1Tag, Tagged, toArrayBuffer } from '../../cbor' +import { decodeFirstSync, encodeAsync, Tagged, toArrayBuffer } from '../../cbor' + import { UnprotectedHeader } from "../../desugar" +import { tag } from "../../iana/assignments/cbor" + export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { const coseSign1Signer = sign1.signer({ remote }) return { @@ -13,7 +16,7 @@ export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { const coseSign1 = await coseSign1Signer.sign(req) const decoded = decodeFirstSync(coseSign1) decoded.value[2] = null - return encodeAsync(new Tagged(Sign1Tag, decoded.value), { canonical: true }) + return encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true }) } } } @@ -25,7 +28,7 @@ export const verifier = ({ resolver }: sign1.RequestCoseSign1Verifier) => { const decoded = decodeFirstSync(req.coseSign1) const payloadBuffer = toArrayBuffer(req.payload); decoded.value[2] = payloadBuffer - const attached = await encodeAsync(new Tagged(Sign1Tag, decoded.value), { canonical: true }) + const attached = await encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true }) return verifier.verify({ coseSign1: attached }) } } diff --git a/src/cose/receipt/add.ts b/src/cose/receipt/add.ts index 2ba1fa4..4674ae9 100644 --- a/src/cose/receipt/add.ts +++ b/src/cose/receipt/add.ts @@ -1,12 +1,15 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged, Sign1Tag } from '../../cbor' +import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../cbor' import { CoseSign1Bytes } from "../sign1"; import { draft_headers } from '../../iana/requested/cose'; +import * as cbor from '../../iana/assignments/cbor'; + + export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) - if (tag !== Sign1Tag) { + if (tag !== cbor.tag.COSE_Sign1) { throw new Error('Receipts can only be added to cose-sign1') } if (!(value[1] instanceof Map)) { @@ -16,5 +19,5 @@ export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): P const receipts = value[1].get(draft_headers.receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ receipts.push(receipt) value[1].set(draft_headers.receipts, receipts) - return toArrayBuffer(await encodeAsync(new Tagged(Sign1Tag, value), { canonical: true })); + return toArrayBuffer(await encodeAsync(new Tagged(cbor.tag.COSE_Sign1, value), { canonical: true })); } \ No newline at end of file diff --git a/src/cose/receipt/get.ts b/src/cose/receipt/get.ts index 1174fc5..3a3dc04 100644 --- a/src/cose/receipt/get.ts +++ b/src/cose/receipt/get.ts @@ -1,12 +1,13 @@ -import { decodeFirstSync, Sign1Tag } from '../../cbor' +import { decodeFirstSync } from '../../cbor' import { CoseSign1Bytes } from "../sign1"; import { draft_headers } from '../../iana/requested/cose'; +import * as cbor from '../../iana/assignments/cbor'; export const get = async (signature: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) - if (tag !== Sign1Tag) { + if (tag !== cbor.tag.COSE_Sign1) { throw new Error('Receipts can only be added to cose-sign1') } if (!(value[1] instanceof Map)) { diff --git a/src/cose/receipt/remove.ts b/src/cose/receipt/remove.ts index d978d92..9e1432f 100644 --- a/src/cose/receipt/remove.ts +++ b/src/cose/receipt/remove.ts @@ -1,11 +1,13 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged, Sign1Tag } from '../../cbor' +import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../cbor' import { CoseSign1Bytes } from "../sign1"; +import * as cbor from '../../iana/assignments/cbor' + export const remove = async (signature: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) - if (tag !== Sign1Tag) { + if (tag !== cbor.tag.COSE_Sign1) { throw new Error('Receipts can only be added to cose-sign1') } value[1] = new Map(); - return toArrayBuffer(await encodeAsync(new Tagged(Sign1Tag, value), { canonical: true })); + return toArrayBuffer(await encodeAsync(new Tagged(cbor.tag.COSE_Sign1, value), { canonical: true })); } \ No newline at end of file diff --git a/src/cose/sign1/signer.ts b/src/cose/sign1/signer.ts index f9ec9db..da9aee1 100644 --- a/src/cose/sign1/signer.ts +++ b/src/cose/sign1/signer.ts @@ -1,9 +1,9 @@ -import { encode, encodeAsync, EMPTY_BUFFER, Tagged, Sign1Tag, toArrayBuffer } from '../../cbor' +import { encode, encodeAsync, EMPTY_BUFFER, Tagged, toArrayBuffer } from '../../cbor' import { RequestCoseSign1Signer, RequestCoseSign1, CoseSign1Bytes } from "./types" - +import { tag } from '../../iana/assignments/cbor' const signer = ({ remote }: RequestCoseSign1Signer) => { return { sign: async ({ protectedHeader, unprotectedHeader, externalAAD, payload }: RequestCoseSign1): Promise => { @@ -19,7 +19,7 @@ const signer = ({ remote }: RequestCoseSign1Signer) => { const encodedToBeSigned = encode(decodedToBeSigned); const signature = await remote.sign(encodedToBeSigned) const coseSign1Structure = [protectedHeaderBytes, unprotectedHeader, payloadBuffer, signature]; - return toArrayBuffer(await encodeAsync(new Tagged(Sign1Tag, coseSign1Structure), { canonical: true })); + return toArrayBuffer(await encodeAsync(new Tagged(tag.COSE_Sign1, coseSign1Structure), { canonical: true })); } } } diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index e4d1e12..65dd5dd 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -2,7 +2,7 @@ import fs from 'fs' -import { JWK } from 'jose' + import * as cose from '../src' const encoder = new TextEncoder(); diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index ae4344b..2506a0c 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -3,6 +3,7 @@ import fs from 'fs' import * as cose from '../src' import { JWK } from 'jose' +import { web_key_type } from '../src/iana/assignments/jose' it('verify multiple receipts', async () => { @@ -73,7 +74,7 @@ it('verify multiple receipts', async () => { }) const transparentSignature1 = await cose.receipt.add(signatureForImage, receiptForImageSignature1) const transparentSignature = await cose.receipt.add(transparentSignature1, receiptForImageSignature2) - const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { + const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 8dca2a4..e42db9a 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -3,6 +3,7 @@ import moment from 'moment' import * as jose from 'jose' import * as cose from '../src' import { labels_to_algorithms } from '../src/iana/requested/cose' +import { web_key_type } from '../src/iana/assignments/jose' it('sign and verify with x5t and key resolver', async () => { const cert = await cose.certificate.root({ @@ -31,7 +32,7 @@ it('sign and verify with x5t and key resolver', async () => { ]), payload: content }) - const certificateFromThumbprint = async (coseSign1: cose.CoseSign1Bytes): Promise => { + const certificateFromThumbprint = async (coseSign1: cose.CoseSign1Bytes): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') @@ -51,7 +52,7 @@ it('sign and verify with x5t and key resolver', async () => { // could do extra certificate policy validation here... const publicKeyJwk = await jose.exportJWK(await jose.importX509(cert.public, algName)) publicKeyJwk.alg = algName - return publicKeyJwk + return publicKeyJwk as web_key_type } } throw new Error('Certificate is not trusted.') From ff2f7b05c9c5c379bbc2138d6330b8ef5a542901 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:31:23 -0500 Subject: [PATCH 31/67] naming --- src/cose/sign1/hashEnvelopeSigner.ts | 2 +- src/crypto/signer.ts | 2 +- src/crypto/{subtleCryptoProvider.ts => subtle.ts} | 0 src/crypto/verifier.ts | 2 +- .../draft-ietf-jose-fully-specified-algorithms/thumbprint.ts | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/crypto/{subtleCryptoProvider.ts => subtle.ts} (100%) diff --git a/src/cose/sign1/hashEnvelopeSigner.ts b/src/cose/sign1/hashEnvelopeSigner.ts index a56ecca..1c73c82 100644 --- a/src/cose/sign1/hashEnvelopeSigner.ts +++ b/src/cose/sign1/hashEnvelopeSigner.ts @@ -1,6 +1,6 @@ import signer from "./signer"; -import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; +import subtleCryptoProvider from "../../crypto/subtle"; import { RequestCoseSign1Signer, RequestCoseSign1 } from "./types" diff --git a/src/crypto/signer.ts b/src/crypto/signer.ts index 81e310c..8c99077 100644 --- a/src/crypto/signer.ts +++ b/src/crypto/signer.ts @@ -4,7 +4,7 @@ import { JWK } from 'jose' import { toArrayBuffer } from '../cbor' -import subtleCryptoProvider from './subtleCryptoProvider' +import subtleCryptoProvider from './subtle' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' diff --git a/src/crypto/subtleCryptoProvider.ts b/src/crypto/subtle.ts similarity index 100% rename from src/crypto/subtleCryptoProvider.ts rename to src/crypto/subtle.ts diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index 1c4534d..d97d2e0 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -4,7 +4,7 @@ import { JWK } from 'jose' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' -import subtleCryptoProvider from './subtleCryptoProvider' +import subtleCryptoProvider from './subtle' const verifier = ({ publicKeyJwk }: { publicKeyJwk: JWK }) => { const digest = getDigestFromVerificationKey(`${publicKeyJwk.alg}`) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts index 8226f36..bfddab8 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts @@ -2,7 +2,7 @@ import { calculateJwkThumbprint, calculateJwkThumbprintUri, base64url, JWK } fro import { encodeCanonical } from "../../cbor"; -import subtleCryptoProvider from "../../crypto/subtleCryptoProvider"; +import subtleCryptoProvider from "../../crypto/subtle"; import * as cose from '../../iana/assignments/cose' import { web_key_type } from "../../iana/assignments/jose"; From 2b71e5d0dd8917bf21332b2dd1826982715f8d78 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:39:39 -0500 Subject: [PATCH 32/67] factoring --- .../index.ts | 26 +++++---- tests/crypto.test.ts | 56 ++++++++++++------- tests/receipt.test.ts | 4 +- tests/sign1.attached.test.ts | 4 +- tests/sign1.detached.test.ts | 4 +- tests/signer.test.ts | 2 +- tests/verifiers.test.ts | 6 +- 7 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 7d3f8c3..2f70763 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -189,7 +189,7 @@ export const parse = < } -export const generate = async ({ id, type, algorithm, extractable }: request_crypto_key): Promise => { +const _generate = async ({ id, type, algorithm, extractable }: request_crypto_key): Promise => { switch (type) { case 'application/jwk+json': { const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) @@ -209,6 +209,19 @@ export const generate = async ({ id, type, algorithm, extractable }: request_cry } } +// generate parsed. +export const generate = async < + alg extends parseable_fully_specified_signature_algorithms, + cty extends crypto_key_type +>({ id, algorithm, type }: { + id?: string, + algorithm: alg, + type: cty +}): Promise> => { + const key = await _generate({ id, algorithm, type }) + return parse({ key, type }) +} + export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto_key_type, to: crypto_key_type }) => { switch (from) { case 'application/jwk+json': { @@ -228,17 +241,6 @@ export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto } } -// generate parsed. -export const gen = async < - alg extends parseable_fully_specified_signature_algorithms, - cty extends crypto_key_type ->({ algorithm, type }: { - algorithm: alg, - type: cty -}): Promise> => { - const key = await generate({ algorithm, type }) - return parse({ key, type }) -} export const serialize = < diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts index e61f099..77991e8 100644 --- a/tests/crypto.test.ts +++ b/tests/crypto.test.ts @@ -6,20 +6,23 @@ import * as jose from '../src/iana/assignments/jose' describe('web key', () => { it('generate', async () => { - const key = new Map(Object.entries(JSON.parse(new TextDecoder().decode(await crypto.key.generate({ + const key = new Map(Object.entries(await crypto.key.generate({ id: 'magic-key', type: 'application/jwk+json', algorithm: 'ES256' - }))))) + }))) expect(key.get(jose.web_key.kid)).toBe('magic-key') expect(key.get(jose.web_key.alg)).toBe('ES256') }) it('parse', async () => { const jwk = crypto.key.parse<'ES256', 'application/jwk+json'>({ - key: await crypto.key.generate({ - id: 'magic-key', + key: crypto.key.serialize({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'ES256' + }), type: 'application/jwk+json', - algorithm: 'ES256' }), type: 'application/jwk+json', }) @@ -30,10 +33,13 @@ describe('web key', () => { it('parse polymorphic', async () => { const jwk = crypto.key.parse<'EdDSA', 'application/jwk+json'>({ - key: await crypto.key.generate({ - id: 'magic-key', + key: crypto.key.serialize({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'EdDSA' + }), type: 'application/jwk+json', - algorithm: 'EdDSA' }), type: 'application/jwk+json', }) @@ -44,10 +50,13 @@ describe('web key', () => { it('parse fully specified', async () => { const jwk = crypto.key.parse<'Ed25519', 'application/jwk+json'>({ - key: await crypto.key.generate({ - id: 'magic-key', + key: crypto.key.serialize({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'Ed25519' + }), type: 'application/jwk+json', - algorithm: 'Ed25519' }), type: 'application/jwk+json', }) @@ -59,10 +68,13 @@ describe('web key', () => { describe('cose key', () => { it('generate', async () => { - const bytes = await crypto.key.generate({ - id: 'magic-key', - type: 'application/cose-key', - algorithm: 'ES256' + const bytes = crypto.key.serialize({ + key: await crypto.key.generate({ + id: 'magic-key', + type: 'application/cose-key', + algorithm: 'ES256' + }), + type: 'application/cose-key' }) const key = cbor.decode(bytes) expect(key.get(cose.cose_key.kid)).toBe('magic-key') @@ -71,9 +83,12 @@ describe('cose key', () => { it('parse polymorphic EC2', async () => { const key = crypto.key.parse<'ES256', 'application/cose-key'>({ - key: await crypto.key.generate({ + key: crypto.key.serialize({ + key: await crypto.key.generate({ + type: 'application/cose-key', + algorithm: 'ES256' + }), type: 'application/cose-key', - algorithm: 'ES256' }), type: 'application/cose-key', }) @@ -84,9 +99,12 @@ describe('cose key', () => { it('parse fully specified EC2', async () => { const key = crypto.key.parse<'ESP256', 'application/cose-key'>({ - key: await crypto.key.generate({ + key: crypto.key.serialize({ + key: await crypto.key.generate({ + type: 'application/cose-key', + algorithm: 'ES256' + }), type: 'application/cose-key', - algorithm: 'ES256' }), type: 'application/cose-key', }) diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts index 65dd5dd..f563a7b 100644 --- a/tests/receipt.test.ts +++ b/tests/receipt.test.ts @@ -16,7 +16,7 @@ it('issue & verify', async () => { return cose.receipt.leaf(entry) })) - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) @@ -73,7 +73,7 @@ it('issue & verify', async () => { }) it("add / remove from receipts", async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) diff --git a/tests/sign1.attached.test.ts b/tests/sign1.attached.test.ts index f4fe39f..55f3c23 100644 --- a/tests/sign1.attached.test.ts +++ b/tests/sign1.attached.test.ts @@ -4,7 +4,7 @@ import * as cose from '../src' import { JWK } from 'jose' it('sign and verify', async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) @@ -37,7 +37,7 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) diff --git a/tests/sign1.detached.test.ts b/tests/sign1.detached.test.ts index 5cefb5f..7f79212 100644 --- a/tests/sign1.detached.test.ts +++ b/tests/sign1.detached.test.ts @@ -4,7 +4,7 @@ import * as cose from '../src' import { JWK } from 'jose' it('sign and verify', async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) @@ -42,7 +42,7 @@ it('sign and verify', async () => { }) it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 493041f..465bec7 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -2,7 +2,7 @@ import fs from 'fs' import * as cose from '../src' it('sign and verify large image from file system', async () => { - const privateKeyJwk = await cose.crypto.key.gen<'ES256', 'application/jwk+json'>({ + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", algorithm: "ES256" }) diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 2506a0c..11d0dd7 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -7,15 +7,15 @@ import { web_key_type } from '../src/iana/assignments/jose' it('verify multiple receipts', async () => { - const issuerSecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + const issuerSecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ type: "application/cose-key", algorithm: "ES256" }) - const notary1SecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + const notary1SecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ type: "application/cose-key", algorithm: "ES256" }) - const notary2SecretKey = await cose.crypto.key.gen<'ES256', 'application/cose-key'>({ + const notary2SecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ type: "application/cose-key", algorithm: "ES256" }) From 8f7b9e736bcd6861c5d943fd0b9acb778fad8f9b Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:47:03 -0500 Subject: [PATCH 33/67] less code --- src/cose/attached/index.ts | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/cose/attached/index.ts b/src/cose/attached/index.ts index d4a1cb4..8fa8d06 100644 --- a/src/cose/attached/index.ts +++ b/src/cose/attached/index.ts @@ -1,19 +1,2 @@ -import * as sign1 from "../sign1" - -export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { - const coseSign1Signer = sign1.signer({ remote }) - return { - sign: (req: sign1.RequestCoseSign1) => { - return coseSign1Signer.sign(req) - } - } -} - -export const verifier = ({ resolver }: sign1.RequestCoseSign1Verifier) => { - return { - verify: async (req: sign1.RequestCoseSign1Verify) => { - const coseSign1Verifier = sign1.verifier({ resolver }) - return coseSign1Verifier.verify(req) - } - } -} \ No newline at end of file +export { default as signer } from "../sign1/signer" +export { default as verifier } from "../sign1/verifier" From a67868797b2f4c085367651ced57243deec9645e Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 12:54:51 -0500 Subject: [PATCH 34/67] move --- .../draft-ietf-cose-merkle-tree-proofs/index.ts | 3 +++ .../receipt/add.ts | 10 ++++------ .../receipt/consistency/index.ts | 0 .../receipt/consistency/issue.ts | 10 +++++----- .../receipt/consistency/verify.ts | 6 +++--- .../receipt/get.ts | 8 ++++---- .../receipt/inclusion/index.ts | 0 .../receipt/inclusion/issue.ts | 8 ++++---- .../receipt/inclusion/verify.ts | 6 +++--- .../receipt/index.ts | 0 .../receipt/leaf.ts | 0 .../receipt/remove.ts | 6 +++--- .../receipt/verifier.ts | 6 +++--- src/index.ts | 15 +++++++-------- 14 files changed, 39 insertions(+), 39 deletions(-) create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/add.ts (79%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/consistency/index.ts (100%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/consistency/issue.ts (91%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/consistency/verify.ts (95%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/get.ts (68%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/inclusion/index.ts (100%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/inclusion/issue.ts (88%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/inclusion/verify.ts (94%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/index.ts (100%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/leaf.ts (100%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/remove.ts (77%) rename src/{cose => drafts/draft-ietf-cose-merkle-tree-proofs}/receipt/verifier.ts (90%) diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts new file mode 100644 index 0000000..16be920 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -0,0 +1,3 @@ +import * as receipt from './receipt' + +export { receipt } \ No newline at end of file diff --git a/src/cose/receipt/add.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts similarity index 79% rename from src/cose/receipt/add.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts index 4674ae9..6968e23 100644 --- a/src/cose/receipt/add.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts @@ -1,11 +1,9 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../cbor' - -import { CoseSign1Bytes } from "../sign1"; - -import { draft_headers } from '../../iana/requested/cose'; -import * as cbor from '../../iana/assignments/cbor'; +import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../../cbor' +import { CoseSign1Bytes } from "../../../cose/sign1"; +import { draft_headers } from '../../../iana/requested/cose'; +import * as cbor from '../../../iana/assignments/cbor'; export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) diff --git a/src/cose/receipt/consistency/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts similarity index 100% rename from src/cose/receipt/consistency/index.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts diff --git a/src/cose/receipt/consistency/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts similarity index 91% rename from src/cose/receipt/consistency/issue.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts index c1b066e..9b71b9b 100644 --- a/src/cose/receipt/consistency/issue.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts @@ -1,14 +1,14 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' -import { CoseSign1Bytes, CoseSign1Signer } from "../../sign1" -import { toArrayBuffer } from '../../../cbor' +import { CoseSign1Bytes, CoseSign1Signer } from "../../../../cose/sign1" +import { toArrayBuffer } from '../../../../cbor' -import { draft_headers } from '../../..' +import { draft_headers } from '../../../..' -import { HeaderMap } from '../../..' +import { HeaderMap } from '../../../..' export type RequestIssueConsistencyReceipt = { diff --git a/src/cose/receipt/consistency/verify.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts similarity index 95% rename from src/cose/receipt/consistency/verify.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts index 64d6292..304c89b 100644 --- a/src/cose/receipt/consistency/verify.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts @@ -1,11 +1,11 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' -import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../sign1" +import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../../../cose/sign1" -import { draft_headers } from '../../..' +import { draft_headers } from '../../../..' export type RequestVerifyConsistencyReceipt = { diff --git a/src/cose/receipt/get.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts similarity index 68% rename from src/cose/receipt/get.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts index 3a3dc04..b8bbc7e 100644 --- a/src/cose/receipt/get.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts @@ -1,9 +1,9 @@ -import { decodeFirstSync } from '../../cbor' +import { decodeFirstSync } from '../../../cbor' -import { CoseSign1Bytes } from "../sign1"; +import { CoseSign1Bytes } from "../../../cose/sign1"; -import { draft_headers } from '../../iana/requested/cose'; -import * as cbor from '../../iana/assignments/cbor'; +import { draft_headers } from '../../../iana/requested/cose'; +import * as cbor from '../../../iana/assignments/cbor'; export const get = async (signature: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) diff --git a/src/cose/receipt/inclusion/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts similarity index 100% rename from src/cose/receipt/inclusion/index.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts diff --git a/src/cose/receipt/inclusion/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts similarity index 88% rename from src/cose/receipt/inclusion/issue.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts index 51018af..6605ead 100644 --- a/src/cose/receipt/inclusion/issue.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts @@ -1,13 +1,13 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' -import { CoseSign1Signer } from "../../sign1" +import { CoseSign1Signer } from "../../../../cose/sign1" -import { draft_headers } from '../../..' +import { draft_headers } from '../../../..' -import { HeaderMap } from '../../..' +import { HeaderMap } from '../../../..' export type RequestIssueInclusionReceipt = { protectedHeader: HeaderMap diff --git a/src/cose/receipt/inclusion/verify.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts similarity index 94% rename from src/cose/receipt/inclusion/verify.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts index e94daaf..2cfb1ec 100644 --- a/src/cose/receipt/inclusion/verify.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts @@ -1,12 +1,12 @@ import { CoMETRE } from '@transmute/rfc9162' -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../..' +import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' -import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../sign1" +import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../../../cose/sign1" -import { draft_headers } from '../../..' +import { draft_headers } from '../../../..' export type RequestVerifyInclusionReceipt = { diff --git a/src/cose/receipt/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts similarity index 100% rename from src/cose/receipt/index.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts diff --git a/src/cose/receipt/leaf.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts similarity index 100% rename from src/cose/receipt/leaf.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts diff --git a/src/cose/receipt/remove.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts similarity index 77% rename from src/cose/receipt/remove.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts index 9e1432f..a9dbfd0 100644 --- a/src/cose/receipt/remove.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts @@ -1,7 +1,7 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../cbor' -import { CoseSign1Bytes } from "../sign1"; +import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../../cbor' +import { CoseSign1Bytes } from "../../../cose/sign1"; -import * as cbor from '../../iana/assignments/cbor' +import * as cbor from '../../../iana/assignments/cbor' export const remove = async (signature: CoseSign1Bytes): Promise => { const { tag, value } = decodeFirstSync(signature) diff --git a/src/cose/receipt/verifier.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts similarity index 90% rename from src/cose/receipt/verifier.ts rename to src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts index bc2a641..4b6ac2b 100644 --- a/src/cose/receipt/verifier.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts @@ -1,15 +1,15 @@ -import { RequestCoseSign1DectachedVerify } from "../sign1" +import { RequestCoseSign1DectachedVerify } from "../../../cose/sign1" -import { detached } from "../.." +import { detached } from "../../.." import { get } from "./get" import * as inclusion from './inclusion' import { leaf } from "./leaf" import { remove } from "./remove" -import { web_key_type } from "../../iana/assignments/jose" +import { web_key_type } from "../../../iana/assignments/jose" export type RequestHeaderVerifier = { resolve: (signature: ArrayBuffer) => Promise diff --git a/src/index.ts b/src/index.ts index 854db55..86e3b55 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,12 +4,11 @@ import * as attached from './cose/attached' import * as detached from './cose/detached' +import * as cbor from './cbor' -export * from './cose/sign1' -export * from './x509' - +import * as crypto from './crypto' -export * from './desugar' +export * from './drafts/draft-ietf-cose-merkle-tree-proofs' export * from './drafts/draft-ietf-jose-fully-specified-algorithms' // https://github.com/dajiaji/hpke-js/issues/302 @@ -20,8 +19,8 @@ export * from './drafts/draft-ietf-jose-fully-specified-algorithms' export * from './iana/assignments/cbor' export * from './iana/assignments/cose' export * from './iana/requested/cose' +export * from './cose/sign1' +export * from './x509' +export * from './desugar' -import * as cbor from './cbor' -import * as receipt from './cose/receipt' -import * as crypto from './crypto' -export { crypto, cbor, attached, detached, receipt } \ No newline at end of file +export { crypto, cbor, attached, detached } \ No newline at end of file From 5beddd9ad105d242af5c0216ab07d7cf5c7a46c2 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 13:32:36 -0500 Subject: [PATCH 35/67] add diag --- src/cbor/pretty/indentBlock.ts | 5 +++++ src/cbor/pretty/prettyCoseSign1.ts | 19 +++++++++++++++++- src/cbor/pretty/prettyHeader.ts | 25 ++++++++++++++++++++++++ src/cbor/pretty/prettyPayload.ts | 6 ++++++ tests/__fixtures__/detached-payload.diag | 9 +++++++++ tests/edn.test.ts | 8 ++------ 6 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/cbor/pretty/indentBlock.ts create mode 100644 src/cbor/pretty/prettyHeader.ts create mode 100644 src/cbor/pretty/prettyPayload.ts create mode 100644 tests/__fixtures__/detached-payload.diag diff --git a/src/cbor/pretty/indentBlock.ts b/src/cbor/pretty/indentBlock.ts new file mode 100644 index 0000000..c02aca9 --- /dev/null +++ b/src/cbor/pretty/indentBlock.ts @@ -0,0 +1,5 @@ +export const indentBlock = (lines: string, padding: string) => { + return lines.split('\n').map((line: string) => { + return padding + line + }).join('\n') +} \ No newline at end of file diff --git a/src/cbor/pretty/prettyCoseSign1.ts b/src/cbor/pretty/prettyCoseSign1.ts index a84d2bb..b12fff9 100644 --- a/src/cbor/pretty/prettyCoseSign1.ts +++ b/src/cbor/pretty/prettyCoseSign1.ts @@ -1,7 +1,24 @@ import * as cbor from 'cbor-web' +import { prettyHeader } from './prettyHeader' +import { prettyPayload } from './prettyPayload' + +import { ellideBytes } from './ellideBytes' + export const prettyCoseSign1 = (data: Buffer) => { - return cbor.diagnose(data) + const decoded = cbor.decode(data) + const [encodedProtected, decodedUnprotected, encodedPayload, signature] = decoded.value + const decodedProtected = cbor.decode(encodedProtected) + return ` +/ cose-sign1 / ${decoded.tag}([ + / protected / << +${prettyHeader(decodedProtected)} + >> + / unprotected / ${prettyHeader(decodedUnprotected)} + / payload / ${prettyPayload(encodedPayload)} + / signature / ${ellideBytes(signature)} +]) +` } diff --git a/src/cbor/pretty/prettyHeader.ts b/src/cbor/pretty/prettyHeader.ts new file mode 100644 index 0000000..e97fd7c --- /dev/null +++ b/src/cbor/pretty/prettyHeader.ts @@ -0,0 +1,25 @@ + + +import { indentBlock } from "./indentBlock" + +import { header, labels_to_algorithms } from "../../iana/assignments/cose" + +export const prettyHeader = (map: Map | object) => { + if (!(map instanceof Map)) { + return '{},' + } + let result = `` + for (const [label, value] of map.entries()) { + switch (label) { + case header.alg: { + result += indentBlock(`/ algorithm / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + break + } + default: { + result += indentBlock(`${label}: ${value},`, ' ') + } + } + } + result += `` + return result +} \ No newline at end of file diff --git a/src/cbor/pretty/prettyPayload.ts b/src/cbor/pretty/prettyPayload.ts new file mode 100644 index 0000000..0eea240 --- /dev/null +++ b/src/cbor/pretty/prettyPayload.ts @@ -0,0 +1,6 @@ +export const prettyPayload = (payload: ArrayBuffer | null) => { + if (payload === null) { + return 'null,' + } + return 'payload' +} \ No newline at end of file diff --git a/tests/__fixtures__/detached-payload.diag b/tests/__fixtures__/detached-payload.diag new file mode 100644 index 0000000..54cd71c --- /dev/null +++ b/tests/__fixtures__/detached-payload.diag @@ -0,0 +1,9 @@ + +/ cose-sign1 / 18([ + / protected / << + / algorithm / 1 : -35, # ES384 + >> + / unprotected / {}, + / payload / null, + / signature / h'b4e0d531...b0a48a3a' +]) diff --git a/tests/edn.test.ts b/tests/edn.test.ts index aa89507..2ec6de7 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -11,12 +11,8 @@ it('cose key', async () => { it('detached payload cose sign1', async () => { const input = fs.readFileSync('./tests/__fixtures__/detached-payload.cbor') - // const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') + const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') const diag = await cose.cbor.diag(input, "application/cose") - // console.log(diag) - // expect(diag).toBe(output.toString()) + expect(diag).toBe(output.toString()) }) -// Tomorrow... -// More EDN examples for SCITT stuff -// maybe HPKE... \ No newline at end of file From 708d7b5986503d5bd45c94d954cc3895dd9a7753 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 15:46:47 -0500 Subject: [PATCH 36/67] factoring --- src/cose/index.ts | 5 ++ src/crypto/cose_key_to_crypto_key.ts | 11 ++++ src/crypto/index.ts | 9 +++- src/crypto/web.ts | 53 +++++++++++++++++++ src/crypto/web_key_to_crypto_key.ts | 34 ++++++++++++ .../index.ts | 1 + .../signer.ts | 25 +++++++++ src/index.ts | 7 ++- src/x509/certificate.ts | 9 +++- tests/crypto.test.ts | 30 ++++++++++- tests/edn.test.ts | 28 ++++++++-- tests/verifiers.test.ts | 2 +- tests/x509.test.ts | 2 +- 13 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 src/cose/index.ts create mode 100644 src/crypto/cose_key_to_crypto_key.ts create mode 100644 src/crypto/web.ts create mode 100644 src/crypto/web_key_to_crypto_key.ts create mode 100644 src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts diff --git a/src/cose/index.ts b/src/cose/index.ts new file mode 100644 index 0000000..ee73427 --- /dev/null +++ b/src/cose/index.ts @@ -0,0 +1,5 @@ +import * as sign1 from './sign1' +import * as attached from './attached' +import * as detached from './detached' + +export { sign1, attached, detached } \ No newline at end of file diff --git a/src/crypto/cose_key_to_crypto_key.ts b/src/crypto/cose_key_to_crypto_key.ts new file mode 100644 index 0000000..c3dfbd1 --- /dev/null +++ b/src/crypto/cose_key_to_crypto_key.ts @@ -0,0 +1,11 @@ +import { any_cose_key, cose_key, cose_key_type } from "../iana/assignments/cose" +import { cose_key_to_web_key } from "./key" +import { web_key_to_crypto_key } from "./web_key_to_crypto_key" + +export const cose_key_to_crypto_key = async (key: any_cose_key): Promise => { + if (key.get(cose_key.kty) != cose_key_type.ec2) { + throw new Error('Only EC2 keys are supported') + } + const jwk = await cose_key_to_web_key(key) + return web_key_to_crypto_key(jwk) +} \ No newline at end of file diff --git a/src/crypto/index.ts b/src/crypto/index.ts index e5feb4e..15e04b9 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -1,8 +1,13 @@ import signer from "./signer"; - import verifier from "./verifier"; +import subtle from "./subtle"; import * as key from './key' +import { web_key_to_crypto_key } from "./web_key_to_crypto_key"; + +import * as web from './web' + +export { signer, verifier, key, subtle, web_key_to_crypto_key, web } + -export { signer, verifier, key } \ No newline at end of file diff --git a/src/crypto/web.ts b/src/crypto/web.ts new file mode 100644 index 0000000..fd73a3e --- /dev/null +++ b/src/crypto/web.ts @@ -0,0 +1,53 @@ + +import subtle from "./subtle"; + +export const webCryptoSignatureAlgorithmByCoseSignatureAlgorithm = { + 'ES256': { + name: "ECDSA", + hash: { name: 'SHA-256' }, + }, + 'ES384': { + name: "ECDSA", + hash: { name: 'SHA-384' }, + }, + 'ES521': { + name: "ECDSA", + hash: { name: 'SHA-512' }, + } +} as const + +export type WebCryptoCoseSignatureAlgorithm = keyof typeof webCryptoSignatureAlgorithmByCoseSignatureAlgorithm + +export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseSignatureAlgorithm }) => { + return { + sign: async (toBeSigned: ArrayBuffer): Promise => { + return subtle().then((subtle) => { + return subtle.sign( + webCryptoSignatureAlgorithmByCoseSignatureAlgorithm[algorithm], + key, + toBeSigned, + ) + }) + } + } +} + +export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseSignatureAlgorithm }) => { + return { + verify: async (toBeSigned: ArrayBuffer, signature: ArrayBuffer): Promise => { + return subtle().then(async (subtle) => { + const verified = await subtle.verify( + webCryptoSignatureAlgorithmByCoseSignatureAlgorithm[algorithm], + key, + signature, + toBeSigned, + ); + if (!verified) { + throw new Error('Signature verification failed'); + } + return toBeSigned; + }) + } + } +} + diff --git a/src/crypto/web_key_to_crypto_key.ts b/src/crypto/web_key_to_crypto_key.ts new file mode 100644 index 0000000..7abcea9 --- /dev/null +++ b/src/crypto/web_key_to_crypto_key.ts @@ -0,0 +1,34 @@ +import subtle from "./subtle" + +const webCryptoKeyParamsByCoseAlgorithm = { + 'ES256': { + name: "ECDSA", + namedCurve: 'P-256', // not true... + }, + 'ES384': { + name: "ECDSA", + namedCurve: 'P-384', // not true... + }, + 'ES521': { + name: "ECDSA", + namedCurve: 'P-521', // not true... + } +} as const + +export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgorithm + +export const web_key_to_crypto_key = async (jwk: any, key_ops?: string[]): Promise => { + if (jwk.kty != 'EC') { + throw new Error('Only EC keys are supported') + } + return subtle().then((subtle) => { + const { alg, ...unrestrictedKey } = jwk + return subtle.importKey( + "jwk", + unrestrictedKey, + webCryptoKeyParamsByCoseAlgorithm[alg as WebCryptoCoseAlgorithm], + jwk.ext || true, + jwk.key_ops || key_ops || [], + ) + }) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 2f70763..2a67d64 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -15,6 +15,7 @@ import { web_key_thumbprint } from './thumbprint' export { web_key_to_cose_key, cose_key_to_web_key, public_from_private } export * from './thumbprint' +export * from './signer' const encoder = new TextEncoder() const decoder = new TextDecoder() diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts new file mode 100644 index 0000000..78a4c22 --- /dev/null +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts @@ -0,0 +1,25 @@ + +import { cose_key_to_web_key } from "."; + +import { any_cose_key, crypto } from "../.."; +import { web_key_type } from "../../iana/assignments/jose"; + +export const signer = async ({ key, algorithm }: { key: web_key_type | any_cose_key | CryptoKey, algorithm: 'ES256' }) => { + let privateKey + if (key instanceof CryptoKey) { + // nothing to do + } else if (key instanceof Map) { + const jwk = await cose_key_to_web_key(key as any_cose_key) + privateKey = await crypto.web_key_to_crypto_key(jwk, ['sign']) + } else if ((key as web_key_type).kty) { + privateKey = await crypto.web_key_to_crypto_key(key, ['sign']) + } + if (privateKey === undefined) { + throw new Error('Unsupported key') + } + return crypto.web + .signer({ + key: privateKey, + algorithm + }) +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 86e3b55..5eaa161 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,7 @@ -import * as attached from './cose/attached' -import * as detached from './cose/detached' + import * as cbor from './cbor' import * as crypto from './crypto' @@ -19,8 +18,8 @@ export * from './drafts/draft-ietf-jose-fully-specified-algorithms' export * from './iana/assignments/cbor' export * from './iana/assignments/cose' export * from './iana/requested/cose' -export * from './cose/sign1' +export * from './cose' export * from './x509' export * from './desugar' -export { crypto, cbor, attached, detached } \ No newline at end of file +export { crypto, cbor } \ No newline at end of file diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 57230eb..71586f4 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -2,7 +2,7 @@ import { exportJWK, exportPKCS8, importPKCS8 } from 'jose'; import * as x509 from "@peculiar/x509"; -import { detached, RequestCoseSign1VerifyDetached } from '..'; +import { detached } from '..'; import { crypto } from '..'; import { JWK } from 'jose' import * as cose from '../iana/assignments/cose'; @@ -10,6 +10,9 @@ import { labels_to_algorithms } from '../iana/requested/cose'; import { web_key_type } from '../iana/assignments/jose'; +import { RequestCoseSign1DectachedVerify } from '../../src/cose/sign1/types'; + + // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) @@ -23,6 +26,7 @@ const provide = async () => { } } +// need to DRY... const algTowebCryptoParams: Record = { 'ESP256': { @@ -116,9 +120,10 @@ export type RequestCertificateVerifier = { } } + const verifier = ({ resolver }: RequestCertificateVerifier) => { return { - verify: async (req: RequestCoseSign1VerifyDetached) => { + verify: async (req: RequestCoseSign1DectachedVerify) => { const verifier = detached.verifier({ resolver }) return verifier.verify(req) } diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts index 77991e8..27c0f8a 100644 --- a/tests/crypto.test.ts +++ b/tests/crypto.test.ts @@ -1,9 +1,37 @@ -import { crypto } from '../src' +import { crypto, public_from_private } from '../src' import * as cbor from 'cbor-web' import * as cose from '../src/iana/assignments/cose' import * as jose from '../src/iana/assignments/jose' +describe('crypto key', () => { + it('sanity', async () => { + const privateKey = await crypto.key.generate({ + id: 'magic-key', + type: 'application/jwk+json', + algorithm: 'ES256' + }) + const publicKey = public_from_private({ + key: privateKey, + type: 'application/jwk+json' + }) + const toBeSigned = Buffer.from('hello') + const signature = await crypto.web + .signer({ + key: await crypto.web_key_to_crypto_key(privateKey, ['sign']), + algorithm: 'ES256' + }) + .sign(toBeSigned) + const verified = await crypto.web + .verifier({ + key: await crypto.web_key_to_crypto_key(publicKey, ['verify']), + algorithm: 'ES256' + }) + .verify(toBeSigned, signature) + expect(verified.toString()).toBe('hello') + }) +}) + describe('web key', () => { it('generate', async () => { const key = new Map(Object.entries(await crypto.key.generate({ diff --git a/tests/edn.test.ts b/tests/edn.test.ts index 2ec6de7..03c88ea 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -1,14 +1,14 @@ import fs from 'fs' import * as cose from '../src' +const key = fs.readFileSync('./tests/__fixtures__/cose-key.cbor') + it('cose key', async () => { - const input = fs.readFileSync('./tests/__fixtures__/cose-key.cbor') const output = fs.readFileSync('./tests/__fixtures__/cose-key.diag') - const diag = await cose.cbor.diag(input, "application/cose-key") + const diag = await cose.cbor.diag(key, "application/cose-key") expect(diag).toBe(output.toString()) }) - it('detached payload cose sign1', async () => { const input = fs.readFileSync('./tests/__fixtures__/detached-payload.cbor') const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') @@ -16,3 +16,25 @@ it('detached payload cose sign1', async () => { expect(diag).toBe(output.toString()) }) +it.skip('hash envelope', async () => { + const signature = await cose.sign1.hash + .signer({ + remote: await cose.crypto.key.signer({ + algorithm: 'ES256', + key: await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ + key, + type: 'application/cose-key' + }), + }) + }) + .sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256] + ]), + payload: Buffer.from('hello') + }) + + console.log(signature) +}) + diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts index 11d0dd7..aff9239 100644 --- a/tests/verifiers.test.ts +++ b/tests/verifiers.test.ts @@ -74,7 +74,7 @@ it('verify multiple receipts', async () => { }) const transparentSignature1 = await cose.receipt.add(signatureForImage, receiptForImageSignature1) const transparentSignature = await cose.receipt.add(transparentSignature1, receiptForImageSignature2) - const resolve = async (coseSign1: cose.CoseSign1Bytes): Promise => { + const resolve = async (coseSign1: ArrayBuffer): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') diff --git a/tests/x509.test.ts b/tests/x509.test.ts index e42db9a..748cd6f 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -32,7 +32,7 @@ it('sign and verify with x5t and key resolver', async () => { ]), payload: content }) - const certificateFromThumbprint = async (coseSign1: cose.CoseSign1Bytes): Promise => { + const certificateFromThumbprint = async (coseSign1: ArrayBuffer): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') From a52619c6e8db5739fcb57279dbacfc3d136f7504 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 15:49:23 -0500 Subject: [PATCH 37/67] order --- src/crypto/cose_key_to_crypto_key.ts | 11 ----- src/crypto/index.ts | 4 +- src/crypto/web.ts | 43 +++++++++++++++++++ src/crypto/web_key_to_crypto_key.ts | 34 --------------- .../signer.ts | 4 +- tests/crypto.test.ts | 4 +- 6 files changed, 48 insertions(+), 52 deletions(-) delete mode 100644 src/crypto/cose_key_to_crypto_key.ts delete mode 100644 src/crypto/web_key_to_crypto_key.ts diff --git a/src/crypto/cose_key_to_crypto_key.ts b/src/crypto/cose_key_to_crypto_key.ts deleted file mode 100644 index c3dfbd1..0000000 --- a/src/crypto/cose_key_to_crypto_key.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { any_cose_key, cose_key, cose_key_type } from "../iana/assignments/cose" -import { cose_key_to_web_key } from "./key" -import { web_key_to_crypto_key } from "./web_key_to_crypto_key" - -export const cose_key_to_crypto_key = async (key: any_cose_key): Promise => { - if (key.get(cose_key.kty) != cose_key_type.ec2) { - throw new Error('Only EC2 keys are supported') - } - const jwk = await cose_key_to_web_key(key) - return web_key_to_crypto_key(jwk) -} \ No newline at end of file diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 15e04b9..d1e39a6 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -4,10 +4,8 @@ import subtle from "./subtle"; import * as key from './key' -import { web_key_to_crypto_key } from "./web_key_to_crypto_key"; - import * as web from './web' -export { signer, verifier, key, subtle, web_key_to_crypto_key, web } +export { signer, verifier, key, subtle, web } diff --git a/src/crypto/web.ts b/src/crypto/web.ts index fd73a3e..6684e2d 100644 --- a/src/crypto/web.ts +++ b/src/crypto/web.ts @@ -1,5 +1,8 @@ import subtle from "./subtle"; +import { any_cose_key, cose_key, cose_key_type } from "../iana/assignments/cose" +import { cose_key_to_web_key } from "./key" + export const webCryptoSignatureAlgorithmByCoseSignatureAlgorithm = { 'ES256': { @@ -51,3 +54,43 @@ export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCry } } +const webCryptoKeyParamsByCoseAlgorithm = { + 'ES256': { + name: "ECDSA", + namedCurve: 'P-256', // not true... + }, + 'ES384': { + name: "ECDSA", + namedCurve: 'P-384', // not true... + }, + 'ES521': { + name: "ECDSA", + namedCurve: 'P-521', // not true... + } +} as const + +export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgorithm + +export const web_key_to_crypto_key = async (jwk: any, key_ops?: string[]): Promise => { + if (jwk.kty != 'EC') { + throw new Error('Only EC keys are supported') + } + return subtle().then((subtle) => { + const { alg, ...unrestrictedKey } = jwk + return subtle.importKey( + "jwk", + unrestrictedKey, + webCryptoKeyParamsByCoseAlgorithm[alg as WebCryptoCoseAlgorithm], + jwk.ext || true, + jwk.key_ops || key_ops || [], + ) + }) +} + +export const cose_key_to_crypto_key = async (key: any_cose_key): Promise => { + if (key.get(cose_key.kty) != cose_key_type.ec2) { + throw new Error('Only EC2 keys are supported') + } + const jwk = await cose_key_to_web_key(key) + return web_key_to_crypto_key(jwk) +} \ No newline at end of file diff --git a/src/crypto/web_key_to_crypto_key.ts b/src/crypto/web_key_to_crypto_key.ts deleted file mode 100644 index 7abcea9..0000000 --- a/src/crypto/web_key_to_crypto_key.ts +++ /dev/null @@ -1,34 +0,0 @@ -import subtle from "./subtle" - -const webCryptoKeyParamsByCoseAlgorithm = { - 'ES256': { - name: "ECDSA", - namedCurve: 'P-256', // not true... - }, - 'ES384': { - name: "ECDSA", - namedCurve: 'P-384', // not true... - }, - 'ES521': { - name: "ECDSA", - namedCurve: 'P-521', // not true... - } -} as const - -export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgorithm - -export const web_key_to_crypto_key = async (jwk: any, key_ops?: string[]): Promise => { - if (jwk.kty != 'EC') { - throw new Error('Only EC keys are supported') - } - return subtle().then((subtle) => { - const { alg, ...unrestrictedKey } = jwk - return subtle.importKey( - "jwk", - unrestrictedKey, - webCryptoKeyParamsByCoseAlgorithm[alg as WebCryptoCoseAlgorithm], - jwk.ext || true, - jwk.key_ops || key_ops || [], - ) - }) -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts index 78a4c22..c0ac07a 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts @@ -10,9 +10,9 @@ export const signer = async ({ key, algorithm }: { key: web_key_type | any_cose_ // nothing to do } else if (key instanceof Map) { const jwk = await cose_key_to_web_key(key as any_cose_key) - privateKey = await crypto.web_key_to_crypto_key(jwk, ['sign']) + privateKey = await crypto.web.web_key_to_crypto_key(jwk, ['sign']) } else if ((key as web_key_type).kty) { - privateKey = await crypto.web_key_to_crypto_key(key, ['sign']) + privateKey = await crypto.web.web_key_to_crypto_key(key, ['sign']) } if (privateKey === undefined) { throw new Error('Unsupported key') diff --git a/tests/crypto.test.ts b/tests/crypto.test.ts index 27c0f8a..7270703 100644 --- a/tests/crypto.test.ts +++ b/tests/crypto.test.ts @@ -18,13 +18,13 @@ describe('crypto key', () => { const toBeSigned = Buffer.from('hello') const signature = await crypto.web .signer({ - key: await crypto.web_key_to_crypto_key(privateKey, ['sign']), + key: await crypto.web.web_key_to_crypto_key(privateKey, ['sign']), algorithm: 'ES256' }) .sign(toBeSigned) const verified = await crypto.web .verifier({ - key: await crypto.web_key_to_crypto_key(publicKey, ['verify']), + key: await crypto.web.web_key_to_crypto_key(publicKey, ['verify']), algorithm: 'ES256' }) .verify(toBeSigned, signature) From 36439ee7060b9499702398d31459166ff2350fd1 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 15:51:26 -0500 Subject: [PATCH 38/67] cleaning --- src/cose/sign1/index.ts | 3 +-- .../draft-ietf-cose-hash-envelope/index.ts} | 4 ++-- src/index.ts | 1 + tests/edn.test.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename src/{cose/sign1/hashEnvelopeSigner.ts => drafts/draft-ietf-cose-hash-envelope/index.ts} (87%) diff --git a/src/cose/sign1/index.ts b/src/cose/sign1/index.ts index 92f15f2..fb8aad8 100644 --- a/src/cose/sign1/index.ts +++ b/src/cose/sign1/index.ts @@ -1,8 +1,7 @@ import signer from "./signer"; import verifier from "./verifier"; -import { hash } from './hashEnvelopeSigner' export * from './types' -export { signer, verifier, hash } \ No newline at end of file +export { signer, verifier } \ No newline at end of file diff --git a/src/cose/sign1/hashEnvelopeSigner.ts b/src/drafts/draft-ietf-cose-hash-envelope/index.ts similarity index 87% rename from src/cose/sign1/hashEnvelopeSigner.ts rename to src/drafts/draft-ietf-cose-hash-envelope/index.ts index 1c73c82..4f9e24d 100644 --- a/src/cose/sign1/hashEnvelopeSigner.ts +++ b/src/drafts/draft-ietf-cose-hash-envelope/index.ts @@ -1,8 +1,8 @@ -import signer from "./signer"; +import signer from "../../cose/sign1/signer"; import subtleCryptoProvider from "../../crypto/subtle"; -import { RequestCoseSign1Signer, RequestCoseSign1 } from "./types" +import { RequestCoseSign1Signer, RequestCoseSign1 } from "../../cose/sign1/types" import * as cose from '../../iana/assignments/cose' import { draft_headers } from '../../iana/requested/cose' diff --git a/src/index.ts b/src/index.ts index 5eaa161..60ae05f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import * as crypto from './crypto' export * from './drafts/draft-ietf-cose-merkle-tree-proofs' export * from './drafts/draft-ietf-jose-fully-specified-algorithms' +export * from './drafts/draft-ietf-cose-hash-envelope' // https://github.com/dajiaji/hpke-js/issues/302 // this issue also effect vercel ncc diff --git a/tests/edn.test.ts b/tests/edn.test.ts index 03c88ea..bdeed2c 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -17,7 +17,7 @@ it('detached payload cose sign1', async () => { }) it.skip('hash envelope', async () => { - const signature = await cose.sign1.hash + const signature = await cose.hash .signer({ remote: await cose.crypto.key.signer({ algorithm: 'ES256', From bb94a3cb0aa17857495f07ea72d86121f4f36e57 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 16:02:57 -0500 Subject: [PATCH 39/67] less --- src/crypto/web.ts | 38 ++++++++++++------------------------- src/x509/certificate.ts | 42 ++++------------------------------------- tests/edn.test.ts | 16 +++++++++------- 3 files changed, 25 insertions(+), 71 deletions(-) diff --git a/src/crypto/web.ts b/src/crypto/web.ts index 6684e2d..40db2b7 100644 --- a/src/crypto/web.ts +++ b/src/crypto/web.ts @@ -3,30 +3,32 @@ import subtle from "./subtle"; import { any_cose_key, cose_key, cose_key_type } from "../iana/assignments/cose" import { cose_key_to_web_key } from "./key" - -export const webCryptoSignatureAlgorithmByCoseSignatureAlgorithm = { +export const webCryptoKeyParamsByCoseAlgorithm = { 'ES256': { name: "ECDSA", - hash: { name: 'SHA-256' }, + hash: 'SHA-256', + namedCurve: 'P-256', // not true... }, 'ES384': { name: "ECDSA", - hash: { name: 'SHA-384' }, + hash: 'SHA-384', + namedCurve: 'P-384', // not true... }, 'ES521': { name: "ECDSA", - hash: { name: 'SHA-512' }, + hash: 'SHA-512', + namedCurve: 'P-521', // not true... } } as const -export type WebCryptoCoseSignatureAlgorithm = keyof typeof webCryptoSignatureAlgorithmByCoseSignatureAlgorithm +export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgorithm -export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseSignatureAlgorithm }) => { +export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseAlgorithm }) => { return { sign: async (toBeSigned: ArrayBuffer): Promise => { return subtle().then((subtle) => { return subtle.sign( - webCryptoSignatureAlgorithmByCoseSignatureAlgorithm[algorithm], + webCryptoKeyParamsByCoseAlgorithm[algorithm], key, toBeSigned, ) @@ -35,12 +37,12 @@ export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCrypt } } -export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseSignatureAlgorithm }) => { +export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseAlgorithm }) => { return { verify: async (toBeSigned: ArrayBuffer, signature: ArrayBuffer): Promise => { return subtle().then(async (subtle) => { const verified = await subtle.verify( - webCryptoSignatureAlgorithmByCoseSignatureAlgorithm[algorithm], + webCryptoKeyParamsByCoseAlgorithm[algorithm], key, signature, toBeSigned, @@ -54,22 +56,6 @@ export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCry } } -const webCryptoKeyParamsByCoseAlgorithm = { - 'ES256': { - name: "ECDSA", - namedCurve: 'P-256', // not true... - }, - 'ES384': { - name: "ECDSA", - namedCurve: 'P-384', // not true... - }, - 'ES521': { - name: "ECDSA", - namedCurve: 'P-521', // not true... - } -} as const - -export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgorithm export const web_key_to_crypto_key = async (jwk: any, key_ops?: string[]): Promise => { if (jwk.kty != 'EC') { diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 71586f4..289eee1 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -9,10 +9,10 @@ import * as cose from '../iana/assignments/cose'; import { labels_to_algorithms } from '../iana/requested/cose'; import { web_key_type } from '../iana/assignments/jose'; +import { webCryptoKeyParamsByCoseAlgorithm, WebCryptoCoseAlgorithm } from '../crypto/web'; import { RequestCoseSign1DectachedVerify } from '../../src/cose/sign1/types'; - // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) @@ -26,38 +26,8 @@ const provide = async () => { } } -// need to DRY... -const algTowebCryptoParams: Record - = { - 'ESP256': { - name: "ECDSA", - hash: "SHA-256", - namedCurve: "P-256", - }, - 'ESP384': { - name: "ECDSA", - hash: "SHA-384", - namedCurve: "P-384", - }, - 'ES256': { - name: "ECDSA", - hash: "SHA-256", - namedCurve: "P-256", - }, - 'ES384': { - name: "ECDSA", - hash: "SHA-384", - namedCurve: "P-384", - }, - 'ES512': { - name: "ECDSA", - hash: "SHA-512", - namedCurve: "P-521", - } -} - export type RequestRootCertificate = { - alg: string + alg: WebCryptoCoseAlgorithm sub: string iss: string nbf: string @@ -76,12 +46,8 @@ export type RootCertificateResponse = { public: string, private: string } const root = async (req: RequestRootCertificate): Promise => { const crypto = await provide() x509.cryptoProvider.set(crypto); - const extensions: x509.JsonGeneralNames = [ - // { - // type: 'url', value: `https://vendor.example` - // } - ] - const webCryptoAlgorithm = algTowebCryptoParams[req.alg] + const extensions: x509.JsonGeneralNames = [] + const webCryptoAlgorithm = webCryptoKeyParamsByCoseAlgorithm[req.alg] const caKeys = await crypto.subtle.generateKey(webCryptoAlgorithm, extractable, ["sign", "verify"]); const caCert = await x509.X509CertificateGenerator.create({ serialNumber: req.serial || "01", diff --git a/tests/edn.test.ts b/tests/edn.test.ts index bdeed2c..61d8e15 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -17,15 +17,17 @@ it('detached payload cose sign1', async () => { }) it.skip('hash envelope', async () => { + const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ + key, + type: 'application/cose-key' + }) + const signer = await cose.crypto.key.signer({ + algorithm: 'ES256', + key: k, + }) const signature = await cose.hash .signer({ - remote: await cose.crypto.key.signer({ - algorithm: 'ES256', - key: await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ - key, - type: 'application/cose-key' - }), - }) + remote: signer }) .sign({ protectedHeader: cose.ProtectedHeader([ From 87e499f78d2c26579be86fe2db507053fc63c115 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 16:18:06 -0500 Subject: [PATCH 40/67] cleaning --- src/crypto/web.ts | 8 ++++++++ tests/__fixtures__/hash-envelope.cbor | 2 ++ tests/__fixtures__/hash-envelope.diag | 9 +++++++++ tests/edn.test.ts | 15 ++++++++++----- 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 tests/__fixtures__/hash-envelope.cbor create mode 100644 tests/__fixtures__/hash-envelope.diag diff --git a/src/crypto/web.ts b/src/crypto/web.ts index 40db2b7..a27d3be 100644 --- a/src/crypto/web.ts +++ b/src/crypto/web.ts @@ -4,6 +4,11 @@ import { any_cose_key, cose_key, cose_key_type } from "../iana/assignments/cose" import { cose_key_to_web_key } from "./key" export const webCryptoKeyParamsByCoseAlgorithm = { + 'ESP256': { + name: "ECDSA", + hash: 'SHA-256', + namedCurve: 'P-256', // true + }, 'ES256': { name: "ECDSA", hash: 'SHA-256', @@ -63,6 +68,9 @@ export const web_key_to_crypto_key = async (jwk: any, key_ops?: string[]): Promi } return subtle().then((subtle) => { const { alg, ...unrestrictedKey } = jwk + if (!webCryptoKeyParamsByCoseAlgorithm[alg as WebCryptoCoseAlgorithm]) { + throw new Error('Unknown algorithm: ' + alg) + } return subtle.importKey( "jwk", unrestrictedKey, diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor new file mode 100644 index 0000000..ff3b6e2 --- /dev/null +++ b/tests/__fixtures__/hash-envelope.cbor @@ -0,0 +1,2 @@ +Ò„X%£&9/9‘xtext/plain; charset=utf-8÷X ö_¬@õ|=Y[YTùu ¨ šëOäF-‡ÓÑ9IˆZŽX@ÁË­_ }C‚qeÄ£ƒ)³cl˜àhU.ÙŽÚ!PËpD«Õ +¨à%DC±Èíà"½ò[?¿FYMËè \ No newline at end of file diff --git a/tests/__fixtures__/hash-envelope.diag b/tests/__fixtures__/hash-envelope.diag new file mode 100644 index 0000000..cfbd0a6 --- /dev/null +++ b/tests/__fixtures__/hash-envelope.diag @@ -0,0 +1,9 @@ + +/ cose-sign1 / 18([ + / protected / << + / algorithm / 1 : -7, # ES256 -6800: -16, -6802: text/plain; charset=utf-8, + >> + / unprotected / {}, + / payload / payload + / signature / h'08c1cb19...594dcbe8' +]) diff --git a/tests/edn.test.ts b/tests/edn.test.ts index 61d8e15..0db34c9 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -16,7 +16,7 @@ it('detached payload cose sign1', async () => { expect(diag).toBe(output.toString()) }) -it.skip('hash envelope', async () => { +it('hash envelope', async () => { const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ key, type: 'application/cose-key' @@ -32,11 +32,16 @@ it.skip('hash envelope', async () => { .sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], - [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256] + [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256], + [cose.draft_headers.payload_preimage_content_type, 'text/plain; charset=utf-8'] ]), - payload: Buffer.from('hello') + payload: Buffer.from('🔥 hello') }) - - console.log(signature) + fs.writeFileSync('./tests/__fixtures__/hash-envelope.cbor', signature) + const input = fs.readFileSync('./tests/__fixtures__/hash-envelope.cbor') + const diag = await cose.cbor.diag(input, "application/cose") + fs.writeFileSync('./tests/__fixtures__/hash-envelope.diag', diag) + const output = fs.readFileSync('./tests/__fixtures__/hash-envelope.diag') + expect(diag).toBe(output.toString()) }) From dcc5ec9fa8dd0bf1f2676c360bee846b0a365824 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 16:43:03 -0500 Subject: [PATCH 41/67] cleaning --- src/cbor/diag.ts | 11 +++++++++-- src/cbor/pretty/ellideBytes.ts | 13 +++++++++++-- src/cbor/pretty/prettyHeader.ts | 17 +++++++++++++++-- src/cbor/pretty/prettyPayload.ts | 5 ++++- tests/__fixtures__/detached-payload.diag | 3 +-- tests/__fixtures__/hash-envelope.cbor | 3 +-- tests/__fixtures__/hash-envelope.diag | 10 ++++++---- tests/edn.test.ts | 8 +++++--- 8 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/cbor/diag.ts b/src/cbor/diag.ts index d7d9366..5d4c7bf 100644 --- a/src/cbor/diag.ts +++ b/src/cbor/diag.ts @@ -7,14 +7,21 @@ import { diagnostic_types } from '../iana/assignments/media-types' import { prettyCoseKey } from './pretty/prettyCoseKey' import { prettyCose } from './pretty/prettyCose' +const removeBlankLines = (text: string) => { + return text.replace(/(^[ \t]*\n)/gm, "") +} + export const diag = async (data: any, contentType: diagnostic_types) => { try { + let text = '' if (contentType === 'application/cose-key') { - return prettyCoseKey(data) + text = await prettyCoseKey(data) } if (contentType === 'application/cose') { - return prettyCose(data) + text = await prettyCose(data) } + return removeBlankLines(text); + } catch (e) { return cbor.diagnose(data) } diff --git a/src/cbor/pretty/ellideBytes.ts b/src/cbor/pretty/ellideBytes.ts index d9bc7b7..63d014c 100644 --- a/src/cbor/pretty/ellideBytes.ts +++ b/src/cbor/pretty/ellideBytes.ts @@ -1,4 +1,13 @@ -export const ellideBytes = (bytes: Buffer) => { - const line = `h'${bytes.toString('hex').toLowerCase()}'` + + + +function buf2hex(buffer: ArrayBuffer) { // buffer is an ArrayBuffer + return [...new Uint8Array(buffer)] + .map(x => x.toString(16).padStart(2, '0')) + .join(''); +} + +export const ellideBytes = (bytes: ArrayBuffer) => { + const line = `h'${buf2hex(bytes)}'` return line.replace(/h'(.{8}).+(.{8})'/g, `h'$1...$2'`).trim() } diff --git a/src/cbor/pretty/prettyHeader.ts b/src/cbor/pretty/prettyHeader.ts index e97fd7c..5ba13da 100644 --- a/src/cbor/pretty/prettyHeader.ts +++ b/src/cbor/pretty/prettyHeader.ts @@ -3,6 +3,7 @@ import { indentBlock } from "./indentBlock" import { header, labels_to_algorithms } from "../../iana/assignments/cose" +import { draft_headers } from "../../iana/requested/cose" export const prettyHeader = (map: Map | object) => { if (!(map instanceof Map)) { @@ -12,11 +13,23 @@ export const prettyHeader = (map: Map | object) => { for (const [label, value] of map.entries()) { switch (label) { case header.alg: { - result += indentBlock(`/ algorithm / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + result += indentBlock(`/ algorithm / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + '\n' + break + } + case draft_headers.payload_hash_algorithm: { + result += indentBlock(`/ hash / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + '\n' + break + } + case draft_headers.payload_preimage_content_type: { + result += indentBlock(`/ content / ${label} : "${value}",`, ' ') + '\n' + break + } + case draft_headers.payload_location: { + result += indentBlock(`/ location / ${label} : "${value}",`, ' ') + '\n' break } default: { - result += indentBlock(`${label}: ${value},`, ' ') + result += indentBlock(`${label}: ${value},`, ' ') + '\n' } } } diff --git a/src/cbor/pretty/prettyPayload.ts b/src/cbor/pretty/prettyPayload.ts index 0eea240..0502742 100644 --- a/src/cbor/pretty/prettyPayload.ts +++ b/src/cbor/pretty/prettyPayload.ts @@ -1,6 +1,9 @@ + + +import { ellideBytes } from "./ellideBytes" export const prettyPayload = (payload: ArrayBuffer | null) => { if (payload === null) { return 'null,' } - return 'payload' + return `${ellideBytes(payload)}` } \ No newline at end of file diff --git a/tests/__fixtures__/detached-payload.diag b/tests/__fixtures__/detached-payload.diag index 54cd71c..3554a91 100644 --- a/tests/__fixtures__/detached-payload.diag +++ b/tests/__fixtures__/detached-payload.diag @@ -1,7 +1,6 @@ - / cose-sign1 / 18([ / protected / << - / algorithm / 1 : -35, # ES384 + / algorithm / 1 : -35, # ES384 >> / unprotected / {}, / payload / null, diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor index ff3b6e2..aa3449f 100644 --- a/tests/__fixtures__/hash-envelope.cbor +++ b/tests/__fixtures__/hash-envelope.cbor @@ -1,2 +1 @@ -Ò„X%£&9/9‘xtext/plain; charset=utf-8÷X ö_¬@õ|=Y[YTùu ¨ šëOäF-‡ÓÑ9IˆZŽX@ÁË­_ }C‚qeÄ£ƒ)³cl˜àhU.ÙŽÚ!PËpD«Õ -¨à%DC±Èíà"½ò[?¿FYMËè \ No newline at end of file +Ò„X>¤&9/9‘uapplication/spdx+json9xhttps://s.example/sbom/42÷X a·Xl¯¢ÜÓ+”LlZ2Ëu«ä½]TÄŒhVWò@X@O¨Ûkp¹ÿ¤*8Á4µÌ~}Ó÷‡†î”ºÜ]· ?ë^@‡oFPz‹€¤á'1/É1" köÔãƒ~~Ä \ No newline at end of file diff --git a/tests/__fixtures__/hash-envelope.diag b/tests/__fixtures__/hash-envelope.diag index cfbd0a6..88d0b81 100644 --- a/tests/__fixtures__/hash-envelope.diag +++ b/tests/__fixtures__/hash-envelope.diag @@ -1,9 +1,11 @@ - / cose-sign1 / 18([ / protected / << - / algorithm / 1 : -7, # ES256 -6800: -16, -6802: text/plain; charset=utf-8, + / algorithm / 1 : -7, # ES256 + / hash / -6800 : -16, # SHA-256 + / content / -6802 : "application/spdx+json", + / location / -6801 : "https://s.example/sbom/42", >> / unprotected / {}, - / payload / payload - / signature / h'08c1cb19...594dcbe8' + / payload / h'61b71758...5657f240' + / signature / h'154fa8db...147e02c4' ]) diff --git a/tests/edn.test.ts b/tests/edn.test.ts index 0db34c9..f2ad154 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -11,8 +11,9 @@ it('cose key', async () => { it('detached payload cose sign1', async () => { const input = fs.readFileSync('./tests/__fixtures__/detached-payload.cbor') - const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') const diag = await cose.cbor.diag(input, "application/cose") + fs.writeFileSync('./tests/__fixtures__/detached-payload.diag', diag) + const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') expect(diag).toBe(output.toString()) }) @@ -33,9 +34,10 @@ it('hash envelope', async () => { protectedHeader: cose.ProtectedHeader([ [cose.header.alg, cose.algorithm.es256], [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256], - [cose.draft_headers.payload_preimage_content_type, 'text/plain; charset=utf-8'] + [cose.draft_headers.payload_preimage_content_type, 'application/spdx+json'], + [cose.draft_headers.payload_location, 'https://s.example/sbom/42'] ]), - payload: Buffer.from('🔥 hello') + payload: Buffer.from('🔥 not a real sbom') }) fs.writeFileSync('./tests/__fixtures__/hash-envelope.cbor', signature) const input = fs.readFileSync('./tests/__fixtures__/hash-envelope.cbor') From 35d5ae783d3402aea80e897e6eab4ae3e016de62 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 17:46:20 -0500 Subject: [PATCH 42/67] inclusion diag --- src/cbor/pretty/prettyCoseSign1.ts | 16 ++++--- src/cbor/pretty/prettyHeader.ts | 17 +++++++- src/cbor/pretty/prettyPayload.ts | 2 +- src/cbor/pretty/prettyProofs.ts | 40 ++++++++++++++++++ .../index.ts | 16 +++++++ .../receipt/inclusion/issue.ts | 6 +-- src/iana/requested/cose.ts | 2 +- tests/__fixtures__/detached-payload.diag | 7 +-- tests/__fixtures__/hash-envelope.cbor | Bin 167 -> 167 bytes tests/__fixtures__/hash-envelope.diag | 11 ++--- tests/__fixtures__/inclusion.receipt.cbor | 1 + tests/__fixtures__/inclusion.receipt.diag | 20 +++++++++ tests/edn.test.ts | 35 +++++++++++++++ 13 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 src/cbor/pretty/prettyProofs.ts create mode 100644 tests/__fixtures__/inclusion.receipt.cbor create mode 100644 tests/__fixtures__/inclusion.receipt.diag diff --git a/src/cbor/pretty/prettyCoseSign1.ts b/src/cbor/pretty/prettyCoseSign1.ts index b12fff9..710ef00 100644 --- a/src/cbor/pretty/prettyCoseSign1.ts +++ b/src/cbor/pretty/prettyCoseSign1.ts @@ -7,15 +7,17 @@ import { prettyPayload } from './prettyPayload' import { ellideBytes } from './ellideBytes' export const prettyCoseSign1 = (data: Buffer) => { - const decoded = cbor.decode(data) - const [encodedProtected, decodedUnprotected, encodedPayload, signature] = decoded.value - const decodedProtected = cbor.decode(encodedProtected) - return ` + const decoded = cbor.decode(data) + const [encodedProtected, decodedUnprotected, encodedPayload, signature] = decoded.value + const decodedProtected = cbor.decode(encodedProtected) + return ` / cose-sign1 / ${decoded.tag}([ - / protected / << + / protected / <<{ ${prettyHeader(decodedProtected)} - >> - / unprotected / ${prettyHeader(decodedUnprotected)} + }>>, + / unprotected / { +${prettyHeader(decodedUnprotected)} + }, / payload / ${prettyPayload(encodedPayload)} / signature / ${ellideBytes(signature)} ]) diff --git a/src/cbor/pretty/prettyHeader.ts b/src/cbor/pretty/prettyHeader.ts index 5ba13da..fc768ed 100644 --- a/src/cbor/pretty/prettyHeader.ts +++ b/src/cbor/pretty/prettyHeader.ts @@ -4,10 +4,13 @@ import { indentBlock } from "./indentBlock" import { header, labels_to_algorithms } from "../../iana/assignments/cose" import { draft_headers } from "../../iana/requested/cose" +import { transparency } from "../../drafts/draft-ietf-cose-merkle-tree-proofs" + +import { prettyProofs } from "./prettyProofs" export const prettyHeader = (map: Map | object) => { if (!(map instanceof Map)) { - return '{},' + return '' } let result = `` for (const [label, value] of map.entries()) { @@ -28,6 +31,18 @@ export const prettyHeader = (map: Map | object) => { result += indentBlock(`/ location / ${label} : "${value}",`, ' ') + '\n' break } + case draft_headers.verifiable_data_structure: { + result += indentBlock(`/ notarized / ${label} : ${value}, # ${transparency.get(value)}`, ' ') + '\n' + break + } + case draft_headers.verifiable_data_proofs: { + let proofs = '' + proofs += ` / proofs / ${label} : {\n` + proofs += indentBlock(prettyProofs(value), ' ') + proofs += `\n },\n` + result += proofs + break + } default: { result += indentBlock(`${label}: ${value},`, ' ') + '\n' } diff --git a/src/cbor/pretty/prettyPayload.ts b/src/cbor/pretty/prettyPayload.ts index 0502742..9ad61b9 100644 --- a/src/cbor/pretty/prettyPayload.ts +++ b/src/cbor/pretty/prettyPayload.ts @@ -5,5 +5,5 @@ export const prettyPayload = (payload: ArrayBuffer | null) => { if (payload === null) { return 'null,' } - return `${ellideBytes(payload)}` + return `${ellideBytes(payload)},` } \ No newline at end of file diff --git a/src/cbor/pretty/prettyProofs.ts b/src/cbor/pretty/prettyProofs.ts new file mode 100644 index 0000000..f2ae230 --- /dev/null +++ b/src/cbor/pretty/prettyProofs.ts @@ -0,0 +1,40 @@ + + +import * as cbor from 'cbor-web' + +import { ellideBytes } from './ellideBytes' + +import { rfc9162_sha256_proof_types, transparency } from '../../drafts/draft-ietf-cose-merkle-tree-proofs' +import { indentBlock } from './indentBlock' +export const prettyProof = (bytes: ArrayBuffer) => { + const [size, index, path] = cbor.decode(bytes) + return indentBlock(`<<[ + / size / ${size}, / leaf / ${index}, + / inclusion path / +${path.map((p: ArrayBuffer) => { + return ' ' + ellideBytes(p) + }).join(',\n')} +]>>`, ' ') +} + +export const prettyProofs = (proofs: Map) => { + let result = '' + for (const [label, value] of proofs.entries()) { + switch (label) { + case rfc9162_sha256_proof_types.inclusion: { + result += `/ ${transparency.get(label)} / ${label} : [\n` + for (const proof of value) { + result += prettyProof(proof) + } + result += `\n],\n` + break + } + default: { + throw new Error('Unknown proof type') + } + } + } + + return result.trim() + +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts index 16be920..9e4a606 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -1,3 +1,19 @@ import * as receipt from './receipt' + +export const verifiable_data_structures = { + rfc9162_sha256: 1 +} + +export const transparency = new Map([ + [1, 'RFC9162 SHA-256'], + [-1, 'inclusion'], + [-2, 'consistency'] +]) + +export const rfc9162_sha256_proof_types = { + 'inclusion': -1, + 'consistency': -2 +} + export { receipt } \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts index 6605ead..577da77 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts @@ -16,7 +16,7 @@ export type RequestIssueInclusionReceipt = { signer: CoseSign1Signer } -export const issue = async (req: RequestIssueInclusionReceipt) => { +export const issue = async (req: RequestIssueInclusionReceipt): Promise => { const { protectedHeader, entry, entries, signer } = req; const vds = protectedHeader.get(draft_headers.verifiable_data_structure) if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { @@ -37,9 +37,9 @@ export const issue = async (req: RequestIssueInclusionReceipt) => { ]) const unprotectedHeader = new Map(); unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) - return signer.sign({ + return new Uint8Array(await signer.sign({ protectedHeader, unprotectedHeader, payload: root - }) + })) } \ No newline at end of file diff --git a/src/iana/requested/cose.ts b/src/iana/requested/cose.ts index c6e8ba3..b819561 100644 --- a/src/iana/requested/cose.ts +++ b/src/iana/requested/cose.ts @@ -27,7 +27,7 @@ export enum draft_headers { // https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/ receipts = 394, verifiable_data_structure = 395, - verifiable_data_proofs = 395 + verifiable_data_proofs = 396 } export const VerifiableDataStructures = { diff --git a/tests/__fixtures__/detached-payload.diag b/tests/__fixtures__/detached-payload.diag index 3554a91..3eaf161 100644 --- a/tests/__fixtures__/detached-payload.diag +++ b/tests/__fixtures__/detached-payload.diag @@ -1,8 +1,9 @@ / cose-sign1 / 18([ - / protected / << + / protected / <<{ / algorithm / 1 : -35, # ES384 - >> - / unprotected / {}, + }>>, + / unprotected / { + }, / payload / null, / signature / h'b4e0d531...b0a48a3a' ]) diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor index aa3449f28c45a88efff9aecb04f4a202af8145d4..f09d0965be52649bf9a0f473207795018b4eda2f 100644 GIT binary patch delta 71 zcmV-N0J#6B0jB|wXFvd3`nU|8yy)%Z-8AUD`~C7XQ_05B)%Vcj-9c1g&bp!pH-XGH do=iC*Xx&5C%Ks~;I25&}C ddW(P_qz~aIF)s+oF(ROA_S78XgMJi#0>sIlArJrn diff --git a/tests/__fixtures__/hash-envelope.diag b/tests/__fixtures__/hash-envelope.diag index 88d0b81..375b197 100644 --- a/tests/__fixtures__/hash-envelope.diag +++ b/tests/__fixtures__/hash-envelope.diag @@ -1,11 +1,12 @@ / cose-sign1 / 18([ - / protected / << + / protected / <<{ / algorithm / 1 : -7, # ES256 / hash / -6800 : -16, # SHA-256 / content / -6802 : "application/spdx+json", / location / -6801 : "https://s.example/sbom/42", - >> - / unprotected / {}, - / payload / h'61b71758...5657f240' - / signature / h'154fa8db...147e02c4' + }>>, + / unprotected / { + }, + / payload / h'61b71758...5657f240', + / signature / h'005bfab8...b4f287c6' ]) diff --git a/tests/__fixtures__/inclusion.receipt.cbor b/tests/__fixtures__/inclusion.receipt.cbor new file mode 100644 index 0000000..a58bba6 --- /dev/null +++ b/tests/__fixtures__/inclusion.receipt.cbor @@ -0,0 +1 @@ +Ò„G¢&‹¡Œ¡ XHƒ‚X ŠºþYtå¯éæë<FƒOÞ÷*þÜ8LToOÛŒMX 1v¢—Y‹ËL’H7 ùJÕÎrT QïLö]¦X Ø+ÙÓñãÝ‚Pm°ÒígYk/éZdÕÜž/Ú¶X@ù蹦½ÛÞڥ˅K]s$cÍ¡cÿ|tŒÝ4®ásbrâk´&e$²ï ¸„ ¤ÿ°%þ¿qÌ  \ No newline at end of file diff --git a/tests/__fixtures__/inclusion.receipt.diag b/tests/__fixtures__/inclusion.receipt.diag new file mode 100644 index 0000000..7b85c1b --- /dev/null +++ b/tests/__fixtures__/inclusion.receipt.diag @@ -0,0 +1,20 @@ +/ cose-sign1 / 18([ + / protected / <<{ + / algorithm / 1 : -7, # ES256 + / notarized / 395 : 1, # RFC9162 SHA-256 + }>>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 3, / leaf / 1, + / inclusion path / + h'8aba08fe...4fdb8c4d', + h'0b317603...f65d1ea6' + ]>> + ], + }, + }, + / payload / h'd82bd9d3...2f90dab6', + / signature / h'f91ae8b9...0671cca0' +]) diff --git a/tests/edn.test.ts b/tests/edn.test.ts index f2ad154..02a29fd 100644 --- a/tests/edn.test.ts +++ b/tests/edn.test.ts @@ -2,6 +2,7 @@ import fs from 'fs' import * as cose from '../src' const key = fs.readFileSync('./tests/__fixtures__/cose-key.cbor') +const encoder = new TextEncoder(); it('cose key', async () => { const output = fs.readFileSync('./tests/__fixtures__/cose-key.diag') @@ -47,3 +48,37 @@ it('hash envelope', async () => { expect(diag).toBe(output.toString()) }) +it('cose receipt', async () => { + const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ + key, + type: 'application/cose-key' + }) + const entries = await Promise.all([`💣 test`, `✨ test`, `🔥 test`] + .map((entry) => { + return encoder.encode(entry) + }) + .map((entry) => { + return cose.receipt.leaf(entry) + })) + const signer = await cose.sign1 + .signer({ + remote: await cose.crypto.key.signer({ + algorithm: 'ES256', + key: k, + }) + }) + const inclusion = await cose.receipt.inclusion.issue({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] + ]), + entry: 1, + entries, + signer + }) + fs.writeFileSync('./tests/__fixtures__/inclusion.receipt.cbor', inclusion) + const input = fs.readFileSync('./tests/__fixtures__/inclusion.receipt.cbor') + const diag = await cose.cbor.diag(input, "application/cose") + fs.writeFileSync('./tests/__fixtures__/inclusion.receipt.diag', diag) +}) + From 02f675e3a4977886ea8371378408322940eb3381 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sun, 22 Sep 2024 18:00:52 -0500 Subject: [PATCH 43/67] cleaning --- src/cbor/pretty/prettyProofs.ts | 25 ++++++++++++++++-- .../receipt/consistency/issue.ts | 4 +-- tests/__fixtures__/consistency.receipt.cbor | 1 + tests/__fixtures__/consistency.receipt.diag | 21 +++++++++++++++ tests/__fixtures__/hash-envelope.cbor | Bin 167 -> 167 bytes tests/__fixtures__/hash-envelope.diag | 2 +- tests/__fixtures__/inclusion.receipt.cbor | 2 +- tests/__fixtures__/inclusion.receipt.diag | 4 +-- tests/edn.test.ts | 20 ++++++++++++-- 9 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 tests/__fixtures__/consistency.receipt.cbor create mode 100644 tests/__fixtures__/consistency.receipt.diag diff --git a/src/cbor/pretty/prettyProofs.ts b/src/cbor/pretty/prettyProofs.ts index f2ae230..36329ba 100644 --- a/src/cbor/pretty/prettyProofs.ts +++ b/src/cbor/pretty/prettyProofs.ts @@ -6,7 +6,8 @@ import { ellideBytes } from './ellideBytes' import { rfc9162_sha256_proof_types, transparency } from '../../drafts/draft-ietf-cose-merkle-tree-proofs' import { indentBlock } from './indentBlock' -export const prettyProof = (bytes: ArrayBuffer) => { + +export const prettyInclusionProof = (bytes: ArrayBuffer) => { const [size, index, path] = cbor.decode(bytes) return indentBlock(`<<[ / size / ${size}, / leaf / ${index}, @@ -17,6 +18,18 @@ ${path.map((p: ArrayBuffer) => { ]>>`, ' ') } +export const prettyConsistencyProof = (bytes: ArrayBuffer) => { + const [size1, size2, path] = cbor.decode(bytes) + + return indentBlock(`<<[ + / old / ${size1}, / new / ${size2}, + / consistency path / +${path.map((p: ArrayBuffer) => { + return ' ' + ellideBytes(p) + }).join(',\n')} +]>>`, ' ') +} + export const prettyProofs = (proofs: Map) => { let result = '' for (const [label, value] of proofs.entries()) { @@ -24,7 +37,15 @@ export const prettyProofs = (proofs: Map) => { case rfc9162_sha256_proof_types.inclusion: { result += `/ ${transparency.get(label)} / ${label} : [\n` for (const proof of value) { - result += prettyProof(proof) + result += prettyInclusionProof(proof) + } + result += `\n],\n` + break + } + case rfc9162_sha256_proof_types.consistency: { + result += `/ ${transparency.get(label)} / ${label} : [\n` + for (const proof of value) { + result += prettyConsistencyProof(proof) } result += `\n],\n` break diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts index 9b71b9b..d23d079 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts @@ -68,11 +68,11 @@ export const issue = async (req: RequestIssueConsistencyReceipt) => { const unprotectedHeader = new Map(); unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) - const consistency = await signer.sign({ + const consistency = new Uint8Array(await signer.sign({ protectedHeader, unprotectedHeader, payload: root - }) + })) return { root, receipt: consistency, } } \ No newline at end of file diff --git a/tests/__fixtures__/consistency.receipt.cbor b/tests/__fixtures__/consistency.receipt.cbor new file mode 100644 index 0000000..6c6c5d7 --- /dev/null +++ b/tests/__fixtures__/consistency.receipt.cbor @@ -0,0 +1 @@ +Ò„G¢&‹¡Œ¡!XjƒƒX 1v¢—Y‹ËL’H7 ùJÕÎrT QïLö]¦X [‡¡2€Ó”*<=^AæóôÉ´›{yÀl&ÏÒq²%íX "féÝêFÓ˜.ÃA'zbŸÒ)9–Õ¶«â®C!ùN’çöX@cæJ"¢¦Téjñ¤½ a#ŒßëÛ6Mœ” ‹#c~‘¿ûÒLËÖ¿ÍÿâÂî Û°„ðéo.¿e0€$Þóp¶ \ No newline at end of file diff --git a/tests/__fixtures__/consistency.receipt.diag b/tests/__fixtures__/consistency.receipt.diag new file mode 100644 index 0000000..b389504 --- /dev/null +++ b/tests/__fixtures__/consistency.receipt.diag @@ -0,0 +1,21 @@ +/ cose-sign1 / 18([ + / protected / <<{ + / algorithm / 1 : -7, # ES256 + / notarized / 395 : 1, # RFC9162 SHA-256 + }>>, + / unprotected / { + / proofs / 396 : { + / consistency / -2 : [ + <<[ + / old / 3, / new / 4, + / consistency path / + h'0b317603...f65d1ea6', + h'5b87a132...71b225ed', + h'2266e9dd...f94e92e7' + ]>> + ], + }, + }, + / payload / null, + / signature / h'63e6034a...f37004b6' +]) diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor index f09d0965be52649bf9a0f473207795018b4eda2f..9483eb900eee2583abd807e7762629a12daa17bd 100644 GIT binary patch delta 71 zcmV-N0J#6B0jB|wXFvfrx}KLo8H&so(guq1gaIoF^u0^#bI%M7Z;Ncn#uwOm)nT&v ddA<>Q`)1n { expect(diag).toBe(output.toString()) }) -it('cose receipt', async () => { +it('cose receipts', async () => { const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ key, type: 'application/cose-key' @@ -60,7 +60,7 @@ it('cose receipt', async () => { .map((entry) => { return cose.receipt.leaf(entry) })) - const signer = await cose.sign1 + const signer = await cose.detached .signer({ remote: await cose.crypto.key.signer({ algorithm: 'ES256', @@ -80,5 +80,21 @@ it('cose receipt', async () => { const input = fs.readFileSync('./tests/__fixtures__/inclusion.receipt.cbor') const diag = await cose.cbor.diag(input, "application/cose") fs.writeFileSync('./tests/__fixtures__/inclusion.receipt.diag', diag) + + entries.push(await cose.receipt.leaf(encoder.encode('✨ new entry ✨'))) + // ask the transparency service for the latest root, and a consistency proof + // based on a previous receipt + const { root, receipt } = await cose.receipt.consistency.issue({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] + ]), + receipt: inclusion, + entries, + signer + }) + fs.writeFileSync('./tests/__fixtures__/consistency.receipt.cbor', receipt) + const diag2 = await cose.cbor.diag(receipt, "application/cose") + fs.writeFileSync('./tests/__fixtures__/consistency.receipt.diag', diag2) }) From 31f0b2ea316100df1704c60671a86405b82dbbdf Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 1 Oct 2024 16:06:31 -0500 Subject: [PATCH 44/67] add tile log --- .gitignore | 1 + package-lock.json | 463 ++++++++++- package.json | 4 +- src/cose/detached/index.ts | 4 +- src/desugar.ts | 5 + .../decode_consistency_proof.ts | 21 + .../decode_inclusion_proof.ts | 21 + .../encode_consistency_proof.ts | 14 + .../encode_inclusion_proof.ts | 14 + .../index.ts | 10 +- .../log/Tile.ts | 762 ++++++++++++++++++ .../log/Tree.ts | 379 +++++++++ .../log/TreeHash.ts | 35 + .../log/index.ts | 5 + src/index.ts | 2 +- .../test_log.ts | 60 ++ .../tiled_log.test.ts | 98 +++ 17 files changed, 1889 insertions(+), 9 deletions(-) create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_consistency_proof.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_inclusion_proof.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_consistency_proof.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_inclusion_proof.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tree.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/log/TreeHash.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/log/index.ts create mode 100644 tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts create mode 100644 tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts diff --git a/.gitignore b/.gitignore index dc5d4c8..df9a443 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules dist .DS_Store .env +*.db \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bae408c..51314ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,10 +15,12 @@ "jose": "^4.14.4" }, "devDependencies": { + "@types/better-sqlite3": "^7.6.11", "@types/jest": "^29.2.4", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "axios": "^1.6.7", + "better-sqlite3": "^11.3.0", "csv-parser": "^3.0.0", "eslint": "^8.30.0", "jest": "^29.3.1", @@ -1359,6 +1361,16 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.11", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.11.tgz", + "integrity": "sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "dev": true, @@ -1765,11 +1777,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.6.7", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -1865,6 +1879,61 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.3.0.tgz", + "integrity": "sha512-iHt9j8NPYF3oKCNOO5ZI4JwThjt3Z6J6XrcwG85VNMVzv1ByqrHWv5VILEbCMFWDsoHhXvQ7oC8vgRXFAKgl9w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "dev": true, @@ -1931,6 +2000,31 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "dev": true, @@ -2001,6 +2095,13 @@ "node": ">=10" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/ci-info": { "version": "3.8.0", "dev": true, @@ -2134,11 +2235,37 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "0.7.0", "dev": true, "license": "MIT" }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -2160,6 +2287,16 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "dev": true, @@ -2229,6 +2366,16 @@ "dev": true, "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "dev": true, @@ -2469,6 +2616,16 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.5.0", "dev": true, @@ -2552,6 +2709,13 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "dev": true, @@ -2627,6 +2791,13 @@ "node": ">= 6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "dev": true, @@ -2684,6 +2855,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "dev": true, @@ -2789,6 +2967,27 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.2.4", "dev": true, @@ -2852,6 +3051,13 @@ "dev": true, "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/ipaddr.js": { "version": "2.1.0", "license": "MIT", @@ -3724,11 +3930,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3762,6 +3970,19 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "dev": true, @@ -3781,6 +4002,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/moment": { "version": "2.30.1", "dev": true, @@ -3794,6 +4022,13 @@ "dev": true, "license": "MIT" }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -3804,6 +4039,19 @@ "dev": true, "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.68.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz", + "integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-int64": { "version": "0.4.0", "dev": true, @@ -4055,6 +4303,33 @@ "node": ">=8" } }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -4104,6 +4379,17 @@ "dev": true, "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "dev": true, @@ -4164,11 +4450,52 @@ ], "license": "MIT" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/reflect-metadata": { "version": "0.2.1", "license": "Apache-2.0" @@ -4277,6 +4604,27 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/semver": { "version": "7.6.3", "dev": true, @@ -4312,6 +4660,53 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "dev": true, @@ -4366,6 +4761,16 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-length": { "version": "4.0.2", "dev": true, @@ -4451,6 +4856,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "dev": true, @@ -4607,6 +5042,19 @@ "node": ">= 6.0.0" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -4686,6 +5134,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 282c6d6..83d21a2 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,12 @@ }, "homepage": "https://github.com/transmute-industries/cose#readme", "devDependencies": { + "@types/better-sqlite3": "^7.6.11", "@types/jest": "^29.2.4", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "axios": "^1.6.7", + "better-sqlite3": "^11.3.0", "csv-parser": "^3.0.0", "eslint": "^8.30.0", "jest": "^29.3.1", @@ -50,4 +52,4 @@ "cbor-web": "^9.0.2", "jose": "^4.14.4" } -} \ No newline at end of file +} diff --git a/src/cose/detached/index.ts b/src/cose/detached/index.ts index f3e6126..e1eac59 100644 --- a/src/cose/detached/index.ts +++ b/src/cose/detached/index.ts @@ -16,7 +16,7 @@ export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { const coseSign1 = await coseSign1Signer.sign(req) const decoded = decodeFirstSync(coseSign1) decoded.value[2] = null - return encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true }) + return new Uint8Array(await encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true })) } } } @@ -29,7 +29,7 @@ export const verifier = ({ resolver }: sign1.RequestCoseSign1Verifier) => { const payloadBuffer = toArrayBuffer(req.payload); decoded.value[2] = payloadBuffer const attached = await encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true }) - return verifier.verify({ coseSign1: attached }) + return new Uint8Array(await verifier.verify({ coseSign1: attached })) } } } \ No newline at end of file diff --git a/src/desugar.ts b/src/desugar.ts index 3abf6b5..3f3fb7d 100644 --- a/src/desugar.ts +++ b/src/desugar.ts @@ -13,3 +13,8 @@ export const UnprotectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } + + +export const VerifiableDataStructureProofs = (entries: [number, any][]) => { + return new Map(entries) +} diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_consistency_proof.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_consistency_proof.ts new file mode 100644 index 0000000..2053c2c --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_consistency_proof.ts @@ -0,0 +1,21 @@ + +import { rfc9162_sha256_proof_types } from '.' +import * as cbor from '../../cbor' +import { draft_headers } from '../../iana/requested/cose' + +export const decode_consistency_proof = (receipt: Uint8Array) => { + const decoded = cbor.decode(receipt) + if (decoded.tag !== 18) { + throw new Error('Expected cose-sign1 (tag 18)') + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_protectedHeader, unprotectedHeader, _payload, _signature] = decoded.value + const proofs = unprotectedHeader.get(draft_headers.verifiable_data_proofs) + const consistency_proofs = proofs.get(rfc9162_sha256_proof_types.consistency) + return consistency_proofs.map((p: Uint8Array) => { + const [old_tree_size, new_tree_size, consistency_path] = cbor.decode(p) + return [old_tree_size, new_tree_size, consistency_path.map((p2: ArrayBuffer) => { + return new Uint8Array(p2) + })] + }) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_inclusion_proof.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_inclusion_proof.ts new file mode 100644 index 0000000..e34c669 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/decode_inclusion_proof.ts @@ -0,0 +1,21 @@ + +import { rfc9162_sha256_proof_types } from '.' +import * as cbor from '../../cbor' +import { draft_headers } from '../../iana/requested/cose' + +export const decode_inclusion_proof = (receipt: Uint8Array) => { + const decoded = cbor.decode(receipt) + if (decoded.tag !== 18) { + throw new Error('Expected cose-sign1 (tag 18)') + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_protectedHeader, unprotectedHeader, _payload, _signature] = decoded.value + const proofs = unprotectedHeader.get(draft_headers.verifiable_data_proofs) + const inclusion_proofs = proofs.get(rfc9162_sha256_proof_types.inclusion) + return inclusion_proofs.map((p: Uint8Array) => { + const [size, index, path] = cbor.decode(p) + return [size, index, path.map((p2: ArrayBuffer) => { + return new Uint8Array(p2) + })] + }) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_consistency_proof.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_consistency_proof.ts new file mode 100644 index 0000000..2b5b1f9 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_consistency_proof.ts @@ -0,0 +1,14 @@ + +import * as cbor from '../../cbor' +type old_tree_size = number +type new_tree_size = number +type consistency_path = Uint8Array[] +type consistency_proof = [old_tree_size, new_tree_size, consistency_path] +type encoded_consistency_proof = ArrayBuffer + +export const encode_consistency_proof = (proof: consistency_proof): encoded_consistency_proof => { + const [old_tree_size, new_tree_size, consistency_path] = proof + return cbor.toArrayBuffer(cbor.encode([old_tree_size, new_tree_size, consistency_path.map((p) => { + return cbor.toArrayBuffer(p) + })])) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_inclusion_proof.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_inclusion_proof.ts new file mode 100644 index 0000000..33efa54 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/encode_inclusion_proof.ts @@ -0,0 +1,14 @@ + +import * as cbor from '../../cbor' +type tree_size = number +type record_index = number +type inclusion_path = Uint8Array[] +type inclusion_proof = [tree_size, record_index, inclusion_path] +type encoded_inclusion_proof = ArrayBuffer + +export const encode_inclusion_proof = (proof: inclusion_proof): encoded_inclusion_proof => { + const [size, index, path] = proof + return cbor.toArrayBuffer(cbor.encode([size, index, path.map((p) => { + return cbor.toArrayBuffer(p) + })])) +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts index 9e4a606..1c2cc9a 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -1,5 +1,11 @@ import * as receipt from './receipt' +export * from './encode_inclusion_proof' +export * from './decode_inclusion_proof' + +export * from './encode_consistency_proof' +export * from './decode_consistency_proof' + export const verifiable_data_structures = { rfc9162_sha256: 1 @@ -16,4 +22,6 @@ export const rfc9162_sha256_proof_types = { 'consistency': -2 } -export { receipt } \ No newline at end of file +export { receipt } + +export * from './log' \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts new file mode 100644 index 0000000..8723ecd --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts @@ -0,0 +1,762 @@ + + + +import { trailing_zeros_64 } from "./Tree" + +import { TreeHash, concat, verify_match } from "./TreeHash" + +export interface HashReader { + read_hashes: (indexes: number[]) => Uint8Array[] +} + +export interface TileStorage { + height: () => number + read_tiles: (tiles: Tile[]) => Uint8Array[] + save_tiles: (tiles: Tile[], data: Uint8Array[]) => void +} + +export type Tile = [number, number, number, number] + +export type RecordProof = Uint8Array[] +export type TreeProof = Uint8Array[] + +export type InclusionProof = [ + number, // tree size + number, // record index + RecordProof // tree path +] + +export type ConsistencyProof = [ + number, // old tree size + number, // new tree size + TreeProof // tree path +] + +export type TileLogParameters = { + tile_height: number + hash_size: number + hash_function: (bytes: Uint8Array) => Uint8Array + read_tile: (tile: string) => Uint8Array + update_tiles: (tile_path: string, start: number, end: number, stored_hash: Uint8Array) => Uint8Array | null +} + + +export function create_tile(height: number, level: number, hash_number: number, width: number) { + return [height, level, hash_number, width] as Tile +} + +export function stored_hash_index(level: number, hash_number: number) { + for (let l = level; l > 0; l--) { + hash_number = 2 * hash_number + 1 + } + let i = 0; + while (hash_number > 0) { + i += hash_number + hash_number >>= 1 + } + return i + level +} + +export function split_stored_hash_index(storage_id: number) { + let hash_number = Math.ceil(storage_id / 2) + let index_hash_number = stored_hash_index(0, hash_number) + index_hash_number = Math.ceil(index_hash_number) + if (index_hash_number > storage_id) { + throw new Error('bad math') + } + let x + // eslint-disable-next-line no-constant-condition + while (true) { + x = index_hash_number + 1 + trailing_zeros_64(hash_number + 1) + if (x > storage_id) { + break + } + hash_number++ + index_hash_number = x + } + const level = storage_id - index_hash_number + hash_number = hash_number >> level + return [level, hash_number] +} + +export function tile_for_storage_id(hash_size: number, height: number, storage_id: number): [Tile, number, number] { + if (height < 0) { + throw new Error(`tile_for_storage_id: invalid height ${height}`) + } + const tile_height = height + let [level, n] = split_stored_hash_index(storage_id) + const tile_level = Math.floor(level / height) + level -= tile_level * height + const hash_number = n << level >> height + n -= hash_number << tile_height >> level + const tile_width = (n + 1) >> 0 << level + const start = (n << level) * hash_size + const end = ((n + 1) << level) * hash_size + const tile = create_tile(tile_height, tile_level, hash_number, tile_width) + return [tile, start, end] +} + +export function tile_to_path(tile: Tile) { + const [H, L, N, W] = tile + return `tile/${H}/${L}/${N}.${W}` +} + + +export function hash_from_tile(tree_hasher: TreeHash, tile: Tile, tile_data: Uint8Array, storage_id: number) { + const [tile_height, tile_level, hash_number, tile_width] = tile + if (tile_height < 1 || tile_height > 30 || tile_level < 0 || tile_level >= 64 || tile_width < 1 || tile_width > (1 << tile_height)) { + throw new Error(`invalid ${tile_to_path(tile)}`) + } + if (tile_data.length < tile_width * tree_hasher.hash_size) { + throw new Error(`data length ${tile_data.length} is too short for ${tile_to_path(tile)}`) + } + const [t1, start, end] = tile_for_storage_id(tree_hasher.hash_size, tile_height, storage_id) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_t1H, t1L, t1N, t1W] = t1 + if (tile_level !== t1L || hash_number !== t1N || tile_width < t1W) { + throw new Error(`index ${storage_id} is in ${tile_to_path(t1)} not ${tile_to_path(tile)}`) + } + const tile_slice = tile_data.slice(start, end) + return tile_hash(tree_hasher, tile_slice) +} + +export function tile_hash(tree_hasher: TreeHash, tile_data: Uint8Array): Uint8Array { + if (tile_data.length == 0) { + throw new Error("bad math in tile_hash") + } + if (tile_data.length === tree_hasher.hash_size) { + return tile_data + } + const n = tile_data.length / 2 + const left = tile_data.slice(0, n) + const right = tile_data.slice(n, tile_data.length) + + return tree_hasher.hash_children( + tile_hash(tree_hasher, left), + tile_hash(tree_hasher, right) + ) +} + +export function new_tiles(tile_height: number, old_tree_size: number, new_tree_size: number) { + if (tile_height < 0) { + throw new Error(`new_tiles: invalid height ${tile_height}`) + } + const tiles = [] as Tile[] + for (let level = 0; (new_tree_size >> (tile_height * level)) > 0; level++) { + const oldN = old_tree_size >> (tile_height * level) + const newN = new_tree_size >> (tile_height * level) + if (oldN == newN) { + continue + } + for (let n = oldN >> tile_height; n < (newN >> tile_height); n++) { + tiles.push(create_tile(tile_height, level, n, 1 << tile_height)) + } + const n = newN >> tile_height + const w = newN - (n << tile_height) + if (w > 0) { + tiles.push(create_tile(tile_height, level, n, w)) + } + } + return tiles +} + +export function read_tile_data(tile: Tile, hash_reader: HashReader) { + let size = tile[3] + if (size === 0) { + size = 1 << tile[0] + } + const start = tile[2] << tile[0] + const indexes = [] + for (let i = 0; i < size; i++) { + indexes[i] = stored_hash_index(tile[0] * tile[1], start + i) + } + const hashes = hash_reader.read_hashes(indexes) + if (hashes.length != indexes.length) { + throw new Error(`tlog: read_hashes(${indexes.length} indexes) = ${hashes.length} hashes`) + } + return hashes.reduce(concat) +} + +export function stored_hash_count(record_index: number) { + if (record_index === 0) { + return 0 + } + let num_hash = stored_hash_index(0, record_index - 1) + 1 + for (let i = record_index - 1; (i & 1) != 0; i >>= 1) { + num_hash++ + } + return num_hash +} + +export function stored_hashes_for_record_hash(tree_hasher: TreeHash, record_index: number, record_hash: Uint8Array, hash_reader: HashReader) { + const hashes = [record_hash] as Uint8Array[] + const m = trailing_zeros_64(record_index + 1) + const indexes = new Array(m).fill(0) + for (let i = 0; i < m; i++) { + const next = (record_index >> i) - 1 + indexes[m - 1 - i] = stored_hash_index(i, next) + } + const old = hash_reader.read_hashes(indexes) + for (let i = 0; i < m; i++) { + record_hash = tree_hasher.hash_children(old[m - 1 - i], record_hash) + hashes.push(record_hash) + } + return hashes +} + + + +export function stored_hashes(tree_hasher: TreeHash, record_index: number, data: Uint8Array, hash_reader: HashReader) { + return stored_hashes_for_record_hash(tree_hasher, record_index, tree_hasher.hash_leaf(data), hash_reader) +} + +export function max_power_2(n: number) { + let l = 0 + while ((1 << (l + 1)) < n) { + l++ + } + return [1 << l, l] +} + +export function subtree_index(lo: number, hi: number, needed_storage_ids: number[]) { + while (lo < hi) { + const [k, level] = max_power_2(hi - lo + 1) + if ((lo & (k - 1)) != 0) { + throw new Error(`tlog: bad math in subtree_index`) + } + needed_storage_ids.push(stored_hash_index(level, lo >> level)) + lo += k + } + return needed_storage_ids +} + +export function subtree_hash(tree_hasher: TreeHash, lo: number, hi: number, hashes: Uint8Array[]): [Uint8Array, Uint8Array[]] { + let num_tree = 0 + while (lo < hi) { + const [k, _] = max_power_2(hi - lo + 1) + if ((lo & (k - 1)) != 0 || lo >= hi) { + throw new Error(`tlog: bad math in subtree_hash`) + } + num_tree++ + lo += k + } + if (hashes.length < num_tree) { + throw new Error(`tlog: bad index math in subtree_hash`) + } + let h = hashes[num_tree - 1] + for (let i = num_tree - 2; i >= 0; i--) { + h = tree_hasher.hash_children(hashes[i], h) + } + return [h, hashes.slice(num_tree, hashes.length)] +} + + +export function tree_hash(tree_hasher: TreeHash, tree_size: number, hash_reader: HashReader) { + if (tree_size === 0) { + return tree_hasher.empty_root() + } + const indexes = subtree_index(0, tree_size, []) + let hashes = hash_reader.read_hashes(indexes) + const sth = subtree_hash(tree_hasher, 0, tree_size, hashes) + const hash = sth[0] + hashes = sth[1] + if (hashes.length !== 0) { + throw new Error(`tlog: bad index math in tree_hash`) + } + return hash +} + + + +export function leaf_proof_index(lo: number, hi: number, record_index: number, needed_storage_ids: number[]) { + if (!(lo <= record_index && record_index < hi)) { + throw new Error(`tlog: bad math in leaf_proof_index`) + } + if ((lo + 1) == hi) { + return needed_storage_ids + } + const [k, _] = max_power_2(hi - lo) + if (record_index < lo + k) { + needed_storage_ids = leaf_proof_index(lo, lo + k, record_index, needed_storage_ids) + needed_storage_ids = subtree_index(lo + k, hi, needed_storage_ids) + } else { + + needed_storage_ids = subtree_index(lo, lo + k, needed_storage_ids) + needed_storage_ids = leaf_proof_index(lo + k, hi, record_index, needed_storage_ids) + } + return needed_storage_ids +} + +export function leaf_proof(tree_hasher: TreeHash, lo: number, hi: number, record_index: number, hashes: Uint8Array[]): [RecordProof, Uint8Array[]] { + if (!(lo <= record_index && record_index < hi)) { + throw new Error(`tlog: bad math in leaf_proof`) + } + if (lo + 1 == hi) { + return [[] as RecordProof, hashes] + } + let p: Uint8Array[] + let next_hash: Uint8Array + const [k, _] = max_power_2(hi - lo) + if (record_index < lo + k) { + [p, hashes] = leaf_proof(tree_hasher, lo, lo + k, record_index, hashes) + const sth = subtree_hash(tree_hasher, lo + k, hi, hashes) + next_hash = sth[0] + hashes = sth[1] + } else { + [next_hash, hashes] = subtree_hash(tree_hasher, lo, lo + k, hashes) + const lp = leaf_proof(tree_hasher, lo + k, hi, record_index, hashes) + p = lp[0] + hashes = lp[1] + } + p.push(next_hash) + return [p, hashes] +} + + + +export function prove_record(tree_hasher: TreeHash, tile: number, record_index: number, hash_reader: HashReader) { + if (tile < 0 || record_index < 0 || record_index >= tile) { + throw new Error('tlog: invalid inputs in prove_record') + } + const indexes = leaf_proof_index(0, tile, record_index, []) + if (indexes.length === 0) { + return [] as RecordProof + } + let hashes = hash_reader.read_hashes(indexes) + if (hashes.length != indexes.length) { + throw new Error(`tlog: read_hashes(${indexes.length} indexes) = ${hashes.length} hashes`) + } + let p; + // eslint-disable-next-line prefer-const + [p, hashes] = leaf_proof(tree_hasher, 0, tile, record_index, hashes) + if (hashes.length != 0) { + throw new Error(`tlog: bad index math in prove_record`) + } + return p +} + +export function run_record_proof(tree_hasher: TreeHash, record_proof: RecordProof, lo: number, hi: number, record_index: number, record_hash: Uint8Array): Uint8Array { + if (!(lo <= record_index && record_index < hi)) { + throw new Error(`tlog: bad math in run_record_proof`) + } + if (lo + 1 === hi) { + if (record_proof.length !== 0) { + throw new Error('errProofFailed') + } + return record_hash + } + + if (record_proof.length === 0) { + throw new Error('errProofFailed') + } + + const [k, _] = max_power_2(hi - lo) + if (record_index < lo + k) { + const nextHash = run_record_proof(tree_hasher, record_proof.slice(0, record_proof.length - 1), lo, lo + k, record_index, record_hash) + return tree_hasher.hash_children(nextHash, record_proof[record_proof.length - 1]) + } else { + const nextHash = run_record_proof(tree_hasher, record_proof.slice(0, record_proof.length - 1), lo + k, hi, record_index, record_hash) + return tree_hasher.hash_children(record_proof[record_proof.length - 1], nextHash) + } + +} + +export function root_from_record_proof(tree_hasher: TreeHash, record_proof: RecordProof, tree_size: number, record_index: number, record_hash: Uint8Array) { + if (tree_size < 0) { + throw new Error(`tlog: tree_size less than 0 in root_from_record_proof`) + } + if (record_index < 0) { + throw new Error(`tlog: record_index less than 0 in root_from_record_proof`) + } + if (record_index >= tree_size) { + throw new Error(`tlog: record_index greater than or equal to tree_size in root_from_record_proof`) + } + return run_record_proof(tree_hasher, record_proof, 0, tree_size, record_index, record_hash) +} + +export function check_record(tree_hasher: TreeHash, record_proof: RecordProof, record_index: number, tree_root: Uint8Array, tree_size: number, record_hash: Uint8Array) { + const reconstructed_root = root_from_record_proof(tree_hasher, record_proof, record_index, tree_size, record_hash) + return verify_match(reconstructed_root, tree_root) +} + +export function tile_parent(tile: Tile, k: number, n: number): Tile { + // eslint-disable-next-line prefer-const + let [tile_height, tile_level, hash_number, tile_width] = [...tile] + tile_level += k + hash_number >>= (k * tile_height) + tile_width = 1 << (tile_height) + const max = n >> (tile_level * tile_height) + if ((hash_number << tile_height) + tile_width >= max) { + if ((hash_number << tile_height) >= max) { + return create_tile(tile_height, tile_level, hash_number, tile_width) // ? + } + tile_width = max - (hash_number << tile_height) + } + return create_tile(tile_height, tile_level, hash_number, tile_width) +} + + +export function tree_proof_index(lo: number, hi: number, record_index: number, needed_storage_ids: number[]) { + if (!(lo < record_index && record_index <= hi)) { + throw new Error(`tlog: bad math in tree_proof_index`) + } + if (record_index === hi) { + if (lo === 0) { + return needed_storage_ids + } + return subtree_index(lo, hi, needed_storage_ids) + } + const [k, _] = max_power_2(hi - lo) + if (record_index <= lo + k) { + needed_storage_ids = tree_proof_index(lo, lo + k, record_index, needed_storage_ids) + needed_storage_ids = subtree_index(lo + k, hi, needed_storage_ids) + } else { + needed_storage_ids = subtree_index(lo, lo + k, needed_storage_ids) + needed_storage_ids = tree_proof_index(lo + k, hi, record_index, needed_storage_ids) + } + return needed_storage_ids +} + + +export function tree_proof(tree_hasher: TreeHash, lo: number, hi: number, record_index: number, hashes: Uint8Array[]): [Uint8Array[], Uint8Array[]] { + if (!(lo < record_index && record_index <= hi)) { + throw new Error(`tlog: bad math in tree_proof`) + } + if (record_index === hi) { + if (lo == 0) { + return [[], hashes] + } + let next_hash + [next_hash, hashes] = subtree_hash(tree_hasher, lo, hi, hashes) + return [[next_hash], hashes] + } + + // Interior node for the proof. + let p + let next_hash: Uint8Array + + const [k, _] = max_power_2(hi - lo) + if (record_index <= lo + k) { + [p, hashes] = tree_proof(tree_hasher, lo, lo + k, record_index, hashes) + const sth: [Uint8Array, Uint8Array[]] = subtree_hash(tree_hasher, lo + k, hi, hashes) + next_hash = sth[0] + hashes = sth[1] + + } else { + [next_hash, hashes] = subtree_hash(tree_hasher, lo, lo + k, hashes) + const tp = tree_proof(tree_hasher, lo + k, hi, record_index, hashes) + p = tp[0] + hashes = tp[1] + + } + + p.push(next_hash) + return [p, hashes] + +} + +export function prove_tree(tree_hasher: TreeHash, tile: number, record_index: number, hash_reader: HashReader) { + if (tile < 1 || record_index < 1 || record_index > tile) { + throw new Error(`tlog: invalid inputs in prove_tree`) + } + const indexes = tree_proof_index(0, tile, record_index, []) + if (indexes.length === 0) { + return [] + } + let hashes = hash_reader.read_hashes(indexes) + if (hashes.length != indexes.length) { + throw new Error(`tlog: read_hashes(%d indexes) = %d hashes`) + } + let p + // eslint-disable-next-line prefer-const + [p, hashes] = tree_proof(tree_hasher, 0, tile, record_index, hashes) + if (hashes.length != 0) { + throw new Error(`tlog: bad index math in prove_tree`) + } + return p +} + +export function run_tree_proof(tree_hasher: TreeHash, tree_proof: TreeProof, lo: number, hi: number, record_index: number, old_tree_root: Uint8Array): [Uint8Array, Uint8Array] { + if (!(lo < record_index && record_index <= hi)) { + throw new Error(`tlog: bad math in run_tree_proof`) + } + if (record_index == hi) { + if (lo == 0) { + if (tree_proof.length !== 0) { + throw new Error(`errProofFailed`) + } + return [old_tree_root, old_tree_root] + } + if (tree_proof.length != 1) { + throw new Error(`errProofFailed`) + } + return [tree_proof[0], tree_proof[0]] + } + + if (tree_proof.length == 0) { + throw new Error(`errProofFailed`) + } + + const [k, _] = max_power_2(hi - lo) + if (record_index <= lo + k) { + const [oh, next_hash] = run_tree_proof(tree_hasher, tree_proof.slice(0, tree_proof.length - 1), lo, lo + k, record_index, old_tree_root) + return [oh, tree_hasher.hash_children(next_hash, tree_proof[tree_proof.length - 1])] + } else { + const [oh, next_hash] = run_tree_proof(tree_hasher, tree_proof.slice(0, tree_proof.length - 1), lo + k, hi, record_index, old_tree_root) + return [tree_hasher.hash_children(tree_proof[tree_proof.length - 1], oh), tree_hasher.hash_children(tree_proof[tree_proof.length - 1], next_hash)] + } +} + +export function new_tree_root_from_tree_proof(tree_hasher: TreeHash, tree_proof: Uint8Array[], new_tree_size: number, old_tree_size: number, old_tree_root: Uint8Array) { + if (old_tree_size > new_tree_size) { + throw new Error(`tlog: old_tree_size is greater than new_tree_size in check_tree`) + } + if (old_tree_size < 1) { + throw new Error(`tlog: old_tree_size is less than 1 in check_tree`) + } + if (new_tree_size < 1) { + throw new Error(`tlog: new_tree_size is less than 1 in check_tree`) + } + const [reconstructed_old_root, reconstructed_new_root] = run_tree_proof(tree_hasher, tree_proof, 0, new_tree_size, old_tree_size, old_tree_root) + if (verify_match(reconstructed_old_root, old_tree_root)) { + return reconstructed_new_root + } + return new Uint8Array() +} + +export function check_tree(tree_hasher: TreeHash, tree_proof: Uint8Array[], new_tree_size: number, new_tree_root: Uint8Array, old_tree_size: number, old_tree_root: Uint8Array) { + const reconstructed_new_tree_root = new_tree_root_from_tree_proof(tree_hasher, tree_proof, new_tree_size, old_tree_size, old_tree_root) + if (verify_match(reconstructed_new_tree_root, new_tree_root)) { + return true + } + throw new Error('check_tree failed') +} + + + +export class TileHashReader implements HashReader { + constructor(public size: number, public root: Uint8Array, public tile_storage: TileStorage, public tree_hasher: TreeHash) { } + read_hashes(indexes: number[]) { + const height = this.tile_storage.height() + const tileOrder = {} as Record + const tiles = [] as Tile[] + const stx = subtree_index(0, this.size, []) + const stxTileOrder = new Array(stx.length).fill(0) + for (let i = 0; i < stx.length; i++) { + const x = stx[i] + let [tile] = tile_for_storage_id(this.tree_hasher.hash_size, height, x) + tile = tile_parent(tile, 0, this.size) + if (tileOrder[tile_to_path(tile)]) { + stxTileOrder[i] = tileOrder[tile_to_path(tile)] + continue + } + stxTileOrder[i] = tiles.length + tileOrder[tile_to_path(tile)] = tiles.length + tiles.push(tile) + } + + // Plan to fetch tiles containing the indexes, + // along with any parent tiles needed + // for authentication. For most calls, + // the parents are being fetched anyway. + + const indexTileOrder = new Array(indexes.length).fill(0) + for (let i = 0; i < indexes.length; i++) { + const x = indexes[i] + if (x >= stored_hash_index(0, this.size)) { + throw new Error(`indexes not in tree`) + } + + const [tile] = tile_for_storage_id(this.tree_hasher.hash_size, height, x) + let k = 0; + for (; ; k++) { + const p = tile_parent(tile, k, this.size) + if (tileOrder[tile_to_path(p)] !== undefined) { + if (k === 0) { + indexTileOrder[i] = tileOrder[tile_to_path(p)] + } + break + } + } + + // Walk back down recording child tiles after parents. + // This loop ends by revisiting the tile for this index + // (tile_parent(tile, 0, r.tree.N)) unless k == 0, in which + // case the previous loop did it. + + for (k--; k >= 0; k--) { + // console.log("r.tree.N ", this.size) + const p = tile_parent(tile, k, this.size) + if (p[3] != (1 << p[0])) { + // Only full tiles have parents. + // This tile has a parent, so it must be full. + throw new Error(`"bad math in tileHashReader: %d %d %v`) + } + tileOrder[tile_to_path(p)] = tiles.length + if (k == 0) { + indexTileOrder[i] = tiles.length + } + tiles.push(p) + } + + } + // Fetch all the tile data. + + const data = this.tile_storage.read_tiles(tiles) + if (data.length != tiles.length) { + throw new Error(`TileStorage returned bad result slice (len=%d, want %d)`) + } + + // this slows things down... and should be removed... + // for (let i = 0; i < tiles.length; i++) { + // const tile = tiles[i] + // if (data[i].length !== tile[3] * hash_size) { + // throw new Error(`TileStorage returned bad result slice (%v len=%d, want %d)`) + // } + // } + + // Authenticate the initial tiles against the tree hash. + // They are arranged so that parents are authenticated before children. + // First the tiles needed for the tree hash. + + let next_hash = hash_from_tile(this.tree_hasher, tiles[stxTileOrder[stx.length - 1]], data[stxTileOrder[stx.length - 1]], stx[stx.length - 1]) + for (let i = stx.length - 2; i >= 0; i--) { + const h = hash_from_tile(this.tree_hasher, tiles[stxTileOrder[i]], data[stxTileOrder[i]], stx[i]) + next_hash = this.tree_hasher.hash_children(h, next_hash) + } + if (!verify_match(next_hash, this.root)) { + throw new Error(`downloaded inconsistent tile`) + } + + // Authenticate full tiles against their parents. + for (let i = stx.length; i < tiles.length; i++) { + const tile = tiles[i] + const p = tile_parent(tile, 1, this.size) + const j = tileOrder[tile_to_path(p)] + if (j === undefined) { + throw new Error(`bad math in tileHashReader %d %v: lost parent of %v`) + } + const h = hash_from_tile(this.tree_hasher, p, data[j], stored_hash_index(p[1] * p[0], tile[2])) + if (!verify_match(h, tile_hash(this.tree_hasher, data[i]))) { + throw new Error(`downloaded inconsistent tile 2`) + } + } + + this.tile_storage.save_tiles(tiles, data) + // pull out requested hashes + const hashes = new Array(indexes.length).fill(new Uint8Array()) + for (let i = 0; i < indexes.length; i++) { + const x = indexes[i] + const j = indexTileOrder[i] + const h = hash_from_tile(this.tree_hasher, tiles[j], data[j], x) + hashes[i] = h + } + return hashes + } +} + +export class TileLog implements TileStorage, HashReader { + public tree_hasher: TreeHash + public thr: TileHashReader + public tree_size = 0 + public tree_root: Uint8Array + public read_tile + public update_tiles + public tile_height: number + constructor( + config: TileLogParameters + ) { + this.tile_height = config.tile_height + this.tree_hasher = new TreeHash(config.hash_function, config.hash_size) + this.tree_root = this.tree_hasher.empty_root() + this.thr = new TileHashReader(this.tree_size, this.tree_root, this, this.tree_hasher,) + this.read_tile = config.read_tile + this.update_tiles = config.update_tiles + } + record_hash(data: Uint8Array) { + return this.tree_hasher.hash_leaf(data) + } + inclusion_proof(tree_size: number, record_index: number): InclusionProof { + const inclusion_path = prove_record(this.tree_hasher, tree_size, record_index, this) + return [tree_size, record_index, inclusion_path.map((p) => { return new Uint8Array(p) })] + } + verify_inclusion_proof(root: Uint8Array, inclusion_proof: InclusionProof, record_hash: Uint8Array) { + const [tree_size, record_index, record_proof] = inclusion_proof + return check_record(this.tree_hasher, record_proof, tree_size, root, record_index, record_hash) + } + consistency_proof(old_tree_size: number, new_tree_size: number): ConsistencyProof { + const consistency_path = prove_tree(this.tree_hasher, new_tree_size, old_tree_size, this) + return [old_tree_size, new_tree_size, consistency_path.map((p) => { return new Uint8Array(p) })] + } + verify_consistency_proof(old_tree_root: Uint8Array, consistency_proof: ConsistencyProof, new_tree_root: Uint8Array) { + const [old_tree_size, new_tree_size, proof] = consistency_proof + return check_tree(this.tree_hasher, proof, new_tree_size, new_tree_root, old_tree_size, old_tree_root) + } + root_from_inclusion_proof(inclusion_proof: InclusionProof, record_hash: Uint8Array): Uint8Array { + const [tree_size, record_index, record_proof] = inclusion_proof + return root_from_record_proof(this.tree_hasher, record_proof, tree_size, record_index, record_hash) + } + root_from_consistency_proof(old_tree_root: Uint8Array, consistency_proof: ConsistencyProof) { + const [old_tree_size, new_tree_size, tree_proof] = consistency_proof + return new_tree_root_from_tree_proof(this.tree_hasher, tree_proof, new_tree_size, old_tree_size, old_tree_root) + } + height() { + return this.tile_height + } + read_tiles(tiles: Tile[]) { + const result = [] as Uint8Array[] + for (const tile of tiles) { + const tile_data = this.read_tile(tile_to_path(tile)) + result.push(tile_data) + } + return result + } + save_tiles(tiles: Tile[]) { + // this is usually called on the client + // there is no needed_storage_ids to save tiles on the server + // since they are already saved when this is called + // in order to make a client implementation + // we needed_storage_ids to make the whole process async + } + read_hashes(storage_ids: number[]) { + return storage_ids.map((storage_id) => { + const [tile] = tile_for_storage_id(this.tree_hasher.hash_size, 2, storage_id) + const tileData = this.read_tile(tile_to_path(tile)) + const hash = hash_from_tile(this.tree_hasher, tile, tileData, storage_id) + return hash + }) + } + size() { + return this.tree_size + } + root() { + return this.root_at(this.tree_size) + } + root_at(tree_size: number) { + return tree_hash(this.tree_hasher, tree_size, this) + } + write_record_hashes = (record_hashes: Uint8Array[]) => { + for (const record_hash of record_hashes) { + const record_index = this.size() + const hashes = stored_hashes_for_record_hash(this.tree_hasher, record_index, record_hash, this) + let storage_id = stored_hash_count(record_index) + for (const stored_hash of hashes) { + // some hashes here, are not meant to be stored at all! + // needed_storage_ids to figure out if a hash belongs in a tile or not. + + const [tile, start, end] = tile_for_storage_id(this.tree_hasher.hash_size, this.tile_height, storage_id) + const tile_path = tile_to_path(tile) + const tileData = this.update_tiles(tile_path, start, end, stored_hash) + if (tileData === null) { + storage_id++ + continue + } + storage_id++ + } + this.tree_size++; + } + } + write_record = (record: Uint8Array) => { + this.write_record_hashes([this.record_hash(record)]) + } +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tree.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tree.ts new file mode 100644 index 0000000..6a7335c --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tree.ts @@ -0,0 +1,379 @@ + + + +import { TreeHash, verify_match } from "./TreeHash"; + +export type TreeNode = [number, number] + +export type TreeNodes = { + ids: TreeNode[], + begin: number, + end: number, + ephem: TreeNode +} + +export type HashChildren = (left: Uint8Array, right: Uint8Array) => Uint8Array + + +export function create_tree_node(level: number, index: number) { + return [level, index] as TreeNode +} + +// not efficient +export function trailing_zeros_64(n: number) { + const ns = n.toString(2) + let count = 0; + for (let i = 0; i < ns.length; i++) { + if (ns[i] === '1') { + count = 0 + } else { + count++ + } + } + return count +} + +export function length_64(num: number) { + return num.toString(2).length; +} + +export function ones_count_64(x: number) { + return (x.toString(2).match(/1/g) || []).length +} + +export function decompose(begin: number, end: number) { + if (begin === 0) { + return [0, end] + } + const xbegin = (begin >>> 0) - 1 + const d = length_64(xbegin ^ end) - 1 + const mask = (1 << d) - 1 + return [(-1 ^ xbegin) & mask, end & mask] +} + +export function range_size(begin: number, end: number) { + const [left, right] = decompose(begin, end) + return ones_count_64(left) + ones_count_64(right) +} + + +export function node_parent([level, index]: TreeNode) { + return create_tree_node(level + 1, index >> 1) +} + +export function node_sibling([level, index]: TreeNode) { + return create_tree_node(level, index ^ 1) +} + +export function node_coverage([level, index]: TreeNode) { + return create_tree_node(index << level, (index + 1) << level) +} + +export function range_nodes(begin: number, end: number, ids: TreeNode[]) { + let [left, right] = decompose(begin, end) + let pos = begin + let bit = 0 + while (left !== 0) { + const level = trailing_zeros_64(left) + bit = 1 << level + ids.push([level, pos >> level]) + pos = pos + bit + left = left ^ bit + } + bit = 0 + while (right !== 0) { + const level = length_64(right) - 1 + bit = 1 << level + ids.push([level, pos >> level]) + pos = pos + bit + right = right ^ bit + } + return ids +} + + + +function skip_first(nodes: TreeNodes) { + nodes.ids = nodes.ids.slice(1, nodes.ids.length) + if (nodes.begin < nodes.end) { + nodes.begin-- + nodes.end-- + } + return nodes +} + +export function create_nodes(index: number, level: number, size: number): TreeNodes { + const inner = length_64(index ^ (size >> level)) - 1 + const fork = create_tree_node(level + inner, index >> inner) + const [begin, end] = node_coverage(fork) + const left = range_size(0, begin) + const right = range_size(end, size) + let node = create_tree_node(level, index) + let nodes = [node] + while (node[0] < fork[0]) { + nodes.push(node_sibling(node)) + node = node_parent(node) + } + let len1 = nodes.length + nodes = range_nodes(end, size, nodes) + nodes = [...nodes.slice(0, nodes.length - right), ...nodes.slice(nodes.length - right, nodes.length).reverse()] + let len2 = nodes.length + nodes = range_nodes(0, begin, nodes) + nodes = [...nodes.slice(0, nodes.length - left), ...nodes.slice(nodes.length - left, nodes.length).reverse()] + if (len1 >= len2) { + len1 = 0 + len2 = 0 + } + return { + ids: nodes, + begin: len1, + end: len2, + ephem: node_sibling(fork) + } +} + +function inner_proof_size(index: number, size: number) { + return length_64(index ^ (size - 1)) +} + +function decompose_inclusion_proof(index: number, size: number) { + const inner = inner_proof_size(index, size) + const border = ones_count_64(index >> inner) + return [inner, border] +} + +function chain_inner(tree_hasher: TreeHash, seed: Uint8Array, proof: Uint8Array[], index: number) { + let i = 0; + while (i < proof.length) { + const h = proof[i] + if ((index >> i) == 0) { + seed = tree_hasher.hash_children(seed, h) + } else { + seed = tree_hasher.hash_children(h, seed) + } + i++; + } + return seed +} + +export function chain_inner_right(tree_hasher: TreeHash, seed: Uint8Array, proof: Uint8Array[], index: number) { + let i = 0; + while (i < proof.length) { + const h = proof[i] + if (index >> i) { + seed = tree_hasher.hash_children(h, seed) + } + i++ + } + return seed +} + +function chain_border_right(tree_hasher: TreeHash, seed: Uint8Array, proof: Uint8Array[]) { + const i = 0; + while (i < proof.length) { + const h = proof[i] + seed = tree_hasher.hash_children(h, seed) + } + return seed +} + +function root_from_inclusion_proof(tree_hasher: TreeHash, index: number, size: number, leaf_hash: Uint8Array, proof: Uint8Array[]) { + if (index >= size) { + throw new Error(`index is beyond size: ${index} >= ${size}`) + } + if (leaf_hash.length != tree_hasher.hash_size) { + throw new Error(`leaf_hash has unexpected size ${leaf_hash.length}, want ${tree_hasher.hash_size}`) + } + const [inner, border] = decompose_inclusion_proof(index, size) + + if (proof.length != inner + border) { + throw new Error(`wrong proof size ${proof.length}, want ${inner + border}`) + } + let res = chain_inner(tree_hasher, leaf_hash, proof.slice(0, inner), index) + res = chain_border_right(tree_hasher, res, proof.slice(inner, proof.length)) + return res +} + + + +export function verify_inclusion(tree_hasher: TreeHash, index: number, size: number, leaf_hash: Uint8Array, proof: Uint8Array[], root: Uint8Array) { + const reconstructed_root = root_from_inclusion_proof(tree_hasher, index, size, leaf_hash, proof) + return verify_match(reconstructed_root, root) +} + + +export function verify_consistency(tree_hasher: TreeHash, size1: number, size2: number, proof: Uint8Array[], root1: Uint8Array, root2: Uint8Array) { + if (size2 < size1) { + throw new Error(`size2 (${size2}) < size1 (${size1})`) + } + if (size1 === size2) { + if (proof.length > 0) { + throw new Error(`size1=size2, but proof is not empty`) + } + return verify_match(root1, root2) + } + if (size1 == 0) { + if (proof.length > 0) { + throw new Error(`expected empty proof, but got ${proof.length} components`) + } + return true // Proof OK. + } + if (proof.length === 0) { + throw new Error(`empty proof`) + } + + // eslint-disable-next-line prefer-const + let [inner, border] = decompose_inclusion_proof(size1 - 1, size2) + const shift = trailing_zeros_64(size1) + inner -= shift + + let seed = proof[0] + let start = 1 + if (size1 === (1 << shift)) { + seed = root1 + start = 0 + } + if (proof.length != start + inner + border) { + throw new Error(`wrong proof size ${proof.length}, want ${start + inner + border}`) + } + proof = proof.slice(start, proof.length) + const mask = (size1 - 1) >> shift + let hash1 = chain_inner_right(tree_hasher, seed, proof.slice(0, inner), mask) + hash1 = chain_border_right(tree_hasher, hash1, proof.slice(inner, proof.length)) + if (!verify_match(hash1, root1)) { + throw new Error('inconsistency with root 1') + } + let hash2 = chain_inner(tree_hasher, seed, proof.slice(0, inner), mask) + hash2 = chain_border_right(tree_hasher, hash2, proof.slice(inner, proof.length)) + if (!verify_match(hash2, root2)) { + throw new Error('inconsistency with root 2') + } + return true +} + + +export function inclusion(index: number, size: number): TreeNodes { + if (index >= size) { + throw new Error(`Index ${index} out of bounds for tree size ${size}`) + } + const nodes = create_nodes(index, 0, size) + return skip_first(nodes) +} + +export function consistency(old_tree_size: number, new_tree_size: number): TreeNodes { + if (old_tree_size > new_tree_size) { + throw new Error(`tree size ${old_tree_size} > ${new_tree_size}`) + } + if (old_tree_size === new_tree_size && old_tree_size === 0) { + return { ids: [], begin: 0, end: 0, ephem: [0, 0] } + } + const level = trailing_zeros_64(old_tree_size) + const index = (old_tree_size - 1) >> level + const p = create_nodes(index, level, new_tree_size) + if (index == 0) { + return skip_first(p) + } + return p +} + +export function rehash(nodes: TreeNodes, hashes: Uint8Array[], hash_children: HashChildren) { + if (hashes.length != nodes.ids.length) { + throw new Error(`got ${hashes.length} hashes but expected ${nodes.ids.length}`) + } + let cursor = 0; + let i = 0 + const ln = hashes.length + while (i < ln) { + let hash = hashes[i] + if (i >= nodes.begin && i < nodes.end) { + while (++i < nodes.end) { + const left = hashes[i] + const right = hash + hash = hash_children(left, right) + i++ + } + i-- + } + hashes[cursor] = hash + i = i + 1 + cursor = cursor + 1 + } + return hashes.slice(0, cursor) +} + + +export class Tree { + public size: number + public encoder: TextEncoder + + constructor(public tree_hasher: TreeHash, public hashes: Uint8Array[][] = []) { + this.size = this.hashes.length + this.encoder = new TextEncoder() + } + + encode_data(data: string) { + return this.encoder.encode(data) + } + + append_data(data: Uint8Array) { + const hash = this.tree_hasher.hash_leaf(data) + this.append_record_hash(hash) + } + + append_record_hash(hash: Uint8Array) { + let level = 0 + while (((this.size >> level) & 1) == 1) { + this.hashes[level].push(hash) + const row = this.hashes[level] + hash = this.tree_hasher.hash_children(row[row.length - 2], hash) + level++ + } + if (level > this.hashes.length) { + throw new Error('Gap in tree appends') + } else if (level === this.hashes.length) { + this.hashes.push([]) + } + this.hashes[level].push(hash) + this.size++ + } + + hash() { + return this.root_at(this.size) + } + + get_nodes(ids: TreeNode[]) { + const hashes = new Array(ids.length) as Uint8Array[] + for (const i in ids) { + const id = ids[i] + const [level, index] = id + hashes[i] = this.hashes[level][index] + } + return hashes + } + + root_at(size: number) { + if (size === 0) { + return this.tree_hasher.empty_root() + } + const hashes = this.get_nodes(range_nodes(0, size, [])) + let hash = hashes[hashes.length - 1] + let i = hashes.length - 2; + while (i >= 0) { + hash = this.tree_hasher.hash_children(hashes[i], hash) + i-- + } + return hash + } + + inclusion_proof(record_index: number, tree_size: number) { + const nodes = inclusion(record_index, tree_size) + const hashes = this.get_nodes(nodes.ids) + return rehash(nodes, hashes, this.tree_hasher.hash_children) + } + + consistency_proof(old_tree_size: number, new_tree_size: number) { + const nodes = consistency(old_tree_size, new_tree_size) + const hashes = this.get_nodes(nodes.ids) + return rehash(nodes, hashes, this.tree_hasher.hash_children) + } +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/TreeHash.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/TreeHash.ts new file mode 100644 index 0000000..c0db42a --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/TreeHash.ts @@ -0,0 +1,35 @@ +export const empty_bytes = new Uint8Array() +export const leaf_prefix = new Uint8Array([0]) +export const intermediate_prefix = new Uint8Array([1]) + +export function concat(a1: Uint8Array, a2: Uint8Array): Uint8Array { + // sum of individual array lengths + const mergedArray = new Uint8Array(a1.length + a2.length) + mergedArray.set(a1) + mergedArray.set(a2, a1.length) + return mergedArray +} + +export function to_hex(bytes: Uint8Array) { + return bytes.reduce( + (str: string, byte: number) => str + byte.toString(16).padStart(2, '0'), + '', + ) +} + +export function verify_match(root1: Uint8Array, root2: Uint8Array) { + return to_hex(root1) === to_hex(root2) +} + +export class TreeHash { + constructor(public hash: (data: Uint8Array) => Uint8Array, public hash_size: number) { } + empty_root() { + return this.hash(empty_bytes) + } + hash_leaf(leaf: Uint8Array) { + return this.hash(concat(leaf_prefix, leaf)) + } + hash_children(left: Uint8Array, right: Uint8Array) { + return this.hash(concat(intermediate_prefix, concat(left, right))) + } +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/index.ts new file mode 100644 index 0000000..9fe1d8b --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/index.ts @@ -0,0 +1,5 @@ + + +export * from './TreeHash' +export * from './Tree' +export * from './Tile' \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 60ae05f..5b63330 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,8 +8,8 @@ import * as cbor from './cbor' import * as crypto from './crypto' export * from './drafts/draft-ietf-cose-merkle-tree-proofs' -export * from './drafts/draft-ietf-jose-fully-specified-algorithms' export * from './drafts/draft-ietf-cose-hash-envelope' +export * from './drafts/draft-ietf-jose-fully-specified-algorithms' // https://github.com/dajiaji/hpke-js/issues/302 // this issue also effect vercel ncc diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts b/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts new file mode 100644 index 0000000..3ea5283 --- /dev/null +++ b/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts @@ -0,0 +1,60 @@ +import crypto from 'crypto' + +import sqlite from 'better-sqlite3' +import * as cose from '../../src'; + +const db = new sqlite("./tests/draft-ietf-cose-merkle-tree-proofs/transparency.db"); + +db.prepare(` +CREATE TABLE IF NOT EXISTS tiles +(id TEXT PRIMARY KEY, data BLOB); + `).run() +const hash_size = 32 +const tile_height = 2 + +export const log = new cose.TileLog({ + tile_height, + hash_size, + hash_function: (data: Uint8Array) => { + return new Uint8Array(crypto.createHash('sha256').update(data).digest()); + }, + read_tile: (tile: string): Uint8Array => { + const [base_tile] = tile.split('.') + // look for completed tiles first + for (let i = 4; i > 0; i--) { + const tile_path = base_tile + '.' + i + const rows = db.prepare(` + SELECT * FROM tiles + WHERE id = '${tile_path}' + `).all(); + if (rows.length) { + const [row] = rows as { id: string, data: Uint8Array }[] + return row.data + } + } + return new Uint8Array(32) + }, + update_tiles: function (tile_path: string, start: number, end: number, stored_hash: Uint8Array) { + if (end - start !== 32) { + // this hash was an intermediate of the tile + // so it will never be persisted + return null + } + let tile_data = this.read_tile(tile_path) + if (tile_data.length < end) { + const expanded_tile_data = new Uint8Array(tile_data.length + 32) + expanded_tile_data.set(tile_data) + tile_data = expanded_tile_data + } + tile_data.set(stored_hash, start) + try { + db.prepare(` + INSERT INTO tiles (id, data) + VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString('hex')}'); + `).run() + } catch (e) { + // ignore errors + } + return tile_data + } +}) \ No newline at end of file diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts new file mode 100644 index 0000000..d4c913b --- /dev/null +++ b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts @@ -0,0 +1,98 @@ + +import * as cose from '../../src'; + +import { log } from './test_log'; + +it("cose receipts from a tiled transparency log", async () => { + const encoder = new TextEncoder() + for (let i = 0; i < 26; i++) { + const record = encoder.encode(`entry-${i}`) + log.write_record(record) + } + // prove 17 was in log at tree size 20 + const inclusion_proof = log.inclusion_proof(20, 17) + const root_from_inclusion_proof = log.root_from_inclusion_proof(inclusion_proof, log.record_hash(encoder.encode(`entry-${17}`))) + // prove log is append only from root at 20 to current log size + const consistency_proof = log.consistency_proof(20, log.size()) + const root_from_consistency_proof = log.root_from_consistency_proof(root_from_inclusion_proof, consistency_proof) + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) + const publicKeyJwk = cose.public_from_private({ + key: privateKeyJwk, + type: "application/jwk+json" + }) + const encoded_inclusion_proof = cose.encode_inclusion_proof(inclusion_proof) + const inclusion_receipt = await cose.detached + .signer({ + remote: cose.crypto.signer({ + privateKeyJwk + }) + }) + .sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] + ]), + unprotectedHeader: cose.UnprotectedHeader([ + [cose.draft_headers.verifiable_data_proofs, cose.VerifiableDataStructureProofs([ + [cose.rfc9162_sha256_proof_types.inclusion, [encoded_inclusion_proof]], + ])] + ]), + payload: root_from_inclusion_proof + }) + const [inclusion_proof_from_unprotected_header] = cose.decode_inclusion_proof(inclusion_receipt) + const reconstructed_inclusion_root_from_unprotected_header = log.root_from_inclusion_proof(inclusion_proof_from_unprotected_header, log.record_hash(encoder.encode(`entry-${17}`))) + const verified_inclusion_receipt = await cose.detached + .verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk + } + } + }) + .verify({ + coseSign1: inclusion_receipt, + payload: reconstructed_inclusion_root_from_unprotected_header + }) + // verified signed root from inclusion proof + expect(Buffer.from(verified_inclusion_receipt).toString('hex')).toBe(Buffer.from(root_from_inclusion_proof).toString('hex')) + const encoded_consistency_proof = cose.encode_inclusion_proof(consistency_proof) + const consistency_receipt = await cose.detached + .signer({ + remote: cose.crypto.signer({ + privateKeyJwk + }) + }) + .sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] + ]), + unprotectedHeader: cose.UnprotectedHeader([ + [cose.draft_headers.verifiable_data_proofs, cose.VerifiableDataStructureProofs([ + [cose.rfc9162_sha256_proof_types.consistency, [encoded_consistency_proof]], + ])] + ]), + payload: root_from_consistency_proof + }) + const [consistency_proof_from_unprotected_header] = cose.decode_consistency_proof(consistency_receipt) + const reconstructed_consistency_root_from_unprotected_header = log.root_from_consistency_proof(verified_inclusion_receipt, consistency_proof_from_unprotected_header) + const verified_consistency_receipt = await cose.detached + .verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk + } + } + }) + .verify({ + coseSign1: consistency_receipt, + payload: reconstructed_consistency_root_from_unprotected_header + }) + + // verified signed root from consistency proof + expect(Buffer.from(verified_consistency_receipt).toString('hex')).toBe(Buffer.from(root_from_consistency_proof).toString('hex')) + +}) \ No newline at end of file From 80465e78eac0fd1a3b1dfc0ca07b9202bbfbe935 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 1 Oct 2024 16:13:59 -0500 Subject: [PATCH 45/67] remove all receipts --- .../index.ts | 3 - .../receipt/add.ts | 21 --- .../receipt/consistency/index.ts | 2 - .../receipt/consistency/issue.ts | 78 ------------ .../receipt/consistency/verify.ts | 52 -------- .../receipt/get.ts | 19 --- .../receipt/inclusion/index.ts | 2 - .../receipt/inclusion/issue.ts | 45 ------- .../receipt/inclusion/verify.ts | 50 -------- .../receipt/index.ts | 8 -- .../receipt/leaf.ts | 3 - .../receipt/remove.ts | 13 -- .../receipt/verifier.ts | 56 -------- .../consistency.receipt.diag | 22 ++++ .../inclusion.receipt.diag | 21 +++ .../tiled_log.test.ts | 4 +- tests/receipt.test.ts | 120 ------------------ 17 files changed, 46 insertions(+), 473 deletions(-) delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts delete mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts create mode 100644 tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag create mode 100644 tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag delete mode 100644 tests/receipt.test.ts diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts index 1c2cc9a..16f3a31 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -1,4 +1,3 @@ -import * as receipt from './receipt' export * from './encode_inclusion_proof' export * from './decode_inclusion_proof' @@ -22,6 +21,4 @@ export const rfc9162_sha256_proof_types = { 'consistency': -2 } -export { receipt } - export * from './log' \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts deleted file mode 100644 index 6968e23..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/add.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../../cbor' - -import { CoseSign1Bytes } from "../../../cose/sign1"; - -import { draft_headers } from '../../../iana/requested/cose'; -import * as cbor from '../../../iana/assignments/cbor'; - -export const add = async (signature: CoseSign1Bytes, receipt: CoseSign1Bytes): Promise => { - const { tag, value } = decodeFirstSync(signature) - if (tag !== cbor.tag.COSE_Sign1) { - throw new Error('Receipts can only be added to cose-sign1') - } - if (!(value[1] instanceof Map)) { - value[1] = new Map(); - } - // unprotected header - const receipts = value[1].get(draft_headers.receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ - receipts.push(receipt) - value[1].set(draft_headers.receipts, receipts) - return toArrayBuffer(await encodeAsync(new Tagged(cbor.tag.COSE_Sign1, value), { canonical: true })); -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts deleted file mode 100644 index 7b0e7c2..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './issue' -export * from './verify' \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts deleted file mode 100644 index d23d079..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/issue.ts +++ /dev/null @@ -1,78 +0,0 @@ - -import { CoMETRE } from '@transmute/rfc9162' - -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' - -import { CoseSign1Bytes, CoseSign1Signer } from "../../../../cose/sign1" -import { toArrayBuffer } from '../../../../cbor' - -import { draft_headers } from '../../../..' - -import { HeaderMap } from '../../../..' - - -export type RequestIssueConsistencyReceipt = { - protectedHeader: HeaderMap - receipt: CoseSign1Bytes, - entries: Uint8Array[] - signer: CoseSign1Signer -} - -export const issue = async (req: RequestIssueConsistencyReceipt) => { - const { protectedHeader, receipt, entries, signer } = req; - const consistencyVds = protectedHeader.get(draft_headers.verifiable_data_structure) - if (consistencyVds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { - throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') - } - - const { tag, value } = cbor.decode(receipt); - if (tag !== 18) { - throw new Error('Receipt is not tagged cose sign1') - } - - const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value - const receiptProtectedHeader = cbor.decode(protectedHeaderBytes) - const inclusionVds = receiptProtectedHeader.get(draft_headers.verifiable_data_structure); - if (inclusionVds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { - throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') - } - - const [inclusion] = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) - .get(VerifiableDataProofTypes['RFC9162-Inclusion-Proof']) // get first inclusion proof - if (payload !== null) { - throw new Error('payload must be null for this type of proof') - } - const [tree_size, leaf_index, inclusion_path] = cbor.decode(inclusion) - - const consistency_proof = await CoMETRE.RFC9162_SHA256.consistency_proof( - { - log_id: '', - tree_size, - leaf_index, - inclusion_path, - }, - entries, - ) - - const root = await CoMETRE.RFC9162_SHA256.root(entries) - - const proofs = new Map(); - proofs.set(VerifiableDataProofTypes['RFC9162-Consistency-Proof'], [ // -2 is consistency proof for 395 (vds), 1 (RFC9162) - cbor.encode([ - consistency_proof.tree_size_1, - consistency_proof.tree_size_2, - consistency_proof.consistency_path.map(toArrayBuffer), - ]), - ]) - - const unprotectedHeader = new Map(); - unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) - - const consistency = new Uint8Array(await signer.sign({ - protectedHeader, - unprotectedHeader, - payload: root - })) - - return { root, receipt: consistency, } -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts deleted file mode 100644 index 304c89b..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/consistency/verify.ts +++ /dev/null @@ -1,52 +0,0 @@ - -import { CoMETRE } from '@transmute/rfc9162' - -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' - -import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../../../cose/sign1" - -import { draft_headers } from '../../../..' - - -export type RequestVerifyConsistencyReceipt = { - oldRoot: ArrayBuffer, - newRoot: ArrayBuffer, - receipt: CoseSign1Bytes, - verifier: CoseSign1DetachedVerifier -} - -export const verify = async (req: RequestVerifyConsistencyReceipt) => { - const { newRoot, oldRoot, receipt, verifier } = req - const { tag, value } = cbor.decode(receipt); - if (tag !== 18) { - throw new Error('Receipt is not tagged cose sign1') - } - const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value - const protectedHeader = cbor.decode(protectedHeaderBytes) - const vds = protectedHeader.get(draft_headers.verifiable_data_structure); - if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { - throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') - } - const proofs = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) - const [consistency] = proofs.get(VerifiableDataProofTypes['RFC9162-Consistency-Proof']) // get first consistency proof - if (payload !== null) { - throw new Error('payload must be null for this type of proof') - } - const [tree_size_1, - tree_size_2, - consistency_path] = cbor.decode(consistency) - - const verifiedNewRoot = await verifier.verify({ coseSign1: receipt, payload: newRoot }) - const verified = await CoMETRE.RFC9162_SHA256.verify_consistency_proof( - new Uint8Array(oldRoot), - new Uint8Array(verifiedNewRoot), - { - log_id: '', - tree_size_1, - tree_size_2, - consistency_path, - }, - ) - return verified - -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts deleted file mode 100644 index b8bbc7e..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/get.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { decodeFirstSync } from '../../../cbor' - -import { CoseSign1Bytes } from "../../../cose/sign1"; - -import { draft_headers } from '../../../iana/requested/cose'; -import * as cbor from '../../../iana/assignments/cbor'; - -export const get = async (signature: CoseSign1Bytes): Promise => { - const { tag, value } = decodeFirstSync(signature) - if (tag !== cbor.tag.COSE_Sign1) { - throw new Error('Receipts can only be added to cose-sign1') - } - if (!(value[1] instanceof Map)) { - return [] - } - // unprotected header - const receipts = value[1].get(draft_headers.receipts) || []; // see https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/ - return receipts -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts deleted file mode 100644 index 7b0e7c2..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './issue' -export * from './verify' \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts deleted file mode 100644 index 577da77..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/issue.ts +++ /dev/null @@ -1,45 +0,0 @@ - -import { CoMETRE } from '@transmute/rfc9162' - -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' - -import { CoseSign1Signer } from "../../../../cose/sign1" - -import { draft_headers } from '../../../..' - -import { HeaderMap } from '../../../..' - -export type RequestIssueInclusionReceipt = { - protectedHeader: HeaderMap - entry: number, - entries: Uint8Array[] - signer: CoseSign1Signer -} - -export const issue = async (req: RequestIssueInclusionReceipt): Promise => { - const { protectedHeader, entry, entries, signer } = req; - const vds = protectedHeader.get(draft_headers.verifiable_data_structure) - if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { - throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') - } - const root = await CoMETRE.RFC9162_SHA256.root(entries) - const proof = await CoMETRE.RFC9162_SHA256.inclusion_proof( - entry, - entries, - ) - const proofs = new Map(); - proofs.set(VerifiableDataProofTypes['RFC9162-Inclusion-Proof'], [ // -1 is inclusion proof for 395 (vds), 1 (RFC9162) - cbor.encode([ // encoded proof - proof.tree_size, - proof.leaf_index, - proof.inclusion_path.map(cbor.toArrayBuffer), - ]) - ]) - const unprotectedHeader = new Map(); - unprotectedHeader.set(draft_headers.verifiable_data_proofs, proofs) - return new Uint8Array(await signer.sign({ - protectedHeader, - unprotectedHeader, - payload: root - })) -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts deleted file mode 100644 index 2cfb1ec..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/inclusion/verify.ts +++ /dev/null @@ -1,50 +0,0 @@ - -import { CoMETRE } from '@transmute/rfc9162' - -import { cbor, VerifiableDataProofTypes, VerifiableDataStructures } from '../../../..' - -import { CoseSign1Bytes, CoseSign1DetachedVerifier } from "../../../../cose/sign1" - - -import { draft_headers } from '../../../..' - - -export type RequestVerifyInclusionReceipt = { - entry: Uint8Array, - receipt: CoseSign1Bytes, - verifier: CoseSign1DetachedVerifier -} - -export const verify = async (req: RequestVerifyInclusionReceipt) => { - const { entry, receipt, verifier } = req - const { tag, value } = cbor.decode(receipt); - if (tag !== 18) { - throw new Error('Receipt is not tagged cose sign1') - } - const [protectedHeaderBytes, unprotectedHeaderMap, payload] = value - const protectedHeader = cbor.decode(protectedHeaderBytes) - const vds = protectedHeader.get(draft_headers.verifiable_data_structure); - if (vds !== VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']) { - throw new Error('Unsupported verifiable data structure. See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs') - } - const proofs = unprotectedHeaderMap.get(draft_headers.verifiable_data_proofs) - const [inclusion] = proofs.get(VerifiableDataProofTypes['RFC9162-Inclusion-Proof']) // get first inclusion proof - if (payload !== null) { - throw new Error('payload must be null for this type of proof') - } - const [tree_size, leaf_index, inclusion_path] = cbor.decode(inclusion) - const root = await CoMETRE.RFC9162_SHA256.verify_inclusion_proof( - entry, - { - log_id: '', - tree_size, - leaf_index, - inclusion_path, - }, - ) - const verified = verifier.verify({ - coseSign1: receipt, - payload: root - }) - return verified -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts deleted file mode 100644 index ef044bf..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as inclusion from './inclusion' -import * as consistency from './consistency' -import { leaf } from './leaf' -import { add } from './add' -import { get } from './get' -import { remove } from './remove' -import { verifier } from './verifier' -export { leaf, inclusion, consistency, add, get, remove, verifier } \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts deleted file mode 100644 index 9e1dfdd..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/leaf.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CoMETRE } from '@transmute/rfc9162' - -export const leaf = CoMETRE.RFC9162_SHA256.leaf \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts deleted file mode 100644 index a9dbfd0..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/remove.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { decodeFirstSync, toArrayBuffer, encodeAsync, Tagged } from '../../../cbor' -import { CoseSign1Bytes } from "../../../cose/sign1"; - -import * as cbor from '../../../iana/assignments/cbor' - -export const remove = async (signature: CoseSign1Bytes): Promise => { - const { tag, value } = decodeFirstSync(signature) - if (tag !== cbor.tag.COSE_Sign1) { - throw new Error('Receipts can only be added to cose-sign1') - } - value[1] = new Map(); - return toArrayBuffer(await encodeAsync(new Tagged(cbor.tag.COSE_Sign1, value), { canonical: true })); -} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts deleted file mode 100644 index 4b6ac2b..0000000 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/receipt/verifier.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { RequestCoseSign1DectachedVerify } from "../../../cose/sign1" - - - -import { detached } from "../../.." -import { get } from "./get" - -import * as inclusion from './inclusion' -import { leaf } from "./leaf" -import { remove } from "./remove" - -import { web_key_type } from "../../../iana/assignments/jose" - -export type RequestHeaderVerifier = { - resolve: (signature: ArrayBuffer) => Promise -} - -const getVerifierForMessage = async (req: RequestCoseSign1DectachedVerify, resolver: RequestHeaderVerifier) => { - const verifier = detached.verifier({ resolver }) - return verifier -} - -const verifyWithResolve = async (req: RequestCoseSign1DectachedVerify, opt: RequestHeaderVerifier) => { - const verifier = await getVerifierForMessage(req, opt) - const verified = await verifier.verify(req) - return verified -} - -export const verifier = async (opt: RequestHeaderVerifier) => { - return { - verify: async (req: RequestCoseSign1DectachedVerify) => { - const verifiedPayload = await verifyWithResolve(req, opt) - const verification = { - payload: verifiedPayload, - receipts: [] as ArrayBuffer[] - } - const bytesOnLedger = await remove(req.coseSign1) - const receipts = await get(req.coseSign1) - if (receipts.length) { - for (const receipt of receipts) { - const verifier = await getVerifierForMessage({ - coseSign1: receipt, - payload: bytesOnLedger - }, opt) - const verifiedLedgerHead = await inclusion.verify({ - entry: await leaf(new Uint8Array(bytesOnLedger)), - receipt, - verifier - }) - verification.receipts.push(verifiedLedgerHead) - } - } - return verification - } - } -} \ No newline at end of file diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag b/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag new file mode 100644 index 0000000..55918a6 --- /dev/null +++ b/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag @@ -0,0 +1,22 @@ +/ cose-sign1 / 18([ + / protected / <<{ + / algorithm / 1 : -7, # ES256 + / notarized / 395 : 1, # RFC9162 SHA-256 + }>>, + / unprotected / { + / proofs / 396 : { + / consistency / -2 : [ + <<[ + / old / 20, / new / 26, + / consistency path / + h'e5b3e764...c4a813bc', + h'87e8a084...4f529f69', + h'5f9bdd41...45838ee8', + h'd68af9d6...93b1632b' + ]>> + ], + }, + }, + / payload / null, + / signature / h'302b6f58...2585421a' +]) diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag b/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag new file mode 100644 index 0000000..a29f464 --- /dev/null +++ b/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag @@ -0,0 +1,21 @@ +/ cose-sign1 / 18([ + / protected / <<{ + / algorithm / 1 : -7, # ES256 + / notarized / 395 : 1, # RFC9162 SHA-256 + }>>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 20, / leaf / 17, + / inclusion path / + h'fc9f050f...221c92cb', + h'bd0136ad...6b28cf21', + h'd68af9d6...93b1632b' + ]>> + ], + }, + }, + / payload / null, + / signature / h'178d350f...6652a304' +]) diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts index d4c913b..b7cb865 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts +++ b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts @@ -1,4 +1,4 @@ - +import fs from 'fs' import * as cose from '../../src'; import { log } from './test_log'; @@ -42,6 +42,7 @@ it("cose receipts from a tiled transparency log", async () => { ]), payload: root_from_inclusion_proof }) + fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag', await cose.cbor.diag(inclusion_receipt, "application/cose")) const [inclusion_proof_from_unprotected_header] = cose.decode_inclusion_proof(inclusion_receipt) const reconstructed_inclusion_root_from_unprotected_header = log.root_from_inclusion_proof(inclusion_proof_from_unprotected_header, log.record_hash(encoder.encode(`entry-${17}`))) const verified_inclusion_receipt = await cose.detached @@ -77,6 +78,7 @@ it("cose receipts from a tiled transparency log", async () => { ]), payload: root_from_consistency_proof }) + fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag', await cose.cbor.diag(consistency_receipt, "application/cose")) const [consistency_proof_from_unprotected_header] = cose.decode_consistency_proof(consistency_receipt) const reconstructed_consistency_root_from_unprotected_header = log.root_from_consistency_proof(verified_inclusion_receipt, consistency_proof_from_unprotected_header) const verified_consistency_receipt = await cose.detached diff --git a/tests/receipt.test.ts b/tests/receipt.test.ts deleted file mode 100644 index f563a7b..0000000 --- a/tests/receipt.test.ts +++ /dev/null @@ -1,120 +0,0 @@ - - - -import fs from 'fs' - -import * as cose from '../src' -const encoder = new TextEncoder(); - -it('issue & verify', async () => { - - const entries = await Promise.all([`💣 test`, `✨ test`, `🔥 test`] - .map((entry) => { - return encoder.encode(entry) - }) - .map((entry) => { - return cose.receipt.leaf(entry) - })) - - const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ - type: "application/jwk+json", - algorithm: "ES256" - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { d, ...publicKeyJwk } = privateKeyJwk - const signer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk - }) - }) - const verifier = cose.detached.verifier({ - resolver: { - resolve: async () => { - return publicKeyJwk - } - } - }) - const inclusion = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 - ]), - entry: 1, - entries, - signer - }) - const oldVerifiedRoot = await cose.receipt.inclusion.verify({ - entry: entries[1], - receipt: inclusion, - verifier - }) - // because entries are stable, verified root is stable. - expect(Buffer.from(oldVerifiedRoot).toString('hex')).toBe('d82bd9d3f1e3dd82506d1ab09dd2ed6790596b1a2fe95a64d504dc9e2f90dab6') - // new entries are added over time. - entries.push(await cose.receipt.leaf(encoder.encode('✨ new entry ✨'))) - // ask the transparency service for the latest root, and a consistency proof - // based on a previous receipt - const { root, receipt } = await cose.receipt.consistency.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 - ]), - receipt: inclusion, - entries, - signer - }) - const consistencyValidated = await cose.receipt.consistency.verify({ - oldRoot: oldVerifiedRoot, - newRoot: root, - receipt: receipt, - verifier - }) - expect(consistencyValidated).toBe(true) -}) - -it("add / remove from receipts", async () => { - const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ - type: "application/jwk+json", - algorithm: "ES256" - }) - const publicKeyJwk = await cose.crypto.key.public_from_private<'ES256', 'application/jwk+json'>({ - key: privateKeyJwk, - type: 'application/jwk+json' - }) - const signer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk - }) - }) - const content = fs.readFileSync('./examples/image.png') - const signatureForImage = await signer.sign({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.header.content_type, "image/png"], // content_type image/png - ]), - payload: content - }) - const transparencyLogContainingImageSignatures = [await cose.receipt.leaf(signatureForImage)] - // inclusion proof receipt for image signature - const receiptForImageSignature = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 - ]), - entry: 0, - entries: transparencyLogContainingImageSignatures, - signer - }) - const transparentSignature = await cose.receipt.add(signatureForImage, receiptForImageSignature) - const { value } = cose.cbor.decode(transparentSignature) - expect(value[1].get(cose.draft_headers.receipts).length).toBe(1) // expect 1 receipt - const receipts = await cose.receipt.get(transparentSignature) - expect(receipts.length).toBe(1) // expect 1 receipt - const coseKey = await cose.crypto.key.web_key_to_cose_key>(publicKeyJwk) - coseKey.set(cose.ec2.kid, await cose.crypto.key.cose_key_thumbprint_uri(coseKey)) - const publicKey = cose.crypto.key.serialize<'ES256', 'application/cose-key'>({ key: coseKey, type: 'application/cose-key' }) - expect(publicKey).toBeDefined(); - // fs.writeFileSync('./examples/image.ckt.signature.cbor', Buffer.from(transparentSignature)) - // fs.writeFileSync('./examples/image.ckt.public-key.cbor', publicKey) -}) - From 1accefa6fa791ce4c2bcd8ffedd7fe21a0db1f7f Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 1 Oct 2024 16:15:38 -0500 Subject: [PATCH 46/67] remove older receipt tests --- tests/__fixtures__/hash-envelope.cbor | Bin 167 -> 167 bytes tests/__fixtures__/hash-envelope.diag | 2 +- .../consistency.receipt.diag | 2 +- .../inclusion.receipt.diag | 2 +- tests/edn.test.ts | 51 -------- tests/verifiers.test.ts | 118 ------------------ 6 files changed, 3 insertions(+), 172 deletions(-) delete mode 100644 tests/verifiers.test.ts diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor index 9483eb900eee2583abd807e7762629a12daa17bd..1052a80a9301bc38257a2054f9803dc0230cc2ba 100644 GIT binary patch delta 71 zcmV-N0J#6B0jB|wXF$AHVVFG)_!VX3@6_48v{M$J3vbjO{>_+PWc7dlyg;My@!kD9 dHS(?Eb@c**Q43AL1|+cbsug!%N{O&r;lDJ7CD8x? delta 71 zcmV-N0J#6B0jB|wXFvfrx}KLo8H&so(guq1gaIoF^u0^#bI%M7Z;Ncn#uwOm)nT&v ddA<>Q`)1n { const output = fs.readFileSync('./tests/__fixtures__/hash-envelope.diag') expect(diag).toBe(output.toString()) }) - -it('cose receipts', async () => { - const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ - key, - type: 'application/cose-key' - }) - const entries = await Promise.all([`💣 test`, `✨ test`, `🔥 test`] - .map((entry) => { - return encoder.encode(entry) - }) - .map((entry) => { - return cose.receipt.leaf(entry) - })) - const signer = await cose.detached - .signer({ - remote: await cose.crypto.key.signer({ - algorithm: 'ES256', - key: k, - }) - }) - const inclusion = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], - [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] - ]), - entry: 1, - entries, - signer - }) - fs.writeFileSync('./tests/__fixtures__/inclusion.receipt.cbor', inclusion) - const input = fs.readFileSync('./tests/__fixtures__/inclusion.receipt.cbor') - const diag = await cose.cbor.diag(input, "application/cose") - fs.writeFileSync('./tests/__fixtures__/inclusion.receipt.diag', diag) - - entries.push(await cose.receipt.leaf(encoder.encode('✨ new entry ✨'))) - // ask the transparency service for the latest root, and a consistency proof - // based on a previous receipt - const { root, receipt } = await cose.receipt.consistency.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.alg, cose.algorithm.es256], - [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256] - ]), - receipt: inclusion, - entries, - signer - }) - fs.writeFileSync('./tests/__fixtures__/consistency.receipt.cbor', receipt) - const diag2 = await cose.cbor.diag(receipt, "application/cose") - fs.writeFileSync('./tests/__fixtures__/consistency.receipt.diag', diag2) -}) - diff --git a/tests/verifiers.test.ts b/tests/verifiers.test.ts deleted file mode 100644 index aff9239..0000000 --- a/tests/verifiers.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -import fs from 'fs' - -import * as cose from '../src' - -import { JWK } from 'jose' -import { web_key_type } from '../src/iana/assignments/jose' - -it('verify multiple receipts', async () => { - - const issuerSecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ - type: "application/cose-key", - algorithm: "ES256" - }) - const notary1SecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ - type: "application/cose-key", - algorithm: "ES256" - }) - const notary2SecretKey = await cose.crypto.key.generate<'ES256', 'application/cose-key'>({ - type: "application/cose-key", - algorithm: "ES256" - }) - - const issuerSigner = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(issuerSecretKey) - }) - - }) - const notary1Signer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(notary1SecretKey) - }) - - }) - const notary2Signer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: await cose.crypto.key.cose_key_to_web_key(notary2SecretKey) - }) - }) - const issuerCkt = await cose.crypto.key.cose_key_thumbprint_uri(issuerSecretKey) - const notary1Ckt = await cose.crypto.key.cose_key_thumbprint_uri(notary1SecretKey) - const notary2Ckt = await cose.crypto.key.cose_key_thumbprint_uri(notary2SecretKey) - - const content = fs.readFileSync('./examples/image.png') - const signatureForImage = await issuerSigner.sign({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.kid, issuerCkt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.header.content_type, "image/png"], // content_type image/png - ]), - payload: content - }) - const transparencyLogContainingImageSignatures = [await cose.receipt.leaf(signatureForImage)] - // inclusion proof receipt for image signature - const receiptForImageSignature1 = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.kid, notary1Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 - ]), - entry: 0, - entries: transparencyLogContainingImageSignatures, - signer: notary1Signer - }) - const receiptForImageSignature2 = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.header.kid, notary2Ckt], // kid urn:ietf:params:oauth:ckt:sha-256:T6ixLT_utMNJ... - [cose.header.alg, cose.algorithm.es256], // alg ES256 - [cose.draft_headers.verifiable_data_structure, cose.VerifiableDataStructures['RFC9162-Binary-Merkle-Tree']] // vds RFC9162 - ]), - entry: 0, - entries: transparencyLogContainingImageSignatures, - signer: notary2Signer - }) - const transparentSignature1 = await cose.receipt.add(signatureForImage, receiptForImageSignature1) - const transparentSignature = await cose.receipt.add(transparentSignature1, receiptForImageSignature2) - const resolve = async (coseSign1: ArrayBuffer): Promise => { - const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) - if (tag !== cose.tag.COSE_Sign1) { - throw new Error('Only tagged cose sign 1 are supported') - } - const [protectedHeaderBytes] = value; - const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes) - const kid = protectedHeaderMap.get(cose.header.kid); - if (kid === issuerCkt) { - return cose.crypto.key.cose_key_to_web_key( - await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ - key: issuerSecretKey, - type: 'application/cose-key' - }) - ) - } - if (kid === notary1Ckt) { - return cose.crypto.key.cose_key_to_web_key( - await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ - key: notary1SecretKey, - type: 'application/cose-key' - }) - ) - } - if (kid === notary2Ckt) { - return cose.crypto.key.cose_key_to_web_key( - await cose.crypto.key.public_from_private<'ES256', 'application/cose-key'>({ - key: notary2SecretKey, - type: 'application/cose-key' - }) - ) - } - throw new Error('No verification key found in trust store.') - } - const verifier = await cose.receipt.verifier({ - resolve - }) - const verified = await verifier.verify({ coseSign1: transparentSignature, payload: content }) - expect(verified.payload).toBeDefined() - expect(verified.receipts.length).toBe(2) -}) - From 2ea4c7011a8f963d1f4696c5d5cd843c2578a2a2 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Tue, 1 Oct 2024 17:32:15 -0500 Subject: [PATCH 47/67] wip --- scripts/make-iana-assignments.ts | 4 - src/desugar.ts | 7 +- .../scitt-integration.test.ts | 40 +++++ .../test_utils.ts | 159 ++++++++++++++++++ 4 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 tests/draft-ietf-scitt-architecture/scitt-integration.test.ts create mode 100644 tests/draft-ietf-scitt-architecture/test_utils.ts diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index 47d215b..d66ceb6 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -515,10 +515,6 @@ ${tags} } -// type WebKeyParameter = { - -// } - const getJose = async () => { const rows = await getRows<{ 'Parameter Name': string, diff --git a/src/desugar.ts b/src/desugar.ts index 3f3fb7d..b194354 100644 --- a/src/desugar.ts +++ b/src/desugar.ts @@ -13,8 +13,11 @@ export const UnprotectedHeader = (entries: HeaderMapEntry[]) => { return new Map(entries) } - - export const VerifiableDataStructureProofs = (entries: [number, any][]) => { return new Map(entries) } + +export const CWTClaims = (entries: [number, any][]) => { + return new Map(entries) +} + diff --git a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts new file mode 100644 index 0000000..459bf48 --- /dev/null +++ b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts @@ -0,0 +1,40 @@ + +import fs from 'fs' + +import * as cose from '../../src'; + +import { create_software_producer, create_transparency_service } from './test_utils' + +it('', async () => { + + const software_producer = await create_software_producer({ + website: 'https://green.software.vendor.example', + product: 'https://green.software.vendor.example/awesome-cli@v1.2.3' + }) + + const blue_notary = await create_transparency_service({ + notary: 'https://blue.software.vendor.example', + database: './tests/draft-ietf-scitt-architecture/blue.transparency.db' + }) + + const signed_statement = await software_producer.signer.sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.kid, software_producer.kid], + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256], + [cose.draft_headers.payload_preimage_content_type, 'application/spdx+json'], + [cose.draft_headers.payload_location, 'https://cloud.example/sbom/42'], + [cose.header.cwt_claims, cose.CWTClaims([ + [1, software_producer.website], // iss + [2, software_producer.product] // sub + ])] + ]), + payload: fs.readFileSync('./package.json') + }) + + const receipt = await blue_notary.register_signed_statement(signed_statement) + + + // console.log(receipt) + +}) \ No newline at end of file diff --git a/tests/draft-ietf-scitt-architecture/test_utils.ts b/tests/draft-ietf-scitt-architecture/test_utils.ts new file mode 100644 index 0000000..2224e4e --- /dev/null +++ b/tests/draft-ietf-scitt-architecture/test_utils.ts @@ -0,0 +1,159 @@ +import crypto from 'crypto' +import sqlite from 'better-sqlite3' +import * as cose from '../../src' +type create_transparency_params = { + notary: string + database: string +} + +const hash_size = 32 +const tile_height = 2 + +export const create_software_producer = async ({ website, product }: { website: string, product: string }) => { + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) + const publicKeyJwk = cose.public_from_private({ + key: privateKeyJwk, + type: "application/jwk+json" + }) + const signer = cose.hash.signer({ + remote: cose.crypto.signer({ + privateKeyJwk + }) + }) + const verifier = cose.detached + .verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk + } + } + }) + + + return { website, product, signer, verifier, kid: publicKeyJwk.kid } +} + +export const create_transparency_service = async ({ notary, database }: create_transparency_params) => { + const db = new sqlite(database); + db.prepare(` + CREATE TABLE IF NOT EXISTS tiles + (id TEXT PRIMARY KEY, data BLOB); + `).run() + + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ + type: "application/jwk+json", + algorithm: "ES256" + }) + + const publicKeyJwk = cose.public_from_private({ + key: privateKeyJwk, + type: "application/jwk+json" + }) + + const signer = cose.detached.signer({ + remote: cose.crypto.signer({ + privateKeyJwk + }) + }) + + const verifier = cose.detached + .verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk + } + } + }) + + const log = new cose.TileLog({ + tile_height, + hash_size, + hash_function: (data: Uint8Array) => { + return new Uint8Array(crypto.createHash('sha256').update(data).digest()); + }, + read_tile: (tile: string): Uint8Array => { + const [base_tile] = tile.split('.') + // look for completed tiles first + for (let i = 4; i > 0; i--) { + const tile_path = base_tile + '.' + i + const rows = db.prepare(` + SELECT * FROM tiles + WHERE id = '${tile_path}' + `).all(); + if (rows.length) { + const [row] = rows as { id: string, data: Uint8Array }[] + return row.data + } + } + return new Uint8Array(32) + }, + update_tiles: function (tile_path: string, start: number, end: number, stored_hash: Uint8Array) { + if (end - start !== 32) { + // this hash was an intermediate of the tile + // so it will never be persisted + return null + } + let tile_data = this.read_tile(tile_path) + if (tile_data.length < end) { + const expanded_tile_data = new Uint8Array(tile_data.length + 32) + expanded_tile_data.set(tile_data) + tile_data = expanded_tile_data + } + tile_data.set(stored_hash, start) + try { + db.prepare(` + INSERT INTO tiles (id, data) + VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString('hex')}'); + `).run() + } catch (e) { + // ignore errors + } + return tile_data + } + }) + + const register_signed_statement = async (signed_statement: Uint8Array) => { + const root = log.root() + const index = log.size() + log.write_record(signed_statement) + + const decoded = cose.cbor.decode(signed_statement) + const signed_statement_header = cose.cbor.decode(decoded.value[0]) + const signed_statement_claims = signed_statement_header.get(cose.header.cwt_claims) + // console.log(signed_statement_header) + const inclusion_proof = log.inclusion_proof(index + 1, index) + return signer.sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.kid, publicKeyJwk.kid], + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256], + [cose.header.cwt_claims, cose.CWTClaims([ + // TODO: IANA registry for CWT Claims with types. + [1, notary], // issuer notary + // receipt subject is statement subject. + // ... could be receipts have different subject id + [2, signed_statement_claims.get(2)] + ])] + ]), + unprotectedHeader: cose.UnprotectedHeader([ + [cose.draft_headers.verifiable_data_proofs, cose.VerifiableDataStructureProofs([ + [cose.rfc9162_sha256_proof_types.inclusion, [inclusion_proof]], + ])] + ]), + payload: root + }) + } + + return { + notary, + db, + signer, + verifier, + log, + register_signed_statement + } + +} \ No newline at end of file From 0e2ead0efc0e7136aaf595e6e764b64706133ea2 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 09:25:24 -0500 Subject: [PATCH 48/67] wip --- src/cbor/pretty/prettyCoseSign1.ts | 1 + src/cbor/pretty/prettyCwtClaims.ts | 23 +++++++ src/cbor/pretty/prettyHeader.ts | 25 ++++++- src/cbor/pretty/prettyProofs.ts | 12 ++-- src/cbor/pretty/prettyReceipts.ts | 10 +++ .../add_receipt.ts | 19 ++++++ .../index.ts | 1 + .../scitt-integration.test.ts | 26 ++++--- .../test_utils.ts | 8 +-- .../transparent_signed_statement.diag | 67 +++++++++++++++++++ 10 files changed, 170 insertions(+), 22 deletions(-) create mode 100644 src/cbor/pretty/prettyCwtClaims.ts create mode 100644 src/cbor/pretty/prettyReceipts.ts create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/add_receipt.ts create mode 100644 tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag diff --git a/src/cbor/pretty/prettyCoseSign1.ts b/src/cbor/pretty/prettyCoseSign1.ts index 710ef00..c0b294c 100644 --- a/src/cbor/pretty/prettyCoseSign1.ts +++ b/src/cbor/pretty/prettyCoseSign1.ts @@ -22,5 +22,6 @@ ${prettyHeader(decodedUnprotected)} / signature / ${ellideBytes(signature)} ]) ` + } diff --git a/src/cbor/pretty/prettyCwtClaims.ts b/src/cbor/pretty/prettyCwtClaims.ts new file mode 100644 index 0000000..803ebcd --- /dev/null +++ b/src/cbor/pretty/prettyCwtClaims.ts @@ -0,0 +1,23 @@ +import { indentBlock } from "./indentBlock" +export const prettyCwtClaims = (claims: Map) => { + if (!(claims instanceof Map)) { + return '' + } + let result = `` + for (const [label, value] of claims.entries()) { + switch (label) { + case 1: { + result += indentBlock(`/ issuer / ${label} : "${value}",`, ' ') + '\n' + break + } + case 2: { + result += indentBlock(`/ subject / ${label} : "${value}",`, ' ') + '\n' + break + } + default: { + result += indentBlock(`${label}: ${value},`, ' ') + '\n' + } + } + } + return result +} \ No newline at end of file diff --git a/src/cbor/pretty/prettyHeader.ts b/src/cbor/pretty/prettyHeader.ts index fc768ed..455e4d1 100644 --- a/src/cbor/pretty/prettyHeader.ts +++ b/src/cbor/pretty/prettyHeader.ts @@ -7,6 +7,10 @@ import { draft_headers } from "../../iana/requested/cose" import { transparency } from "../../drafts/draft-ietf-cose-merkle-tree-proofs" import { prettyProofs } from "./prettyProofs" +import { ellideBytes } from "./ellideBytes" + +import { prettyCwtClaims } from "./prettyCwtClaims" +import { prettyReceipts } from "./prettyReceipts" export const prettyHeader = (map: Map | object) => { if (!(map instanceof Map)) { @@ -15,10 +19,21 @@ export const prettyHeader = (map: Map | object) => { let result = `` for (const [label, value] of map.entries()) { switch (label) { + case header.kid: { + const renered_key_id = typeof value === 'string' ? `"${value}"` : ellideBytes(value) + result += indentBlock(`/ key / ${label} : ${renered_key_id},`, ' ') + '\n' + break + } case header.alg: { result += indentBlock(`/ algorithm / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + '\n' break } + case header.cwt_claims: { + result += indentBlock(`/ claims / ${label} : {`, ' ') + '\n' + result += indentBlock(prettyCwtClaims(value), ' ') + result += `\n },\n` + break + } case draft_headers.payload_hash_algorithm: { result += indentBlock(`/ hash / ${label} : ${value}, # ${labels_to_algorithms.get(value)}`, ' ') + '\n' break @@ -32,7 +47,7 @@ export const prettyHeader = (map: Map | object) => { break } case draft_headers.verifiable_data_structure: { - result += indentBlock(`/ notarized / ${label} : ${value}, # ${transparency.get(value)}`, ' ') + '\n' + result += indentBlock(`/ notary / ${label} : ${value}, # ${transparency.get(value)}`, ' ') + '\n' break } case draft_headers.verifiable_data_proofs: { @@ -43,6 +58,14 @@ export const prettyHeader = (map: Map | object) => { result += proofs break } + case draft_headers.receipts: { + let receipts = '' + receipts += ` / receipts / ${label} : {\n` + receipts += indentBlock(prettyReceipts(value), ' ') + receipts += `\n },\n` + result += receipts + break + } default: { result += indentBlock(`${label}: ${value},`, ' ') + '\n' } diff --git a/src/cbor/pretty/prettyProofs.ts b/src/cbor/pretty/prettyProofs.ts index 36329ba..406d3fb 100644 --- a/src/cbor/pretty/prettyProofs.ts +++ b/src/cbor/pretty/prettyProofs.ts @@ -7,24 +7,24 @@ import { ellideBytes } from './ellideBytes' import { rfc9162_sha256_proof_types, transparency } from '../../drafts/draft-ietf-cose-merkle-tree-proofs' import { indentBlock } from './indentBlock' -export const prettyInclusionProof = (bytes: ArrayBuffer) => { - const [size, index, path] = cbor.decode(bytes) +export const prettyInclusionProof = (proof: ArrayBuffer | [number, number, Buffer[]]) => { + const [size, index, path] = Array.isArray(proof) ? proof : cbor.decode(proof) return indentBlock(`<<[ / size / ${size}, / leaf / ${index}, / inclusion path / -${path.map((p: ArrayBuffer) => { +${path.length === 0 ? ' []' : path.map((p: ArrayBuffer) => { return ' ' + ellideBytes(p) }).join(',\n')} ]>>`, ' ') } -export const prettyConsistencyProof = (bytes: ArrayBuffer) => { - const [size1, size2, path] = cbor.decode(bytes) +export const prettyConsistencyProof = (proof: ArrayBuffer | [number, number, Buffer[]]) => { + const [size1, size2, path] = Array.isArray(proof) ? proof : cbor.decode(proof) return indentBlock(`<<[ / old / ${size1}, / new / ${size2}, / consistency path / -${path.map((p: ArrayBuffer) => { +${path.length === 0 ? ' []' : path.map((p: ArrayBuffer) => { return ' ' + ellideBytes(p) }).join(',\n')} ]>>`, ' ') diff --git a/src/cbor/pretty/prettyReceipts.ts b/src/cbor/pretty/prettyReceipts.ts new file mode 100644 index 0000000..072a147 --- /dev/null +++ b/src/cbor/pretty/prettyReceipts.ts @@ -0,0 +1,10 @@ + +import { prettyCoseSign1 } from "./prettyCoseSign1" + +export const prettyReceipts = (receipts: Buffer[]) => { + + return receipts.map((r: any) => { + return `<<${prettyCoseSign1(Buffer.from(r)).trim()}>>` + }).join(',\n') + +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/add_receipt.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/add_receipt.ts new file mode 100644 index 0000000..c4db869 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/add_receipt.ts @@ -0,0 +1,19 @@ +import * as cbor from '../../cbor' +import * as cose from '../../../src' + +export const add_receipt = async (signed_statement: Uint8Array, receipt: Uint8Array) => { + const { value } = cbor.decode(signed_statement) + if (value[1] === undefined) { + value[1] = new Map() + } + if (value[1].get(cose.draft_headers.receipts) === undefined) { + value[1].set(cose.draft_headers.receipts, []) + } + const current_receipts = value[1].get(cose.draft_headers.receipts) + // current_receipts = current_receipts.map((r: any) => { + // return cbor.toArrayBuffer(cbor.encode(r)) + // }) + current_receipts.push(cbor.toArrayBuffer(receipt)) + // value[1].set(cose.draft_headers.receipts, current_receipts) + return new Uint8Array(await cbor.encodeAsync(new cbor.Tagged(cose.tag.COSE_Sign1, value), { canonical: true })); +} \ No newline at end of file diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts index 16f3a31..afae802 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -5,6 +5,7 @@ export * from './decode_inclusion_proof' export * from './encode_consistency_proof' export * from './decode_consistency_proof' +export * from './add_receipt' export const verifiable_data_structures = { rfc9162_sha256: 1 diff --git a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts index 459bf48..0e96f39 100644 --- a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts +++ b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts @@ -2,21 +2,25 @@ import fs from 'fs' import * as cose from '../../src'; - import { create_software_producer, create_transparency_service } from './test_utils' -it('', async () => { +it('integration test', async () => { const software_producer = await create_software_producer({ - website: 'https://green.software.vendor.example', - product: 'https://green.software.vendor.example/awesome-cli@v1.2.3' + website: 'https://green.example', + product: 'https://green.example/cli@v1.2.3' }) const blue_notary = await create_transparency_service({ - notary: 'https://blue.software.vendor.example', + website: 'https://blue.example', database: './tests/draft-ietf-scitt-architecture/blue.transparency.db' }) + const orange_notary = await create_transparency_service({ + website: 'https://orange.example', + database: './tests/draft-ietf-scitt-architecture/orange.transparency.db' + }) + const signed_statement = await software_producer.signer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.kid, software_producer.kid], @@ -29,12 +33,12 @@ it('', async () => { [2, software_producer.product] // sub ])] ]), - payload: fs.readFileSync('./package.json') + payload: Buffer.from('large file that never moves over a network') }) - const receipt = await blue_notary.register_signed_statement(signed_statement) - - - // console.log(receipt) - + const blue_receipt = await blue_notary.register_signed_statement(signed_statement) + const transparent_statement = await cose.add_receipt(signed_statement, blue_receipt) + const orange_receipt = await orange_notary.register_signed_statement(transparent_statement) + const signed_statement_with_multiple_receipts = await cose.add_receipt(transparent_statement, orange_receipt) + fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag', await cose.cbor.diag(signed_statement_with_multiple_receipts, "application/cose")) }) \ No newline at end of file diff --git a/tests/draft-ietf-scitt-architecture/test_utils.ts b/tests/draft-ietf-scitt-architecture/test_utils.ts index 2224e4e..8129bfa 100644 --- a/tests/draft-ietf-scitt-architecture/test_utils.ts +++ b/tests/draft-ietf-scitt-architecture/test_utils.ts @@ -2,7 +2,7 @@ import crypto from 'crypto' import sqlite from 'better-sqlite3' import * as cose from '../../src' type create_transparency_params = { - notary: string + website: string database: string } @@ -36,7 +36,7 @@ export const create_software_producer = async ({ website, product }: { website: return { website, product, signer, verifier, kid: publicKeyJwk.kid } } -export const create_transparency_service = async ({ notary, database }: create_transparency_params) => { +export const create_transparency_service = async ({ website, database }: create_transparency_params) => { const db = new sqlite(database); db.prepare(` CREATE TABLE IF NOT EXISTS tiles @@ -132,7 +132,7 @@ export const create_transparency_service = async ({ notary, database }: create_t [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256], [cose.header.cwt_claims, cose.CWTClaims([ // TODO: IANA registry for CWT Claims with types. - [1, notary], // issuer notary + [1, website], // issuer notary // receipt subject is statement subject. // ... could be receipts have different subject id [2, signed_statement_claims.get(2)] @@ -148,7 +148,7 @@ export const create_transparency_service = async ({ notary, database }: create_t } return { - notary, + website, db, signer, verifier, diff --git a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag new file mode 100644 index 0000000..de2adfe --- /dev/null +++ b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag @@ -0,0 +1,67 @@ +/ cose-sign1 / 18([ + / protected / <<{ + / key / 4 : "1n4tRO6u2tP94w4AZP0jlHgLe8vTwV8T7dvQrRJh8xc", + / algorithm / 1 : -7, # ES256 + / hash / -6800 : -16, # SHA-256 + / content / -6802 : "application/spdx+json", + / location / -6801 : "https://cloud.example/sbom/42", + / claims / 15 : { + / issuer / 1 : "https://green.example", + / subject / 2 : "https://green.example/cli@v1.2.3", + }, + }>>, + / unprotected / { + / receipts / 394 : { + <>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 1, / leaf / 0, + / inclusion path / + [] + ]>> + ], + }, + }, + / payload / null, + / signature / h'1086da6a...8b76135a' + ])>>, + <>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 1, / leaf / 0, + / inclusion path / + [] + ]>> + ], + }, + }, + / payload / null, + / signature / h'f63c7a4d...f0e7186b' + ])>> + }, + }, + / payload / h'0167c57c...deeed6d4', + / signature / h'e53796bb...9fe6369f' +]) From a3d843c1f09f543502d19b4e667d008f68595832 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 11:14:44 -0500 Subject: [PATCH 49/67] fixes for storing metadata --- .../log/Tile.ts | 43 ++++- .../consistency.receipt.diag | 12 +- .../inclusion.receipt.diag | 4 +- .../test_log.ts | 168 ++++++++++++------ .../tiled_log.test.ts | 3 +- .../test_utils.ts | 62 +------ .../transparent_signed_statement.diag | 20 +-- 7 files changed, 177 insertions(+), 135 deletions(-) diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts index 8723ecd..b760622 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/log/Tile.ts @@ -35,9 +35,16 @@ export type ConsistencyProof = [ export type TileLogParameters = { tile_height: number hash_size: number - hash_function: (bytes: Uint8Array) => Uint8Array + read_tree_size: () => number, + update_tree_size: (new_tree_size: number) => void + + read_tree_root: () => Uint8Array | null, + update_tree_root: (new_root: Uint8Array) => void + read_tile: (tile: string) => Uint8Array update_tiles: (tile_path: string, start: number, end: number, stored_hash: Uint8Array) => Uint8Array | null + + hash_function: (bytes: Uint8Array) => Uint8Array } @@ -658,8 +665,16 @@ export class TileHashReader implements HashReader { export class TileLog implements TileStorage, HashReader { public tree_hasher: TreeHash public thr: TileHashReader - public tree_size = 0 + public tree_root: Uint8Array + + public read_tree_size + public update_tree_size + + public read_tree_root + public update_tree_root + + public read_tile public update_tiles public tile_height: number @@ -669,9 +684,20 @@ export class TileLog implements TileStorage, HashReader { this.tile_height = config.tile_height this.tree_hasher = new TreeHash(config.hash_function, config.hash_size) this.tree_root = this.tree_hasher.empty_root() - this.thr = new TileHashReader(this.tree_size, this.tree_root, this, this.tree_hasher,) + + this.read_tree_size = config.read_tree_size + this.update_tree_size = config.update_tree_size + + this.read_tree_root = config.read_tree_root + this.update_tree_root = config.update_tree_root + + this.read_tile = config.read_tile this.update_tiles = config.update_tiles + + + + this.thr = new TileHashReader(this.read_tree_size(), this.tree_root, this, this.tree_hasher,) } record_hash(data: Uint8Array) { return this.tree_hasher.hash_leaf(data) @@ -727,13 +753,16 @@ export class TileLog implements TileStorage, HashReader { }) } size() { - return this.tree_size + return this.read_tree_size() } root() { - return this.root_at(this.tree_size) + return this.root_at(this.size()) } root_at(tree_size: number) { - return tree_hash(this.tree_hasher, tree_size, this) + if (tree_size === 0) { + return new Uint8Array(this.tree_hasher.empty_root()) + } + return new Uint8Array(tree_hash(this.tree_hasher, tree_size, this)) } write_record_hashes = (record_hashes: Uint8Array[]) => { for (const record_hash of record_hashes) { @@ -753,7 +782,7 @@ export class TileLog implements TileStorage, HashReader { } storage_id++ } - this.tree_size++; + this.update_tree_size(record_index + 1) } } write_record = (record: Uint8Array) => { diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag b/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag index ab06a71..db4fd35 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag +++ b/tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag @@ -1,22 +1,24 @@ / cose-sign1 / 18([ / protected / <<{ / algorithm / 1 : -7, # ES256 - / notarized / 395 : 1, # RFC9162 SHA-256 + / notary / 395 : 1, # RFC9162 SHA-256 }>>, / unprotected / { / proofs / 396 : { / consistency / -2 : [ <<[ - / old / 20, / new / 26, + / old / 20, / new / 104, / consistency path / h'e5b3e764...c4a813bc', h'87e8a084...4f529f69', - h'5f9bdd41...45838ee8', - h'd68af9d6...93b1632b' + h'f712f76d...92a0ff36', + h'd68af9d6...93b1632b', + h'249efab6...b7614ccd', + h'85dd6293...38914dc1' ]>> ], }, }, / payload / null, - / signature / h'02148764...61a50b84' + / signature / h'94469f73...52de67a1' ]) diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag b/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag index a3fc66f..739a1f7 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag +++ b/tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag @@ -1,7 +1,7 @@ / cose-sign1 / 18([ / protected / <<{ / algorithm / 1 : -7, # ES256 - / notarized / 395 : 1, # RFC9162 SHA-256 + / notary / 395 : 1, # RFC9162 SHA-256 }>>, / unprotected / { / proofs / 396 : { @@ -17,5 +17,5 @@ }, }, / payload / null, - / signature / h'40da9c62...4f182621' + / signature / h'de24f0cc...9a5ade89' ]) diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts b/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts index 3ea5283..58403f4 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts +++ b/tests/draft-ietf-cose-merkle-tree-proofs/test_log.ts @@ -3,58 +3,118 @@ import crypto from 'crypto' import sqlite from 'better-sqlite3' import * as cose from '../../src'; -const db = new sqlite("./tests/draft-ietf-cose-merkle-tree-proofs/transparency.db"); - -db.prepare(` -CREATE TABLE IF NOT EXISTS tiles -(id TEXT PRIMARY KEY, data BLOB); - `).run() -const hash_size = 32 -const tile_height = 2 - -export const log = new cose.TileLog({ - tile_height, - hash_size, - hash_function: (data: Uint8Array) => { - return new Uint8Array(crypto.createHash('sha256').update(data).digest()); - }, - read_tile: (tile: string): Uint8Array => { - const [base_tile] = tile.split('.') - // look for completed tiles first - for (let i = 4; i > 0; i--) { - const tile_path = base_tile + '.' + i +export const create_sqlite_log = (database: string) => { + + const db = new sqlite(database); + + db.prepare(` + CREATE TABLE IF NOT EXISTS tiles + (id TEXT PRIMARY KEY, data BLOB); + + `).run() + + db.prepare(` + CREATE TABLE IF NOT EXISTS kv + (key text unique, value text); + `).run() + + const hash_size = 32 + const tile_height = 2 + + const log = new cose.TileLog({ + tile_height, + hash_size, + read_tree_size: () => { + const rows = db.prepare(` + SELECT * FROM kv + WHERE key = 'tree_size' + `).all(); + const [row] = rows as { key: string, value: string }[] + try { + return parseInt(row.value, 10) + } catch (e) { + // console.error(e) + return 0 + } + }, + update_tree_size: (new_tree_size: number) => { + try { + db.prepare(` + INSERT OR REPLACE INTO kv (key, value) + VALUES( 'tree_size', '${new_tree_size}'); + `).run() + } catch (e) { + // console.error(e) + // ignore errors + } + }, + + read_tree_root: function () { const rows = db.prepare(` - SELECT * FROM tiles - WHERE id = '${tile_path}' - `).all(); - if (rows.length) { - const [row] = rows as { id: string, data: Uint8Array }[] - return row.data - } - } - return new Uint8Array(32) - }, - update_tiles: function (tile_path: string, start: number, end: number, stored_hash: Uint8Array) { - if (end - start !== 32) { - // this hash was an intermediate of the tile - // so it will never be persisted - return null - } - let tile_data = this.read_tile(tile_path) - if (tile_data.length < end) { - const expanded_tile_data = new Uint8Array(tile_data.length + 32) - expanded_tile_data.set(tile_data) - tile_data = expanded_tile_data - } - tile_data.set(stored_hash, start) - try { - db.prepare(` - INSERT INTO tiles (id, data) - VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString('hex')}'); - `).run() - } catch (e) { - // ignore errors - } - return tile_data - } -}) \ No newline at end of file + SELECT * FROM kv + WHERE key = 'tree_root' + `).all(); + const [row] = rows as { key: string, value: string }[] + try { + return new Uint8Array(Buffer.from(row.value, 'hex')) + } catch (e) { + return null + } + }, + update_tree_root: (new_tree_root: Uint8Array): void => { + try { + db.prepare(` + INSERT OR REPLACE INTO kv (key, value) + VALUES( 'tree_root', '${Buffer.from(new_tree_root).toString('hex')}'); + `).run() + } catch (e) { + // ignore errors + } + }, + + read_tile: (tile: string): Uint8Array => { + const [base_tile] = tile.split('.') + // look for completed tiles first + for (let i = 4; i > 0; i--) { + const tile_path = base_tile + '.' + i + const rows = db.prepare(` + SELECT * FROM tiles + WHERE id = '${tile_path}' + `).all(); + if (rows.length) { + const [row] = rows as { id: string, data: Uint8Array }[] + return row.data + } + } + return new Uint8Array(32) + }, + update_tiles: function (tile_path: string, start: number, end: number, stored_hash: Uint8Array) { + if (end - start !== 32) { + // this hash was an intermediate of the tile + // so it will never be persisted + return null + } + let tile_data = this.read_tile(tile_path) + if (tile_data.length < end) { + const expanded_tile_data = new Uint8Array(tile_data.length + 32) + expanded_tile_data.set(tile_data) + tile_data = expanded_tile_data + } + tile_data.set(stored_hash, start) + try { + db.prepare(` + INSERT INTO tiles (id, data) + VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString('hex')}'); + `).run() + } catch (e) { + // ignore errors + } + return tile_data + }, + hash_function: (data: Uint8Array) => { + return new Uint8Array(crypto.createHash('sha256').update(data).digest()); + }, + }) + + return { db, log } +} diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts index b7cb865..444ecd2 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts +++ b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts @@ -1,7 +1,8 @@ import fs from 'fs' import * as cose from '../../src'; +import { create_sqlite_log } from './test_log'; -import { log } from './test_log'; +const { log } = create_sqlite_log("./tests/draft-ietf-cose-merkle-tree-proofs/transparency.db") it("cose receipts from a tiled transparency log", async () => { const encoder = new TextEncoder() diff --git a/tests/draft-ietf-scitt-architecture/test_utils.ts b/tests/draft-ietf-scitt-architecture/test_utils.ts index 8129bfa..1c6eb6a 100644 --- a/tests/draft-ietf-scitt-architecture/test_utils.ts +++ b/tests/draft-ietf-scitt-architecture/test_utils.ts @@ -1,14 +1,13 @@ -import crypto from 'crypto' -import sqlite from 'better-sqlite3' + import * as cose from '../../src' + +import { create_sqlite_log } from '../draft-ietf-cose-merkle-tree-proofs/test_log' + type create_transparency_params = { website: string database: string } -const hash_size = 32 -const tile_height = 2 - export const create_software_producer = async ({ website, product }: { website: string, product: string }) => { const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", @@ -37,11 +36,7 @@ export const create_software_producer = async ({ website, product }: { website: } export const create_transparency_service = async ({ website, database }: create_transparency_params) => { - const db = new sqlite(database); - db.prepare(` - CREATE TABLE IF NOT EXISTS tiles - (id TEXT PRIMARY KEY, data BLOB); - `).run() + const privateKeyJwk = await cose.crypto.key.generate<'ES256', 'application/jwk+json'>({ type: "application/jwk+json", @@ -68,52 +63,7 @@ export const create_transparency_service = async ({ website, database }: create_ } }) - const log = new cose.TileLog({ - tile_height, - hash_size, - hash_function: (data: Uint8Array) => { - return new Uint8Array(crypto.createHash('sha256').update(data).digest()); - }, - read_tile: (tile: string): Uint8Array => { - const [base_tile] = tile.split('.') - // look for completed tiles first - for (let i = 4; i > 0; i--) { - const tile_path = base_tile + '.' + i - const rows = db.prepare(` - SELECT * FROM tiles - WHERE id = '${tile_path}' - `).all(); - if (rows.length) { - const [row] = rows as { id: string, data: Uint8Array }[] - return row.data - } - } - return new Uint8Array(32) - }, - update_tiles: function (tile_path: string, start: number, end: number, stored_hash: Uint8Array) { - if (end - start !== 32) { - // this hash was an intermediate of the tile - // so it will never be persisted - return null - } - let tile_data = this.read_tile(tile_path) - if (tile_data.length < end) { - const expanded_tile_data = new Uint8Array(tile_data.length + 32) - expanded_tile_data.set(tile_data) - tile_data = expanded_tile_data - } - tile_data.set(stored_hash, start) - try { - db.prepare(` - INSERT INTO tiles (id, data) - VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString('hex')}'); - `).run() - } catch (e) { - // ignore errors - } - return tile_data - } - }) + const { log, db } = create_sqlite_log(database) const register_signed_statement = async (signed_statement: Uint8Array) => { const root = log.root() diff --git a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag index de2adfe..7da31be 100644 --- a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag +++ b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag @@ -1,6 +1,6 @@ / cose-sign1 / 18([ / protected / <<{ - / key / 4 : "1n4tRO6u2tP94w4AZP0jlHgLe8vTwV8T7dvQrRJh8xc", + / key / 4 : "i2vjNrF0wkROkZbKP03J04CxC2EuvQK09baL1aC-v5c", / algorithm / 1 : -7, # ES256 / hash / -6800 : -16, # SHA-256 / content / -6802 : "application/spdx+json", @@ -14,7 +14,7 @@ / receipts / 394 : { <> ], }, }, / payload / null, - / signature / h'1086da6a...8b76135a' + / signature / h'bcba332f...ae37605e' ])>>, <> ], }, }, / payload / null, - / signature / h'f63c7a4d...f0e7186b' + / signature / h'135a0279...c402c1c1' ])>> }, }, / payload / h'0167c57c...deeed6d4', - / signature / h'e53796bb...9fe6369f' + / signature / h'c741bdbb...f452a0ce' ]) From 9833abfa5ea2fe1ce065700e8e2f9855a96f55a1 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 14:58:32 -0500 Subject: [PATCH 50/67] cleaning --- scripts/data/claims-registry.csv | 71 +++++++++++++++++++ scripts/data/confirmation-methods.csv | 10 +++ scripts/make-iana-assignments.ts | 64 +++++++++++++++++ src/cbor/pretty/prettyCwtClaims.ts | 7 +- src/iana/assignments/cwt.ts | 61 ++++++++++++++++ src/index.ts | 2 + .../scitt-integration.test.ts | 4 +- .../test_utils.ts | 10 +-- 8 files changed, 220 insertions(+), 9 deletions(-) create mode 100644 scripts/data/claims-registry.csv create mode 100644 scripts/data/confirmation-methods.csv create mode 100644 src/iana/assignments/cwt.ts diff --git a/scripts/data/claims-registry.csv b/scripts/data/claims-registry.csv new file mode 100644 index 0000000..40ce37f --- /dev/null +++ b/scripts/data/claims-registry.csv @@ -0,0 +1,71 @@ +Claim Name,Claim Description,JWT Claim Name,Claim Key,Claim Value Type,Change Controller,Reference +Reserved for Private Use,,,less than -65536,,,[RFC8392] +Unassigned,,,-65536 to -262,,, +globalplatform_component,"This claim holds an array of CBOR maps in which each array entry holds a map containing claims +about a GlobalPlatform component that is within the boundary of the enclosing Entity Attestation Token.",N/A,-261,map,[GlobalPlatform_Inc.],"[GlobalPlatform Entity Attestation Protocol Specification, GPP_SPE_001, Section 6.5.]" +hcert,Health Certificate,hcert,-260,map,[European_eHealth_Network],[Electronic Health Certificate Specification] +EUPHNonce,Challenge Nonce,EUPHNonce,-259,bstr,[FIDO_Alliance],[FIDO Device Onboard Specification] +EATMAROEPrefix,Signing prefix for multi-app restricted operating environments,EATMAROEPrefix,-258,bstr,[FIDO_Alliance],[FIDO Device Onboard Specification] +EAT-FDO,EAT-FDO may contain related to FIDO Device Onboarding,EAT-FDO,-257,array,[FIDO_Alliance],[FIDO Device Onboard Specification] +Unassigned,,,-256 to -1,,, +Reserved,This registration reserves the key value 0,,0,,[IESG],[RFC8392] +iss,Issuer,iss,1,text string,[IESG],[RFC8392] +sub,Subject,sub,2,text string,[IESG],[RFC8392] +aud,Audience,aud,3,text string,[IESG],[RFC8392] +exp,Expiration Time,exp,4,integer or floating-point number,[IESG],[RFC8392] +nbf,Not Before,nbf,5,integer or floating-point number,[IESG],[RFC8392] +iat,Issued At,iat,6,integer or floating-point number,[IESG],[RFC8392] +cti,CWT ID,jti,7,byte string,[IESG],[RFC8392] +cnf,Confirmation,cnf,8,map,[IESG],[RFC8747] +scope,"The scope of an access token, as defined in [RFC6749].",scope,9,byte string or text string,[IESG],"[RFC8693, Section 4.2]" +Nonce,Nonce,eat_nonce,10,bstr or array,[IETF],[OpenID Connect Core 1.0][RFC-ietf-rats-eat-30] +Unassigned,,,11 to 37,,, +ace_profile,"The ACE profile a token is supposed to be used + with.",ace_profile,38,integer,[IETF],"[RFC9200, Section 5.10]" +cnonce,"The client-nonce sent to the AS by the RS via + the client.",cnonce,39,byte string,[IETF],"[RFC9200, Section 5.10]" +exi,"The expiration time of a token measured from + when it was received at the RS in seconds.",exi,40,unsigned integer,[IETF],"[RFC9200, Section 5.10.3]" +Unassigned,,,41 to 168,,, +identity-data,"Registering the claim for storing identity data of a person, which could be personally identifiable data (PII) mostly used in Foundational/National ID for cross-border interoperability.",identity-data,169,map,[MOSIP],"[CBOR Identity Data in QR Code, Section 3][CBOR Identity Data in QR Code, Section 4]" +Unassigned,,,170 to 255,,, +UEID,The Universal Entity ID,ueid,256,bstr,[IETF],[RFC-ietf-rats-eat-30] +SUEIDs,Semi-permanent UEIDs,sueids,257,map,[IETF],[RFC-ietf-rats-eat-30] +Hardware OEM ID,Hardware OEM ID,oemid,258,bstr or int,[IETF],[RFC-ietf-rats-eat-30] +Hardware Model,Model identifier for hardware,hwmodel,259,bstr,[IETF],[RFC-ietf-rats-eat-30] +Hardware Version,Hardware Version Identifier,hwversion,260,array,[IETF],[RFC-ietf-rats-eat-30] +Uptime,Uptime,uptime,261,uint,[IETF],[RFC-ietf-rats-eat-30] +OEM Authorized Boot,Indicates whether the software booted was OEM authorized,oemboot,262,bool,[IETF],[RFC-ietf-rats-eat-30] +Debug Status,Indicates status of debug facilities,dbgstat,263,uint,[IETF],[RFC-ietf-rats-eat-30] +Location,The geographic location,location,264,map,[IETF],[RFC-ietf-rats-eat-30] +EAT Profile,Indicates the EAT profile followed,eat_profile,265,uri or oid,[IETF],[RFC-ietf-rats-eat-30] +Submodules Section,The section containing submodules,submods,266,map,[IETF],[RFC-ietf-rats-eat-30] +Boot Count,The number times the entity or submodule has been booted,bootcount,267,uint,[IETF],[RFC-ietf-rats-eat-30] +Boot Seed,Identifies a boot cycle,bootseed,268,bstr,[IETF],[RFC-ietf-rats-eat-30] +DLOAs,Certifications received as Digital Letters of Approval,dloas,269,array,[IETF],[RFC-ietf-rats-eat-30] +Software Name,The name of the software running in the entity,swname,270,tstr,[IETF],[RFC-ietf-rats-eat-30] +Software Version,The version of software running in the entity,swversion,271,array,[IETF],[RFC-ietf-rats-eat-30] +Software Manifests,Manifests describing the software installed on the entity,manifests,272,array,[IETF],[RFC-ietf-rats-eat-30] +Measurements,"Measurements of the software, memory configuration and such on the entity",measurements,273,array,[IETF],[RFC-ietf-rats-eat-30] +Software Measurement Results,The results of comparing software measurements to reference values,measres,274,array,[IETF],[RFC-ietf-rats-eat-30] +Intended Use,Indicates intended use of the EAT,intuse,275,uint,[IETF],[RFC-ietf-rats-eat-30] +Unassigned,,,276 to 281,,, +geohash,Geohash String,geohash,282,text string or array,[Consumer_Technology_Association],[Fast and Readable Geographical Hashing (CTA-5009)] +Unassigned,,,283 to 299,,, +wmver,The version of the WM Token,wmver,300,unsigned integer,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmvnd,The WM technology vendor,wmvnd,301,unsigned integer,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmpatlen,The length in bits of the WM pattern,wmpatlen,302,unsigned integer,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmsegduration,The nominal duration of a segment,wmsegduration,303,map,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmpattern,The WM pattern,wmpattern,304,COSE_Encrypt0 or COSE_Encrypt or byte string,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmid,Used as input to derive the WM pattern for indirect mode,wmid,305,text string,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmopid,Used as additional input to derive the WM pattern for indirect mode,wmopid,306,unsigned integer,[DASH-IF],[ETSI TS 104 002 V1.1.1] +wmkeyver,The key to use for derivation of the WM pattern in indirect mode,wmkeyver,307,unsigned integer,[DASH-IF],[ETSI TS 104 002 V1.1.1] +Unassigned,,,308 to 2393,,, +psa-client-id,PSA Client ID,N/A,2394,signed integer,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-security-lifecycle,PSA Security Lifecycle,N/A,2395,unsigned integer,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-implementation-id,PSA Implementation ID,N/A,2396,byte string,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-boot-seed,PSA Boot Seed,N/A,2397,byte string,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-certification-reference,PSA Certification Reference,N/A,2398,text string,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-software-components,PSA Software Components,N/A,2399,array,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +psa-verification-service-indicator,PSA Verification Service Indicator,N/A,2400,text string,[Hannes_Tschofenig],[draft-tschofenig-rats-psa-token-09] +Unassigned,,,2401 to 65535,,, diff --git a/scripts/data/confirmation-methods.csv b/scripts/data/confirmation-methods.csv new file mode 100644 index 0000000..bee47df --- /dev/null +++ b/scripts/data/confirmation-methods.csv @@ -0,0 +1,10 @@ +Confirmation Method Name,Confirmation Method Description,JWT Confirmation Method Name,Confirmation Key,Confirmation Value Type,Change Controller,Reference +COSE_Key,COSE_Key Representing Public Key,jwk,1,COSE_Key structure,[IESG],"[RFC8747, Section 3.2]" +Encrypted_COSE_Key,Encrypted COSE_Key,jwe,2,"COSE_Encrypt or COSE_Encrypt0 + structure (with an optional corresponding COSE_Encrypt or + COSE_Encrypt0 tag)",[IESG],"[RFC8747, Section 3.3]" +kid,Key Identifier,kid,3,binary string,[IESG],"[RFC8747, Section 3.4]" +osc,"OSCORE_Input_Material carrying + the parameters for using OSCORE per-message security with implicit + key confirmation",osc,4,map,[IETF],"[RFC9203, Section 3.2.1]" +ckt,COSE Key SHA-256 Thumbprint,jkt,5,binary string,[IETF],[RFC-ietf-cose-key-thumbprint-06] diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index d66ceb6..f4d5c4c 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -641,8 +641,72 @@ ${definitions} } + +const getCwt = async () => { + + + const rows = await getRows<{ + 'Claim Name': string, + 'Claim Description': string, + 'JWT Claim Name': string, + 'Claim Key': string, + 'Claim Value Type': string, + 'Change Controller': string, + Reference: string, + }>('./scripts/data/claims-registry.csv') + + let definitions = ` +export const cwt_claims = { +` + + for (const row of rows) { + if (row['Claim Name'].includes('Reserved') || row['Claim Name'].includes('Unassigned')) { + continue + } + if (row['JWT Claim Name'].includes('N/A')) { + continue + } + definitions += ` "${row['JWT Claim Name'].toLowerCase().replace(/-/g, '_')}": ${parseInt(row['Claim Key'])},\n` + } + + definitions += `}\n` + + const rows2 = await getRows<{ + 'Confirmation Method Name': string, + 'Confirmation Method Description': string, + 'JWT Confirmation Method Name': string, + 'Confirmation Key': string, + 'Confirmation Value Type': string, + 'Change Controller': string, + Reference: string, + }>('./scripts/data/confirmation-methods.csv') + + definitions += `export const cwt_confirmation_methods = { +` + for (const row of rows2) { + definitions += ` "${row['JWT Confirmation Method Name'].toLowerCase().replace(/-/g, '_')}": ${parseInt(row['Confirmation Key'])},\n` + } + definitions += `}\n` + return definitions +} + +const makeIanaCwtAssignments = async () => { + const definitions = await getCwt(); + const final = ` +// DO NOT edit this file, it is generated automatically +/** + * @see {@link https://www.iana.org/assignments/cwt} + */ +${definitions} +`.trim() + fs.writeFileSync('./src/iana/assignments/cwt.ts', final) +} + + + (async () => { await makeIanaJoseAssignments() await makeIanaCborAssignments() await makeIanaCoseAssignments() + await makeIanaCwtAssignments() })() \ No newline at end of file diff --git a/src/cbor/pretty/prettyCwtClaims.ts b/src/cbor/pretty/prettyCwtClaims.ts index 803ebcd..71fbb04 100644 --- a/src/cbor/pretty/prettyCwtClaims.ts +++ b/src/cbor/pretty/prettyCwtClaims.ts @@ -1,4 +1,7 @@ import { indentBlock } from "./indentBlock" + +import { cwt_claims } from "../../iana/assignments/cwt" + export const prettyCwtClaims = (claims: Map) => { if (!(claims instanceof Map)) { return '' @@ -6,11 +9,11 @@ export const prettyCwtClaims = (claims: Map) => { let result = `` for (const [label, value] of claims.entries()) { switch (label) { - case 1: { + case cwt_claims.iss: { result += indentBlock(`/ issuer / ${label} : "${value}",`, ' ') + '\n' break } - case 2: { + case cwt_claims.sub: { result += indentBlock(`/ subject / ${label} : "${value}",`, ' ') + '\n' break } diff --git a/src/iana/assignments/cwt.ts b/src/iana/assignments/cwt.ts new file mode 100644 index 0000000..47c8f44 --- /dev/null +++ b/src/iana/assignments/cwt.ts @@ -0,0 +1,61 @@ +// DO NOT edit this file, it is generated automatically +/** + * @see {@link https://www.iana.org/assignments/cwt} + */ + +export const cwt_claims = { + "hcert": -260, + "euphnonce": -259, + "eatmaroeprefix": -258, + "eat_fdo": -257, + "iss": 1, + "sub": 2, + "aud": 3, + "exp": 4, + "nbf": 5, + "iat": 6, + "jti": 7, + "cnf": 8, + "scope": 9, + "eat_nonce": 10, + "ace_profile": 38, + "cnonce": 39, + "exi": 40, + "identity_data": 169, + "ueid": 256, + "sueids": 257, + "oemid": 258, + "hwmodel": 259, + "hwversion": 260, + "uptime": 261, + "oemboot": 262, + "dbgstat": 263, + "location": 264, + "eat_profile": 265, + "submods": 266, + "bootcount": 267, + "bootseed": 268, + "dloas": 269, + "swname": 270, + "swversion": 271, + "manifests": 272, + "measurements": 273, + "measres": 274, + "intuse": 275, + "geohash": 282, + "wmver": 300, + "wmvnd": 301, + "wmpatlen": 302, + "wmsegduration": 303, + "wmpattern": 304, + "wmid": 305, + "wmopid": 306, + "wmkeyver": 307, +} +export const cwt_confirmation_methods = { + "jwk": 1, + "jwe": 2, + "kid": 3, + "osc": 4, + "jkt": 5, +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5b63330..04da6e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,8 @@ export * from './drafts/draft-ietf-jose-fully-specified-algorithms' export * from './iana/assignments/cbor' export * from './iana/assignments/cose' +export * from './iana/assignments/cwt' +export * from './iana/assignments/jose' export * from './iana/requested/cose' export * from './cose' export * from './x509' diff --git a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts index 0e96f39..49b2fba 100644 --- a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts +++ b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts @@ -29,8 +29,8 @@ it('integration test', async () => { [cose.draft_headers.payload_preimage_content_type, 'application/spdx+json'], [cose.draft_headers.payload_location, 'https://cloud.example/sbom/42'], [cose.header.cwt_claims, cose.CWTClaims([ - [1, software_producer.website], // iss - [2, software_producer.product] // sub + [cose.cwt_claims.iss, software_producer.website], + [cose.cwt_claims.sub, software_producer.product] ])] ]), payload: Buffer.from('large file that never moves over a network') diff --git a/tests/draft-ietf-scitt-architecture/test_utils.ts b/tests/draft-ietf-scitt-architecture/test_utils.ts index 1c6eb6a..4a92139 100644 --- a/tests/draft-ietf-scitt-architecture/test_utils.ts +++ b/tests/draft-ietf-scitt-architecture/test_utils.ts @@ -66,14 +66,14 @@ export const create_transparency_service = async ({ website, database }: create_ const { log, db } = create_sqlite_log(database) const register_signed_statement = async (signed_statement: Uint8Array) => { + // registration policy goes here... + // for this test, we accept everything const root = log.root() const index = log.size() log.write_record(signed_statement) - const decoded = cose.cbor.decode(signed_statement) const signed_statement_header = cose.cbor.decode(decoded.value[0]) const signed_statement_claims = signed_statement_header.get(cose.header.cwt_claims) - // console.log(signed_statement_header) const inclusion_proof = log.inclusion_proof(index + 1, index) return signer.sign({ protectedHeader: cose.ProtectedHeader([ @@ -81,11 +81,11 @@ export const create_transparency_service = async ({ website, database }: create_ [cose.header.alg, cose.algorithm.es256], [cose.draft_headers.verifiable_data_structure, cose.verifiable_data_structures.rfc9162_sha256], [cose.header.cwt_claims, cose.CWTClaims([ - // TODO: IANA registry for CWT Claims with types. - [1, website], // issuer notary + + [cose.cwt_claims.iss, website], // issuer notary // receipt subject is statement subject. // ... could be receipts have different subject id - [2, signed_statement_claims.get(2)] + [cose.cwt_claims.sub, signed_statement_claims.get(cose.cwt_claims.sub)] ])] ]), unprotectedHeader: cose.UnprotectedHeader([ From 3c1e23fac894c99f53556d1af5eb976e3d3f3f68 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 15:54:35 -0500 Subject: [PATCH 51/67] add example --- .../index.ts | 1 + .../prepare_for_inclusion.ts | 13 +++++ .../scitt-integration.test.ts | 35 ++++++++++-- .../test_utils.ts | 53 +++++++++++++++++-- .../transparent_signed_statement.diag | 21 ++++---- ...sparent_signed_statement.verification.json | 17 ++++++ 6 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 src/drafts/draft-ietf-cose-merkle-tree-proofs/prepare_for_inclusion.ts create mode 100644 tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts index afae802..0c3c449 100644 --- a/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/index.ts @@ -6,6 +6,7 @@ export * from './encode_consistency_proof' export * from './decode_consistency_proof' export * from './add_receipt' +export * from './prepare_for_inclusion' export const verifiable_data_structures = { rfc9162_sha256: 1 diff --git a/src/drafts/draft-ietf-cose-merkle-tree-proofs/prepare_for_inclusion.ts b/src/drafts/draft-ietf-cose-merkle-tree-proofs/prepare_for_inclusion.ts new file mode 100644 index 0000000..ada3694 --- /dev/null +++ b/src/drafts/draft-ietf-cose-merkle-tree-proofs/prepare_for_inclusion.ts @@ -0,0 +1,13 @@ + +// this function returns a copy of a cose sign1 with no unprotected header +// this enables clients to compute the record hash for any given signed statement + + +import * as cbor from '../../cbor' +import * as cose from '../../../src' + +export const prepare_for_inclusion = async (signed_statement: Uint8Array) => { + const { value } = cbor.decode(signed_statement) + value[1] = new Map() + return new Uint8Array(await cbor.encodeAsync(new cbor.Tagged(cose.tag.COSE_Sign1, value), { canonical: true })); +} \ No newline at end of file diff --git a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts index 49b2fba..8db37ae 100644 --- a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts +++ b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts @@ -2,7 +2,7 @@ import fs from 'fs' import * as cose from '../../src'; -import { create_software_producer, create_transparency_service } from './test_utils' +import { create_software_producer, create_transparency_service, verify_transparent_statement } from './test_utils' it('integration test', async () => { @@ -21,9 +21,11 @@ it('integration test', async () => { database: './tests/draft-ietf-scitt-architecture/orange.transparency.db' }) + const statement = Buffer.from('large file that never moves over a network') + const signed_statement = await software_producer.signer.sign({ protectedHeader: cose.ProtectedHeader([ - [cose.header.kid, software_producer.kid], + [cose.header.kid, software_producer.public_key.kid], [cose.header.alg, cose.algorithm.es256], [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256], [cose.draft_headers.payload_preimage_content_type, 'application/spdx+json'], @@ -33,7 +35,7 @@ it('integration test', async () => { [cose.cwt_claims.sub, software_producer.product] ])] ]), - payload: Buffer.from('large file that never moves over a network') + payload: statement }) const blue_receipt = await blue_notary.register_signed_statement(signed_statement) @@ -41,4 +43,31 @@ it('integration test', async () => { const orange_receipt = await orange_notary.register_signed_statement(transparent_statement) const signed_statement_with_multiple_receipts = await cose.add_receipt(transparent_statement, orange_receipt) fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag', await cose.cbor.diag(signed_statement_with_multiple_receipts, "application/cose")) + + const statement_hash = new Uint8Array(await (await cose.crypto.subtle()).digest("SHA-256", statement)) + const verification = await verify_transparent_statement(statement_hash, signed_statement_with_multiple_receipts, { + tree_hasher: blue_notary.log.tree_hasher, // both logs use same tree algorithm + resolver: { + resolve: async (token: Buffer) => { + const decoded = cose.cbor.decode(token) + const header = cose.cbor.decode(decoded.value[0]) + const kid = header.get(cose.header.kid) + switch (kid) { + case software_producer.public_key.kid: { + return software_producer.public_key + } + case blue_notary.public_key.kid: { + return blue_notary.public_key + } + case orange_notary.public_key.kid: { + return orange_notary.public_key + } + default: { + throw new Error('Unknown key: ' + kid) + } + } + } + } + }) + fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json', JSON.stringify(verification, null, 2)) }) \ No newline at end of file diff --git a/tests/draft-ietf-scitt-architecture/test_utils.ts b/tests/draft-ietf-scitt-architecture/test_utils.ts index 4a92139..c70690e 100644 --- a/tests/draft-ietf-scitt-architecture/test_utils.ts +++ b/tests/draft-ietf-scitt-architecture/test_utils.ts @@ -32,7 +32,7 @@ export const create_software_producer = async ({ website, product }: { website: }) - return { website, product, signer, verifier, kid: publicKeyJwk.kid } + return { website, product, signer, verifier, public_key: publicKeyJwk } } export const create_transparency_service = async ({ website, database }: create_transparency_params) => { @@ -68,13 +68,16 @@ export const create_transparency_service = async ({ website, database }: create_ const register_signed_statement = async (signed_statement: Uint8Array) => { // registration policy goes here... // for this test, we accept everything + + const record = await cose.prepare_for_inclusion(signed_statement) + log.write_record(record) + const root = log.root() const index = log.size() - log.write_record(signed_statement) const decoded = cose.cbor.decode(signed_statement) const signed_statement_header = cose.cbor.decode(decoded.value[0]) const signed_statement_claims = signed_statement_header.get(cose.header.cwt_claims) - const inclusion_proof = log.inclusion_proof(index + 1, index) + const inclusion_proof = log.inclusion_proof(index, index - 1) return signer.sign({ protectedHeader: cose.ProtectedHeader([ [cose.header.kid, publicKeyJwk.kid], @@ -103,7 +106,49 @@ export const create_transparency_service = async ({ website, database }: create_ signer, verifier, log, - register_signed_statement + register_signed_statement, + public_key: publicKeyJwk } +} + +export const verify_transparent_statement = async (statement_hash: Uint8Array, signature: Uint8Array, config: any) => { + // first, we verify the signed statement + const verifier = cose.detached.verifier(config) + const verified_statement = await verifier.verify({ + coseSign1: signature, + payload: statement_hash + }) + const decoded_signed_statement = cose.cbor.decode(signature) + const signed_statement_claims = cose.cbor.decode(decoded_signed_statement.value[0]).get(cose.header.cwt_claims) + const result = { + issuer: signed_statement_claims.get(cose.cwt_claims.iss), + subject: signed_statement_claims.get(cose.cwt_claims.sub), + verified_statement_hash: cose.to_hex(verified_statement), + receipts: [] + } as Record + const decoded_signature = cose.cbor.decode(signature) + const receipts = decoded_signature.value[1].get(cose.draft_headers.receipts) + // next verify each receipt + for (const receipt of receipts) { + const decoded_receipt = cose.cbor.decode(receipt) + const proofs = decoded_receipt.value[1].get(cose.draft_headers.verifiable_data_proofs) + // first proof of inclusion only + const [[size, index, inclusion_path]] = proofs.get(cose.rfc9162_sha256_proof_types.inclusion) + // we need to remove receipts in order to compute leaf hash + const record = await cose.prepare_for_inclusion(signature) + const record_hash = config.tree_hasher.hash_leaf(record) + const root = cose.root_from_record_proof(config.tree_hasher, inclusion_path, size, index, record_hash) + const verified_root = await verifier.verify({ + coseSign1: receipt, + payload: Buffer.from(root) + }) + const receipt_claims = cose.cbor.decode(decoded_receipt.value[0]).get(cose.header.cwt_claims) + result.receipts.push({ + issuer: receipt_claims.get(cose.cwt_claims.iss), + subject: receipt_claims.get(cose.cwt_claims.sub), + verified_root: cose.to_hex(verified_root) + }) + } + return result } \ No newline at end of file diff --git a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag index 7da31be..8d79508 100644 --- a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag +++ b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag @@ -1,6 +1,6 @@ / cose-sign1 / 18([ / protected / <<{ - / key / 4 : "i2vjNrF0wkROkZbKP03J04CxC2EuvQK09baL1aC-v5c", + / key / 4 : "vCl7UcS0ZZY99VpRthDc-0iUjLdfLtnmFqLJ2-Tt8N4", / algorithm / 1 : -7, # ES256 / hash / -6800 : -16, # SHA-256 / content / -6802 : "application/spdx+json", @@ -14,7 +14,7 @@ / receipts / 394 : { <> ], }, }, / payload / null, - / signature / h'bcba332f...ae37605e' + / signature / h'02d227ed...ccd3774f' ])>>, <> ], }, }, / payload / null, - / signature / h'135a0279...c402c1c1' + / signature / h'36581f38...a5581960' ])>> }, }, / payload / h'0167c57c...deeed6d4', - / signature / h'c741bdbb...f452a0ce' + / signature / h'2544f2ed...5840893b' ]) diff --git a/tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json new file mode 100644 index 0000000..fd78f5f --- /dev/null +++ b/tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json @@ -0,0 +1,17 @@ +{ + "issuer": "https://green.example", + "subject": "https://green.example/cli@v1.2.3", + "verified_statement_hash": "0167c57c4ee2834522223a844d06738098140dddb19b3bd41fef5144deeed6d4", + "receipts": [ + { + "issuer": "https://blue.example", + "subject": "https://green.example/cli@v1.2.3", + "verified_root": "9ad73869fce7428336527df01985be25e5d65dd0b2e87ea7e8b394fad9115860" + }, + { + "issuer": "https://orange.example", + "subject": "https://green.example/cli@v1.2.3", + "verified_root": "5095252cae232cc41f3e4ff00cec59f5c1b25c494222d02c611ad1e36f7c081a" + } + ] +} \ No newline at end of file From 78d580fe9daf9b0ea00c6268cdd5d512f152ce82 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 15:55:24 -0500 Subject: [PATCH 52/67] comment out example generation --- tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts | 4 ++-- tests/draft-ietf-scitt-architecture/scitt-integration.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts index 444ecd2..08ce21c 100644 --- a/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts +++ b/tests/draft-ietf-cose-merkle-tree-proofs/tiled_log.test.ts @@ -43,7 +43,7 @@ it("cose receipts from a tiled transparency log", async () => { ]), payload: root_from_inclusion_proof }) - fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag', await cose.cbor.diag(inclusion_receipt, "application/cose")) + // fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/inclusion.receipt.diag', await cose.cbor.diag(inclusion_receipt, "application/cose")) const [inclusion_proof_from_unprotected_header] = cose.decode_inclusion_proof(inclusion_receipt) const reconstructed_inclusion_root_from_unprotected_header = log.root_from_inclusion_proof(inclusion_proof_from_unprotected_header, log.record_hash(encoder.encode(`entry-${17}`))) const verified_inclusion_receipt = await cose.detached @@ -79,7 +79,7 @@ it("cose receipts from a tiled transparency log", async () => { ]), payload: root_from_consistency_proof }) - fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag', await cose.cbor.diag(consistency_receipt, "application/cose")) + // fs.writeFileSync('./tests/draft-ietf-cose-merkle-tree-proofs/consistency.receipt.diag', await cose.cbor.diag(consistency_receipt, "application/cose")) const [consistency_proof_from_unprotected_header] = cose.decode_consistency_proof(consistency_receipt) const reconstructed_consistency_root_from_unprotected_header = log.root_from_consistency_proof(verified_inclusion_receipt, consistency_proof_from_unprotected_header) const verified_consistency_receipt = await cose.detached diff --git a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts index 8db37ae..4dffe0d 100644 --- a/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts +++ b/tests/draft-ietf-scitt-architecture/scitt-integration.test.ts @@ -42,7 +42,7 @@ it('integration test', async () => { const transparent_statement = await cose.add_receipt(signed_statement, blue_receipt) const orange_receipt = await orange_notary.register_signed_statement(transparent_statement) const signed_statement_with_multiple_receipts = await cose.add_receipt(transparent_statement, orange_receipt) - fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag', await cose.cbor.diag(signed_statement_with_multiple_receipts, "application/cose")) + // fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.diag', await cose.cbor.diag(signed_statement_with_multiple_receipts, "application/cose")) const statement_hash = new Uint8Array(await (await cose.crypto.subtle()).digest("SHA-256", statement)) const verification = await verify_transparent_statement(statement_hash, signed_statement_with_multiple_receipts, { @@ -69,5 +69,5 @@ it('integration test', async () => { } } }) - fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json', JSON.stringify(verification, null, 2)) + // fs.writeFileSync('./tests/draft-ietf-scitt-architecture/transparent_signed_statement.verification.json', JSON.stringify(verification, null, 2)) }) \ No newline at end of file From 329c0a8833b034d084cf88a86f7f92630ed62763 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 15:58:00 -0500 Subject: [PATCH 53/67] cleaning --- tests/__fixtures__/consistency.receipt.cbor | 1 - tests/__fixtures__/consistency.receipt.diag | 21 ------------------- tests/__fixtures__/detached-payload.cbor | 1 - tests/__fixtures__/detached-payload.diag | 9 -------- tests/__fixtures__/hash-envelope.cbor | 1 - tests/__fixtures__/inclusion.receipt.cbor | 1 - tests/__fixtures__/inclusion.receipt.diag | 20 ------------------ .../hash-envelope.cbor | 1 + .../hash-envelope.diag | 2 +- .../hash-envelope.test.ts} | 19 +++++------------ 10 files changed, 7 insertions(+), 69 deletions(-) delete mode 100644 tests/__fixtures__/consistency.receipt.cbor delete mode 100644 tests/__fixtures__/consistency.receipt.diag delete mode 100644 tests/__fixtures__/detached-payload.cbor delete mode 100644 tests/__fixtures__/detached-payload.diag delete mode 100644 tests/__fixtures__/hash-envelope.cbor delete mode 100644 tests/__fixtures__/inclusion.receipt.cbor delete mode 100644 tests/__fixtures__/inclusion.receipt.diag create mode 100644 tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor rename tests/{__fixtures__ => draft-ietf-cose-hash-envelope}/hash-envelope.diag (87%) rename tests/{edn.test.ts => draft-ietf-cose-hash-envelope/hash-envelope.test.ts} (60%) diff --git a/tests/__fixtures__/consistency.receipt.cbor b/tests/__fixtures__/consistency.receipt.cbor deleted file mode 100644 index 6c6c5d7..0000000 --- a/tests/__fixtures__/consistency.receipt.cbor +++ /dev/null @@ -1 +0,0 @@ -Ò„G¢&‹¡Œ¡!XjƒƒX 1v¢—Y‹ËL’H7 ùJÕÎrT QïLö]¦X [‡¡2€Ó”*<=^AæóôÉ´›{yÀl&ÏÒq²%íX "féÝêFÓ˜.ÃA'zbŸÒ)9–Õ¶«â®C!ùN’çöX@cæJ"¢¦Téjñ¤½ a#ŒßëÛ6Mœ” ‹#c~‘¿ûÒLËÖ¿ÍÿâÂî Û°„ðéo.¿e0€$Þóp¶ \ No newline at end of file diff --git a/tests/__fixtures__/consistency.receipt.diag b/tests/__fixtures__/consistency.receipt.diag deleted file mode 100644 index b389504..0000000 --- a/tests/__fixtures__/consistency.receipt.diag +++ /dev/null @@ -1,21 +0,0 @@ -/ cose-sign1 / 18([ - / protected / <<{ - / algorithm / 1 : -7, # ES256 - / notarized / 395 : 1, # RFC9162 SHA-256 - }>>, - / unprotected / { - / proofs / 396 : { - / consistency / -2 : [ - <<[ - / old / 3, / new / 4, - / consistency path / - h'0b317603...f65d1ea6', - h'5b87a132...71b225ed', - h'2266e9dd...f94e92e7' - ]>> - ], - }, - }, - / payload / null, - / signature / h'63e6034a...f37004b6' -]) diff --git a/tests/__fixtures__/detached-payload.cbor b/tests/__fixtures__/detached-payload.cbor deleted file mode 100644 index d493085..0000000 --- a/tests/__fixtures__/detached-payload.cbor +++ /dev/null @@ -1 +0,0 @@ -Ò„D¡8" öX`´àÕ1Èf ÙJŠg¡<Ÿ&Ì{N2äk¨•ì<*15‚Ï‚ò—G• m/qݹ—¼‹]üwJ5éáÍÏPrâ^%I†¥™Z÷»æ±œ©ë¢.ˆ^°¤Š: \ No newline at end of file diff --git a/tests/__fixtures__/detached-payload.diag b/tests/__fixtures__/detached-payload.diag deleted file mode 100644 index 3eaf161..0000000 --- a/tests/__fixtures__/detached-payload.diag +++ /dev/null @@ -1,9 +0,0 @@ -/ cose-sign1 / 18([ - / protected / <<{ - / algorithm / 1 : -35, # ES384 - }>>, - / unprotected / { - }, - / payload / null, - / signature / h'b4e0d531...b0a48a3a' -]) diff --git a/tests/__fixtures__/hash-envelope.cbor b/tests/__fixtures__/hash-envelope.cbor deleted file mode 100644 index 1052a80..0000000 --- a/tests/__fixtures__/hash-envelope.cbor +++ /dev/null @@ -1 +0,0 @@ -Ò„X>¤&9/9‘uapplication/spdx+json9xhttps://s.example/sbom/42÷X a·Xl¯¢ÜÓ+”LlZ2Ëu«ä½]TÄŒhVWò@X@¼Wa˜= øeäïÔÙ¾´SŸ oÔþ͘^dõÿ¼@£ðñÝý;5ò­âuõ‚Q MÀ$°ôªw_J‰°[á¿ \ No newline at end of file diff --git a/tests/__fixtures__/inclusion.receipt.cbor b/tests/__fixtures__/inclusion.receipt.cbor deleted file mode 100644 index c3650b5..0000000 --- a/tests/__fixtures__/inclusion.receipt.cbor +++ /dev/null @@ -1 +0,0 @@ -Ò„G¢&‹¡Œ¡ XHƒ‚X ŠºþYtå¯éæë<FƒOÞ÷*þÜ8LToOÛŒMX 1v¢—Y‹ËL’H7 ùJÕÎrT QïLö]¦öX@öû=†£4‹ä)Ýå·?â‡öUKë¤Hòƒöü·w¦´Ás#19»b ÔˆÆÂÃ|z¬y›ÐüÆd§|< :†ÓŠö \ No newline at end of file diff --git a/tests/__fixtures__/inclusion.receipt.diag b/tests/__fixtures__/inclusion.receipt.diag deleted file mode 100644 index 4c15f60..0000000 --- a/tests/__fixtures__/inclusion.receipt.diag +++ /dev/null @@ -1,20 +0,0 @@ -/ cose-sign1 / 18([ - / protected / <<{ - / algorithm / 1 : -7, # ES256 - / notarized / 395 : 1, # RFC9162 SHA-256 - }>>, - / unprotected / { - / proofs / 396 : { - / inclusion / -1 : [ - <<[ - / size / 3, / leaf / 1, - / inclusion path / - h'8aba08fe...4fdb8c4d', - h'0b317603...f65d1ea6' - ]>> - ], - }, - }, - / payload / null, - / signature / h'f6fb3d86...86d38af6' -]) diff --git a/tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor b/tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor new file mode 100644 index 0000000..61bafd7 --- /dev/null +++ b/tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor @@ -0,0 +1 @@ +Ò„X>¤&9/9‘uapplication/spdx+json9xhttps://s.example/sbom/42÷X a·Xl¯¢ÜÓ+”LlZ2Ëu«ä½]TÄŒhVWò@X@FÓPGv¬ ¼Q=Ìð½¸Û¸s¹6¢< WB(êô.ߧº!na { const output = fs.readFileSync('./tests/__fixtures__/cose-key.diag') @@ -10,14 +9,6 @@ it('cose key', async () => { expect(diag).toBe(output.toString()) }) -it('detached payload cose sign1', async () => { - const input = fs.readFileSync('./tests/__fixtures__/detached-payload.cbor') - const diag = await cose.cbor.diag(input, "application/cose") - fs.writeFileSync('./tests/__fixtures__/detached-payload.diag', diag) - const output = fs.readFileSync('./tests/__fixtures__/detached-payload.diag') - expect(diag).toBe(output.toString()) -}) - it('hash envelope', async () => { const k = await cose.crypto.key.parse<'ES256', 'application/cose-key'>({ key, @@ -40,10 +31,10 @@ it('hash envelope', async () => { ]), payload: Buffer.from('🔥 not a real sbom') }) - fs.writeFileSync('./tests/__fixtures__/hash-envelope.cbor', signature) - const input = fs.readFileSync('./tests/__fixtures__/hash-envelope.cbor') + fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor', signature) + const input = fs.readFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor') const diag = await cose.cbor.diag(input, "application/cose") - fs.writeFileSync('./tests/__fixtures__/hash-envelope.diag', diag) - const output = fs.readFileSync('./tests/__fixtures__/hash-envelope.diag') + fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.diag', diag) + const output = fs.readFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.diag') expect(diag).toBe(output.toString()) }) From 31cc826ad321305a083c0e2f39c6ff46f7c0a657 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 15:58:10 -0500 Subject: [PATCH 54/67] comment out --- tests/draft-ietf-cose-hash-envelope/hash-envelope.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/draft-ietf-cose-hash-envelope/hash-envelope.test.ts b/tests/draft-ietf-cose-hash-envelope/hash-envelope.test.ts index 62c68e1..a5779ad 100644 --- a/tests/draft-ietf-cose-hash-envelope/hash-envelope.test.ts +++ b/tests/draft-ietf-cose-hash-envelope/hash-envelope.test.ts @@ -31,10 +31,10 @@ it('hash envelope', async () => { ]), payload: Buffer.from('🔥 not a real sbom') }) - fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor', signature) + // fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor', signature) const input = fs.readFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.cbor') const diag = await cose.cbor.diag(input, "application/cose") - fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.diag', diag) + // fs.writeFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.diag', diag) const output = fs.readFileSync('./tests/draft-ietf-cose-hash-envelope/hash-envelope.diag') expect(diag).toBe(output.toString()) }) From 745eef322336ab98426f976fb5a21b51ff379caf Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Wed, 2 Oct 2024 17:12:20 -0500 Subject: [PATCH 55/67] add example --- README.md | 204 ++++++++++++++++++++++-------------------------------- 1 file changed, 83 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index 1609af1..8932877 100644 --- a/README.md +++ b/README.md @@ -21,140 +21,101 @@ npm install '@transmute/cose' ``` -```ts -import * as cose from "@transmute/cose"; -``` - ```js const cose = require("@transmute/cose"); ``` -### SCITT Receipts - ```ts -const issuerSecretKeyJwk = await cose.key.generate( - "ES256", - "application/jwk+json" -); -const issuerPublicKeyJwk = await cose.key.publicFromPrivate( - issuerSecretKeyJwk -); +import * as cose from "@transmute/cose"; -const notarySecretKeyJwk = await cose.key.generate( +const private_key = await cose.crypto.key.generate< "ES256", "application/jwk+json" -); -const notaryPublicKeyJwk = await cose.key.publicFromPrivate( - notarySecretKeyJwk -); - -const issuer = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: issuerSecretKeyJwk, - }), -}); -const notary = cose.detached.signer({ - remote: cose.crypto.signer({ - privateKeyJwk: notarySecretKeyJwk, - }), -}); -const content = fs.readFileSync("./examples/image.png"); -const signatureForImage = await issuer.sign({ - protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], // signing algorithm ES256 - [cose.Protected.ContentType, "image/png"], // content type image/png - [cose.Protected.Kid, issuerPublicKeyJwk.kid], // issuer key identifier - ]), - payload: content, +>({ + type: "application/jwk+json", + algorithm: "ES256", }); -const transparencyLogContainingImageSignatures = [ - await cose.receipt.leaf(signatureForImage), -]; -const receiptForImageSignature = await cose.receipt.inclusion.issue({ - protectedHeader: cose.ProtectedHeader([ - [cose.Protected.Alg, cose.Signature.ES256], - [ - cose.Protected.VerifiableDataStructure, - cose.VerifiableDataStructures["RFC9162-Binary-Merkle-Tree"], - ], - [cose.Protected.Kid, notaryPublicKeyJwk.kid], - ]), - entry: 0, - entries: transparencyLogContainingImageSignatures, - signer: notary, -}); -const transparentSignature = await cose.receipt.add( - signatureForImage, - receiptForImageSignature -); -const resolve = async ( - coseSign1: cose.CoseSign1Bytes -): Promise => { - const { tag, value } = cose.cbor.decodeFirstSync(coseSign1); - if (tag !== cose.COSE_Sign1) { - throw new Error("Only tagged cose sign 1 are supported"); - } - const [protectedHeaderBytes] = value; - const protectedHeaderMap = cose.cbor.decodeFirstSync(protectedHeaderBytes); - const kid = protectedHeaderMap.get(cose.Protected.Kid); - if (kid === issuerPublicKeyJwk.kid) { - return issuerPublicKeyJwk; - } - if (kid === notaryPublicKeyJwk.kid) { - return notaryPublicKeyJwk; - } - throw new Error("No verification key found in trust store."); -}; -const verifier = await cose.receipt.verifier({ - resolve, -}); -const verified = await verifier.verify({ - coseSign1: transparentSignature, - payload: content, + +const public_key = cose.public_from_private({ + key: private_key, + type: "application/jwk+json", }); -``` -### HPKE +// see tests for current APIs +``` -```ts -const message = "💀 My lungs taste the air of Time Blown past falling sands ⌛"; -const plaintext = new TextEncoder().encode(message); -const encryptionKeys = { - keys: [ - { - kid: "meriadoc.brandybuck@buckland.example", - alg: "HPKE-Base-P256-SHA256-AES128GCM", - kty: "EC", - crv: "P-256", - x: "Ze2loSV3wrroKUN_4zhwGhCqo3Xhu1td4QjeQ5wIVR0", - y: "HlLtdXARY_f55A3fnzQbPcm6hgr34Mp8p-nuzQCE0Zw", +### Transparency + +```edn +/ cose-sign1 / 18([ + / protected / <<{ + / key / 4 : "vCl7UcS0ZZY99VpRthDc-0iUjLdfLtnmFqLJ2-Tt8N4", + / algorithm / 1 : -7, # ES256 + / hash / -6800 : -16, # SHA-256 + / content / -6802 : "application/spdx+json", + / location / -6801 : "https://cloud.example/sbom/42", + / claims / 15 : { + / issuer / 1 : "https://green.example", + / subject / 2 : "https://green.example/cli@v1.2.3", }, - ], -}; -const decryptionKeys = { - keys: [ - { - kid: "meriadoc.brandybuck@buckland.example", - alg: "HPKE-Base-P256-SHA256-AES128GCM", - kty: "EC", - crv: "P-256", - x: "Ze2loSV3wrroKUN_4zhwGhCqo3Xhu1td4QjeQ5wIVR0", - y: "HlLtdXARY_f55A3fnzQbPcm6hgr34Mp8p-nuzQCE0Zw", - d: "r_kHyZ-a06rmxM3yESK84r1otSg-aQcVStkRhA-iCM8", + }>>, + / unprotected / { + / receipts / 394 : { + <>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 9, / leaf / 8, + / inclusion path / + h'7558a95f...e02e35d6' + ]>> + ], + }, + }, + / payload / null, + / signature / h'02d227ed...ccd3774f' + ])>>, + <>, + / unprotected / { + / proofs / 396 : { + / inclusion / -1 : [ + <<[ + / size / 6, / leaf / 5, + / inclusion path / + h'9352f974...4ffa7ce0', + h'54806f32...f007ea06' + ]>> + ], + }, + }, + / payload / null, + / signature / h'36581f38...a5581960' + ])>> }, - ], -}; -const ciphertext = await cose.encrypt.direct({ - protectedHeader: ProtectedHeader([ - [Protected.Alg, Direct["HPKE-Base-P256-SHA256-AES128GCM"]], - ]), - plaintext, - recipients: encryptionKeys, -}); -const decrypted = await cose.decrypt.direct({ - ciphertext, - recipients: decryptionKeys, -}); + }, + / payload / h'0167c57c...deeed6d4', + / signature / h'2544f2ed...5840893b' +]) + ``` ### COSE RFCs @@ -166,11 +127,12 @@ const decrypted = await cose.decrypt.direct({ ### COSE Drafts - [COSE Receipts](https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/) +- [COSE Hash Envelope](https://datatracker.ietf.org/doc/draft-ietf-cose-hash-envelope/) - [COSE HPKE](https://datatracker.ietf.org/doc/draft-ietf-cose-hpke/) ### SCITT Drafts -- [An Architecture for Trustworthy and Transparent Digital Supply Chains](https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/) +- [SCITT Architecture](https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/) ## Develop From 82337bd16b697d2833402344db9d71f6d8976a6d Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 08:40:46 -0500 Subject: [PATCH 56/67] update readme --- README.md | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 359 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8932877..4abd53b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,11 @@ const cose = require("@transmute/cose"); ```ts import * as cose from "@transmute/cose"; +``` + +### Key Generation +```ts const private_key = await cose.crypto.key.generate< "ES256", "application/jwk+json" @@ -44,7 +48,361 @@ const public_key = cose.public_from_private({ // see tests for current APIs ``` -### Transparency +### COSE Receipts & Signature Transparency + +```ts +import crypto from "crypto"; +import sqlite from "better-sqlite3"; +import * as cose from "@transmute/cose"; + +const create_software_producer = async ({ + website, + product, +}: { + website: string; + product: string; +}) => { + const privateKeyJwk = await cose.crypto.key.generate< + "ES256", + "application/jwk+json" + >({ + type: "application/jwk+json", + algorithm: "ES256", + }); + const publicKeyJwk = cose.public_from_private({ + key: privateKeyJwk, + type: "application/jwk+json", + }); + const signer = cose.hash.signer({ + remote: cose.crypto.signer({ + privateKeyJwk, + }), + }); + const verifier = cose.detached.verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk; + }, + }, + }); + + return { website, product, signer, verifier, public_key: publicKeyJwk }; +}; + +const create_sqlite_log = (database: string) => { + const db = new sqlite(database); + + db.prepare( + ` + CREATE TABLE IF NOT EXISTS tiles + (id TEXT PRIMARY KEY, data BLOB); + + ` + ).run(); + + db.prepare( + ` + CREATE TABLE IF NOT EXISTS kv + (key text unique, value text); + ` + ).run(); + + const hash_size = 32; + const tile_height = 2; + + const log = new cose.TileLog({ + tile_height, + hash_size, + read_tree_size: () => { + const rows = db + .prepare( + ` + SELECT * FROM kv + WHERE key = 'tree_size' + ` + ) + .all(); + const [row] = rows as { key: string; value: string }[]; + try { + return parseInt(row.value, 10); + } catch (e) { + // console.error(e) + return 0; + } + }, + update_tree_size: (new_tree_size: number) => { + try { + db.prepare( + ` + INSERT OR REPLACE INTO kv (key, value) + VALUES( 'tree_size', '${new_tree_size}'); + ` + ).run(); + } catch (e) { + // console.error(e) + // ignore errors + } + }, + + read_tree_root: function () { + const rows = db + .prepare( + ` + SELECT * FROM kv + WHERE key = 'tree_root' + ` + ) + .all(); + const [row] = rows as { key: string; value: string }[]; + try { + return new Uint8Array(Buffer.from(row.value, "hex")); + } catch (e) { + return null; + } + }, + update_tree_root: (new_tree_root: Uint8Array): void => { + try { + db.prepare( + ` + INSERT OR REPLACE INTO kv (key, value) + VALUES( 'tree_root', '${Buffer.from(new_tree_root).toString("hex")}'); + ` + ).run(); + } catch (e) { + // ignore errors + } + }, + + read_tile: (tile: string): Uint8Array => { + const [base_tile] = tile.split("."); + // look for completed tiles first + for (let i = 4; i > 0; i--) { + const tile_path = base_tile + "." + i; + const rows = db + .prepare( + ` + SELECT * FROM tiles + WHERE id = '${tile_path}' + ` + ) + .all(); + if (rows.length) { + const [row] = rows as { id: string; data: Uint8Array }[]; + return row.data; + } + } + return new Uint8Array(32); + }, + update_tiles: function ( + tile_path: string, + start: number, + end: number, + stored_hash: Uint8Array + ) { + if (end - start !== 32) { + // this hash was an intermediate of the tile + // so it will never be persisted + return null; + } + let tile_data = this.read_tile(tile_path); + if (tile_data.length < end) { + const expanded_tile_data = new Uint8Array(tile_data.length + 32); + expanded_tile_data.set(tile_data); + tile_data = expanded_tile_data; + } + tile_data.set(stored_hash, start); + try { + db.prepare( + ` + INSERT INTO tiles (id, data) + VALUES( '${tile_path}', x'${Buffer.from(tile_data).toString("hex")}'); + ` + ).run(); + } catch (e) { + // ignore errors + } + return tile_data; + }, + hash_function: (data: Uint8Array) => { + return new Uint8Array(crypto.createHash("sha256").update(data).digest()); + }, + }); + + return { db, log }; +}; + +const create_transparency_service = async ({ + website, + database, +}: { + website: string; + database: string; +}) => { + const privateKeyJwk = await cose.crypto.key.generate< + "ES256", + "application/jwk+json" + >({ + type: "application/jwk+json", + algorithm: "ES256", + }); + const publicKeyJwk = cose.public_from_private({ + key: privateKeyJwk, + type: "application/jwk+json", + }); + const signer = cose.detached.signer({ + remote: cose.crypto.signer({ + privateKeyJwk, + }), + }); + const verifier = cose.detached.verifier({ + resolver: { + resolve: async () => { + return publicKeyJwk; + }, + }, + }); + const { log, db } = create_sqlite_log(database); + const register_signed_statement = async (signed_statement: Uint8Array) => { + // registration policy goes here... + // for this test, we accept everything + const record = await cose.prepare_for_inclusion(signed_statement); + log.write_record(record); + const root = log.root(); + const index = log.size(); + const decoded = cose.cbor.decode(signed_statement); + const signed_statement_header = cose.cbor.decode(decoded.value[0]); + const signed_statement_claims = signed_statement_header.get( + cose.header.cwt_claims + ); + const inclusion_proof = log.inclusion_proof(index, index - 1); + return signer.sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.kid, publicKeyJwk.kid], + [cose.header.alg, cose.algorithm.es256], + [ + cose.draft_headers.verifiable_data_structure, + cose.verifiable_data_structures.rfc9162_sha256, + ], + [ + cose.header.cwt_claims, + cose.CWTClaims([ + [cose.cwt_claims.iss, website], // issuer notary + // receipt subject is statement subject. + // ... could be receipts have different subject id + [ + cose.cwt_claims.sub, + signed_statement_claims.get(cose.cwt_claims.sub), + ], + ]), + ], + ]), + unprotectedHeader: cose.UnprotectedHeader([ + [ + cose.draft_headers.verifiable_data_proofs, + cose.VerifiableDataStructureProofs([ + [cose.rfc9162_sha256_proof_types.inclusion, [inclusion_proof]], + ]), + ], + ]), + payload: root, + }); + }; + return { + website, + db, + signer, + verifier, + log, + register_signed_statement, + public_key: publicKeyJwk, + }; +}; + +const software_producer = await create_software_producer({ + website: "https://green.example", + product: "https://green.example/cli@v1.2.3", +}); + +const blue_notary = await create_transparency_service({ + website: "https://blue.example", + database: "./tests/draft-ietf-scitt-architecture/blue.transparency.db", +}); + +const orange_notary = await create_transparency_service({ + website: "https://orange.example", + database: "./tests/draft-ietf-scitt-architecture/orange.transparency.db", +}); + +const statement = Buffer.from("large file that never moves over a network"); + +const signed_statement = await software_producer.signer.sign({ + protectedHeader: cose.ProtectedHeader([ + [cose.header.kid, software_producer.public_key.kid], + [cose.header.alg, cose.algorithm.es256], + [cose.draft_headers.payload_hash_algorithm, cose.algorithm.sha_256], + [cose.draft_headers.payload_preimage_content_type, "application/spdx+json"], + [cose.draft_headers.payload_location, "https://cloud.example/sbom/42"], + [ + cose.header.cwt_claims, + cose.CWTClaims([ + [cose.cwt_claims.iss, software_producer.website], + [cose.cwt_claims.sub, software_producer.product], + ]), + ], + ]), + payload: statement, +}); + +const blue_receipt = await blue_notary.register_signed_statement( + signed_statement +); +const transparent_statement = await cose.add_receipt( + signed_statement, + blue_receipt +); +const orange_receipt = await orange_notary.register_signed_statement( + transparent_statement +); +const signed_statement_with_multiple_receipts = await cose.add_receipt( + transparent_statement, + orange_receipt +); +const statement_hash = new Uint8Array( + await (await cose.crypto.subtle()).digest("SHA-256", statement) +); +const verification = await verify_transparent_statement( + statement_hash, + signed_statement_with_multiple_receipts, + { + tree_hasher: blue_notary.log.tree_hasher, // both logs use same tree algorithm + resolver: { + resolve: async (token: Buffer) => { + const decoded = cose.cbor.decode(token); + const header = cose.cbor.decode(decoded.value[0]); + const kid = header.get(cose.header.kid); + switch (kid) { + case software_producer.public_key.kid: { + return software_producer.public_key; + } + case blue_notary.public_key.kid: { + return blue_notary.public_key; + } + case orange_notary.public_key.kid: { + return orange_notary.public_key; + } + default: { + throw new Error("Unknown key: " + kid); + } + } + }, + }, + } +); +``` + +Example of a transparent signed statement with multiple receipts in extended diagnostic notation: + +#### Transparent Statement ```edn / cose-sign1 / 18([ From 013d296b519d05123df5f5066ae4301b3e7c70c1 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 08:41:33 -0500 Subject: [PATCH 57/67] update readme --- README.md | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/README.md b/README.md index 4abd53b..5750d08 100644 --- a/README.md +++ b/README.md @@ -29,24 +29,7 @@ const cose = require("@transmute/cose"); import * as cose from "@transmute/cose"; ``` -### Key Generation - -```ts -const private_key = await cose.crypto.key.generate< - "ES256", - "application/jwk+json" ->({ - type: "application/jwk+json", - algorithm: "ES256", -}); - -const public_key = cose.public_from_private({ - key: private_key, - type: "application/jwk+json", -}); - -// see tests for current APIs -``` +## Examples ### COSE Receipts & Signature Transparency From c78fdaf30214a26acdd7928f00f304b57e340dcd Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 08:42:48 -0500 Subject: [PATCH 58/67] update ts ignore --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index aef3552..e802139 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,8 @@ "exclude": [ "tests", "attic", - "examples" + "examples", + "scripts" ], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ From 48e3795e94c6db58a8ee98ae3b73b61537a62f80 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 11:34:56 -0500 Subject: [PATCH 59/67] remove ArrayBuffer from interfaces --- src/cose/detached/index.ts | 18 +++++++++++---- src/cose/sign1/signer.ts | 18 +++++++++++---- src/crypto/signer.ts | 7 ++---- src/crypto/web.ts | 8 +++---- .../draft-ietf-cose-hash-envelope/index.ts | 23 +++++++++++++------ 5 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/cose/detached/index.ts b/src/cose/detached/index.ts index e1eac59..9e3f891 100644 --- a/src/cose/detached/index.ts +++ b/src/cose/detached/index.ts @@ -6,14 +6,22 @@ import { UnprotectedHeader } from "../../desugar" import { tag } from "../../iana/assignments/cbor" -export const signer = ({ remote }: sign1.RequestCoseSign1Signer) => { +export const signer = ({ remote }: { + remote: { + sign: (toBeSigned: Uint8Array) => Promise + } +}) => { const coseSign1Signer = sign1.signer({ remote }) return { - sign: async (req: sign1.RequestCoseSign1) => { - if (req.unprotectedHeader === undefined) { - req.unprotectedHeader = UnprotectedHeader([]) + sign: async ({ protectedHeader, unprotectedHeader, payload }: { + protectedHeader: Map + unprotectedHeader?: Map + payload: Uint8Array + }) => { + if (unprotectedHeader === undefined) { + unprotectedHeader = UnprotectedHeader([]) } - const coseSign1 = await coseSign1Signer.sign(req) + const coseSign1 = await coseSign1Signer.sign({ protectedHeader, unprotectedHeader, payload }) const decoded = decodeFirstSync(coseSign1) decoded.value[2] = null return new Uint8Array(await encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true })) diff --git a/src/cose/sign1/signer.ts b/src/cose/sign1/signer.ts index da9aee1..26bf15a 100644 --- a/src/cose/sign1/signer.ts +++ b/src/cose/sign1/signer.ts @@ -1,12 +1,20 @@ import { encode, encodeAsync, EMPTY_BUFFER, Tagged, toArrayBuffer } from '../../cbor' -import { RequestCoseSign1Signer, RequestCoseSign1, CoseSign1Bytes } from "./types" - import { tag } from '../../iana/assignments/cbor' -const signer = ({ remote }: RequestCoseSign1Signer) => { + +const signer = ({ remote }: { + remote: { + sign: (toBeSigned: Uint8Array) => Promise + } +}) => { return { - sign: async ({ protectedHeader, unprotectedHeader, externalAAD, payload }: RequestCoseSign1): Promise => { + sign: async ({ protectedHeader, unprotectedHeader, externalAAD, payload }: { + protectedHeader: Map, + unprotectedHeader?: Map + externalAAD?: Uint8Array + payload: Uint8Array + }): Promise => { // assume the caller does not realize that cbor will preserve the the View Type, and remove it. const payloadBuffer = toArrayBuffer(payload); const protectedHeaderBytes = (protectedHeader.size === 0) ? EMPTY_BUFFER : encode(protectedHeader); @@ -19,7 +27,7 @@ const signer = ({ remote }: RequestCoseSign1Signer) => { const encodedToBeSigned = encode(decodedToBeSigned); const signature = await remote.sign(encodedToBeSigned) const coseSign1Structure = [protectedHeaderBytes, unprotectedHeader, payloadBuffer, signature]; - return toArrayBuffer(await encodeAsync(new Tagged(tag.COSE_Sign1, coseSign1Structure), { canonical: true })); + return new Uint8Array(await encodeAsync(new Tagged(tag.COSE_Sign1, coseSign1Structure), { canonical: true })); } } } diff --git a/src/crypto/signer.ts b/src/crypto/signer.ts index 8c99077..886553f 100644 --- a/src/crypto/signer.ts +++ b/src/crypto/signer.ts @@ -1,9 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { JWK } from 'jose' -import { toArrayBuffer } from '../cbor' - - import subtleCryptoProvider from './subtle' import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificationKey' @@ -12,7 +9,7 @@ const signer = ({ privateKeyJwk }: { privateKeyJwk: JWK | any }) => { const digest = getDigestFromVerificationKey(`${privateKeyJwk.alg}`) const { alg, ...withoutAlg } = privateKeyJwk return { - sign: async (toBeSigned: ArrayBuffer): Promise => { + sign: async (toBeSigned: Uint8Array): Promise => { const subtle = await subtleCryptoProvider() const signingKey = await subtle.importKey( "jwk", @@ -33,7 +30,7 @@ const signer = ({ privateKeyJwk }: { privateKeyJwk: JWK | any }) => { toBeSigned, ); - return toArrayBuffer(signature); + return new Uint8Array(signature); } } } diff --git a/src/crypto/web.ts b/src/crypto/web.ts index a27d3be..e99a36f 100644 --- a/src/crypto/web.ts +++ b/src/crypto/web.ts @@ -30,13 +30,13 @@ export type WebCryptoCoseAlgorithm = keyof typeof webCryptoKeyParamsByCoseAlgori export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseAlgorithm }) => { return { - sign: async (toBeSigned: ArrayBuffer): Promise => { - return subtle().then((subtle) => { - return subtle.sign( + sign: async (toBeSigned: Uint8Array): Promise => { + return subtle().then(async (subtle) => { + return new Uint8Array(await subtle.sign( webCryptoKeyParamsByCoseAlgorithm[algorithm], key, toBeSigned, - ) + )) }) } } diff --git a/src/drafts/draft-ietf-cose-hash-envelope/index.ts b/src/drafts/draft-ietf-cose-hash-envelope/index.ts index 4f9e24d..7ece46a 100644 --- a/src/drafts/draft-ietf-cose-hash-envelope/index.ts +++ b/src/drafts/draft-ietf-cose-hash-envelope/index.ts @@ -1,22 +1,31 @@ + +import * as cose from '../../iana/assignments/cose' + +import { draft_headers } from '../../iana/requested/cose' + import signer from "../../cose/sign1/signer"; import subtleCryptoProvider from "../../crypto/subtle"; -import { RequestCoseSign1Signer, RequestCoseSign1 } from "../../cose/sign1/types" - -import * as cose from '../../iana/assignments/cose' -import { draft_headers } from '../../iana/requested/cose' export const hash = { - signer: ({ remote }: RequestCoseSign1Signer) => { + signer: ({ remote }: { + remote: { + sign: (toBeSigned: Uint8Array) => Promise + } + }) => { return { - sign: async ({ protectedHeader, unprotectedHeader, payload }: RequestCoseSign1): Promise => { + sign: async ({ protectedHeader, unprotectedHeader, payload }: { + protectedHeader: Map + unprotectedHeader?: Map + payload: Uint8Array + }): Promise => { const subtle = await subtleCryptoProvider(); const hashEnvelopeAlgorithm = protectedHeader.get(draft_headers.payload_hash_algorithm) if (hashEnvelopeAlgorithm !== cose.algorithm.sha_256) { throw new Error('Unsupported hash envelope algorithm (-16 is only one supported)') } - const payloadHash = await subtle.digest("SHA-256", payload) + const payloadHash = new Uint8Array(await subtle.digest("SHA-256", payload)) const normalSigner = signer({ remote }) return new Uint8Array(await normalSigner.sign({ protectedHeader, unprotectedHeader, payload: payloadHash })) } From f375b818fb769aa76554b6f959ef44bb22c93ba6 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:14:23 -0500 Subject: [PATCH 60/67] cleaning types --- src/cose/detached/index.ts | 14 ++++++++++---- src/cose/sign1/types.ts | 15 +-------------- src/cose/sign1/verifier.ts | 13 ++++++++++--- src/crypto/verifier.ts | 2 +- src/crypto/web.ts | 2 +- src/x509/certificate.ts | 32 ++++++++++++++------------------ 6 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/cose/detached/index.ts b/src/cose/detached/index.ts index 9e3f891..7bf7dc2 100644 --- a/src/cose/detached/index.ts +++ b/src/cose/detached/index.ts @@ -29,12 +29,18 @@ export const signer = ({ remote }: { } } -export const verifier = ({ resolver }: sign1.RequestCoseSign1Verifier) => { + + +export const verifier = ({ resolver }: { + resolver: { + resolve: (signature: Uint8Array) => Promise + } +}) => { const verifier = sign1.verifier({ resolver }) return { - verify: async (req: sign1.RequestCoseSign1VerifyDetached) => { - const decoded = decodeFirstSync(req.coseSign1) - const payloadBuffer = toArrayBuffer(req.payload); + verify: async ({ coseSign1, payload }: { coseSign1: Uint8Array, payload: Uint8Array }) => { + const decoded = decodeFirstSync(coseSign1) + const payloadBuffer = toArrayBuffer(payload); decoded.value[2] = payloadBuffer const attached = await encodeAsync(new Tagged(tag.COSE_Sign1, decoded.value), { canonical: true }) return new Uint8Array(await verifier.verify({ coseSign1: attached })) diff --git a/src/cose/sign1/types.ts b/src/cose/sign1/types.ts index e1ac895..87dbc8c 100644 --- a/src/cose/sign1/types.ts +++ b/src/cose/sign1/types.ts @@ -32,10 +32,7 @@ export type RequestCoseSign1Verifier = { } } -export type RequestCoseSign1Verify = { - coseSign1: CoseSign1Bytes, - externalAAD?: ArrayBuffer -} + export type RequestCoseSign1VerifyDetached = { coseSign1: CoseSign1Bytes, @@ -43,15 +40,5 @@ export type RequestCoseSign1VerifyDetached = { externalAAD?: ArrayBuffer } -export type CoseSign1Verifier = { - verify: (req: RequestCoseSign1Verify) => Promise -} -export type RequestCoseSign1DectachedVerify = RequestCoseSign1Verify & { - payload: ArrayBuffer -} - -export type CoseSign1DetachedVerifier = { - verify: (req: RequestCoseSign1DectachedVerify) => Promise -} diff --git a/src/cose/sign1/verifier.ts b/src/cose/sign1/verifier.ts index 7b24a0c..0196e5c 100644 --- a/src/cose/sign1/verifier.ts +++ b/src/cose/sign1/verifier.ts @@ -1,6 +1,6 @@ import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER } from '../../cbor' -import { RequestCoseSign1Verifier, RequestCoseSign1Verify } from './types' + import { DecodedToBeSigned } from './types' import rawVerifier from '../../crypto/verifier' @@ -10,9 +10,16 @@ import { HeaderMap } from '../../desugar' import * as cose from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' -const verifier = ({ resolver }: RequestCoseSign1Verifier) => { +const verifier = ({ resolver }: { + resolver: { + resolve: (signature: Uint8Array) => Promise + } +}) => { return { - verify: async ({ coseSign1, externalAAD }: RequestCoseSign1Verify): Promise => { + verify: async ({ coseSign1, externalAAD }: { + coseSign1: Uint8Array, + externalAAD?: ArrayBuffer + }): Promise => { const publicKeyJwk = await resolver.resolve(coseSign1) const algInPublicKey = algorithms_to_labels.get(publicKeyJwk.alg as string) const ecdsa = rawVerifier({ publicKeyJwk }) diff --git a/src/crypto/verifier.ts b/src/crypto/verifier.ts index d97d2e0..deca07b 100644 --- a/src/crypto/verifier.ts +++ b/src/crypto/verifier.ts @@ -11,7 +11,7 @@ const verifier = ({ publicKeyJwk }: { publicKeyJwk: JWK }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { alg, ...withoutAlg } = publicKeyJwk return { - verify: async (toBeSigned: ArrayBuffer, signature: ArrayBuffer): Promise => { + verify: async (toBeSigned: Uint8Array, signature: Uint8Array): Promise => { const subtle = await subtleCryptoProvider() const verificationKey = await subtle.importKey( "jwk", diff --git a/src/crypto/web.ts b/src/crypto/web.ts index e99a36f..2624b84 100644 --- a/src/crypto/web.ts +++ b/src/crypto/web.ts @@ -44,7 +44,7 @@ export const signer = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCrypt export const verifier = ({ key, algorithm }: { key: CryptoKey, algorithm: WebCryptoCoseAlgorithm }) => { return { - verify: async (toBeSigned: ArrayBuffer, signature: ArrayBuffer): Promise => { + verify: async (toBeSigned: Uint8Array, signature: Uint8Array): Promise => { return subtle().then(async (subtle) => { const verified = await subtle.verify( webCryptoKeyParamsByCoseAlgorithm[algorithm], diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 289eee1..4e18b7e 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -11,7 +11,6 @@ import { labels_to_algorithms } from '../iana/requested/cose'; import { web_key_type } from '../iana/assignments/jose'; import { webCryptoKeyParamsByCoseAlgorithm, WebCryptoCoseAlgorithm } from '../crypto/web'; -import { RequestCoseSign1DectachedVerify } from '../../src/cose/sign1/types'; // eslint-disable-next-line @typescript-eslint/no-empty-function const nodeCrypto = import('crypto').catch(() => { }) @@ -26,14 +25,6 @@ const provide = async () => { } } -export type RequestRootCertificate = { - alg: WebCryptoCoseAlgorithm - sub: string - iss: string - nbf: string - exp: string - serial: string -} // https://datatracker.ietf.org/doc/html/rfc9360#section-2-5.6.1 const thumbprint = async (cert: string): Promise<[number, ArrayBuffer]> => { @@ -41,9 +32,15 @@ const thumbprint = async (cert: string): Promise<[number, ArrayBuffer]> => { return [cose.algorithm.sha_256, await current.getThumbprint('SHA-256')] } -export type RootCertificateResponse = { public: string, private: string } -const root = async (req: RequestRootCertificate): Promise => { +const root = async (req: { + alg: WebCryptoCoseAlgorithm + sub: string + iss: string + nbf: string + exp: string + serial: string +}): Promise<{ public: string, private: string }> => { const crypto = await provide() x509.cryptoProvider.set(crypto); const extensions: x509.JsonGeneralNames = [] @@ -80,18 +77,17 @@ const pkcs8Signer = async ({ alg, privateKeyPKCS8 }: { alg: number, privateKeyPK }) } -export type RequestCertificateVerifier = { + + +const verifier = ({ resolver }: { resolver: { resolve: (signature: ArrayBuffer) => Promise } -} - - -const verifier = ({ resolver }: RequestCertificateVerifier) => { +}) => { return { - verify: async (req: RequestCoseSign1DectachedVerify) => { + verify: async ({ coseSign1, payload }: { coseSign1: Uint8Array, payload: Uint8Array }) => { const verifier = detached.verifier({ resolver }) - return verifier.verify(req) + return verifier.verify({ coseSign1, payload }) } } } From 9ac00e20412a4a11a1fedf4557e9091c68d717e5 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:22:24 -0500 Subject: [PATCH 61/67] cleaning --- src/cose/sign1/index.ts | 2 - src/cose/sign1/payload.ts | 4 +- src/cose/sign1/types.ts | 44 ------------------- src/cose/sign1/verifier.ts | 17 +++---- .../thumbprint.ts | 6 +-- src/x509/certificate.ts | 6 +-- tests/x509.test.ts | 2 +- 7 files changed, 18 insertions(+), 63 deletions(-) delete mode 100644 src/cose/sign1/types.ts diff --git a/src/cose/sign1/index.ts b/src/cose/sign1/index.ts index fb8aad8..68134c5 100644 --- a/src/cose/sign1/index.ts +++ b/src/cose/sign1/index.ts @@ -2,6 +2,4 @@ import signer from "./signer"; import verifier from "./verifier"; -export * from './types' - export { signer, verifier } \ No newline at end of file diff --git a/src/cose/sign1/payload.ts b/src/cose/sign1/payload.ts index ce79455..72c5015 100644 --- a/src/cose/sign1/payload.ts +++ b/src/cose/sign1/payload.ts @@ -3,7 +3,7 @@ import { decodeFirst, encode } from '../../cbor' -export const attach = async (coseSign1Bytes: ArrayBuffer, payload: ArrayBuffer) => { +export const attach = async (coseSign1Bytes: Uint8Array, payload: Uint8Array) => { const obj = await decodeFirst(coseSign1Bytes); const signatureStructure = obj.value; const [protectedHeaderBytes, unprotectedHeader, currentPayload, signature] = signatureStructure; @@ -11,5 +11,5 @@ export const attach = async (coseSign1Bytes: ArrayBuffer, payload: ArrayBuffer) throw new Error('Payload is already attached') } const attached = encode(['Signature1', protectedHeaderBytes, unprotectedHeader, payload, signature]); - return attached + return new Uint8Array(attached) } \ No newline at end of file diff --git a/src/cose/sign1/types.ts b/src/cose/sign1/types.ts deleted file mode 100644 index 87dbc8c..0000000 --- a/src/cose/sign1/types.ts +++ /dev/null @@ -1,44 +0,0 @@ - -import { HeaderMap } from "../../desugar" - -export type CoseSign1Structure = [Buffer, HeaderMap, Buffer, Buffer] -export type DecodedToBeSigned = [string, Buffer, Buffer, Buffer] -export type DecodedCoseSign1 = { - value: CoseSign1Structure -} - -export type RequestCoseSign1Signer = { - remote: { - sign: (toBeSigned: ArrayBuffer) => Promise - } -} - -export type RequestCoseSign1 = { - protectedHeader: HeaderMap, - unprotectedHeader?: HeaderMap, - payload: ArrayBuffer, - externalAAD?: ArrayBuffer -} - -export type CoseSign1Bytes = ArrayBuffer - -export type CoseSign1Signer = { - sign: (req: RequestCoseSign1) => Promise -} - -export type RequestCoseSign1Verifier = { - resolver: { - resolve: (signature: ArrayBuffer) => Promise - } -} - - - -export type RequestCoseSign1VerifyDetached = { - coseSign1: CoseSign1Bytes, - payload: ArrayBuffer - externalAAD?: ArrayBuffer -} - - - diff --git a/src/cose/sign1/verifier.ts b/src/cose/sign1/verifier.ts index 0196e5c..6c25e21 100644 --- a/src/cose/sign1/verifier.ts +++ b/src/cose/sign1/verifier.ts @@ -1,8 +1,8 @@ -import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER } from '../../cbor' +import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER, toArrayBuffer } from '../../cbor' + -import { DecodedToBeSigned } from './types' import rawVerifier from '../../crypto/verifier' import { HeaderMap } from '../../desugar' @@ -18,7 +18,7 @@ const verifier = ({ resolver }: { return { verify: async ({ coseSign1, externalAAD }: { coseSign1: Uint8Array, - externalAAD?: ArrayBuffer + externalAAD?: Uint8Array }): Promise => { const publicKeyJwk = await resolver.resolve(coseSign1) const algInPublicKey = algorithms_to_labels.get(publicKeyJwk.alg as string) @@ -41,12 +41,13 @@ const verifier = ({ resolver }: { if (!signature) { throw new Error('No signature to verify'); } - const decodedToBeSigned = [ + // be careful with Uint8Array near cbor encode... because of aggresive tagging + const decodedToBeSigned: [string, ArrayBuffer, ArrayBuffer, ArrayBuffer] = [ 'Signature1', - protectedHeaderBytes, - externalAAD || EMPTY_BUFFER, - payload - ] as DecodedToBeSigned + toArrayBuffer(protectedHeaderBytes), + toArrayBuffer(externalAAD || EMPTY_BUFFER), + toArrayBuffer(payload) + ] const encodedToBeSigned = encode(decodedToBeSigned); await ecdsa.verify(encodedToBeSigned, signature) return payload; diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts index bfddab8..4d4f6a7 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/thumbprint.ts @@ -6,7 +6,7 @@ import subtleCryptoProvider from "../../crypto/subtle"; import * as cose from '../../iana/assignments/cose' import { web_key_type } from "../../iana/assignments/jose"; -export type cose_key_thumbprint = ArrayBuffer +export type cose_key_thumbprint = Uint8Array export type cose_key_thumbprint_base_encoded = string export type cose_key_thumbprint_uri = `urn:ietf:params:oauth:ckt:sha-256:${cose_key_thumbprint_base_encoded}` @@ -27,8 +27,8 @@ const cose_key_thumbprint = async (coseKey: cose.any_cose_key): Promise => { diff --git a/src/x509/certificate.ts b/src/x509/certificate.ts index 4e18b7e..c25f488 100644 --- a/src/x509/certificate.ts +++ b/src/x509/certificate.ts @@ -27,9 +27,9 @@ const provide = async () => { // https://datatracker.ietf.org/doc/html/rfc9360#section-2-5.6.1 -const thumbprint = async (cert: string): Promise<[number, ArrayBuffer]> => { +const thumbprint = async (cert: string): Promise<[number, Uint8Array]> => { const current = new x509.X509Certificate(cert) - return [cose.algorithm.sha_256, await current.getThumbprint('SHA-256')] + return [cose.algorithm.sha_256, new Uint8Array(await current.getThumbprint('SHA-256'))] } @@ -81,7 +81,7 @@ const pkcs8Signer = async ({ alg, privateKeyPKCS8 }: { alg: number, privateKeyPK const verifier = ({ resolver }: { resolver: { - resolve: (signature: ArrayBuffer) => Promise + resolve: (signature: Uint8Array) => Promise } }) => { return { diff --git a/tests/x509.test.ts b/tests/x509.test.ts index 748cd6f..b6b1e21 100644 --- a/tests/x509.test.ts +++ b/tests/x509.test.ts @@ -32,7 +32,7 @@ it('sign and verify with x5t and key resolver', async () => { ]), payload: content }) - const certificateFromThumbprint = async (coseSign1: ArrayBuffer): Promise => { + const certificateFromThumbprint = async (coseSign1: Uint8Array): Promise => { const { tag, value } = cose.cbor.decodeFirstSync(coseSign1) if (tag !== cose.tag.COSE_Sign1) { throw new Error('Only tagged cose sign 1 are supported') From 0bdf3ecf421636987a1f6f4f4e07cdacec046c12 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:29:36 -0500 Subject: [PATCH 62/67] less array buffer --- src/cbor/pretty/prettyCoseSign1.ts | 2 +- src/cbor/pretty/prettyReceipts.ts | 5 +++-- src/cbor/toArrayBuffer.ts | 6 +++++- .../index.ts | 11 ++++++----- .../web_key_to_cose_key.ts | 3 ++- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/cbor/pretty/prettyCoseSign1.ts b/src/cbor/pretty/prettyCoseSign1.ts index c0b294c..b2a3102 100644 --- a/src/cbor/pretty/prettyCoseSign1.ts +++ b/src/cbor/pretty/prettyCoseSign1.ts @@ -6,7 +6,7 @@ import { prettyPayload } from './prettyPayload' import { ellideBytes } from './ellideBytes' -export const prettyCoseSign1 = (data: Buffer) => { +export const prettyCoseSign1 = (data: ArrayBuffer) => { const decoded = cbor.decode(data) const [encodedProtected, decodedUnprotected, encodedPayload, signature] = decoded.value const decodedProtected = cbor.decode(encodedProtected) diff --git a/src/cbor/pretty/prettyReceipts.ts b/src/cbor/pretty/prettyReceipts.ts index 072a147..c8ee2ae 100644 --- a/src/cbor/pretty/prettyReceipts.ts +++ b/src/cbor/pretty/prettyReceipts.ts @@ -1,10 +1,11 @@ +import { toArrayBuffer } from "../toArrayBuffer" import { prettyCoseSign1 } from "./prettyCoseSign1" -export const prettyReceipts = (receipts: Buffer[]) => { +export const prettyReceipts = (receipts: ArrayBuffer[]) => { return receipts.map((r: any) => { - return `<<${prettyCoseSign1(Buffer.from(r)).trim()}>>` + return `<<${prettyCoseSign1(toArrayBuffer(r)).trim()}>>` }).join(',\n') } \ No newline at end of file diff --git a/src/cbor/toArrayBuffer.ts b/src/cbor/toArrayBuffer.ts index d1b216a..a913b64 100644 --- a/src/cbor/toArrayBuffer.ts +++ b/src/cbor/toArrayBuffer.ts @@ -2,5 +2,9 @@ export const toArrayBuffer = (array: Uint8Array | ArrayBuffer): ArrayBuffer => { if (array instanceof ArrayBuffer) { return array } - return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset) + if (array instanceof Uint8Array) { + return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset) + } + throw new Error('Unsupported buffer type') + } \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts index 2a67d64..56db6af 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/index.ts @@ -11,6 +11,7 @@ import { web_key_to_cose_key } from './web_key_to_cose_key' import { cose_key_to_web_key } from './cose_key_to_web_key' import { public_from_private } from './public_from_private' import { web_key_thumbprint } from './thumbprint' +import { toArrayBuffer } from '../../cbor' export { web_key_to_cose_key, cose_key_to_web_key, public_from_private } @@ -190,11 +191,11 @@ export const parse = < } -const _generate = async ({ id, type, algorithm, extractable }: request_crypto_key): Promise => { +const _generate = async ({ id, type, algorithm, extractable }: request_crypto_key): Promise => { switch (type) { case 'application/jwk+json': { const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) - return Buffer.from(encoder.encode(JSON.stringify(privateKey))) + return encoder.encode(JSON.stringify(privateKey)) } case 'application/cose-key': { const { privateKey } = await generate_web_key({ kid: id, alg: algorithm, ext: extractable || true }) @@ -229,7 +230,7 @@ export const convert = async ({ key, from, to }: { key: Uint8Array, from: crypto switch (to) { case 'application/cose-key': { const k = await web_key_to_cose_key(JSON.parse(decoder.decode(key))) - return cbor.encode(k) + return new Uint8Array(cbor.encode(k)) } default: { throw new Error('Unknown key: ' + from) @@ -249,10 +250,10 @@ export const serialize = < cty extends crypto_key_type >({ key, type }: { key: fully_specified_cose_key | fully_specified_web_key, type: cty }) => { if (type === 'application/jwk+json') { - return Buffer.from(encoder.encode(JSON.stringify(format_web_key(key as JWK), null, 2))) + return new Uint8Array(encoder.encode(JSON.stringify(format_web_key(key as JWK), null, 2))) } if (type === 'application/cose-key') { - return cbor.encode(key) + return new Uint8Array(cbor.encode(key)) } throw new Error('Cannot serialize to unsupported media type: ' + type) } \ No newline at end of file diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts index 0385e72..06c8dcf 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/web_key_to_cose_key.ts @@ -3,6 +3,7 @@ import { base64url } from 'jose' import { curve_to_label, ec2_params_to_labels, key_type_to_label } from '../../iana/assignments/cose' import { algorithms_to_labels } from '../../iana/requested/cose' import { jose_key_type, ec_web_key, web_key_type } from '../../iana/assignments/jose' +import { toArrayBuffer } from '../../cbor' export const web_key_to_cose_key = async (jwk: web_key_type): Promise => { const coseKey = new Map(); @@ -33,7 +34,7 @@ export const web_key_to_cose_key = async (jwk: web_key_type): Promise => { case ec_web_key.y: case ec_web_key.d: { // todo check lengths based on curves - coseKey.set(ec2_params_to_labels.get(key), Buffer.from(base64url.decode(value as string))) + coseKey.set(ec2_params_to_labels.get(key), toArrayBuffer(base64url.decode(value as string))) break; } default: { From e05f00867dc6ac21dde445ecde3a343ee5dca3e1 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:42:05 -0500 Subject: [PATCH 63/67] cleaning --- scripts/make-iana-assignments.ts | 2 +- src/cbor/pretty/prettyCoseKey.ts | 2 +- src/cbor/pretty/prettyProofs.ts | 4 ++-- src/iana/assignments/cose.ts | 40 ++++++++++++++++---------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/scripts/make-iana-assignments.ts b/scripts/make-iana-assignments.ts index f4d5c4c..20d2969 100644 --- a/scripts/make-iana-assignments.ts +++ b/scripts/make-iana-assignments.ts @@ -17,7 +17,7 @@ const cddlToType = (cddl: string) => { return cddl .replace(/\//g, '|') .replace(/tstr/g, 'string') - .replace(/bstr/g, 'Buffer') + .replace(/bstr/g, 'ArrayBuffer') .replace(/uint/g, 'number') .replace(/int/g, 'number') .replace(/bool/g, 'boolean') diff --git a/src/cbor/pretty/prettyCoseKey.ts b/src/cbor/pretty/prettyCoseKey.ts index 2e86aa4..9c3de92 100644 --- a/src/cbor/pretty/prettyCoseKey.ts +++ b/src/cbor/pretty/prettyCoseKey.ts @@ -5,7 +5,7 @@ import { ellideBytes } from './ellideBytes' import { ec2_key, cose_key, cose_key_type, ec2, any_cose_key } from '../../iana/assignments/cose' -export const prettyCoseKey = (data: Buffer) => { +export const prettyCoseKey = (data: ArrayBuffer) => { const decoded = cbor.decode(data) as any_cose_key const kty = decoded.get(cose_key.kty as any) as any if (kty === cose_key_type.ec2) { diff --git a/src/cbor/pretty/prettyProofs.ts b/src/cbor/pretty/prettyProofs.ts index 406d3fb..ea3dbc8 100644 --- a/src/cbor/pretty/prettyProofs.ts +++ b/src/cbor/pretty/prettyProofs.ts @@ -7,7 +7,7 @@ import { ellideBytes } from './ellideBytes' import { rfc9162_sha256_proof_types, transparency } from '../../drafts/draft-ietf-cose-merkle-tree-proofs' import { indentBlock } from './indentBlock' -export const prettyInclusionProof = (proof: ArrayBuffer | [number, number, Buffer[]]) => { +export const prettyInclusionProof = (proof: ArrayBuffer | [number, number, ArrayBuffer[]]) => { const [size, index, path] = Array.isArray(proof) ? proof : cbor.decode(proof) return indentBlock(`<<[ / size / ${size}, / leaf / ${index}, @@ -18,7 +18,7 @@ ${path.length === 0 ? ' []' : path.map((p: ArrayBuffer) => { ]>>`, ' ') } -export const prettyConsistencyProof = (proof: ArrayBuffer | [number, number, Buffer[]]) => { +export const prettyConsistencyProof = (proof: ArrayBuffer | [number, number, ArrayBuffer[]]) => { const [size1, size2, path] = Array.isArray(proof) ? proof : cbor.decode(proof) return indentBlock(`<<[ diff --git a/src/iana/assignments/cose.ts b/src/iana/assignments/cose.ts index b1b951d..ad273bc 100644 --- a/src/iana/assignments/cose.ts +++ b/src/iana/assignments/cose.ts @@ -1499,51 +1499,51 @@ export enum walnutdsa { export type any_cose_key = Map & { - get(k: cose_key.kid): Buffer + get(k: cose_key.kid): ArrayBuffer get(k: cose_key.alg): string | number get(k: cose_key.key_ops): [ (string|number)] - get(k: cose_key.base_iv): Buffer + get(k: cose_key.base_iv): ArrayBuffer } export type okp_key = any_cose_key & { get(k: okp.kty): cose_key_type.okp get(k: okp.crv): okp_curves - get(k: okp.x): Buffer - get(k: okp.d): Buffer + get(k: okp.x): ArrayBuffer + get(k: okp.d): ArrayBuffer } export type ec2_key = any_cose_key & { get(k: ec2.kty): cose_key_type.ec2 get(k: ec2.crv): ec2_curves - get(k: ec2.x): Buffer - get(k: ec2.y): Buffer | boolean - get(k: ec2.d): Buffer + get(k: ec2.x): ArrayBuffer + get(k: ec2.y): ArrayBuffer | boolean + get(k: ec2.d): ArrayBuffer } export type rsa_key = any_cose_key & { get(k: rsa.kty): cose_key_type.rsa - get(k: rsa.n): Buffer - get(k: rsa.e): Buffer - get(k: rsa.d): Buffer - get(k: rsa.p): Buffer - get(k: rsa.q): Buffer - get(k: rsa.dp): Buffer - get(k: rsa.dq): Buffer - get(k: rsa.qinv): Buffer + get(k: rsa.n): ArrayBuffer + get(k: rsa.e): ArrayBuffer + get(k: rsa.d): ArrayBuffer + get(k: rsa.p): ArrayBuffer + get(k: rsa.q): ArrayBuffer + get(k: rsa.dp): ArrayBuffer + get(k: rsa.dq): ArrayBuffer + get(k: rsa.qinv): ArrayBuffer get(k: rsa.other): Array - get(k: rsa.r_i): Buffer - get(k: rsa.d_i): Buffer - get(k: rsa.t_i): Buffer + get(k: rsa.r_i): ArrayBuffer + get(k: rsa.d_i): ArrayBuffer + get(k: rsa.t_i): ArrayBuffer } export type symmetric_key = any_cose_key & { get(k: symmetric.kty): cose_key_type.symmetric - get(k: symmetric.k): Buffer + get(k: symmetric.k): ArrayBuffer } export type hss_lms_key = any_cose_key & { get(k: hss_lms.kty): cose_key_type.hss_lms - get(k: hss_lms.pub): Buffer + get(k: hss_lms.pub): ArrayBuffer } export type walnutdsa_key = any_cose_key & { From 2fcaaedfbb5a5f8189ba84b9728017af079b54b2 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:42:35 -0500 Subject: [PATCH 64/67] cleaning --- src/cbor/pretty/prettyCose.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cbor/pretty/prettyCose.ts b/src/cbor/pretty/prettyCose.ts index 33be1ad..d925bb3 100644 --- a/src/cbor/pretty/prettyCose.ts +++ b/src/cbor/pretty/prettyCose.ts @@ -1,7 +1,7 @@ import * as cbor from 'cbor-web' import { prettyCoseSign1 } from './prettyCoseSign1' -export const prettyCose = (data: Buffer) => { +export const prettyCose = (data: ArrayBuffer) => { const decoded = cbor.decode(data) if (decoded.tag === 18) { return prettyCoseSign1(data) From a4199ba7af8fd78a4aa07d0993a2d4c3dff8bb82 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:46:44 -0500 Subject: [PATCH 65/67] updates --- .github/workflows/ci.yml | 16 ++++++---------- README.md | 15 ++++----------- badges/coverage-branches.svg | 1 - badges/coverage-functions.svg | 1 - badges/coverage-jest coverage.svg | 1 - badges/coverage-lines.svg | 1 - badges/coverage-statements.svg | 1 - 7 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 badges/coverage-branches.svg delete mode 100644 badges/coverage-functions.svg delete mode 100644 badges/coverage-jest coverage.svg delete mode 100644 badges/coverage-lines.svg delete mode 100644 badges/coverage-statements.svg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a13654e..c4b6570 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,9 @@ jobs: bump: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Install - run: npm ci - - name: Coverage - run: npm run coverage - - name: Badges - uses: jpb06/jest-badges-action@latest - with: - branches: main \ No newline at end of file + - name: Checkout + uses: actions/checkout@v4 + - name: Install + run: npm ci + - name: Coverage + run: npm run coverage diff --git a/README.md b/README.md index 5750d08..1a4d531 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,6 @@ # cose [![CI](https://github.com/transmute-industries/cose/actions/workflows/ci.yml/badge.svg)](https://github.com/transmute-industries/cose/actions/workflows/ci.yml) -![Branches](./badges/coverage-branches.svg) -![Functions](./badges/coverage-functions.svg) -![Lines](./badges/coverage-lines.svg) -![Statements](./badges/coverage-statements.svg) -![Jest coverage](./badges/coverage-jest%20coverage.svg) - - - - - -#### [Questions? Contact Transmute](https://transmute.typeform.com/to/RshfIw?typeform-source=cose) ## Usage @@ -483,3 +472,7 @@ npm t npm run lint npm run build ``` + + + +#### [Questions? Contact Transmute](https://transmute.typeform.com/to/RshfIw?typeform-source=cose) diff --git a/badges/coverage-branches.svg b/badges/coverage-branches.svg deleted file mode 100644 index 5fbd3fd..0000000 --- a/badges/coverage-branches.svg +++ /dev/null @@ -1 +0,0 @@ -branches: 51.42%branches51.42% \ No newline at end of file diff --git a/badges/coverage-functions.svg b/badges/coverage-functions.svg deleted file mode 100644 index 5c5272a..0000000 --- a/badges/coverage-functions.svg +++ /dev/null @@ -1 +0,0 @@ -functions: 91.45%functions91.45% \ No newline at end of file diff --git a/badges/coverage-jest coverage.svg b/badges/coverage-jest coverage.svg deleted file mode 100644 index da51971..0000000 --- a/badges/coverage-jest coverage.svg +++ /dev/null @@ -1 +0,0 @@ -jest coverage: 81.31%jest coverage81.31% \ No newline at end of file diff --git a/badges/coverage-lines.svg b/badges/coverage-lines.svg deleted file mode 100644 index 1b0f8e9..0000000 --- a/badges/coverage-lines.svg +++ /dev/null @@ -1 +0,0 @@ -lines: 90.7%lines90.7% \ No newline at end of file diff --git a/badges/coverage-statements.svg b/badges/coverage-statements.svg deleted file mode 100644 index f46424e..0000000 --- a/badges/coverage-statements.svg +++ /dev/null @@ -1 +0,0 @@ -statements: 91.65%statements91.65% \ No newline at end of file From 6d8ea9797fd489eabdb1e66d193bc8b92886f287 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:50:30 -0500 Subject: [PATCH 66/67] CryptoKey interfaces --- .../draft-ietf-jose-fully-specified-algorithms/signer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts index c0ac07a..5c11ceb 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts @@ -4,11 +4,9 @@ import { cose_key_to_web_key } from "."; import { any_cose_key, crypto } from "../.."; import { web_key_type } from "../../iana/assignments/jose"; -export const signer = async ({ key, algorithm }: { key: web_key_type | any_cose_key | CryptoKey, algorithm: 'ES256' }) => { +export const signer = async ({ key, algorithm }: { key: web_key_type | any_cose_key, algorithm: 'ES256' }) => { let privateKey - if (key instanceof CryptoKey) { - // nothing to do - } else if (key instanceof Map) { + if (key instanceof Map) { const jwk = await cose_key_to_web_key(key as any_cose_key) privateKey = await crypto.web.web_key_to_crypto_key(jwk, ['sign']) } else if ((key as web_key_type).kty) { From c79a716390f3438f558982a20a9b418ecd315cd7 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Thu, 3 Oct 2024 12:52:08 -0500 Subject: [PATCH 67/67] interfaces --- .../draft-ietf-jose-fully-specified-algorithms/signer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts index 5c11ceb..d838d4e 100644 --- a/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts +++ b/src/drafts/draft-ietf-jose-fully-specified-algorithms/signer.ts @@ -13,11 +13,11 @@ export const signer = async ({ key, algorithm }: { key: web_key_type | any_cose_ privateKey = await crypto.web.web_key_to_crypto_key(key, ['sign']) } if (privateKey === undefined) { - throw new Error('Unsupported key') + privateKey = key } return crypto.web .signer({ - key: privateKey, + key: privateKey as CryptoKey, algorithm }) } \ No newline at end of file