Skip to content

Commit

Permalink
fix(crypto): limit RSA key size to <= 8192 bits (#1931)
Browse files Browse the repository at this point in the history
Restrict the RSA key sizes we expect from peers. Protects us from spending a lot of compute on verifying signatures from big keys. Similar to libp2p/go-libp2p#2454

---------

Co-authored-by: achingbrain <alex@achingbrain.net>
  • Loading branch information
MarcoPolo and achingbrain authored Aug 4, 2023
1 parent 8f681db commit 58421e1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 3 deletions.
10 changes: 10 additions & 0 deletions packages/crypto/src/keys/rsa-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
23 changes: 23 additions & 0 deletions packages/crypto/src/keys/rsa-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -135,21 +137,42 @@ export class RsaPrivateKey {

export async function unmarshalRsaPrivateKey (bytes: Uint8Array): Promise<RsaPrivateKey> {
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<RsaPrivateKey> {
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<RsaPrivateKey> {
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)
}
10 changes: 10 additions & 0 deletions packages/crypto/src/keys/rsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
23 changes: 23 additions & 0 deletions packages/crypto/test/fixtures/rsa.ts
Original file line number Diff line number Diff line change
@@ -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'
}
}
21 changes: 18 additions & 3 deletions packages/crypto/test/keys/rsa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down

0 comments on commit 58421e1

Please sign in to comment.