@libp2p/crypto 5.0.13 → 5.0.14-2c8ecb455

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.
@@ -0,0 +1,212 @@
1
+ import { Uint8ArrayList } from 'uint8arraylist'
2
+
3
+ interface Context {
4
+ offset: number
5
+ }
6
+
7
+ const TAG_MASK = parseInt('11111', 2)
8
+ const LONG_LENGTH_MASK = parseInt('10000000', 2)
9
+ const LONG_LENGTH_BYTES_MASK = parseInt('01111111', 2)
10
+
11
+ interface Decoder {
12
+ (buf: Uint8Array, context: Context): any
13
+ }
14
+
15
+ const decoders: Record<number, Decoder> = {
16
+ 0x2: readInteger,
17
+ 0x3: readBitString,
18
+ 0x5: readNull,
19
+ 0x6: readObjectIdentifier,
20
+ 0x10: readSequence,
21
+ 0x16: readSequence,
22
+ 0x30: readSequence
23
+ }
24
+
25
+ export function decodeDer (buf: Uint8Array, context: Context = { offset: 0 }): any {
26
+ const tag = buf[context.offset] & TAG_MASK
27
+ context.offset++
28
+
29
+ if (decoders[tag] != null) {
30
+ return decoders[tag](buf, context)
31
+ }
32
+
33
+ throw new Error('No decoder for tag ' + tag)
34
+ }
35
+
36
+ function readLength (buf: Uint8Array, context: Context): number {
37
+ let length = 0
38
+
39
+ if ((buf[context.offset] & LONG_LENGTH_MASK) === LONG_LENGTH_MASK) {
40
+ // long length
41
+ const count = buf[context.offset] & LONG_LENGTH_BYTES_MASK
42
+ let str = '0x'
43
+ context.offset++
44
+
45
+ for (let i = 0; i < count; i++, context.offset++) {
46
+ str += buf[context.offset].toString(16).padStart(2, '0')
47
+ }
48
+
49
+ length = parseInt(str, 16)
50
+ } else {
51
+ length = buf[context.offset]
52
+ context.offset++
53
+ }
54
+
55
+ return length
56
+ }
57
+
58
+ function readSequence (buf: Uint8Array, context: Context): any[] {
59
+ readLength(buf, context)
60
+ const entries: any[] = []
61
+
62
+ while (true) {
63
+ if (context.offset >= buf.byteLength) {
64
+ break
65
+ }
66
+
67
+ const result = decodeDer(buf, context)
68
+
69
+ if (result === null) {
70
+ break
71
+ }
72
+
73
+ entries.push(result)
74
+ }
75
+
76
+ return entries
77
+ }
78
+
79
+ function readInteger (buf: Uint8Array, context: Context): Uint8Array {
80
+ const length = readLength(buf, context)
81
+ const start = context.offset
82
+ const end = context.offset + length
83
+
84
+ const vals: number[] = []
85
+
86
+ for (let i = start; i < end; i++) {
87
+ if (i === start && buf[i] === 0) {
88
+ continue
89
+ }
90
+
91
+ vals.push(buf[i])
92
+ }
93
+
94
+ context.offset += length
95
+
96
+ return Uint8Array.from(vals)
97
+ }
98
+
99
+ function readObjectIdentifier (buf: Uint8Array, context: Context): string[] {
100
+ const count = readLength(buf, context)
101
+
102
+ // skip OID
103
+ context.offset += count
104
+
105
+ return ['oid-unimplemented']
106
+ }
107
+
108
+ function readNull (buf: Uint8Array, context: Context): null {
109
+ context.offset++
110
+
111
+ return null
112
+ }
113
+
114
+ function readBitString (buf: Uint8Array, context: Context): any {
115
+ const length = readLength(buf, context)
116
+ const unusedBits = buf[context.offset]
117
+ context.offset++
118
+ const bytes = buf.subarray(context.offset, context.offset + length)
119
+ context.offset += length
120
+
121
+ if (unusedBits !== 0) {
122
+ // need to shift all bytes along by this many bits
123
+ throw new Error('Unused bits in bit string is unimplemented')
124
+ }
125
+
126
+ return decodeDer(bytes, {
127
+ offset: 0
128
+ })
129
+ }
130
+
131
+ function encodeNumber (value: number): Uint8ArrayList {
132
+ let number = value.toString(16)
133
+
134
+ if (number.length % 2 === 1) {
135
+ number = '0' + number
136
+ }
137
+
138
+ const array = new Uint8ArrayList()
139
+
140
+ for (let i = 0; i < number.length; i += 2) {
141
+ array.append(Uint8Array.from([parseInt(`${number[i]}${number[i + 1]}`, 16)]))
142
+ }
143
+
144
+ return array
145
+ }
146
+
147
+ function encodeLength (bytes: { byteLength: number }): Uint8Array | Uint8ArrayList {
148
+ if (bytes.byteLength < 128) {
149
+ return Uint8Array.from([bytes.byteLength])
150
+ }
151
+
152
+ // long length
153
+ const length = encodeNumber(bytes.byteLength)
154
+
155
+ return new Uint8ArrayList(
156
+ Uint8Array.from([
157
+ length.byteLength | LONG_LENGTH_MASK
158
+ ]),
159
+ length
160
+ )
161
+ }
162
+
163
+ export function encodeInteger (value: Uint8Array | Uint8ArrayList): Uint8ArrayList {
164
+ const contents = new Uint8ArrayList()
165
+
166
+ const mask = parseInt('10000000', 2)
167
+ const positive = (value.subarray()[0] & mask) === mask
168
+
169
+ if (positive) {
170
+ contents.append(Uint8Array.from([0]))
171
+ }
172
+
173
+ contents.append(value)
174
+
175
+ return new Uint8ArrayList(
176
+ Uint8Array.from([0x02]),
177
+ encodeLength(contents),
178
+ contents
179
+ )
180
+ }
181
+
182
+ export function encodeBitString (value: Uint8Array | Uint8ArrayList): Uint8ArrayList {
183
+ // unused bits is always 0 with full-byte-only values
184
+ const unusedBits = Uint8Array.from([0])
185
+
186
+ const contents = new Uint8ArrayList(
187
+ unusedBits,
188
+ value
189
+ )
190
+
191
+ return new Uint8ArrayList(
192
+ Uint8Array.from([0x03]),
193
+ encodeLength(contents),
194
+ contents
195
+ )
196
+ }
197
+
198
+ export function encodeSequence (values: Array<Uint8Array | Uint8ArrayList>): Uint8ArrayList {
199
+ const output = new Uint8ArrayList()
200
+
201
+ for (const buf of values) {
202
+ output.append(
203
+ buf
204
+ )
205
+ }
206
+
207
+ return new Uint8ArrayList(
208
+ Uint8Array.from([0x30]),
209
+ encodeLength(output),
210
+ output
211
+ )
212
+ }
@@ -1,150 +1,106 @@
1
1
  import { InvalidParametersError, InvalidPublicKeyError } from '@libp2p/interface'
