@joe-broadhead/messaging 0.0.0-stage → 0.7.1
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/CHANGELOG.md +220 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +36 -2
- package/dist/bin/messaging-mcp.js +26524 -0
- package/dist/bin/messaging-relay.js +19768 -0
- package/dist/bin/messaging.js +15465 -0
- package/dist/bin/messagingd.js +29449 -0
- package/dist/lib/core-testing.js +1 -0
- package/dist/lib/core.js +1 -0
- package/dist/lib/ipc-testing.js +1 -0
- package/dist/lib/ipc.js +1 -0
- package/dist/lib/modules/core/address.js +42 -0
- package/dist/lib/modules/core/agent/operations.js +595 -0
- package/dist/lib/modules/core/agent/present.js +244 -0
- package/dist/lib/modules/core/agent/text.js +79 -0
- package/dist/lib/modules/core/agent-tools.js +477 -0
- package/dist/lib/modules/core/b64u.js +20 -0
- package/dist/lib/modules/core/cards.js +55 -0
- package/dist/lib/modules/core/check.js +20 -0
- package/dist/lib/modules/core/consent.js +199 -0
- package/dist/lib/modules/core/contact-card.js +74 -0
- package/dist/lib/modules/core/conversation/context.js +80 -0
- package/dist/lib/modules/core/conversation/event.js +100 -0
- package/dist/lib/modules/core/conversation/ids.js +38 -0
- package/dist/lib/modules/core/conversation/link.js +35 -0
- package/dist/lib/modules/core/conversation/log.js +766 -0
- package/dist/lib/modules/core/conversation/ratchet.js +243 -0
- package/dist/lib/modules/core/conversation/seal.js +198 -0
- package/dist/lib/modules/core/delegation.js +174 -0
- package/dist/lib/modules/core/directory/device-log.js +258 -0
- package/dist/lib/modules/core/directory/epoch.js +103 -0
- package/dist/lib/modules/core/directory/handle.js +29 -0
- package/dist/lib/modules/core/directory/jelly-device.js +204 -0
- package/dist/lib/modules/core/directory/jelly-issuer.js +12 -0
- package/dist/lib/modules/core/directory/jelly-token.js +305 -0
- package/dist/lib/modules/core/directory/keys-statement.js +43 -0
- package/dist/lib/modules/core/directory/link.js +81 -0
- package/dist/lib/modules/core/directory/merkle-log.js +250 -0
- package/dist/lib/modules/core/directory/person-view.js +197 -0
- package/dist/lib/modules/core/directory/person.js +40 -0
- package/dist/lib/modules/core/directory/realm.js +59 -0
- package/dist/lib/modules/core/directory/smt.js +266 -0
- package/dist/lib/modules/core/envelope.js +320 -0
- package/dist/lib/modules/core/hash.js +5 -0
- package/dist/lib/modules/core/head.js +27 -0
- package/dist/lib/modules/core/identity.js +71 -0
- package/dist/lib/modules/core/index.js +53 -0
- package/dist/lib/modules/core/jcs.js +96 -0
- package/dist/lib/modules/core/jelly-host-double.js +35 -0
- package/dist/lib/modules/core/jelly-host.js +211 -0
- package/dist/lib/modules/core/keys.js +79 -0
- package/dist/lib/modules/core/peer-text.js +563 -0
- package/dist/lib/modules/core/realm.js +1 -0
- package/dist/lib/modules/core/roster.js +454 -0
- package/dist/lib/modules/core/schema/a2a.js +85 -0
- package/dist/lib/modules/core/schema/common.js +310 -0
- package/dist/lib/modules/core/schema/consent.js +120 -0
- package/dist/lib/modules/core/schema/conversation.js +292 -0
- package/dist/lib/modules/core/schema/delegation.js +58 -0
- package/dist/lib/modules/core/schema/directory-frames.js +270 -0
- package/dist/lib/modules/core/schema/directory.js +169 -0
- package/dist/lib/modules/core/schema/envelope.js +211 -0
- package/dist/lib/modules/core/schema/errors.js +104 -0
- package/dist/lib/modules/core/schema/index.js +16 -0
- package/dist/lib/modules/core/schema/ipc/consent.js +181 -0
- package/dist/lib/modules/core/schema/ipc/device.js +276 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +189 -0
- package/dist/lib/modules/core/schema/ipc/shapes.js +185 -0
- package/dist/lib/modules/core/schema/ipc/surfaces.js +223 -0
- package/dist/lib/modules/core/schema/ipc.js +451 -0
- package/dist/lib/modules/core/schema/jelly.js +122 -0
- package/dist/lib/modules/core/schema/realm.js +27 -0
- package/dist/lib/modules/core/schema/relay.js +112 -0
- package/dist/lib/modules/core/schema/roster.js +126 -0
- package/dist/lib/modules/core/sign.js +43 -0
- package/dist/lib/modules/core/task.js +37 -0
- package/dist/lib/modules/core/testing.js +21 -0
- package/dist/lib/modules/core/ulid.js +36 -0
- package/dist/lib/modules/core/version.js +6 -0
- package/dist/lib/modules/core/wire.js +255 -0
- package/dist/lib/modules/ipc/client.js +289 -0
- package/dist/lib/modules/ipc/framing.js +33 -0
- package/dist/lib/modules/ipc/index.js +6 -0
- package/dist/lib/modules/ipc/paths.js +102 -0
- package/dist/lib/modules/ipc/pipes.js +65 -0
- package/dist/lib/modules/ipc/rpc.js +8 -0
- package/dist/lib/modules/ipc/server.js +317 -0
- package/dist/lib/modules/ipc/testing.js +145 -0
- package/dist/lib/modules/ipc/trust.js +166 -0
- package/dist/lib/modules/ipc/windows-acl.js +275 -0
- package/dist/lib/modules/vendor/zod/index.js +7 -0
- package/dist/lib/modules/vendor/zod/v4/classic/checks.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/classic/coerce.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/compat.js +31 -0
- package/dist/lib/modules/vendor/zod/v4/classic/deep-partial.js +13 -0
- package/dist/lib/modules/vendor/zod/v4/classic/errors.js +57 -0
- package/dist/lib/modules/vendor/zod/v4/classic/external.js +17 -0
- package/dist/lib/modules/vendor/zod/v4/classic/from-json-schema.js +881 -0
- package/dist/lib/modules/vendor/zod/v4/classic/in-out.js +36 -0
- package/dist/lib/modules/vendor/zod/v4/classic/iso.js +15 -0
- package/dist/lib/modules/vendor/zod/v4/classic/parse.js +16 -0
- package/dist/lib/modules/vendor/zod/v4/classic/schemas.js +1535 -0
- package/dist/lib/modules/vendor/zod/v4/core/api.js +1107 -0
- package/dist/lib/modules/vendor/zod/v4/core/checks.js +516 -0
- package/dist/lib/modules/vendor/zod/v4/core/compile.js +1866 -0
- package/dist/lib/modules/vendor/zod/v4/core/core.js +141 -0
- package/dist/lib/modules/vendor/zod/v4/core/doc.js +38 -0
- package/dist/lib/modules/vendor/zod/v4/core/errors.js +267 -0
- package/dist/lib/modules/vendor/zod/v4/core/index.js +19 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-generator.js +99 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema-processors.js +843 -0
- package/dist/lib/modules/vendor/zod/v4/core/json-schema.js +1 -0
- package/dist/lib/modules/vendor/zod/v4/core/memoizer.js +311 -0
- package/dist/lib/modules/vendor/zod/v4/core/parse.js +175 -0
- package/dist/lib/modules/vendor/zod/v4/core/regexes.js +156 -0
- package/dist/lib/modules/vendor/zod/v4/core/registries.js +51 -0
- package/dist/lib/modules/vendor/zod/v4/core/schemas.js +2636 -0
- package/dist/lib/modules/vendor/zod/v4/core/to-json-schema.js +651 -0
- package/dist/lib/modules/vendor/zod/v4/core/util.js +873 -0
- package/dist/lib/modules/vendor/zod/v4/core/versions.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/core/visit.js +169 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ar.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/az.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/be.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bg.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/bn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ca.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ckb.js +133 -0
- package/dist/lib/modules/vendor/zod/v4/locales/cs.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/da.js +120 -0
- package/dist/lib/modules/vendor/zod/v4/locales/de.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/el.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/en.js +125 -0
- package/dist/lib/modules/vendor/zod/v4/locales/eo.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/es.js +136 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fa.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fi.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr-CA.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/fr.js +129 -0
- package/dist/lib/modules/vendor/zod/v4/locales/gu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/he.js +221 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hi.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hr.js +126 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hu.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/hy.js +158 -0
- package/dist/lib/modules/vendor/zod/v4/locales/id.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/index.js +63 -0
- package/dist/lib/modules/vendor/zod/v4/locales/is.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/it.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ja.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ka.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kh.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/km.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/kn.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ko.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/lt.js +207 -0
- package/dist/lib/modules/vendor/zod/v4/locales/mk.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ms.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ne.js +111 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nl.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/nn.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/no.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ota.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ps.js +119 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt-BR.js +143 -0
- package/dist/lib/modules/vendor/zod/v4/locales/pt.js +142 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ro.js +122 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ru.js +168 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sk.js +116 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sl.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/sv.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ta.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tg.js +117 -0
- package/dist/lib/modules/vendor/zod/v4/locales/th.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tk.js +107 -0
- package/dist/lib/modules/vendor/zod/v4/locales/tr.js +110 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ua.js +5 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uk.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/ur.js +115 -0
- package/dist/lib/modules/vendor/zod/v4/locales/uz.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/vi.js +113 -0
- package/dist/lib/modules/vendor/zod/v4/locales/yo.js +112 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-CN.js +114 -0
- package/dist/lib/modules/vendor/zod/v4/locales/zh-TW.js +112 -0
- package/dist/lib/testing-jelly-id.js +1219 -0
- package/dist/lib/zod.js +1 -0
- package/dist/spec/messaging-v1.md +3744 -0
- package/dist/third-party-notices.md +239 -0
- package/dist/third-party.json +88 -0
- package/dist/types/core/address.d.ts +16 -0
- package/dist/types/core/agent/operations.d.ts +129 -0
- package/dist/types/core/agent/present.d.ts +57 -0
- package/dist/types/core/agent/text.d.ts +62 -0
- package/dist/types/core/agent-tools.d.ts +966 -0
- package/dist/types/core/b64u.d.ts +6 -0
- package/dist/types/core/cards.d.ts +21 -0
- package/dist/types/core/check.d.ts +10 -0
- package/dist/types/core/consent.d.ts +132 -0
- package/dist/types/core/contact-card.d.ts +34 -0
- package/dist/types/core/conversation/context.d.ts +56 -0
- package/dist/types/core/conversation/event.d.ts +36 -0
- package/dist/types/core/conversation/ids.d.ts +18 -0
- package/dist/types/core/conversation/link.d.ts +16 -0
- package/dist/types/core/conversation/log.d.ts +142 -0
- package/dist/types/core/conversation/ratchet.d.ts +95 -0
- package/dist/types/core/conversation/seal.d.ts +47 -0
- package/dist/types/core/delegation.d.ts +71 -0
- package/dist/types/core/directory/device-log.d.ts +119 -0
- package/dist/types/core/directory/epoch.d.ts +53 -0
- package/dist/types/core/directory/handle.d.ts +11 -0
- package/dist/types/core/directory/jelly-device.d.ts +88 -0
- package/dist/types/core/directory/jelly-issuer.d.ts +8 -0
- package/dist/types/core/directory/jelly-token.d.ts +116 -0
- package/dist/types/core/directory/keys-statement.d.ts +22 -0
- package/dist/types/core/directory/link.d.ts +36 -0
- package/dist/types/core/directory/merkle-log.d.ts +40 -0
- package/dist/types/core/directory/person-view.d.ts +90 -0
- package/dist/types/core/directory/person.d.ts +8 -0
- package/dist/types/core/directory/realm.d.ts +23 -0
- package/dist/types/core/directory/smt.d.ts +52 -0
- package/dist/types/core/envelope.d.ts +68 -0
- package/dist/types/core/hash.d.ts +2 -0
- package/dist/types/core/head.d.ts +34 -0
- package/dist/types/core/identity.d.ts +35 -0
- package/dist/types/core/index.d.ts +45 -0
- package/dist/types/core/jcs.d.ts +12 -0
- package/dist/types/core/jelly-host-double.d.ts +21 -0
- package/dist/types/core/jelly-host.d.ts +86 -0
- package/dist/types/core/keys.d.ts +16 -0
- package/dist/types/core/peer-text.d.ts +87 -0
- package/dist/types/core/realm.d.ts +36 -0
- package/dist/types/core/roster.d.ts +146 -0
- package/dist/types/core/schema/a2a.d.ts +251 -0
- package/dist/types/core/schema/common.d.ts +328 -0
- package/dist/types/core/schema/consent.d.ts +140 -0
- package/dist/types/core/schema/conversation.d.ts +1657 -0
- package/dist/types/core/schema/delegation.d.ts +58 -0
- package/dist/types/core/schema/directory-frames.d.ts +1301 -0
- package/dist/types/core/schema/directory.d.ts +375 -0
- package/dist/types/core/schema/envelope.d.ts +756 -0
- package/dist/types/core/schema/errors.d.ts +142 -0
- package/dist/types/core/schema/index.d.ts +16 -0
- package/dist/types/core/schema/ipc/consent.d.ts +555 -0
- package/dist/types/core/schema/ipc/device.d.ts +735 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +501 -0
- package/dist/types/core/schema/ipc/shapes.d.ts +414 -0
- package/dist/types/core/schema/ipc/surfaces.d.ts +680 -0
- package/dist/types/core/schema/ipc.d.ts +3146 -0
- package/dist/types/core/schema/jelly.d.ts +160 -0
- package/dist/types/core/schema/realm.d.ts +38 -0
- package/dist/types/core/schema/relay.d.ts +1134 -0
- package/dist/types/core/schema/roster.d.ts +347 -0
- package/dist/types/core/sign.d.ts +13 -0
- package/dist/types/core/task.d.ts +6 -0
- package/dist/types/core/testing.d.ts +19 -0
- package/dist/types/core/ulid.d.ts +5 -0
- package/dist/types/core/version.d.ts +6 -0
- package/dist/types/core/wire.d.ts +14 -0
- package/dist/types/ipc/client.d.ts +85 -0
- package/dist/types/ipc/framing.d.ts +12 -0
- package/dist/types/ipc/index.d.ts +6 -0
- package/dist/types/ipc/paths.d.ts +39 -0
- package/dist/types/ipc/pipes.d.ts +16 -0
- package/dist/types/ipc/rpc.d.ts +13 -0
- package/dist/types/ipc/server.d.ts +94 -0
- package/dist/types/ipc/testing.d.ts +89 -0
- package/dist/types/ipc/trust.d.ts +49 -0
- package/dist/types/ipc/windows-acl.d.ts +56 -0
- package/dist/types/testing/jelly-id.d.ts +518 -0
- package/dist/types/zod.d.ts +1 -0
- package/package.json +77 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Identity } from "./identity.js";
|
|
2
|
+
import type { RealmView } from "./realm.js";
|
|
3
|
+
import { type Hash, type MachineId, type SessionAddress, type Ulid } from "./schema/common.js";
|
|
4
|
+
import type { RealmId } from "./schema/realm.js";
|
|
5
|
+
import { DelegationGrant, type DelegationSummary } from "./schema/delegation.js";
|
|
6
|
+
/** SHA-256 of the JCS form of the full grant, including `sig`: the next grant's `parent`. */
|
|
7
|
+
export declare function grantHash(grant: DelegationGrant): Hash;
|
|
8
|
+
export interface GrantArgs {
|
|
9
|
+
realm: RealmId;
|
|
10
|
+
task: Ulid;
|
|
11
|
+
/** The delegating session, on the signing machine. */
|
|
12
|
+
from: SessionAddress;
|
|
13
|
+
to: SessionAddress;
|
|
14
|
+
/** The grant this one extends: the last grant of the chain. Absent for a root. */
|
|
15
|
+
parent?: DelegationGrant;
|
|
16
|
+
/** Default: the parent's scope, or unrestricted for a root. Never wider than the parent's (`forbidden`). */
|
|
17
|
+
scope?: readonly string[];
|
|
18
|
+
/** Default: the parent's maxDepth, or `LIMITS.delegationDepth` for a root. Never above the parent's (`forbidden`). */
|
|
19
|
+
maxDepth?: number;
|
|
20
|
+
/** Default and maximum `LIMITS.ttlMs`. A grant never outlives its parent. */
|
|
21
|
+
ttlMs?: number;
|
|
22
|
+
now?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Signs one grant with the machine key (spec §6.7): a root, or the next hop after `parent`. The new grant
|
|
26
|
+
* may narrow the parent's scope and maxDepth, never widen them, and expires no later than the parent.
|
|
27
|
+
* It does not check the chain as a whole; run `verifyChain` on the extended chain for that.
|
|
28
|
+
* Throws only MessagingError: `invalid_request` for bad arguments, `forbidden` for a widening, `expired`
|
|
29
|
+
* for a parent that has expired.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createGrant(identity: Identity, args: GrantArgs): DelegationGrant;
|
|
32
|
+
/** The hop a chain arrived on: the envelope that carried it and the task it requests. */
|
|
33
|
+
export interface ChainHop {
|
|
34
|
+
/** The envelope's `from.device`. */
|
|
35
|
+
by: MachineId;
|
|
36
|
+
/** The sender's address, `person/session@label`. */
|
|
37
|
+
from: SessionAddress;
|
|
38
|
+
/** The addressed session on this machine. */
|
|
39
|
+
to: SessionAddress;
|
|
40
|
+
/** The task id of the request. */
|
|
41
|
+
task: Ulid;
|
|
42
|
+
}
|
|
43
|
+
export interface ChainContext {
|
|
44
|
+
/** The locally verified realm: a `Roster` for a roster realm. */
|
|
45
|
+
realm: RealmView;
|
|
46
|
+
now: number;
|
|
47
|
+
hop: ChainHop;
|
|
48
|
+
}
|
|
49
|
+
export interface VerifiedChain {
|
|
50
|
+
/** The raw grants, unknown fields included: store them to extend the chain later. */
|
|
51
|
+
grants: DelegationGrant[];
|
|
52
|
+
summary: DelegationSummary;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verifies a delegation chain for the hop it arrived on (spec §6.7), against the locally verified realm.
|
|
56
|
+
* Checks, in order, stopping at the first failure:
|
|
57
|
+
*
|
|
58
|
+
* 1. schema, I-JSON and 1…`LIMITS.delegationDepth` grants (`invalid_request`); JCS within
|
|
59
|
+
* `LIMITS.delegationBytes` (`too_large`);
|
|
60
|
+
* 2. for each grant, root first: this realm (`invalid_request`); the time window, as for envelopes
|
|
61
|
+
* (`expired`); `by` a current member whose person and label `from` names (`not_member`); the
|
|
62
|
+
* signature over the raw grant (`bad_signature`); linkage, no `parent` on the root and `from = previous.to`,
|
|
63
|
+
* `parent = grantHash(previous)` after it (`invalid_request`); narrowing of scope, maxDepth and exp
|
|
64
|
+
* (`forbidden`);
|
|
65
|
+
* 3. at most the smallest maxDepth grants (`forbidden`);
|
|
66
|
+
* 4. no session twice (`forbidden`);
|
|
67
|
+
* 5. the last grant is for this hop (`forbidden`).
|
|
68
|
+
*
|
|
69
|
+
* Throws only MessagingError, and never quotes the chain.
|
|
70
|
+
*/
|
|
71
|
+
export declare function verifyChain(raw: unknown, ctx: ChainContext): VerifiedChain;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A hosted person's device log (spec §15.3): an append-only, hash-chained list of signed entries that binds,
|
|
3
|
+
* attests and unbinds the person's devices. `DeviceLog` replays and extends it, enforcing every authority rule;
|
|
4
|
+
* the directory and every client run the same checks. Binding freshness against the epoch log (an entry's
|
|
5
|
+
* first epoch) is `verifyPersonView`'s, which knows the epochs.
|
|
6
|
+
*/
|
|
7
|
+
import type { KeyObject } from "node:crypto";
|
|
8
|
+
import { Identity } from "../identity.js";
|
|
9
|
+
import { type Hash, type HostedPersonId } from "../schema/common.js";
|
|
10
|
+
import { LogEntry, type DeviceId, type DeviceKind, type UnbindReason } from "../schema/directory.js";
|
|
11
|
+
import type { RealmId } from "../schema/realm.js";
|
|
12
|
+
import { type JellyKey, type JellyToken } from "./jelly-token.js";
|
|
13
|
+
/** How strongly a device is vouched for: by Jelly ID, or by approval from a Jelly-vouched device. */
|
|
14
|
+
export type Vouch = "jelly" | "device";
|
|
15
|
+
/** A device as its person's log has it. */
|
|
16
|
+
export interface DeviceRecord {
|
|
17
|
+
readonly device: DeviceId;
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly kind: DeviceKind;
|
|
20
|
+
readonly vouch: Vouch;
|
|
21
|
+
/** The `ts` of its `bind`. */
|
|
22
|
+
readonly since: number;
|
|
23
|
+
/** The approver of a device bound by link. */
|
|
24
|
+
readonly approver?: DeviceId | undefined;
|
|
25
|
+
/** The Jelly host id of its latest Jelly token, for a Jelly-vouched device. */
|
|
26
|
+
readonly host?: string | undefined;
|
|
27
|
+
/** False once unbound. */
|
|
28
|
+
readonly current: boolean;
|
|
29
|
+
readonly unbound?: {
|
|
30
|
+
readonly ts: number;
|
|
31
|
+
readonly reason: UnbindReason;
|
|
32
|
+
readonly by: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The person's profile, from the newest Jelly token of the log (spec §15.3): Jelly-signed claims. */
|
|
36
|
+
export interface JellyProfile {
|
|
37
|
+
readonly iss: string;
|
|
38
|
+
readonly sub: string;
|
|
39
|
+
readonly name?: string | undefined;
|
|
40
|
+
/** Only when Jelly ID proved it (`email_verified`). */
|
|
41
|
+
readonly email?: string | undefined;
|
|
42
|
+
/** The `ts` of the entry that carried it. */
|
|
43
|
+
readonly at: number;
|
|
44
|
+
}
|
|
45
|
+
export interface DeviceLogContext {
|
|
46
|
+
readonly realm: RealmId;
|
|
47
|
+
readonly person: HostedPersonId;
|
|
48
|
+
/** The realm's Jelly issuer and resource, and the keys `JellyKeyring.prepare` found for the log's tokens. */
|
|
49
|
+
readonly jelly: {
|
|
50
|
+
readonly issuer: string;
|
|
51
|
+
readonly audience: string;
|
|
52
|
+
readonly key: (kid: string) => JellyKey | undefined;
|
|
53
|
+
};
|
|
54
|
+
/** The directory's epoch keys, current and past: the only signers of a `service` unbind. */
|
|
55
|
+
readonly directoryKeys: readonly string[];
|
|
56
|
+
}
|
|
57
|
+
export interface AppendOptions {
|
|
58
|
+
/** The directory's clock when it accepts an append: `ts` must be within the clock skew of it (`expired`). */
|
|
59
|
+
readonly now?: number | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** `SHA-256(JCS(entry))`, the signature included. */
|
|
62
|
+
export declare function logEntryHash(entry: LogEntry): Hash;
|
|
63
|
+
/** A log entry without its signer and signature, as a caller builds it. */
|
|
64
|
+
export type UnsignedLogEntry = Omit<LogEntry, "by" | "sig">;
|
|
65
|
+
/**
|
|
66
|
+
* Signs an entry: `by` becomes the signer's public key, `sig` its signature over JCS(entry without sig). A
|
|
67
|
+
* device signs with its `Identity`; the directory signs the unbinds of an erased account with its epoch key.
|
|
68
|
+
* Throws `invalid_request`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function signLogEntry(signer: Identity | KeyObject, entry: UnsignedLogEntry): LogEntry;
|
|
71
|
+
/**
|
|
72
|
+
* One person's device log, verified entry by entry from `seq` 0. A failed `append` changes nothing. Errors:
|
|
73
|
+
* `invalid_request` (schema, size), `log_conflict` (not on the head), `device_limit` (an eleventh device),
|
|
74
|
+
* `expired` (with `now`), and `binding_invalid` for every other rule and signature.
|
|
75
|
+
*/
|
|
76
|
+
export declare class DeviceLog {
|
|
77
|
+
#private;
|
|
78
|
+
readonly realm: RealmId;
|
|
79
|
+
readonly person: HostedPersonId;
|
|
80
|
+
constructor(realm: RealmId, person: HostedPersonId);
|
|
81
|
+
/** A copy that appends independently of this log. Entries are not re-verified. */
|
|
82
|
+
clone(): DeviceLog;
|
|
83
|
+
/** Replays `entries` from `seq` 0. */
|
|
84
|
+
static fromEntries(entries: readonly unknown[], context: DeviceLogContext): DeviceLog;
|
|
85
|
+
get length(): number;
|
|
86
|
+
/** The head `{seq, hash}`, or undefined for an empty log. */
|
|
87
|
+
head(): {
|
|
88
|
+
seq: number;
|
|
89
|
+
hash: Hash;
|
|
90
|
+
} | undefined;
|
|
91
|
+
/** The hash of entry `seq`. */
|
|
92
|
+
hashAt(seq: number): Hash | undefined;
|
|
93
|
+
entries(): readonly LogEntry[];
|
|
94
|
+
/** Every device the log has bound, current or not. */
|
|
95
|
+
allDevices(): DeviceRecord[];
|
|
96
|
+
/** The current devices. */
|
|
97
|
+
devices(): DeviceRecord[];
|
|
98
|
+
device(device: DeviceId): DeviceRecord | undefined;
|
|
99
|
+
/** The device that replaced `device` (an `unbind` with reason `replaced`), if it did and is current (spec §15.3). */
|
|
100
|
+
successor(device: DeviceId): DeviceId | undefined;
|
|
101
|
+
/** The profile from the newest Jelly token in the log. */
|
|
102
|
+
profile(): JellyProfile | undefined;
|
|
103
|
+
/** The verified Jelly token of entry `seq`, for an entry with a `jelly` authority. */
|
|
104
|
+
token(seq: number): JellyToken | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* A `bind` or `attest` of this log carried a sign-in's token (`src: "device"`) with this `jti`: that token
|
|
107
|
+
* vouched for its one act and vouches for nothing else (spec §15.3), an erasure included.
|
|
108
|
+
*/
|
|
109
|
+
spentSignIn(jti: string): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The latest time at which entry `seq` may first appear in an epoch: its Jelly token's `exp` plus the clock
|
|
112
|
+
* skew, in ms. Undefined for an entry with no `jelly` authority.
|
|
113
|
+
*/
|
|
114
|
+
bindingDeadline(seq: number): number | undefined;
|
|
115
|
+
/** Checks `entry` as the next entry and appends it. */
|
|
116
|
+
append(entry: unknown, context: DeviceLogContext, options?: AppendOptions): LogEntry;
|
|
117
|
+
/** Checks `entry` as the next entry without appending it. */
|
|
118
|
+
check(entry: unknown, context: DeviceLogContext, options?: AppendOptions): LogEntry;
|
|
119
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The epoch log's signed tree heads (spec §15.6): signing and verifying STHs and epoch-key rotations, proving
|
|
3
|
+
* an epoch leaf into an STH, and the checks a client makes before it adopts a newer STH (§15.6, "Adopting").
|
|
4
|
+
*/
|
|
5
|
+
import type { KeyObject } from "node:crypto";
|
|
6
|
+
import { type Hash } from "../schema/common.js";
|
|
7
|
+
import { EpochLeaf, EpochProof, KeyRotation, STH } from "../schema/directory.js";
|
|
8
|
+
import type { EpochAnchor, RealmId } from "../schema/realm.js";
|
|
9
|
+
/** The RFC 9162 leaf hash of an epoch leaf: `SHA-256(0x00 ‖ UTF-8(JCS(leaf)))`. */
|
|
10
|
+
export declare function epochLeafHash(leaf: EpochLeaf): Hash;
|
|
11
|
+
/** An STH signed with the directory's epoch key. Throws `invalid_request`. */
|
|
12
|
+
export declare function signSth(key: KeyObject, head: {
|
|
13
|
+
realm: RealmId;
|
|
14
|
+
size: number;
|
|
15
|
+
root: Hash;
|
|
16
|
+
ts: number;
|
|
17
|
+
}): STH;
|
|
18
|
+
/** True when `sth` is signed by `key`. Never throws. */
|
|
19
|
+
export declare function verifySthSignature(sth: STH, key: string): boolean;
|
|
20
|
+
/** The anchor an envelope carries for this STH. */
|
|
21
|
+
export declare function epochAnchor(sth: STH): EpochAnchor;
|
|
22
|
+
/** A rotation of the epoch key from `oldKey` to `newKey`, signed by both, from `fromSize` on. Throws `invalid_request`. */
|
|
23
|
+
export declare function signKeyRotation(oldKey: KeyObject, newKey: KeyObject, args: {
|
|
24
|
+
realm: RealmId;
|
|
25
|
+
fromSize: number;
|
|
26
|
+
}): KeyRotation;
|
|
27
|
+
/**
|
|
28
|
+
* Checks that `proof` puts its leaf at its index in the tree of `sth` (the leaf's `epoch` is its index). Throws
|
|
29
|
+
* `invalid_request` for a malformed proof and `directory_inconsistent` when it does not prove the leaf.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyEpochProof(sth: STH, proof: unknown): EpochProof;
|
|
32
|
+
/** What a client holds of the epoch log: the STH it adopted and the epoch key it trusts. */
|
|
33
|
+
export interface AdoptedHead {
|
|
34
|
+
readonly sth: STH;
|
|
35
|
+
readonly key: string;
|
|
36
|
+
}
|
|
37
|
+
export interface AdoptArgs {
|
|
38
|
+
readonly realm: RealmId;
|
|
39
|
+
/** The STH to adopt. */
|
|
40
|
+
readonly next: unknown;
|
|
41
|
+
/** RFC 9162 consistency proof from the adopted STH's size to `next`'s. */
|
|
42
|
+
readonly consistency?: readonly Hash[] | undefined;
|
|
43
|
+
/** Rotation statements from the trusted key to the key that signed `next`, oldest first. */
|
|
44
|
+
readonly rotations?: readonly unknown[] | undefined;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The checks before a client adopts a newer STH (spec §15.6): the schema (`invalid_request`); the realm; the
|
|
48
|
+
* signature by the trusted key, or by a key that rotation statements reach from it; `size` and `ts` not below
|
|
49
|
+
* the adopted STH's; and the consistency proof from the adopted STH (equal sizes: equal roots). Every other
|
|
50
|
+
* failure is `directory_inconsistent`. `current` is undefined for the first STH, which only the pinned key
|
|
51
|
+
* (`pinnedKey`) may sign, or a key its rotations reach.
|
|
52
|
+
*/
|
|
53
|
+
export declare function adoptSth(current: AdoptedHead | undefined, pinnedKey: string, args: AdoptArgs): AdoptedHead;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lower-case the local part, map every run of characters outside `[a-z0-9]` to `-`, trim `-`, and, when the
|
|
3
|
+
* result does not start with a letter, prefix `u-`; then cut to 32 characters and trim a trailing `-`. An empty
|
|
4
|
+
* result is `u`. The result always matches `[a-z][a-z0-9-]{0,31}`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hostedHandle(email: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* The org of a verified email until Jelly contract v1.2 (spec §15.10): `domain:<d>` when its domain `d` is one of
|
|
9
|
+
* the realm's `orgDomains`, otherwise none (an individual).
|
|
10
|
+
*/
|
|
11
|
+
export declare function emailOrg(email: string | undefined, orgDomains: readonly string[]): string | undefined;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { MachineId } from "../schema/common.js";
|
|
2
|
+
/** RFC 8628's device grant type. */
|
|
3
|
+
export declare const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
4
|
+
/** RFC 8628 §3.5, v1.2 §12.3: `slow_down` adds 5 s to the interval. The interval is kept within [1, 60] s. */
|
|
5
|
+
export declare const LOGIN_SLOW_DOWN_S = 5;
|
|
6
|
+
export declare const LOGIN_INTERVAL_MAX_S = 60;
|
|
7
|
+
/** The most bytes of an answer from Jelly ID that the daemon reads. */
|
|
8
|
+
export declare const JELLY_ANSWER_BYTES = 65536;
|
|
9
|
+
/**
|
|
10
|
+
* The key's fingerprint as Jelly ID's consent page shows it (v1.2 §12.2, item 3): the first six bytes of the RFC 7638
|
|
11
|
+
* thumbprint of `{kty: "OKP", crv: "Ed25519", x}` (SHA-256 over its members in that RFC's order), in lower-case hex,
|
|
12
|
+
* in three groups of four.
|
|
13
|
+
*/
|
|
14
|
+
export declare function jellyKeyFingerprint(device: MachineId): string;
|
|
15
|
+
/** Where a device signs in, read from Jelly ID's metadata. */
|
|
16
|
+
export interface DeviceSignIn {
|
|
17
|
+
/** `jelly_resource_device_authorization_endpoint`. */
|
|
18
|
+
readonly codeEndpoint: string;
|
|
19
|
+
/** `jelly_resource_device_token_endpoint`. */
|
|
20
|
+
readonly tokenEndpoint: string;
|
|
21
|
+
/** The resource's `name` in `jelly_resources`: the device client's `client_id`. */
|
|
22
|
+
readonly clientId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reads device sign-in from Jelly ID's RFC 8414 metadata (`<issuer>/.well-known/oauth-authorization-server`, as
|
|
26
|
+
* text). It needs the two members of v1.2 §12 (`jelly_resource_device_authorization_endpoint`, `…_token_endpoint`), both on the issuer's own origin, and the realm's resource in
|
|
27
|
+
* `jelly_resources`. It never takes RFC 8628's `device_authorization_endpoint`: at Jelly ID that route enrols hosts.
|
|
28
|
+
* Throws `login_unsupported` (Jelly ID lacks it) or `jelly_unavailable` (an answer that is not metadata for `issuer`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function readDeviceSignIn(text: string, realm: {
|
|
31
|
+
readonly issuer: string;
|
|
32
|
+
readonly resource: string;
|
|
33
|
+
}): DeviceSignIn;
|
|
34
|
+
/** Where the person signs in and what they type there. Never a link with the code in it. */
|
|
35
|
+
export interface DeviceCodeAnswer {
|
|
36
|
+
readonly deviceCode: string;
|
|
37
|
+
readonly userCode: string;
|
|
38
|
+
/** Jelly ID's `/link` page, where the person types `userCode`. */
|
|
39
|
+
readonly verificationUri: string;
|
|
40
|
+
readonly expiresInS: number;
|
|
41
|
+
readonly intervalS: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads Jelly ID's answer to the code request (v1.2 §12.1). Every refusal Jelly ID defines throws in the house style:
|
|
45
|
+
* - `429 slow_down` (too many codes from this address, or waiting for this client): `rate_limited`;
|
|
46
|
+
* - `400 invalid_target` with `detail: "invalid_cnf_jwk"` (Jelly would not bind this key): `binding_invalid`;
|
|
47
|
+
* - `400 invalid_target` (not this client's resource), `401 invalid_client` (not a device client, or device sign-in
|
|
48
|
+
* off for it): `login_unsupported`;
|
|
49
|
+
* - `400 invalid_request` (a field missing, repeated or malformed): `invalid_request`;
|
|
50
|
+
* - `503 temporarily_unavailable`, and anything Jelly ID does not define: `jelly_unavailable`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readDeviceCodeAnswer(status: number, text: string, issuer: string): DeviceCodeAnswer;
|
|
53
|
+
/** What a poll of the token route says (RFC 8628 §3.5; v1.2 §12.3). */
|
|
54
|
+
export type DevicePoll = {
|
|
55
|
+
readonly kind: "token";
|
|
56
|
+
readonly token: string;
|
|
57
|
+
} | {
|
|
58
|
+
readonly kind: "pending";
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: "slow_down";
|
|
61
|
+
}
|
|
62
|
+
/** Jelly ID could not answer now (a 5xx, a timeout, an answer that is not one): keep polling until the code expires. */
|
|
63
|
+
| {
|
|
64
|
+
readonly kind: "retry";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Reads a poll's answer (v1.2 §12.3). `authorization_pending` and `slow_down` keep polling. A final refusal throws:
|
|
68
|
+
* `login_denied` (`access_denied`: the person denied it, or the account is disabled or being deleted), `expired`
|
|
69
|
+
* (`expired_token`: the code's time passed, decided or not; or `invalid_grant`: the code was used, or Jelly ID no
|
|
70
|
+
* longer knows it), `login_unsupported` (`invalid_client`, `unsupported_grant_type`). `503 temporarily_unavailable`,
|
|
71
|
+
* and any answer Jelly ID does not define, is `retry`: the daemon polls on until the code expires.
|
|
72
|
+
*/
|
|
73
|
+
export declare function readDevicePoll(status: number, text: string): DevicePoll;
|
|
74
|
+
/** The form of the code request (v1.2 §12.1). */
|
|
75
|
+
export declare function deviceCodeForm(args: {
|
|
76
|
+
clientId: string;
|
|
77
|
+
resource: string;
|
|
78
|
+
device: MachineId;
|
|
79
|
+
label: string;
|
|
80
|
+
kind: string;
|
|
81
|
+
}): URLSearchParams;
|
|
82
|
+
/** The form of a poll (v1.2 §12.3). */
|
|
83
|
+
export declare function devicePollForm(args: {
|
|
84
|
+
clientId: string;
|
|
85
|
+
deviceCode: string;
|
|
86
|
+
}): URLSearchParams;
|
|
87
|
+
/** For tests of the fingerprint's preimage: the RFC 7638 thumbprint, base64url. */
|
|
88
|
+
export declare function jellyKeyThumbprint(device: MachineId): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Jelly ID issuer double, for tests and the vectors only (exported from `@messaging/core/testing`): it signs a
|
|
3
|
+
* resource token as Jelly ID does (contract v1.1 §4). A JWS signs its own base64url text, not JCS, so this is
|
|
4
|
+
* Jelly's format and not a Messaging signature.
|
|
5
|
+
*/
|
|
6
|
+
import { type KeyObject } from "node:crypto";
|
|
7
|
+
/** A compact JWS over `header` and `claims` as given (members in their insertion order), signed with `key`. */
|
|
8
|
+
export declare function signJellyToken(key: KeyObject, header: Record<string, unknown>, claims: Record<string, unknown>): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { MachineId } from "../schema/common.js";
|
|
2
|
+
import { ArchivedJellyKey, CnfJwk, JellyClaims, JellyTokenHeader } from "../schema/jelly.js";
|
|
3
|
+
/** Contract v1.1 §5 step 5: a token lives at most 900 s, and its `iat` may be at most 60 s ahead of now. */
|
|
4
|
+
export declare const JELLY_MAX_LIFETIME_S = 900;
|
|
5
|
+
export declare const JELLY_IAT_LEEWAY_S = 60;
|
|
6
|
+
/** Contract v1.1 §5 step 2: Jelly's key set is believed for 24 h; the key history for 5 min. */
|
|
7
|
+
export declare const JELLY_JWKS_TTL_MS: number;
|
|
8
|
+
export declare const JELLY_HISTORY_TTL_MS: number;
|
|
9
|
+
/** An archived key's revocation is rechecked against the key history when older than this (spec §15.2). */
|
|
10
|
+
export declare const JELLY_REVOCATION_CHECK_MS: number;
|
|
11
|
+
/** An unknown `kid` refetches a key set at most this often, so a stream of unknown `kid`s cannot loop. */
|
|
12
|
+
export declare const JELLY_REFETCH_MIN_MS: number;
|
|
13
|
+
/** A decoded, not yet verified, resource token. */
|
|
14
|
+
export interface JellyToken {
|
|
15
|
+
readonly header: JellyTokenHeader;
|
|
16
|
+
readonly claims: JellyClaims;
|
|
17
|
+
/** `header.payload`, the bytes the signature covers. */
|
|
18
|
+
readonly signingInput: Uint8Array;
|
|
19
|
+
readonly signature: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
/** A Jelly key with what the key history says of it; a key seen only in the key set has no window. */
|
|
22
|
+
export interface JellyKey {
|
|
23
|
+
readonly kid: string;
|
|
24
|
+
readonly x: string;
|
|
25
|
+
/** Seconds: first published (key history). */
|
|
26
|
+
readonly nbf?: number | undefined;
|
|
27
|
+
/** Seconds: dropped from the key set; null while published (key history). */
|
|
28
|
+
readonly retiredAt?: number | null | undefined;
|
|
29
|
+
/** Seconds: revoked by an operator (key history). */
|
|
30
|
+
readonly revokedAt?: number | null | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Decodes a compact resource token: three base64url segments, a header with `alg: EdDSA`, `typ:
|
|
34
|
+
* jelly-resource+jwt` and a printable `kid`, and claims of the contract's shape (contract v1.1 §4, §5 step 1;
|
|
35
|
+
* `cnf`, when present, exactly `{jwk: {kty, crv, x}}`). Verifies nothing. Throws `binding_invalid`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function decodeJellyToken(token: string): JellyToken;
|
|
38
|
+
/**
|
|
39
|
+
* True when a key may have signed a token issued at `iat` (seconds): always for a key seen only in the key set;
|
|
40
|
+
* for a key the history describes, `iat ∈ [nbf, retired_at)` (an open end while `retired_at` is null) and
|
|
41
|
+
* before any `revoked_at` (contract v1.1 §5 step 2, §11).
|
|
42
|
+
*/
|
|
43
|
+
export declare function jellyKeyCovers(key: JellyKey, iat: number): boolean;
|
|
44
|
+
/** True when `x` is a key Jelly ID would bind (contract v1.1 §3): a canonical Ed25519 point not of small order. */
|
|
45
|
+
export declare function isBindableKey(x: string): boolean;
|
|
46
|
+
/** The `cnf_jwk` a host asks Jelly ID to bind for `device` (contract v1.1 §3, §6). Throws `invalid_request` for an unusable key. */
|
|
47
|
+
export declare function cnfJwk(device: MachineId): CnfJwk;
|
|
48
|
+
export interface JellyVerifyOptions {
|
|
49
|
+
/** The realm's issuer and resource, compared exactly with `iss` and `aud`. */
|
|
50
|
+
readonly issuer: string;
|
|
51
|
+
readonly audience: string;
|
|
52
|
+
/** The key with this `kid`, as `JellyKeyring.prepare` found it; undefined when no source knows it. */
|
|
53
|
+
readonly key: (kid: string) => JellyKey | undefined;
|
|
54
|
+
/** The device the token must be bound to (`cnf.jwk.x`). Without it, `cnf` is checked for shape only. */
|
|
55
|
+
readonly cnf?: MachineId | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Now, in milliseconds, to check a token as it is presented (contract v1.1 §5 step 5: `exp` in the future, `iat`
|
|
58
|
+
* at most 60 s ahead). Omit it to check a token kept in a log, whose freshness is judged at its append (§15.3).
|
|
59
|
+
*/
|
|
60
|
+
readonly now?: number | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Verifies a resource token (contract v1.1 §5; spec §15.2 steps 1–6): the header, the key and its window, the
|
|
64
|
+
* signature, `iss` and `aud`, the lifetime, `sub`, how it was minted (`hst`, or `src: "device"` with no `hst`), and
|
|
65
|
+
* `cnf`. Returns the decoded token. Throws
|
|
66
|
+
* `binding_invalid`; never reports a missing key as anything else (finding it is `JellyKeyring`'s).
|
|
67
|
+
*/
|
|
68
|
+
export declare function verifyJellyToken(token: string, options: JellyVerifyOptions): JellyToken;
|
|
69
|
+
export interface JellyKeySources {
|
|
70
|
+
/** `<issuer>/.well-known/jwks.json`, as text. Rejects when it cannot be fetched. */
|
|
71
|
+
readonly fetchJwks: () => Promise<string>;
|
|
72
|
+
/** `<issuer>/.well-known/jwks-history.json`, as text. Rejects when it cannot be fetched. */
|
|
73
|
+
readonly fetchHistory: () => Promise<string>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Finds the keys of resource tokens, in the order of spec §15.2: the client's own archive of every key it has
|
|
77
|
+
* verified, then Jelly ID's key set (cached 24 h; an unknown `kid` refetches it at most once a minute), then the
|
|
78
|
+
* key history (cached 5 min). A key found in the key set or the history joins the archive, with the history's
|
|
79
|
+
* window. An archived key whose revocation was last checked over 24 h ago is checked again, and keeps its archived
|
|
80
|
+
* window if the history cannot be fetched. Persist `archive()`; pass it back to the constructor.
|
|
81
|
+
*/
|
|
82
|
+
export declare class JellyKeyring {
|
|
83
|
+
#private;
|
|
84
|
+
constructor(sources: JellyKeySources, archive?: readonly ArchivedJellyKey[]);
|
|
85
|
+
/** Every key this client has verified, to persist. */
|
|
86
|
+
archive(): ArchivedJellyKey[];
|
|
87
|
+
/** The archived key with this `kid`, without fetching. */
|
|
88
|
+
archived(kid: string): JellyKey | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Finds the keys of these tokens (a token that does not decode is skipped: verifying it fails anyway) and
|
|
91
|
+
* returns the synchronous lookup `verifyJellyToken` takes. Throws `jelly_unavailable` when a `kid` is in no
|
|
92
|
+
* source and a source it needed could not be fetched: never `binding_invalid` for an unreachable Jelly ID.
|
|
93
|
+
*/
|
|
94
|
+
prepare(tokens: readonly string[], now: number): Promise<(kid: string) => JellyKey | undefined>;
|
|
95
|
+
/** Finds one key (see the class). Returns undefined when every source answered and none knows it. */
|
|
96
|
+
resolve(kid: string, now: number): Promise<JellyKey | undefined>;
|
|
97
|
+
}
|
|
98
|
+
/** What a refusal to mint a bound token means for the daemon (contract v1.1 §3, §6, and Jelly's change of 2026-11-05). */
|
|
99
|
+
export type JellyMintRefusal =
|
|
100
|
+
/** `invalid_target` with `detail: "invalid_cnf_jwk"`: Jelly would not bind this key. A daemon bug: alarm, never retry. */
|
|
101
|
+
"unbindable_key"
|
|
102
|
+
/** `invalid_target` without a detail: the realm's Jelly ID offers no such resource. Say so in status; do not retry. */
|
|
103
|
+
| "no_resource"
|
|
104
|
+
/** `access_denied`: the account is disabled or being deleted. Final. */
|
|
105
|
+
| "denied"
|
|
106
|
+
/** `not_enrolled` or `invalid_client`: this host is not (or no longer) enrolled. Final until it is. */
|
|
107
|
+
| "not_enrolled"
|
|
108
|
+
/** `invalid_request`: the ask was malformed. A daemon or plugin bug: alarm, never retry. */
|
|
109
|
+
| "bad_request"
|
|
110
|
+
/** `unavailable`, `temporarily_unavailable`, `slow_down` or anything else: hold 30 s, then retry. */
|
|
111
|
+
| "retry";
|
|
112
|
+
/**
|
|
113
|
+
* Reads a refusal from Jelly ID's mint route (`{error, detail?}`) or the engine accessor (an `Error` with
|
|
114
|
+
* `code` and `detail`). Branches on `detail`: a plain `invalid_target` still means no such resource.
|
|
115
|
+
*/
|
|
116
|
+
export declare function jellyMintRefusal(code: string, detail?: string): JellyMintRefusal;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { type Hash, type HostedPersonId } from "../schema/common.js";
|
|
3
|
+
import { KeysStatement } from "../schema/directory.js";
|
|
4
|
+
import type { RealmId } from "../schema/realm.js";
|
|
5
|
+
/** `SHA-256(JCS(statement))`, the signature included: what the map commits to. */
|
|
6
|
+
export declare function keysStatementHash(statement: KeysStatement): Hash;
|
|
7
|
+
/** This device's statement of its current encryption key. Throws `invalid_request`. */
|
|
8
|
+
export declare function createKeysStatement(identity: Identity, args: {
|
|
9
|
+
realm: RealmId;
|
|
10
|
+
person: HostedPersonId;
|
|
11
|
+
seq: number;
|
|
12
|
+
ts: number;
|
|
13
|
+
}): KeysStatement;
|
|
14
|
+
/**
|
|
15
|
+
* Checks a keys statement: the schema and `LIMITS.keysStatementBytes` (`invalid_request`, `too_large`), then
|
|
16
|
+
* the realm, the person, and the device's signature (`binding_invalid`). Whether the device is current, and
|
|
17
|
+
* whether `seq` is above the last one seen, are the caller's (`verifyPersonView` checks both).
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyKeysStatement(statement: unknown, context: {
|
|
20
|
+
realm: RealmId;
|
|
21
|
+
person: HostedPersonId;
|
|
22
|
+
}): KeysStatement;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { type MachineLabel } from "../schema/common.js";
|
|
3
|
+
import { LinkRequest, type DeviceKind } from "../schema/directory.js";
|
|
4
|
+
import type { RealmId } from "../schema/realm.js";
|
|
5
|
+
/** The prefix of a link request's text form. A link is not a secret, but it should not travel through chat. */
|
|
6
|
+
export declare const LINK_PREFIX = "messaging-link.";
|
|
7
|
+
export interface LinkArgs {
|
|
8
|
+
readonly realm: RealmId;
|
|
9
|
+
readonly label: MachineLabel;
|
|
10
|
+
readonly kind: DeviceKind;
|
|
11
|
+
/** 16 random bytes. */
|
|
12
|
+
readonly nonce: Uint8Array;
|
|
13
|
+
readonly ts: number;
|
|
14
|
+
}
|
|
15
|
+
/** The new device's signed link request. Throws `invalid_request`. */
|
|
16
|
+
export declare function createLinkRequest(identity: Identity, args: LinkArgs): LinkRequest;
|
|
17
|
+
/** `messaging-link.` and the base64url of the request's JCS. */
|
|
18
|
+
export declare function encodeLink(link: LinkRequest): string;
|
|
19
|
+
/** Reads a link's text (surrounding whitespace ignored), with `parseWire`. Verifies nothing. Throws `invalid_request`. */
|
|
20
|
+
export declare function decodeLink(text: string): LinkRequest;
|
|
21
|
+
/** True when the request's signature is its device's. Never throws. */
|
|
22
|
+
export declare function verifyLinkSignature(link: LinkRequest): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* What an approving device checks before it shows the request (§15.3): the schema (`invalid_request`), the
|
|
25
|
+
* signature and the realm (`binding_invalid`), and `ts` no older than `LIMITS.linkTtlMs` and no later than the
|
|
26
|
+
* clock skew ahead of `now` (`expired`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyLinkRequest(link: unknown, context: {
|
|
29
|
+
realm: RealmId;
|
|
30
|
+
now: number;
|
|
31
|
+
}): LinkRequest;
|
|
32
|
+
/**
|
|
33
|
+
* The six-digit short authentication string both devices show: the first four bytes of SHA-256(JCS(request)),
|
|
34
|
+
* big-endian, modulo 1,000,000, zero-padded. The person compares the two before approving.
|
|
35
|
+
*/
|
|
36
|
+
export declare function linkSas(link: LinkRequest): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Hash } from "../schema/common.js";
|
|
2
|
+
type Bytes = Uint8Array;
|
|
3
|
+
/** `SHA-256(0x00 ‖ data)`, the hash of a leaf. */
|
|
4
|
+
export declare function merkleLeafHash(data: Bytes): Hash;
|
|
5
|
+
/**
|
|
6
|
+
* The leaf hashes of a log, appended in order. It answers the tree head at any size it holds, and the audit
|
|
7
|
+
* path and consistency proof between sizes (RFC 9162 §2.1.1-§2.1.4). Subtree roots are cached, so a proof
|
|
8
|
+
* costs O(log² n) hashes after the first.
|
|
9
|
+
*/
|
|
10
|
+
export declare class MerkleLog {
|
|
11
|
+
#private;
|
|
12
|
+
/** A log of these leaf hashes (from `merkleLeafHash`), in order. */
|
|
13
|
+
static fromLeafHashes(hashes: readonly Hash[]): MerkleLog;
|
|
14
|
+
get size(): number;
|
|
15
|
+
/** Appends the leaf whose data is `data`; returns its index. */
|
|
16
|
+
append(data: Bytes): number;
|
|
17
|
+
/** Appends a leaf by its hash; returns its index. */
|
|
18
|
+
appendLeafHash(hash: Hash): number;
|
|
19
|
+
/** The root the log would have with the leaf `hash` appended; the log itself does not change. */
|
|
20
|
+
rootWith(hash: Hash): Hash;
|
|
21
|
+
leafHash(index: number): Hash;
|
|
22
|
+
/** MTH of the first `size` leaves (default: all). The empty tree's root is SHA-256 of nothing. */
|
|
23
|
+
root(size?: number): Hash;
|
|
24
|
+
/** The audit path of leaf `index` in the tree of the first `size` leaves, bottom up (RFC 9162 §2.1.3.1). */
|
|
25
|
+
inclusionProof(index: number, size?: number): Hash[];
|
|
26
|
+
/** The consistency proof from the tree of `from` leaves to the tree of `size` leaves (RFC 9162 §2.1.4.1). */
|
|
27
|
+
consistencyProof(from: number, size?: number): Hash[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* True when `proof` is the audit path of the leaf with hash `leafHash` at `index` in a tree of `size` leaves
|
|
31
|
+
* whose root is `root` (RFC 9162 §2.1.3.2). Never throws.
|
|
32
|
+
*/
|
|
33
|
+
export declare function verifyInclusion(index: number, size: number, leafHash: Hash, proof: readonly Hash[], root: Hash): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* True when `proof` shows that the tree of `size1` leaves with root `root1` is a prefix of the tree of `size2`
|
|
36
|
+
* leaves with root `root2` (RFC 9162 §2.1.4.2). Equal sizes need an empty proof and equal roots; a proof from
|
|
37
|
+
* the empty tree proves nothing and is false. Never throws.
|
|
38
|
+
*/
|
|
39
|
+
export declare function verifyConsistency(size1: number, size2: number, proof: readonly Hash[], root1: Hash, root2: Hash): boolean;
|
|
40
|
+
export {};
|