@libp2p/crypto 3.0.4 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/index.min.js +72 -14
  2. package/dist/src/index.d.ts +0 -2
  3. package/dist/src/index.d.ts.map +1 -1
  4. package/dist/src/index.js +0 -2
  5. package/dist/src/index.js.map +1 -1
  6. package/dist/src/keys/ed25519-browser.d.ts +1 -1
  7. package/dist/src/keys/ed25519-browser.d.ts.map +1 -1
  8. package/dist/src/keys/index.d.ts +4 -2
  9. package/dist/src/keys/index.d.ts.map +1 -1
  10. package/dist/src/keys/index.js +7 -12
  11. package/dist/src/keys/index.js.map +1 -1
  12. package/dist/src/keys/rsa-browser.d.ts +0 -2
  13. package/dist/src/keys/rsa-browser.d.ts.map +1 -1
  14. package/dist/src/keys/rsa-browser.js +0 -25
  15. package/dist/src/keys/rsa-browser.js.map +1 -1
  16. package/dist/src/keys/rsa-class.d.ts +6 -5
  17. package/dist/src/keys/rsa-class.d.ts.map +1 -1
  18. package/dist/src/keys/rsa-class.js +11 -25
  19. package/dist/src/keys/rsa-class.js.map +1 -1
  20. package/dist/src/keys/rsa-utils.d.ts +15 -2
  21. package/dist/src/keys/rsa-utils.d.ts.map +1 -1
  22. package/dist/src/keys/rsa-utils.js +304 -39
  23. package/dist/src/keys/rsa-utils.js.map +1 -1
  24. package/dist/src/keys/rsa.d.ts +0 -2
  25. package/dist/src/keys/rsa.d.ts.map +1 -1
  26. package/dist/src/keys/rsa.js +2 -22
  27. package/dist/src/keys/rsa.js.map +1 -1
  28. package/dist/src/pbkdf2.d.ts +1 -1
  29. package/dist/src/pbkdf2.d.ts.map +1 -1
  30. package/dist/src/pbkdf2.js +14 -10
  31. package/dist/src/pbkdf2.js.map +1 -1
  32. package/dist/src/util.d.ts +0 -7
  33. package/dist/src/util.d.ts.map +1 -1
  34. package/dist/src/util.js +0 -25
  35. package/dist/src/util.js.map +1 -1
  36. package/dist/src/webcrypto-browser.d.ts +5 -0
  37. package/dist/src/webcrypto-browser.d.ts.map +1 -0
  38. package/dist/src/webcrypto-browser.js +17 -0
  39. package/dist/src/webcrypto-browser.js.map +1 -0
  40. package/dist/src/webcrypto.d.ts +3 -1
  41. package/dist/src/webcrypto.d.ts.map +1 -1
  42. package/dist/src/webcrypto.js +4 -11
  43. package/dist/src/webcrypto.js.map +1 -1
  44. package/dist/typedoc-urls.json +9 -4
  45. package/package.json +7 -14
  46. package/src/index.ts +0 -2
  47. package/src/keys/ed25519-browser.ts +1 -1
  48. package/src/keys/index.ts +10 -12
  49. package/src/keys/rsa-browser.ts +0 -29
  50. package/src/keys/rsa-class.ts +11 -28
  51. package/src/keys/rsa-utils.ts +373 -39
  52. package/src/keys/rsa.ts +2 -23
  53. package/src/pbkdf2.ts +17 -15
  54. package/src/util.ts +0 -29
  55. package/src/webcrypto-browser.ts +24 -0
  56. package/src/webcrypto.ts +5 -18
  57. package/dist/src/aes/cipher-mode.d.ts +0 -2
  58. package/dist/src/aes/cipher-mode.d.ts.map +0 -1
  59. package/dist/src/aes/cipher-mode.js +0 -13
  60. package/dist/src/aes/cipher-mode.js.map +0 -1
  61. package/dist/src/aes/ciphers-browser.d.ts +0 -7
  62. package/dist/src/aes/ciphers-browser.d.ts.map +0 -1
  63. package/dist/src/aes/ciphers-browser.js +0 -26
  64. package/dist/src/aes/ciphers-browser.js.map +0 -1
  65. package/dist/src/aes/ciphers.d.ts +0 -5
  66. package/dist/src/aes/ciphers.d.ts.map +0 -1
  67. package/dist/src/aes/ciphers.js +0 -4
  68. package/dist/src/aes/ciphers.js.map +0 -1
  69. package/dist/src/aes/index.d.ts +0 -50
  70. package/dist/src/aes/index.d.ts.map +0 -1
  71. package/dist/src/aes/index.js +0 -61
  72. package/dist/src/aes/index.js.map +0 -1
  73. package/dist/src/keys/jwk2pem.d.ts +0 -8
  74. package/dist/src/keys/jwk2pem.d.ts.map +0 -1
  75. package/dist/src/keys/jwk2pem.js +0 -14
  76. package/dist/src/keys/jwk2pem.js.map +0 -1
  77. package/src/aes/cipher-mode.ts +0 -15
  78. package/src/aes/ciphers-browser.ts +0 -31
  79. package/src/aes/ciphers.ts +0 -4
  80. package/src/aes/index.ts +0 -70
  81. package/src/keys/jwk2pem.ts +0 -21