2
2
  import { sha256 } from '@noble/hashes/sha256'
3
- import * as asn1js from 'asn1js'
4
3
  import { create } from 'multiformats/hashes/digest'
5
4
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
6
5
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
7
6
  import * as pb from '../keys.js'
7
+ import { decodeDer, encodeBitString, encodeInteger, encodeSequence } from './der.js'
8
8
  import { RSAPrivateKey as RSAPrivateKeyClass, RSAPublicKey as RSAPublicKeyClass } from './rsa.js'
9
9
  import { generateRSAKey, rsaKeySize } from './index.js'
10
10
  import type { JWKKeyPair } from '../interface.js'
11
11
  import type { RSAPrivateKey, RSAPublicKey } from '@libp2p/interface'
12
+ import type { Digest } from 'multiformats/hashes/digest'
12
13
 
13
14
  export const MAX_RSA_KEY_SIZE = 8192
14
15
  const SHA2_256_CODE = 0x12
16
+ const MAX_RSA_JWK_SIZE = 1062
17
+
18
+ const RSA_ALGORITHM_IDENTIFIER = Uint8Array.from([
19
+ 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00
20
+ ])
15
21
 
16
22
  /**
17
- * Convert a PKCS#1 in ASN1 DER format to a JWK key
23
+ * Convert a PKCS#1 in ASN1 DER format to a JWK private key
18
24
  */
