@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.
- package/README.md +54 -0
- package/dist/index.min.js +153 -18
- package/dist/src/constants.d.ts +16 -0
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +16 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/index.d.ts +54 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +54 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/private-to-private/util.d.ts +2 -2
- package/dist/src/private-to-private/util.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.d.ts +11 -5
- package/dist/src/private-to-public/listener.d.ts.map +1 -1
- package/dist/src/private-to-public/listener.js +49 -98
- package/dist/src/private-to-public/listener.js.map +1 -1
- package/dist/src/private-to-public/transport.d.ts +66 -5
- package/dist/src/private-to-public/transport.d.ts.map +1 -1
- package/dist/src/private-to-public/transport.js +149 -2
- package/dist/src/private-to-public/transport.js.map +1 -1
- package/dist/src/private-to-public/utils/pem.d.ts +6 -0
- package/dist/src/private-to-public/utils/pem.d.ts.map +1 -0
- package/dist/src/private-to-public/utils/pem.js +15 -0
- package/dist/src/private-to-public/utils/pem.js.map +1 -0
- package/package.json +12 -9
- package/src/constants.ts +20 -0
- package/src/index.ts +54 -0
- package/src/private-to-private/util.ts +2 -2
- package/src/private-to-public/listener.ts +64 -119
- package/src/private-to-public/transport.ts +242 -7
- package/src/private-to-public/utils/pem.ts +18 -0
- package/dist/typedoc-urls.json +0 -14
@@ -1,49 +1,46 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import { multiaddr,
|
5
|
-
import {
|
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
|
-
|
33
|
+
certificate: TransportCertificate
|
33
34
|
maxInboundStreams?: number
|
34
35
|
dataChannel?: DataChannelOptions
|
35
36
|
rtcConfiguration?: RTCConfiguration | (() => RTCConfiguration | Promise<RTCConfiguration>)
|
36
|
-
|
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
|
60
|
-
private certificate
|
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
|
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
|
-
|
106
|
-
throw new Error('UDP port must be specified in webrtc-direct multiaddr')
|
107
|
-
}
|
95
|
+
let udpMuxServer: UDPMuxServer | undefined
|
108
96
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
137
|
-
|
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
|
140
|
-
|
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
|
-
|
144
|
-
|
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:
|
156
|
-
isIPv6:
|
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
|
-
|
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 {
|
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
|
+
}
|
package/dist/typedoc-urls.json
DELETED
@@ -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
|
-
}
|