From dd7842353e2c79ab80e503537fc88293072a898c Mon Sep 17 00:00:00 2001 From: "Visal .In" Date: Thu, 20 Aug 2020 16:51:53 +0700 Subject: [PATCH] Add RS256 sign support --- src/binary.ts | 7 ++++++ src/rsa/common.ts | 2 +- src/rsa/mod.ts | 17 ++++++++++++++ src/rsa/rsa_internal.ts | 27 ++++++++++++++++++++-- src/rsa/rsa_js.ts | 12 ++++++++++ tests/rsa/rsa.verify.test.ts | 45 ++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/binary.ts b/src/binary.ts index b40e8d6..ba9cb41 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -11,6 +11,13 @@ export class RawBinary extends Uint8Array { return btoa(String.fromCharCode.apply(null, [...this])); } + base64url(): string { + let a = btoa(String.fromCharCode.apply(null, [...this])).replace(/=/g, ""); + a = a.replace(/\+/g, "-"); + a = a.replace(/\//g, "_"); + return a; + } + base32(): string { const lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; const trim = [0x0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff]; diff --git a/src/rsa/common.ts b/src/rsa/common.ts index f1dcc83..27e5bd7 100644 --- a/src/rsa/common.ts +++ b/src/rsa/common.ts @@ -16,6 +16,6 @@ export interface RSAOption { } export interface RSASignOption { - hash: "sha1" | "sha256"; + hash: "sha256"; algorithm: "rsassa-pkcs1-v1_5"; } diff --git a/src/rsa/mod.ts b/src/rsa/mod.ts index b663aab..2b8ca6d 100644 --- a/src/rsa/mod.ts +++ b/src/rsa/mod.ts @@ -75,6 +75,23 @@ export class RSA { ); } + async sign( + message: Uint8Array | string, + options?: Partial, + ): Promise { + const computedOption: RSASignOption = { + ...options, + algorithm: "rsassa-pkcs1-v1_5", + hash: "sha256", + }; + + return await PureRSA.sign( + this.key, + computeMessage(message), + computedOption, + ); + } + static parseKey(key: string): RSAKey { if (key.indexOf("-----BEGIN RSA PRIVATE KEY-----") === 0) { const trimmedKey = key.substr(31, key.length - 61); diff --git a/src/rsa/rsa_internal.ts b/src/rsa/rsa_internal.ts index 543a2eb..6154da4 100644 --- a/src/rsa/rsa_internal.ts +++ b/src/rsa/rsa_internal.ts @@ -3,6 +3,7 @@ import { eme_oaep_encode, eme_oaep_decode } from "./eme_oaep.ts"; import { os2ip, i2osp } from "./primitives.ts"; import { concat, random_bytes } from "./../helper.ts"; import { ber_decode, ber_simple } from "./basic_encoding_rule.ts"; +import { RawBinary } from "../binary.ts"; /** * @param n public key modulus @@ -120,5 +121,27 @@ export function rsa_pkcs1_verify( return true; } -// 3031300d0609608648016503040201050004208041fb8cba9e4f8cc1483790b05262841f27fdcb211bc039ddf8864374db5f53 -// 8041fb8cba9e4f8cc1483790b05262841f27fdcb211bc039ddf8864374db5f53 +export function rsa_pkcs1_sign( + bytes: number, + n: bigint, + d: bigint, + message: Uint8Array, +): RawBinary { + // deno-fmt-ignore + const oid = [0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00] + const der = [ + 0x30, + message.length + 2 + oid.length, + ...oid, + 0x04, + message.length, + ...message, + ]; + + const ps = new Array(bytes - 3 - der.length).fill(0xff); + const em = new Uint8Array([0x00, 0x01, ...ps, 0x00, ...der]); + + const msg = os2ip(em); + const c = rsaep(n, d, msg); + return new RawBinary(i2osp(c, bytes)); +} diff --git a/src/rsa/rsa_js.ts b/src/rsa/rsa_js.ts index f64a17e..a87fad4 100644 --- a/src/rsa/rsa_js.ts +++ b/src/rsa/rsa_js.ts @@ -4,6 +4,7 @@ import { rsa_oaep_decrypt, rsa_pkcs1_decrypt, rsa_pkcs1_verify, + rsa_pkcs1_sign, } from "./rsa_internal.ts"; import { RawBinary } from "./../binary.ts"; import { RSAKey, RSAOption, RSASignOption } from "./common.ts"; @@ -66,4 +67,15 @@ export class PureRSA { createHash(options.hash).update(message).digest(), ); } + + static async sign(key: RSAKey, message: Uint8Array, options: RSASignOption) { + if (!key.d) throw "You need private key to sign the message"; + + return rsa_pkcs1_sign( + key.length, + key.n, + key.d, + createHash(options.hash).update(message).digest(), + ); + } } diff --git a/tests/rsa/rsa.verify.test.ts b/tests/rsa/rsa.verify.test.ts index 083ee88..c1e19be 100644 --- a/tests/rsa/rsa.verify.test.ts +++ b/tests/rsa/rsa.verify.test.ts @@ -29,3 +29,48 @@ Deno.test("Verify RSASSA-PKSC1-v1_5", async () => { assertEquals(verified, true); }); + +Deno.test("Sign RSASSA-PKSC1-v1_5", async () => { + const privateKey = `-----BEGIN RSA PRIVATE KEY----- + MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw + kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr + m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi + NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV + 3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2 + QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs + kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go + amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM + +bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9 + D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC + 0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y + lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+ + hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp + bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X + +jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B + BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC + 2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx + QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz + 5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9 + Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0 + NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j + 8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma + 3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K + y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB + jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE= + -----END RSA PRIVATE KEY-----`; + + const key = RSA.parseKey(privateKey); + const rsa = new RSA(key); + let expectedSignature = + "POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6" + + "tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdg" + + "XMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8" + + "y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA"; + + const signature = await rsa.sign( + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0", + { algorithm: "rsassa-pkcs1-v1_5", hash: "sha256" }, + ); + + assertEquals(signature.base64url(), expectedSignature); +});