19
25
  export function pkcs1ToJwk (bytes: Uint8Array): JsonWebKey {
20
- const { result } = asn1js.fromBER(bytes)
21
-
22
- // @ts-expect-error this looks fragile but DER is a canonical format so we are
23
- // safe to have deeply property chains like this
24
- const values: asn1js.Integer[] = result.valueBlock.value
25
-
26
- const key = {
27
- n: asn1jsIntegerToBase64(values[1]),
28
- e: asn1jsIntegerToBase64(values[2]),
29
- d: asn1jsIntegerToBase64(values[3]),
30
- p: asn1jsIntegerToBase64(values[4]),
31
- q: asn1jsIntegerToBase64(values[5]),
32
- dp: asn1jsIntegerToBase64(values[6]),
33
- dq: asn1jsIntegerToBase64(values[7]),
34
- qi: asn1jsIntegerToBase64(values[8]),
35
- kty: 'RSA',
36
- alg: 'RS256'
37
- }
26
+ const values = decodeDer(bytes)
38
27
 
39
- return key
28
+ return {
29
+ n: uint8ArrayToString(values[1], 'base64url'),
30
+ e: uint8ArrayToString(values[2], 'base64url'),
31
+ d: uint8ArrayToString(values[3], 'base64url'),
32
+ p: uint8ArrayToString(values[4], 'base64url'),
33
+ q: uint8ArrayToString(values[5], 'base64url'),
34
+ dp: uint8ArrayToString(values[6], 'base64url'),
35
+ dq: uint8ArrayToString(values[7], 'base64url'),
36
+ qi: uint8ArrayToString(values[8], 'base64url'),
37
+ kty: 'RSA'
38
+ }
40
39
  }
41
40
 
42
41
  /**
43
- * Convert a JWK key into PKCS#1 in ASN1 DER format
42
+ * Convert a JWK private key into PKCS#1 in ASN1 DER format
44
43
  */
45
44
  export function jwkToPkcs1 (jwk: JsonWebKey): Uint8Array {
46
45
  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) {
47
46
  throw new InvalidParametersError('JWK was missing components')
48
47
  }
49
48
 
50
- const root = new asn1js.Sequence({
51
- value: [
52
- new asn1js.Integer({ value: 0 }),
53
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.n, 'base64url'))),
54
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.e, 'base64url'))),
55
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.d, 'base64url'))),
56
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.p, 'base64url'))),
57
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.q, 'base64url'))),
58
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.dp, 'base64url'))),
59
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.dq, 'base64url'))),
60
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.qi, 'base64url')))
61
- ]
62
- })
63
-
64
- const der = root.toBER()
65
-
66
- return new Uint8Array(der, 0, der.byteLength)
49
+ return encodeSequence([
50
+ encodeInteger(Uint8Array.from([0])),
51
+ encodeInteger(uint8ArrayFromString(jwk.n, 'base64url')),
52
+ encodeInteger(uint8ArrayFromString(jwk.e, 'base64url')),
53
+ encodeInteger(uint8ArrayFromString(jwk.d, 'base64url')),
54
+ encodeInteger(uint8ArrayFromString(jwk.p, 'base64url')),
55
+ encodeInteger(uint8ArrayFromString(jwk.q, 'base64url')),
56
+ encodeInteger(uint8ArrayFromString(jwk.dp, 'base64url')),
57
+ encodeInteger(uint8ArrayFromString(jwk.dq, 'base64url')),
58
+ encodeInteger(uint8ArrayFromString(jwk.qi, 'base64url'))
59
+ ]).subarray()
67
60
  }