@@ -1,74 +1,408 @@
1
- import 'node-forge/lib/asn1.js'
2
- import 'node-forge/lib/rsa.js'
3
1
  import { CodeError } from '@libp2p/interface'
4
- // @ts-expect-error types are missing
5
- import forge from 'node-forge/lib/forge.js'
2
+ import { pbkdf2Async } from '@noble/hashes/pbkdf2'
3
+ import { sha512 } from '@noble/hashes/sha512'
4
+ import * as asn1js from 'asn1js'
6
5
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
6
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8
- import { bigIntegerToUintBase64url, base64urlToBigInteger } from './../util.js'
7
+ import randomBytes from '../random-bytes.js'
8
+ import webcrypto from '../webcrypto.js'
9
+ import { type RsaPrivateKey, unmarshalRsaPrivateKey } from './rsa-class.js'
9
10
 
10
- // Convert a PKCS#1 in ASN1 DER format to a JWK key
11
+ /**
12
+ * Convert a PKCS#1 in ASN1 DER format to a JWK key
13
+ */
11
14
  export function pkcs1ToJwk (bytes: Uint8Array): JsonWebKey {
12
- const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
13
- const privateKey = forge.pki.privateKeyFromAsn1(asn1)
15
+ const { result } = asn1js.fromBER(bytes)
14
16
 
15
- // https://tools.ietf.org/html/rfc7518#section-6.3.1
16
- return {
17
+ // @ts-expect-error this looks fragile but DER is a canonical format so we are
18
+ // safe to have deeply property chains like this
19
+ const values: asn1js.Integer[] = result.valueBlock.value
20
+
21
+ const key = {
22
+ n: uint8ArrayToString(bnToBuf(values[1].toBigInt()), 'base64url'),
23
+ e: uint8ArrayToString(bnToBuf(values[2].toBigInt()), 'base64url'),
24
+ d: uint8ArrayToString(bnToBuf(values[3].toBigInt()), 'base64url'),
25
+ p: uint8ArrayToString(bnToBuf(values[4].toBigInt()), 'base64url'),
26
+ q: uint8ArrayToString(bnToBuf(values[5].toBigInt()), 'base64url'),
27
+ dp: uint8ArrayToString(bnToBuf(values[6].toBigInt()), 'base64url'),
28
+ dq: uint8ArrayToString(bnToBuf(values[7].toBigInt()), 'base64url'),
29
+ qi: uint8ArrayToString(bnToBuf(values[8].toBigInt()), 'base64url'),
17
30
  kty: 'RSA',
18
- n: bigIntegerToUintBase64url(privateKey.n),
19
- e: bigIntegerToUintBase64url(privateKey.e),
20
- d: bigIntegerToUintBase64url(privateKey.d),
21
- p: bigIntegerToUintBase64url(privateKey.p),
22
- q: bigIntegerToUintBase64url(privateKey.q),
23
- dp: bigIntegerToUintBase64url(privateKey.dP),
24
- dq: bigIntegerToUintBase64url(privateKey.dQ),
25
- qi: bigIntegerToUintBase64url(privateKey.qInv),
26
31
  alg: 'RS256'
27
32
  }
33
+
34
+ return key
28
35
  }
29
36
 
30
- // Convert a JWK key into PKCS#1 in ASN1 DER format
37
+ /**
38
+ * Convert a JWK key into PKCS#1 in ASN1 DER format
39
+ */
31
40
  export function jwkToPkcs1 (jwk: JsonWebKey): Uint8Array {
32
41
  if (jwk.n == null || jwk.e == null || jwk.d == null || jwk.p == null || jwk.q == null || jwk.dp == null || jwk.dq == null || jwk.qi == null) {
33
42
  throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS')
34
43
  }
35
44
 
36
- const asn1 = forge.pki.privateKeyToAsn1({
37
- n: base64urlToBigInteger(jwk.n),
38
- e: base64urlToBigInteger(jwk.e),
39
- d: base64urlToBigInteger(jwk.d),
40
- p: base64urlToBigInteger(jwk.p),
41
- q: base64urlToBigInteger(jwk.q),
42
- dP: base64urlToBigInteger(jwk.dp),
43
- dQ: base64urlToBigInteger(jwk.dq),
44
- qInv: base64urlToBigInteger(jwk.qi)
45
+ const root = new asn1js.Sequence({
46
+ value: [
47
+ new asn1js.Integer({ value: 0 }),
48
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.n, 'base64url'))),
49
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.e, 'base64url'))),
50
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.d, 'base64url'))),
51
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.p, 'base64url'))),
52
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.q, 'base64url'))),
53
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.dp, 'base64url'))),
54
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.dq, 'base64url'))),
55
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.qi, 'base64url')))
56
+ ]
45
57
  })
