wire-mesh-core 4.0.6 → 4.0.7

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.
@@ -8,6 +8,8 @@ const require_domain_room_token_verification = require("./room-token-verificatio
8
8
  *
9
9
  * The receiving side's own security property is worth stating plainly, since it's what makes this handler simpler than it might look: it needs no separate check of who actually sent the request. verifyRoomToken (obligation 1) already certifies that the recipient's OWN held room:member token's delegation chain terminates at the room's real, rightful owner -- deriving the ECDH shared secret against that certified owner's own rootIssuerKey means a wrapped-key only ever unwraps correctly when it was actually produced by someone holding the real owner's private key. A forged room.rekey from anyone else -- an attacker, a not-yet-admitted stranger, even another legitimate member with no admission authority -- cannot produce ciphertext that unwraps, so it fails closed on the cryptography itself rather than needing a redundant, separately-maintained live-sender identity check.
10
10
  */
11
+ /** The error code a room.rekey is answered with when it unwrapped but the recipient could not store the keys, so the sender knows the key is not held and sends it again. */
12
+ const REKEY_STORE_FAILED = "store_failed";
11
13
  /**
12
14
  * Builds a room.rekey command per room.cddl. The outer verb is room:member, the same "one resource, several inner verbs" pattern room.send/room.leave/room.members already establish -- not a new capability of its own.
13
15
  */
@@ -29,7 +31,7 @@ async function sendRoomRekey(session, roomPath, keyEpoch, wrappedKey, targetDevi
29
31
  }, targetDevice, token);
30
32
  }
31
33
  /**
32
- * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result.
34
+ * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result. The unwrapped keys are then handed to onRekey, and the sender is answered "ok" only once onRekey has stored them, or "store_failed" when it could not.
33
35
  */
34
36
  function createRoomRekeyHandler(options) {
35
37
  return async function handleRoomRekey(incoming) {
@@ -99,15 +101,24 @@ function createRoomRekeyHandler(options) {
99
101
  });
100
102
  return;
101
103
  }
104
+ try {
105
+ await options.onRekey({
106
+ room: roomPath,
107
+ keyEpoch,
108
+ contentKeys
109
+ });
110
+ } catch (error) {
111
+ await incoming.respond({
112
+ result: "error",
113
+ code: REKEY_STORE_FAILED
114
+ });
115
+ throw error;
116
+ }
102
117
  await incoming.respond({ result: "ok" });
103
- await options.onRekey({
104
- room: roomPath,
105
- keyEpoch,
106
- contentKeys
107
- });
108
118
  };
109
119
  }
110
120
  //#endregion
121
+ exports.REKEY_STORE_FAILED = REKEY_STORE_FAILED;
111
122
  exports.buildRoomRekeyCommand = buildRoomRekeyCommand;
112
123
  exports.createRoomRekeyHandler = createRoomRekeyHandler;
113
124
  exports.sendRoomRekey = sendRoomRekey;
@@ -4,6 +4,8 @@ import { t as Clock } from "../clock-DhUPRfLh.cjs";
4
4
  import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
5
5
  import { RevocationCheck } from "./tokens.cjs";
6
6
  //#region src/domain/room-rekey.d.ts
7
+ /** The error code a room.rekey is answered with when it unwrapped but the recipient could not store the keys, so the sender knows the key is not held and sends it again. */
8
+ export declare const REKEY_STORE_FAILED = "store_failed";
7
9
  /**
8
10
  * Builds a room.rekey command per room.cddl. The outer verb is room:member, the same "one resource, several inner verbs" pattern room.send/room.leave/room.members already establish -- not a new capability of its own.
9
11
  */
@@ -25,11 +27,11 @@ export interface CreateRoomRekeyHandlerOptions {
25
27
  revocation: RevocationCheck;
26
28
  /** This recipient's own currently-held room:member token for the room being rekeyed, verified fresh on every incoming room.rekey (not cached) -- see this module's own doc comment for why its certified root issuer-key is what the ECDH derivation uses, rather than a separately-tracked live-sender identity. */
27
29
  ownRoomMemberToken: CapabilityToken;
28
- /** Called once per successfully unwrapped room.rekey, after the sender has been told it succeeded. May be asynchronous, as storing a key durably is; the handler waits for it, so a failure to store reaches whoever awaits the handler. */
30
+ /** Called once per successfully unwrapped room.rekey, before the sender is told it succeeded. May be asynchronous, as storing a key durably is; the handler waits for it and answers "ok" only once it has settled, and when it rejects the handler answers `store_failed` and rejects with the same error, so the failure reaches both the sender and whoever awaits the handler. */
29
31
  onRekey: (event: Readonly<RoomRekeyEvent>) => Promise<void> | void;
30
32
  }
31
33
  /**
32
- * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result.
34
+ * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result. The unwrapped keys are then handed to onRekey, and the sender is answered "ok" only once onRekey has stored them, or "store_failed" when it could not.
33
35
  */
