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,
|
|
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,
|
|
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 };
|