@libp2p/webrtc 5.2.8 → 5.2.9-4c64bd06d

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 (32) hide show
  1. package/README.md +54 -0
  2. package/dist/index.min.js +153 -18
  3. package/dist/src/constants.d.ts +16 -0
  4. package/dist/src/constants.d.ts.map +1 -1
  5. package/dist/src/constants.js +16 -0
  6. package/dist/src/constants.js.map +1 -1
  7. package/dist/src/index.d.ts +54 -0
  8. package/dist/src/index.d.ts.map +1 -1
  9. package/dist/src/index.js +54 -0
  10. package/dist/src/index.js.map +1 -1
  11. package/dist/src/private-to-private/util.d.ts +2 -2
  12. package/dist/src/private-to-private/util.d.ts.map +1 -1
  13. package/dist/src/private-to-public/listener.d.ts +11 -5
  14. package/dist/src/private-to-public/listener.d.ts.map +1 -1
  15. package/dist/src/private-to-public/listener.js +49 -98
  16. package/dist/src/private-to-public/listener.js.map +1 -1
  17. package/dist/src/private-to-public/transport.d.ts +66 -5
  18. package/dist/src/private-to-public/transport.d.ts.map +1 -1
  19. package/dist/src/private-to-public/transport.js +149 -2
  20. package/dist/src/private-to-public/transport.js.map +1 -1
  21. package/dist/src/private-to-public/utils/pem.d.ts +6 -0
  22. package/dist/src/private-to-public/utils/pem.d.ts.map +1 -0
  23. package/dist/src/private-to-public/utils/pem.js +15 -0
  24. package/dist/src/private-to-public/utils/pem.js.map +1 -0
  25. package/package.json +12 -9
  26. package/src/constants.ts +20 -0
  27. package/src/index.ts +54 -0
  28. package/src/private-to-private/util.ts +2 -2
  29. package/src/private-to-public/listener.ts +64 -119
  30. package/src/private-to-public/transport.ts +242 -7
  31. package/src/private-to-public/utils/pem.ts +18 -0
  32. package/dist/typedoc-urls.json +0 -14
@@ -1,49 +1,46 @@
1
- import { networkInterfaces } from 'node:os'
2
- import { isIPv4, isIPv6 } from '@chainsafe/is-ip'
3
- import { InvalidPeerIdError, TypedEventEmitter } from '@libp2p/interface'
4
- import { multiaddr, protocols, fromStringTuples } from '@multiformats/multiaddr'
5
- import { IP4, WebRTCDirect } from '@multiformats/multiaddr-matcher'
6
- import { Crypto } from '@peculiar/webcrypto'
1
+ import { isIPv4 } from '@chainsafe/is-ip'
2
+ import { InvalidParametersError, TypedEventEmitter } from '@libp2p/interface'
3
+ import { getThinWaistAddresses } from '@libp2p/utils/get-thin-waist-addresses'
4
+ import { multiaddr, fromStringTuples } from '@multiformats/multiaddr'
5
+ import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
7
6
  import getPort from 'get-port'
8
7
  import pWaitFor from 'p-wait-for'
9
8
  import { CODEC_CERTHASH, CODEC_WEBRTC_DIRECT } from '../constants.js'
10
9
  import { connect } from './utils/connect.js'