34
36
  export declare function createRoomRekeyHandler(options: Readonly<CreateRoomRekeyHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
35
37
  //#endregion
@@ -4,6 +4,8 @@ import { t as Clock } from "../clock-DhUPRfLh.mjs";
4
4
  import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
5
5
  import { RevocationCheck } from "./tokens.mjs";
6
6
  //#region src/domain/room-rekey.d.ts
7
+ /** The error code a room.rekey is answered with when it unwrapped but the recipient could not store the keys, so the sender knows the key is not held and sends it again. */
8
+ export declare const REKEY_STORE_FAILED = "store_failed";
7
9
  /**
8
10
  * Builds a room.rekey command per room.cddl. The outer verb is room:member, the same "one resource, several inner verbs" pattern room.send/room.leave/room.members already establish -- not a new capability of its own.
9
11
  */
@@ -25,11 +27,11 @@ export interface CreateRoomRekeyHandlerOptions {
25
27
  revocation: RevocationCheck;
26
28
  /** This recipient's own currently-held room:member token for the room being rekeyed, verified fresh on every incoming room.rekey (not cached) -- see this module's own doc comment for why its certified root issuer-key is what the ECDH derivation uses, rather than a separately-tracked live-sender identity. */
27
29
  ownRoomMemberToken: CapabilityToken;
28
- /** Called once per successfully unwrapped room.rekey, after the sender has been told it succeeded. May be asynchronous, as storing a key durably is; the handler waits for it, so a failure to store reaches whoever awaits the handler. */
30
+ /** Called once per successfully unwrapped room.rekey, before the sender is told it succeeded. May be asynchronous, as storing a key durably is; the handler waits for it and answers "ok" only once it has settled, and when it rejects the handler answers `store_failed` and rejects with the same error, so the failure reaches both the sender and whoever awaits the handler. */
29
31
  onRekey: (event: Readonly<RoomRekeyEvent>) => Promise<void> | void;
30
32
  }
31
33
  /**
32
- * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result.
34
+ * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result. The unwrapped keys are then handed to onRekey, and the sender is answered "ok" only once onRekey has stored them, or "store_failed" when it could not.
33
35
  */
34
36
  export declare function createRoomRekeyHandler(options: Readonly<CreateRoomRekeyHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
35
37
  //#endregion
@@ -7,6 +7,8 @@ import { ROOM_MEMBER_CAPABILITY, verifyRoomToken } from "./room-token-verificati
7
7
  *
8
8
  * The receiving side's own security property is worth stating plainly, since it's what makes this handler simpler than it might look: it needs no separate check of who actually sent the request. verifyRoomToken (obligation 1) already certifies that the recipient's OWN held room:member token's delegation chain terminates at the room's real, rightful owner -- deriving the ECDH shared secret against that certified owner's own rootIssuerKey means a wrapped-key only ever unwraps correctly when it was actually produced by someone holding the real owner's private key. A forged room.rekey from anyone else -- an attacker, a not-yet-admitted stranger, even another legitimate member with no admission authority -- cannot produce ciphertext that unwraps, so it fails closed on the cryptography itself rather than needing a redundant, separately-maintained live-sender identity check.
9
9
  */
10
+ /** The error code a room.rekey is answered with when it unwrapped but the recipient could not store the keys, so the sender knows the key is not held and sends it again. */
11
+ const REKEY_STORE_FAILED = "store_failed";
10
12
  /**
11
13
  * Builds a room.rekey command per room.cddl. The outer verb is room:member, the same "one resource, several inner verbs" pattern room.send/room.leave/room.members already establish -- not a new capability of its own.
12
14
  */
@@ -28,7 +30,7 @@ async function sendRoomRekey(session, roomPath, keyEpoch, wrappedKey, targetDevi
28
30
  }, targetDevice, token);
29
31
  }
30
32
  /**
31
- * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result.
33
+ * Builds a reusable handler for one room's incoming room.rekey messages. Checks, in order: the outer verb is room:member (otherwise "malformed"); the params payload parses against room.rekey's own CDDL shape (otherwise "malformed"); the request's own scope is a room scope (otherwise "scope_mismatch"); ownRoomMemberToken independently passes every one of core/room's six verifier obligations, scoped to the incoming request's own room path (a failure responds with verifyRoomToken's own specific reason, e.g. "wrong_chain_root"/"wrong_scope_path"/"expired"); this identity actually supports deriveSharedSecret (otherwise "ecdh_unsupported" -- an Ed25519-only identity genuinely cannot participate). Only then does it derive the shared secret against the verified chain's own rootIssuerKey, derive one wrapping key per granted epoch (per the array-ordering convention: entry i is epoch i+1, counting back from key-epoch), and unwrap each -- a failure at that final cryptographic step (a forged sender, or genuine corruption) responds "unwrap_failed" rather than surfacing a partially-decoded result. The unwrapped keys are then handed to onRekey, and the sender is answered "ok" only once onRekey has stored them, or "store_failed" when it could not.
32
34
  */
33
35
  function createRoomRekeyHandler(options) {
34
36
  return async function handleRoomRekey(incoming) {
@@ -98,13 +100,21 @@ function createRoomRekeyHandler(options) {
98
100
  });
99
101
  return;
100
102
  }
103
+ try {
104
+ await options.onRekey({
105
+ room: roomPath,
106
+ keyEpoch,
107
+ contentKeys
108
+ });
109
+ } catch (error) {
110
+ await incoming.respond({
111
+ result: "error",
112
+ code: REKEY_STORE_FAILED
113
+ });
114
+ throw error;
115
+ }
101
116
  await incoming.respond({ result: "ok" });
102
- await options.onRekey({
103
- room: roomPath,
104
- keyEpoch,
105
- contentKeys
106
- });
107
117
  };
108
118
  }
109
119
  //#endregion
110
- export { buildRoomRekeyCommand, createRoomRekeyHandler, sendRoomRekey };
120
+ export { REKEY_STORE_FAILED, buildRoomRekeyCommand, createRoomRekeyHandler, sendRoomRekey };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "4.0.6",
3
+ "version": "4.0.7",
4
4
  "dependencies": {
5
5
  "cbor2": "2.3.0",
6
6
  "cddl.js": "1.0.1",