diff --git a/README.md b/README.md index bf35c63..dd8b40f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ npm install @peculiar/webcrypto | HMAC | X | | X | X | | | | | RSASSA-PKCS1-v1_5 | X | | X | X | | | | | RSA-PSS | X | | X | X | | | | +| RSA-PKCS12| X | | X | X | X | X | | | RSA-OAEP | X | | X | | X | X | | | AES-CMAC | X | | X | X | | | | | AES-CBC | X | | X | | X | X | | diff --git a/src/mechs/rsa/crypto.ts b/src/mechs/rsa/crypto.ts index 137118c..1ebc2bb 100644 --- a/src/mechs/rsa/crypto.ts +++ b/src/mechs/rsa/crypto.ts @@ -19,7 +19,7 @@ export class RsaCrypto { public static publicKeyUsages = ["verify", "encrypt", "wrapKey"]; public static privateKeyUsages = ["sign", "decrypt", "unwrapKey"]; - public static async generateKey(algorithm: RsaHashedKeyGenParams, extractable: boolean, keyUsages: string[]): Promise { + public static async generateKey(algorithm: RsaHashedKeyGenParams | RsaKeyGenParams, extractable: boolean, keyUsages: string[]): Promise { const privateKey = new RsaPrivateKey(); privateKey.algorithm = algorithm as RsaHashedKeyAlgorithm; privateKey.extractable = extractable; diff --git a/src/mechs/rsa/helper.ts b/src/mechs/rsa/helper.ts index dff4d5f..5b374f2 100644 --- a/src/mechs/rsa/helper.ts +++ b/src/mechs/rsa/helper.ts @@ -9,6 +9,8 @@ export function getJwkAlgorithm(algorithm: RsaHashedKeyAlgorithm) { return `RS${/(\d+)$/.exec(algorithm.hash.name)![1]}`; case "RSA-PSS": return `PS${/(\d+)$/.exec(algorithm.hash.name)![1]}`; + case "RSA-PKCS1": + return `RS1`; default: throw new core.OperationError("algorithm: Is not recognized"); } diff --git a/src/mechs/rsa/index.ts b/src/mechs/rsa/index.ts index b077c10..7d805ce 100644 --- a/src/mechs/rsa/index.ts +++ b/src/mechs/rsa/index.ts @@ -3,3 +3,4 @@ export * from "./public_key"; export * from "./rsa_ssa"; export * from "./rsa_pss"; export * from "./rsa_oaep"; +export * from "./rsa_pkcs1"; diff --git a/src/mechs/rsa/rsa_pkcs1.ts b/src/mechs/rsa/rsa_pkcs1.ts new file mode 100644 index 0000000..6f86d96 --- /dev/null +++ b/src/mechs/rsa/rsa_pkcs1.ts @@ -0,0 +1,134 @@ +import * as crypto from "crypto"; +import { Convert } from "pvtsutils"; +import * as core from "webcrypto-core"; +import { CryptoKey } from "../../keys"; +import { RsaCrypto } from "./crypto"; +import { RsaPrivateKey } from "./private_key"; +import { RsaPublicKey } from "./public_key"; + +export type RsaPkcs1Params = Algorithm; +export type RsaPkcs1SignParams = core.HashedAlgorithm; + +export class RsaPkcs1Provider extends core.ProviderCrypto { + + public name = "RSA-PKCS1"; + public usages = { + publicKey: ["encrypt", "wrapKey", "verify"] as core.KeyUsages, + privateKey: ["decrypt", "unwrapKey", "sign"] as core.KeyUsages, + }; + public hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; + + public async onGenerateKey(algorithm: RsaKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const key = await RsaCrypto.generateKey( + { + ...algorithm, + name: this.name, + }, + extractable, + keyUsages); + + return key; + } + + public checkGenerateKeyParams(algorithm: RsaKeyGenParams) { + // public exponent + this.checkRequiredProperty(algorithm, "publicExponent"); + if (!(algorithm.publicExponent && algorithm.publicExponent instanceof Uint8Array)) { + throw new TypeError("publicExponent: Missing or not a Uint8Array"); + } + const publicExponent = Convert.ToBase64(algorithm.publicExponent); + if (!(publicExponent === "Aw==" || publicExponent === "AQAB")) { + throw new TypeError("publicExponent: Must be [3] or [1,0,1]"); + } + + // modulus length + this.checkRequiredProperty(algorithm, "modulusLength"); + switch (algorithm.modulusLength) { + case 1024: + case 2048: + case 4096: + break; + default: + throw new TypeError("modulusLength: Must be 1024, 2048, or 4096"); + } + } + + public async onSign(algorithm: RsaPkcs1SignParams, key: RsaPrivateKey, data: ArrayBuffer): Promise { + const signature = crypto + .createSign((algorithm.hash as Algorithm).name.replace("-", "")) + .update(Buffer.from(data)) + .sign(this.toCryptoOptions(key) as any); + return new Uint8Array(signature).buffer; + } + + public checkSign(algorithm: RsaPkcs1SignParams, key: CryptoKey, data: ArrayBuffer) { + this.checkAlgorithmName(algorithm); + this.checkAlgorithmSignParams(algorithm); + this.checkCryptoKey(key, "sign"); + } + + public checkAlgorithmSignParams(algorithm: RsaPkcs1SignParams) { + this.checkRequiredProperty(algorithm, "hash"); + this.checkHashAlgorithm(algorithm.hash as Algorithm, this.hashAlgorithms); + } + + public async onVerify(algorithm: RsaPkcs1SignParams, key: RsaPublicKey, signature: ArrayBuffer, data: ArrayBuffer): Promise { + const ok = crypto + .createVerify((algorithm.hash as Algorithm).name.replace("-", "")) + .update(Buffer.from(data)) + .verify(this.toCryptoOptions(key) as any, Buffer.from(signature)); + return ok; + } + + public checkVerify(algorithm: RsaPkcs1SignParams, key: CryptoKey, signature: ArrayBuffer, data: ArrayBuffer) { + this.checkAlgorithmName(algorithm); + this.checkAlgorithmSignParams(algorithm); + this.checkCryptoKey(key, "verify"); + } + + public async onEncrypt(algorithm: RsaPkcs1Params, key: RsaPublicKey, data: ArrayBuffer): Promise { + const options = this.toCryptoOptions(key); + const enc = crypto.publicEncrypt(options, new Uint8Array(data)); + return new Uint8Array(enc).buffer; + } + + public async onDecrypt(algorithm: RsaPkcs1Params, key: RsaPrivateKey, data: ArrayBuffer): Promise { + const options = this.toCryptoOptions(key); + const dec = crypto.privateDecrypt(options, new Uint8Array(data)); + return new Uint8Array(dec).buffer; + } + + public async onExportKey(format: KeyFormat, key: CryptoKey): Promise { + return RsaCrypto.exportKey(format, key); + } + + public async onImportKey(format: KeyFormat, keyData: JsonWebKey | ArrayBuffer, algorithm: RsaHashedImportParams, extractable: boolean, keyUsages: KeyUsage[]): Promise { + const key = await RsaCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); + return key; + } + + public checkCryptoKey(key: CryptoKey, keyUsage?: KeyUsage) { + super.checkCryptoKey(key, keyUsage); + if (!(key instanceof RsaPrivateKey || key instanceof RsaPublicKey)) { + throw new TypeError("key: Is not RSA CryptoKey"); + } + } + + private toCryptoOptions(key: RsaPrivateKey): crypto.RsaPrivateKey; + private toCryptoOptions(key: RsaPublicKey): crypto.RsaPublicKey; + private toCryptoOptions(key: RsaPrivateKey | RsaPublicKey) { + const type = key.type.toUpperCase(); + return { + key: `-----BEGIN ${type} KEY-----\n${key.data.toString("base64")}\n-----END ${type} KEY-----`, + // @ts-ignore + padding: crypto.constants.RSA_PKCS1_PADDING, + }; + } + + private prepareSignData(algorithm: RsaPkcs1SignParams, data: ArrayBuffer) { + return crypto + .createHash((algorithm.hash as Algorithm).name.replace("-", "")) + .update(Buffer.from(data)) + .digest(); + } +} diff --git a/src/subtle.ts b/src/subtle.ts index 53428ee..79ab90d 100644 --- a/src/subtle.ts +++ b/src/subtle.ts @@ -6,7 +6,7 @@ import { EcdsaProvider, HkdfProvider, HmacProvider, Pbkdf2Provider, - RsaOaepProvider, RsaPssProvider, RsaSsaProvider, + RsaOaepProvider, RsaPkcs1Provider, RsaPssProvider, RsaSsaProvider, Sha1Provider, Sha256Provider, Sha384Provider, Sha512Provider, } from "./mechs"; @@ -31,6 +31,7 @@ export class SubtleCrypto extends core.SubtleCrypto { this.providers.set(new RsaSsaProvider()); this.providers.set(new RsaPssProvider()); this.providers.set(new RsaOaepProvider()); + this.providers.set(new RsaPkcs1Provider()); //#endregion //#region EC diff --git a/test/rsa.ts b/test/rsa.ts index f714312..37910e9 100644 --- a/test/rsa.ts +++ b/test/rsa.ts @@ -300,6 +300,114 @@ context("RSA", () => { ], }, }, + // RSA-PKCS1 + { + name: "RSA-PKCS1", + actions: { + generateKey: [ + { + algorithm: { + name: "RSA-PKCS1", + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 1024, + } as RsaKeyGenParams, + extractable: false, + keyUsages: ["encrypt", "decrypt"], + } as ITestGenerateKeyAction, + ], + encrypt: [ + { + algorithm: { + name: "RSA-PKCS1", + } as Algorithm, + data: Convert.FromHex("01435e62ad3ec4850720e34f8cab620e203749f2315b203d"), + encData: Convert.FromHex("76e5ea6e1df52471454f790923f60e2baa7adf5017fe0a36c0af3e32f6390d570e1d592375ba6035fdf4ffa70764b797ab54d0ab1efe89cf31d7fc98240a4d08c2476b7eb4c2d92355b8bf60e3897c3fcbfe09f20c7b159d9a9c4a6b2ce5021dd313e492afa762c24930f97f03a429f7b2b1e1d6088651d60e323835807c6fefe7952f74e5da29e8e327ea46e69a0a6684272f022bf18ec602ffcd10a62666b35a51ec7c7d101096f663ddfa0924a86bdbcde0433b4f71dc42bfd9facf329558026f8667f1a71c3365e09843a12339d8aaf31987b0d800e53fd0835e990096cb145e278153faf1188cd5713c6fcd289cb77d80515e1d200139b8ccac4d3bcebc"), + key: { + publicKey: { + format: "jwk" as KeyFormat, + algorithm: { name: "RSA-PKCS1" } as Algorithm, + data: { + alg: "RS1", + e: "AQAB", + ext: true, + key_ops: ["encrypt"], + kty: "RSA", + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + }, + extractable: true, + keyUsages: ["encrypt"] as KeyUsage[], + }, + privateKey: { + format: "jwk" as KeyFormat, + algorithm: { name: "RSA-PKCS1"} as Algorithm, + data: { + kty: "RSA", + alg: "RS1", + key_ops: ["decrypt"], + ext: true, + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + e: "AQAB", + d: "kZ2IoQ3G7UcshMdL8kC85vadW7wktldLtkqqf1qSVIo6cOfTJCWJe5yrWPG_VIJjfkeQgOh2hHKRjcV67HfwwWEZr-IrPMu6R1_DRPSxYdohiNUnUEi7TlkJ1tT882OF74rWQeaIZIS13wzjUk7_XjKWHsfO1d6t9dwWbiYx1nj4syQCcUrvHIgVXCfL85Tyu3NHqpxOdbzRb2OLmkv5ciHFExm4ai98xAgsEXbNvZQeSOOfKNsiCb-NjBXLYrbaDIsakAEV75893JubfeD51UHn7dPT8M8MmKEvrTOKCscShf01scTDHfx_hiOXK3XG4tVx9l2YGEkt3xCedljocQ", + p: "_dWMJ57SECcBbOjPRCvT97ypDyw9ydvnSZXTsn9c7ScxvUxBk6-wuMtgsLI8OWkhZGDBLyVrn-I3RMAN-A5QI_adoGdK7fq5lFWmQYvb1u1xUaGEInVFsM3BW7RBBF8N7OzHwULEQLTXb4jkpgwyCynsX0OEbVVvVerqrcr7osM", + q: "yHEjuQe9TNo-leMrL6cu-yDPfA85M8xQuBM59Cwz06-ggBRi9EOpbV-CrejGUbVlE9QmKGqIBT8C3NVBQwybzlgUihgIpnVgkb01lLEf13ohQ_GWV1mS8ybznjMgaVtVF5Lva4WixIDlXbOu4svVQpkr-KRpKvEMUCTsX-Sxx7k", + dp: "jMP4TaCN7dczuyoAh1Wm3yQIvRlTyrXgtbYZCEwJRJsPwmKfmz87Sb-_hz3QmCXtFrVxbKvb23agH8hB9uY5GziQgXvG2eLJN7Gn2YGuEKrsxNBFbraKR1pTeH-l7r6oAlPtEwfrvdaMApZv9oWc2wQMyWev8NIIRCVar7Z5hfE", + dq: "wi2g3sJZp9cRpGEDWFHM2KnrdxLEZqK7W-f8T8h2mM9eXFXjmyDlRLivP0zuuv9QoUn3gVXa2cI2QrsxUwQm-Fop47Hux1uUpvs2qgqBf1yoV0r2Sz7Sdk442fxLnOVG5OSKno5Cpbz89q54cOvoeHEswN59p4UHWai7eRZzB7k", + qi: "k9hlEyvZCWj8Fvxrknj5WHgaLrSqaVku3PVod2wUJox3aZ8vUsGmmD27lfiWwVKNRmgxLiazY40pLPu07SEmlJgF8QjzDb33k5Pcn9wRuezcCi-53LBRK6-EptZ-UjEINBlM_Cx_WOuxs7P77pwcCo2NV76ilxP5PP_34SUZ0ts", + }, + extractable: true, + keyUsages: ["decrypt"] as KeyUsage[], + }, + }, + }, + ], + sign: [ + { + algorithm: { + name: "RSA-PKCS1", + hash: "SHA-256", + } as Algorithm, + data: Convert.FromUtf8String("12345678901234567890"), // SHA-1 hash + signature: Convert.FromHex("3538e16a7181ea028f2bdb3e295e24b701635607c32802a3f8dfb7489f8469ddbf2095d221cf9aaeb7274c45e7e7fc127cc67d01d0954bf71a2b6c7e18f3449cf09ddb1eb85aea687e36ac4747deeb896583992d66216298b3ef85b799afec281298da777aa2eb0dbd71d80c0258be01566dd37f99022e98c7e59b8fb0e619b3990a7d120e31ac15ec39643f8f4ae3dd9282cb2c68ff9c364eb9a2caa28053e8008d531ba6c559187ebafd931554b94bb630ab96e0abffa824a10791eef3716fe1853260331173d3a1b04e5cd644423985b90d73828a7d7091519e9821eb12f2eb7d86bf6569f06cc6526931018b1deaba4e8b2dd8d02f7bdd8c53cef4f04a93"), + key: { + publicKey: { + format: "jwk" as KeyFormat, + algorithm: { name: "RSA-PKCS1" } as Algorithm, + data: { + alg: "RS1", + e: "AQAB", + ext: true, + key_ops: ["verify"], + kty: "RSA", + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + }, + extractable: true, + keyUsages: ["verify"] as KeyUsage[], + }, + privateKey: { + format: "jwk" as KeyFormat, + algorithm: { name: "RSA-PKCS1"} as Algorithm, + data: { + kty: "RSA", + alg: "RS1", + key_ops: ["sign"], + ext: true, + n: "xr8ELXq5dGFycys8jrc8vVPkWl2GzuRgyOxATtjcNIy5MD7j1XVsUH62VVdIVUUGt0IQ7K288ij3gkIPcIkRO6GmV0vbQAqHrjSHYUAtKQXbIgNRIuJGZvO5AXsxSo1X-tfhOxe140pseOkaehz1bGduhdcYWNR3xLmp7i-GQTRDo-v6CQXtFvSUwG_EIOXnl1trN2Q1Yw4wA1dbtY9FDz69uH-dEWTx7BFCAXVTQMjNe7BTvgGeQcX7XZIw5e2pd0pXjdIgb0xMgziwmc5bbABrGlhK7TmKqA47RlWzY_Lcj7VcTUfMfh7YKKichGTUbqxlgsRTma_e-0-vgDEz6w", + e: "AQAB", + d: "kZ2IoQ3G7UcshMdL8kC85vadW7wktldLtkqqf1qSVIo6cOfTJCWJe5yrWPG_VIJjfkeQgOh2hHKRjcV67HfwwWEZr-IrPMu6R1_DRPSxYdohiNUnUEi7TlkJ1tT882OF74rWQeaIZIS13wzjUk7_XjKWHsfO1d6t9dwWbiYx1nj4syQCcUrvHIgVXCfL85Tyu3NHqpxOdbzRb2OLmkv5ciHFExm4ai98xAgsEXbNvZQeSOOfKNsiCb-NjBXLYrbaDIsakAEV75893JubfeD51UHn7dPT8M8MmKEvrTOKCscShf01scTDHfx_hiOXK3XG4tVx9l2YGEkt3xCedljocQ", + p: "_dWMJ57SECcBbOjPRCvT97ypDyw9ydvnSZXTsn9c7ScxvUxBk6-wuMtgsLI8OWkhZGDBLyVrn-I3RMAN-A5QI_adoGdK7fq5lFWmQYvb1u1xUaGEInVFsM3BW7RBBF8N7OzHwULEQLTXb4jkpgwyCynsX0OEbVVvVerqrcr7osM", + q: "yHEjuQe9TNo-leMrL6cu-yDPfA85M8xQuBM59Cwz06-ggBRi9EOpbV-CrejGUbVlE9QmKGqIBT8C3NVBQwybzlgUihgIpnVgkb01lLEf13ohQ_GWV1mS8ybznjMgaVtVF5Lva4WixIDlXbOu4svVQpkr-KRpKvEMUCTsX-Sxx7k", + dp: "jMP4TaCN7dczuyoAh1Wm3yQIvRlTyrXgtbYZCEwJRJsPwmKfmz87Sb-_hz3QmCXtFrVxbKvb23agH8hB9uY5GziQgXvG2eLJN7Gn2YGuEKrsxNBFbraKR1pTeH-l7r6oAlPtEwfrvdaMApZv9oWc2wQMyWev8NIIRCVar7Z5hfE", + dq: "wi2g3sJZp9cRpGEDWFHM2KnrdxLEZqK7W-f8T8h2mM9eXFXjmyDlRLivP0zuuv9QoUn3gVXa2cI2QrsxUwQm-Fop47Hux1uUpvs2qgqBf1yoV0r2Sz7Sdk442fxLnOVG5OSKno5Cpbz89q54cOvoeHEswN59p4UHWai7eRZzB7k", + qi: "k9hlEyvZCWj8Fvxrknj5WHgaLrSqaVku3PVod2wUJox3aZ8vUsGmmD27lfiWwVKNRmgxLiazY40pLPu07SEmlJgF8QjzDb33k5Pcn9wRuezcCi-53LBRK6-EptZ-UjEINBlM_Cx_WOuxs7P77pwcCo2NV76ilxP5PP_34SUZ0ts", + }, + extractable: true, + keyUsages: ["sign"] as KeyUsage[], + }, + }, + }, + ], + }, + }, ]); });