@johnhenry/wsh 0.3.0 → 0.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@johnhenry/wsh",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Web Shell \u2014 browser-native remote command execution over WebTransport and WebSocket with Ed25519 authentication",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
package/src/client.mjs CHANGED
@@ -357,11 +357,13 @@ export class WshClient {
357
357
  return this.#sessionId;
358
358
  }
359
359
 
360
- // Sign the challenge.
360
+ // Sign the challenge. Challenge.session_id (not the one from
361
+ // ServerHello) is authoritative for the transcript — see the
362
+ // comment in the CHALLENGE-first branch below for why.
361
363
  const { signature, publicKeyRaw } = await signChallenge(
362
364
  keyPair.privateKey,
363
365
  keyPair.publicKey,
364
- tempSessionId,
366
+ challengeMsg.session_id,
365
367
  challengeMsg.nonce,
366
368
  { username }
367
369
  );
@@ -388,17 +390,18 @@ export class WshClient {
388
390
  throw new Error('Server sent CHALLENGE but no key pair was provided');
389
391
  }
390
392
 
391
- // We need a session ID for the transcript. Use the nonce-derived ID
392
- // or a fresh random placeholder if the server hasn't provided one.
393
- // (A fixed literal here would make the transcript's session-id
394
- // component identical across every connection taking this path,
395
- // leaving only the nonce to bind the signature to this connection.)
396
- tempSessionId = tempSessionId || crypto.randomUUID();
397
-
393
+ // Challenge carries session_id directly (protocol requirement as
394
+ // of wsh-v1's Challenge.session_id field), so the transcript's
395
+ // session-id component is always the server's real, authoritative
396
+ // value regardless of whether ServerHello was sent, dropped, or
397
+ // arrived out of order. No synthesizing a placeholder here. Keep
398
+ // tempSessionId as a fallback for this.#sessionId below in case
399
+ // AUTH_OK's own session_id is ever absent.
400
+ tempSessionId = firstResponse.session_id;
398
401
  const { signature, publicKeyRaw } = await signChallenge(
399
402
  keyPair.privateKey,
400
403
  keyPair.publicKey,
401
- tempSessionId,
404
+ firstResponse.session_id,
402
405
  firstResponse.nonce,
403
406
  { username }
404
407
  );
@@ -1436,7 +1439,7 @@ export class WshClient {
1436
1439
  }
1437
1440
 
1438
1441
  const { signature, publicKeyRaw } = await signChallenge(
1439
- keyPair.privateKey, keyPair.publicKey, tempSessionId, challengeMsg.nonce, { username }
1442
+ keyPair.privateKey, keyPair.publicKey, challengeMsg.session_id, challengeMsg.nonce, { username }
1440
1443
  );
1441
1444
 
1442
1445
  await this.#transport.sendControl(authMsg({
@@ -1450,11 +1453,13 @@ export class WshClient {
1450
1453
  } else if (firstResponse.type === MSG.CHALLENGE) {
1451
1454
  if (!keyPair) throw new Error('Server sent CHALLENGE but no key pair provided');
1452
1455
 
1453
- // See connect()'s CHALLENGE branch for why this must be random
1454
- // rather than a fixed literal.
1455
- tempSessionId = tempSessionId || crypto.randomUUID();
1456
+ // Challenge carries session_id directly — see connect()'s
1457
+ // CHALLENGE branch for why. Keep tempSessionId as a fallback for
1458
+ // this.#sessionId below in case AUTH_OK's own session_id is ever
1459
+ // absent.
1460
+ tempSessionId = firstResponse.session_id;
1456
1461
  const { signature, publicKeyRaw } = await signChallenge(
1457
- keyPair.privateKey, keyPair.publicKey, tempSessionId, firstResponse.nonce, { username }
1462
+ keyPair.privateKey, keyPair.publicKey, firstResponse.session_id, firstResponse.nonce, { username }
1458
1463
  );
1459
1464
 
1460
1465
  await this.#transport.sendControl(authMsg({
@@ -200,10 +200,11 @@ export function serverHello({ sessionId, features = [], fingerprints = [] } = {}
200
200
  };
201
201
  }
202
202
 
203
- export function challenge({ nonce } = {}) {
203
+ export function challenge({ nonce, sessionId } = {}) {
204
204
  return {
205
205
  type: MSG.CHALLENGE,
206
206
  nonce,
207
+ session_id: sessionId,
207
208
  };
208
209
  }
209
210