From 8cfc91db24bdade04bcd102f14e3b2b61a043478 Mon Sep 17 00:00:00 2001 From: timonson Date: Mon, 14 Dec 2020 19:25:49 +0100 Subject: [PATCH 1/4] Add RS512 PS256 and PS512 --- algorithm.ts | 10 +++++++++- deps.ts | 2 +- signature.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- tests/test.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) diff --git a/algorithm.ts b/algorithm.ts index 8f0e924..44aef5f 100644 --- a/algorithm.ts +++ b/algorithm.ts @@ -3,7 +3,15 @@ * are described in the separate JSON Web Algorithms (JWA) specification: * https://www.rfc-editor.org/rfc/rfc7518 */ -export type Algorithm = "none" | "HS256" | "HS512" | "RS256"; +export type Algorithm = + | "none" + | "HS256" + | "HS512" + | "RS256" + | "RS512" + | "PS256" + | "PS512"; + export type AlgorithmInput = Algorithm | Array>; export function verify(algorithm: AlgorithmInput, jwtAlg: string): boolean { diff --git a/deps.ts b/deps.ts index bca1dee..0b56b2e 100644 --- a/deps.ts +++ b/deps.ts @@ -5,4 +5,4 @@ export { } from "https://deno.land/std@0.80.0/encoding/hex.ts"; export { HmacSha256 } from "https://deno.land/std@0.80.0/hash/sha256.ts"; export { HmacSha512 } from "https://deno.land/std@0.80.0/hash/sha512.ts"; -export { RSA } from "https://deno.land/x/god_crypto@v1.4.6/rsa.ts"; +export { RSA } from "https://deno.land/x/god_crypto@v1.4.7/rsa.ts"; diff --git a/signature.ts b/signature.ts index 008e29e..4820af7 100644 --- a/signature.ts +++ b/signature.ts @@ -53,7 +53,31 @@ async function encrypt( return new HmacSha512(key).update(message).toString(); case "RS256": return ( - await new RSA(RSA.parseKey(key)).sign(message, { hash: "sha256" }) + await new RSA(RSA.parseKey(key)).sign( + message, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" }, + ) + ).hex(); + case "RS512": + return ( + await new RSA(RSA.parseKey(key)).sign( + message, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha512" }, + ) + ).hex(); + case "PS256": + return ( + await new RSA(RSA.parseKey(key)).sign( + message, + { algorithm: "rsassa-pss", hash: "sha256" }, + ) + ).hex(); + case "PS512": + return ( + await new RSA(RSA.parseKey(key)).sign( + message, + { algorithm: "rsassa-pss", hash: "sha512" }, + ) ).hex(); default: assertNever( @@ -95,7 +119,28 @@ export async function verify({ return await new RSA(RSA.parseKey(key)).verify( convertHexToUint8Array(signature), signingInput, - { hash: "sha256" }, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" }, + ); + } + case "RS512": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha512" }, + ); + } + case "PS256": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pss", hash: "sha256" }, + ); + } + case "PS512": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pss", hash: "sha512" }, ); } default: diff --git a/tests/test.ts b/tests/test.ts index 6abb3bb..a5ad38d 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -437,6 +437,52 @@ Deno.test("[jwt] RS256 algorithm", async function (): Promise { assertEquals(receivedPayload, payload); }); +Deno.test("[jwt] RS512 algorithm", async function (): Promise { + const header = { alg: "RS512" as const, typ: "JWT" }; + const payload = { + sub: "1234567890", + name: "John Doe", + admin: true, + iat: 1516239022, + }; + const moduleDir = dirname(fromFileUrl(import.meta.url)); + const publicKey = await Deno.readTextFile(moduleDir + "/certs/public.pem"); + const privateKey = await Deno.readTextFile(moduleDir + "/certs/private.pem"); + const externallyVerifiedJwt = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.JlX3gXGyClTBFciHhknWrjo7SKqyJ5iBO0n-3S2_I7cIgfaZAeRDJ3SQEbaPxVC7X8aqGCOM-pQOjZPKUJN8DMFrlHTOdqMs0TwQ2PRBmVAxXTSOZOoEhD4ZNCHohYoyfoDhJDP4Qye_FCqu6POJzg0Jcun4d3KW04QTiGxv2PkYqmB7nHxYuJdnqE3704hIS56pc_8q6AW0WIT0W-nIvwzaSbtBU9RgaC7ZpBD2LiNE265UBIFraMDF8IAFw9itZSUCTKg1Q-q27NwwBZNGYStMdIBDor2Bsq5ge51EkWajzZ7ALisVp-bskzUsqUf77ejqX_CBAqkNdH1Zebn93A"; + const jwt = await create(header, payload, privateKey); + const receivedPayload = await verify( + jwt, + publicKey, + "RS512", + ); + assertEquals(jwt, externallyVerifiedJwt); + assertEquals(receivedPayload, payload); +}); + +Deno.test("[jwt] PS256 algorithm", async function (): Promise { + const header = { alg: "PS256" as const, typ: "JWT" }; + const payload = { + sub: "1234567890", + name: "John Doe", + admin: true, + iat: 1516239022, + }; + const moduleDir = dirname(fromFileUrl(import.meta.url)); + const publicKey = await Deno.readTextFile(moduleDir + "/certs/public.pem"); + const privateKey = await Deno.readTextFile(moduleDir + "/certs/private.pem"); + const externallyVerifiedJwt = + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hZnl5amPk_I3tb4O-Otci_5XZdVWhPlFyVRvcqSwnDo_srcysDvhhKOD01DigPK1lJvTSTolyUgKGtpLqMfRDXQlekRsF4XhAjYZTmcynf-C-6wO5EI4wYewLNKFGGJzHAknMgotJFjDi_NCVSjHsW3a10nTao1lB82FRS305T226Q0VqNVJVWhE4G0JQvi2TssRtCxYTqzXVt22iDKkXeZJARZ1paXHGV5Kd1CljcZtkNZYIGcwnj65gvuCwohbkIxAnhZMJXCLaVvHqv9l-AAUV7esZvkQR1IpwBAiDQJh4qxPjFGylyXrHMqh5NlT_pWL2ZoULWTg_TJjMO9TuQ"; + const jwt = await create(header, payload, privateKey); + const receivedPayload = await verify( + jwt, + publicKey, + "PS256", + ); + assertEquals(jwt, externallyVerifiedJwt); + assertEquals(receivedPayload, payload); +}); + Deno.test("[jwt] getNumericDate", function (): void { // A specific date: const t1 = getNumericDate(new Date("2020-01-01")); From 138eab6604ebebd82798fd69a1dde6b159796851 Mon Sep 17 00:00:00 2001 From: timonson Date: Tue, 22 Dec 2020 09:58:45 +0100 Subject: [PATCH 2/4] Make adjustments for RS512 PS256 and PS512 --- README.md | 3 ++ deps.ts | 2 +- signature.ts | 88 +++++++++++++++++++++++++++------------------------ tests/test.ts | 64 ++++++++++++++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index a024a15..c8874bf 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,9 @@ The following signature and MAC algorithms have been implemented: - HS256 (HMAC SHA-256) - HS512 (HMAC SHA-512) - RS256 (RSASSA-PKCS1-v1_5 SHA-256) +- RS512 (RSASSA-PKCS1-v1_5 SHA-512) +- PS256 (rsassa-pss SHA-256) +- PS512 (rsassa-pss SHA-512) - none ([_Unsecured JWTs_](https://tools.ietf.org/html/rfc7519#section-6)). ## Serialization diff --git a/deps.ts b/deps.ts index 0b56b2e..0bd3c68 100644 --- a/deps.ts +++ b/deps.ts @@ -5,4 +5,4 @@ export { } from "https://deno.land/std@0.80.0/encoding/hex.ts"; export { HmacSha256 } from "https://deno.land/std@0.80.0/hash/sha256.ts"; export { HmacSha512 } from "https://deno.land/std@0.80.0/hash/sha512.ts"; -export { RSA } from "https://deno.land/x/god_crypto@v1.4.7/rsa.ts"; +export { RSA } from "https://deno.land/x/god_crypto@v1.4.8/rsa.ts"; diff --git a/signature.ts b/signature.ts index 4820af7..8eee8bb 100644 --- a/signature.ts +++ b/signature.ts @@ -106,47 +106,53 @@ export async function verify({ algorithm: Algorithm; signingInput: string; }): Promise { - switch (algorithm) { - case "none": - case "HS256": - case "HS512": { - return safeCompare( - signature, - (await encrypt(algorithm, key, signingInput)), - ); - } - case "RS256": { - return await new RSA(RSA.parseKey(key)).verify( - convertHexToUint8Array(signature), - signingInput, - { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" }, - ); - } - case "RS512": { - return await new RSA(RSA.parseKey(key)).verify( - convertHexToUint8Array(signature), - signingInput, - { algorithm: "rsassa-pkcs1-v1_5", hash: "sha512" }, - ); - } - case "PS256": { - return await new RSA(RSA.parseKey(key)).verify( - convertHexToUint8Array(signature), - signingInput, - { algorithm: "rsassa-pss", hash: "sha256" }, - ); - } - case "PS512": { - return await new RSA(RSA.parseKey(key)).verify( - convertHexToUint8Array(signature), - signingInput, - { algorithm: "rsassa-pss", hash: "sha512" }, - ); + // Need to add a try...catch statement because the god_crypto library throws + // strings instead of Error objects. + try { + switch (algorithm) { + case "none": + case "HS256": + case "HS512": { + return safeCompare( + signature, + (await encrypt(algorithm, key, signingInput)), + ); + } + case "RS256": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" }, + ); + } + case "RS512": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha512" }, + ); + } + case "PS256": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pss", hash: "sha256" }, + ); + } + case "PS512": { + return await new RSA(RSA.parseKey(key)).verify( + convertHexToUint8Array(signature), + signingInput, + { algorithm: "rsassa-pss", hash: "sha512" }, + ); + } + default: + assertNever( + algorithm, + "no matching crypto algorithm in the header: " + algorithm, + ); } - default: - assertNever( - algorithm, - "no matching crypto algorithm in the header: " + algorithm, - ); + } catch (err) { + throw err instanceof Error ? err : new Error(err); } } diff --git a/tests/test.ts b/tests/test.ts index a5ad38d..f171be7 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -395,6 +395,15 @@ Deno.test({ Error, `The jwt's algorithm does not match the specified algorithm 'HS256'.`, ); + assertThrowsAsync( + async () => { + const jwtWithInvalidSignature = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzcXNrz0ogthfEd2o"; + await verify(jwtWithInvalidSignature, key, "HS256"); + }, + Error, + "The jwt's signature does not match the verification signature.", + ); }, }); @@ -435,6 +444,20 @@ Deno.test("[jwt] RS256 algorithm", async function (): Promise { ); assertEquals(jwt, externallyVerifiedJwt); assertEquals(receivedPayload, payload); + + assertThrowsAsync( + async () => { + const jwtWithInvalidSignature = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtlQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA"; + const receivedPayload2 = await verify( + jwtWithInvalidSignature, + publicKey, + "RS256", + ); + }, + Error, + `Decryption error`, + ); }); Deno.test("[jwt] RS512 algorithm", async function (): Promise { @@ -479,7 +502,46 @@ Deno.test("[jwt] PS256 algorithm", async function (): Promise { publicKey, "PS256", ); - assertEquals(jwt, externallyVerifiedJwt); + const receivedPayloadFromExternalJwt = await verify( + externallyVerifiedJwt, + publicKey, + "PS256", + ); + assertEquals(receivedPayload, payload); + assertEquals(receivedPayloadFromExternalJwt, payload); + + assertThrowsAsync( + async () => { + const jwtWithInvalidSignature = + "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hZnl5amPk_I3tb4O-Otci_5XZdVWhPlFyVRvcqSwnDo_srcysDvhhKOD01DigPK1lJvTSTolyUgKGtpLqMfRDXQlekRsF4XhAjYZTmcynf-C-6wO5EI4wYewLNKFGGJzHAknMgotJFjDi_NCVSjHsW3a10nTao1lB82FRS305T226Q0VqNVJVWhE4G0JQvi2TssRtCzXVt22iDKkXeZJARZ1paXHGV5Kd1CljcZtkNZYIGcwnj65gvuCwohbkIxAnhZMJXCLaVvHqv9l-AAUV7esZvkQR1IpwBAiDQJh4qxPjFGylyXrHMqh5NlT_pWL2ZoULWTg_TJjMO9TuQ"; + const receivedPayload2 = await verify( + jwtWithInvalidSignature, + publicKey, + "PS256", + ); + }, + Error, + `The jwt's signature does not match the verification signature.`, + ); +}); + +Deno.test("[jwt] PS512 algorithm", async function (): Promise { + const header = { alg: "PS512" as const, typ: "JWT" }; + const payload = { + sub: "1234567890", + name: "John Doe", + admin: true, + iat: 1516239022, + }; + const moduleDir = dirname(fromFileUrl(import.meta.url)); + const publicKey = await Deno.readTextFile(moduleDir + "/certs/public.pem"); + const privateKey = await Deno.readTextFile(moduleDir + "/certs/private.pem"); + const jwt = await create(header, payload, privateKey); + const receivedPayload = await verify( + jwt, + publicKey, + "PS512", + ); assertEquals(receivedPayload, payload); }); From 9f8caa691a79bda9447e85df493fd5fb87630d0f Mon Sep 17 00:00:00 2001 From: timonson Date: Tue, 22 Dec 2020 10:15:06 +0100 Subject: [PATCH 3/4] - --- .gitignore | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index db054f7..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -egg.json From 9716984928f95b71dfe227d7fc6b0ce4f8938f0c Mon Sep 17 00:00:00 2001 From: timonson Date: Thu, 21 Jan 2021 08:22:10 +0100 Subject: [PATCH 4/4] Bump versions --- .github/workflows/test.yml | 2 +- deps.ts | 8 ++++---- examples/example_deps.ts | 6 +++--- tests/test_deps.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec923c6..aa54552 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,5 +9,5 @@ jobs: - uses: actions/checkout@master - uses: denolib/setup-deno@master with: - deno-version: v1.6.0 + deno-version: v1.7.0 - run: deno test -A diff --git a/deps.ts b/deps.ts index 0bd3c68..8846c9c 100644 --- a/deps.ts +++ b/deps.ts @@ -1,8 +1,8 @@ -export * as base64url from "https://deno.land/std@0.80.0/encoding/base64url.ts"; +export * as base64url from "https://deno.land/std@0.84.0/encoding/base64url.ts"; export { decodeString as convertHexToUint8Array, encodeToString as convertUint8ArrayToHex, -} from "https://deno.land/std@0.80.0/encoding/hex.ts"; -export { HmacSha256 } from "https://deno.land/std@0.80.0/hash/sha256.ts"; -export { HmacSha512 } from "https://deno.land/std@0.80.0/hash/sha512.ts"; +} from "https://deno.land/std@0.84.0/encoding/hex.ts"; +export { HmacSha256 } from "https://deno.land/std@0.84.0/hash/sha256.ts"; +export { HmacSha512 } from "https://deno.land/std@0.84.0/hash/sha512.ts"; export { RSA } from "https://deno.land/x/god_crypto@v1.4.8/rsa.ts"; diff --git a/examples/example_deps.ts b/examples/example_deps.ts index 511bdf8..8ffb07f 100644 --- a/examples/example_deps.ts +++ b/examples/example_deps.ts @@ -1,3 +1,3 @@ -export { serve } from "https://deno.land/std@0.80.0/http/server.ts"; -export { decode, encode } from "https://deno.land/std@0.80.0/encoding/utf8.ts"; -export { dirname, fromFileUrl } from "https://deno.land/std@0.80.0/path/mod.ts"; +export { serve } from "https://deno.land/std@0.84.0/http/server.ts"; +export { decode, encode } from "https://deno.land/std@0.84.0/encoding/utf8.ts"; +export { dirname, fromFileUrl } from "https://deno.land/std@0.84.0/path/mod.ts"; diff --git a/tests/test_deps.ts b/tests/test_deps.ts index 60c2d4e..8ce7cae 100644 --- a/tests/test_deps.ts +++ b/tests/test_deps.ts @@ -2,6 +2,6 @@ export { assertEquals, assertThrows, assertThrowsAsync, -} from "https://deno.land/std@0.80.0/testing/asserts.ts"; +} from "https://deno.land/std@0.84.0/testing/asserts.ts"; -export { dirname, fromFileUrl } from "https://deno.land/std@0.80.0/path/mod.ts"; +export { dirname, fromFileUrl } from "https://deno.land/std@0.84.0/path/mod.ts";