11
- import { generateTransportCertificate } from './utils/generate-certificates.js'
12
10
  import { createDialerRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
13
11
  import { stunListener } from './utils/stun-listener.js'
14
12
  import type { DataChannelOptions, TransportCertificate } from '../index.js'
13
+ import type { WebRTCDirectTransportCertificateEvents } from './transport.js'
15
14
  import type { DirectRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
16
15
  import type { StunServer } from './utils/stun-listener.js'
17
- import type { PeerId, ListenerEvents, Listener, Upgrader, ComponentLogger, Logger, CounterGroup, Metrics, PrivateKey } from '@libp2p/interface'
16
+ import type { PeerId, ListenerEvents, Listener, Upgrader, ComponentLogger, Logger, CounterGroup, Metrics, PrivateKey, TypedEventTarget } from '@libp2p/interface'
17
+ import type { Keychain } from '@libp2p/keychain'
18
18
  import type { Multiaddr } from '@multiformats/multiaddr'
19
-
20
- const crypto = new Crypto()
19
+ import type { Datastore } from 'interface-datastore'
21
20
 
22
21
  export interface WebRTCDirectListenerComponents {
23
22
  peerId: PeerId
24
23
  privateKey: PrivateKey
25
24
  logger: ComponentLogger
26
25
  upgrader: Upgrader
26
+ keychain?: Keychain
27
+ datastore: Datastore
27
28
  metrics?: Metrics
28
29
  }
29
30
 
30
31
  export interface WebRTCDirectListenerInit {
31
32
  upgrader: Upgrader
32
- certificates?: TransportCertificate[]
33
+ certificate: TransportCertificate
33
34
  maxInboundStreams?: number
34
35
  dataChannel?: DataChannelOptions
35
36
  rtcConfiguration?: RTCConfiguration | (() => RTCConfiguration | Promise<RTCConfiguration>)
36
- useLibjuice?: boolean
37
+ emitter: TypedEventTarget<WebRTCDirectTransportCertificateEvents>
37
38
  }
38
39
 
39
40
  export interface WebRTCListenerMetrics {
40
41
  listenerEvents: CounterGroup
41
42
  }
42
43
 
43
- const UDP_PROTOCOL = protocols('udp')
44
- const IP4_PROTOCOL = protocols('ip4')
45
- const IP6_PROTOCOL = protocols('ip6')
46
-
47
44
  interface UDPMuxServer {
48
45
  server: Promise<StunServer>
49
46
  isIPv4: boolean
@@ -56,8 +53,9 @@ interface UDPMuxServer {
56
53
  let UDP_MUX_LISTENERS: UDPMuxServer[] = []
57
54
 
58
55
  export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> implements Listener {
59
- private readonly multiaddrs: Multiaddr[]
60
- private certificate?: TransportCertificate
56
+ private listeningMultiaddr?: Multiaddr
57
+ private certificate: TransportCertificate
58
+ private stunServer?: StunServer
61
59
  private readonly connections: Map<string, DirectRTCPeerConnection>
62
60
  private readonly log: Logger
63
61
  private readonly init: WebRTCDirectListenerInit
@@ -70,11 +68,10 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
70
68
 
71
69
  this.init = init
72
70
  this.components = components
73
- this.multiaddrs = []
74
71
  this.connections = new Map()
75
72
  this.log = components.logger.forComponent('libp2p:webrtc-direct:listener')
76
- this.certificate = init.certificates?.[0]
77
73
  this.shutdownController = new AbortController()
74
+ this.certificate = init.certificate
78
75
 
79
76
  if (components.metrics != null) {
80
77
  this.metrics = {
@@ -84,95 +81,64 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
84
81
  })
85
82
  }
86
83
  }
84
+
85
+ // inform the transport manager our addresses have changed
86
+ init.emitter.addEventListener('certificate:renew', evt => {
87
+ this.certificate = evt.detail
88
+ this.safeDispatchEvent('listening')
89
+ })
87
90
  }
88
91
 
89
92
  async listen (ma: Multiaddr): Promise<void> {
90
- const parts = ma.stringTuples()
91
- const ipVersion = IP4.matches(ma) ? 4 : 6
92
- const host = parts
93
- .filter(([code]) => code === IP4_PROTOCOL.code)
94
- .pop()?.[1] ?? parts
95
- .filter(([code]) => code === IP6_PROTOCOL.code)
96
- .pop()?.[1]
97
-
98
- if (host == null) {
99
- throw new Error('IP4/6 host must be specified in webrtc-direct multiaddr')
100
- }
101
- const port = parseInt(parts
102
- .filter(([code, value]) => code === UDP_PROTOCOL.code)
103
- .pop()?.[1] ?? '')
93
+ const { host, port, family } = ma.toOptions()
104
94
 
105
- if (isNaN(port)) {
106
- throw new Error('UDP port must be specified in webrtc-direct multiaddr')
107
- }
95
+ let udpMuxServer: UDPMuxServer | undefined
108
96
 
109
- // have to do this before any async work happens so starting two listeners
110
- // for the same port concurrently (e.g. ipv4/ipv6 both port 0) results in a
111
- // single mux listener. This is necessary because libjuice binds to all
112
- // interfaces for a given port so we we need to key on just the port number
113
- // not the host + the port number
114
- let existingServer = UDP_MUX_LISTENERS.find(s => s.port === port)
115
-
116
- // if the server has not been started yet, or the port is a wildcard port
117
- // and there is already a wildcard port for this address family, start a new
118
- // UDP mux server
119
- const wildcardPorts = port === 0 && existingServer?.port === 0
120
- const sameAddressFamily = (existingServer?.isIPv4 === true && isIPv4(host)) || (existingServer?.isIPv6 === true && isIPv6(host))
121
- let createdMuxServer = false
122
-
123
- if (existingServer == null || (wildcardPorts && sameAddressFamily)) {
124
- this.log('starting UDP mux server on %s:%p', host, port)
125
- existingServer = this.startUDPMuxServer(host, port)
126
- UDP_MUX_LISTENERS.push(existingServer)
127
- createdMuxServer = true
128
- }
97
+ if (port !== 0) {
98
+ // libjuice binds to all interfaces (IPv4/IPv6) for a given port so if we
99
+ // want to listen on a specific port, and there's already a mux listener
100
+ // for that port for the other family started by this node, we should
101
+ // reuse it
102
+ udpMuxServer = UDP_MUX_LISTENERS.find(s => s.port === port)
129
103
 
130
- if (!existingServer.peerId.equals(this.components.peerId)) {
131
- // this would have to be another in-process peer so we are likely in a
132
- // testing environment
133
- throw new InvalidPeerIdError(`Another peer is already performing UDP mux on ${host}:${existingServer.port}`)
134
- }
104
+ // make sure the port is free for the given family
105
+ if (udpMuxServer != null && ((udpMuxServer.isIPv4 && family === 4) || (udpMuxServer.isIPv6 && family === 6))) {
106
+ throw new InvalidParametersError(`There is already a listener for ${host}:${port}`)
107
+ }
135
108
 
136
- const server = await existingServer.server
137
- const address = server.address()
109
+ // check that we own the mux server
110
+ if (udpMuxServer != null && !udpMuxServer.peerId.equals(this.components.peerId)) {
111
+ throw new InvalidParametersError(`Another peer is already performing UDP mux on ${host}:${port}`)
112
+ }
113
+ }
138
114
 
139
- if (!createdMuxServer) {
140
- this.log('reused existing UDP mux server on %s:%p', host, address.port)
115
+ // start the mux server if we don't have one already
116
+ if (udpMuxServer == null) {
117
+ this.log('starting UDP mux server on %s:%p', host, port)
118
+ udpMuxServer = this.startUDPMuxServer(host, port, family)
119
+ UDP_MUX_LISTENERS.push(udpMuxServer)
141
120
  }
142
121
 
143
- getNetworkAddresses(host, address.port, ipVersion).forEach((ma) => {
144
- this.multiaddrs.push(multiaddr(`${ma}/webrtc-direct/certhash/${this.certificate?.certhash}`))
145
- })
122
+ if (family === 4) {
123
+ udpMuxServer.isIPv4 = true
124
+ } else if (family === 6) {
125
+ udpMuxServer.isIPv6 = true
126
+ }
146
127
 
128
+ this.stunServer = await udpMuxServer.server
129
+ this.listeningMultiaddr = ma
147
130
  this.safeDispatchEvent('listening')
148
131
  }
149
132
 
150
- private startUDPMuxServer (host: string, port: number): UDPMuxServer {
133
+ private startUDPMuxServer (host: string, port: number, family: 4 | 6): UDPMuxServer {
151
134
  return {
152
135
  peerId: this.components.peerId,
153
136
  owner: this,
154
137
  port,
155
- isIPv4: isIPv4(host),
156
- isIPv6: isIPv6(host),
138
+ isIPv4: family === 4,
139
+ isIPv6: family === 6,
157
140
  server: Promise.resolve()
158
141
  .then(async (): Promise<StunServer> => {
159
- // ensure we have a certificate
160
- if (this.certificate == null) {
161
- this.log.trace('creating TLS certificate')
162
- const keyPair = await crypto.subtle.generateKey({
163
- name: 'ECDSA',
164
- namedCurve: 'P-256'
165
- }, true, ['sign', 'verify'])
166
-
167
- const certificate = await generateTransportCertificate(keyPair, {
168
- days: 365 * 10
169
- })
170
-
171
- if (this.certificate == null) {
172
- this.certificate = certificate
173
- }
174
- }
175
-
176
142
  if (port === 0) {
177
143
  // libjuice doesn't map 0 to a random free port so we have to do it
178
144
  // ourselves
@@ -247,7 +213,15 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
247
213
  }
248
214
 
249
215
  getAddrs (): Multiaddr[] {
250
- return this.multiaddrs
216
+ if (this.stunServer == null) {
217
+ return []
218
+ }
219
+
220
+ const address = this.stunServer.address()
221
+
222
+ return getThinWaistAddresses(this.listeningMultiaddr, address.port).map(ma => {
223
+ return ma.encapsulate(`/webrtc-direct/certhash/${this.certificate?.certhash}`)
224
+ })
251
225
  }
252
226
 
253
227
  updateAnnounceAddrs (multiaddrs: Multiaddr[]): void {
@@ -309,32 +283,3 @@ export class WebRTCDirectListener extends TypedEventEmitter<ListenerEvents> impl
309
283
  this.safeDispatchEvent('close')
310
284
  }
311
285
  }
312
-
313
- function getNetworkAddresses (host: string, port: number, version: 4 | 6): string[] {
314
- if (host === '0.0.0.0' || host === '::') {
315
- // return all ip4 interfaces
316
- return Object.entries(networkInterfaces())
317
- .flatMap(([_, addresses]) => addresses)
318
- .map(address => address?.address)
319
- .filter(address => {
320
- if (address == null) {
321
- return false
322
- }
323
-
324
- if (version === 4) {
325
- return isIPv4(address)
326
- }
327
-
328
- if (version === 6) {
329
- return isIPv6(address)
330
- }
331
-
332
- return false
333
- })
334
- .map(address => `/ip${version}/${address}/udp/${port}`)
335
- }
336
-
337
- return [
338
- `/ip${version}/${host}/udp/${port}`
339
- ]
340
- }
@@ -1,20 +1,30 @@
1
- import { serviceCapabilities, transportSymbol } from '@libp2p/interface'
1
+ import { generateKeyPair, privateKeyToCryptoKeyPair } from '@libp2p/crypto/keys'
2
+ import { NotFoundError, NotStartedError, TypedEventEmitter, serviceCapabilities, transportSymbol } from '@libp2p/interface'
2
3
  import { peerIdFromString } from '@libp2p/peer-id'
3
4
  import { WebRTCDirect } from '@multiformats/multiaddr-matcher'
5
+ import { BasicConstraintsExtension, X509Certificate, X509CertificateGenerator } from '@peculiar/x509'
6
+ import { Key } from 'interface-datastore'
7
+ import { base64url } from 'multiformats/bases/base64'
8
+ import { sha256 } from 'multiformats/hashes/sha2'
4
9
  import { raceSignal } from 'race-signal'
10
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
11
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
12
+ import { DEFAULT_CERTIFICATE_DATASTORE_KEY, DEFAULT_CERTIFICATE_LIFESPAN, DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME } from '../constants.js'
5
13
  import { genUfrag } from '../util.js'
6
14
  import { WebRTCDirectListener } from './listener.js'
7
15
  import { connect } from './utils/connect.js'
8
16
  import { createDialerRTCPeerConnection } from './utils/get-rtcpeerconnection.js'
17
+ import { formatAsPem } from './utils/pem.js'
9
18
  import type { DataChannelOptions, TransportCertificate } from '../index.js'
10
19
  import type { WebRTCDialEvents } from '../private-to-private/transport.js'
11
- import type { CreateListenerOptions, Transport, Listener, ComponentLogger, Logger, Connection, CounterGroup, Metrics, PeerId, DialTransportOptions, PrivateKey, Upgrader } from '@libp2p/interface'
20
+ import type { CreateListenerOptions, Transport, Listener, ComponentLogger, Logger, Connection, CounterGroup, Metrics, PeerId, DialTransportOptions, PrivateKey, Upgrader, Startable, TypedEventTarget } from '@libp2p/interface'
12
21
  import type { TransportManager } from '@libp2p/interface-internal'
22
+ import type { Keychain } from '@libp2p/keychain'
13
23
  import type { Multiaddr } from '@multiformats/multiaddr'
24
+ import type { Datastore } from 'interface-datastore'
25
+
26
+ const ONE_DAY_MS = 86_400_000
14
27
 
15
- /**
16
- * The peer for this transport
17
- */
18
28
  export interface WebRTCDirectTransportComponents {
19
29
  peerId: PeerId
20
30
  privateKey: PrivateKey
@@ -22,6 +32,8 @@ export interface WebRTCDirectTransportComponents {
22
32
  logger: ComponentLogger
23
33
  transportManager: TransportManager
24
34
  upgrader: Upgrader
35
+ keychain?: Keychain
36
+ datastore: Datastore
25
37
  }
26
38
 
27
39
  export interface WebRTCMetrics {
@@ -29,26 +41,85 @@ export interface WebRTCMetrics {
29
41
  }
30
42
 
31
43
  export interface WebRTCTransportDirectInit {
44
+ /**
45
+ * The default configuration used by all created RTCPeerConnections
46
+ */
32
47
  rtcConfiguration?: RTCConfiguration | (() => RTCConfiguration | Promise<RTCConfiguration>)
48
+
49
+ /**
50
+ * The default configuration used by all created RTCDataChannels
51
+ */
33
52
  dataChannel?: DataChannelOptions
53
+
54
+ /**
55
+ * @deprecated use `certificate` instead - this option will be removed in a future release
56
+ */
34
57
  certificates?: TransportCertificate[]
35
58
 
59
+ /**
60
+ * Use an existing TLS certificate to secure incoming connections or supply
61
+ * settings to generate one.
62
+ *
63
+ * This must be an ECDSA certificate using the P-256 curve.
64
+ *
65
+ * From our testing we find that P-256 elliptic curve is supported by Pion,
66
+ * webrtc-rs, as well as Chromium (P-228 and P-384 was not supported in
67
+ * Chromium).
68
+ */
69
+ certificate?: TransportCertificate
70
+
36
71
  /**
37
72
  * @deprecated this setting is ignored and will be removed in a future release
38
73
  */
39
74
  useLibjuice?: boolean
75
+
76
+ /**
77
+ * The key the certificate is stored in the datastore under
78
+ *
79
+ * @default '/libp2p/webrtc-direct/certificate'
80
+ */
81
+ certificateDatastoreKey?: string
82
+
83
+ /**
84
+ * The name the certificate private key is stored in the keychain with
85
+ *
86
+ * @default 'webrtc-direct-certificate-private-key'
87
+ */
88
+ certificateKeychainName?: string
89
+
90
+ /**
91
+ * Number of days a certificate should be valid for
92
+ *
93
+ * @default 365
94
+ */
95
+ certificateLifespan?: number
96
+
97
+ /**
98
+ * Certificates will be renewed this many days before their expiry
99
+ *
100
+ * @default 5
101
+ */
102
+ certificateRenewalThreshold?: number
103
+ }
104
+
105
+ export interface WebRTCDirectTransportCertificateEvents {
106
+ 'certificate:renew': CustomEvent<TransportCertificate>
40
107
  }
41
108
 
42
- export class WebRTCDirectTransport implements Transport {
109
+ export class WebRTCDirectTransport implements Transport, Startable {
43
110
  private readonly log: Logger
44
111
  private readonly metrics?: WebRTCMetrics
45
112
  private readonly components: WebRTCDirectTransportComponents
46
113
  private readonly init: WebRTCTransportDirectInit
114
+ private certificate?: TransportCertificate
115
+ private privateKey?: PrivateKey
116
+ private readonly emitter: TypedEventTarget<WebRTCDirectTransportCertificateEvents>
47
117
 
48
118
  constructor (components: WebRTCDirectTransportComponents, init: WebRTCTransportDirectInit = {}) {
49
119
  this.log = components.logger.forComponent('libp2p:webrtc-direct')
50
120
  this.components = components
51
121
  this.init = init
122
+ this.emitter = new TypedEventEmitter()
52
123
 
53
124
  if (components.metrics != null) {
54
125
  this.metrics = {
@@ -68,6 +139,14 @@ export class WebRTCDirectTransport implements Transport {
68
139
  '@libp2p/transport'
69
140
  ]
70
141
 
142
+ async start (): Promise<void> {
143
+ this.certificate = await this.getCertificate()
144
+ }
145
+
146
+ async stop (): Promise<void> {
147
+
148
+ }
149
+
71
150
  /**
72
151
  * Dial a given multiaddr
73
152
  */
@@ -81,9 +160,15 @@ export class WebRTCDirectTransport implements Transport {
81
160
  * Create transport listeners no supported by browsers
82
161
  */
83
162
  createListener (options: CreateListenerOptions): Listener {
163
+ if (this.certificate == null) {
164
+ throw new NotStartedError()
165
+ }
166
+
84
167
  return new WebRTCDirectListener(this.components, {
85
168
  ...this.init,
86
- ...options
169
+ ...options,
170
+ certificate: this.certificate,
171
+ emitter: this.emitter
87
172
  })
88
173
  }
89
174
 
@@ -139,4 +224,154 @@ export class WebRTCDirectTransport implements Transport {
139
224
  throw err
140
225
  }
141
226
  }
227
+
228
+ private async getCertificate (): Promise<TransportCertificate> {
229
+ if (isTransportCertificate(this.init.certificate)) {
230
+ this.log.trace('using provided TLS certificate')
231
+ return this.init.certificate
232
+ }
233
+
234
+ const privateKey = await this.loadOrCreatePrivateKey()
235
+ const { pem, certhash } = await this.loadOrCreateCertificate(privateKey)
236
+
237
+ return {
238
+ privateKey: await formatAsPem(privateKey),
239
+ pem,
240
+ certhash
241
+ }
242
+ }
243
+
244
+ private async loadOrCreatePrivateKey (): Promise<PrivateKey> {
245
+ if (this.privateKey != null) {
246
+ return this.privateKey
247
+ }
248
+
249
+ const keychainName = this.init.certificateKeychainName ?? DEFAULT_CERTIFICATE_PRIVATE_KEY_NAME
250
+ const keychain = this.getKeychain()
251
+
252
+ try {
253
+ if (keychain == null) {
254
+ this.log('no keychain configured - not checking for stored private key')
255
+ throw new NotFoundError()
256
+ }
257
+
258
+ this.log.trace('checking for stored private key')
259
+ this.privateKey = await keychain.exportKey(keychainName)
260
+ } catch (err: any) {
261
+ if (err.name !== 'NotFoundError') {
262
+ throw err
263
+ }
264
+
265
+ this.log.trace('generating private key')
266
+ this.privateKey = await generateKeyPair('ECDSA', 'P-256')
267
+
268
+ if (keychain != null) {
269
+ this.log.trace('storing private key')
270
+ await keychain.importKey(keychainName, this.privateKey)
271
+ } else {
272
+ this.log('no keychain configured - not storing private key')
273
+ }
274
+ }
275
+
276
+ return this.privateKey
277
+ }
278
+
279
+ private async loadOrCreateCertificate (privateKey: PrivateKey): Promise<{ pem: string, certhash: string }> {
280
+ if (this.certificate != null) {
281
+ return this.certificate
282
+ }
283
+
284
+ let cert: X509Certificate
285
+ const dsKey = new Key(this.init.certificateDatastoreKey ?? DEFAULT_CERTIFICATE_DATASTORE_KEY)
286
+ const keyPair = await privateKeyToCryptoKeyPair(privateKey)
287
+
288
+ try {
289
+ this.log.trace('checking for stored TLS certificate')
290
+ cert = await this.loadCertificate(dsKey, keyPair)
291
+ } catch (err: any) {
292
+ if (err.name !== 'NotFoundError') {
293
+ throw err
294
+ }
295
+
296
+ this.log('generating TLS certificate using private key')
297
+ cert = await this.createCertificate(dsKey, keyPair)
298
+ }
299
+
300
+ return {
301
+ pem: cert.toString('pem'),
302
+ certhash: base64url.encode((await sha256.digest(new Uint8Array(cert.rawData))).bytes)
303
+ }
304
+ }
305
+
306
+ async loadCertificate (dsKey: Key, keyPair: CryptoKeyPair): Promise<X509Certificate> {
307
+ const buf = await this.components.datastore.get(dsKey)
308
+ const cert = new X509Certificate(buf)
309
+
310
+ // check expiry date
311
+ const threshold = Date.now() - ((this.init.certificateLifespan ?? DEFAULT_CERTIFICATE_LIFESPAN) * ONE_DAY_MS)
312
+
313
+ if (cert.notAfter.getTime() < threshold) {
314
+ this.log('stored TLS certificate has expired')
315
+ // act as if no certificate was present
316
+ throw new NotFoundError()
317
+ }
318
+
319
+ // check public keys match
320
+ const exportedCertKey = await cert.publicKey.export(crypto)
321
+ const rawCertKey = await crypto.subtle.exportKey('raw', exportedCertKey)
322
+ const rawKeyPairKey = await crypto.subtle.exportKey('raw', keyPair.publicKey)
323
+
324
+ if (!uint8ArrayEquals(
325
+ new Uint8Array(rawCertKey, 0, rawCertKey.byteLength),
326
+ new Uint8Array(rawKeyPairKey, 0, rawKeyPairKey.byteLength)
327
+ )) {
328
+ this.log('stored TLS certificate public key did not match public key from private key')
329
+ throw new NotFoundError()
330
+ }
331
+
332
+ return cert
333
+ }
334
+
335
+ async createCertificate (dsKey: Key, keyPair: CryptoKeyPair): Promise<X509Certificate> {
336
+ const notBefore = new Date()
337
+ const notAfter = new Date(notBefore.getTime() + ((this.init.certificateLifespan ?? DEFAULT_CERTIFICATE_LIFESPAN) * ONE_DAY_MS))
338
+
339
+ // have to set ms to 0 to work around https://github.com/PeculiarVentures/x509/issues/73
340
+ notBefore.setMilliseconds(0)
341
+ notAfter.setMilliseconds(0)
342
+
343
+ const cert = await X509CertificateGenerator.createSelfSigned({
344
+ serialNumber: (BigInt(Math.random().toString().replace('.', '')) * 100000n).toString(16),
345
+ name: 'CN=example.com, C=US, L=CA, O=example, ST=CA',
346
+ notBefore,
347
+ notAfter,
348
+ keys: keyPair,
349
+ extensions: [
350
+ new BasicConstraintsExtension(false, undefined, true)
351
+ ]
352
+ }, crypto)
353
+
354
+ if (this.getKeychain() != null) {
355
+ this.log.trace('storing TLS certificate')
356
+ await this.components.datastore.put(dsKey, uint8ArrayFromString(cert.toString('pem')))
357
+ } else {
358
+ this.log('no keychain is configured so not storing TLS certificate since the private key will not be reused')
359
+ }
360
+
361
+ return cert
362
+ }
363
+
364
+ private getKeychain (): Keychain | undefined {
365
+ try {
366
+ return this.components.keychain
367
+ } catch {}
368
+ }
369
+ }
370
+
371
+ function isTransportCertificate (obj?: any): obj is TransportCertificate {
372
+ if (obj == null) {
373
+ return false
374
+ }
375
+
376
+ return typeof obj.privateKey === 'string' && typeof obj.pem === 'string' && typeof obj.certhash === 'string'
142
377
  }
@@ -0,0 +1,18 @@
1
+ import { privateKeyToCryptoKeyPair } from '@libp2p/crypto/keys'
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
3
+ import type { PrivateKey } from '@libp2p/interface'
4
+
5
+ export function toBuffer (uint8Array: Uint8Array): Buffer {
6
+ return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength)
7
+ }
8
+
9
+ export async function formatAsPem (privateKey: PrivateKey): Promise<string> {
10
+ const keyPair = await privateKeyToCryptoKeyPair(privateKey)
11
+ const exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
12
+
13
+ return [
14
+ '-----BEGIN PRIVATE KEY-----',
15
+ ...uint8ArrayToString(new Uint8Array(exported), 'base64pad').split(/(.{64})/).filter(Boolean),
16
+ '-----END PRIVATE KEY-----'
17
+ ].join('\n')
18
+ }
@@ -1,14 +0,0 @@
1
- {
2
- "DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
3
- ".:DataChannelOptions": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.DataChannelOptions.html",
4
- "TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
5
- ".:TransportCertificate": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.TransportCertificate.html",
6
- "WebRTCDirectTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCDirectTransportComponents.html",
7
- "WebRTCTransportComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportComponents.html",
8
- "WebRTCTransportDirectInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportDirectInit.html",
9
- "WebRTCTransportInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_webrtc.WebRTCTransportInit.html",
10
- "webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
11
- ".:webRTC": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTC.html",
12
- "webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html",
13
- ".:webRTCDirect": "https://libp2p.github.io/js-libp2p/functions/_libp2p_webrtc.webRTCDirect.html"
14
- }