@bsv/sdk 1.3.28 → 1.3.29
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/cjs/package.json +1 -1
- package/dist/cjs/src/auth/Peer.js +142 -91
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/auth/SessionManager.js +82 -21
- package/dist/cjs/src/auth/SessionManager.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +138 -88
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/auth/SessionManager.js +88 -22
- package/dist/esm/src/auth/SessionManager.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts +21 -23
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/auth/SessionManager.d.ts +25 -7
- package/dist/types/src/auth/SessionManager.d.ts.map +1 -1
- package/dist/types/src/auth/types.d.ts +1 -0
- package/dist/types/src/auth/types.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/auth.md +33 -38
- package/package.json +1 -1
- package/src/auth/Peer.ts +186 -130
- package/src/auth/SessionManager.ts +89 -22
- package/src/auth/__tests/Peer.test.ts +66 -0
- package/src/auth/__tests/SessionManager.test.ts +3 -2
- package/src/auth/types.ts +1 -0
package/src/auth/Peer.ts
CHANGED
|
@@ -20,6 +20,8 @@ const AUTH_VERSION = '0.1'
|
|
|
20
20
|
* Represents a peer capable of performing mutual authentication.
|
|
21
21
|
* Manages sessions, handles authentication handshakes, certificate requests and responses,
|
|
22
22
|
* and sending and receiving general messages over a transport layer.
|
|
23
|
+
*
|
|
24
|
+
* This version supports multiple concurrent sessions per peer identityKey.
|
|
23
25
|
*/
|
|
24
26
|
export class Peer {
|
|
25
27
|
public sessionManager: SessionManager
|
|
@@ -55,7 +57,7 @@ export class Peer {
|
|
|
55
57
|
// Whether to auto-persist the session with the last-interacted-with peer
|
|
56
58
|
private readonly autoPersistLastSession: boolean = true
|
|
57
59
|
|
|
58
|
-
// Last-interacted-with peer identity key
|
|
60
|
+
// Last-interacted-with peer identity key (if the user calls toPeer with no identityKey)
|
|
59
61
|
private lastInteractedWithPeer: string | undefined
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -81,7 +83,8 @@ export class Peer {
|
|
|
81
83
|
types: {}
|
|
82
84
|
}
|
|
83
85
|
this.transport.onData(this.handleIncomingMessage.bind(this)).catch(console.error)
|
|
84
|
-
this.sessionManager =
|
|
86
|
+
this.sessionManager =
|
|
87
|
+
sessionManager != null ? sessionManager : new SessionManager()
|
|
85
88
|
if (autoPersistLastSession === false) {
|
|
86
89
|
this.autoPersistLastSession = false
|
|
87
90
|
} else {
|
|
@@ -93,7 +96,8 @@ export class Peer {
|
|
|
93
96
|
* Sends a general message to a peer, and initiates a handshake if necessary.
|
|
94
97
|
*
|
|
95
98
|
* @param {number[]} message - The message payload to send.
|
|
96
|
-
* @param {string} [identityKey] - The identity public key of the peer. If not provided,
|
|
99
|
+
* @param {string} [identityKey] - The identity public key of the peer. If not provided, uses lastInteractedWithPeer (if any).
|
|
100
|
+
* @param {number} [maxWaitTime] - optional max wait time in ms
|
|
97
101
|
* @returns {Promise<void>}
|
|
98
102
|
* @throws Will throw an error if the message fails to send.
|
|
99
103
|
*/
|
|
@@ -104,16 +108,13 @@ export class Peer {
|
|
|
104
108
|
): Promise<void> {
|
|
105
109
|
if (
|
|
106
110
|
this.autoPersistLastSession &&
|
|
107
|
-
this.lastInteractedWithPeer
|
|
108
|
-
this.lastInteractedWithPeer !== null &&
|
|
111
|
+
typeof this.lastInteractedWithPeer === 'string' &&
|
|
109
112
|
typeof identityKey !== 'string'
|
|
110
113
|
) {
|
|
111
114
|
identityKey = this.lastInteractedWithPeer
|
|
112
115
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
maxWaitTime
|
|
116
|
-
)
|
|
116
|
+
|
|
117
|
+
const peerSession = await this.getAuthenticatedSession(identityKey, maxWaitTime)
|
|
117
118
|
|
|
118
119
|
// Prepare the general message
|
|
119
120
|
const requestNonce = Utils.toBase64(Random(32))
|
|
@@ -135,11 +136,15 @@ export class Peer {
|
|
|
135
136
|
signature
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
peerSession.lastUpdate = Date.now()
|
|
140
|
+
this.sessionManager.updateSession(peerSession)
|
|
141
|
+
|
|
138
142
|
try {
|
|
139
143
|
await this.transport.send(generalMessage)
|
|
140
|
-
} catch (error) {
|
|
144
|
+
} catch (error: any) {
|
|
141
145
|
const e = new Error(
|
|
142
|
-
`Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
146
|
+
`Failed to send message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
147
|
+
}: ${String(error.message)}`
|
|
143
148
|
)
|
|
144
149
|
e.stack = error.stack
|
|
145
150
|
throw e
|
|
@@ -152,7 +157,7 @@ export class Peer {
|
|
|
152
157
|
* an initial handshake or message has been exchanged.
|
|
153
158
|
*
|
|
154
159
|
* @param {RequestedCertificateSet} certificatesToRequest - Specifies the certifiers and types of certificates required from the peer.
|
|
155
|
-
* @param {string} [identityKey] - The identity public key of the peer. If not provided, the current session identity is used.
|
|
160
|
+
* @param {string} [identityKey] - The identity public key of the peer. If not provided, the current or last session identity is used.
|
|
156
161
|
* @param {number} [maxWaitTime=10000] - Maximum time in milliseconds to wait for the peer session to be authenticated.
|
|
157
162
|
* @returns {Promise<void>} Resolves if the certificate request message is successfully sent.
|
|
158
163
|
* @throws Will throw an error if the peer session is not authenticated or if sending the request fails.
|
|
@@ -162,12 +167,20 @@ export class Peer {
|
|
|
162
167
|
identityKey?: string,
|
|
163
168
|
maxWaitTime = 10000
|
|
164
169
|
): Promise<void> {
|
|
170
|
+
if (
|
|
171
|
+
this.autoPersistLastSession &&
|
|
172
|
+
typeof this.lastInteractedWithPeer === 'string' &&
|
|
173
|
+
typeof identityKey !== 'string'
|
|
174
|
+
) {
|
|
175
|
+
identityKey = this.lastInteractedWithPeer
|
|
176
|
+
}
|
|
177
|
+
|
|
165
178
|
const peerSession = await this.getAuthenticatedSession(
|
|
166
179
|
identityKey,
|
|
167
180
|
maxWaitTime
|
|
168
181
|
)
|
|
169
182
|
|
|
170
|
-
// Prepare the
|
|
183
|
+
// Prepare the message
|
|
171
184
|
const requestNonce = Utils.toBase64(Random(32))
|
|
172
185
|
const { signature } = await this.wallet.createSignature({
|
|
173
186
|
data: Utils.toArray(JSON.stringify(certificatesToRequest), 'utf8'),
|
|
@@ -188,11 +201,16 @@ export class Peer {
|
|
|
188
201
|
signature
|
|
189
202
|
}
|
|
190
203
|
|
|
204
|
+
// Update last-used timestamp
|
|
205
|
+
peerSession.lastUpdate = Date.now()
|
|
206
|
+
this.sessionManager.updateSession(peerSession)
|
|
207
|
+
|
|
191
208
|
try {
|
|
192
209
|
await this.transport.send(certRequestMessage)
|
|
193
210
|
} catch (error: any) {
|
|
194
211
|
throw new Error(
|
|
195
|
-
`Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
212
|
+
`Failed to send certificate request message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
213
|
+
}: ${String(error.message)}`
|
|
196
214
|
)
|
|
197
215
|
}
|
|
198
216
|
}
|
|
@@ -201,12 +219,13 @@ export class Peer {
|
|
|
201
219
|
* Retrieves an authenticated session for a given peer identity. If no session exists
|
|
202
220
|
* or the session is not authenticated, initiates a handshake to create or authenticate the session.
|
|
203
221
|
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
222
|
+
* - If `identityKey` is provided, we look up any existing session for that identity key.
|
|
223
|
+
* - If none is found or not authenticated, we do a new handshake.
|
|
224
|
+
* - If `identityKey` is not provided, but we have a `lastInteractedWithPeer`, we try that key.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} [identityKey] - The identity public key of the peer.
|
|
227
|
+
* @param {number} [maxWaitTime] - The maximum time in milliseconds to wait for the handshake.
|
|
208
228
|
* @returns {Promise<PeerSession>} - A promise that resolves with an authenticated `PeerSession`.
|
|
209
|
-
* @throws {Error} - Throws an error if the transport is not connected or if the handshake fails.
|
|
210
229
|
*/
|
|
211
230
|
async getAuthenticatedSession (
|
|
212
231
|
identityKey?: string,
|
|
@@ -216,16 +235,18 @@ export class Peer {
|
|
|
216
235
|
throw new Error('Peer transport is not connected!')
|
|
217
236
|
}
|
|
218
237
|
|
|
219
|
-
let peerSession
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
let peerSession: PeerSession | undefined
|
|
239
|
+
if (typeof identityKey === 'string') {
|
|
240
|
+
peerSession = this.sessionManager.getSession(identityKey)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// If that session doesn't exist or isn't authenticated, initiate handshake
|
|
244
|
+
if ((peerSession == null) || !peerSession.isAuthenticated) {
|
|
245
|
+
// This will create a brand-new session
|
|
246
|
+
const sessionNonce = await this.initiateHandshake(identityKey, maxWaitTime)
|
|
247
|
+
// Now retrieve it by the sessionNonce
|
|
248
|
+
peerSession = this.sessionManager.getSession(sessionNonce)
|
|
249
|
+
if ((peerSession == null) || !peerSession.isAuthenticated) {
|
|
229
250
|
throw new Error('Unable to establish mutual authentication with peer!')
|
|
230
251
|
}
|
|
231
252
|
}
|
|
@@ -259,7 +280,7 @@ export class Peer {
|
|
|
259
280
|
/**
|
|
260
281
|
* Registers a callback to listen for certificates received from peers.
|
|
261
282
|
*
|
|
262
|
-
* @param {(certs: VerifiableCertificate[]) => void} callback - The function to call when certificates are received.
|
|
283
|
+
* @param {(senderPublicKey: string, certs: VerifiableCertificate[]) => void} callback - The function to call when certificates are received.
|
|
263
284
|
* @returns {number} The ID of the callback listener.
|
|
264
285
|
*/
|
|
265
286
|
listenForCertificatesReceived (
|
|
@@ -310,6 +331,7 @@ export class Peer {
|
|
|
310
331
|
*
|
|
311
332
|
* @private
|
|
312
333
|
* @param {string} [identityKey] - The identity public key of the peer.
|
|
334
|
+
* @param {number} [maxWaitTime=10000] - how long to wait for handshake
|
|
313
335
|
* @returns {Promise<string>} A promise that resolves to the session nonce.
|
|
314
336
|
*/
|
|
315
337
|
private async initiateHandshake (
|
|
@@ -317,10 +339,14 @@ export class Peer {
|
|
|
317
339
|
maxWaitTime = 10000
|
|
318
340
|
): Promise<string> {
|
|
319
341
|
const sessionNonce = await createNonce(this.wallet) // Initial request nonce
|
|
342
|
+
|
|
343
|
+
// Create the preliminary session (not yet authenticated)
|
|
344
|
+
const now = Date.now()
|
|
320
345
|
this.sessionManager.addSession({
|
|
321
346
|
isAuthenticated: false,
|
|
322
347
|
sessionNonce,
|
|
323
|
-
peerIdentityKey: identityKey
|
|
348
|
+
peerIdentityKey: identityKey,
|
|
349
|
+
lastUpdate: now
|
|
324
350
|
})
|
|
325
351
|
|
|
326
352
|
const initialRequest: AuthMessage = {
|
|
@@ -347,14 +373,11 @@ export class Peer {
|
|
|
347
373
|
maxWaitTime = 10000
|
|
348
374
|
): Promise<string> {
|
|
349
375
|
return await new Promise((resolve, reject) => {
|
|
350
|
-
const callbackID = this.listenForInitialResponse(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
resolve(sessionNonce)
|
|
356
|
-
}
|
|
357
|
-
)
|
|
376
|
+
const callbackID = this.listenForInitialResponse(sessionNonce, nonce => {
|
|
377
|
+
clearTimeout(timeoutHandle)
|
|
378
|
+
this.stopListeningForInitialResponses(callbackID)
|
|
379
|
+
resolve(nonce)
|
|
380
|
+
})
|
|
358
381
|
|
|
359
382
|
const timeoutHandle = setTimeout(() => {
|
|
360
383
|
this.stopListeningForInitialResponses(callbackID)
|
|
@@ -400,9 +423,9 @@ export class Peer {
|
|
|
400
423
|
* @returns {Promise<void>}
|
|
401
424
|
*/
|
|
402
425
|
private async handleIncomingMessage (message: AuthMessage): Promise<void> {
|
|
403
|
-
if (
|
|
426
|
+
if (typeof message.version !== 'string' || message.version !== AUTH_VERSION) {
|
|
404
427
|
console.error(
|
|
405
|
-
`Invalid message auth version! Received: ${message.version}, expected: ${AUTH_VERSION}`
|
|
428
|
+
`Invalid or unsupported message auth version! Received: ${message.version}, expected: ${AUTH_VERSION}`
|
|
406
429
|
)
|
|
407
430
|
return
|
|
408
431
|
}
|
|
@@ -425,7 +448,9 @@ export class Peer {
|
|
|
425
448
|
break
|
|
426
449
|
default:
|
|
427
450
|
console.error(
|
|
428
|
-
`Unknown message type of ${String(message.messageType)} from ${String(
|
|
451
|
+
`Unknown message type of ${String(message.messageType)} from ${String(
|
|
452
|
+
message.identityKey
|
|
453
|
+
)}`
|
|
429
454
|
)
|
|
430
455
|
}
|
|
431
456
|
}
|
|
@@ -434,32 +459,43 @@ export class Peer {
|
|
|
434
459
|
* Processes an initial request message from a peer.
|
|
435
460
|
*
|
|
436
461
|
* @param {AuthMessage} message - The incoming initial request message.
|
|
437
|
-
* @returns {Promise<void>}
|
|
438
462
|
*/
|
|
439
|
-
async processInitialRequest (message: AuthMessage): Promise<void> {
|
|
440
|
-
if (
|
|
441
|
-
|
|
463
|
+
private async processInitialRequest (message: AuthMessage): Promise<void> {
|
|
464
|
+
if (
|
|
465
|
+
typeof message.identityKey !== 'string' ||
|
|
466
|
+
typeof message.initialNonce !== 'string' ||
|
|
467
|
+
message.initialNonce === ''
|
|
468
|
+
) {
|
|
469
|
+
throw new Error('Missing required fields in initialRequest message.')
|
|
442
470
|
}
|
|
443
471
|
|
|
444
|
-
// Create
|
|
472
|
+
// Create a new sessionNonce for our side
|
|
445
473
|
const sessionNonce = await createNonce(this.wallet)
|
|
474
|
+
const now = Date.now()
|
|
475
|
+
|
|
476
|
+
// We'll treat this as fully authenticated from *our* perspective (the responding side).
|
|
446
477
|
this.sessionManager.addSession({
|
|
447
478
|
isAuthenticated: true,
|
|
448
479
|
sessionNonce,
|
|
449
480
|
peerNonce: message.initialNonce,
|
|
450
|
-
peerIdentityKey: message.identityKey
|
|
481
|
+
peerIdentityKey: message.identityKey,
|
|
482
|
+
lastUpdate: now
|
|
451
483
|
})
|
|
452
484
|
|
|
453
|
-
//
|
|
454
|
-
let certificatesToInclude
|
|
455
|
-
if (
|
|
485
|
+
// Possibly handle the peer's requested certs
|
|
486
|
+
let certificatesToInclude: VerifiableCertificate[] | undefined
|
|
487
|
+
if (
|
|
488
|
+
(message.requestedCertificates != null) &&
|
|
489
|
+
Array.isArray(message.requestedCertificates.certifiers) &&
|
|
490
|
+
message.requestedCertificates.certifiers.length > 0
|
|
491
|
+
) {
|
|
456
492
|
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
|
|
457
|
-
//
|
|
458
|
-
this.onCertificateRequestReceivedCallbacks.forEach(
|
|
459
|
-
|
|
493
|
+
// Let the application handle it
|
|
494
|
+
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
|
|
495
|
+
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
|
|
460
496
|
})
|
|
461
497
|
} else {
|
|
462
|
-
// Attempt to find
|
|
498
|
+
// Attempt to find automatically
|
|
463
499
|
certificatesToInclude = await getVerifiableCertificates(
|
|
464
500
|
this.wallet,
|
|
465
501
|
message.requestedCertificates,
|
|
@@ -468,7 +504,7 @@ export class Peer {
|
|
|
468
504
|
}
|
|
469
505
|
}
|
|
470
506
|
|
|
471
|
-
// Create
|
|
507
|
+
// Create signature
|
|
472
508
|
const { signature } = await this.wallet.createSignature({
|
|
473
509
|
data: Utils.toArray(message.initialNonce + sessionNonce, 'base64'),
|
|
474
510
|
protocolID: [2, 'auth message signature'],
|
|
@@ -488,11 +524,12 @@ export class Peer {
|
|
|
488
524
|
signature
|
|
489
525
|
}
|
|
490
526
|
|
|
491
|
-
//
|
|
492
|
-
if (this.lastInteractedWithPeer === undefined
|
|
527
|
+
// If we haven't interacted with a peer yet, store this identity as "lastInteracted"
|
|
528
|
+
if (this.lastInteractedWithPeer === undefined) {
|
|
493
529
|
this.lastInteractedWithPeer = message.identityKey
|
|
494
530
|
}
|
|
495
531
|
|
|
532
|
+
// Send the response
|
|
496
533
|
await this.transport.send(initialResponseMessage)
|
|
497
534
|
}
|
|
498
535
|
|
|
@@ -501,8 +538,7 @@ export class Peer {
|
|
|
501
538
|
*
|
|
502
539
|
* @private
|
|
503
540
|
* @param {AuthMessage} message - The incoming initial response message.
|
|
504
|
-
* @
|
|
505
|
-
* @throws Will throw an error if nonce verification or signature verification fails.
|
|
541
|
+
* @throws Will throw an error if nonce or signature verification fails.
|
|
506
542
|
*/
|
|
507
543
|
private async processInitialResponse (message: AuthMessage): Promise<void> {
|
|
508
544
|
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
|
|
@@ -512,17 +548,19 @@ export class Peer {
|
|
|
512
548
|
)
|
|
513
549
|
}
|
|
514
550
|
|
|
551
|
+
// This is the session we previously created by calling initiateHandshake
|
|
515
552
|
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
|
|
516
|
-
if (peerSession
|
|
553
|
+
if (peerSession == null) {
|
|
517
554
|
throw new Error(`Peer session not found for peer: ${message.identityKey}`)
|
|
518
555
|
}
|
|
519
556
|
|
|
520
557
|
// Validate message signature
|
|
558
|
+
const dataToVerify = Utils.toArray(
|
|
559
|
+
(peerSession.sessionNonce ?? '') + (message.initialNonce ?? ''),
|
|
560
|
+
'base64'
|
|
561
|
+
)
|
|
521
562
|
const { valid } = await this.wallet.verifySignature({
|
|
522
|
-
data:
|
|
523
|
-
(peerSession.sessionNonce ?? '') + (message.initialNonce ?? ''),
|
|
524
|
-
'base64'
|
|
525
|
-
),
|
|
563
|
+
data: dataToVerify,
|
|
526
564
|
signature: message.signature as number[],
|
|
527
565
|
protocolID: [2, 'auth message signature'],
|
|
528
566
|
keyID: `${peerSession.sessionNonce ?? ''} ${message.initialNonce ?? ''}`,
|
|
@@ -534,51 +572,49 @@ export class Peer {
|
|
|
534
572
|
)
|
|
535
573
|
}
|
|
536
574
|
|
|
537
|
-
//
|
|
538
|
-
// Save the peer's identity key and initial nonce
|
|
539
|
-
// This allows future requests to be linked to the same session
|
|
575
|
+
// Now mark the session as authenticated
|
|
540
576
|
peerSession.peerNonce = message.initialNonce
|
|
541
577
|
peerSession.peerIdentityKey = message.identityKey
|
|
542
578
|
peerSession.isAuthenticated = true
|
|
543
|
-
|
|
579
|
+
peerSession.lastUpdate = Date.now()
|
|
544
580
|
this.sessionManager.updateSession(peerSession)
|
|
545
581
|
|
|
546
|
-
//
|
|
582
|
+
// If the handshake had requested certificates, validate them
|
|
547
583
|
if (
|
|
548
|
-
|
|
549
|
-
|
|
584
|
+
this.certificatesToRequest?.certifiers?.length > 0 &&
|
|
585
|
+
message.certificates?.length as number > 0
|
|
550
586
|
) {
|
|
551
|
-
await validateCertificates(
|
|
552
|
-
this.wallet,
|
|
553
|
-
message,
|
|
554
|
-
this.certificatesToRequest
|
|
555
|
-
)
|
|
587
|
+
await validateCertificates(this.wallet, message, this.certificatesToRequest)
|
|
556
588
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
)
|
|
562
|
-
}
|
|
589
|
+
// Notify listeners
|
|
590
|
+
this.onCertificatesReceivedCallbacks.forEach(cb =>
|
|
591
|
+
cb(message.identityKey, message.certificates as VerifiableCertificate[])
|
|
592
|
+
)
|
|
563
593
|
}
|
|
564
594
|
|
|
595
|
+
// Update lastInteractedWithPeer
|
|
565
596
|
this.lastInteractedWithPeer = message.identityKey
|
|
566
597
|
|
|
598
|
+
// Let the handshake wait-latch know we got our response
|
|
567
599
|
this.onInitialResponseReceivedCallbacks.forEach(entry => {
|
|
568
|
-
if (entry
|
|
600
|
+
if (entry.sessionNonce === peerSession.sessionNonce) {
|
|
569
601
|
entry.callback(peerSession.sessionNonce)
|
|
570
602
|
}
|
|
571
603
|
})
|
|
572
604
|
|
|
573
|
-
//
|
|
574
|
-
if (
|
|
605
|
+
// The peer might also request certificates from us
|
|
606
|
+
if (
|
|
607
|
+
(message.requestedCertificates != null) &&
|
|
608
|
+
Array.isArray(message.requestedCertificates.certifiers) &&
|
|
609
|
+
message.requestedCertificates.certifiers.length > 0
|
|
610
|
+
) {
|
|
575
611
|
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
|
|
576
|
-
//
|
|
577
|
-
this.onCertificateRequestReceivedCallbacks.forEach(
|
|
578
|
-
|
|
612
|
+
// Let the application handle it
|
|
613
|
+
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
|
|
614
|
+
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
|
|
579
615
|
})
|
|
580
616
|
} else {
|
|
581
|
-
// Attempt
|
|
617
|
+
// Attempt auto
|
|
582
618
|
const verifiableCertificates = await getVerifiableCertificates(
|
|
583
619
|
this.wallet,
|
|
584
620
|
message.requestedCertificates,
|
|
@@ -594,11 +630,10 @@ export class Peer {
|
|
|
594
630
|
|
|
595
631
|
/**
|
|
596
632
|
* Processes an incoming certificate request message from a peer.
|
|
597
|
-
* Verifies
|
|
598
|
-
* then initiates a response with any requested certificates that are available.
|
|
633
|
+
* Verifies nonce/signature and then possibly sends a certificateResponse.
|
|
599
634
|
*
|
|
600
635
|
* @param {AuthMessage} message - The certificate request message received from the peer.
|
|
601
|
-
* @throws {Error}
|
|
636
|
+
* @throws {Error} if nonce or signature is invalid.
|
|
602
637
|
*/
|
|
603
638
|
private async processCertificateRequest (message: AuthMessage): Promise<void> {
|
|
604
639
|
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
|
|
@@ -608,41 +643,45 @@ export class Peer {
|
|
|
608
643
|
)
|
|
609
644
|
}
|
|
610
645
|
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
|
|
646
|
+
if (peerSession == null) {
|
|
647
|
+
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
|
|
648
|
+
}
|
|
611
649
|
|
|
612
650
|
const { valid } = await this.wallet.verifySignature({
|
|
613
|
-
data: Utils.toArray(
|
|
614
|
-
JSON.stringify(message.requestedCertificates),
|
|
615
|
-
'utf8'
|
|
616
|
-
),
|
|
651
|
+
data: Utils.toArray(JSON.stringify(message.requestedCertificates), 'utf8'),
|
|
617
652
|
signature: message.signature as number[],
|
|
618
653
|
protocolID: [2, 'auth message signature'],
|
|
619
|
-
keyID: `${message.nonce ?? ''} ${peerSession
|
|
620
|
-
counterparty: peerSession
|
|
654
|
+
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
|
|
655
|
+
counterparty: peerSession.peerIdentityKey
|
|
621
656
|
})
|
|
622
|
-
|
|
623
657
|
if (!valid) {
|
|
624
658
|
throw new Error(
|
|
625
|
-
`Invalid signature in certificate request message from ${peerSession
|
|
659
|
+
`Invalid signature in certificate request message from ${peerSession.peerIdentityKey as string}`
|
|
626
660
|
)
|
|
627
661
|
}
|
|
628
662
|
|
|
629
|
-
|
|
663
|
+
// Update usage
|
|
664
|
+
peerSession.lastUpdate = Date.now()
|
|
665
|
+
this.sessionManager.updateSession(peerSession)
|
|
666
|
+
|
|
667
|
+
if (
|
|
668
|
+
(message.requestedCertificates != null) &&
|
|
669
|
+
Array.isArray(message.requestedCertificates.certifiers) &&
|
|
670
|
+
message.requestedCertificates.certifiers.length > 0
|
|
671
|
+
) {
|
|
630
672
|
if (this.onCertificateRequestReceivedCallbacks.size > 0) {
|
|
631
|
-
//
|
|
632
|
-
this.onCertificateRequestReceivedCallbacks.forEach(
|
|
633
|
-
|
|
673
|
+
// Let the application handle it
|
|
674
|
+
this.onCertificateRequestReceivedCallbacks.forEach(cb => {
|
|
675
|
+
cb(message.identityKey, message.requestedCertificates as RequestedCertificateSet)
|
|
634
676
|
})
|
|
635
677
|
} else {
|
|
636
|
-
// Attempt
|
|
678
|
+
// Attempt auto
|
|
637
679
|
const verifiableCertificates = await getVerifiableCertificates(
|
|
638
680
|
this.wallet,
|
|
639
681
|
message.requestedCertificates,
|
|
640
682
|
message.identityKey
|
|
641
683
|
)
|
|
642
|
-
await this.sendCertificateResponse(
|
|
643
|
-
message.identityKey,
|
|
644
|
-
verifiableCertificates
|
|
645
|
-
)
|
|
684
|
+
await this.sendCertificateResponse(message.identityKey, verifiableCertificates)
|
|
646
685
|
}
|
|
647
686
|
}
|
|
648
687
|
}
|
|
@@ -651,10 +690,8 @@ export class Peer {
|
|
|
651
690
|
* Sends a certificate response message containing the specified certificates to a peer.
|
|
652
691
|
*
|
|
653
692
|
* @param {string} verifierIdentityKey - The identity key of the peer requesting the certificates.
|
|
654
|
-
* @param {VerifiableCertificate[]} certificates - The list of certificates to
|
|
655
|
-
* @
|
|
656
|
-
*
|
|
657
|
-
* @throws {Error} Throws an error if the peer session could not be authenticated or if message signing fails.
|
|
693
|
+
* @param {VerifiableCertificate[]} certificates - The list of certificates to include in the response.
|
|
694
|
+
* @throws Will throw an error if the transport fails to send the message.
|
|
658
695
|
*/
|
|
659
696
|
async sendCertificateResponse (
|
|
660
697
|
verifierIdentityKey: string,
|
|
@@ -681,12 +718,17 @@ export class Peer {
|
|
|
681
718
|
signature
|
|
682
719
|
}
|
|
683
720
|
|
|
721
|
+
// Update usage
|
|
722
|
+
peerSession.lastUpdate = Date.now()
|
|
723
|
+
this.sessionManager.updateSession(peerSession)
|
|
724
|
+
|
|
684
725
|
try {
|
|
685
726
|
await this.transport.send(certificateResponse)
|
|
686
727
|
} catch (error: any) {
|
|
687
728
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
688
729
|
throw new Error(
|
|
689
|
-
`Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
730
|
+
`Failed to send certificate response message to peer ${peerSession.peerIdentityKey ?? 'unknown'
|
|
731
|
+
}: ${errorMessage}`
|
|
690
732
|
)
|
|
691
733
|
}
|
|
692
734
|
}
|
|
@@ -696,43 +738,49 @@ export class Peer {
|
|
|
696
738
|
*
|
|
697
739
|
* @private
|
|
698
740
|
* @param {AuthMessage} message - The incoming certificate response message.
|
|
699
|
-
* @returns {Promise<void>}
|
|
700
741
|
* @throws Will throw an error if nonce verification or signature verification fails.
|
|
701
742
|
*/
|
|
702
743
|
private async processCertificateResponse (message: AuthMessage): Promise<void> {
|
|
703
744
|
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
|
|
704
745
|
if (!validNonce) {
|
|
705
746
|
throw new Error(
|
|
706
|
-
`Unable to verify nonce for certificate response from: ${message.identityKey}
|
|
747
|
+
`Unable to verify nonce for certificate response from: ${message.identityKey}`
|
|
707
748
|
)
|
|
708
749
|
}
|
|
750
|
+
|
|
709
751
|
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
|
|
752
|
+
if (peerSession == null) {
|
|
753
|
+
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
|
|
754
|
+
}
|
|
710
755
|
|
|
711
756
|
// Validate message signature
|
|
712
757
|
const { valid } = await this.wallet.verifySignature({
|
|
713
758
|
data: Utils.toArray(JSON.stringify(message.certificates), 'utf8'),
|
|
714
759
|
signature: message.signature as number[],
|
|
715
760
|
protocolID: [2, 'auth message signature'],
|
|
716
|
-
keyID: `${message.nonce ?? ''} ${peerSession
|
|
761
|
+
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
|
|
717
762
|
counterparty: message.identityKey
|
|
718
763
|
})
|
|
719
|
-
|
|
720
764
|
if (!valid) {
|
|
721
765
|
throw new Error(
|
|
722
766
|
`Unable to verify certificate response signature for peer: ${message.identityKey}`
|
|
723
767
|
)
|
|
724
768
|
}
|
|
725
769
|
|
|
726
|
-
//
|
|
770
|
+
// We also handle optional validation if there's a requestedCertificates field
|
|
727
771
|
await validateCertificates(
|
|
728
772
|
this.wallet,
|
|
729
773
|
message,
|
|
730
774
|
message.requestedCertificates
|
|
731
775
|
)
|
|
732
776
|
|
|
733
|
-
|
|
734
|
-
|
|
777
|
+
// Notify any listeners
|
|
778
|
+
this.onCertificatesReceivedCallbacks.forEach(cb => {
|
|
779
|
+
cb(message.identityKey, message.certificates ?? [])
|
|
735
780
|
})
|
|
781
|
+
|
|
782
|
+
peerSession.lastUpdate = Date.now()
|
|
783
|
+
this.sessionManager.updateSession(peerSession)
|
|
736
784
|
}
|
|
737
785
|
|
|
738
786
|
/**
|
|
@@ -740,8 +788,7 @@ export class Peer {
|
|
|
740
788
|
*
|
|
741
789
|
* @private
|
|
742
790
|
* @param {AuthMessage} message - The incoming general message.
|
|
743
|
-
* @
|
|
744
|
-
* @throws Will throw an error if nonce verification or signature verification fails.
|
|
791
|
+
* @throws Will throw an error if nonce or signature verification fails.
|
|
745
792
|
*/
|
|
746
793
|
private async processGeneralMessage (message: AuthMessage): Promise<void> {
|
|
747
794
|
const validNonce = await verifyNonce(message.yourNonce as string, this.wallet)
|
|
@@ -750,26 +797,35 @@ export class Peer {
|
|
|
750
797
|
`Unable to verify nonce for general message from: ${message.identityKey}`
|
|
751
798
|
)
|
|
752
799
|
}
|
|
800
|
+
|
|
753
801
|
const peerSession = this.sessionManager.getSession(message.yourNonce as string)
|
|
802
|
+
if (peerSession == null) {
|
|
803
|
+
throw new Error(`Session not found for nonce: ${message.yourNonce as string}`)
|
|
804
|
+
}
|
|
754
805
|
|
|
755
806
|
const { valid } = await this.wallet.verifySignature({
|
|
756
807
|
data: message.payload,
|
|
757
808
|
signature: message.signature as number[],
|
|
758
809
|
protocolID: [2, 'auth message signature'],
|
|
759
|
-
keyID: `${message.nonce ?? ''} ${peerSession
|
|
760
|
-
counterparty: peerSession
|
|
810
|
+
keyID: `${message.nonce ?? ''} ${peerSession.sessionNonce ?? ''}`,
|
|
811
|
+
counterparty: peerSession.peerIdentityKey
|
|
761
812
|
})
|
|
762
|
-
|
|
763
813
|
if (!valid) {
|
|
764
814
|
throw new Error(
|
|
765
|
-
`Invalid signature in generalMessage from ${peerSession
|
|
815
|
+
`Invalid signature in generalMessage from ${peerSession.peerIdentityKey as string}`
|
|
766
816
|
)
|
|
767
817
|
}
|
|
768
818
|
|
|
819
|
+
// Mark last usage
|
|
820
|
+
peerSession.lastUpdate = Date.now()
|
|
821
|
+
this.sessionManager.updateSession(peerSession)
|
|
822
|
+
|
|
823
|
+
// Update lastInteractedWithPeer
|
|
769
824
|
this.lastInteractedWithPeer = message.identityKey
|
|
770
825
|
|
|
771
|
-
|
|
772
|
-
|
|
826
|
+
// Dispatch callbacks
|
|
827
|
+
this.onGeneralMessageReceivedCallbacks.forEach(cb => {
|
|
828
|
+
cb(message.identityKey, message.payload ?? [])
|
|
773
829
|
})
|
|
774
830
|
}
|
|
775
831
|
}
|