whalibmob 5.14.4 → 5.14.5
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/cli.js +6 -1
- package/lib/noise.js +55 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -202,7 +202,12 @@ function enableWireTrace() {
|
|
|
202
202
|
return origWrite(chunk, ...rest);
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
// Prepended, not appended: the socket already carries the handshake's own
|
|
206
|
+
// data listener, registered while connect() was building the socket. An
|
|
207
|
+
// appended one runs after the frame has been read, so a frame the server
|
|
208
|
+
// sent would print *after* the error it caused — or not at all, once the
|
|
209
|
+
// failure tears the connection down. The trace has to come first.
|
|
210
|
+
sock.prependListener('data', (d) => {
|
|
206
211
|
if (this.secured) return;
|
|
207
212
|
// Label positionally, but say so when the reply is plainly not a Noise
|
|
208
213
|
// frame — a proxy or captive portal answering in ASCII is worth reading
|
package/lib/noise.js
CHANGED
|
@@ -301,6 +301,25 @@ function readFrameLength(data) {
|
|
|
301
301
|
return (data[0] << 16) | (data[1] << 8) | data[2];
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
+
// ─── Handshake trace ─────────────────────────────────────────────────────────
|
|
305
|
+
//
|
|
306
|
+
// The handshake runs before there are stanzas to log, so without this the debug
|
|
307
|
+
// output jumps from "connecting" to either a secured channel or an error with
|
|
308
|
+
// nothing in between — and when it fails, nothing to say which step it got to.
|
|
309
|
+
//
|
|
310
|
+
// Only public material is rendered: ephemeral and static public keys, frame and
|
|
311
|
+
// field sizes, certificate outcome. Derived session keys and anything private
|
|
312
|
+
// are named but never printed, because a debug trace is the thing people paste
|
|
313
|
+
// into a bug report.
|
|
314
|
+
function _hsKey(buf) {
|
|
315
|
+
if (!Buffer.isBuffer(buf)) return String(buf);
|
|
316
|
+
return buf.toString('hex') + ' (' + buf.length + 'B)';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function _hsSize(buf) {
|
|
320
|
+
return Buffer.isBuffer(buf) ? buf.length + 'B' : 'absent';
|
|
321
|
+
}
|
|
322
|
+
|
|
304
323
|
// ─── NoiseSocket ─────────────────────────────────────────────────────────────
|
|
305
324
|
//
|
|
306
325
|
// Events emitted:
|
|
@@ -395,9 +414,18 @@ class NoiseSocket extends EventEmitter {
|
|
|
395
414
|
};
|
|
396
415
|
this.noiseState.hash(this._ephemeralKeyPair.public);
|
|
397
416
|
|
|
417
|
+
_whaDbg('[DBG] NOISE handshake begin transport=' + this.transport +
|
|
418
|
+
' prologue=' + this._prologue.toString('hex') +
|
|
419
|
+
' pattern=Noise_XX_25519_AESGCM_SHA256');
|
|
420
|
+
_whaDbg('[DBG] NOISE ──▶ ClientHello ephemeral=' + _hsKey(this._ephemeralKeyPair.public));
|
|
421
|
+
|
|
398
422
|
const helloPayload = encodeHandshakeClientHello(this._ephemeralKeyPair.public);
|
|
399
423
|
const frame = Buffer.concat([this._prologue, makeFrame(helloPayload)]);
|
|
424
|
+
_whaDbg('[DBG] NOISE ──▶ ClientHello ' + frame.length + 'B on the wire = prologue ' +
|
|
425
|
+
this._prologue.length + 'B + header ' + FRAME_HEADER_SIZE + 'B + payload ' +
|
|
426
|
+
helloPayload.length + 'B');
|
|
400
427
|
this.socket.write(frame);
|
|
428
|
+
_whaDbg('[DBG] NOISE waiting for ServerHello');
|
|
401
429
|
}
|
|
402
430
|
|
|
403
431
|
_onData(data) {
|
|
@@ -424,16 +452,24 @@ class NoiseSocket extends EventEmitter {
|
|
|
424
452
|
|
|
425
453
|
_handleHandshakeFrame(frame) {
|
|
426
454
|
try {
|
|
455
|
+
_whaDbg('[DBG] NOISE ◀── ServerHello ' + frame.length + 'B frame');
|
|
427
456
|
const serverHello = decodeServerHello(frame);
|
|
457
|
+
_whaDbg('[DBG] NOISE ◀── ServerHello ephemeral=' + _hsSize(serverHello.ephemeral) +
|
|
458
|
+
' static=' + _hsSize(serverHello.staticEnc) + ' encrypted' +
|
|
459
|
+
' certificate=' + _hsSize(serverHello.payload) + ' encrypted');
|
|
428
460
|
if (!serverHello.ephemeral) throw new Error('No server ephemeral in ServerHello');
|
|
461
|
+
_whaDbg('[DBG] NOISE ◀── server ephemeral=' + _hsKey(serverHello.ephemeral));
|
|
429
462
|
|
|
430
463
|
this.noiseState.hash(serverHello.ephemeral);
|
|
431
464
|
const sharedEE = dhShared(this._ephemeralKeyPair.private, serverHello.ephemeral);
|
|
432
465
|
this.noiseState.mixKey(sharedEE);
|
|
466
|
+
_whaDbg('[DBG] NOISE mixed DH(our ephemeral, server ephemeral)');
|
|
433
467
|
|
|
434
468
|
const serverStaticDec = this.noiseState.decryptWithAd(serverHello.staticEnc);
|
|
469
|
+
_whaDbg('[DBG] NOISE ◀── server static key decrypted=' + _hsKey(serverStaticDec));
|
|
435
470
|
const sharedSE = dhShared(this._ephemeralKeyPair.private, serverStaticDec);
|
|
436
471
|
this.noiseState.mixKey(sharedSE);
|
|
472
|
+
_whaDbg('[DBG] NOISE mixed DH(our ephemeral, server static)');
|
|
437
473
|
|
|
438
474
|
// The server is authenticated here, before ClientFinish hands it our
|
|
439
475
|
// static key and payload. A ServerHello without a certificate leaves
|
|
@@ -442,14 +478,20 @@ class NoiseSocket extends EventEmitter {
|
|
|
442
478
|
throw new Error('WA server cert validation failed: the server sent no certificate');
|
|
443
479
|
}
|
|
444
480
|
const certPlain = this.noiseState.decryptWithAd(serverHello.payload);
|
|
481
|
+
_whaDbg('[DBG] NOISE ◀── certificate decrypted=' + certPlain.length + 'B — verifying');
|
|
445
482
|
validateServerCert(certPlain, serverStaticDec);
|
|
483
|
+
_whaDbg('[DBG] NOISE ◀── certificate verified: signed by the root CA and ' +
|
|
484
|
+
'certifying the static key above — server authenticated');
|
|
446
485
|
|
|
447
486
|
const noisePriv = this.store.noiseKeyPair.private;
|
|
448
487
|
// Send the raw 32-byte noise public key (no 0x05 Signal prefix)
|
|
449
488
|
const noisePub = stripKeyPrefix(this.store.noiseKeyPair.public);
|
|
450
489
|
const encStatic = this.noiseState.encryptWithAd(noisePub);
|
|
490
|
+
_whaDbg('[DBG] NOISE ──▶ our static key=' + _hsKey(noisePub) +
|
|
491
|
+
' encrypted to ' + encStatic.length + 'B');
|
|
451
492
|
const sharedSS = dhShared(noisePriv, serverHello.ephemeral);
|
|
452
493
|
this.noiseState.mixKey(sharedSS);
|
|
494
|
+
_whaDbg('[DBG] NOISE mixed DH(our static, server ephemeral)');
|
|
453
495
|
|
|
454
496
|
// Web mode hands us a finished companion payload; there is no phone
|
|
455
497
|
// identity to derive anything from, and the fields below do not exist on
|
|
@@ -457,6 +499,9 @@ class NoiseSocket extends EventEmitter {
|
|
|
457
499
|
if (this._buildPayload) {
|
|
458
500
|
const encPayload = this.noiseState.encryptWithAd(this._buildPayload());
|
|
459
501
|
const finish = encodeHandshakeClientFinish(encStatic, encPayload);
|
|
502
|
+
_whaDbg('[DBG] NOISE ──▶ ClientFinish static=' + encStatic.length + 'B payload=' +
|
|
503
|
+
encPayload.length + 'B (companion) ' + (finish.length + FRAME_HEADER_SIZE) +
|
|
504
|
+
'B on the wire');
|
|
460
505
|
this.socket.write(makeFrame(finish));
|
|
461
506
|
|
|
462
507
|
const webKeys = this.noiseState.split();
|
|
@@ -465,6 +510,7 @@ class NoiseSocket extends EventEmitter {
|
|
|
465
510
|
this.writeCounter = 0;
|
|
466
511
|
this.readCounter = 0;
|
|
467
512
|
this.secured = true;
|
|
513
|
+
_whaDbg('[DBG] NOISE split() derived the send and receive keys — channel secured');
|
|
468
514
|
|
|
469
515
|
if (this._expectSuccess) {
|
|
470
516
|
this._awaitingAuth = true;
|
|
@@ -560,6 +606,9 @@ class NoiseSocket extends EventEmitter {
|
|
|
560
606
|
|
|
561
607
|
const encPayload = this.noiseState.encryptWithAd(payload);
|
|
562
608
|
const finish = encodeHandshakeClientFinish(encStatic, encPayload);
|
|
609
|
+
_whaDbg('[DBG] NOISE ──▶ ClientFinish static=' + encStatic.length + 'B payload=' +
|
|
610
|
+
encPayload.length + 'B (username=' + this.store.phoneNumber + ' version=' +
|
|
611
|
+
_version + ') ' + (finish.length + FRAME_HEADER_SIZE) + 'B on the wire');
|
|
563
612
|
this.socket.write(makeFrame(finish));
|
|
564
613
|
|
|
565
614
|
const keys = this.noiseState.split();
|
|
@@ -567,6 +616,8 @@ class NoiseSocket extends EventEmitter {
|
|
|
567
616
|
this.readKey = keys.readKey;
|
|
568
617
|
this.writeCounter = 0;
|
|
569
618
|
this.readCounter = 0;
|
|
619
|
+
_whaDbg('[DBG] NOISE split() derived the send and receive keys — channel secured');
|
|
620
|
+
_whaDbg('[DBG] NOISE waiting for the server to accept the payload');
|
|
570
621
|
this.secured = true;
|
|
571
622
|
|
|
572
623
|
// Wait for the server's <success> or <failure> confirmation before
|
|
@@ -575,6 +626,8 @@ class NoiseSocket extends EventEmitter {
|
|
|
575
626
|
this._awaitingAuth = true;
|
|
576
627
|
|
|
577
628
|
} catch (err) {
|
|
629
|
+
// The step that failed is the one after the last line traced above.
|
|
630
|
+
_whaDbg('[DBG] NOISE ✗ handshake failed: ' + (err && err.message));
|
|
578
631
|
if (this._connectReject) {
|
|
579
632
|
this._connectReject(err);
|
|
580
633
|
this._connectResolve = null;
|
|
@@ -596,6 +649,8 @@ class NoiseSocket extends EventEmitter {
|
|
|
596
649
|
if (this._awaitingAuth) {
|
|
597
650
|
this._awaitingAuth = false;
|
|
598
651
|
const tag = node && node.description;
|
|
652
|
+
_whaDbg('[DBG] NOISE ◀── <' + tag + '> — the first frame over the secured channel, ' +
|
|
653
|
+
'and the server\'s answer on the payload');
|
|
599
654
|
|
|
600
655
|
if (tag === 'success') {
|
|
601
656
|
if (this._connectResolve) {
|