@cef-ai/vault-sdk 3.3.0 → 4.0.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/client.d.ts +26 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +40 -3
- package/dist/client.js.map +1 -1
- package/dist/core/canonical.d.ts +17 -1
- package/dist/core/canonical.d.ts.map +1 -1
- package/dist/core/canonical.js +26 -1
- package/dist/core/canonical.js.map +1 -1
- package/dist/core/endpoints.d.ts +5 -4
- package/dist/core/endpoints.d.ts.map +1 -1
- package/dist/core/endpoints.js +13 -4
- package/dist/core/endpoints.js.map +1 -1
- package/dist/core/grant-doc.d.ts +345 -0
- package/dist/core/grant-doc.d.ts.map +1 -0
- package/dist/core/grant-doc.js +603 -0
- package/dist/core/grant-doc.js.map +1 -0
- package/dist/core/types.d.ts +181 -49
- package/dist/core/types.d.ts.map +1 -1
- package/dist/fluent/vault.d.ts +6 -0
- package/dist/fluent/vault.d.ts.map +1 -1
- package/dist/fluent/vault.js +7 -0
- package/dist/fluent/vault.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/memberships.d.ts +360 -0
- package/dist/internal/memberships.d.ts.map +1 -0
- package/dist/internal/memberships.js +403 -0
- package/dist/internal/memberships.js.map +1 -0
- package/dist/internal/vaults.d.ts +22 -0
- package/dist/internal/vaults.d.ts.map +1 -1
- package/dist/internal/vaults.js +11 -0
- package/dist/internal/vaults.js.map +1 -1
- package/dist/internal-exports.d.ts +2 -1
- package/dist/internal-exports.d.ts.map +1 -1
- package/dist/internal-exports.js +1 -1
- package/dist/internal-exports.js.map +1 -1
- package/package.json +3 -3
- package/dist/internal/orgs.d.ts +0 -59
- package/dist/internal/orgs.d.ts.map +0 -1
- package/dist/internal/orgs.js +0 -80
- package/dist/internal/orgs.js.map +0 -1
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
// src/core/grant-doc.ts
|
|
2
|
+
//
|
|
3
|
+
// The canonical grant document — the bytes a vault owner signs to say "this
|
|
4
|
+
// person holds this role and these scopes on this vault" — and the check that
|
|
5
|
+
// a served signature is theirs.
|
|
6
|
+
//
|
|
7
|
+
// This is the TypeScript mirror of ddc-node's
|
|
8
|
+
// `pkg/compute/vault/grantsig.go` — the implementation moved there from
|
|
9
|
+
// `internal/compute/vault/authz` when the orchestrator became its second
|
|
10
|
+
// reader, and `authz` is now a delegation shim, so the `authz.*` names used
|
|
11
|
+
// throughout these comments all forward to that file. Its whole reason to
|
|
12
|
+
// exist is
|
|
13
|
+
// that `GET /api/v1/vaults/:vaultId/roster/signed` is only worth serving if a
|
|
14
|
+
// party that is NOT vault-api can check it. If every consumer had to
|
|
15
|
+
// re-implement base64 → ed25519 → schema check by hand, most would check the
|
|
16
|
+
// signature and then read the response's own convenience fields, which is a
|
|
17
|
+
// decorative signature: verifying one thing and enforcing another.
|
|
18
|
+
//
|
|
19
|
+
// # Read role and scopes out of the DOCUMENT, never off the response
|
|
20
|
+
//
|
|
21
|
+
// `SignedGrant.role` and `SignedGrant.scopes` are a RENDERING of `doc` that
|
|
22
|
+
// vault-api provides for display. They are deliberately typed as plain
|
|
23
|
+
// strings here, not as `MemberRole`/`ScopeGrants`, so that feeding them
|
|
24
|
+
// straight into code that decides access is awkward rather than natural.
|
|
25
|
+
// `verifySignedRoster` returns `Grant`s decoded from the verified bytes; those
|
|
26
|
+
// are the values to act on.
|
|
27
|
+
//
|
|
28
|
+
// # The document is verified as received, never re-encoded
|
|
29
|
+
//
|
|
30
|
+
// `verifyGrantSig` checks the signature over the exact bytes it was handed.
|
|
31
|
+
// It does NOT rebuild them with `canonicalGrantBytes` and compare: a verifier
|
|
32
|
+
// that re-encodes is checking bytes it just composed itself, and it would
|
|
33
|
+
// start rejecting perfectly valid documents the moment this encoder and the
|
|
34
|
+
// server's drifted by a space. Go's `DecodeGrantDoc` is a reader for the same
|
|
35
|
+
// reason. `canonicalGrantBytes` exists for the SIGNING side (whoever mints a
|
|
36
|
+
// grant must produce bytes vault-api will accept) and for tests.
|
|
37
|
+
//
|
|
38
|
+
// # Mirroring Go's encoder exactly, in the two places it is not obvious
|
|
39
|
+
//
|
|
40
|
+
// 1. `encoding/json` escapes `<`, `>` and `&` as the six-character escapes
|
|
41
|
+
// `\u003c`, `\u003e`, `\u0026`
|
|
42
|
+
// (`SetEscapeHTML` defaults to true), plus U+2028/U+2029. `JSON.stringify`
|
|
43
|
+
// escapes none of them. A scope named `a<b` would otherwise produce
|
|
44
|
+
// different bytes on the two sides and a signature that never verifies.
|
|
45
|
+
// 2. Go sorts scope names as raw UTF-8 byte strings, which is code-point
|
|
46
|
+
// order. JavaScript's `<` compares UTF-16 code units, which disagrees for
|
|
47
|
+
// astral characters: `"😀" < "A"` is true in UTF-16 and false by code
|
|
48
|
+
// point. `compareCodePoints` below sorts the way Go does.
|
|
49
|
+
//
|
|
50
|
+
// 3. `issuedAt` and `expiresAt` are STRINGS carried verbatim, never `Date`s.
|
|
51
|
+
// Go's `encoding/json` would trim trailing zeros off a re-marshalled
|
|
52
|
+
// fraction (`…:56.100Z` → `…:56.1Z`), so neither side may re-format what
|
|
53
|
+
// the other signed. Whatever string the signer chose is what goes in the
|
|
54
|
+
// bytes.
|
|
55
|
+
//
|
|
56
|
+
// 4. `expiresAt` IS ALWAYS PRESENT, and "no expiry" is the EMPTY STRING
|
|
57
|
+
// (`NO_GRANT_EXPIRY`) rather than a dropped key or a `null`. One document
|
|
58
|
+
// shape, always: omitting the field would make a never-expires grant
|
|
59
|
+
// byte-identical to a document minted before the field existed, so every
|
|
60
|
+
// signature already stored would verify as a brand-new unlimited grant.
|
|
61
|
+
// See `NO_GRANT_EXPIRY`.
|
|
62
|
+
//
|
|
63
|
+
// Both are pinned by golden vectors produced by running the real Go function
|
|
64
|
+
// (see `grant-doc.test.ts`), not by reading its source and guessing.
|
|
65
|
+
//
|
|
66
|
+
// # A GRANT DOCUMENT IS AUTHORITY ONLY WHEN YOU FETCHED IT YOURSELF
|
|
67
|
+
//
|
|
68
|
+
// This mirrors `pkg/compute/vault/grantsig.go`'s rule, and it is the reason
|
|
69
|
+
// `verifySignedRoster` takes a whole roster rather than a loose grant.
|
|
70
|
+
//
|
|
71
|
+
// A verified signature proves the vault owner once signed these bytes. It does
|
|
72
|
+
// NOT prove the grant still stands: revocation deletes the membership row and
|
|
73
|
+
// cannot reach back and unsign a document somebody already saved. `issuedAt`
|
|
74
|
+
// does not change that — a timestamp bounds how OLD a document is, never
|
|
75
|
+
// whether it was WITHDRAWN.
|
|
76
|
+
//
|
|
77
|
+
// So: fetch the signed roster yourself, verify it, and act on what it
|
|
78
|
+
// contains. Absence from a freshly fetched roster is what revocation looks
|
|
79
|
+
// like, and it is the only signal that carries it. Accepting a document a
|
|
80
|
+
// caller HANDS you and authorising them on it is wrong in a way that passes
|
|
81
|
+
// every test — a revoked member's saved grant verifies forever — and checking
|
|
82
|
+
// that its `issuedAt` is recent does not fix it, it only narrows the window.
|
|
83
|
+
import * as ed from "@noble/ed25519";
|
|
84
|
+
// The `./blake2` SUBPATH, not the package root. Both work at runtime, but
|
|
85
|
+
// 14.0.3's root barrel does not re-export either blake2 name to
|
|
86
|
+
// TypeScript (`encodeAddress` and the rest come through fine), so the
|
|
87
|
+
// root import compiles nowhere. `./blake2` is a declared export subpath.
|
|
88
|
+
import { blake2AsHex } from "@polkadot/util-crypto/blake2";
|
|
89
|
+
import { decodeBase64, hexToBytes, strip0xHex } from "./canonical.js";
|
|
90
|
+
const enc = new TextEncoder();
|
|
91
|
+
const dec = new TextDecoder();
|
|
92
|
+
/**
|
|
93
|
+
* The schema string carried INSIDE the signed bytes (`authz.GrantSchema`).
|
|
94
|
+
*
|
|
95
|
+
* A v2 changes this string, so a v1 signature can never be replayed as a v2
|
|
96
|
+
* grant — which is why `decodeGrantDoc` refuses any other value instead of
|
|
97
|
+
* handing a v2 document's fields to a v1 reader under a signature that is
|
|
98
|
+
* perfectly valid for the v2 meaning.
|
|
99
|
+
*/
|
|
100
|
+
export const GRANT_SCHEMA = "cere.vault.grant.v1";
|
|
101
|
+
/**
|
|
102
|
+
* The header a membership write carries the vault owner's grant signature in
|
|
103
|
+
* (`authz.GrantSignatureHeader`), hex, with or without an `0x` prefix.
|
|
104
|
+
*
|
|
105
|
+
* A HEADER OF ITS OWN, not the wallet auth's `X-Signature`, because the two
|
|
106
|
+
* cover different bytes: `X-Signature` covers the request BODY and
|
|
107
|
+
* authenticates the CALLER (who may be an admin acting for the owner), while
|
|
108
|
+
* this covers the canonical grant document and attests the OWNER's
|
|
109
|
+
* authorisation of that exact grant. The body of `PUT .../members` is
|
|
110
|
+
* `{"role":…,"timestamp":…}` — the vault id and the member's key are URL
|
|
111
|
+
* parameters and never enter the signed bytes — so one owner-signed
|
|
112
|
+
* `{"role":"admin",…}` would otherwise verify identically for every member of
|
|
113
|
+
* every vault that owner holds.
|
|
114
|
+
*/
|
|
115
|
+
export const GRANT_SIGNATURE_HEADER = "X-Grant-Signature";
|
|
116
|
+
/**
|
|
117
|
+
* The header carrying the `issuedAt` the signed document was minted with
|
|
118
|
+
* (`pkgvault.GrantIssuedAtHeader`).
|
|
119
|
+
*
|
|
120
|
+
* vault-api does not RECEIVE the grant document, it REBUILDS it — from the URL
|
|
121
|
+
* parameters, the body, and the other half read back from its store — and then
|
|
122
|
+
* checks your signature over what it built. Every field must therefore be
|
|
123
|
+
* something the server can reproduce, and a mint time is the one thing it
|
|
124
|
+
* cannot: a server-chosen `now` would never equal yours. So it travels beside
|
|
125
|
+
* the signature.
|
|
126
|
+
*
|
|
127
|
+
* NOT the body's `timestamp`, even though every mutation body already carries
|
|
128
|
+
* one and the wallet middleware already bounds its skew. The signer and the
|
|
129
|
+
* sender are different parties: the route admits an admin, but the document
|
|
130
|
+
* must be signed by the VAULT's wallet, so an admin presents a grant their
|
|
131
|
+
* owner signed out of band. Binding the mint time to the request body would
|
|
132
|
+
* make the owner predict the admin's exact timestamp string, and the skew
|
|
133
|
+
* window would expire their pre-signed grant in five minutes.
|
|
134
|
+
*/
|
|
135
|
+
export const GRANT_ISSUED_AT_HEADER = "X-Grant-Issued-At";
|
|
136
|
+
/**
|
|
137
|
+
* `auth.CanonicalPubKeyHex`, reproduced: `0x`-prefixed lowercase hex, or the
|
|
138
|
+
* input UNCHANGED when it is not hex at all.
|
|
139
|
+
*
|
|
140
|
+
* THE SERVER APPLIES THIS TO THE URL PARAMETER BEFORE IT BUILDS THE DOCUMENT
|
|
141
|
+
* (`memberPubkeyParam`), so a signer that skips it produces a perfectly valid
|
|
142
|
+
* signature over bytes the server never derives — the failure that presents
|
|
143
|
+
* as a key problem rather than a spelling one. Callers have always been
|
|
144
|
+
* allowed to pass a bare or upper-case key (the store canonicalises on the
|
|
145
|
+
* way in), so signing their exact spelling would break the writes that work
|
|
146
|
+
* today.
|
|
147
|
+
*
|
|
148
|
+
* A non-hex value passes through untouched, exactly as Go's does: a malformed
|
|
149
|
+
* key must reach the server as the caller wrote it and be refused there,
|
|
150
|
+
* rather than be silently rewritten into a different key here.
|
|
151
|
+
*/
|
|
152
|
+
export function canonicalPubkeyHex(s) {
|
|
153
|
+
// `normalizeHexKey`: TrimPrefix "0x" THEN TrimPrefix "0X" — one each, in
|
|
154
|
+
// that order — then lower-case. Reproduced literally rather than as one
|
|
155
|
+
// regex so the two implementations can be read side by side.
|
|
156
|
+
let bare = s.startsWith("0x") ? s.slice(2) : s;
|
|
157
|
+
if (bare.startsWith("0X"))
|
|
158
|
+
bare = bare.slice(2);
|
|
159
|
+
bare = bare.toLowerCase();
|
|
160
|
+
// `bare == ""` and a `hex.DecodeString` failure — which includes an
|
|
161
|
+
// ODD-LENGTH string — both return the input unchanged.
|
|
162
|
+
if (bare === "" || bare.length % 2 !== 0 || !/^[0-9a-f]+$/.test(bare))
|
|
163
|
+
return s;
|
|
164
|
+
return `0x${bare}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The vault id a wallet key owns — `pkgvault.IDFor`, reproduced.
|
|
168
|
+
*
|
|
169
|
+
* `"v-"` + blake2b-128 of the key's CANONICAL HEX SPELLING, as 32 lowercase
|
|
170
|
+
* hex characters.
|
|
171
|
+
*
|
|
172
|
+
* # This is a one-way function, and that is the whole point
|
|
173
|
+
*
|
|
174
|
+
* It runs pubkey -> id. It does NOT run backwards, and no amount of holding
|
|
175
|
+
* an id recovers the key that made it: blake2b-128 is a hash. What a caller
|
|
176
|
+
* holding only a vault id CAN do is the inverse QUESTION rather than the
|
|
177
|
+
* inverse function — take a candidate key and ask whether it derives that id.
|
|
178
|
+
* That single check is what turns `verifySignedRoster` from a consistency
|
|
179
|
+
* test into a verification, because the id came from outside the response
|
|
180
|
+
* and the key inside it now has to match something the attacker cannot
|
|
181
|
+
* choose.
|
|
182
|
+
*
|
|
183
|
+
* # Two ways to get this wrong, both of which produce a plausible id
|
|
184
|
+
*
|
|
185
|
+
* 1. The key is hashed as its hex TEXT (Go writes `[]byte(walletPubkeyHex)`),
|
|
186
|
+
* never as the 32 bytes that text denotes. `@polkadot`'s `blake2AsHex`
|
|
187
|
+
* decodes a `0x`-prefixed STRING as hex, so it must be handed encoded
|
|
188
|
+
* bytes — passing the spelling straight in silently hashes the wrong
|
|
189
|
+
* input.
|
|
190
|
+
* 2. Because the text is the input, the SPELLING is part of it: `deadbeef`,
|
|
191
|
+
* `0xdeadbeef` and `0xDEADBEEF` are three different vault ids in Go.
|
|
192
|
+
* Callers have always been allowed any of those spellings, so the
|
|
193
|
+
* canonical form is applied first — the same rule the server derives with.
|
|
194
|
+
*
|
|
195
|
+
* Both are pinned by golden vectors taken from the running Go function.
|
|
196
|
+
*/
|
|
197
|
+
export function vaultIdFor(walletPubkeyHex) {
|
|
198
|
+
const canonical = canonicalPubkeyHex(walletPubkeyHex);
|
|
199
|
+
// `enc.encode` first — see (1) above; `blake2AsHex` returns `0x`-prefixed
|
|
200
|
+
// hex where Go's `hex.EncodeToString` does not, hence the slice.
|
|
201
|
+
return `v-${blake2AsHex(enc.encode(canonical), 128).slice(2)}`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The ONE spelling of "this membership does not expire": the empty string,
|
|
205
|
+
* always emitted, never an absent key and never `null`.
|
|
206
|
+
*
|
|
207
|
+
* A FIXED SENTINEL so the canonical document has exactly one shape. Two shapes
|
|
208
|
+
* is how a signature comes to verify against bytes the other side never
|
|
209
|
+
* builds, and here conditional omission would be worse than ambiguous: a
|
|
210
|
+
* document that DROPPED `expiresAt` when there is no expiry is byte-identical
|
|
211
|
+
* to a document minted before the field existed, so every signature already
|
|
212
|
+
* stored would verify as a brand-new never-expires grant. Emitting `""` makes
|
|
213
|
+
* those older bytes unbuildable, which is the version gate this change would
|
|
214
|
+
* otherwise have to spend a new {@link GRANT_SCHEMA} string on.
|
|
215
|
+
*
|
|
216
|
+
* The empty string also cannot be mistaken for a moment — no RFC 3339 spelling
|
|
217
|
+
* is empty — so, unlike a far-future sentinel instant, nothing downstream can
|
|
218
|
+
* parse it into a date and silently act on the year 9999.
|
|
219
|
+
*/
|
|
220
|
+
export const NO_GRANT_EXPIRY = "";
|
|
221
|
+
/**
|
|
222
|
+
* Tags a timestamp string as the grant's expiry.
|
|
223
|
+
*
|
|
224
|
+
* It does NOT validate: the string is carried into the signed bytes verbatim
|
|
225
|
+
* and neither side parses or re-formats it, so a check here would be a second
|
|
226
|
+
* opinion about a value the server rebuilds from what you sent. vault-api DOES
|
|
227
|
+
* validate — `PUT …/members` 400s an `expiresAt` that is not RFC 3339, because
|
|
228
|
+
* that value also has to reach a `timestamptz` column — so a malformed expiry
|
|
229
|
+
* is refused at the one place that can refuse it usefully.
|
|
230
|
+
*
|
|
231
|
+
* Pass the SAME string you put in the request body. Formatting a `Date` twice
|
|
232
|
+
* is how the two copies drift by a trimmed fractional zero.
|
|
233
|
+
*/
|
|
234
|
+
export function grantExpiry(rfc3339) {
|
|
235
|
+
return rfc3339;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* A signed grant that could not be trusted: the signature does not verify,
|
|
239
|
+
* the document does not decode, its schema is unknown, or it describes a
|
|
240
|
+
* different membership from the one it was served under.
|
|
241
|
+
*
|
|
242
|
+
* A THROWN error rather than a filtered-out entry, because every one of those
|
|
243
|
+
* states on an authorization artifact means the response is not what it
|
|
244
|
+
* claims to be. Silently dropping the bad entry and returning the rest would
|
|
245
|
+
* hand a caller a shorter roster that looks complete. `memberPubkey` names
|
|
246
|
+
* the entry so an operator can find it.
|
|
247
|
+
*/
|
|
248
|
+
export class GrantVerificationError extends Error {
|
|
249
|
+
memberPubkey;
|
|
250
|
+
constructor(message,
|
|
251
|
+
/**
|
|
252
|
+
* The member the failure is about, or `""` when it is about the ENVELOPE
|
|
253
|
+
* — a missing anchor, a roster for the wrong vault, a malformed shape —
|
|
254
|
+
* which belongs to no single entry.
|
|
255
|
+
*/
|
|
256
|
+
memberPubkey = "") {
|
|
257
|
+
super(message);
|
|
258
|
+
this.memberPubkey = memberPubkey;
|
|
259
|
+
this.name = "GrantVerificationError";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* The exact bytes a grant signature covers, mirroring
|
|
264
|
+
* `authz.CanonicalGrantBytes`.
|
|
265
|
+
*
|
|
266
|
+
* Every bound value is inside the output: a document proves ONE member's
|
|
267
|
+
* standing on ONE vault and nothing else. `scopes` is emitted as an ARRAY
|
|
268
|
+
* sorted by name — not as an object — because the ordering is then this
|
|
269
|
+
* function's own step and can be tested, where a JSON object would have the
|
|
270
|
+
* encoder sort the keys and a stability test would pass whatever this code
|
|
271
|
+
* did.
|
|
272
|
+
*
|
|
273
|
+
* An incomplete grant throws rather than producing shorter bytes: a document
|
|
274
|
+
* naming no vault or no member would verify perfectly and authorise nobody in
|
|
275
|
+
* particular. `issuedAt` is required on the same grounds — an empty one is a
|
|
276
|
+
* document with no mint time that verifies exactly as well as one with a mint
|
|
277
|
+
* time.
|
|
278
|
+
*
|
|
279
|
+
* `issuedAt` is LAST BUT ONE and `expiresAt` is LAST, mirroring Go's field and
|
|
280
|
+
* parameter order. In Go the map between `issuedAt` and the other strings is
|
|
281
|
+
* what makes a transposition a compile error instead of a valid signature over
|
|
282
|
+
* the wrong bytes; the one pair the map cannot sit between is `issuedAt` and
|
|
283
|
+
* `expiresAt`, which Go separates with a defined type and this file mirrors
|
|
284
|
+
* with {@link GrantExpiry}'s brand.
|
|
285
|
+
*
|
|
286
|
+
* `issuedAt` has NO DEFAULT on purpose: a `new Date().toISOString()` fallback
|
|
287
|
+
* would be a hidden clock inside the encoder, and a caller who forgot to pass
|
|
288
|
+
* one would silently sign a different moment from the one they sent. `scopes`
|
|
289
|
+
* loses its `= {}` default with it — a default that can never be used, because
|
|
290
|
+
* a required parameter follows, reads as an option and is not one. `expiresAt`
|
|
291
|
+
* has none for the sharper version of the same reason: its default would be
|
|
292
|
+
* "never expires", the one wrong answer that looks right.
|
|
293
|
+
*
|
|
294
|
+
* `expiresAt` is the ONE parameter with no emptiness check, because `""` is
|
|
295
|
+
* {@link NO_GRANT_EXPIRY} — a legal, common, deliberate value rather than a
|
|
296
|
+
* missing one.
|
|
297
|
+
*/
|
|
298
|
+
export function canonicalGrantBytes(vaultId, memberPubkey, role, scopes, issuedAt, expiresAt) {
|
|
299
|
+
if (vaultId === "")
|
|
300
|
+
throw new Error("canonical grant: vaultId is required");
|
|
301
|
+
if (memberPubkey === "")
|
|
302
|
+
throw new Error("canonical grant: memberPubkey is required");
|
|
303
|
+
if (role === "")
|
|
304
|
+
throw new Error("canonical grant: role is required");
|
|
305
|
+
if (issuedAt === "")
|
|
306
|
+
throw new Error("canonical grant: issuedAt is required");
|
|
307
|
+
const names = Object.keys(scopes).sort(compareCodePoints);
|
|
308
|
+
const parts = names.map((name) => `{"name":${goString(name)},"level":${goString(scopes[name])}}`);
|
|
309
|
+
// Hand-assembled rather than `JSON.stringify`d because the field ORDER is
|
|
310
|
+
// the wire contract (Go emits struct fields in declaration order) and
|
|
311
|
+
// because `goString` has to do the HTML escaping `JSON.stringify` will not.
|
|
312
|
+
const doc = `{"schema":${goString(GRANT_SCHEMA)},` +
|
|
313
|
+
`"vaultId":${goString(vaultId)},` +
|
|
314
|
+
`"memberPubkey":${goString(memberPubkey)},` +
|
|
315
|
+
`"role":${goString(role)},` +
|
|
316
|
+
`"scopes":[${parts.join(",")}],` +
|
|
317
|
+
`"issuedAt":${goString(issuedAt)},` +
|
|
318
|
+
`"expiresAt":${goString(expiresAt)}}`;
|
|
319
|
+
return enc.encode(doc);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Reads a canonical grant document back into its parts, mirroring
|
|
323
|
+
* `authz.DecodeGrantDoc`. Never re-encodes; the bytes stay the authority.
|
|
324
|
+
*
|
|
325
|
+
* Throws on an unknown schema (see `GRANT_SCHEMA`) and on the same three
|
|
326
|
+
* required fields the encoder refuses to omit.
|
|
327
|
+
*/
|
|
328
|
+
export function decodeGrantDoc(doc) {
|
|
329
|
+
if (doc.length === 0)
|
|
330
|
+
throw new Error("decode grant: empty document");
|
|
331
|
+
let raw;
|
|
332
|
+
try {
|
|
333
|
+
raw = JSON.parse(dec.decode(doc));
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
throw new Error(`decode grant: ${err instanceof Error ? err.message : String(err)}`);
|
|
337
|
+
}
|
|
338
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
339
|
+
throw new Error("decode grant: document is not a JSON object");
|
|
340
|
+
}
|
|
341
|
+
const obj = raw;
|
|
342
|
+
const schema = stringField(obj, "schema");
|
|
343
|
+
if (schema !== GRANT_SCHEMA) {
|
|
344
|
+
throw new Error(`decode grant: schema ${JSON.stringify(schema)} is not ${JSON.stringify(GRANT_SCHEMA)}`);
|
|
345
|
+
}
|
|
346
|
+
const vaultId = stringField(obj, "vaultId");
|
|
347
|
+
const memberPubkey = stringField(obj, "memberPubkey");
|
|
348
|
+
const role = stringField(obj, "role");
|
|
349
|
+
if (vaultId === "" || memberPubkey === "" || role === "") {
|
|
350
|
+
throw new Error("decode grant: vaultId, memberPubkey and role are all required");
|
|
351
|
+
}
|
|
352
|
+
// `scopes` is an array in the document and a map here, exactly as Go's
|
|
353
|
+
// decoder does it: the array exists so the ENCODER's ordering is a testable
|
|
354
|
+
// step, and a decoder has no ordering left to prove — every consumer of a
|
|
355
|
+
// decoded grant asks "what level on scope X", which is a map question.
|
|
356
|
+
const rawScopes = obj.scopes;
|
|
357
|
+
if (!Array.isArray(rawScopes)) {
|
|
358
|
+
throw new Error("decode grant: scopes must be an array");
|
|
359
|
+
}
|
|
360
|
+
const scopes = {};
|
|
361
|
+
for (const entry of rawScopes) {
|
|
362
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
363
|
+
throw new Error("decode grant: a scope entry is not an object");
|
|
364
|
+
}
|
|
365
|
+
const name = stringField(entry, "name");
|
|
366
|
+
if (name === "")
|
|
367
|
+
throw new Error("decode grant: a scope with no name");
|
|
368
|
+
scopes[name] = stringField(entry, "level");
|
|
369
|
+
}
|
|
370
|
+
// LAST, because the fields are checked in WIRE ORDER and `issuedAt` is the
|
|
371
|
+
// last field of the document. Required exactly as Go's decoder requires it:
|
|
372
|
+
// a document predating the field is REFUSED rather than passed on as a
|
|
373
|
+
// grant with no mint time.
|
|
374
|
+
const issuedAt = stringField(obj, "issuedAt");
|
|
375
|
+
if (issuedAt === "")
|
|
376
|
+
throw new Error("decode grant: issuedAt is required");
|
|
377
|
+
// PRESENCE, not emptiness, and the asymmetry with `issuedAt` above is the
|
|
378
|
+
// point: `""` is NO_GRANT_EXPIRY, a legal value most documents carry, while
|
|
379
|
+
// an ABSENT key is a document minted before the field existed — one whose
|
|
380
|
+
// signer attested a standing and never its duration. `stringField` already
|
|
381
|
+
// refuses absent ("got nothing") and `null` ("got object"), which is exactly
|
|
382
|
+
// what Go's `raw.ExpiresAt == nil` refuses; an `=== ""` guard here would make
|
|
383
|
+
// "this membership never expires" undecodable.
|
|
384
|
+
const expiresAt = stringField(obj, "expiresAt");
|
|
385
|
+
return { schema, vaultId, memberPubkey, role, scopes, issuedAt, expiresAt };
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Is `sigHex` `orgWalletPubkeyHex`'s ed25519 signature over `doc`?
|
|
389
|
+
*
|
|
390
|
+
* A boolean, not a throw, because nothing here can be undecided: both inputs
|
|
391
|
+
* are in hand and the check is arithmetic. Malformed inputs (a key or
|
|
392
|
+
* signature of the wrong length, non-hex) are `false` rather than an
|
|
393
|
+
* exception, mirroring `authz.VerifyGrantSig` — the caller that needs to tell
|
|
394
|
+
* "nothing was sent" from "it does not verify" looks at the inputs itself.
|
|
395
|
+
*
|
|
396
|
+
* Hex is accepted with or without an `0x` prefix, in either case, because
|
|
397
|
+
* wallets and SDKs disagree about both.
|
|
398
|
+
*/
|
|
399
|
+
export async function verifyGrantSig(orgWalletPubkeyHex, doc, sigHex) {
|
|
400
|
+
if (doc.length === 0 || sigHex === "")
|
|
401
|
+
return false;
|
|
402
|
+
let pub;
|
|
403
|
+
let sig;
|
|
404
|
+
try {
|
|
405
|
+
pub = hexToBytes(strip0xHex(orgWalletPubkeyHex).toLowerCase());
|
|
406
|
+
sig = hexToBytes(strip0xHex(sigHex).toLowerCase());
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
if (pub.length !== 32 || sig.length !== 64)
|
|
412
|
+
return false;
|
|
413
|
+
try {
|
|
414
|
+
return await ed.verifyAsync(sig, doc, pub);
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// @noble throws on a point that is not on the curve; that is a failed
|
|
418
|
+
// verification, not a caller error.
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Verifies EVERY grant in a signed roster against the vault owner's key and
|
|
424
|
+
* returns the grants decoded from the verified bytes.
|
|
425
|
+
*
|
|
426
|
+
* This is the whole point of the signed roster: after this call you hold
|
|
427
|
+
* values that came out of bytes the vault owner signed, and you can throw the
|
|
428
|
+
* response away. Enforce on the returned `Grant`s — never on
|
|
429
|
+
* `SignedGrant.role` / `SignedGrant.scopes`, which are vault-api's rendering
|
|
430
|
+
* of the same document and are not covered by anything you checked.
|
|
431
|
+
*
|
|
432
|
+
* Three bindings are checked per entry, and the last two matter as much as
|
|
433
|
+
* the signature: the document must carry the schema this SDK understands, it
|
|
434
|
+
* must name the vault the roster is FOR, and it must name the member it was
|
|
435
|
+
* served UNDER. A document that verifies but describes a different membership
|
|
436
|
+
* is a valid signature over somebody else's grant — the substitution the
|
|
437
|
+
* canonical bytes bind all four values to prevent.
|
|
438
|
+
*
|
|
439
|
+
* Fail-closed: the FIRST unverifiable entry throws
|
|
440
|
+
* `GrantVerificationError` and no partial list is returned. An authorization
|
|
441
|
+
* artifact with one entry you cannot prove is not a shorter roster, it is a
|
|
442
|
+
* roster you cannot trust.
|
|
443
|
+
*
|
|
444
|
+
* # `expectedVaultId` is the trust anchor, and it is why this is a
|
|
445
|
+
* # verification rather than a consistency check
|
|
446
|
+
*
|
|
447
|
+
* PASS THE ID YOU ASKED FOR — the same one handed to
|
|
448
|
+
* `sdk.memberships.signedRoster()`. It is required, and the requirement is
|
|
449
|
+
* the fix for the hole it closes:
|
|
450
|
+
*
|
|
451
|
+
* Every other value used here arrives INSIDE the response. Given only a
|
|
452
|
+
* roster, "verify it" can mean no more than "does this agree with itself?" —
|
|
453
|
+
* and it always does, because whoever composed it chose every field. Anyone
|
|
454
|
+
* able to answer on this address could sign a roster with a key of their own,
|
|
455
|
+
* name that key as `orgWalletPubkey`, and watch it verify.
|
|
456
|
+
*
|
|
457
|
+
* One externally-held value breaks the circle. `vaultIdFor` is a one-way
|
|
458
|
+
* derivation, so a key that derives `expectedVaultId` cannot be picked; it
|
|
459
|
+
* can only be the vault's actual owner. The two checks are therefore:
|
|
460
|
+
*
|
|
461
|
+
* 1. the roster must be FOR the vault asked about (`roster.vaultId`), which
|
|
462
|
+
* is the binding the two separate calls otherwise leave open — a
|
|
463
|
+
* response about somebody else's vault used to verify perfectly; and
|
|
464
|
+
* 2. `orgWalletPubkey` must DERIVE that vault, which pins the signing key
|
|
465
|
+
* to an identity the response did not get to choose.
|
|
466
|
+
*
|
|
467
|
+
* Both run BEFORE any signature is checked, mirroring
|
|
468
|
+
* `RosterAuthority.verifyRoster` in ddc-node: until the anchor holds, every
|
|
469
|
+
* byte of the envelope is attacker-supplied, including the key the
|
|
470
|
+
* signatures would be checked against.
|
|
471
|
+
*
|
|
472
|
+
* A caller who holds the OWNER KEY rather than the id needs nothing extra —
|
|
473
|
+
* `vaultIdFor(ownerKey)` is the id. That direction works; the reverse does
|
|
474
|
+
* not exist.
|
|
475
|
+
*
|
|
476
|
+
* This anchors the roster to a vault, which is all a hash can do. It does not
|
|
477
|
+
* tell you the id itself is the vault you meant: if `expectedVaultId` was
|
|
478
|
+
* itself learned from an untrusted party, the result is trust in THAT party's
|
|
479
|
+
* choice of vault. Anchor it to something you already knew.
|
|
480
|
+
*/
|
|
481
|
+
export async function verifySignedRoster(roster, expectedVaultId) {
|
|
482
|
+
if (typeof expectedVaultId !== "string" || expectedVaultId === "") {
|
|
483
|
+
throw new GrantVerificationError("expectedVaultId is required: verifying a roster against the vault id it " +
|
|
484
|
+
"carries only proves the response agrees with itself");
|
|
485
|
+
}
|
|
486
|
+
// The envelope is checked as SHAPE before anything is read out of it. A
|
|
487
|
+
// `grants: null` used to reach the `for` loop and surface as a bare
|
|
488
|
+
// TypeError — an error the caller cannot distinguish from a bug in their
|
|
489
|
+
// own code, on a path whose entire job is to report untrustworthy input.
|
|
490
|
+
if (typeof roster !== "object" || roster === null) {
|
|
491
|
+
throw new GrantVerificationError(`signed roster is not an object (got ${typeOf(roster)})`);
|
|
492
|
+
}
|
|
493
|
+
if (typeof roster.vaultId !== "string" || roster.vaultId === "") {
|
|
494
|
+
throw new GrantVerificationError("signed roster carries no vaultId");
|
|
495
|
+
}
|
|
496
|
+
if (typeof roster.orgWalletPubkey !== "string" || roster.orgWalletPubkey === "") {
|
|
497
|
+
throw new GrantVerificationError("signed roster carries no orgWalletPubkey");
|
|
498
|
+
}
|
|
499
|
+
if (!Array.isArray(roster.grants)) {
|
|
500
|
+
throw new GrantVerificationError(`signed roster's grants is not an array (got ${typeOf(roster.grants)})`);
|
|
501
|
+
}
|
|
502
|
+
// THE ANCHOR, both halves, before a single signature is checked.
|
|
503
|
+
if (roster.vaultId !== expectedVaultId) {
|
|
504
|
+
throw new GrantVerificationError(`signed roster names vault ${roster.vaultId}, asked about ${expectedVaultId}`);
|
|
505
|
+
}
|
|
506
|
+
if (vaultIdFor(roster.orgWalletPubkey) !== expectedVaultId) {
|
|
507
|
+
throw new GrantVerificationError(`orgWalletPubkey ${roster.orgWalletPubkey} does not derive vault ${expectedVaultId}`);
|
|
508
|
+
}
|
|
509
|
+
const out = [];
|
|
510
|
+
for (const grant of roster.grants) {
|
|
511
|
+
if (typeof grant !== "object" || grant === null) {
|
|
512
|
+
throw new GrantVerificationError(`signed roster holds a grant that is not an object`);
|
|
513
|
+
}
|
|
514
|
+
out.push(await verifyGrant(expectedVaultId, roster.orgWalletPubkey, grant));
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
/** `typeof`, but naming `null` and arrays, for a message a caller can act on. */
|
|
519
|
+
function typeOf(v) {
|
|
520
|
+
if (v === null)
|
|
521
|
+
return "null";
|
|
522
|
+
return Array.isArray(v) ? "an array" : typeof v;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* One entry of `verifySignedRoster`, exposed for a caller holding a single
|
|
526
|
+
* grant (a member proving their own standing, say) rather than a whole
|
|
527
|
+
* roster. Same bindings, same fail-closed throw.
|
|
528
|
+
*
|
|
529
|
+
* `vaultId` and `orgWalletPubkey` must CORRESPOND — the same anchor
|
|
530
|
+
* `verifySignedRoster` applies, checked here too because both arguments are
|
|
531
|
+
* the caller's and the natural way to fill them is straight off a response,
|
|
532
|
+
* which is exactly the unanchored call this check refuses.
|
|
533
|
+
*/
|
|
534
|
+
export async function verifyGrant(vaultId, orgWalletPubkey, grant) {
|
|
535
|
+
if (vaultIdFor(orgWalletPubkey) !== vaultId) {
|
|
536
|
+
throw new GrantVerificationError(`orgWalletPubkey ${orgWalletPubkey} does not derive vault ${vaultId}`, grant.memberPubkey);
|
|
537
|
+
}
|
|
538
|
+
let doc;
|
|
539
|
+
try {
|
|
540
|
+
doc = decodeBase64(grant.doc);
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
throw new GrantVerificationError(`grant document is not valid base64: ${err instanceof Error ? err.message : String(err)}`, grant.memberPubkey);
|
|
544
|
+
}
|
|
545
|
+
if (!(await verifyGrantSig(orgWalletPubkey, doc, grant.sig))) {
|
|
546
|
+
throw new GrantVerificationError(`grant document is not signed by the vault owner (${orgWalletPubkey})`, grant.memberPubkey);
|
|
547
|
+
}
|
|
548
|
+
let decoded;
|
|
549
|
+
try {
|
|
550
|
+
decoded = decodeGrantDoc(doc);
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
throw new GrantVerificationError(err instanceof Error ? err.message : String(err), grant.memberPubkey);
|
|
554
|
+
}
|
|
555
|
+
if (decoded.vaultId !== vaultId || decoded.memberPubkey !== grant.memberPubkey) {
|
|
556
|
+
throw new GrantVerificationError(`grant document describes a different membership: it names ` +
|
|
557
|
+
`${decoded.memberPubkey} on ${decoded.vaultId}, and was served as ` +
|
|
558
|
+
`${grant.memberPubkey} on ${vaultId}`, grant.memberPubkey);
|
|
559
|
+
}
|
|
560
|
+
return decoded;
|
|
561
|
+
}
|
|
562
|
+
/** A required string field, or a typed failure naming it. */
|
|
563
|
+
function stringField(obj, key) {
|
|
564
|
+
const v = obj[key];
|
|
565
|
+
if (typeof v !== "string") {
|
|
566
|
+
throw new Error(`decode grant: ${key} must be a string, got ${v === undefined ? "nothing" : typeof v}`);
|
|
567
|
+
}
|
|
568
|
+
return v;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* `encoding/json`'s string encoding, which `JSON.stringify` does not
|
|
572
|
+
* reproduce: `<`, `>`, `&` and U+2028/U+2029 are escaped. Everything else is
|
|
573
|
+
* left to `JSON.stringify`, so the two agree on quotes, control characters
|
|
574
|
+
* and lone surrogates.
|
|
575
|
+
*/
|
|
576
|
+
function goString(s) {
|
|
577
|
+
return JSON.stringify(s)
|
|
578
|
+
.replace(/</g, "\\u003c")
|
|
579
|
+
.replace(/>/g, "\\u003e")
|
|
580
|
+
.replace(/&/g, "\\u0026")
|
|
581
|
+
.replace(/\u2028/g, "\\u2028")
|
|
582
|
+
.replace(/\u2029/g, "\\u2029");
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Sorts the way Go sorts strings — by UTF-8 bytes, which is code-point order.
|
|
586
|
+
* JavaScript's `<` compares UTF-16 code units and disagrees for astral
|
|
587
|
+
* characters, so `[...]sort()` would order a scope named "😀" (U+1F600)
|
|
588
|
+
* BEFORE one named "A" (U+FF21) while Go puts it after, and the signature
|
|
589
|
+
* over the resulting bytes would never verify.
|
|
590
|
+
*/
|
|
591
|
+
function compareCodePoints(a, b) {
|
|
592
|
+
const ai = Array.from(a);
|
|
593
|
+
const bi = Array.from(b);
|
|
594
|
+
const n = Math.min(ai.length, bi.length);
|
|
595
|
+
for (let i = 0; i < n; i++) {
|
|
596
|
+
const x = ai[i].codePointAt(0);
|
|
597
|
+
const y = bi[i].codePointAt(0);
|
|
598
|
+
if (x !== y)
|
|
599
|
+
return x - y;
|
|
600
|
+
}
|
|
601
|
+
return ai.length - bi.length;
|
|
602
|
+
}
|
|
603
|
+
//# sourceMappingURL=grant-doc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grant-doc.js","sourceRoot":"","sources":["../../src/core/grant-doc.ts"],"names":[],"mappings":"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"}
|