Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(crypto): limit RSA key size to <= 8192 bits #1931

Merged
merged 3 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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