68
61
 
69
62
  /**
70
- * Convert a PKIX in ASN1 DER format to a JWK key
63
+ * Convert a PKIX in ASN1 DER format to a JWK public key
71
64
  */
72
65
  export function pkixToJwk (bytes: Uint8Array): JsonWebKey {
73
- const { result } = asn1js.fromBER(bytes)
74
-
75
- // @ts-expect-error this looks fragile but DER is a canonical format so we are
76
- // safe to have deeply property chains like this
77
- const values: asn1js.Integer[] = result.valueBlock.value[1].valueBlock.value[0].valueBlock.value
66
+ const decoded = decodeDer(bytes, {
67
+ offset: 0
68
+ })
78
69
 
70
+ // this looks fragile but DER is a canonical format so we are safe to have
71
+ // deeply property chains like this
79
72
  return {
80
73
  kty: 'RSA',
81
- n: asn1jsIntegerToBase64(values[0]),
82
- e: asn1jsIntegerToBase64(values[1])
74
+ n: uint8ArrayToString(
75
+ decoded[1][0],
76
+ 'base64url'
77
+ ),
78
+ e: uint8ArrayToString(
79
+ decoded[1][1],
80
+ 'base64url'
81
+ )
83
82
  }
84
83
  }
85
84
 
86
85
  /**
87
- * Convert a JWK key to PKIX in ASN1 DER format
86
+ * Convert a JWK public key to PKIX in ASN1 DER format
88
87
  */
89
88
  export function jwkToPkix (jwk: JsonWebKey): Uint8Array {
90
89
  if (jwk.n == null || jwk.e == null) {
91
90
  throw new InvalidParametersError('JWK was missing components')
92
91
  }
93
92
 
94
- const root = new asn1js.Sequence({
95
- value: [
96
- new asn1js.Sequence({
97
- value: [
98
- // rsaEncryption
99
- new asn1js.ObjectIdentifier({
100
- value: '1.2.840.113549.1.1.1'
101
- }),
102
- new asn1js.Null()
103
- ]
104
- }),
105
- // this appears to be a bug in asn1js.js - this should really be a Sequence
106
- // and not a BitString but it generates the same bytes as node-forge so 🤷‍♂️
107
- new asn1js.BitString({
108
- valueHex: new asn1js.Sequence({
109
- value: [
110
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.n, 'base64url'))),
111
- asn1js.Integer.fromBigInt(bufToBn(uint8ArrayFromString(jwk.e, 'base64url')))
112
- ]
113
- }).toBER()
114
- })
115
- ]
116
- })
117
-
118
- const der = root.toBER()
119
-
120
- return new Uint8Array(der, 0, der.byteLength)
121
- }
122
-
123
- function asn1jsIntegerToBase64 (int: asn1js.Integer): string {
124
- let buf = int.valueBlock.valueHexView
125
-
126
- // chrome rejects values with leading 0s
127
- while (buf[0] === 0) {
128
- buf = buf.subarray(1)
129
- }
130
-
131
- return uint8ArrayToString(buf, 'base64url')
132
- }
133
-
134
- function bufToBn (u8: Uint8Array): bigint {
135
- const hex: string[] = []
136
-
137
- u8.forEach(function (i) {
138
- let h = i.toString(16)
139
-
140
- if (h.length % 2 > 0) {
141
- h = `0${h}`
142
- }
143
-
144
- hex.push(h)
145
- })
146
-
147
- return BigInt('0x' + hex.join(''))
93
+ const subjectPublicKeyInfo = encodeSequence([
94
+ RSA_ALGORITHM_IDENTIFIER,
95
+ encodeBitString(
96
+ encodeSequence([
97
+ encodeInteger(uint8ArrayFromString(jwk.n, 'base64url')),
98
+ encodeInteger(uint8ArrayFromString(jwk.e, 'base64url'))
99
+ ])
100
+ )
101
+ ])
102
+
103
+ return subjectPublicKeyInfo.subarray()
148
104
  }
