wire-mesh-core 1.32.0 → 1.34.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/dist/domain/capability-grant.cjs +1 -1
- package/dist/domain/capability-grant.d.cts +1 -1
- package/dist/domain/capability-grant.d.mts +1 -1
- package/dist/domain/capability-grant.mjs +1 -1
- package/dist/domain/capability-request.cjs +1 -1
- package/dist/domain/capability-request.mjs +1 -1
- package/dist/domain/group-key.cjs +18 -0
- package/dist/domain/group-key.d.cts +8 -0
- package/dist/domain/group-key.d.mts +8 -0
- package/dist/domain/group-key.mjs +15 -1
- package/dist/domain/revocation-view.cjs +1 -1
- package/dist/domain/revocation-view.d.cts +1 -1
- package/dist/domain/revocation-view.d.mts +1 -1
- package/dist/domain/revocation-view.mjs +1 -1
- package/dist/domain/room-rekey.cjs +112 -0
- package/dist/domain/room-rekey.d.cts +35 -0
- package/dist/domain/room-rekey.d.mts +35 -0
- package/dist/domain/room-rekey.mjs +109 -0
- package/dist/domain/room-token-verification.cjs +3 -2
- package/dist/domain/room-token-verification.d.cts +4 -2
- package/dist/domain/room-token-verification.d.mts +4 -2
- package/dist/domain/room-token-verification.mjs +3 -2
- package/dist/domain/room.cjs +8 -1
- package/dist/domain/room.d.cts +4 -2
- package/dist/domain/room.d.mts +4 -2
- package/dist/domain/room.mjs +8 -1
- package/dist/domain/tokens.cjs +1 -1
- package/dist/domain/tokens.d.cts +1 -1
- package/dist/domain/tokens.d.mts +1 -1
- package/dist/domain/tokens.mjs +1 -1
- package/dist/{tokens-CNQirfHi.d.cts → tokens-C9Kwy0Gk.d.cts} +3 -1
- package/dist/{tokens-VicRWTj1.mjs → tokens-CdQwzCyJ.mjs} +2 -0
- package/dist/{tokens-CiNXdKJ8.cjs → tokens-DxGVURpB.cjs} +2 -0
- package/dist/{tokens-C_LvOuYO.d.mts → tokens-k172l0Iy.d.mts} +3 -1
- package/package.json +5 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
3
|
const require_token_scope = require("../token-scope-CxHXTT3u.cjs");
|
|
4
|
-
const require_tokens = require("../tokens-
|
|
4
|
+
const require_tokens = require("../tokens-DxGVURpB.cjs");
|
|
5
5
|
//#region src/domain/capability-grant.ts
|
|
6
6
|
/**
|
|
7
7
|
* The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
|
|
@@ -2,7 +2,7 @@ import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as Capabilit
|
|
|
2
2
|
import { t as IdentityPort } from "../identity-B4XBWkqg.cjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
4
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
|
|
5
|
-
import { a as RevocationCheck } from "../tokens-
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-C9Kwy0Gk.cjs";
|
|
6
6
|
//#region src/domain/capability-grant.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
@@ -2,7 +2,7 @@ import { D as DeviceId, V as ManageCommand, f as CapabilityScope, p as Capabilit
|
|
|
2
2
|
import { t as IdentityPort } from "../identity-CIb9dGkJ.mjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
4
|
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
|
|
5
|
-
import { a as RevocationCheck } from "../tokens-
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-k172l0Iy.mjs";
|
|
6
6
|
//#region src/domain/capability-grant.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Builds a capability-grant command per management.cddl. The outer `manage-command.verb` is the capability string itself, the identical convention capability-request.ts's own buildCapabilityRequestCommand already establishes -- a receiver's per-capability handler is how it knows which grant this push is even for. `params.verb` is the fixed "capability.grant" marker. No scope or invitee field: manage-request-frame's own top-level scope already carries the former, and the request's own destination already carries the latter -- naming either a second time inside params could only ever disagree with the fact it duplicates.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { capabilityGrantSchema } from "../generated/protocol.mjs";
|
|
2
2
|
import { n as scopeNarrows } from "../token-scope-Z4bmci4M.mjs";
|
|
3
|
-
import { i as verifyCapabilityToken } from "../tokens-
|
|
3
|
+
import { i as verifyCapabilityToken } from "../tokens-CdQwzCyJ.mjs";
|
|
4
4
|
//#region src/domain/capability-grant.ts
|
|
5
5
|
/**
|
|
6
6
|
* The generic, capability-agnostic capability-grant primitive (wire-mesh#117, spec/management.cddl) -- the unsolicited push counterpart to capability-request.ts's own ask/response primitive. Where capability-request lifts core/room's room.join shape (a pull: the requester asks, the owner mints and returns a grant on the same response) out of that one domain, capability-grant lifts room.invite's own shape (a push: the owner mints unprompted and delivers the grant in the request itself, since there is no approval round-trip to carry it back) the same way. core/room's own room-client.ts is the reference consumer: it builds sendRoomInvite as a thin wrapper over sendCapabilityGrant below, and wires createRoomRouter's dispatch onto createCapabilityGrantHandler for the receiving side.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
|
-
const require_tokens = require("../tokens-
|
|
3
|
+
const require_tokens = require("../tokens-DxGVURpB.cjs");
|
|
4
4
|
//#region src/domain/capability-request.ts
|
|
5
5
|
/**
|
|
6
6
|
* The generic, capability-agnostic capability-request / capability-grant-ok primitive (wire-mesh#78, spec/management.cddl). Lifts core/room's own room.join shape -- an ungated ask, held open, a human (or any other domain-supplied decision) accepts or refuses, acceptance mints and returns a token on the same response -- out of that one domain so any capability can reuse it, not just room:member. core/room's own room-client.ts is the reference consumer: it rewires requestToJoin/createRoomRouter's room.join handling onto requestCapability/createCapabilityRequestHandler below, supplying room-specific extension fields (its member list) through this module's own extensions mechanism rather than this module knowing anything about rooms.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { capabilityGrantOkSchema, capabilityRequestSchema } from "../generated/protocol.mjs";
|
|
2
|
-
import { n as mintCapabilityToken } from "../tokens-
|
|
2
|
+
import { n as mintCapabilityToken } from "../tokens-CdQwzCyJ.mjs";
|
|
3
3
|
//#region src/domain/capability-request.ts
|
|
4
4
|
/**
|
|
5
5
|
* The generic, capability-agnostic capability-request / capability-grant-ok primitive (wire-mesh#78, spec/management.cddl). Lifts core/room's own room.join shape -- an ungated ask, held open, a human (or any other domain-supplied decision) accepts or refuses, acceptance mints and returns a token on the same response -- out of that one domain so any capability can reuse it, not just room:member. core/room's own room-client.ts is the reference consumer: it rewires requestToJoin/createRoomRouter's room.join handling onto requestCapability/createCapabilityRequestHandler below, supplying room-specific extension fields (its member list) through this module's own extensions mechanism rather than this module knowing anything about rooms.
|
|
@@ -84,10 +84,28 @@ async function encryptNoticeContent(contentKey, plaintext) {
|
|
|
84
84
|
async function decryptNoticeContent(contentKey, ciphertext) {
|
|
85
85
|
return aesGcmDecrypt(await importContentKey(contentKey), ciphertext);
|
|
86
86
|
}
|
|
87
|
+
/** The literal content-type suffix marking a room-notice's content as AES-256-GCM-encrypted under a room.rekey epoch's content key (room.cddl's own key-epoch obligation comment) -- the same suffix convention MIME structured syntaxes like `application/jose+json` already use, so a reader without the key still learns the notice's true underlying kind. */
|
|
88
|
+
const ENCRYPTED_CONTENT_TYPE_SUFFIX = "+aes256gcm";
|
|
89
|
+
/** The notice's own TRUE content-type, with the encryption suffix appended -- never a separate generic sentinel, so the true type stays visible without decrypting anything. Idempotent: an already-suffixed type is returned unchanged. */
|
|
90
|
+
function encryptedContentType(plaintextContentType_) {
|
|
91
|
+
return isEncryptedContentType(plaintextContentType_) ? plaintextContentType_ : `${plaintextContentType_}${ENCRYPTED_CONTENT_TYPE_SUFFIX}`;
|
|
92
|
+
}
|
|
93
|
+
/** Strips the encryption suffix back off, recovering the plaintext content-type the sender originally had. A content-type carrying no suffix is returned unchanged. */
|
|
94
|
+
function plaintextContentType(encryptedContentType_) {
|
|
95
|
+
return isEncryptedContentType(encryptedContentType_) ? encryptedContentType_.slice(0, encryptedContentType_.length - 10) : encryptedContentType_;
|
|
96
|
+
}
|
|
97
|
+
/** Whether a content-type names encrypted content (and therefore that the notice carrying it MUST also name its key-epoch -- room.cddl obligation 7). */
|
|
98
|
+
function isEncryptedContentType(contentType) {
|
|
99
|
+
return contentType.endsWith(ENCRYPTED_CONTENT_TYPE_SUFFIX);
|
|
100
|
+
}
|
|
87
101
|
//#endregion
|
|
102
|
+
exports.ENCRYPTED_CONTENT_TYPE_SUFFIX = ENCRYPTED_CONTENT_TYPE_SUFFIX;
|
|
88
103
|
exports.decryptNoticeContent = decryptNoticeContent;
|
|
89
104
|
exports.deriveWrappingKey = deriveWrappingKey;
|
|
90
105
|
exports.encryptNoticeContent = encryptNoticeContent;
|
|
106
|
+
exports.encryptedContentType = encryptedContentType;
|
|
91
107
|
exports.generateContentKey = generateContentKey;
|
|
108
|
+
exports.isEncryptedContentType = isEncryptedContentType;
|
|
109
|
+
exports.plaintextContentType = plaintextContentType;
|
|
92
110
|
exports.unwrapContentKey = unwrapContentKey;
|
|
93
111
|
exports.wrapContentKey = wrapContentKey;
|
|
@@ -21,4 +21,12 @@ export declare function unwrapContentKey(wrappingKey: CryptoKey, wrapped: Uint8A
|
|
|
21
21
|
export declare function encryptNoticeContent(contentKey: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
22
22
|
/** Decrypts a room-notice's own `content`, given the content-encryption key for the epoch its key-epoch field names. Rejects on a wrong key or tampered ciphertext, same as unwrapContentKey. */
|
|
23
23
|
export declare function decryptNoticeContent(contentKey: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
24
|
+
/** The literal content-type suffix marking a room-notice's content as AES-256-GCM-encrypted under a room.rekey epoch's content key (room.cddl's own key-epoch obligation comment) -- the same suffix convention MIME structured syntaxes like `application/jose+json` already use, so a reader without the key still learns the notice's true underlying kind. */
|
|
25
|
+
export declare const ENCRYPTED_CONTENT_TYPE_SUFFIX = "+aes256gcm";
|
|
26
|
+
/** The notice's own TRUE content-type, with the encryption suffix appended -- never a separate generic sentinel, so the true type stays visible without decrypting anything. Idempotent: an already-suffixed type is returned unchanged. */
|
|
27
|
+
export declare function encryptedContentType(plaintextContentType_: string): string;
|
|
28
|
+
/** Strips the encryption suffix back off, recovering the plaintext content-type the sender originally had. A content-type carrying no suffix is returned unchanged. */
|
|
29
|
+
export declare function plaintextContentType(encryptedContentType_: string): string;
|
|
30
|
+
/** Whether a content-type names encrypted content (and therefore that the notice carrying it MUST also name its key-epoch -- room.cddl obligation 7). */
|
|
31
|
+
export declare function isEncryptedContentType(contentType: string): boolean;
|
|
24
32
|
//#endregion
|
|
@@ -21,4 +21,12 @@ export declare function unwrapContentKey(wrappingKey: CryptoKey, wrapped: Uint8A
|
|
|
21
21
|
export declare function encryptNoticeContent(contentKey: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
22
22
|
/** Decrypts a room-notice's own `content`, given the content-encryption key for the epoch its key-epoch field names. Rejects on a wrong key or tampered ciphertext, same as unwrapContentKey. */
|
|
23
23
|
export declare function decryptNoticeContent(contentKey: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
24
|
+
/** The literal content-type suffix marking a room-notice's content as AES-256-GCM-encrypted under a room.rekey epoch's content key (room.cddl's own key-epoch obligation comment) -- the same suffix convention MIME structured syntaxes like `application/jose+json` already use, so a reader without the key still learns the notice's true underlying kind. */
|
|
25
|
+
export declare const ENCRYPTED_CONTENT_TYPE_SUFFIX = "+aes256gcm";
|
|
26
|
+
/** The notice's own TRUE content-type, with the encryption suffix appended -- never a separate generic sentinel, so the true type stays visible without decrypting anything. Idempotent: an already-suffixed type is returned unchanged. */
|
|
27
|
+
export declare function encryptedContentType(plaintextContentType_: string): string;
|
|
28
|
+
/** Strips the encryption suffix back off, recovering the plaintext content-type the sender originally had. A content-type carrying no suffix is returned unchanged. */
|
|
29
|
+
export declare function plaintextContentType(encryptedContentType_: string): string;
|
|
30
|
+
/** Whether a content-type names encrypted content (and therefore that the notice carrying it MUST also name its key-epoch -- room.cddl obligation 7). */
|
|
31
|
+
export declare function isEncryptedContentType(contentType: string): boolean;
|
|
24
32
|
//#endregion
|
|
@@ -83,5 +83,19 @@ async function encryptNoticeContent(contentKey, plaintext) {
|
|
|
83
83
|
async function decryptNoticeContent(contentKey, ciphertext) {
|
|
84
84
|
return aesGcmDecrypt(await importContentKey(contentKey), ciphertext);
|
|
85
85
|
}
|
|
86
|
+
/** The literal content-type suffix marking a room-notice's content as AES-256-GCM-encrypted under a room.rekey epoch's content key (room.cddl's own key-epoch obligation comment) -- the same suffix convention MIME structured syntaxes like `application/jose+json` already use, so a reader without the key still learns the notice's true underlying kind. */
|
|
87
|
+
const ENCRYPTED_CONTENT_TYPE_SUFFIX = "+aes256gcm";
|
|
88
|
+
/** The notice's own TRUE content-type, with the encryption suffix appended -- never a separate generic sentinel, so the true type stays visible without decrypting anything. Idempotent: an already-suffixed type is returned unchanged. */
|
|
89
|
+
function encryptedContentType(plaintextContentType_) {
|
|
90
|
+
return isEncryptedContentType(plaintextContentType_) ? plaintextContentType_ : `${plaintextContentType_}${ENCRYPTED_CONTENT_TYPE_SUFFIX}`;
|
|
91
|
+
}
|
|
92
|
+
/** Strips the encryption suffix back off, recovering the plaintext content-type the sender originally had. A content-type carrying no suffix is returned unchanged. */
|
|
93
|
+
function plaintextContentType(encryptedContentType_) {
|
|
94
|
+
return isEncryptedContentType(encryptedContentType_) ? encryptedContentType_.slice(0, encryptedContentType_.length - 10) : encryptedContentType_;
|
|
95
|
+
}
|
|
96
|
+
/** Whether a content-type names encrypted content (and therefore that the notice carrying it MUST also name its key-epoch -- room.cddl obligation 7). */
|
|
97
|
+
function isEncryptedContentType(contentType) {
|
|
98
|
+
return contentType.endsWith(ENCRYPTED_CONTENT_TYPE_SUFFIX);
|
|
99
|
+
}
|
|
86
100
|
//#endregion
|
|
87
|
-
export { decryptNoticeContent, deriveWrappingKey, encryptNoticeContent, generateContentKey, unwrapContentKey, wrapContentKey };
|
|
101
|
+
export { ENCRYPTED_CONTENT_TYPE_SUFFIX, decryptNoticeContent, deriveWrappingKey, encryptNoticeContent, encryptedContentType, generateContentKey, isEncryptedContentType, plaintextContentType, unwrapContentKey, wrapContentKey };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_domain_device_id = require("./device-id.cjs");
|
|
3
|
-
const require_tokens = require("../tokens-
|
|
3
|
+
const require_tokens = require("../tokens-DxGVURpB.cjs");
|
|
4
4
|
//#region src/domain/revocation-view.ts
|
|
5
5
|
function revocationKey(tokenId) {
|
|
6
6
|
return require_domain_device_id.bytesToHex(tokenId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _t as RevocationEntry } from "../protocol-gbXtNeOc.cjs";
|
|
2
|
-
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-
|
|
2
|
+
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-C9Kwy0Gk.cjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* An in-memory RevocationCheck fed by ingested revocation-announce frames. Keyed by token-id alone, per management.cddl's contract: a token-id can legitimately carry multiple recorded entries from different issuers (the token's own issuer, and/or any number of parties holding a delegated manage:revoke authorization over it) -- entriesFor returns every one of them unfiltered, since checking which actually apply (issuer-match, or a verified authorization) is verifyTokenChain's own obligation, not this store's.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _t as RevocationEntry } from "../protocol-gbXtNeOc.mjs";
|
|
2
|
-
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-
|
|
2
|
+
import { a as RevocationCheck, d as VerifyRevocationEntryOptions, o as RevocationEntryVerdict } from "../tokens-k172l0Iy.mjs";
|
|
3
3
|
//#region src/domain/revocation-view.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* An in-memory RevocationCheck fed by ingested revocation-announce frames. Keyed by token-id alone, per management.cddl's contract: a token-id can legitimately carry multiple recorded entries from different issuers (the token's own issuer, and/or any number of parties holding a delegated manage:revoke authorization over it) -- entriesFor returns every one of them unfiltered, since checking which actually apply (issuer-match, or a verified authorization) is verifyTokenChain's own obligation, not this store's.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bytesToHex } from "./device-id.mjs";
|
|
2
|
-
import { a as verifyRevocationEntry } from "../tokens-
|
|
2
|
+
import { a as verifyRevocationEntry } from "../tokens-CdQwzCyJ.mjs";
|
|
3
3
|
//#region src/domain/revocation-view.ts
|
|
4
4
|
function revocationKey(tokenId) {
|
|
5
5
|
return bytesToHex(tokenId);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
|
+
const require_domain_group_key = require("./group-key.cjs");
|
|
4
|
+
const require_domain_room_token_verification = require("./room-token-verification.cjs");
|
|
5
|
+
//#region src/domain/room-rekey.ts
|
|
6
|
+
/**
|
|
7
|
+
* room.rekey (wire-mesh#141, spec/room.cddl): distributes and rotates a room's symmetric content-encryption key. Two responsibilities, split the same way capability-grant.ts and capability-request.ts already split theirs: sendRoomRekey is the pushing side (a thin wrapper over MeshSession.sendManageRequest), and createRoomRekeyHandler is the receiving side.
|
|
8
|
+
*
|
|
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
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 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
|
+
*/
|
|
14
|
+
function buildRoomRekeyCommand(keyEpoch, wrappedKey) {
|
|
15
|
+
return {
|
|
16
|
+
verb: require_domain_room_token_verification.ROOM_MEMBER_CAPABILITY,
|
|
17
|
+
params: {
|
|
18
|
+
verb: "room.rekey",
|
|
19
|
+
"key-epoch": keyEpoch,
|
|
20
|
+
"wrapped-key": wrappedKey
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Sends a room.rekey for roomPath. targetDevice/token forward directly to MeshSession.sendManageRequest's own identically-named parameters, exactly as sendRoomMessage's own doc comment already describes. */
|
|
25
|
+
async function sendRoomRekey(session, roomPath, keyEpoch, wrappedKey, targetDevice, token) {
|
|
26
|
+
return session.sendManageRequest(buildRoomRekeyCommand(keyEpoch, wrappedKey), {
|
|
27
|
+
kind: "room",
|
|
28
|
+
path: roomPath
|
|
29
|
+
}, targetDevice, token);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
function createRoomRekeyHandler(options) {
|
|
35
|
+
return async function handleRoomRekey(incoming) {
|
|
36
|
+
if (incoming.command.verb !== "room:member") {
|
|
37
|
+
await incoming.respond({
|
|
38
|
+
result: "error",
|
|
39
|
+
code: "malformed"
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const parsed = require_generated_protocol.roomRekeySchema.safeParse(incoming.command.params);
|
|
44
|
+
if (!parsed.success) {
|
|
45
|
+
await incoming.respond({
|
|
46
|
+
result: "error",
|
|
47
|
+
code: "malformed"
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (incoming.scope.kind !== "room" || incoming.scope.path === void 0) {
|
|
52
|
+
await incoming.respond({
|
|
53
|
+
result: "error",
|
|
54
|
+
code: "scope_mismatch"
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const roomPath = incoming.scope.path;
|
|
59
|
+
const verdict = await require_domain_room_token_verification.verifyRoomToken(options.ownRoomMemberToken, {
|
|
60
|
+
identity: options.identity,
|
|
61
|
+
clock: options.clock,
|
|
62
|
+
revocation: options.revocation,
|
|
63
|
+
expectedBearer: options.identity.deviceId,
|
|
64
|
+
roomPath
|
|
65
|
+
});
|
|
66
|
+
if (!verdict.ok) {
|
|
67
|
+
await incoming.respond({
|
|
68
|
+
result: "error",
|
|
69
|
+
code: verdict.reason
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const deriveSharedSecret = options.identity.deriveSharedSecret;
|
|
74
|
+
if (deriveSharedSecret === void 0) {
|
|
75
|
+
await incoming.respond({
|
|
76
|
+
result: "error",
|
|
77
|
+
code: "ecdh_unsupported"
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const keyEpoch = parsed.data["key-epoch"];
|
|
82
|
+
const wrappedKeys = Array.isArray(parsed.data["wrapped-key"]) ? parsed.data["wrapped-key"] : [parsed.data["wrapped-key"]];
|
|
83
|
+
const firstEpoch = keyEpoch - wrappedKeys.length + 1;
|
|
84
|
+
let contentKeys;
|
|
85
|
+
try {
|
|
86
|
+
const sharedSecret = await deriveSharedSecret(verdict.rootIssuerKey);
|
|
87
|
+
contentKeys = await Promise.all(wrappedKeys.map(async (wrapped, i) => {
|
|
88
|
+
const wrappingKey = await require_domain_group_key.deriveWrappingKey(sharedSecret, {
|
|
89
|
+
room: roomPath,
|
|
90
|
+
keyEpoch: firstEpoch + i
|
|
91
|
+
});
|
|
92
|
+
return require_domain_group_key.unwrapContentKey(wrappingKey, wrapped);
|
|
93
|
+
}));
|
|
94
|
+
} catch {
|
|
95
|
+
await incoming.respond({
|
|
96
|
+
result: "error",
|
|
97
|
+
code: "unwrap_failed"
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await incoming.respond({ result: "ok" });
|
|
102
|
+
options.onRekey({
|
|
103
|
+
room: roomPath,
|
|
104
|
+
keyEpoch,
|
|
105
|
+
contentKeys
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
exports.buildRoomRekeyCommand = buildRoomRekeyCommand;
|
|
111
|
+
exports.createRoomRekeyHandler = createRoomRekeyHandler;
|
|
112
|
+
exports.sendRoomRekey = sendRoomRekey;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { D as DeviceId, V as ManageCommand, p as CapabilityToken } from "../protocol-gbXtNeOc.cjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-B4XBWkqg.cjs";
|
|
3
|
+
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
|
+
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.cjs";
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-C9Kwy0Gk.cjs";
|
|
6
|
+
//#region src/domain/room-rekey.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* 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
|
+
*/
|
|
10
|
+
export declare function buildRoomRekeyCommand(keyEpoch: number, wrappedKey: Uint8Array | readonly Uint8Array[]): ManageCommand;
|
|
11
|
+
/** Sends a room.rekey for roomPath. targetDevice/token forward directly to MeshSession.sendManageRequest's own identically-named parameters, exactly as sendRoomMessage's own doc comment already describes. */
|
|
12
|
+
export declare function sendRoomRekey(session: Readonly<MeshSession>, roomPath: string, keyEpoch: number, wrappedKey: Uint8Array | readonly Uint8Array[], targetDevice?: DeviceId, token?: CapabilityToken): Promise<ManageOutcome>;
|
|
13
|
+
/** One incoming, fully-verified and unwrapped room.rekey, surfaced for the domain to store (index content keys by epoch for later room-notice decryption). */
|
|
14
|
+
export interface RoomRekeyEvent {
|
|
15
|
+
room: string;
|
|
16
|
+
/** The highest epoch this room.rekey granted -- the same value the wire message itself carried. */
|
|
17
|
+
keyEpoch: number;
|
|
18
|
+
/** contentKeys[i] is the content-encryption key for epoch i+1, matching wrapped-key's own array-ordering convention (spec/room.cddl) -- always at least one entry (the current epoch), even under the default single-bstr, members-at-the-time policy. */
|
|
19
|
+
contentKeys: readonly Uint8Array[];
|
|
20
|
+
}
|
|
21
|
+
export interface CreateRoomRekeyHandlerOptions {
|
|
22
|
+
/** This recipient's own identity -- both the verification primitives ownRoomMemberToken is checked with, and (via deriveSharedSecret) the ECDH half of unwrapping. */
|
|
23
|
+
identity: IdentityPort;
|
|
24
|
+
clock: Clock;
|
|
25
|
+
revocation: RevocationCheck;
|
|
26
|
+
/** 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
|
+
ownRoomMemberToken: CapabilityToken;
|
|
28
|
+
/** Called once per successfully unwrapped room.rekey. */
|
|
29
|
+
onRekey: (event: Readonly<RoomRekeyEvent>) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createRoomRekeyHandler(options: Readonly<CreateRoomRekeyHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
|
|
35
|
+
//#endregion
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { D as DeviceId, V as ManageCommand, p as CapabilityToken } from "../protocol-gbXtNeOc.mjs";
|
|
2
|
+
import { t as IdentityPort } from "../identity-CIb9dGkJ.mjs";
|
|
3
|
+
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
|
+
import { IncomingManageRequest, ManageOutcome, MeshSession } from "./mesh-session.mjs";
|
|
5
|
+
import { a as RevocationCheck } from "../tokens-k172l0Iy.mjs";
|
|
6
|
+
//#region src/domain/room-rekey.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* 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
|
+
*/
|
|
10
|
+
export declare function buildRoomRekeyCommand(keyEpoch: number, wrappedKey: Uint8Array | readonly Uint8Array[]): ManageCommand;
|
|
11
|
+
/** Sends a room.rekey for roomPath. targetDevice/token forward directly to MeshSession.sendManageRequest's own identically-named parameters, exactly as sendRoomMessage's own doc comment already describes. */
|
|
12
|
+
export declare function sendRoomRekey(session: Readonly<MeshSession>, roomPath: string, keyEpoch: number, wrappedKey: Uint8Array | readonly Uint8Array[], targetDevice?: DeviceId, token?: CapabilityToken): Promise<ManageOutcome>;
|
|
13
|
+
/** One incoming, fully-verified and unwrapped room.rekey, surfaced for the domain to store (index content keys by epoch for later room-notice decryption). */
|
|
14
|
+
export interface RoomRekeyEvent {
|
|
15
|
+
room: string;
|
|
16
|
+
/** The highest epoch this room.rekey granted -- the same value the wire message itself carried. */
|
|
17
|
+
keyEpoch: number;
|
|
18
|
+
/** contentKeys[i] is the content-encryption key for epoch i+1, matching wrapped-key's own array-ordering convention (spec/room.cddl) -- always at least one entry (the current epoch), even under the default single-bstr, members-at-the-time policy. */
|
|
19
|
+
contentKeys: readonly Uint8Array[];
|
|
20
|
+
}
|
|
21
|
+
export interface CreateRoomRekeyHandlerOptions {
|
|
22
|
+
/** This recipient's own identity -- both the verification primitives ownRoomMemberToken is checked with, and (via deriveSharedSecret) the ECDH half of unwrapping. */
|
|
23
|
+
identity: IdentityPort;
|
|
24
|
+
clock: Clock;
|
|
25
|
+
revocation: RevocationCheck;
|
|
26
|
+
/** 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
|
+
ownRoomMemberToken: CapabilityToken;
|
|
28
|
+
/** Called once per successfully unwrapped room.rekey. */
|
|
29
|
+
onRekey: (event: Readonly<RoomRekeyEvent>) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createRoomRekeyHandler(options: Readonly<CreateRoomRekeyHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
|
|
35
|
+
//#endregion
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { roomRekeySchema } from "../generated/protocol.mjs";
|
|
2
|
+
import { deriveWrappingKey, unwrapContentKey } from "./group-key.mjs";
|
|
3
|
+
import { ROOM_MEMBER_CAPABILITY, verifyRoomToken } from "./room-token-verification.mjs";
|
|
4
|
+
//#region src/domain/room-rekey.ts
|
|
5
|
+
/**
|
|
6
|
+
* room.rekey (wire-mesh#141, spec/room.cddl): distributes and rotates a room's symmetric content-encryption key. Two responsibilities, split the same way capability-grant.ts and capability-request.ts already split theirs: sendRoomRekey is the pushing side (a thin wrapper over MeshSession.sendManageRequest), and createRoomRekeyHandler is the receiving side.
|
|
7
|
+
*
|
|
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
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 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
|
+
*/
|
|
13
|
+
function buildRoomRekeyCommand(keyEpoch, wrappedKey) {
|
|
14
|
+
return {
|
|
15
|
+
verb: ROOM_MEMBER_CAPABILITY,
|
|
16
|
+
params: {
|
|
17
|
+
verb: "room.rekey",
|
|
18
|
+
"key-epoch": keyEpoch,
|
|
19
|
+
"wrapped-key": wrappedKey
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Sends a room.rekey for roomPath. targetDevice/token forward directly to MeshSession.sendManageRequest's own identically-named parameters, exactly as sendRoomMessage's own doc comment already describes. */
|
|
24
|
+
async function sendRoomRekey(session, roomPath, keyEpoch, wrappedKey, targetDevice, token) {
|
|
25
|
+
return session.sendManageRequest(buildRoomRekeyCommand(keyEpoch, wrappedKey), {
|
|
26
|
+
kind: "room",
|
|
27
|
+
path: roomPath
|
|
28
|
+
}, targetDevice, token);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
33
|
+
function createRoomRekeyHandler(options) {
|
|
34
|
+
return async function handleRoomRekey(incoming) {
|
|
35
|
+
if (incoming.command.verb !== "room:member") {
|
|
36
|
+
await incoming.respond({
|
|
37
|
+
result: "error",
|
|
38
|
+
code: "malformed"
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const parsed = roomRekeySchema.safeParse(incoming.command.params);
|
|
43
|
+
if (!parsed.success) {
|
|
44
|
+
await incoming.respond({
|
|
45
|
+
result: "error",
|
|
46
|
+
code: "malformed"
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (incoming.scope.kind !== "room" || incoming.scope.path === void 0) {
|
|
51
|
+
await incoming.respond({
|
|
52
|
+
result: "error",
|
|
53
|
+
code: "scope_mismatch"
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const roomPath = incoming.scope.path;
|
|
58
|
+
const verdict = await verifyRoomToken(options.ownRoomMemberToken, {
|
|
59
|
+
identity: options.identity,
|
|
60
|
+
clock: options.clock,
|
|
61
|
+
revocation: options.revocation,
|
|
62
|
+
expectedBearer: options.identity.deviceId,
|
|
63
|
+
roomPath
|
|
64
|
+
});
|
|
65
|
+
if (!verdict.ok) {
|
|
66
|
+
await incoming.respond({
|
|
67
|
+
result: "error",
|
|
68
|
+
code: verdict.reason
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const deriveSharedSecret = options.identity.deriveSharedSecret;
|
|
73
|
+
if (deriveSharedSecret === void 0) {
|
|
74
|
+
await incoming.respond({
|
|
75
|
+
result: "error",
|
|
76
|
+
code: "ecdh_unsupported"
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const keyEpoch = parsed.data["key-epoch"];
|
|
81
|
+
const wrappedKeys = Array.isArray(parsed.data["wrapped-key"]) ? parsed.data["wrapped-key"] : [parsed.data["wrapped-key"]];
|
|
82
|
+
const firstEpoch = keyEpoch - wrappedKeys.length + 1;
|
|
83
|
+
let contentKeys;
|
|
84
|
+
try {
|
|
85
|
+
const sharedSecret = await deriveSharedSecret(verdict.rootIssuerKey);
|
|
86
|
+
contentKeys = await Promise.all(wrappedKeys.map(async (wrapped, i) => {
|
|
87
|
+
const wrappingKey = await deriveWrappingKey(sharedSecret, {
|
|
88
|
+
room: roomPath,
|
|
89
|
+
keyEpoch: firstEpoch + i
|
|
90
|
+
});
|
|
91
|
+
return unwrapContentKey(wrappingKey, wrapped);
|
|
92
|
+
}));
|
|
93
|
+
} catch {
|
|
94
|
+
await incoming.respond({
|
|
95
|
+
result: "error",
|
|
96
|
+
code: "unwrap_failed"
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await incoming.respond({ result: "ok" });
|
|
101
|
+
options.onRekey({
|
|
102
|
+
room: roomPath,
|
|
103
|
+
keyEpoch,
|
|
104
|
+
contentKeys
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { buildRoomRekeyCommand, createRoomRekeyHandler, sendRoomRekey };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_domain_device_id = require("./device-id.cjs");
|
|
3
|
-
const require_tokens = require("../tokens-
|
|
3
|
+
const require_tokens = require("../tokens-DxGVURpB.cjs");
|
|
4
4
|
const require_domain_room_path = require("./room-path.cjs");
|
|
5
5
|
//#region src/domain/room-token-verification.ts
|
|
6
6
|
/**
|
|
@@ -35,7 +35,8 @@ async function verifyRoomToken(token, options) {
|
|
|
35
35
|
};
|
|
36
36
|
return {
|
|
37
37
|
ok: true,
|
|
38
|
-
claims: verdict.claims
|
|
38
|
+
claims: verdict.claims,
|
|
39
|
+
rootIssuerKey: verdict.rootIssuerKey
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DeviceId, It as TokenClaims, p as CapabilityToken } from "../protocol-gbXtNeOc.cjs";
|
|
2
|
-
import { l as TokenVerdictReason, u as VerifyCapabilityTokenOptions } from "../tokens-
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, It as TokenClaims, p as CapabilityToken } from "../protocol-gbXtNeOc.cjs";
|
|
2
|
+
import { l as TokenVerdictReason, u as VerifyCapabilityTokenOptions } from "../tokens-C9Kwy0Gk.cjs";
|
|
3
3
|
//#region src/domain/room-token-verification.d.ts
|
|
4
4
|
/** The one capability every core/room verb (room.send/read/leave/members) is gated by, per spec/room.cddl -- room.join/room.invite are deliberately ungated instead and need no token check at all. */
|
|
5
5
|
export declare const ROOM_MEMBER_CAPABILITY = "room:member";
|
|
@@ -7,6 +7,8 @@ export type RoomTokenVerdictReason = TokenVerdictReason | "wrong_scope_kind" | "
|
|
|
7
7
|
export type RoomTokenVerdict = {
|
|
8
8
|
ok: true;
|
|
9
9
|
claims: TokenClaims;
|
|
10
|
+
/** The chain's own certified root issuer-key -- for a named room, this is exactly the room's rightful owner (obligation 1 just confirmed the chain terminates there), and for a DM it's the verifying identity's own key. Lets a caller (e.g. room.rekey's own handler) derive an ECDH shared secret against the room's real owner with no separate live-sender identity check needed. */
|
|
11
|
+
rootIssuerKey: IdentityKey;
|
|
10
12
|
} | {
|
|
11
13
|
ok: false;
|
|
12
14
|
reason: RoomTokenVerdictReason;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DeviceId, It as TokenClaims, p as CapabilityToken } from "../protocol-gbXtNeOc.mjs";
|
|
2
|
-
import { l as TokenVerdictReason, u as VerifyCapabilityTokenOptions } from "../tokens-
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, It as TokenClaims, p as CapabilityToken } from "../protocol-gbXtNeOc.mjs";
|
|
2
|
+
import { l as TokenVerdictReason, u as VerifyCapabilityTokenOptions } from "../tokens-k172l0Iy.mjs";
|
|
3
3
|
//#region src/domain/room-token-verification.d.ts
|
|
4
4
|
/** The one capability every core/room verb (room.send/read/leave/members) is gated by, per spec/room.cddl -- room.join/room.invite are deliberately ungated instead and need no token check at all. */
|
|
5
5
|
export declare const ROOM_MEMBER_CAPABILITY = "room:member";
|
|
@@ -7,6 +7,8 @@ export type RoomTokenVerdictReason = TokenVerdictReason | "wrong_scope_kind" | "
|
|
|
7
7
|
export type RoomTokenVerdict = {
|
|
8
8
|
ok: true;
|
|
9
9
|
claims: TokenClaims;
|
|
10
|
+
/** The chain's own certified root issuer-key -- for a named room, this is exactly the room's rightful owner (obligation 1 just confirmed the chain terminates there), and for a DM it's the verifying identity's own key. Lets a caller (e.g. room.rekey's own handler) derive an ECDH shared secret against the room's real owner with no separate live-sender identity check needed. */
|
|
11
|
+
rootIssuerKey: IdentityKey;
|
|
10
12
|
} | {
|
|
11
13
|
ok: false;
|
|
12
14
|
reason: RoomTokenVerdictReason;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
|
-
import { i as verifyCapabilityToken } from "../tokens-
|
|
2
|
+
import { i as verifyCapabilityToken } from "../tokens-CdQwzCyJ.mjs";
|
|
3
3
|
import { parseRoomPath } from "./room-path.mjs";
|
|
4
4
|
//#region src/domain/room-token-verification.ts
|
|
5
5
|
/**
|
|
@@ -34,7 +34,8 @@ async function verifyRoomToken(token, options) {
|
|
|
34
34
|
};
|
|
35
35
|
return {
|
|
36
36
|
ok: true,
|
|
37
|
-
claims: verdict.claims
|
|
37
|
+
claims: verdict.claims,
|
|
38
|
+
rootIssuerKey: verdict.rootIssuerKey
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
//#endregion
|
package/dist/domain/room.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_generated_protocol = require("../generated/protocol.cjs");
|
|
3
|
+
const require_domain_group_key = require("./group-key.cjs");
|
|
3
4
|
const require_domain_room_token_verification = require("./room-token-verification.cjs");
|
|
4
5
|
let cbor2 = require("cbor2");
|
|
5
6
|
//#region src/domain/room.ts
|
|
@@ -91,6 +92,11 @@ async function verifyRoomNotice(notice, options) {
|
|
|
91
92
|
ok: false,
|
|
92
93
|
reason: "content_expired"
|
|
93
94
|
};
|
|
95
|
+
const keyEpoch = claims["key-epoch"];
|
|
96
|
+
if (require_domain_group_key.isEncryptedContentType(claims["content-type"]) !== (keyEpoch !== void 0)) return {
|
|
97
|
+
ok: false,
|
|
98
|
+
reason: "key_epoch_mismatch"
|
|
99
|
+
};
|
|
94
100
|
const tokenVerdict = await require_domain_room_token_verification.verifyRoomToken(claims.token, {
|
|
95
101
|
identity: options.identity,
|
|
96
102
|
clock: options.clock,
|
|
@@ -130,7 +136,8 @@ async function createRoomNotice(options) {
|
|
|
130
136
|
"content-type": options.contentType,
|
|
131
137
|
content: options.content,
|
|
132
138
|
...options.refs !== void 0 ? { refs: [...options.refs] } : {},
|
|
133
|
-
...options.validUntil !== void 0 ? { "valid-until": options.validUntil } : {}
|
|
139
|
+
...options.validUntil !== void 0 ? { "valid-until": options.validUntil } : {},
|
|
140
|
+
...options.keyEpoch !== void 0 ? { "key-epoch": options.keyEpoch } : {}
|
|
134
141
|
});
|
|
135
142
|
const protectedHeader = protectedHeaderFor(options.identity);
|
|
136
143
|
return [
|
package/dist/domain/room.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Dt as RoomPath, Et as RoomNoticeClaims, Tt as RoomNotice, p as CapabilityToken, q as MessageRef } from "../protocol-gbXtNeOc.cjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-B4XBWkqg.cjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
4
|
-
import { a as RevocationCheck } from "../tokens-
|
|
4
|
+
import { a as RevocationCheck } from "../tokens-C9Kwy0Gk.cjs";
|
|
5
5
|
import { RoomTokenVerdictReason } from "./room-token-verification.cjs";
|
|
6
6
|
//#region src/domain/room.d.ts
|
|
7
|
-
export type RoomNoticeVerdictReason = "malformed" | "wrong_room" | "bad_signature" | "wrong_poster" | "content_expired" | RoomTokenVerdictReason;
|
|
7
|
+
export type RoomNoticeVerdictReason = "malformed" | "wrong_room" | "bad_signature" | "wrong_poster" | "content_expired" | "key_epoch_mismatch" | RoomTokenVerdictReason;
|
|
8
8
|
export type RoomNoticeVerdict = {
|
|
9
9
|
ok: true;
|
|
10
10
|
claims: RoomNoticeClaims;
|
|
@@ -45,6 +45,8 @@ export interface CreateRoomNoticeOptions {
|
|
|
45
45
|
content: Uint8Array<ArrayBuffer>;
|
|
46
46
|
refs?: readonly MessageRef[];
|
|
47
47
|
validUntil?: number;
|
|
48
|
+
/** Which room.rekey epoch content is encrypted under (wire-mesh#141), present iff content-type names an encrypted content-type -- see room-notice-claims' own obligation 7 comment in room.cddl. This function does not itself encrypt content: the caller encrypts via group-key.ts's encryptNoticeContent beforehand and passes the resulting ciphertext as content, exactly as it would pass any other opaque bytes. */
|
|
49
|
+
keyEpoch?: number;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Mints one self-certifying room-notice as `identity` -- the counterpart to verifyRoomNotice above, producing exactly what it accepts. `posted-at` is always stamped from the injected clock, never caller-supplied, matching obligation 4's own requirement that a poster's own log stay non-decreasing: a caller backdating its own posted-at would be indistinguishable from a clock bug at mint time, so this function simply doesn't expose the field to override.
|
package/dist/domain/room.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Dt as RoomPath, Et as RoomNoticeClaims, Tt as RoomNotice, p as CapabilityToken, q as MessageRef } from "../protocol-gbXtNeOc.mjs";
|
|
2
2
|
import { t as IdentityPort } from "../identity-CIb9dGkJ.mjs";
|
|
3
3
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
4
|
-
import { a as RevocationCheck } from "../tokens-
|
|
4
|
+
import { a as RevocationCheck } from "../tokens-k172l0Iy.mjs";
|
|
5
5
|
import { RoomTokenVerdictReason } from "./room-token-verification.mjs";
|
|
6
6
|
//#region src/domain/room.d.ts
|
|
7
|
-
export type RoomNoticeVerdictReason = "malformed" | "wrong_room" | "bad_signature" | "wrong_poster" | "content_expired" | RoomTokenVerdictReason;
|
|
7
|
+
export type RoomNoticeVerdictReason = "malformed" | "wrong_room" | "bad_signature" | "wrong_poster" | "content_expired" | "key_epoch_mismatch" | RoomTokenVerdictReason;
|
|
8
8
|
export type RoomNoticeVerdict = {
|
|
9
9
|
ok: true;
|
|
10
10
|
claims: RoomNoticeClaims;
|
|
@@ -45,6 +45,8 @@ export interface CreateRoomNoticeOptions {
|
|
|
45
45
|
content: Uint8Array<ArrayBuffer>;
|
|
46
46
|
refs?: readonly MessageRef[];
|
|
47
47
|
validUntil?: number;
|
|
48
|
+
/** Which room.rekey epoch content is encrypted under (wire-mesh#141), present iff content-type names an encrypted content-type -- see room-notice-claims' own obligation 7 comment in room.cddl. This function does not itself encrypt content: the caller encrypts via group-key.ts's encryptNoticeContent beforehand and passes the resulting ciphertext as content, exactly as it would pass any other opaque bytes. */
|
|
49
|
+
keyEpoch?: number;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Mints one self-certifying room-notice as `identity` -- the counterpart to verifyRoomNotice above, producing exactly what it accepts. `posted-at` is always stamped from the injected clock, never caller-supplied, matching obligation 4's own requirement that a poster's own log stay non-decreasing: a caller backdating its own posted-at would be indistinguishable from a clock bug at mint time, so this function simply doesn't expose the field to override.
|
package/dist/domain/room.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { roomNoticeClaimsSchema } from "../generated/protocol.mjs";
|
|
2
|
+
import { isEncryptedContentType } from "./group-key.mjs";
|
|
2
3
|
import { verifyRoomToken } from "./room-token-verification.mjs";
|
|
3
4
|
import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
|
|
4
5
|
//#region src/domain/room.ts
|
|
@@ -90,6 +91,11 @@ async function verifyRoomNotice(notice, options) {
|
|
|
90
91
|
ok: false,
|
|
91
92
|
reason: "content_expired"
|
|
92
93
|
};
|
|
94
|
+
const keyEpoch = claims["key-epoch"];
|
|
95
|
+
if (isEncryptedContentType(claims["content-type"]) !== (keyEpoch !== void 0)) return {
|
|
96
|
+
ok: false,
|
|
97
|
+
reason: "key_epoch_mismatch"
|
|
98
|
+
};
|
|
93
99
|
const tokenVerdict = await verifyRoomToken(claims.token, {
|
|
94
100
|
identity: options.identity,
|
|
95
101
|
clock: options.clock,
|
|
@@ -129,7 +135,8 @@ async function createRoomNotice(options) {
|
|
|
129
135
|
"content-type": options.contentType,
|
|
130
136
|
content: options.content,
|
|
131
137
|
...options.refs !== void 0 ? { refs: [...options.refs] } : {},
|
|
132
|
-
...options.validUntil !== void 0 ? { "valid-until": options.validUntil } : {}
|
|
138
|
+
...options.validUntil !== void 0 ? { "valid-until": options.validUntil } : {},
|
|
139
|
+
...options.keyEpoch !== void 0 ? { "key-epoch": options.keyEpoch } : {}
|
|
133
140
|
});
|
|
134
141
|
const protectedHeader = protectedHeaderFor(options.identity);
|
|
135
142
|
return [
|
package/dist/domain/tokens.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_tokens = require("../tokens-
|
|
2
|
+
const require_tokens = require("../tokens-DxGVURpB.cjs");
|
|
3
3
|
exports.canGrant = require_tokens.canGrant;
|
|
4
4
|
exports.mintCapabilityToken = require_tokens.mintCapabilityToken;
|
|
5
5
|
exports.mintRevocationEntry = require_tokens.mintRevocationEntry;
|
package/dist/domain/tokens.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RevocationCheck, c as TokenVerdict, d as VerifyRevocationEntryOptions, f as canGrant, g as verifyRevocationEntry, h as verifyCapabilityToken, i as MintVerdict, l as TokenVerdictReason, m as mintRevocationEntry, n as MintRefusalReason, o as RevocationEntryVerdict, p as mintCapabilityToken, r as MintRevocationEntryOptions, s as RevocationEntryVerdictReason, t as MintCapabilityTokenOptions, u as VerifyCapabilityTokenOptions } from "../tokens-
|
|
1
|
+
import { a as RevocationCheck, c as TokenVerdict, d as VerifyRevocationEntryOptions, f as canGrant, g as verifyRevocationEntry, h as verifyCapabilityToken, i as MintVerdict, l as TokenVerdictReason, m as mintRevocationEntry, n as MintRefusalReason, o as RevocationEntryVerdict, p as mintCapabilityToken, r as MintRevocationEntryOptions, s as RevocationEntryVerdictReason, t as MintCapabilityTokenOptions, u as VerifyCapabilityTokenOptions } from "../tokens-C9Kwy0Gk.cjs";
|
|
2
2
|
export { MintCapabilityTokenOptions, MintRefusalReason, MintRevocationEntryOptions, MintVerdict, RevocationCheck, RevocationEntryVerdict, RevocationEntryVerdictReason, TokenVerdict, TokenVerdictReason, VerifyCapabilityTokenOptions, VerifyRevocationEntryOptions, canGrant, mintCapabilityToken, mintRevocationEntry, verifyCapabilityToken, verifyRevocationEntry };
|
package/dist/domain/tokens.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as RevocationCheck, c as TokenVerdict, d as VerifyRevocationEntryOptions, f as canGrant, g as verifyRevocationEntry, h as verifyCapabilityToken, i as MintVerdict, l as TokenVerdictReason, m as mintRevocationEntry, n as MintRefusalReason, o as RevocationEntryVerdict, p as mintCapabilityToken, r as MintRevocationEntryOptions, s as RevocationEntryVerdictReason, t as MintCapabilityTokenOptions, u as VerifyCapabilityTokenOptions } from "../tokens-
|
|
1
|
+
import { a as RevocationCheck, c as TokenVerdict, d as VerifyRevocationEntryOptions, f as canGrant, g as verifyRevocationEntry, h as verifyCapabilityToken, i as MintVerdict, l as TokenVerdictReason, m as mintRevocationEntry, n as MintRefusalReason, o as RevocationEntryVerdict, p as mintCapabilityToken, r as MintRevocationEntryOptions, s as RevocationEntryVerdictReason, t as MintCapabilityTokenOptions, u as VerifyCapabilityTokenOptions } from "../tokens-k172l0Iy.mjs";
|
|
2
2
|
export { MintCapabilityTokenOptions, MintRefusalReason, MintRevocationEntryOptions, MintVerdict, RevocationCheck, RevocationEntryVerdict, RevocationEntryVerdictReason, TokenVerdict, TokenVerdictReason, VerifyCapabilityTokenOptions, VerifyRevocationEntryOptions, canGrant, mintCapabilityToken, mintRevocationEntry, verifyCapabilityToken, verifyRevocationEntry };
|
package/dist/domain/tokens.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as verifyRevocationEntry, i as verifyCapabilityToken, n as mintCapabilityToken, r as mintRevocationEntry, t as canGrant } from "../tokens-
|
|
1
|
+
import { a as verifyRevocationEntry, i as verifyCapabilityToken, n as mintCapabilityToken, r as mintRevocationEntry, t as canGrant } from "../tokens-CdQwzCyJ.mjs";
|
|
2
2
|
export { canGrant, mintCapabilityToken, mintRevocationEntry, verifyCapabilityToken, verifyRevocationEntry };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, It as TokenClaims, _t as RevocationEntry, gt as RevocationClaims, p as CapabilityToken } from "./protocol-gbXtNeOc.cjs";
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, It as TokenClaims, _t as RevocationEntry, gt as RevocationClaims, p as CapabilityToken } from "./protocol-gbXtNeOc.cjs";
|
|
2
2
|
import { t as IdentityPort } from "./identity-B4XBWkqg.cjs";
|
|
3
3
|
import { t as Clock } from "./clock-DiSx-WKM.cjs";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -36,6 +36,8 @@ type TokenVerdict = {
|
|
|
36
36
|
claims: TokenClaims;
|
|
37
37
|
/** The device-id at the root of this token's delegation chain: its own issuer when it carries no parent, otherwise the root of its parent's chain. Lets a caller (e.g. core/room's obligation that a chain must terminate at the path's own owner, or the verifier itself for a DM) check the chain's root with one equality comparison instead of re-walking the parent chain a second time. */
|
|
38
38
|
rootIssuer: DeviceId;
|
|
39
|
+
/** The root ancestor's own self-certifying issuer-key -- already decoded during the walk (every TokenClaims carries its own issuer-key), just threaded up rather than re-derived. Lets a caller compute an ECDH shared secret against the chain's root (e.g. room.rekey's own sender, for a named room whose token chain terminates at the owner) without a separate, out-of-band way to learn that issuer's public key. */
|
|
40
|
+
rootIssuerKey: IdentityKey;
|
|
39
41
|
/** How many delegation hops this token is from its own root -- 0 for a root grant. Costs nothing extra once rootIssuer is being tracked, and makes the delegation bound observable for diagnostics. */
|
|
40
42
|
depth: number;
|
|
41
43
|
} | {
|
|
@@ -310,6 +310,7 @@ async function verifyTokenChain(token, options) {
|
|
|
310
310
|
ok: true,
|
|
311
311
|
claims,
|
|
312
312
|
rootIssuer: parentVerdict.rootIssuer,
|
|
313
|
+
rootIssuerKey: parentVerdict.rootIssuerKey,
|
|
313
314
|
depth: parentVerdict.depth + 1
|
|
314
315
|
};
|
|
315
316
|
}
|
|
@@ -317,6 +318,7 @@ async function verifyTokenChain(token, options) {
|
|
|
317
318
|
ok: true,
|
|
318
319
|
claims,
|
|
319
320
|
rootIssuer: claims.issuer,
|
|
321
|
+
rootIssuerKey: claims["issuer-key"],
|
|
320
322
|
depth: 0
|
|
321
323
|
};
|
|
322
324
|
}
|
|
@@ -310,6 +310,7 @@ async function verifyTokenChain(token, options) {
|
|
|
310
310
|
ok: true,
|
|
311
311
|
claims,
|
|
312
312
|
rootIssuer: parentVerdict.rootIssuer,
|
|
313
|
+
rootIssuerKey: parentVerdict.rootIssuerKey,
|
|
313
314
|
depth: parentVerdict.depth + 1
|
|
314
315
|
};
|
|
315
316
|
}
|
|
@@ -317,6 +318,7 @@ async function verifyTokenChain(token, options) {
|
|
|
317
318
|
ok: true,
|
|
318
319
|
claims,
|
|
319
320
|
rootIssuer: claims.issuer,
|
|
321
|
+
rootIssuerKey: claims["issuer-key"],
|
|
320
322
|
depth: 0
|
|
321
323
|
};
|
|
322
324
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, It as TokenClaims, _t as RevocationEntry, gt as RevocationClaims, p as CapabilityToken } from "./protocol-gbXtNeOc.mjs";
|
|
1
|
+
import { B as IdentityKey, D as DeviceId, It as TokenClaims, _t as RevocationEntry, gt as RevocationClaims, p as CapabilityToken } from "./protocol-gbXtNeOc.mjs";
|
|
2
2
|
import { t as IdentityPort } from "./identity-CIb9dGkJ.mjs";
|
|
3
3
|
import { t as Clock } from "./clock-DiSx-WKM.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -36,6 +36,8 @@ type TokenVerdict = {
|
|
|
36
36
|
claims: TokenClaims;
|
|
37
37
|
/** The device-id at the root of this token's delegation chain: its own issuer when it carries no parent, otherwise the root of its parent's chain. Lets a caller (e.g. core/room's obligation that a chain must terminate at the path's own owner, or the verifier itself for a DM) check the chain's root with one equality comparison instead of re-walking the parent chain a second time. */
|
|
38
38
|
rootIssuer: DeviceId;
|
|
39
|
+
/** The root ancestor's own self-certifying issuer-key -- already decoded during the walk (every TokenClaims carries its own issuer-key), just threaded up rather than re-derived. Lets a caller compute an ECDH shared secret against the chain's root (e.g. room.rekey's own sender, for a named room whose token chain terminates at the owner) without a separate, out-of-band way to learn that issuer's public key. */
|
|
40
|
+
rootIssuerKey: IdentityKey;
|
|
39
41
|
/** How many delegation hops this token is from its own root -- 0 for a root grant. Costs nothing extra once rootIssuer is being tracked, and makes the delegation bound observable for diagnostics. */
|
|
40
42
|
depth: number;
|
|
41
43
|
} | {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wire-mesh-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
|
|
6
6
|
"repository": {
|
|
@@ -146,6 +146,10 @@
|
|
|
146
146
|
"import": "./dist/domain/room-path.mjs",
|
|
147
147
|
"require": "./dist/domain/room-path.cjs"
|
|
148
148
|
},
|
|
149
|
+
"./domain/room-rekey": {
|
|
150
|
+
"import": "./dist/domain/room-rekey.mjs",
|
|
151
|
+
"require": "./dist/domain/room-rekey.cjs"
|
|
152
|
+
},
|
|
149
153
|
"./domain/room-token-verification": {
|
|
150
154
|
"import": "./dist/domain/room-token-verification.mjs",
|
|
151
155
|
"require": "./dist/domain/room-token-verification.cjs"
|