diff --git a/package-lock.json b/package-lock.json index c99cef4..17fbafa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@cloudflare/privacypass-ts": "0.7.0-beta.0", + "@cloudflare/privacypass-ts": "file:/Users/lina/workspace/privacypass-ts/cloudflare-privacypass-ts-0.7.0-beta.0.tgz", "@sentry/cli": "2.26.0", "@sentry/types": "7.95.0", "cron-parser": "4.9.0", @@ -574,6 +574,7 @@ "version": "0.4.2", "resolved": "file:vendor/cloudflare-blindrsa-ts-0.4.2.tgz", "integrity": "sha512-m3fYCy8AkOVfyPKy2OqE97bh4gmri6GkH25bUwqMXEIbOmlidLmU7K+GyPl0pjELRHz/4EvdLT2TSzNZn/3ctQ==", + "license": "Apache-2.0", "dependencies": { "sjcl": "1.0.8" }, @@ -596,10 +597,11 @@ }, "node_modules/@cloudflare/privacypass-ts": { "version": "0.7.0-beta.0", - "resolved": "https://registry.npmjs.org/@cloudflare/privacypass-ts/-/privacypass-ts-0.7.0-beta.0.tgz", - "integrity": "sha512-4udK/l5ERrefzVHu7y3wH3HsWM62KWIqwHCsu6U5SJispE+0rxojqMnMpcrRMjpoJVyyXu15796LRJutTlSgYw==", + "resolved": "file:../privacypass-ts/cloudflare-privacypass-ts-0.7.0-beta.0.tgz", + "integrity": "sha512-MEXH/NHBc5ePkuLSLoVt4f3w93W6XQVWFhXTo2XDeLacMb9g0WVnTCGSYvx8f3+KjNbH+BCPshwcnBmoI8YsUw==", + "license": "Apache-2.0", "dependencies": { - "@cloudflare/blindrsa-ts": "0.4.2", + "@cloudflare/blindrsa-ts": "file:/Users/lina/workspace/blindrsa-ts/cloudflare-blindrsa-ts-0.4.2.tgz", "@cloudflare/voprf-ts": "1.0.0", "asn1-parser": "1.1.8", "asn1js": "3.0.5", @@ -614,6 +616,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@cloudflare/voprf-ts/-/voprf-ts-1.0.0.tgz", "integrity": "sha512-OvvWlnm5bjwPr7KaJ8MItQDaMz4DgN6BuOO3x/WXLVqCwM4rn6H/8Mgdbxq2XAEAi3BAh58XWDlAfmHL+AZTBA==", + "license": "BSD-3-Clause", "engines": { "node": ">=20" }, @@ -2114,6 +2117,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.5.0.tgz", "integrity": "sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==", + "license": "MIT", "optional": true, "dependencies": { "@noble/hashes": "1.4.0" @@ -2126,6 +2130,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", "optional": true, "engines": { "node": ">= 16" @@ -2550,16 +2555,21 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.15.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.12.tgz", - "integrity": "sha512-Wha1UwsB3CYdqUm2PPzh/1gujGCNtWVUYF0mB00fJFoR4gTyWTDPjSm+zBF787Ahw8vSGgBja90MkgFwvB86Dg==", - "dev": true + "version": "22.13.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", + "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } }, "node_modules/@types/node-forge": { "version": "1.3.11", "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2817,9 +2827,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -3286,9 +3296,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001702", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz", - "integrity": "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==", + "version": "1.0.30001703", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", + "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", "dev": true, "funding": [ { @@ -3349,6 +3359,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3716,9 +3727,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.112", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz", - "integrity": "sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==", + "version": "1.5.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz", + "integrity": "sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg==", "dev": true, "license": "ISC" }, @@ -6511,6 +6522,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/luxon": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", @@ -6687,9 +6708,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", "dev": true, "funding": [ { @@ -6755,12 +6776,6 @@ "version": "1.6.6", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "dev": true - }, - "node_modules/node-fetch/node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, "license": "MIT" }, @@ -7108,7 +7123,8 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", @@ -7395,12 +7411,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/quicvarint": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/quicvarint/-/quicvarint-0.1.4.tgz", - "integrity": "sha512-Khpzdux2Ugx6SmD+scyxktiwmZWcWg1t6su1AjZMNgKuoieCQxxbmDReEN1poeKRr25N7mulsBEAb/KdQVhDDQ==" + "integrity": "sha512-Khpzdux2Ugx6SmD+scyxktiwmZWcWg1t6su1AjZMNgKuoieCQxxbmDReEN1poeKRr25N7mulsBEAb/KdQVhDDQ==", + "license": "MIT" }, "node_modules/react-is": { "version": "18.3.1", @@ -7522,7 +7540,8 @@ "node_modules/rfc4648": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.3.tgz", - "integrity": "sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==" + "integrity": "sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==", + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", @@ -7630,6 +7649,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -8125,7 +8145,8 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici": { "version": "5.28.4", @@ -8815,9 +8836,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "dev": true, "license": "MIT", "engines": { @@ -8840,7 +8861,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/y18n": { "version": "5.0.8", @@ -8852,6 +8874,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index 34fb8a4..d091474 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "wrangler": "3.60.3" }, "dependencies": { - "@cloudflare/privacypass-ts": "0.7.0-beta.0", + "@cloudflare/privacypass-ts": "file:/Users/lina/workspace/privacypass-ts/cloudflare-privacypass-ts-0.7.0-beta.0.tgz", "@sentry/cli": "2.26.0", "@sentry/types": "7.95.0", "cron-parser": "4.9.0", diff --git a/preseeded-key.json b/preseeded-key.json new file mode 100644 index 0000000..c1240d9 --- /dev/null +++ b/preseeded-key.json @@ -0,0 +1,5 @@ +{ + "tokenKey": 43, + "privateKeyBase64": "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzaien/z4vdy2fRnkLIJaadtCo/Iv48nhK2HzlFqPFcwCgYuatCCk+fadBdIL19CEQnWqDHbmb0Suf3L/bv2li+HYqcXXOX28ru6E5omoyeDRURR3L69NENVZ2qtNcuHzHH26i5o2nI+WoMYzlyM37nwpuwn2FpDtLExudB8Ac5d6iC4D3J4+OKBihVn8FMvBvna0JNc3ZzfO3mVqBOPweOvWNPtOmsrfDgicgL+7Or8Sw452WpZYYsRC4wQ2C938Fa3ZJ69F6z0OzQTlrdU0L0v8Z1z886pgQef/n2Cz2SCyz0ET3TCr27mTuKnR71FnpzMQo5X30Mv+yLm9fBlaFAgMBAAECggEAHzAuaZlItZaZkyz8gK8vDvXliuKv8FwyBgzBFU/Ms1es/bSDlgOrq8XLC+lVlKzWDJ3YtKc3qzr+wuDsZyZMixxW6kTj7jaPzEHnIm412MUlj5qeNeMuTBabi7BhYqZdZn3zzRSX/jySwRyv+gfgqsN4XE2p5U/p0MCnFbKRtlQCQHdGth7HVn0jJzx5eDKU1rRfCcuPdD4u1uniB7OP7hIwAheEJijEy8yiZTNY/dXLRc9XqLFIHtNN86GDIKG5SPiJbHk401WdBlUJnqjng5wriZUu69lI9GF3qSMAJlFsWIgSnbWpkDPH785knVNBQst9EYDD2N/LeTY9rMn7twKBgQDyM9pmSb4cDZmcIrnegkjP1/Jn7M+RuQ3hSF95GCy6mQJHL+Zxy//dOmjq2saZdClqQWiE578Lx0HqSTLeAjJS6VVyBkjuk4t+/7zVXGJ+8HyvMIEvL040Zwxpd48p454KLNVjOAOciFzSUycRDVZlN8qKQR6P8eE8piDDL6hmUwKBgQC9oqNo6Yh5JcktWtXSdduZWyONJiNml1ZmkD37k2orkxVVIzZg2bNWeO6rzyzcHm5f1l9j9cORikPMKQf+oCG4fhAwFQ/f9Vr5Bqh90T6B1DOI+2AYi+SSAR9jTb5nqCaY/rfZBarWi00cpHdRU41kNMirhMBfH+NPO1axxLSExwKBgQCRrtbjR9/uB9AptkmOqVcajY3lLO/9ew36QAoNUJk28+oG360BLe+NJiENguKKUvDGVOmFZ8/mSchAIB9UooWakXcvys/7kQwLK9BtldA5AnY8+jP6Kb4kjwdMOPoH/D2HaUhBEeQ6N1t9tz58Z0VcRJ6zYk/7zUXpsRNr1DK6uQKBgE0HjHMoMYxsYdyvgh18TFht4fIK5OReYvVEcDkJt1294DN2GzeaFrPwaZqWjDVZkyIQ1SyofulWjZWXsSyn5Sqo4nB1jb4+TtbK8pQw88AO72QcH/u4j38TP6m5wbcfYZZSGWHpYGzHpuoUkHcThmKG4mBxiybYsB/WDbAmI+GvAoGBAJMuLUSfeMpWoGA3SUIexcD1ccfBtsk3TaJJ52SyiSHojdNX2lg/tf2PMG8Nm/QPSQ2eRQvZCURBOaVZFg8fTcgGEkbNIYK+Cc9KgKXWFsegAzadHyihu1hSBLsBdmkjRHCEJaWIW3LRvuehuqtdIoGltquloP3mCqrIi2qMyjHX", + "publicKeyBase64": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs2onp/8+L3ctn0Z5CyCWmnbQqPyL+PJ4Sth85RajxXMAoGLmrQgpPn2nQXSC9fQhEJ1qgx25m9Ern9y/279pYvh2KnF1zl9vK7uhOaJqMng0VEUdy+vTRDVWdqrTXLh8xx9uouaNpyPlqDGM5cjN+58KbsJ9haQ7SxMbnQfAHOXeoguA9yePjigYoVZ/BTLwb52tCTXN2c3zt5lagTj8Hjr1jT7TprK3w4InIC/uzq/EsOOdlqWWGLEQuMENgvd/BWt2SevRes9Ds0E5a3VNC9L/Gdc/POqYEHn/59gs9kgss9BE90wq9u5k7ip0e9RZ6czEKOV99DL/si5vXwZWhQIDAQAB" +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 70e1868..755fde8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -78,9 +78,11 @@ export const handleSingleTokenRequest = async (ctx: Context, request: Request) = } const keyID = tokenRequest.truncatedTokenKeyId; + const { sk, pk } = await getBlindRSAKeyPair(ctx, keyID); const domain = new URL(request.url).host; + const issuer = new Issuer(BlindRSAMode.PSS, domain, sk, pk, { supportsRSARAW: true }); const signedToken = await issuer.issue(tokenRequest); ctx.metrics.signedTokenTotal.inc({ key_id: keyID }); @@ -95,49 +97,65 @@ export const handleSingleTokenRequest = async (ctx: Context, request: Request) = }; const handleBatchedTokenRequest = async (ctx: Context, request: Request): Promise => { - const buffer = await request.arrayBuffer(); - const batchedTokenRequest = BatchedTokenRequest.deserialize(new Uint8Array(buffer)); + try { + // Read request body + const buffer = await request.arrayBuffer(); - if (batchedTokenRequest.tokenRequests.length === 0) { - const responseBytes = (new BatchedTokenResponse([])).serialize() - return new Response(responseBytes, { headers: { 'Content-Type': MediaType.ARBITRARY_BATCHED_TOKEN_RESPONSE } }) - } + // Deserialize the batched token request + const batchedTokenRequest = BatchedTokenRequest.deserialize(new Uint8Array(buffer)); - // Validate that all token requests have the same key ID and correct token type - const keyID = batchedTokenRequest.tokenRequests[0].truncatedTokenKeyId; - for (const tokenRequest of batchedTokenRequest.tokenRequests) { - if (tokenRequest.tokenType !== TOKEN_TYPES.BLIND_RSA.value) { - throw new InvalidTokenTypeError(); + if (batchedTokenRequest.tokenRequests.length === 0) { + const responseBytes = new BatchedTokenResponse([]).serialize(); + return new Response(responseBytes, { + headers: { "Content-Type": MediaType.ARBITRARY_BATCHED_TOKEN_RESPONSE } + }); } - if (tokenRequest.truncatedTokenKeyId != keyID) { - throw new MismatchedTokenKeyIDError(); + + // Extract key ID and validate token requests + const keyID = batchedTokenRequest.tokenRequests[0].truncatedTokenKeyId; + + for (let i = 0; i < batchedTokenRequest.tokenRequests.length; i++) { + const tokenRequest = batchedTokenRequest.tokenRequests[i]; + + if (tokenRequest.tokenType !== TOKEN_TYPES.BLIND_RSA.value) { + throw new InvalidTokenTypeError(); + } + if (tokenRequest.truncatedTokenKeyId !== keyID) { + throw new MismatchedTokenKeyIDError(); + } } - } - const { sk, pk } = await getBlindRSAKeyPair(ctx, keyID); - const domain = new URL(request.url).host; + // Retrieve key pair + const { sk, pk } = await getBlindRSAKeyPair(ctx, keyID); - // We only support type 2 (Blind RSA, e.g. PSS) for now - const issuer = new Issuer(publicVerif.BlindRSAMode.PSS, domain, sk, pk, { supportsRSARAW: true }); - const batchedTokenIssuer = new BatchedTokensIssuer(issuer); - const batchedTokenResponse = await batchedTokenIssuer.issue(batchedTokenRequest); + const domain = new URL(request.url).host; + const issuer = new Issuer(BlindRSAMode.PSS, domain, sk, pk, { supportsRSARAW: true }); - const responseBytes = batchedTokenResponse.serialize(); + const batchedTokenIssuer = new BatchedTokensIssuer(issuer); + const batchedTokenResponse = await batchedTokenIssuer.issue(batchedTokenRequest); + const responseBytes = batchedTokenResponse.serialize(); - // Determine if any token response is empty (null) and choose the proper status code: - // If at least one token request failed, return HTTP 206 (Partial Content), otherwise 200. - const partial = batchedTokenResponse.tokenResponses.some((resp) => resp.tokenResponse === null); - const status = partial ? 206 : 200; - return new Response(responseBytes, { - status, - headers: { - "Content-Type": MediaType.ARBITRARY_BATCHED_TOKEN_RESPONSE, - "Content-Length": responseBytes.length.toString(), - }, - }); + // Determine if any token response is empty (null) and set the appropriate status code + const partial = batchedTokenResponse.tokenResponses.some((resp) => resp.tokenResponse === null); + const status = partial ? 206 : 200; + console.log(`[handleBatchedTokenRequest] Response status: ${status} (Partial: ${partial})`); + + // Return response + return new Response(responseBytes, { + status, + headers: { + "Content-Type": MediaType.ARBITRARY_BATCHED_TOKEN_RESPONSE, + "Content-Length": responseBytes.length.toString(), + }, + }); + } catch (error) { + console.error("[handleBatchedTokenRequest] Error handling batched token request:", error); + return new Response("Internal Server Error", { status: 500 }); + } }; + const getBlindRSAKeyPair = async (ctx: Context, keyID: number) => { const key = await ctx.bucket.ISSUANCE_KEYS.get(keyID.toString()); @@ -162,7 +180,6 @@ const getBlindRSAKeyPair = async (ctx: Context, keyID: number) => { 'pkcs8', privateKey, { - // RSA-RAW is handled directly by blindrsa-ts name: ctx.isTest() ? 'RSA-PSS' : 'RSA-RAW', hash: 'SHA-384', length: 2048, diff --git a/test/e2e/issuer.ts b/test/e2e/issuer.ts index f07044a..7fad54a 100644 --- a/test/e2e/issuer.ts +++ b/test/e2e/issuer.ts @@ -109,9 +109,9 @@ export async function testE2E( nTokens: number, mTLS?: MTLSConfiguration ): Promise { - const client = new Client(MODE); - const origin = new Origin(MODE); - console.log(nTokens) + // const client = new Client(MODE); + // const origin = new Origin(MODE); + // console.log(nTokens) const issuerConfig: IssuerConfiguration = await getIssuerConfig(issuerName, mTLS); @@ -120,12 +120,13 @@ export async function testE2E( return testArbitraryBatchedRequest(issuerName, nTokens, mTLS); // return testArbitraryBatchedRequest(client, origin, issuerName, nTokens, mTLS); } else { - return testTokenRequest(client, origin, issuerName, mTLS); + return testTokenRequest(issuerName, mTLS); } } // TODO: Add type of client and origin -export async function testTokenRequest(client, origin, issuerName: string, mTLS?: MTLSConfiguration) { +export async function testTokenRequest(issuerName: string, mTLS?: MTLSConfiguration) { + const client = new Client(MODE); const redemptionContext = new Uint8Array(32); redemptionContext.fill(0xfe); const challenge = new TokenChallenge(TOKEN_TYPES.BLIND_RSA.value, issuerName, redemptionContext); diff --git a/test/index.test.ts b/test/index.test.ts index 285d22a..fc6c952 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -21,12 +21,15 @@ import { MediaType, PRIVATE_TOKEN_ISSUER_DIRECTORY, publicVerif, - TOKEN_TYPES, + arbitraryBatched, util, + TokenChallenge, + TOKEN_TYPES } from '@cloudflare/privacypass-ts'; import { getDirectoryCache } from '../src/cache'; import { shouldRotateKey, shouldClearKey } from '../src/utils/keyRotation'; const { TokenRequest, BLIND_RSA } = publicVerif; +const { TokenRequest: batchedTokenRequest, Client: BatchedTokensClient, BatchedTokenResponse, BatchedTokenRequest } = arbitraryBatched; const sampleURL = 'http://localhost'; @@ -36,25 +39,32 @@ const keyToTokenKeyID = async (key: Uint8Array): Promise => { return u8[u8.length - 1]; }; + describe('challenge handlers', () => { const suite = RSABSSA.SHA384.PSS.Deterministic(); - const tokenRequestURL = `${sampleURL}/token-request`; const mockPrivateKey = async (ctx: Context): Promise => { const keypair = await suite.generateKey({ publicExponent: Uint8Array.from([1, 0, 1]), modulusLength: 2048, }); - const publicKey = new Uint8Array( - (await crypto.subtle.exportKey('spki', keypair.publicKey)) as ArrayBuffer - ); - const publicKeyEnc = b64ToB64URL(u8ToB64(util.convertEncToRSASSAPSS(publicKey))); - const tokenKey = await keyToTokenKeyID(new TextEncoder().encode(publicKeyEnc)); + + const publicKeyRaw = await crypto.subtle.exportKey("spki", keypair.publicKey); + const privateKeyRaw = await crypto.subtle.exportKey("pkcs8", keypair.privateKey); + const publicKeyBase64 = Buffer.from(new Uint8Array(publicKeyRaw as ArrayBuffer)).toString("base64"); + const privateKeyBase64 = Buffer.from(new Uint8Array(privateKeyRaw as ArrayBuffer)).toString("base64"); + + // Convert the raw public key, then derive the key from the converted bytes. + const publicKeyRawBytes = Uint8Array.from(Buffer.from(publicKeyBase64, "base64")); + const convertedPublicKey = util.convertEncToRSASSAPSS(publicKeyRawBytes); + const tokenKey = await keyToTokenKeyID(convertedPublicKey); + await ctx.env.ISSUANCE_KEYS.put( tokenKey.toString(), - (await crypto.subtle.exportKey('pkcs8', keypair.privateKey)) as ArrayBuffer, - { customMetadata: { publicKey: publicKeyEnc } } + privateKeyRaw as ArrayBuffer, + { customMetadata: { publicKey: publicKeyBase64 } } ); + return keypair; }; @@ -70,15 +80,15 @@ describe('challenge handlers', () => { const preparedMsg = suite.prepare(message); const { publicKey } = await mockPrivateKey(ctx); const { blindedMsg, inv } = await suite.blind(publicKey, preparedMsg); + const publicKeyExport = new Uint8Array( (await crypto.subtle.exportKey('spki', publicKey)) as ArrayBuffer ); - const publicKeyU8 = util.convertEncToRSASSAPSS(publicKeyExport); - const publicKeyEnc = b64ToB64URL(u8ToB64(publicKeyU8)); - const tokenKeyId = await keyToTokenKeyID(new TextEncoder().encode(publicKeyEnc)); + const publicKeyConverted = util.convertEncToRSASSAPSS(publicKeyExport); + const tokenKeyId = await keyToTokenKeyID(publicKeyConverted); // note that blindedMsg should be the payload and not the message directly - const tokenRequest = new TokenRequest(tokenKeyId, blindedMsg, TOKEN_TYPES.BLIND_RSA); + const tokenRequest = new TokenRequest(tokenKeyId, blindedMsg, BLIND_RSA); const request = new Request(tokenRequestURL, { method: 'POST', @@ -108,38 +118,53 @@ describe('challenge handlers', () => { const message = new TextEncoder().encode(msgString); const preparedMsg = suite.prepare(message); const { publicKey } = await mockPrivateKey(ctx); - const { blindedMsg, inv } = await suite.blind(publicKey, preparedMsg); const publicKeyExport = new Uint8Array( (await crypto.subtle.exportKey('spki', publicKey)) as ArrayBuffer ); - const publicKeyU8 = util.convertEncToRSASSAPSS(publicKeyExport); - const publicKeyEnc = b64ToB64URL(u8ToB64(publicKeyU8)); - const tokenKeyId = await keyToTokenKeyID(new TextEncoder().encode(publicKeyEnc)); + const publicKeyConverted = util.convertEncToRSASSAPSS(publicKeyExport); + const tokenKeyId = await keyToTokenKeyID(publicKeyConverted); - // note that blindedMsg should be the payload and not the message directly - const tokenRequest = new TokenRequest(tokenKeyId, blindedMsg, BLIND_RSA); + const nTokens = 2; + const invs: Uint8Array[] = []; - // Create a batched token request - const batchedTokenRequest = new Uint8Array([ - ...tokenRequest.serialize(), - ...tokenRequest.serialize(), // Add another token request to the batch - ]); + const tokReqs = new Array(nTokens); + const batchedTokenReqs = new Array(nTokens); + + for (let i = 0; i < nTokens; i++) { + const { blindedMsg, inv } = await suite.blind(publicKey, preparedMsg); + tokReqs[i] = new publicVerif.TokenRequest(tokenKeyId, blindedMsg, BLIND_RSA); + batchedTokenReqs[i] = new arbitraryBatched.TokenRequest(tokReqs[i]); + invs.push(inv); + } + + const batchedTokenRequest = new arbitraryBatched.BatchedTokenRequest(batchedTokenReqs); const request = new Request(tokenRequestURL, { method: 'POST', headers: { 'content-type': MediaType.ARBITRARY_BATCHED_TOKEN_REQUEST }, - body: batchedTokenRequest, + body: batchedTokenRequest.serialize(), }); const response = await handleTokenRequest(ctx, request); + expect(response.status).toBe(200); // Could be 206 if some tokens failed + expect(response.headers.get('content-type')).toBe(MediaType.ARBITRARY_BATCHED_TOKEN_RESPONSE); - expect(response.status).toBe(200); - expect(response.headers.get('content-type')).toBe(MediaType.ARBITRARY_BATCHED_TOKEN_REQUEST); + const responseBytes = new Uint8Array(await response.arrayBuffer()); + const batchedTokenResponse = arbitraryBatched.BatchedTokenResponse.deserialize(responseBytes); - const blindSignature = new Uint8Array(await response.arrayBuffer()); - const signature = await suite.finalize(publicKey, preparedMsg, blindSignature, inv); - const isValid = await suite.verify(publicKey, signature, preparedMsg); - expect(isValid).toBe(true); + expect(batchedTokenResponse.tokenResponses.length).toBe(nTokens); + + // Verify each issued token + for (let i = 0; i < nTokens; i++) { + const tokenResponse = batchedTokenResponse.tokenResponses[i]; + expect(tokenResponse.tokenResponse).not.toBeNull(); + + const blindSignature = tokenResponse.tokenResponse!; + const signature = await suite.finalize(publicKey, preparedMsg, blindSignature, invs[i]); + + const isValid = await suite.verify(publicKey, signature, preparedMsg); + expect(isValid).toBe(true); + } }); });