46
58
 
47
- return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
59
+ const der = root.toBER()
60
+
61
+ return new Uint8Array(der, 0, der.byteLength)
48
62
  }
49
63
 
50
- // Convert a PKCIX in ASN1 DER format to a JWK key
64
+ /**
65
+ * Convert a PKCIX in ASN1 DER format to a JWK key
66
+ */
51
67
  export function pkixToJwk (bytes: Uint8Array): JsonWebKey {
52
- const asn1 = forge.asn1.fromDer(uint8ArrayToString(bytes, 'ascii'))
53
- const publicKey = forge.pki.publicKeyFromAsn1(asn1)
68
+ const { result } = asn1js.fromBER(bytes)
69
+
70
+ // @ts-expect-error this looks fragile but DER is a canonical format so we are
71
+ // safe to have deeply property chains like this
72
+ const values: asn1js.Integer[] = result.valueBlock.value[1].valueBlock.value[0].valueBlock.value
54
73
 
55
74
  return {
56
75
  kty: 'RSA',
57
- n: bigIntegerToUintBase64url(publicKey.n),
58
- e: bigIntegerToUintBase64url(publicKey.e)
76
+ n: uint8ArrayToString(bnToBuf(values[0].toBigInt()), 'base64url'),
77
+ e: uint8ArrayToString(bnToBuf(values[1].toBigInt()), 'base64url')
59
78
  }
60
79
  }
61
80
 
