Skip to content

Commit

Permalink
Merge pull request #44 from timonson/alg-patch1
Browse files Browse the repository at this point in the history
Add RS512, PS256 and PS512
  • Loading branch information
timonson authored Jan 21, 2021
2 parents f56ad16 + 9716984 commit eb22a8b
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion .gitignore

This file was deleted.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exclude<Algorithm, "none">>;

export function verify(algorithm: AlgorithmInput, jwtAlg: string): boolean {
Expand Down
10 changes: 5 additions & 5 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -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";
export { RSA } from "https://deno.land/x/[email protected].6/rsa.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/[email protected].8/rsa.ts";
6 changes: 3 additions & 3 deletions examples/example_deps.ts
Original file line number Diff line number Diff line change
@@ -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";
93 changes: 72 additions & 21 deletions signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -82,26 +106,53 @@ export async function verify({
algorithm: Algorithm;
signingInput: string;
}): Promise<boolean> {
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,
{ hash: "sha256" },
);
// 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);
}
}
108 changes: 108 additions & 0 deletions tests/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
);
},
});

Expand Down Expand Up @@ -435,6 +444,105 @@ Deno.test("[jwt] RS256 algorithm", async function (): Promise<void> {
);
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<void> {
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<void> {
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",
);
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<void> {
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);
});

Deno.test("[jwt] getNumericDate", function (): void {
Expand Down
4 changes: 2 additions & 2 deletions tests/test_deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

0 comments on commit eb22a8b

Please sign in to comment.