wire-mesh-core 3.7.2 → 3.8.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/adapters/byte-stream-connection.d.cts +2 -2
- package/dist/adapters/byte-stream-connection.d.mts +2 -2
- package/dist/adapters/frame-codec.d.cts +1 -1
- package/dist/adapters/frame-codec.d.mts +1 -1
- package/dist/adapters/node-identity.d.cts +2 -2
- package/dist/adapters/node-identity.d.mts +2 -2
- package/dist/adapters/tcp-transport.d.cts +1 -1
- package/dist/adapters/tcp-transport.d.mts +1 -1
- package/dist/adapters/threshold-identity.d.cts +2 -2
- package/dist/adapters/threshold-identity.d.mts +2 -2
- package/dist/adapters/threshold-wasm.d.cts +1 -1
- package/dist/adapters/threshold-wasm.d.mts +1 -1
- package/dist/adapters/tls-transport.d.cts +1 -1
- package/dist/adapters/tls-transport.d.mts +1 -1
- package/dist/domain/advert-extension-policy.d.cts +1 -1
- package/dist/domain/advert-extension-policy.d.mts +1 -1
- package/dist/domain/bulk.d.cts +2 -2
- package/dist/domain/bulk.d.mts +2 -2
- package/dist/domain/capability-grant.cjs +1 -1
- package/dist/domain/capability-grant.d.cts +3 -3
- package/dist/domain/capability-grant.d.mts +3 -3
- package/dist/domain/capability-grant.mjs +1 -1
- package/dist/domain/capability-request.cjs +52 -4
- package/dist/domain/capability-request.d.cts +14 -3
- package/dist/domain/capability-request.d.mts +14 -3
- package/dist/domain/capability-request.mjs +52 -5
- package/dist/domain/coordinator-election.d.cts +1 -1
- package/dist/domain/coordinator-election.d.mts +1 -1
- package/dist/domain/data-sync.d.cts +2 -2
- package/dist/domain/data-sync.d.mts +2 -2
- package/dist/domain/device-id.d.cts +1 -1
- package/dist/domain/device-id.d.mts +1 -1
- package/dist/domain/direct-manage-request.d.cts +2 -2
- package/dist/domain/direct-manage-request.d.mts +2 -2
- package/dist/domain/gossip-expansion.d.cts +1 -1
- package/dist/domain/gossip-expansion.d.mts +1 -1
- package/dist/domain/grant-candidates.d.cts +1 -1
- package/dist/domain/grant-candidates.d.mts +1 -1
- package/dist/domain/handshake.d.cts +1 -1
- package/dist/domain/handshake.d.mts +1 -1
- package/dist/domain/hub-mailbox.d.cts +1 -1
- package/dist/domain/hub-mailbox.d.mts +1 -1
- package/dist/domain/mesh-session.cjs +1 -1
- package/dist/domain/mesh-session.d.cts +3 -3
- package/dist/domain/mesh-session.d.mts +3 -3
- package/dist/domain/mesh-session.mjs +1 -1
- package/dist/domain/notice-board.d.cts +3 -3
- package/dist/domain/notice-board.d.mts +3 -3
- package/dist/domain/path-trace.d.cts +1 -1
- package/dist/domain/path-trace.d.mts +1 -1
- package/dist/domain/peer-advert.d.cts +2 -2
- package/dist/domain/peer-advert.d.mts +2 -2
- package/dist/domain/relay-advert.d.cts +1 -1
- package/dist/domain/relay-advert.d.mts +1 -1
- package/dist/domain/relay-hub.d.cts +2 -2
- package/dist/domain/relay-hub.d.mts +2 -2
- package/dist/domain/relay-use-gate.d.cts +2 -2
- package/dist/domain/relay-use-gate.d.mts +2 -2
- package/dist/domain/revocation-view.cjs +1 -1
- package/dist/domain/revocation-view.d.cts +2 -2
- package/dist/domain/revocation-view.d.mts +2 -2
- package/dist/domain/revocation-view.mjs +1 -1
- package/dist/domain/room-rekey.d.cts +3 -3
- package/dist/domain/room-rekey.d.mts +3 -3
- package/dist/domain/room-token-verification.cjs +1 -1
- package/dist/domain/room-token-verification.d.cts +1 -1
- package/dist/domain/room-token-verification.d.mts +1 -1
- package/dist/domain/room-token-verification.mjs +1 -1
- package/dist/domain/room.d.cts +4 -4
- package/dist/domain/room.d.mts +4 -4
- package/dist/domain/shard-manifest.d.cts +1 -1
- package/dist/domain/shard-manifest.d.mts +1 -1
- package/dist/domain/threshold-subject.cjs +1 -1
- package/dist/domain/threshold-subject.mjs +1 -1
- package/dist/domain/tokens.cjs +3 -1
- package/dist/domain/tokens.d.cts +2 -2
- package/dist/domain/tokens.d.mts +2 -2
- package/dist/domain/tokens.mjs +2 -2
- package/dist/domain/topology-snapshot.d.cts +1 -1
- package/dist/domain/topology-snapshot.d.mts +1 -1
- package/dist/domain/topology.d.cts +1 -1
- package/dist/domain/topology.d.mts +1 -1
- package/dist/domain/webrtc-signaling.cjs +1 -1
- package/dist/domain/webrtc-signaling.d.cts +2 -2
- package/dist/domain/webrtc-signaling.d.mts +2 -2
- package/dist/domain/webrtc-signaling.mjs +1 -1
- package/dist/generated/protocol.cjs +4 -1
- package/dist/generated/protocol.d.cts +1 -1
- package/dist/generated/protocol.d.mts +1 -1
- package/dist/generated/protocol.mjs +4 -1
- package/dist/{identity-DVCNuFLG.d.cts → identity-DJbr96J3.d.cts} +1 -1
- package/dist/{identity-DcaYgjXT.d.mts → identity-ViYdZrIU.d.mts} +1 -1
- package/dist/ports/identity.d.cts +1 -1
- package/dist/ports/identity.d.mts +1 -1
- package/dist/ports/transport.d.cts +1 -1
- package/dist/ports/transport.d.mts +1 -1
- package/dist/{protocol-DCDae3z4.d.cts → protocol-BgR1e_NI.d.cts} +3 -0
- package/dist/{protocol-DCDae3z4.d.mts → protocol-BgR1e_NI.d.mts} +3 -0
- package/dist/{room-token-verification-CkGnFuQc.d.mts → room-token-verification-7zD4ib-A.d.mts} +2 -2
- package/dist/{room-token-verification-PSv4eQ1x.d.cts → room-token-verification-hFtBDZPO.d.cts} +2 -2
- package/dist/{tokens-DqFu7iMX.d.cts → tokens-BMWCnQEF.d.cts} +37 -5
- package/dist/{tokens-CMdWiRBb.mjs → tokens-D8JlTJNA.mjs} +175 -17
- package/dist/{tokens-CBiSYuzp.d.mts → tokens-DRkm_QOR.d.mts} +37 -5
- package/dist/{tokens-DSG3SDr8.cjs → tokens-tPJQ7Vzt.cjs} +186 -16
- package/dist/{transport-B0GywEzG.d.mts → transport-DP3hdehk.d.cts} +1 -1
- package/dist/{transport-DIXjoQKn.d.cts → transport-Dv_LTCsF.d.mts} +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_generated_protocol = require("./generated/protocol.cjs");
|
|
2
|
+
const require_domain_device_id = require("./domain/device-id.cjs");
|
|
2
3
|
const require_token_scope = require("./token-scope-CxHXTT3u.cjs");
|
|
3
4
|
let cbor2 = require("cbor2");
|
|
4
5
|
let zod = require("zod");
|
|
@@ -24,6 +25,24 @@ const NARROWING_SYSTEMS = [
|
|
|
24
25
|
function isNarrowingSystem(system) {
|
|
25
26
|
return system === "bearer-is" || system === "expires-at" || system === "scope-narrows" || system === "capability-is" || system === "depth-remaining";
|
|
26
27
|
}
|
|
28
|
+
/** The two subject-mode delegate systems (wire-mesh#323), registered only when a ConditionsContext carries a subject, and indeterminate (fail-closed) whenever one does not. */
|
|
29
|
+
const GRANTEE_IS = "grantee-is";
|
|
30
|
+
const GRANTED_CAPABILITY_IS = "granted-capability-is";
|
|
31
|
+
function isSubjectSystem(system) {
|
|
32
|
+
return system === "grantee-is" || system === "granted-capability-is";
|
|
33
|
+
}
|
|
34
|
+
const subjectHandlers = {
|
|
35
|
+
[GRANTEE_IS]: (payload, context) => {
|
|
36
|
+
if (context.subject === void 0) return { found: false };
|
|
37
|
+
if (typeof payload !== "string") return { found: false };
|
|
38
|
+
return booleanFound(require_domain_device_id.deviceIdToHex(context.subject.bearer) === payload);
|
|
39
|
+
},
|
|
40
|
+
[GRANTED_CAPABILITY_IS]: (payload, context) => {
|
|
41
|
+
if (context.subject === void 0) return { found: false };
|
|
42
|
+
if (typeof payload !== "string") return { found: false };
|
|
43
|
+
return booleanFound(context.subject.capability === payload);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
27
46
|
function booleanFound(value) {
|
|
28
47
|
return {
|
|
29
48
|
found: true,
|
|
@@ -58,15 +77,15 @@ const narrowingHandlers = {
|
|
|
58
77
|
return booleanFound(context.candidate.delegationsRemaining !== void 0 && context.candidate.delegationsRemaining < parentRemaining);
|
|
59
78
|
}
|
|
60
79
|
};
|
|
61
|
-
/** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload
|
|
62
|
-
function delegateIsTrue(system) {
|
|
80
|
+
/** `{kind:"compare", op:"eq", left:{kind:"delegate", system, payload}, right:{kind:"booleanLiteral", value:true}}` -- delegate is an ExpressionNode, not itself a PredicateNode (trilean's PredicateNodeSchema has no `delegate` member), so every predicate op is expressed this way: resolve the delegate to a boolean ComputedValue, then compare it against the literal `true`. The narrowing checks pass no payload (every fact they need arrives via context); the subject-mode systems take their comparison value as the payload. */
|
|
81
|
+
function delegateIsTrue(system, payload = null) {
|
|
63
82
|
return {
|
|
64
83
|
kind: "compare",
|
|
65
84
|
op: "eq",
|
|
66
85
|
left: {
|
|
67
86
|
kind: "delegate",
|
|
68
87
|
system,
|
|
69
|
-
payload
|
|
88
|
+
payload
|
|
70
89
|
},
|
|
71
90
|
right: {
|
|
72
91
|
kind: "booleanLiteral",
|
|
@@ -104,16 +123,25 @@ async function evaluateNarrowing(parentClaims, childIssuer, candidate) {
|
|
|
104
123
|
/**
|
|
105
124
|
* Evaluates token-claims' own `conditions` field (issue #85): additional, issuer-chosen predicates strictly additive to the five mandatory narrowing checks above, which are computed structurally (from a delegated token's own claims and its parent's) and never read from this field -- the five built-ins are inherently about a parent/child relationship, so they are not meaningfully reusable inside a bare, parent-less conditions evaluation, unlike a genuine domain-specific condition (a message TTL, #84's future revoke-authorization check) which reasons about the presented token's own claims directly. `extraHandlers` is the registration point for those: none exist yet, so a `conditions` entry naming any system with no registered handler resolves `{found: false}` -- indeterminate, fail-closed. Absent `nodes` (an empty array, the caller's own signal for "no conditions field present") is trivially `{ok: true}` -- no extra restrictions, exactly today's behaviour. Every entry MUST evaluate to a definite `true`; the loop short-circuits on the first indeterminate or false result.
|
|
106
125
|
*/
|
|
107
|
-
async function evaluateConditions(nodes, claims, clock, extraHandlers = {}) {
|
|
108
|
-
const context = {
|
|
126
|
+
async function evaluateConditions(nodes, claims, clock, extraHandlers = {}, subject) {
|
|
127
|
+
const context = subject === void 0 ? {
|
|
109
128
|
clock,
|
|
110
129
|
claims
|
|
130
|
+
} : {
|
|
131
|
+
clock,
|
|
132
|
+
claims,
|
|
133
|
+
subject
|
|
111
134
|
};
|
|
112
135
|
const resolvers = {
|
|
113
136
|
resolveValue: unusedResolveValue,
|
|
114
137
|
resolveLookup: unusedResolveLookup,
|
|
115
138
|
resolveCollection: unusedResolveCollection,
|
|
116
139
|
resolveDelegate: async (system, payload) => {
|
|
140
|
+
if (subject !== void 0 && isSubjectSystem(system)) try {
|
|
141
|
+
return await subjectHandlers[system](payload, context);
|
|
142
|
+
} catch {
|
|
143
|
+
return { found: false };
|
|
144
|
+
}
|
|
117
145
|
const handler = Object.hasOwn(extraHandlers, system) ? extraHandlers[system] : void 0;
|
|
118
146
|
if (handler === void 0) return { found: false };
|
|
119
147
|
try {
|
|
@@ -134,6 +162,10 @@ async function evaluateConditions(nodes, claims, clock, extraHandlers = {}) {
|
|
|
134
162
|
}
|
|
135
163
|
//#endregion
|
|
136
164
|
//#region src/domain/tokens.ts
|
|
165
|
+
/** The capability verb of a grant-capability (wire-mesh#323): authority to mint other capability tokens, the same register manage:revoke already established for authority over tokens. */
|
|
166
|
+
const MANAGE_GRANT_CAPABILITY = "manage:grant";
|
|
167
|
+
/** Both link kinds walk at most this many hops before the verifier gives up, mirroring Rust's own MAX_CHAIN_DEPTH. Two colluding issuers can construct a mutually-bearing parent cycle that would otherwise recurse forever, and the authorised-by link makes the attack cheaper to mount, so TS gains the bound rather than relying on JS recursion limits. */
|
|
168
|
+
const MAX_CHAIN_DEPTH = 64;
|
|
137
169
|
/** RFC 9052 §4.4 Sig_structure for a COSE_Sign1 with no external AAD: ["Signature1", protected, external_aad, payload]. Exported (not just used internally by verifyTokenChain) so any other self-certifying COSE_Sign1 construction in this codebase -- e.g. threshold-subject.ts's own to-be-signed bytes for a FROST-signed capability-token/revocation-entry/handle-record/room-notice -- reuses this single construction rather than a second hand-rolled one. */
|
|
138
170
|
function sig1ToBeSigned(protectedHeader, payload) {
|
|
139
171
|
return (0, cbor2.encode)([
|
|
@@ -207,12 +239,22 @@ async function revocationEntryGrantsRevoke(entry, targetClaims, options) {
|
|
|
207
239
|
});
|
|
208
240
|
return authorizationVerdict.ok && authorizationVerdict.claims.capability === "manage:revoke" && require_token_scope.scopeNarrows(authorizationVerdict.claims.scope, targetClaims.scope);
|
|
209
241
|
}
|
|
210
|
-
async function verifyTokenChain(token, options) {
|
|
242
|
+
async function verifyTokenChain(token, options, subjectClaims, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
211
243
|
const [protectedHeader, , payload, signature] = token;
|
|
212
244
|
if (payload === null) return {
|
|
213
245
|
ok: false,
|
|
214
246
|
reason: "malformed"
|
|
215
247
|
};
|
|
248
|
+
if (depth >= MAX_CHAIN_DEPTH) return {
|
|
249
|
+
ok: false,
|
|
250
|
+
reason: "chain_too_deep"
|
|
251
|
+
};
|
|
252
|
+
const payloadHex = require_domain_device_id.bytesToHex(payload);
|
|
253
|
+
if (seen.has(payloadHex)) return {
|
|
254
|
+
ok: false,
|
|
255
|
+
reason: "chain_cycle"
|
|
256
|
+
};
|
|
257
|
+
const deeperSeen = new Set(seen).add(payloadHex);
|
|
216
258
|
let decodedClaims;
|
|
217
259
|
try {
|
|
218
260
|
decodedClaims = (0, cbor2.decode)(payload, cbor2.cdeDecodeOptions);
|
|
@@ -271,15 +313,25 @@ async function verifyTokenChain(token, options) {
|
|
|
271
313
|
ok: false,
|
|
272
314
|
reason: "conditions_invalid"
|
|
273
315
|
};
|
|
274
|
-
if (!(await evaluateConditions(conditionsResult.data, claims, options.clock, options.extraPredicateResolvers)).ok) return {
|
|
316
|
+
if (!(await evaluateConditions(conditionsResult.data, claims, options.clock, options.extraPredicateResolvers, subjectClaims)).ok) return {
|
|
275
317
|
ok: false,
|
|
276
318
|
reason: "conditions_not_satisfied"
|
|
277
319
|
};
|
|
278
320
|
}
|
|
279
|
-
|
|
321
|
+
const parentBytes = claims.parent;
|
|
322
|
+
const authorisationBytes = claims["authorised-by"];
|
|
323
|
+
if (claims.capability === "manage:grant" && parentBytes !== void 0) return {
|
|
324
|
+
ok: false,
|
|
325
|
+
reason: "authorisation_invalid"
|
|
326
|
+
};
|
|
327
|
+
if (parentBytes !== void 0 && authorisationBytes !== void 0) return {
|
|
328
|
+
ok: false,
|
|
329
|
+
reason: "malformed"
|
|
330
|
+
};
|
|
331
|
+
if (parentBytes !== void 0) {
|
|
280
332
|
let decodedParent;
|
|
281
333
|
try {
|
|
282
|
-
decodedParent = (0, cbor2.decode)(
|
|
334
|
+
decodedParent = (0, cbor2.decode)(parentBytes, cbor2.cdeDecodeOptions);
|
|
283
335
|
} catch {
|
|
284
336
|
return {
|
|
285
337
|
ok: false,
|
|
@@ -291,11 +343,17 @@ async function verifyTokenChain(token, options) {
|
|
|
291
343
|
ok: false,
|
|
292
344
|
reason: "parent_invalid"
|
|
293
345
|
};
|
|
294
|
-
const parentVerdict = await verifyTokenChain(parentResult.data, options);
|
|
295
|
-
if (!parentVerdict.ok)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
346
|
+
const parentVerdict = await verifyTokenChain(parentResult.data, options, void 0, depth + 1, deeperSeen);
|
|
347
|
+
if (!parentVerdict.ok) {
|
|
348
|
+
if (parentVerdict.reason === "chain_too_deep" || parentVerdict.reason === "chain_cycle") return {
|
|
349
|
+
ok: false,
|
|
350
|
+
reason: parentVerdict.reason
|
|
351
|
+
};
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
reason: "parent_invalid"
|
|
355
|
+
};
|
|
356
|
+
}
|
|
299
357
|
const candidate = {
|
|
300
358
|
capability: claims.capability,
|
|
301
359
|
scope: claims.scope,
|
|
@@ -314,6 +372,46 @@ async function verifyTokenChain(token, options) {
|
|
|
314
372
|
depth: parentVerdict.depth + 1
|
|
315
373
|
};
|
|
316
374
|
}
|
|
375
|
+
if (authorisationBytes !== void 0) {
|
|
376
|
+
let decodedAuthoriser;
|
|
377
|
+
try {
|
|
378
|
+
decodedAuthoriser = (0, cbor2.decode)(authorisationBytes, cbor2.cdeDecodeOptions);
|
|
379
|
+
} catch {
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
reason: "authorisation_invalid"
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const authoriserResult = require_generated_protocol.capabilityTokenSchema.safeParse(decodedAuthoriser);
|
|
386
|
+
if (!authoriserResult.success) return {
|
|
387
|
+
ok: false,
|
|
388
|
+
reason: "authorisation_invalid"
|
|
389
|
+
};
|
|
390
|
+
const authoriserVerdict = await verifyTokenChain(authoriserResult.data, options, claims, depth + 1, deeperSeen);
|
|
391
|
+
if (!authoriserVerdict.ok) {
|
|
392
|
+
if (authoriserVerdict.reason === "chain_too_deep" || authoriserVerdict.reason === "chain_cycle") return {
|
|
393
|
+
ok: false,
|
|
394
|
+
reason: authoriserVerdict.reason
|
|
395
|
+
};
|
|
396
|
+
return {
|
|
397
|
+
ok: false,
|
|
398
|
+
reason: "authorisation_invalid"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const authoriser = authoriserVerdict.claims;
|
|
402
|
+
const grantsCapability = authoriser["grants-capability"];
|
|
403
|
+
if (!(require_token_scope.bytesEqual(authoriser.bearer, claims.issuer) && authoriser.capability === "manage:grant" && (grantsCapability === void 0 || grantsCapability === claims.capability) && require_token_scope.scopeNarrows(authoriser.scope, claims.scope) && claims.expires <= authoriser.expires && (authoriser["delegations-remaining"] === void 0 || claims["delegations-remaining"] !== void 0 && claims["delegations-remaining"] < authoriser["delegations-remaining"]))) return {
|
|
404
|
+
ok: false,
|
|
405
|
+
reason: "authorisation_invalid"
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
ok: true,
|
|
409
|
+
claims,
|
|
410
|
+
rootIssuer: authoriserVerdict.rootIssuer,
|
|
411
|
+
rootIssuerKey: authoriserVerdict.rootIssuerKey,
|
|
412
|
+
depth: authoriserVerdict.depth + 1
|
|
413
|
+
};
|
|
414
|
+
}
|
|
317
415
|
return {
|
|
318
416
|
ok: true,
|
|
319
417
|
claims,
|
|
@@ -373,8 +471,30 @@ async function checkNarrowing(parentClaims, granterDeviceId, candidate) {
|
|
|
373
471
|
const failedSystem = await evaluateNarrowing(parentClaims, granterDeviceId, candidate);
|
|
374
472
|
return failedSystem === void 0 ? void 0 : NARROWING_SYSTEM_TO_MINT_REFUSAL[failedSystem];
|
|
375
473
|
}
|
|
474
|
+
/** The authorised-by link's own arithmetic (wire-mesh#323), the analogue checkNarrowing already is for the parent link: everything tokens.cddl's authorised-by obligations require, checked against a not-yet-minted candidate, shared between mintCapabilityToken and canGrantVia so the two can never drift. The authoriser's conditions are evaluated in subject mode against the candidate, the same evaluation the verifier performs against the minted child. Returns the specific refusal reason, or undefined when the authorisation holds. */
|
|
475
|
+
async function checkAuthorisation(authorisingClaims, minterDeviceId, candidate) {
|
|
476
|
+
if (authorisingClaims.parent !== void 0) return "authorisation_parent_forbidden";
|
|
477
|
+
if (!require_token_scope.bytesEqual(authorisingClaims.bearer, minterDeviceId)) return "authorisation_bearer_mismatch";
|
|
478
|
+
const grantsCapability = authorisingClaims["grants-capability"];
|
|
479
|
+
if (authorisingClaims.capability !== "manage:grant" || grantsCapability !== void 0 && grantsCapability !== candidate.capability) return "authorisation_capability_mismatch";
|
|
480
|
+
if (!require_token_scope.scopeNarrows(authorisingClaims.scope, candidate.scope)) return "authorisation_scope_does_not_narrow";
|
|
481
|
+
if (candidate.expires > authorisingClaims.expires) return "authorisation_exceeds_authoriser";
|
|
482
|
+
const remaining = authorisingClaims["delegations-remaining"];
|
|
483
|
+
if (remaining !== void 0 && (candidate.delegationsRemaining === void 0 || candidate.delegationsRemaining >= remaining)) return "authorisation_delegation_exceeds";
|
|
484
|
+
if (authorisingClaims.conditions !== void 0) {
|
|
485
|
+
let decodedConditions;
|
|
486
|
+
try {
|
|
487
|
+
decodedConditions = (0, cbor2.decode)(authorisingClaims.conditions, cbor2.cdeDecodeOptions);
|
|
488
|
+
} catch {
|
|
489
|
+
return "authorisation_malformed";
|
|
490
|
+
}
|
|
491
|
+
const conditionsResult = conditionsListSchema.safeParse(decodedConditions);
|
|
492
|
+
if (!conditionsResult.success) return "authorisation_malformed";
|
|
493
|
+
if (!(await evaluateConditions(conditionsResult.data, authorisingClaims, { now: () => 0 }, {}, candidate)).ok) return "authorisation_conditions_not_satisfied";
|
|
494
|
+
}
|
|
495
|
+
}
|
|
376
496
|
/**
|
|
377
|
-
* A pure query: could deviceId, presenting heldToken as its own delegation authority, successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
|
|
497
|
+
* A pure query: could deviceId, presenting heldToken as its own delegation authority," successfully mint a delegation matching candidate right now -- without attempting (and potentially failing) a real mint just to find out. Reuses mintCapabilityToken's own narrowing arithmetic via checkNarrowing, so the two can never silently drift into different ideas of what "narrows" means.
|
|
378
498
|
*
|
|
379
499
|
* Deliberately narrower than a full mint attempt in one respect: this checks only the narrowing rules tokens.cddl's own delegation obligations require (bearer match, expiry, scope, capability, delegations-remaining), the same scope mintCapabilityToken itself checks a *parent* against -- it does not verify heldToken's own signature or revocation status, exactly as mintCapabilityToken never re-verifies its own parent's signature either. A caller that also needs heldToken's cryptographic validity confirmed calls verifyCapabilityToken separately.
|
|
380
500
|
*
|
|
@@ -386,6 +506,13 @@ async function canGrant(heldToken, deviceId, candidate, now) {
|
|
|
386
506
|
if (heldClaims === void 0) return false;
|
|
387
507
|
return await checkNarrowing(heldClaims, deviceId, candidate) === void 0;
|
|
388
508
|
}
|
|
509
|
+
/** A pure query: could deviceId, presenting heldGrantCapability as its minting authority, mint a token matching candidate right now (wire-mesh#323) -- canGrant's authorised-by analogue, with the same deliberately-narrower-than-verify caveat canGrant's own doc comment states: the held token's signature and revocation status are not checked here; a caller needing those confirmed calls verifyCapabilityToken separately. The candidate carries a bearer because the authoriser's subject-mode conditions read it (the no-self-grant bar). */
|
|
510
|
+
async function canGrantVia(heldGrantCapability, deviceId, candidate, now) {
|
|
511
|
+
if (candidate.expires <= now) return false;
|
|
512
|
+
const heldClaims = decodeTokenClaims(heldGrantCapability);
|
|
513
|
+
if (heldClaims === void 0) return false;
|
|
514
|
+
return await checkAuthorisation(heldClaims, deviceId, candidate) === void 0;
|
|
515
|
+
}
|
|
389
516
|
/**
|
|
390
517
|
* Mints one capability token: builds token-claims from the given fields, signs it as a COSE_Sign1 under `identity`'s own key, with a protected header matching what the frozen conformance vectors actually encode (`{1: alg, 4: issuer device-id}`, not the empty header a token merely needs to verify against itself).
|
|
391
518
|
*
|
|
@@ -396,6 +523,14 @@ async function mintCapabilityToken(options) {
|
|
|
396
523
|
ok: false,
|
|
397
524
|
reason: "already_expired"
|
|
398
525
|
};
|
|
526
|
+
if (options.parent !== void 0 && options.authorisedBy !== void 0) return {
|
|
527
|
+
ok: false,
|
|
528
|
+
reason: "links_exclusive"
|
|
529
|
+
};
|
|
530
|
+
if (options.capability === "manage:grant" && options.parent !== void 0) return {
|
|
531
|
+
ok: false,
|
|
532
|
+
reason: "authorisation_parent_forbidden"
|
|
533
|
+
};
|
|
399
534
|
let parentBytes;
|
|
400
535
|
if (options.parent !== void 0) {
|
|
401
536
|
const parentClaims = decodeTokenClaims(options.parent);
|
|
@@ -415,6 +550,26 @@ async function mintCapabilityToken(options) {
|
|
|
415
550
|
};
|
|
416
551
|
parentBytes = encodeBuf(options.parent);
|
|
417
552
|
}
|
|
553
|
+
let authorisationBytes;
|
|
554
|
+
if (options.authorisedBy !== void 0) {
|
|
555
|
+
const authorisingClaims = decodeTokenClaims(options.authorisedBy);
|
|
556
|
+
if (authorisingClaims === void 0) return {
|
|
557
|
+
ok: false,
|
|
558
|
+
reason: "authorisation_malformed"
|
|
559
|
+
};
|
|
560
|
+
const refusal = await checkAuthorisation(authorisingClaims, options.identity.deviceId, {
|
|
561
|
+
capability: options.capability,
|
|
562
|
+
scope: options.scope,
|
|
563
|
+
bearer: options.bearer,
|
|
564
|
+
expires: options.expires,
|
|
565
|
+
...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
|
|
566
|
+
});
|
|
567
|
+
if (refusal !== void 0) return {
|
|
568
|
+
ok: false,
|
|
569
|
+
reason: refusal
|
|
570
|
+
};
|
|
571
|
+
authorisationBytes = encodeBuf(options.authorisedBy);
|
|
572
|
+
}
|
|
418
573
|
const payload = encodeBuf({
|
|
419
574
|
"token-id": options.tokenId,
|
|
420
575
|
issuer: options.identity.deviceId,
|
|
@@ -426,7 +581,10 @@ async function mintCapabilityToken(options) {
|
|
|
426
581
|
...options.notBefore !== void 0 ? { "not-before": options.notBefore } : {},
|
|
427
582
|
...parentBytes !== void 0 ? { parent: parentBytes } : {},
|
|
428
583
|
...options.delegationsRemaining !== void 0 ? { "delegations-remaining": options.delegationsRemaining } : {},
|
|
429
|
-
...options.conditions !== void 0 ? { conditions: encodeBuf(options.conditions) } : {}
|
|
584
|
+
...options.conditions !== void 0 ? { conditions: encodeBuf(options.conditions) } : {},
|
|
585
|
+
...authorisationBytes !== void 0 ? { "authorised-by": authorisationBytes } : {},
|
|
586
|
+
...options.grantsCapability !== void 0 ? { "grants-capability": options.grantsCapability } : {},
|
|
587
|
+
...options.requestsCapability !== void 0 ? { "requests-capability": options.requestsCapability } : {}
|
|
430
588
|
});
|
|
431
589
|
const protectedHeader = protectedHeaderFor(options.identity);
|
|
432
590
|
return {
|
|
@@ -456,12 +614,24 @@ async function mintRevocationEntry(options) {
|
|
|
456
614
|
];
|
|
457
615
|
}
|
|
458
616
|
//#endregion
|
|
617
|
+
Object.defineProperty(exports, "MANAGE_GRANT_CAPABILITY", {
|
|
618
|
+
enumerable: true,
|
|
619
|
+
get: function() {
|
|
620
|
+
return MANAGE_GRANT_CAPABILITY;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
459
623
|
Object.defineProperty(exports, "canGrant", {
|
|
460
624
|
enumerable: true,
|
|
461
625
|
get: function() {
|
|
462
626
|
return canGrant;
|
|
463
627
|
}
|
|
464
628
|
});
|
|
629
|
+
Object.defineProperty(exports, "canGrantVia", {
|
|
630
|
+
enumerable: true,
|
|
631
|
+
get: function() {
|
|
632
|
+
return canGrantVia;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
465
635
|
Object.defineProperty(exports, "mintCapabilityToken", {
|
|
466
636
|
enumerable: true,
|
|
467
637
|
get: function() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, N as Frame } from "./protocol-
|
|
1
|
+
import { D as DeviceId, N as Frame } from "./protocol-BgR1e_NI.cjs";
|
|
2
2
|
//#region src/ports/transport.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* An async, serialisable-data contract for exchanging frame values over some connection -- deliberately without baking in any specific transport's own primitives (no raw socket types, no WebSocket-specific options), so a TCP adapter, a WebSocket adapter, or an in-memory adapter for tests can all satisfy it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as DeviceId, N as Frame } from "./protocol-
|
|
1
|
+
import { D as DeviceId, N as Frame } from "./protocol-BgR1e_NI.mjs";
|
|
2
2
|
//#region src/ports/transport.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* An async, serialisable-data contract for exchanging frame values over some connection -- deliberately without baking in any specific transport's own primitives (no raw socket types, no WebSocket-specific options), so a TCP adapter, a WebSocket adapter, or an in-memory adapter for tests can all satisfy it.
|