62
- // Convert a JWK key to PKCIX in ASN1 DER format
81
+ /**
82
+ * Convert a JWK key to PKCIX in ASN1 DER format
83
+ */
63
84
  export function jwkToPkix (jwk: JsonWebKey): Uint8Array {
64
85
  if (jwk.n == null || jwk.e == null) {
65
86
  throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS')
66
87
  }
67
88
 
68
- const asn1 = forge.pki.publicKeyToAsn1({
69
- n: base64urlToBigInteger(jwk.n),
70
- e: base64urlToBigInteger(jwk.e)
89
+ const root = new asn1js.Sequence({
90
+ value: [
91
+ new asn1js.Sequence({
92
+ value: [
93
+ // rsaEncryption
94
+ new asn1js.ObjectIdentifier({
95
+ value: '1.2.840.113549.1.1.1'
96
+ }),
97
+ new asn1js.Null()
98
+ ]
99
+ }),
100
+ // this appears to be a bug in asn1js.js - this should really be a Sequence
101
+ // and not a BitString but it generates the same bytes as node-forge so 🤷‍♂️
102
+ new asn1js.BitString({
103
+ valueHex: new asn1js.Sequence({
104
+ value: [
105
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.n, 'base64url'))),
106
+ asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.e, 'base64url')))
107
+ ]
108
+ }).toBER()
109
+ })
110
+ ]
111
+ })
112
+
113
+ const der = root.toBER()
114
+
115
+ return new Uint8Array(der, 0, der.byteLength)
116
+ }
117
+
118
+ function bnToBuf (bn: bigint): Uint8Array {
119
+ let hex = bn.toString(16)
120
+
121
+ if (hex.length % 2 > 0) {
122
+ hex = `0${hex}`
123
+ }
124
+
125
+ const len = hex.length / 2
126
+ const u8 = new Uint8Array(len)
127
+
128
+ let i = 0
129
+ let j = 0
130
+
131
+ while (i < len) {
132
+ u8[i] = parseInt(hex.slice(j, j + 2), 16)
133
+ i += 1
134
+ j += 2
135
+ }
136
+
137
+ return u8
138
+ }
139
+
140
+ function bufToBn (u8: Uint8Array): bigint {
141
+ const hex: string[] = []
142
+
143
+ u8.forEach(function (i) {
144
+ let h = i.toString(16)
145
+
146
+ if (h.length % 2 > 0) {
147
+ h = `0${h}`
148
+ }
149
+
150
+ hex.push(h)
151
+ })
152
+
153
+ return BigInt('0x' + hex.join(''))
154
+ }
155
+
156
+ const SALT_LENGTH = 16
157
+ const KEY_SIZE = 32
158
+ const ITERATIONS = 10000
159
+
160
+ export async function exportToPem (privateKey: RsaPrivateKey, password: string): Promise<string> {
161
+ const crypto = webcrypto.get()
162
+
163
+ // PrivateKeyInfo
164
+ const keyWrapper = new asn1js.Sequence({
165
+ value: [
166
+ // version (0)
167
+ new asn1js.Integer({ value: 0 }),
168
+
169
+ // privateKeyAlgorithm
170
+ new asn1js.Sequence({
171
+ value: [
172
+ // rsaEncryption OID
173
+ new asn1js.ObjectIdentifier({
174
+ value: '1.2.840.113549.1.1.1'
175
+ }),
176
+ new asn1js.Null()
177
+ ]
178
+ }),
179
+
180
+ // PrivateKey
181
+ new asn1js.OctetString({
182
+ valueHex: privateKey.marshal()
183
+ })
184
+ ]
71
185
  })
72
186
 
73
- return uint8ArrayFromString(forge.asn1.toDer(asn1).getBytes(), 'ascii')
187
+ const keyBuf = keyWrapper.toBER()
188
+ const keyArr = new Uint8Array(keyBuf, 0, keyBuf.byteLength)
189
+ const salt = randomBytes(SALT_LENGTH)
190
+
191
+ const encryptionKey = await pbkdf2Async(
192
+ sha512,
193
+ password,
194
+ salt, {
195
+ c: ITERATIONS,
196
+ dkLen: KEY_SIZE
197
+ }
198
+ )
199
+
200
+ const iv = randomBytes(16)
201
+ const cryptoKey = await crypto.subtle.importKey('raw', encryptionKey, 'AES-CBC', false, ['encrypt'])
202
+ const encrypted = await crypto.subtle.encrypt({
203
+ name: 'AES-CBC',
204
+ iv
205
+ }, cryptoKey, keyArr)
206
+
207
+ const pbkdf2Params = new asn1js.Sequence({
208
+ value: [
209
+ // salt
210
+ new asn1js.OctetString({ valueHex: salt }),
211
+
212
+ // iteration count
213
+ new asn1js.Integer({ value: ITERATIONS }),
214
+
215
+ // key length
216
+ new asn1js.Integer({ value: KEY_SIZE }),
217
+
218
+ // AlgorithmIdentifier
219
+ new asn1js.Sequence({
220
+ value: [
221
+ // hmacWithSHA512
222
+ new asn1js.ObjectIdentifier({ value: '1.2.840.113549.2.11' }),
223
+ new asn1js.Null()
224
+ ]
225
+ })
226
+ ]
227
+ })
228
+
229
+ const encryptionAlgorithm = new asn1js.Sequence({
230
+ value: [
231
+ // pkcs5PBES2
232
+ new asn1js.ObjectIdentifier({
233
+ value: '1.2.840.113549.1.5.13'
234
+ }),
235
+ new asn1js.Sequence({
236
+ value: [
237
+ // keyDerivationFunc
238
+ new asn1js.Sequence({
239
+ value: [
240
+ // pkcs5PBKDF2
241
+ new asn1js.ObjectIdentifier({
242
+ value: '1.2.840.113549.1.5.12'
243
+ }),
244
+ // PBKDF2-params
245
+ pbkdf2Params
246
+ ]
247
+ }),
248
+
249
+ // encryptionScheme
250
+ new asn1js.Sequence({
251
+ value: [
252
+ // aes256-CBC
253
+ new asn1js.ObjectIdentifier({
254
+ value: '2.16.840.1.101.3.4.1.42'
255
+ }),
256
+ // iv
257
+ new asn1js.OctetString({
258
+ valueHex: iv
259
+ })
260
+ ]
261
+ })
262
+ ]
263
+ })
264
+ ]
265
+ })
266
+
267
+ const finalWrapper = new asn1js.Sequence({
268
+ value: [
269
+ encryptionAlgorithm,
270
+ new asn1js.OctetString({ valueHex: encrypted })
271
+ ]
272
+ })
273
+
274
+ const finalWrapperBuf = finalWrapper.toBER()
275
+ const finalWrapperArr = new Uint8Array(finalWrapperBuf, 0, finalWrapperBuf.byteLength)
276
+
277
+ return [
278
+ '-----BEGIN ENCRYPTED PRIVATE KEY-----',
279
+ ...uint8ArrayToString(finalWrapperArr, 'base64pad').split(/(.{64})/).filter(Boolean),
280
+ '-----END ENCRYPTED PRIVATE KEY-----'
281
+ ].join('\n')
282
+ }
283
+
284
+ export async function importFromPem (pem: string, password: string): Promise<RsaPrivateKey> {
285
+ const crypto = webcrypto.get()
286
+ let plaintext: Uint8Array
287
+
288
+ if (pem.includes('-----BEGIN ENCRYPTED PRIVATE KEY-----')) {
289
+ const key = uint8ArrayFromString(
290
+ pem
291
+ .replace('-----BEGIN ENCRYPTED PRIVATE KEY-----', '')
292
+ .replace('-----END ENCRYPTED PRIVATE KEY-----', '')
293
+ .replace(/\n/g, '')
294
+ .trim(),
295
+ 'base64pad'
296
+ )
297
+
298
+ const { result } = asn1js.fromBER(key)
299
+
300
+ const {
301
+ iv,
302
+ salt,
303
+ iterations,
304
+ keySize,
305
+ cipherText
306
+ } = findEncryptedPEMData(result)
307
+
308
+ const encryptionKey = await pbkdf2Async(
309
+ sha512,
310
+ password,
311
+ salt, {
312
+ c: iterations,
313
+ dkLen: keySize
314
+ }
315
+ )
316
+
317
+ const cryptoKey = await crypto.subtle.importKey('raw', encryptionKey, 'AES-CBC', false, ['decrypt'])
318
+ const decrypted = toUint8Array(await crypto.subtle.decrypt({
319
+ name: 'AES-CBC',
320
+ iv
321
+ }, cryptoKey, cipherText))
322
+
323
+ const { result: decryptedResult } = asn1js.fromBER(decrypted)
324
+ plaintext = findPEMData(decryptedResult)
325
+ } else if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
326
+ const key = uint8ArrayFromString(
327
+ pem
328
+ .replace('-----BEGIN PRIVATE KEY-----', '')
329
+ .replace('-----END PRIVATE KEY-----', '')
330
+ .replace(/\n/g, '')
331
+ .trim(),
332
+ 'base64pad'
333
+ )
334
+
335
+ const { result } = asn1js.fromBER(key)
336
+
337
+ plaintext = findPEMData(result)
338
+ } else {
339
+ throw new CodeError('Could not parse private key from PEM data', 'ERR_INVALID_PARAMETERS')
340
+ }
341
+
342
+ return unmarshalRsaPrivateKey(plaintext)
343
+ }
344
+
345
+ function findEncryptedPEMData (root: any): { cipherText: Uint8Array, iv: Uint8Array, salt: Uint8Array, iterations: number, keySize: number } {
346
+ const encryptionAlgorithm = root.valueBlock.value[0]
347
+ const scheme = encryptionAlgorithm.valueBlock.value[0].toString()
348
+
349
+ if (scheme !== 'OBJECT IDENTIFIER : 1.2.840.113549.1.5.13') {
350
+ throw new CodeError('Only pkcs5PBES2 encrypted private keys are supported', 'ERR_INVALID_PARAMS')
351
+ }
352
+
353
+ const keyDerivationFunc = encryptionAlgorithm.valueBlock.value[1].valueBlock.value[0]
354
+ const keyDerivationFuncName = keyDerivationFunc.valueBlock.value[0].toString()
355
+
356
+ if (keyDerivationFuncName !== 'OBJECT IDENTIFIER : 1.2.840.113549.1.5.12') {
357
+ throw new CodeError('Only pkcs5PBKDF2 key derivation functions are supported', 'ERR_INVALID_PARAMS')
358
+ }
359
+
360
+ const pbkdf2Params = keyDerivationFunc.valueBlock.value[1]
361
+
362
+ const salt = toUint8Array(pbkdf2Params.valueBlock.value[0].getValue())
363
+
364
+ let iterations = ITERATIONS
365
+ let keySize = KEY_SIZE
366
+
367
+ if (pbkdf2Params.valueBlock.value.length === 3) {
368
+ iterations = Number((pbkdf2Params.valueBlock.value[1] as asn1js.Integer).toBigInt())
369
+ keySize = Number((pbkdf2Params.valueBlock.value[2]).toBigInt())
370
+ } else if (pbkdf2Params.valueBlock.value.length === 2) {
371
+ throw new CodeError('Could not derive key size and iterations from PEM file - please use @libp2p/rsa to re-import your key', 'ERR_INVALID_PARAMS')
372
+ }
373
+
374
+ const encryptionScheme = encryptionAlgorithm.valueBlock.value[1].valueBlock.value[1]
375
+ const encryptionSchemeName = encryptionScheme.valueBlock.value[0].toString()
376
+
377
+ if (encryptionSchemeName === 'OBJECT IDENTIFIER : 1.2.840.113549.3.7') {
378
+ // des-EDE3-CBC
379
+ } else if (encryptionSchemeName === 'OBJECT IDENTIFIER : 1.3.14.3.2.7') {
380
+ // des-CBC
381
+ } else if (encryptionSchemeName === 'OBJECT IDENTIFIER : 2.16.840.1.101.3.4.1.2') {
382
+ // aes128-CBC
383
+ } else if (encryptionSchemeName === 'OBJECT IDENTIFIER : 2.16.840.1.101.3.4.1.22') {
384
+ // aes192-CBC
385
+ } else if (encryptionSchemeName === 'OBJECT IDENTIFIER : 2.16.840.1.101.3.4.1.42') {
386
+ // aes256-CBC
387
+ } else {
388
+ throw new CodeError('Only AES-CBC encryption schemes are supported', 'ERR_INVALID_PARAMS')
389
+ }
390
+
391
+ const iv = toUint8Array(encryptionScheme.valueBlock.value[1].getValue())
392
+
393
+ return {
394
+ cipherText: toUint8Array(root.valueBlock.value[1].getValue()),
395
+ salt,
396
+ iterations,
397
+ keySize,
398
+ iv
399
+ }
400
+ }
401
+
402
+ function findPEMData (seq: any): Uint8Array {
403
+ return toUint8Array(seq.valueBlock.value[2].getValue())
404
+ }
405
+
406
+ function toUint8Array (buf: ArrayBuffer): Uint8Array {
407
+ return new Uint8Array(buf, 0, buf.byteLength)
74
408
  }