149
105
 
150
106
  /**
@@ -159,18 +115,20 @@ export function pkcs1ToRSAPrivateKey (bytes: Uint8Array): RSAPrivateKey {
159
115
  /**
160
116
  * Turn PKIX bytes to a PublicKey
161
117
  */
162
- export function pkixToRSAPublicKey (bytes: Uint8Array): RSAPublicKey {
163
- const jwk = pkixToJwk(bytes)
164
-
165
- if (rsaKeySize(jwk) > MAX_RSA_KEY_SIZE) {
118
+ export function pkixToRSAPublicKey (bytes: Uint8Array, digest?: Digest<18, number>): RSAPublicKey {
119
+ if (bytes.byteLength >= MAX_RSA_JWK_SIZE) {
166
120
  throw new InvalidPublicKeyError('Key size is too large')
167
121
  }
168
122
 
169
- const hash = sha256(pb.PublicKey.encode({
170
- Type: pb.KeyType.RSA,
171
- Data: bytes
172
- }))
173
- const digest = create(SHA2_256_CODE, hash)
123
+ const jwk = pkixToJwk(bytes)
124
+
125
+ if (digest == null) {
126
+ const hash = sha256(pb.PublicKey.encode({
127
+ Type: pb.KeyType.RSA,
128
+ Data: bytes
129
+ }))
130
+ digest = create(SHA2_256_CODE, hash)
131
+ }
174
132
 
175
133
  return new RSAPublicKeyClass(jwk, digest)
176
134
  }
@@ -1,34 +0,0 @@
1
- {
2
- "create": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.ciphers.AES_GCM.create.html",
3
- "HMAC": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.hmac.HMAC.html",
4
- "./hmac:HMAC": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.hmac.HMAC.html",
5
- "./hmac:create": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.hmac.create.html",
6
- "pbkdf2": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.index.pbkdf2.html",
7
- "randomBytes": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.index.randomBytes.html",
8
- "ECDHKey": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.keys.ECDHKey.html",
9
- "ECDHKeyPair": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.keys.ECDHKeyPair.html",
10
- "EnhancedKey": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.keys.EnhancedKey.html",
11
- "EnhancedKeyPair": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_crypto.keys.EnhancedKeyPair.html",
12
- "Curve": "https://libp2p.github.io/js-libp2p/types/_libp2p_crypto.keys.Curve.html",
13
- "generateEphemeralKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateEphemeralKeyPair.html",
14
- "generateKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPair.html",
15
- "./keys:generateKeyPair": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPair.html",
16
- "generateKeyPairFromSeed": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPairFromSeed.html",
17
- "./keys:generateKeyPairFromSeed": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.generateKeyPairFromSeed.html",
18
- "keyStretcher": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.keyStretcher.html",
19
- "privateKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromProtobuf.html",
20
- "./keys:privateKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromProtobuf.html",
21
- "privateKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromRaw.html",
22
- "./keys:privateKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyFromRaw.html",
23
- "privateKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToProtobuf.html",
24
- "./keys:privateKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.privateKeyToProtobuf.html",
25
- "publicKeyFromMultihash": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromMultihash.html",
26
- "./keys:publicKeyFromMultihash": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromMultihash.html",
27
- "publicKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromProtobuf.html",
28
- "./keys:publicKeyFromProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromProtobuf.html",
29
- "publicKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromRaw.html",
30
- "./keys:publicKeyFromRaw": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyFromRaw.html",
31
- "publicKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyToProtobuf.html",
32
- "./keys:publicKeyToProtobuf": "https://libp2p.github.io/js-libp2p/functions/_libp2p_crypto.keys.publicKeyToProtobuf.html",
33
- "default": "https://libp2p.github.io/js-libp2p/variables/_libp2p_crypto.webcrypto.default.html"
34
- }