@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.
- package/dist/index.min.js +72 -14
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +0 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/keys/ed25519-browser.d.ts +1 -1
- package/dist/src/keys/ed25519-browser.d.ts.map +1 -1
- package/dist/src/keys/index.d.ts +4 -2
- package/dist/src/keys/index.d.ts.map +1 -1
- package/dist/src/keys/index.js +7 -12
- package/dist/src/keys/index.js.map +1 -1
- package/dist/src/keys/rsa-browser.d.ts +0 -2
- package/dist/src/keys/rsa-browser.d.ts.map +1 -1
- package/dist/src/keys/rsa-browser.js +0 -25
- package/dist/src/keys/rsa-browser.js.map +1 -1
- package/dist/src/keys/rsa-class.d.ts +6 -5
- package/dist/src/keys/rsa-class.d.ts.map +1 -1
- package/dist/src/keys/rsa-class.js +11 -25
- package/dist/src/keys/rsa-class.js.map +1 -1
- package/dist/src/keys/rsa-utils.d.ts +15 -2
- package/dist/src/keys/rsa-utils.d.ts.map +1 -1
- package/dist/src/keys/rsa-utils.js +304 -39
- package/dist/src/keys/rsa-utils.js.map +1 -1
- package/dist/src/keys/rsa.d.ts +0 -2
- package/dist/src/keys/rsa.d.ts.map +1 -1
- package/dist/src/keys/rsa.js +2 -22
- package/dist/src/keys/rsa.js.map +1 -1
- package/dist/src/pbkdf2.d.ts +1 -1
- package/dist/src/pbkdf2.d.ts.map +1 -1
- package/dist/src/pbkdf2.js +14 -10
- package/dist/src/pbkdf2.js.map +1 -1
- package/dist/src/util.d.ts +0 -7
- package/dist/src/util.d.ts.map +1 -1
- package/dist/src/util.js +0 -25
- package/dist/src/util.js.map +1 -1
- package/dist/src/webcrypto-browser.d.ts +5 -0
- package/dist/src/webcrypto-browser.d.ts.map +1 -0
- package/dist/src/webcrypto-browser.js +17 -0
- package/dist/src/webcrypto-browser.js.map +1 -0
- package/dist/src/webcrypto.d.ts +3 -1
- package/dist/src/webcrypto.d.ts.map +1 -1
- package/dist/src/webcrypto.js +4 -11
- package/dist/src/webcrypto.js.map +1 -1
- package/dist/typedoc-urls.json +9 -4
- package/package.json +7 -14
- package/src/index.ts +0 -2
- package/src/keys/ed25519-browser.ts +1 -1
- package/src/keys/index.ts +10 -12
- package/src/keys/rsa-browser.ts +0 -29
- package/src/keys/rsa-class.ts +11 -28
- package/src/keys/rsa-utils.ts +373 -39
- package/src/keys/rsa.ts +2 -23
- package/src/pbkdf2.ts +17 -15
- package/src/util.ts +0 -29
- package/src/webcrypto-browser.ts +24 -0
- package/src/webcrypto.ts +5 -18
- package/dist/src/aes/cipher-mode.d.ts +0 -2
- package/dist/src/aes/cipher-mode.d.ts.map +0 -1
- package/dist/src/aes/cipher-mode.js +0 -13
- package/dist/src/aes/cipher-mode.js.map +0 -1
- package/dist/src/aes/ciphers-browser.d.ts +0 -7
- package/dist/src/aes/ciphers-browser.d.ts.map +0 -1
- package/dist/src/aes/ciphers-browser.js +0 -26
- package/dist/src/aes/ciphers-browser.js.map +0 -1
- package/dist/src/aes/ciphers.d.ts +0 -5
- package/dist/src/aes/ciphers.d.ts.map +0 -1
- package/dist/src/aes/ciphers.js +0 -4
- package/dist/src/aes/ciphers.js.map +0 -1
- package/dist/src/aes/index.d.ts +0 -50
- package/dist/src/aes/index.d.ts.map +0 -1
- package/dist/src/aes/index.js +0 -61
- package/dist/src/aes/index.js.map +0 -1
- package/dist/src/keys/jwk2pem.d.ts +0 -8
- package/dist/src/keys/jwk2pem.d.ts.map +0 -1
- package/dist/src/keys/jwk2pem.js +0 -14
- package/dist/src/keys/jwk2pem.js.map +0 -1
- package/src/aes/cipher-mode.ts +0 -15
- package/src/aes/ciphers-browser.ts +0 -31
- package/src/aes/ciphers.ts +0 -4
- package/src/aes/index.ts +0 -70
- package/src/keys/jwk2pem.ts +0 -21
package/src/keys/rsa-utils.ts
CHANGED
|
@@ -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
|
-
|
|
5
|
-
import
|
|
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
|
|
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
|
-
|
|
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
|
|
13
|
-
const privateKey = forge.pki.privateKeyFromAsn1(asn1)
|
|
15
|
+
const { result } = asn1js.fromBER(bytes)
|
|
14
16
|
|
|
15
|
-
//
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
59
|
+
const der = root.toBER()
|
|
60
|
+
|
|
61
|
+
return new Uint8Array(der, 0, der.byteLength)
|
|
48
62
|
}
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Convert a PKCIX in ASN1 DER format to a JWK key
|
|
66
|
+
*/
|
|
51
67
|
export function pkixToJwk (bytes: Uint8Array): JsonWebKey {
|
|
52
|
-
const
|
|
53
|
-
|
|
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:
|
|
58
|
-
e:
|
|
76
|
+
n: uint8ArrayToString(bnToBuf(values[0].toBigInt()), 'base64url'),
|
|
77
|
+
e: uint8ArrayToString(bnToBuf(values[1].toBigInt()), 'base64url')
|
|
59
78
|
}
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import
|
|
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
|
|
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
|
|
16
|
-
'sha2-256':
|
|
17
|
-
'sha2-512':
|
|
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 =
|
|
31
|
+
const dek = pbkdf2Sync(
|
|
32
|
+
hasher,
|
|
31
33
|
password,
|
|
32
|
-
salt,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
salt, {
|
|
35
|
+
c: iterations,
|
|
36
|
+
dkLen: keySize
|
|
37
|
+
}
|
|
36
38
|
)
|
|
37
39
|
|
|
38
|
-
return
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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 +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"}
|