package/src/keys/rsa.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import crypto from 'crypto'
2
2
  import { promisify } from 'util'
3
3
  import { CodeError } from '@libp2p/interface'
4
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
4
5
  import randomBytes from '../random-bytes.js'
5
6
  import * as utils from './rsa-utils.js'
6
7
  import type { JWKKeyPair } from './interface.js'
@@ -73,34 +74,12 @@ export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint
73
74
  return hash.verify({ format: 'jwk', key }, sig)
74
75
  }
75
76
 
76
- const padding = crypto.constants.RSA_PKCS1_PADDING
77
-
78
- export function encrypt (key: JsonWebKey, bytes: Uint8Array | Uint8ArrayList): Uint8Array {
79
- if (bytes instanceof Uint8Array) {
80
- // @ts-expect-error node types are missing jwk as a format
81
- return crypto.publicEncrypt({ format: 'jwk', key, padding }, bytes)
82
- } else {
83
- // @ts-expect-error node types are missing jwk as a format
84
- return crypto.publicEncrypt({ format: 'jwk', key, padding }, bytes.subarray())
85
- }
86
- }
87
-
88
- export function decrypt (key: JsonWebKey, bytes: Uint8Array | Uint8ArrayList): Uint8Array {
89
- if (bytes instanceof Uint8Array) {
90
- // @ts-expect-error node types are missing jwk as a format
91
- return crypto.privateDecrypt({ format: 'jwk', key, padding }, bytes)
92
- } else {
93
- // @ts-expect-error node types are missing jwk as a format
94
- return crypto.privateDecrypt({ format: 'jwk', key, padding }, bytes.subarray())
95
- }
96
- }
97
-
98
77
  export function keySize (jwk: JsonWebKey): number {
99
78
  if (jwk.kty !== 'RSA') {
100
79
  throw new CodeError('invalid key type', 'ERR_INVALID_KEY_TYPE')
101
80
  } else if (jwk.n == null) {
102
81
  throw new CodeError('invalid key modulus', 'ERR_INVALID_KEY_MODULUS')
103
82
  }
104
- const modulus = Buffer.from(jwk.n, 'base64')
83
+ const modulus = uint8ArrayFromString(jwk.n, 'base64url')
105
84
  return modulus.length * 8
106
85
  }
