wire-mesh-core 1.9.0 → 1.10.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/tokens.cjs +29 -19
- package/dist/domain/tokens.d.cts +13 -0
- package/dist/domain/tokens.d.mts +13 -0
- package/dist/domain/tokens.mjs +29 -20
- package/package.json +1 -1
package/dist/domain/tokens.cjs
CHANGED
|
@@ -214,6 +214,26 @@ async function verifyRevocationEntry(entry, options) {
|
|
|
214
214
|
claims
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
/** The narrowing arithmetic tokens.cddl's own delegation obligations require (bearer match, expiry within the parent's, scope narrows, same capability, delegations-remaining strictly less than the parent's) -- shared between mintCapabilityToken (which additionally builds and signs the resulting token) and canGrant (a pure query with no minting side effect at all), so the two can never silently drift into two different ideas of what "narrows" means. Returns the specific refusal reason, or undefined when every rule is satisfied. */
|
|
218
|
+
function checkNarrowing(parentClaims, granterDeviceId, candidate) {
|
|
219
|
+
if (!bytesEqual(parentClaims.bearer, granterDeviceId)) return "parent_bearer_mismatch";
|
|
220
|
+
if (candidate.expires > parentClaims.expires) return "expires_exceeds_parent";
|
|
221
|
+
if (!scopeNarrows(parentClaims.scope, candidate.scope)) return "scope_does_not_narrow";
|
|
222
|
+
if (parentClaims.capability !== candidate.capability) return "capability_mismatch";
|
|
223
|
+
const parentRemaining = parentClaims["delegations-remaining"];
|
|
224
|
+
if (parentRemaining !== void 0 && (candidate.delegationsRemaining === void 0 || candidate.delegationsRemaining >= parentRemaining)) return "delegation_exceeds_parent";
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 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.
|
|
228
|
+
*
|
|
229
|
+
* 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.
|
|
230
|
+
*/
|
|
231
|
+
function canGrant(heldToken, deviceId, candidate, now) {
|
|
232
|
+
if (candidate.expires <= now) return false;
|
|
233
|
+
const heldClaims = decodeTokenClaims(heldToken);
|
|
234
|
+
if (heldClaims === void 0) return false;
|
|
235
|
+
return checkNarrowing(heldClaims, deviceId, candidate) === void 0;
|
|
236
|
+
}
|
|
217
237
|
/**
|
|
218
238
|
* 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).
|
|
219
239
|
*
|
|
@@ -231,26 +251,15 @@ async function mintCapabilityToken(options) {
|
|
|
231
251
|
ok: false,
|
|
232
252
|
reason: "parent_malformed"
|
|
233
253
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
const refusal = checkNarrowing(parentClaims, options.identity.deviceId, {
|
|
255
|
+
capability: options.capability,
|
|
256
|
+
scope: options.scope,
|
|
257
|
+
expires: options.expires,
|
|
258
|
+
...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
|
|
259
|
+
});
|
|
260
|
+
if (refusal !== void 0) return {
|
|
239
261
|
ok: false,
|
|
240
|
-
reason:
|
|
241
|
-
};
|
|
242
|
-
if (!scopeNarrows(parentClaims.scope, options.scope)) return {
|
|
243
|
-
ok: false,
|
|
244
|
-
reason: "scope_does_not_narrow"
|
|
245
|
-
};
|
|
246
|
-
if (parentClaims.capability !== options.capability) return {
|
|
247
|
-
ok: false,
|
|
248
|
-
reason: "capability_mismatch"
|
|
249
|
-
};
|
|
250
|
-
const parentRemaining = parentClaims["delegations-remaining"];
|
|
251
|
-
if (parentRemaining !== void 0 && (options.delegationsRemaining === void 0 || options.delegationsRemaining >= parentRemaining)) return {
|
|
252
|
-
ok: false,
|
|
253
|
-
reason: "delegation_exceeds_parent"
|
|
262
|
+
reason: refusal
|
|
254
263
|
};
|
|
255
264
|
parentBytes = encodeBuf(options.parent);
|
|
256
265
|
}
|
|
@@ -294,6 +303,7 @@ async function mintRevocationEntry(options) {
|
|
|
294
303
|
];
|
|
295
304
|
}
|
|
296
305
|
//#endregion
|
|
306
|
+
exports.canGrant = canGrant;
|
|
297
307
|
exports.mintCapabilityToken = mintCapabilityToken;
|
|
298
308
|
exports.mintRevocationEntry = mintRevocationEntry;
|
|
299
309
|
exports.verifyCapabilityToken = verifyCapabilityToken;
|
package/dist/domain/tokens.d.cts
CHANGED
|
@@ -55,6 +55,19 @@ export type MintVerdict = {
|
|
|
55
55
|
ok: false;
|
|
56
56
|
reason: MintRefusalReason;
|
|
57
57
|
};
|
|
58
|
+
/** What a would-be delegation needs, to check it narrows a specific parent -- everything mintCapabilityToken itself checks a delegation against, independent of the tokenId/bearer/notBefore/signing concerns unique to actually minting one. */
|
|
59
|
+
interface NarrowingCandidate {
|
|
60
|
+
capability: TokenClaims["capability"];
|
|
61
|
+
scope: TokenClaims["scope"];
|
|
62
|
+
expires: number;
|
|
63
|
+
delegationsRemaining?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 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.
|
|
67
|
+
*
|
|
68
|
+
* 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.
|
|
69
|
+
*/
|
|
70
|
+
export declare function canGrant(heldToken: CapabilityToken, deviceId: DeviceId, candidate: Readonly<NarrowingCandidate>, now: number): boolean;
|
|
58
71
|
export interface MintCapabilityTokenOptions {
|
|
59
72
|
/** The issuer -- signs the token, and supplies the self-certifying issuer/issuer-key claims. */
|
|
60
73
|
identity: IdentityPort;
|
package/dist/domain/tokens.d.mts
CHANGED
|
@@ -55,6 +55,19 @@ export type MintVerdict = {
|
|
|
55
55
|
ok: false;
|
|
56
56
|
reason: MintRefusalReason;
|
|
57
57
|
};
|
|
58
|
+
/** What a would-be delegation needs, to check it narrows a specific parent -- everything mintCapabilityToken itself checks a delegation against, independent of the tokenId/bearer/notBefore/signing concerns unique to actually minting one. */
|
|
59
|
+
interface NarrowingCandidate {
|
|
60
|
+
capability: TokenClaims["capability"];
|
|
61
|
+
scope: TokenClaims["scope"];
|
|
62
|
+
expires: number;
|
|
63
|
+
delegationsRemaining?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 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.
|
|
67
|
+
*
|
|
68
|
+
* 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.
|
|
69
|
+
*/
|
|
70
|
+
export declare function canGrant(heldToken: CapabilityToken, deviceId: DeviceId, candidate: Readonly<NarrowingCandidate>, now: number): boolean;
|
|
58
71
|
export interface MintCapabilityTokenOptions {
|
|
59
72
|
/** The issuer -- signs the token, and supplies the self-certifying issuer/issuer-key claims. */
|
|
60
73
|
identity: IdentityPort;
|
package/dist/domain/tokens.mjs
CHANGED
|
@@ -213,6 +213,26 @@ async function verifyRevocationEntry(entry, options) {
|
|
|
213
213
|
claims
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
|
+
/** The narrowing arithmetic tokens.cddl's own delegation obligations require (bearer match, expiry within the parent's, scope narrows, same capability, delegations-remaining strictly less than the parent's) -- shared between mintCapabilityToken (which additionally builds and signs the resulting token) and canGrant (a pure query with no minting side effect at all), so the two can never silently drift into two different ideas of what "narrows" means. Returns the specific refusal reason, or undefined when every rule is satisfied. */
|
|
217
|
+
function checkNarrowing(parentClaims, granterDeviceId, candidate) {
|
|
218
|
+
if (!bytesEqual(parentClaims.bearer, granterDeviceId)) return "parent_bearer_mismatch";
|
|
219
|
+
if (candidate.expires > parentClaims.expires) return "expires_exceeds_parent";
|
|
220
|
+
if (!scopeNarrows(parentClaims.scope, candidate.scope)) return "scope_does_not_narrow";
|
|
221
|
+
if (parentClaims.capability !== candidate.capability) return "capability_mismatch";
|
|
222
|
+
const parentRemaining = parentClaims["delegations-remaining"];
|
|
223
|
+
if (parentRemaining !== void 0 && (candidate.delegationsRemaining === void 0 || candidate.delegationsRemaining >= parentRemaining)) return "delegation_exceeds_parent";
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 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.
|
|
227
|
+
*
|
|
228
|
+
* 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.
|
|
229
|
+
*/
|
|
230
|
+
function canGrant(heldToken, deviceId, candidate, now) {
|
|
231
|
+
if (candidate.expires <= now) return false;
|
|
232
|
+
const heldClaims = decodeTokenClaims(heldToken);
|
|
233
|
+
if (heldClaims === void 0) return false;
|
|
234
|
+
return checkNarrowing(heldClaims, deviceId, candidate) === void 0;
|
|
235
|
+
}
|
|
216
236
|
/**
|
|
217
237
|
* 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).
|
|
218
238
|
*
|
|
@@ -230,26 +250,15 @@ async function mintCapabilityToken(options) {
|
|
|
230
250
|
ok: false,
|
|
231
251
|
reason: "parent_malformed"
|
|
232
252
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
const refusal = checkNarrowing(parentClaims, options.identity.deviceId, {
|
|
254
|
+
capability: options.capability,
|
|
255
|
+
scope: options.scope,
|
|
256
|
+
expires: options.expires,
|
|
257
|
+
...options.delegationsRemaining !== void 0 ? { delegationsRemaining: options.delegationsRemaining } : {}
|
|
258
|
+
});
|
|
259
|
+
if (refusal !== void 0) return {
|
|
238
260
|
ok: false,
|
|
239
|
-
reason:
|
|
240
|
-
};
|
|
241
|
-
if (!scopeNarrows(parentClaims.scope, options.scope)) return {
|
|
242
|
-
ok: false,
|
|
243
|
-
reason: "scope_does_not_narrow"
|
|
244
|
-
};
|
|
245
|
-
if (parentClaims.capability !== options.capability) return {
|
|
246
|
-
ok: false,
|
|
247
|
-
reason: "capability_mismatch"
|
|
248
|
-
};
|
|
249
|
-
const parentRemaining = parentClaims["delegations-remaining"];
|
|
250
|
-
if (parentRemaining !== void 0 && (options.delegationsRemaining === void 0 || options.delegationsRemaining >= parentRemaining)) return {
|
|
251
|
-
ok: false,
|
|
252
|
-
reason: "delegation_exceeds_parent"
|
|
261
|
+
reason: refusal
|
|
253
262
|
};
|
|
254
263
|
parentBytes = encodeBuf(options.parent);
|
|
255
264
|
}
|
|
@@ -293,4 +302,4 @@ async function mintRevocationEntry(options) {
|
|
|
293
302
|
];
|
|
294
303
|
}
|
|
295
304
|
//#endregion
|
|
296
|
-
export { mintCapabilityToken, mintRevocationEntry, verifyCapabilityToken, verifyRevocationEntry };
|
|
305
|
+
export { canGrant, mintCapabilityToken, mintRevocationEntry, verifyCapabilityToken, verifyRevocationEntry };
|