diff --git a/packages/crypto/src/keys/rsa-browser.ts b/packages/crypto/src/keys/rsa-browser.ts index 193913aa9a..2f0e0c04b1 100644 --- a/packages/crypto/src/keys/rsa-browser.ts +++ b/packages/crypto/src/keys/rsa-browser.ts @@ -155,3 +155,13 @@ export function encrypt (key: JsonWebKey, msg: Uint8Array): Uint8Array { export function decrypt (key: JsonWebKey, msg: Uint8Array): Uint8Array { return convertKey(key, false, msg, (msg, key) => key.decrypt(msg)) } + +export function keySize (jwk: JsonWebKey): number { + if (jwk.kty !== 'RSA') { + throw new CodeError('invalid key type', 'ERR_INVALID_KEY_TYPE') + } else if (jwk.n == null) { + throw new CodeError('invalid key modulus', 'ERR_INVALID_KEY_MODULUS') + } + const bytes = uint8ArrayFromString(jwk.n, 'base64url') + return bytes.length * 8 +} diff --git a/packages/crypto/src/keys/rsa-class.ts b/packages/crypto/src/keys/rsa-class.ts index 83582db8a4..83640ca26b 100644 --- a/packages/crypto/src/keys/rsa-class.ts +++ b/packages/crypto/src/keys/rsa-class.ts @@ -10,6 +10,8 @@ import * as pbm from './keys.js' import * as crypto from './rsa.js' import type { Multibase } from 'multiformats' +export const MAX_KEY_SIZE = 8192 + export class RsaPublicKey { private readonly _key: JsonWebKey @@ -135,21 +137,42 @@ export class RsaPrivateKey { export async function unmarshalRsaPrivateKey (bytes: Uint8Array): Promise { const jwk = crypto.utils.pkcs1ToJwk(bytes) + + if (crypto.keySize(jwk) > MAX_KEY_SIZE) { + throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + } + const keys = await crypto.unmarshalPrivateKey(jwk) + return new RsaPrivateKey(keys.privateKey, keys.publicKey) } export function unmarshalRsaPublicKey (bytes: Uint8Array): RsaPublicKey { const jwk = crypto.utils.pkixToJwk(bytes) + + if (crypto.keySize(jwk) > MAX_KEY_SIZE) { + throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + } + return new RsaPublicKey(jwk) } export async function fromJwk (jwk: JsonWebKey): Promise { + if (crypto.keySize(jwk) > MAX_KEY_SIZE) { + throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + } + const keys = await crypto.unmarshalPrivateKey(jwk) + return new RsaPrivateKey(keys.privateKey, keys.publicKey) } export async function generateKeyPair (bits: number): Promise { + if (bits > MAX_KEY_SIZE) { + throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + } + const keys = await crypto.generateKey(bits) + return new RsaPrivateKey(keys.privateKey, keys.publicKey) } diff --git a/packages/crypto/src/keys/rsa.ts b/packages/crypto/src/keys/rsa.ts index a2411ef382..1d43afc925 100644 --- a/packages/crypto/src/keys/rsa.ts +++ b/packages/crypto/src/keys/rsa.ts @@ -67,3 +67,13 @@ export function decrypt (key: JsonWebKey, bytes: Uint8Array): Uint8Array { // @ts-expect-error node types are missing jwk as a format return crypto.privateDecrypt({ format: 'jwk', key, padding }, bytes) } + +export function keySize (jwk: JsonWebKey): number { + if (jwk.kty !== 'RSA') { + throw new CodeError('invalid key type', 'ERR_INVALID_KEY_TYPE') + } else if (jwk.n == null) { + throw new CodeError('invalid key modulus', 'ERR_INVALID_KEY_MODULUS') + } + const modulus = Buffer.from(jwk.n, 'base64') + return modulus.length * 8 +} diff --git a/packages/crypto/test/fixtures/rsa.ts b/packages/crypto/test/fixtures/rsa.ts new file mode 100644 index 0000000000..2f941850bd --- /dev/null +++ b/packages/crypto/test/fixtures/rsa.ts @@ -0,0 +1,23 @@ +import type { JWKKeyPair } from '../../src/keys/interface' + +/** + * A 8200 bit RSA key + */ +export const RSA_KEY_8200_BITS: JWKKeyPair = { + privateKey: { + kty: 'RSA', + n: 'AtSHgbWZ-5MHNh8LpVD4-2lOjLitwGjADZ8gEcs3EqskGl1E7Lna8oGRt6V-0yiGwicgNgWPVRW3M4aK8kXe9izb1O43I20A2QVfmZDZB7inS23hrj9PZs863x497-WMdiyBBa3SVrg0StU_vQ8iUFYi5ihQr0b58IKJhVHA6AhqpuUeCbbTakoH1btzOlfqWb6chlL2bT-1KY1EUSRN25Qrfk5T7wV59QvbkGMUuHJRreh6caTASf3cGEG6nk_2YXTiZ6Yyc5XzR0hNervbwa9WVPc19iCP9fNJ8T6oFmJsJ-T9MnEQqttDSOYGwhnzleV4FWrv0jiWhC1-LMdJ-rdHVXPUH9pKArXwWu1FZBk8EG4o3uAB5rcxgk7Z_5sV3PAXQlAISZl3IwauWcDaWIgckDUU3AmzpdrddPn8IiqVtSpcTMISi2MkKJw1iom97iQk8V0dkPvCpFB1wzOoEJfL-gcl61ePeU5xohzy0eQcwjsGAtuK6XgT_TB_u7Q6_FbS0AwySISYz6JsnV-FHsb-0FMUjj6-jF0A3vb7El43xwZUrAhx7qqn_DuvrztjChsNWyE7dKWDceTwxIHDobEG7LDivMvsq-jkRYIzYoMJabrIzTgRjGBLruxBp8Zl1CuOepD4vFO6JBzbZl5Qy0hrpuP9YAlYBg3sBhwsYcrATZWVYcNRkJDsNIo-lSFNOl8DalFm7IsxW0A6AMuysLzm4oLZe2hDrmfRI9Mh5JBgio8cKetZan-0pLpoU5ezcMdIszlaUC4ggbOa2h7wVZCanTO79VoeiZLIrCimL4hbtlegpiX6A792jaD5G-cmPhMRGqn2rZcbwIx61e1z3Lz_Wag4lc8I2UwSMFUaIFi67OWN9608JgNNv2-W2uj5NpTFNJSBGUO6gfdOt1C-dgAafhXyPyyQ9m1Uq_0JMwaOkqkkTjW5UNKBacx8Hi9S2AVX7Ln9DN2mNFtFZVCmpnS2qKwd53yhcGQWr_1QL3kshTH5-hkpnLr7ntWIf094NGClp9l5Y2BOB-NqO7JecD_3WHxzjh_3jx6yAiSc3FqX9zmNmiOyIlENc6GHDe10a6p3NNZkI3jTO6C6fQfzOFCQ544htxF7JvxahKLazpSIBRkbf7Hxgdo5s8p8kH5S8HTpl5hdCOsMveJjH0rYU801iTF7Sv5rqn_Zd9FGkT98mjhGl8O4fhEHd967-GR9KupALmG9LtfA2jKwipAcwxyhaXFg2hobCpSvHx9KLOW-3gQt93PqNoE9TAGyIlYbHBFyN3IDsH5bKDYkL4C7RlXUFogBq64SpljjXoW3gYmH2guEaXMwMLf7Mogaoi0Or4ubitxd8EvFuhH2W1TekVc', + e: 'AQAB', + d: 'o85TLBy5PR-XQ-OdJ7ZzGPbyZ9qYstYg_kYA3-H-rYTHFSk9IDP5OgQY8ii_Ut1Mzg3BSPaD9RnrY1LMvbFQFQpKlOUQdFeJuWZI0O_QiBYCvsAUZinsg5O62BqGv25HVX6CKy9v_cuM07PKab6HTUIsqRa82h4uG0U-SCKGE0kRnKFy7svTrxMgZnYBzdilG6xFzkH6pzmtQEwe0EduCozezJS7aJCPL3Qfq9cGcIo4-GOeh_IViHiXX1zy5_87P3LknjWv3koXZqhlxE1F9_9bzFx93hTONYgVK-ZS0UPErUf1fIyZJGOM-ryya62fyTaiGQBJlhOppKNvOktmjMNq9XoTDYZIIJxL4nMa03259T32Tc0APZDK9krhOE30GrAXhQKqqTC_UYZfsnNrKrLSg73IlcTxDY-eH9q6WshoIkCl9ga9Ttfg-pg8TzIKwk875qKFofX6slo9nNM1lSw_xhfzomNEuNCGJ2davuklnLSaNsqsSEK0rp9ib7gaTwLwmKFRtfDamM1Xsz3knHcF4JK4KkVodKp6vno7R-PycmL4Vhx0Kk86fsAUrSeSJFWbseLAV59lQ66Prwrs5po7Ly7EIpLKtyfM80fs0NVy-xDlHPUSuL7DuJSLexaQppxa56Xk5kUiXwfXmCFKJIB6RfeGG_azv7gKFZqPLYgUBJf0yL-nSUa3SoqsS31SbHnFSGLs9Xb0IwX_450Z906Vrva-KNK6N5t4u_Q6S5ZcSrlho7zxcwrpGUWUuqcnuuAVv21qFbO7PshrWhvC_wkXdSrgisRwTxPwCR1OVZ5mTYdWBt5c2mZDVsxmdqLCMoupDOH75eI_FbxuHY82IiSWeWlKTC9Nn0EfXxiin5TuBKMXdJkGLujD66TAkoORG6-E4yx-yQYBD_EuA2niSTmCOFn1H8GHItO0hkY8hd7WOM6PUM7bcJFIALMsCm--ya_-x-uwTU381mY9iliYFKw41GHmTEdA-04igfpjgsI9U5fzLxMYi7ku2fI_NmRtWznY6QU51GxZ9gWnKEVgneowI9fWxi-nAZ0Fl6CMSPSJhurKPAPXgIEYKA-V5ORbzTS5YKMMs5t05lVqSLdO00XBFjuyyeSqAP0X0b3Iy7o5v9S8TTOdKE9KnWR3zda7rwlT-pV_XVHegzclMcpNoRZu68mYIK5r40_mQJfygQBoRpqUyb2lvac3K4o4gVGH82i1YPscDZZvd9iqFAq3YpxutsJSuj3TsRGrYatq0xdgHA1mF1RE1_JN2x4FD2_5QAXLLoGwlLiz6-uhqBT6e3aDNChD8aIFDP88WD7KMjUbzheqt1w2Ym4TMnszfkPru4I45ObIOiN7AoDIwFjOuQ', + p: 'Aa5Enbkyn0LpdzDYDmoxJx3bIrhaZ-lINx0eJTqSsjCLtdoV53Ya1dZOPFNY0z1yF1ckL81M7CiTLzetlzqERK-y13yWe194yti8IFtmyA53eAAx4xB70fpL0dIfXvLHar5456UdeaaQkUYm2xAxaQ5SYYwHMOJoMt-SaLq-WSD6d8ej6dtcBjKfX6vnlZGeeVBQUfSuYYO5gMFTlBqvgltkvWJ-FJjfQtHXDM7mooC3RO6hjiGvbpkzAQVj5fR6tZNoeN-Cb7IW0BWQ01UWdJeutl-4Mxn-1byFBgP1Na801yVNpLR-bE145BwfrZVAUVMqPaskcthClhIHCr3NrDGyp06xfONPUyxTmntQvTD9S6fyOzqoxBLz-vB91eDjeZPvt0ULPtZQx9thfLUzxt_fI_uiqVEeh8NEfco442OVij1ciJvREap5mSFzbj_GzLWsZOxUdB65T-1PzXizcMvUIThzLlxJhgpfEEIUC7hOPKjBDTMImrQbFoXJXCiuxy-PGj79ORvbSH5klM3T90KMSoWg_sUozP_fUcUCFAnQEOWHT3-CROzjhox8T5CLzpYUW2BTKCAFm9CMmRTZsDl8y7PhiXmTB3imQRN9Sc7pTTVsj_kYKgaOHOc7Rzuwb3KVDsHAkSBvPPatZJkvLktt-wTxYFXpz772L6EzuGdd', + q: 'Aa8UOGhm4KQPVD8gQ29WMjUEoDy3otlOI-fQvI6haOX73SI-8gkuG2U70DVWD08yUQMpCGEpJ5dykhrtXC5xruGqIySkO6RousTB2uad6TIpUpn_GCbbaf_Hb2T_dXJd7cX06cTIf78vXC_o1fWuODAGG-k6uit0mrvC8l69MEQ2CL5Dr29sH6DyNXyhw8NnG45sRIcjiwXyHlrmSJUlrd_HcHXU0JK_2gMyaXB_rFnazDShJ2Td7bRRPUmlVvWQgZ-X8Rw6jL0Zfy4Vsp7yaYnLbi1GKjb-13r1GpAm1ECAVH13jDW8rpM608v2tN-fgYG3eh9SUX5cjNQ6tyqGje_abXl3og4t7wzvLCXwhb5L5nMDrk2XQrzZWuhQBlcYoaLlaks7w6DK9eq_T4JNywtB7PuRMYMOHaQ_p54xzz0PH2ZhRTCCitVoxLmt1S5Ib47DSDjmy4-9ghyluH0M5CfPo5IHY0SOBBG-JF0IpH_LIU5JEChVoKUXLBGoTH2pc5Wy4riFfgcVXmn1JPikAuw0IwQjgrOMvBj4BaN9H3z152pOSyxQeI9LBnWWXrDDtSve7ws4SKiuVmpGvp6S-zWDMw_9hIKoD5WQ5Z85LrJZhZJub9jfyaaz6YiGfh8p8tNSiBfqEN_lbKOLQzE9d7lsozqNMmsCXqmTlOlqZVRD', + dp: 'AaFreAifj9p-XN_J-9IR9X0Fh12Kd5zeLnnVMRDuGGj4YWg-L9tolWPfaq8BUY4fDX3A4Y8uvT1v7TL6-egPQgOiYWPBKdepfEFf4ZRK1nMSfBzUSRFIzqEgxWIwhRDLXkeDTFfnjImHXyjLsWK4TM9UJMaQg2Bi7lfv1iK1YAqac3H_F3V0hZ6-9zXy6ivnrpG4GopiUsBUSxFDYD-zXagEx6ax4pGcp1qkYwymu6hcJEfN-_G6HHYMAUWVcRYJpfZoODu-c1Y2w5Doe93kdyBgoxWhwhHKJNlLxFY6oeZkQQUzmkJxE-jlC4gtQ120lDE2nbVXGwOrNaQvqMAStKdSRvYVQq_T2UD7qrdP0PuiAK_iVIvIBtDMWfrOWZxfkyduPEKDcmbA3-N5ZF726E1wUZxiZhkIoXVTESYKoq-N-BsnlnB0F8tnRKQrNB4zY44svYx5Ml5MMbps3U3n59oW5lO5ipSFz8BoEYHJqMUZQcgF4iRMiKmKTgNlIj9lsF_3WOMbDzhzOJVGRwaYSR0KtZGGayQjPR5PLSkPpxkN_hZvezSc0PlwkqvgPeBhgf4fqbZ9dkP6rPtaK3kK5-gdphHREGodtfXadO_PcLeHpxvZQqPKSZsqa-f4nlbOCJEFAtf9Vc49nMtD1IC4QRV0kT5uXaN7vcvpUrP1VUUR', + dq: 'rdjbvs7ufXtpIGQkjfwXF2acMKBmXniy5kQ6JtNVeJqQXcVA2w7rIXJzz267kdba8QlVRcnRG7Sq040yBdD3FC8HKTnKi81otPzxCNxaNU6Q72X_GXyXTP3jILodZVgYEiNpO2EYk8PHy9J8py3xnvx3uSFj_y5xUJOYJzjpBDk-YWzujWLvhnrnszGRv3YPmOp04IMnB-jS8Rm539xoOL03z21aCDSy-WMVPrdejIY-oGL1fio6OOQicVbqsPHsNK6UICxEoeZscetyM8PTaCzQbBXF5JP11rKOWeAu7SxT5p2Vv_4t8VZiH_mIjD7JfcS-zW7nSqyMZvKe99l32GkgiUID6u__Xhn-lfZgGZSGhY_QdZ4w3fRSQyoyxGE8nnMi4OBjTq9LabZpnEU_Q3T82598djv1HE5HjPbNevRkV2eW_a9HyjUMUU2XkajIxKxgrgH1yixFEsSKmHPgd2W3s6ajE_yqC3XBOHvJy3fiIK46g-m0dZ_Yt-5FmtABuzd_U6cSYkzt3JFurY-HVjbYgEzJ4xs4qGEt9Pb7AewxvZ-BlYeGd1NscXOJEIR8xgqMINw8ATr9wrJxIYZpJPWaXDKDhCW-0zSyRfpLqMWNprY_CRmHO2GHJvYGWw8RzMOV-v78ey8NFw-Ms4j0haUYVv6mfJ1iC8Vm4pNz9f0', + qi: 'VWzU4gk0YHzmtUbU9IKfjGDgWaCY5H_hhUQ3oz9rK-WU1IOrrnpH8ltjLSlcvimpL_Nw2G3JxyY8A1IrsFrTgj6_wqexMEyia_j-62AxNtspt4-lHXbLGxvxv4KswKD88mvFdBloomxEcb5j32v_YX_gQfl6znJBR27dbeU8dyRmbH-WOyM8p9lunZRIbQ7UnSAIWlswN60XvNiqFjVrjAWB5rvAu_F0pAv_raWE46OqHFNBWncOsp2GGoNZa_hOsx_4ORaz3YO2qAqMCwkXpux-YJmAG3sXtzVnr7-dV3R7T5Rz-IkUJJMPwXnlW568j9-lH3xnNEQ8uwmaYFChMYTPUAoa2tF-YHwtvKaRUMuoCUwiqKdgNybtLsBewjbFygrJsiMiYjmmIMzwPGqJfVtXkosolHErGWoGCv19vASbNBwB31zE0GBBMculZ6vOYqq7YKN46MIcCvujxlAUdXyg4-3VoqgHBXYSPj0M2y1LeQ2YXtGWaX5i6g8coKdPcWg5gUu38H_J2RKJiyWYwei9nz3fLB_xsl7cS1a2Wfm30HMa_-wWgwb1XfzLRYQKVACu6yBU7-lx1tq0I2D6s8ufurJRjEj6Uu7NPxP3lmeS2FIxkfXDQkXiqpq2FZu4Bj6cV9BDnXVvBBBeNcqBrvnpFeX25IGGKMEyYn6s9UI' + }, + publicKey: { + kty: 'RSA', + n: 'AtSHgbWZ-5MHNh8LpVD4-2lOjLitwGjADZ8gEcs3EqskGl1E7Lna8oGRt6V-0yiGwicgNgWPVRW3M4aK8kXe9izb1O43I20A2QVfmZDZB7inS23hrj9PZs863x497-WMdiyBBa3SVrg0StU_vQ8iUFYi5ihQr0b58IKJhVHA6AhqpuUeCbbTakoH1btzOlfqWb6chlL2bT-1KY1EUSRN25Qrfk5T7wV59QvbkGMUuHJRreh6caTASf3cGEG6nk_2YXTiZ6Yyc5XzR0hNervbwa9WVPc19iCP9fNJ8T6oFmJsJ-T9MnEQqttDSOYGwhnzleV4FWrv0jiWhC1-LMdJ-rdHVXPUH9pKArXwWu1FZBk8EG4o3uAB5rcxgk7Z_5sV3PAXQlAISZl3IwauWcDaWIgckDUU3AmzpdrddPn8IiqVtSpcTMISi2MkKJw1iom97iQk8V0dkPvCpFB1wzOoEJfL-gcl61ePeU5xohzy0eQcwjsGAtuK6XgT_TB_u7Q6_FbS0AwySISYz6JsnV-FHsb-0FMUjj6-jF0A3vb7El43xwZUrAhx7qqn_DuvrztjChsNWyE7dKWDceTwxIHDobEG7LDivMvsq-jkRYIzYoMJabrIzTgRjGBLruxBp8Zl1CuOepD4vFO6JBzbZl5Qy0hrpuP9YAlYBg3sBhwsYcrATZWVYcNRkJDsNIo-lSFNOl8DalFm7IsxW0A6AMuysLzm4oLZe2hDrmfRI9Mh5JBgio8cKetZan-0pLpoU5ezcMdIszlaUC4ggbOa2h7wVZCanTO79VoeiZLIrCimL4hbtlegpiX6A792jaD5G-cmPhMRGqn2rZcbwIx61e1z3Lz_Wag4lc8I2UwSMFUaIFi67OWN9608JgNNv2-W2uj5NpTFNJSBGUO6gfdOt1C-dgAafhXyPyyQ9m1Uq_0JMwaOkqkkTjW5UNKBacx8Hi9S2AVX7Ln9DN2mNFtFZVCmpnS2qKwd53yhcGQWr_1QL3kshTH5-hkpnLr7ntWIf094NGClp9l5Y2BOB-NqO7JecD_3WHxzjh_3jx6yAiSc3FqX9zmNmiOyIlENc6GHDe10a6p3NNZkI3jTO6C6fQfzOFCQ544htxF7JvxahKLazpSIBRkbf7Hxgdo5s8p8kH5S8HTpl5hdCOsMveJjH0rYU801iTF7Sv5rqn_Zd9FGkT98mjhGl8O4fhEHd967-GR9KupALmG9LtfA2jKwipAcwxyhaXFg2hobCpSvHx9KLOW-3gQt93PqNoE9TAGyIlYbHBFyN3IDsH5bKDYkL4C7RlXUFogBq64SpljjXoW3gYmH2guEaXMwMLf7Mogaoi0Or4ubitxd8EvFuhH2W1TekVc', + e: 'AQAB' + } +} diff --git a/packages/crypto/test/keys/rsa.spec.ts b/packages/crypto/test/keys/rsa.spec.ts index 27c9740b54..657ead0937 100644 --- a/packages/crypto/test/keys/rsa.spec.ts +++ b/packages/crypto/test/keys/rsa.spec.ts @@ -3,14 +3,13 @@ import { expect } from 'aegir/chai' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import * as crypto from '../../src/index.js' -import { RsaPrivateKey } from '../../src/keys/rsa-class.js' +import { MAX_KEY_SIZE, RsaPrivateKey, RsaPublicKey } from '../../src/keys/rsa-class.js' import fixtures from '../fixtures/go-key-rsa.js' +import { RSA_KEY_8200_BITS } from '../fixtures/rsa.js' import { testGarbage } from '../helpers/test-garbage-error-handling.js' const rsa = crypto.keys.supportedKeys.rsa -/** @typedef {import('libp2p-crypto').keys.supportedKeys.rsa.RsaPrivateKey} RsaPrivateKey */ - describe('RSA', function () { this.timeout(20 * 1000) let key: RsaPrivateKey @@ -25,6 +24,22 @@ describe('RSA', function () { expect(digest).to.have.length(34) }) + it('does not generate a big key', async () => { + await expect(rsa.generateKeyPair(MAX_KEY_SIZE + 1)).to.be.rejected() + }) + + it('does not unmarshal a big key', async () => { + const k = RSA_KEY_8200_BITS + const sk = new RsaPrivateKey(k.privateKey, k.publicKey) + const pubk = new RsaPublicKey(k.publicKey) + const m = sk.marshal() + const pubm = pubk.marshal() + + await expect(rsa.unmarshalRsaPrivateKey(m)).to.eventually.be.rejectedWith(/too large/) + expect(() => rsa.unmarshalRsaPublicKey(pubm)).to.throw(/too large/) + await expect(rsa.fromJwk(k.privateKey)).to.eventually.be.rejectedWith(/too large/) + }) + it('signs', async () => { const text = key.genSecret() const sig = await key.sign(text)