package/src/pbkdf2.ts CHANGED
@@ -1,39 +1,41 @@
1
1
  import { CodeError } from '@libp2p/interface'
2
- // @ts-expect-error types are missing
3
- import forgePbkdf2 from 'node-forge/lib/pbkdf2.js'
4
- // @ts-expect-error types are missing
5
- import forgeUtil from 'node-forge/lib/util.js'
2
+ import { pbkdf2 as pbkdf2Sync } from '@noble/hashes/pbkdf2'
3
+ import { sha1 } from '@noble/hashes/sha1'
4
+ import { sha256 } from '@noble/hashes/sha256'
5
+ import { sha512 } from '@noble/hashes/sha512'
6
+ import { base64 } from 'multiformats/bases/base64'
6
7
 
7
8
  /**
8
- * Maps an IPFS hash name to its node-forge equivalent.
9
+ * Maps an IPFS hash name to its @noble/hashes equivalent.
9
10
  *
10
11
  * See https://github.com/multiformats/multihash/blob/master/hashtable.csv
11
12
  *
12
13
  * @private
13
14
  */
14
15
  const hashName = {
15
- sha1: 'sha1',
16
- 'sha2-256': 'sha256',
17
- 'sha2-512': 'sha512'
16
+ sha1,
17
+ 'sha2-256': sha256,
18
+ 'sha2-512': sha512
18
19
  }
19
20
 
20
21
  /**
21
22
  * Computes the Password-Based Key Derivation Function 2.
22
23
  */
23
- export default function pbkdf2 (password: string, salt: string, iterations: number, keySize: number, hash: string): string {
24
+ export default function pbkdf2 (password: string, salt: string | Uint8Array, iterations: number, keySize: number, hash: string): string {
24
25
  if (hash !== 'sha1' && hash !== 'sha2-256' && hash !== 'sha2-512') {
25
26
  const types = Object.keys(hashName).join(' / ')
26
27
  throw new CodeError(`Hash '${hash}' is unknown or not supported. Must be ${types}`, 'ERR_UNSUPPORTED_HASH_TYPE')
27
28
  }
28
29
 
29
30
  const hasher = hashName[hash]
30
- const dek = forgePbkdf2(
31
+ const dek = pbkdf2Sync(
32
+ hasher,
31
33
  password,
32
- salt,
33
- iterations,
34
- keySize,
35
- hasher
34
+ salt, {
35
+ c: iterations,
36
+ dkLen: keySize
37
+ }
36
38
  )
37
39
 
38
- return forgeUtil.encode64(dek, null)
40
+ return base64.encode(dek).substring(1)
39
41
  }
package/src/util.ts CHANGED
@@ -1,34 +1,5 @@
1
- import 'node-forge/lib/util.js'
2
- import 'node-forge/lib/jsbn.js'
3
- // @ts-expect-error types are missing
4
- import forge from 'node-forge/lib/forge.js'
5
1
  import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
6
2
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8
-
9
- export function bigIntegerToUintBase64url (num: { abs(): any }, len?: number): string {
10
- // Call `.abs()` to convert to unsigned
11
- let buf = Uint8Array.from(num.abs().toByteArray()) // toByteArray converts to big endian
12
-
13
- // toByteArray() gives us back a signed array, which will include a leading 0
14
- // byte if the most significant bit of the number is 1:
15
- // https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-integer
16
- // Our number will always be positive so we should remove the leading padding.
17
- buf = buf[0] === 0 ? buf.subarray(1) : buf
18
-
19
- if (len != null) {
20
- if (buf.length > len) throw new Error('byte array longer than desired length')
21
- buf = uint8ArrayConcat([new Uint8Array(len - buf.length), buf])
22
- }
23
-
24
- return uint8ArrayToString(buf, 'base64url')
25
- }
26
-
27
- // Convert a base64url encoded string to a BigInteger
28
- export function base64urlToBigInteger (str: string): typeof forge.jsbn.BigInteger {
29
- const buf = base64urlToBuffer(str)
30
- return new forge.jsbn.BigInteger(uint8ArrayToString(buf, 'base16'), 16)
31
- }
32
3
 
33
4
  export function base64urlToBuffer (str: string, len?: number): Uint8Array {
34
5
  let buf = uint8ArrayFromString(str, 'base64urlpad')
@@ -0,0 +1,24 @@
1
+ /* eslint-env browser */
2
+
3
+ // Check native crypto exists and is enabled (In insecure context `self.crypto`
4
+ // exists but `self.crypto.subtle` does not).
5
+ export default {
6
+ get (win = globalThis) {
7
+ const nativeCrypto = win.crypto
8
+
9
+ if (nativeCrypto == null || nativeCrypto.subtle == null) {
10
+ throw Object.assign(
11
+ new Error(
12
+ 'Missing Web Crypto API. ' +
13
+ 'The most likely cause of this error is that this page is being accessed ' +
14
+ 'from an insecure context (i.e. not HTTPS). For more information and ' +
15
+ 'possible resolutions see ' +
16
+ 'https://github.com/libp2p/js-libp2p/blob/main/packages/crypto/README.md#web-crypto-api'
17
+ ),
18
+ { code: 'ERR_MISSING_WEB_CRYPTO' }
19
+ )
20
+ }
21
+
22
+ return nativeCrypto
23
+ }
24
+ }
package/src/webcrypto.ts CHANGED
@@ -1,24 +1,11 @@
1
1
  /* eslint-env browser */
2
2
 
3
- // Check native crypto exists and is enabled (In insecure context `self.crypto`
4
- // exists but `self.crypto.subtle` does not).
3
+ import { webcrypto } from 'crypto'
4
+
5
+ // globalThis `SubtleCrypto` shipped in node.js 19.x, Electron currently uses
6
+ // v18.x so this override file is necessary until Electron updates
5
7
  export default {
6
8
  get (win = globalThis) {
7
- const nativeCrypto = win.crypto
8
-
9
- if (nativeCrypto == null || nativeCrypto.subtle == null) {
10
- throw Object.assign(
11
- new Error(
12
- 'Missing Web Crypto API. ' +
13
- 'The most likely cause of this error is that this page is being accessed ' +
14
- 'from an insecure context (i.e. not HTTPS). For more information and ' +
15
- 'possible resolutions see ' +
16
- 'https://github.com/libp2p/js-libp2p/blob/main/packages/crypto/README.md#web-crypto-api'
17
- ),
18
- { code: 'ERR_MISSING_WEB_CRYPTO' }
19
- )
20
- }
21
-
22
- return nativeCrypto
9
+ return webcrypto
23
10
  }
24
11
  }
@@ -1,2 +0,0 @@
1
- export declare function cipherMode(key: Uint8Array): string;
2
- //# sourceMappingURL=cipher-mode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cipher-mode.d.ts","sourceRoot":"","sources":["../../../src/aes/cipher-mode.ts"],"names":[],"mappings":"AAOA,wBAAgB,UAAU,CAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAOnD"}
@@ -1,13 +0,0 @@
1
- import { CodeError } from '@libp2p/interface';
2
- const CIPHER_MODES = {
3
- 16: 'aes-128-ctr',
4
- 32: 'aes-256-ctr'
5
- };
6
- export function cipherMode(key) {
7
- if (key.length === 16 || key.length === 32) {
8
- return CIPHER_MODES[key.length];
9
- }
10
- const modes = Object.entries(CIPHER_MODES).map(([k, v]) => `${k} (${v})`).join(' / ');
11
- throw new CodeError(`Invalid key length ${key.length} bytes. Must be ${modes}`, 'ERR_INVALID_KEY_LENGTH');
12
- }
13
- //# sourceMappingURL=cipher-mode.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cipher-mode.js","sourceRoot":"","sources":["../../../src/aes/cipher-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAE7C,MAAM,YAAY,GAAG;IACnB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;CAClB,CAAA;AAED,MAAM,UAAU,UAAU,CAAE,GAAe;IACzC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC3C,OAAO,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACjC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrF,MAAM,IAAI,SAAS,CAAC,sBAAsB,GAAG,CAAC,MAAM,mBAAmB,KAAK,EAAE,EAAE,wBAAwB,CAAC,CAAA;AAC3G,CAAC"}
@@ -1,7 +0,0 @@
1
- import 'node-forge/lib/aes.js';
2
- export interface Cipher {
3
- update(data: Uint8Array): Uint8Array;
4
- }
5
- export declare function createCipheriv(mode: any, key: Uint8Array, iv: Uint8Array): Cipher;
6
- export declare function createDecipheriv(mode: any, key: Uint8Array, iv: Uint8Array): Cipher;
7
- //# sourceMappingURL=ciphers-browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ciphers-browser.d.ts","sourceRoot":"","sources":["../../../src/aes/ciphers-browser.ts"],"names":[],"mappings":"AAAA,OAAO,uBAAuB,CAAA;AAM9B,MAAM,WAAW,MAAM;IACrB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;CACrC;AAED,wBAAgB,cAAc,CAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,CASlF;AAED,wBAAgB,gBAAgB,CAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,CASpF"}