@metalabel/dfos-protocol 0.52.1 → 0.54.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/README.md +7 -0
- package/dist/chain/index.d.ts +33 -5
- package/dist/chain/index.js +6 -4
- package/dist/{chunk-BMIOY5W5.js → chunk-ISHAX5WN.js} +1 -1
- package/dist/{chunk-KAW4MT4P.js → chunk-NUIIVAZ3.js} +6 -1
- package/dist/{chunk-Q7FREWMY.js → chunk-QIM3KZXZ.js} +14 -8
- package/dist/{chunk-FZHHSC5N.js → chunk-RPA44ND4.js} +117 -20
- package/dist/credentials/index.d.ts +95 -14
- package/dist/credentials/index.js +14 -2
- package/dist/crypto/index.js +1 -1
- package/dist/{dfos-credential-j-kHtFvG.d.ts → dfos-credential-DgT8XVTm.d.ts} +37 -7
- package/dist/index.d.ts +3 -3
- package/dist/index.js +18 -4
- package/dist/key-proof/index.js +2 -2
- package/examples/api-resource-coverage.json +413 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@ import { signKeyProof, verifyKeyProof } from '@metalabel/dfos-protocol/key-proof
|
|
|
36
36
|
| `@metalabel/dfos-protocol/key-proof` | KEY-PROOF envelopes — compose/sign and verify a challenge-bound proof that a candidate key is held |
|
|
37
37
|
| `@metalabel/dfos-protocol/fold` | Canonical linearization and LWW-map folds for index documents |
|
|
38
38
|
|
|
39
|
+
## `api:` resources and the `jti` member
|
|
40
|
+
|
|
41
|
+
An `api:` resource takes one of two forms: `api:<host>`, the whole API surface, and `api:<host>/spaces/<id>`, one space on it. A bare host is the ancestor of every space at that host, so a grant naming the host covers a space-addressed request while a space-scoped grant covers only its own space. `parseApiResource` and `apiResourceCovers` are that rule on its own; `isAttenuated` and `matchesResource` apply it. Every other resource type is exact byte equality, and a malformed `api:` id covers nothing — itself included.
|
|
42
|
+
|
|
43
|
+
`jti` is the envelope's one registered additive member: a per-request unique string of at most `MAX_JTI_BYTES` UTF-8 bytes, emitted after the canonical members. Pass it typed to `signApiRequest` or `signApiIdentityRequest` (`generateJti()` mints one), set `requireJti` on a verifier to refuse a proof without it, and read the verified value back off the envelope to key a replay cache. A proof carrying none has the bytes it always had.
|
|
44
|
+
|
|
39
45
|
## Specifications
|
|
40
46
|
|
|
41
47
|
| Document | Description |
|
|
@@ -62,6 +68,7 @@ The `examples/` directory contains deterministic reference fixtures that can be
|
|
|
62
68
|
- `credential-write.json` — DFOS write credential (broad + content-narrowed)
|
|
63
69
|
- `credential-read.json` — DFOS read credential
|
|
64
70
|
- `identity-services.json` — genesis publishing a services set (relay locator + content/artifact anchors)
|
|
71
|
+
- `api-resource-coverage.json` — the `api:` hierarchy's parse, coverage, attenuation, and matching rows, read by the TypeScript and Go suites alike
|
|
65
72
|
|
|
66
73
|
## License
|
|
67
74
|
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as IdentityOperation, v as Signer, x as VerifiedIdentity, S as ServiceEntry, c as ContentOperation, R as ResolvedIdentity, r as RevocationChecker, d as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-
|
|
2
|
-
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, s as RevocationPayload, t as ServicesArray, u as SignRequestPayload, y as VoidKeyMembership,
|
|
1
|
+
import { I as IdentityOperation, v as Signer, x as VerifiedIdentity, S as ServiceEntry, c as ContentOperation, R as ResolvedIdentity, r as RevocationChecker, d as CountersignPayload, a as ArtifactPayload } from '../dfos-credential-DgT8XVTm.js';
|
|
2
|
+
export { A as ARTIFACT_CID_ANCHOR_RE, C as CONTENT_ID_ANCHOR_RE, f as CreditClaimPayload, g as DeclaredKeyState, h as Iso8601, M as MAX_ARTIFACT_PAYLOAD_SIZE, j as MAX_CREDIT_CLAIM_SIZE, k as MAX_KEY_PROOFS, l as MAX_OPERATION_SIZE, m as MAX_SERVICES_ENTRIES, n as MAX_SERVICES_PAYLOAD_SIZE, o as MAX_SIGN_REQUEST_PAYLOAD_SIZE, p as MAX_SIGN_REQUEST_SIZE, q as MultikeyPublicKey, s as RevocationPayload, t as ServicesArray, u as SignRequestPayload, y as VoidKeyMembership, J as parseProtocolTimestampUnix } from '../dfos-credential-DgT8XVTm.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/** Ed25519 public key multicodec value */
|
|
@@ -86,6 +86,25 @@ declare const verifyIdentityChain: (input: {
|
|
|
86
86
|
*/
|
|
87
87
|
asOf?: string;
|
|
88
88
|
}) => Promise<VerifiedIdentity>;
|
|
89
|
+
/**
|
|
90
|
+
* The trusted state cannot be extended incrementally: it does not carry the
|
|
91
|
+
* chain-wide id-to-material binding. The caller replays the log.
|
|
92
|
+
*
|
|
93
|
+
* THERE IS NO RECONSTRUCTION, and that is the whole point. `seenKeys` is a
|
|
94
|
+
* monotonic index that never forgets a key id once the chain declared it — even
|
|
95
|
+
* after the id is dropped from the declared arrays — and nothing else in the
|
|
96
|
+
* state remembers it: an id introduced without a proof and then removed is in
|
|
97
|
+
* neither `declared` (removed) nor `provedKeys` (never proved). Reading the
|
|
98
|
+
* binding off those two arrays therefore treats a re-declaration of that id,
|
|
99
|
+
* bound to DIFFERENT material, as a fresh introduction and accepts it — while a
|
|
100
|
+
* full replay of the same operations rejects it. A fast path that accepts what
|
|
101
|
+
* its own re-verification refuses is worse than one that says it cannot answer.
|
|
102
|
+
*
|
|
103
|
+
* The Go twin is `ErrIdentityStateNoSeenKeys`, with this exact message.
|
|
104
|
+
*/
|
|
105
|
+
declare class IdentityStateNoSeenKeysError extends Error {
|
|
106
|
+
constructor();
|
|
107
|
+
}
|
|
89
108
|
/**
|
|
90
109
|
* Verify a single new operation against already-verified identity state
|
|
91
110
|
*
|
|
@@ -100,7 +119,8 @@ declare const verifyIdentityChain: (input: {
|
|
|
100
119
|
* is REJECTED on this path exactly as a replay of the same two operations would
|
|
101
120
|
* reject it. Without that the fast path would accept a chain its own
|
|
102
121
|
* re-verification refuses, and a relay's linear path is the path almost every
|
|
103
|
-
* operation takes.
|
|
122
|
+
* operation takes. A state that does not carry `seenKeys` is therefore not
|
|
123
|
+
* extensible at all — see `IdentityStateNoSeenKeysError`.
|
|
104
124
|
*
|
|
105
125
|
* THE POSSESSION FOLD RUNS HERE TOO, and it needs both halves of the trusted
|
|
106
126
|
* state: the EFFECTIVE arrays (to know what an introduction is a transition out
|
|
@@ -130,8 +150,16 @@ type AnchorKind = 'chain' | 'artifact' | 'invalid';
|
|
|
130
150
|
* Enforce the services byte cap on the CBOR-encoded array — same encoding the
|
|
131
151
|
* wire uses, so the bound is identical across implementations. Mirrors the
|
|
132
152
|
* artifact payload size check.
|
|
153
|
+
*
|
|
154
|
+
* MEASURE THE RAW DECODED PAYLOAD'S ARRAY, never zod's output. The parameter is
|
|
155
|
+
* `unknown` to make that hard to get wrong: the schema is a validator, not a
|
|
156
|
+
* canonicalizer, and its output object is freshly built — a `__proto__` data
|
|
157
|
+
* member (constructible only by `JSON.parse`) does not survive the copy. Handing
|
|
158
|
+
* this the parsed `op.services` measured 19 bytes for a 33,032-byte signed
|
|
159
|
+
* array, which Go's `parseServices` — reading the wire map directly — rejects.
|
|
160
|
+
* Same rule as the operation CID, and for the same reason.
|
|
133
161
|
*/
|
|
134
|
-
declare const assertServicesWithinCap: (services:
|
|
162
|
+
declare const assertServicesWithinCap: (services: unknown) => Promise<void>;
|
|
135
163
|
/**
|
|
136
164
|
* Classify a ContentAnchor target by structural form. Resolvers dispatch on the
|
|
137
165
|
* result: 'chain' → resolve a content chain by contentId; 'artifact' → fetch by
|
|
@@ -560,4 +588,4 @@ declare const assertCanonicalSignRequestPayload: (payloadTyp: string, payloadByt
|
|
|
560
588
|
subject: string;
|
|
561
589
|
}) => void;
|
|
562
590
|
|
|
563
|
-
export { type AnchorKind, ArtifactPayload, ContentOperation, CountersignPayload, type CreditClaimFailureReason, CreditClaimVerifyError, type CreditEntry, type CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, RECOGNIZED_SERVICE_TYPES, ServiceEntry, type SignRequestFailureReason, SignRequestVerifyError, Signer, type VerifiedArtifact, type VerifiedContentChain, type VerifiedCountersignature, type VerifiedCreditClaim, type VerifiedCreditEntry, VerifiedIdentity, type VerifiedRevocation, type VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeEd25519PublicMultikey, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest };
|
|
591
|
+
export { type AnchorKind, ArtifactPayload, ContentOperation, CountersignPayload, type CreditClaimFailureReason, CreditClaimVerifyError, type CreditEntry, type CreditEntryState, ED25519_PRIV_MULTICODEC, ED25519_PUB_MULTICODEC, IdentityOperation, IdentityStateNoSeenKeysError, RECOGNIZED_SERVICE_TYPES, ServiceEntry, type SignRequestFailureReason, SignRequestVerifyError, Signer, type VerifiedArtifact, type VerifiedContentChain, type VerifiedCountersignature, type VerifiedCreditClaim, type VerifiedCreditEntry, VerifiedIdentity, type VerifiedRevocation, type VerifiedSignRequest, anchorsByLabel, assertCanonicalSignRequestPayload, assertServicesWithinCap, buildSignRequest, classifyAnchor, decodeEd25519PublicMultikey, decodeMultikey, deriveChainIdentifier, deriveContentId, encodeEd25519Multikey, isRecognizedServiceType, relayEndpoints, signArtifact, signContentOperation, signCountersignature, signCreditClaim, signIdentityOperation, signRevocation, verifyArtifact, verifyContentChain, verifyContentExtensionFromTrustedState, verifyCountersignature, verifyCreditClaim, verifyCreditEntry, verifyIdentityChain, verifyIdentityExtensionFromTrustedState, verifyRevocation, verifySignRequest };
|
package/dist/chain/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CreditClaimVerifyError,
|
|
9
9
|
DeclaredKeyState,
|
|
10
10
|
IdentityOperation,
|
|
11
|
+
IdentityStateNoSeenKeysError,
|
|
11
12
|
Iso8601,
|
|
12
13
|
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
13
14
|
MAX_CREDIT_CLAIM_SIZE,
|
|
@@ -52,9 +53,9 @@ import {
|
|
|
52
53
|
verifyIdentityExtensionFromTrustedState,
|
|
53
54
|
verifyRevocation,
|
|
54
55
|
verifySignRequest
|
|
55
|
-
} from "../chunk-
|
|
56
|
-
import "../chunk-
|
|
57
|
-
import "../chunk-
|
|
56
|
+
} from "../chunk-QIM3KZXZ.js";
|
|
57
|
+
import "../chunk-RPA44ND4.js";
|
|
58
|
+
import "../chunk-ISHAX5WN.js";
|
|
58
59
|
import {
|
|
59
60
|
ED25519_PRIV_MULTICODEC,
|
|
60
61
|
ED25519_PUB_MULTICODEC,
|
|
@@ -62,7 +63,7 @@ import {
|
|
|
62
63
|
decodeMultikey,
|
|
63
64
|
encodeEd25519Multikey
|
|
64
65
|
} from "../chunk-F7RHI2CK.js";
|
|
65
|
-
import "../chunk-
|
|
66
|
+
import "../chunk-NUIIVAZ3.js";
|
|
66
67
|
export {
|
|
67
68
|
ARTIFACT_CID_ANCHOR_RE,
|
|
68
69
|
ArtifactPayload,
|
|
@@ -75,6 +76,7 @@ export {
|
|
|
75
76
|
ED25519_PRIV_MULTICODEC,
|
|
76
77
|
ED25519_PUB_MULTICODEC,
|
|
77
78
|
IdentityOperation,
|
|
79
|
+
IdentityStateNoSeenKeysError,
|
|
78
80
|
Iso8601,
|
|
79
81
|
MAX_ARTIFACT_PAYLOAD_SIZE,
|
|
80
82
|
MAX_CREDIT_CLAIM_SIZE,
|
|
@@ -161,10 +161,15 @@ var assertJwsProfile = (header, makeError) => {
|
|
|
161
161
|
var decodeJwsSegment = (segmentB64) => {
|
|
162
162
|
let text;
|
|
163
163
|
try {
|
|
164
|
-
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
164
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
165
|
+
base64urlDecode(segmentB64)
|
|
166
|
+
);
|
|
165
167
|
} catch {
|
|
166
168
|
return null;
|
|
167
169
|
}
|
|
170
|
+
if (text.charCodeAt(0) === 65279) {
|
|
171
|
+
throw new Error("leading byte order mark is not canonicalizable");
|
|
172
|
+
}
|
|
168
173
|
assertCanonicalJsonText(text);
|
|
169
174
|
let value;
|
|
170
175
|
try {
|
|
@@ -6,13 +6,13 @@ import {
|
|
|
6
6
|
matchesResource,
|
|
7
7
|
verifyDFOSCredential,
|
|
8
8
|
verifyDelegationChain
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RPA44ND4.js";
|
|
10
10
|
import {
|
|
11
11
|
KEY_ADD_JWS_TYP,
|
|
12
12
|
KEY_ROLES,
|
|
13
13
|
unsafeKeyProofSubject,
|
|
14
14
|
verifyChainKeyProof
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ISHAX5WN.js";
|
|
16
16
|
import {
|
|
17
17
|
decodeEd25519PublicMultikey
|
|
18
18
|
} from "./chunk-F7RHI2CK.js";
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
decodeJwsUnsafe,
|
|
26
26
|
generateIdNoPrefix,
|
|
27
27
|
verifyJws
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-NUIIVAZ3.js";
|
|
29
29
|
|
|
30
30
|
// src/chain/schemas.ts
|
|
31
31
|
import { z } from "zod";
|
|
@@ -540,7 +540,7 @@ var verifyIdentityChain = async (input) => {
|
|
|
540
540
|
});
|
|
541
541
|
if (op.services) {
|
|
542
542
|
try {
|
|
543
|
-
await assertServicesWithinCap(
|
|
543
|
+
await assertServicesWithinCap(decoded.payload["services"]);
|
|
544
544
|
} catch (e) {
|
|
545
545
|
throw new Error(`log[${idx}]: ${e.message}`);
|
|
546
546
|
}
|
|
@@ -661,14 +661,19 @@ var verifyIdentityChain = async (input) => {
|
|
|
661
661
|
seenKeys: [...state.seenKeys.values()]
|
|
662
662
|
};
|
|
663
663
|
};
|
|
664
|
+
var IdentityStateNoSeenKeysError = class extends Error {
|
|
665
|
+
constructor() {
|
|
666
|
+
super("identity state has no seenKeys; replay the chain");
|
|
667
|
+
this.name = "IdentityStateNoSeenKeysError";
|
|
668
|
+
}
|
|
669
|
+
};
|
|
664
670
|
var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
665
671
|
const { currentState, headCID, lastCreatedAt, newOp } = input;
|
|
666
672
|
const priorEffective = keyStateOf(currentState);
|
|
667
673
|
const priorDeclared = currentState.declared ?? priorEffective;
|
|
668
674
|
const priorProved = currentState.provedKeys ?? priorEffective;
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
);
|
|
675
|
+
if (!currentState.seenKeys?.length) throw new IdentityStateNoSeenKeysError();
|
|
676
|
+
const seenKeys = keyMaterialIndex(currentState.seenKeys);
|
|
672
677
|
const priorSeenKeys = [...seenKeys.values()];
|
|
673
678
|
const decoded = decodeJwsUnsafe(newOp);
|
|
674
679
|
if (!decoded) throw new Error("failed to decode JWS");
|
|
@@ -733,7 +738,7 @@ var verifyIdentityExtensionFromTrustedState = async (input) => {
|
|
|
733
738
|
throw new Error("cannot repeat key ids in same usage");
|
|
734
739
|
}
|
|
735
740
|
});
|
|
736
|
-
if (op.services) await assertServicesWithinCap(
|
|
741
|
+
if (op.services) await assertServicesWithinCap(decoded.payload["services"]);
|
|
737
742
|
}
|
|
738
743
|
const newState = (() => {
|
|
739
744
|
switch (op.type) {
|
|
@@ -1679,6 +1684,7 @@ export {
|
|
|
1679
1684
|
anchorsByLabel,
|
|
1680
1685
|
signIdentityOperation,
|
|
1681
1686
|
verifyIdentityChain,
|
|
1687
|
+
IdentityStateNoSeenKeysError,
|
|
1682
1688
|
verifyIdentityExtensionFromTrustedState,
|
|
1683
1689
|
signContentOperation,
|
|
1684
1690
|
verifyContentChain,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
decodeJwsUnsafe,
|
|
11
11
|
sha256,
|
|
12
12
|
verifyJws
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-NUIIVAZ3.js";
|
|
14
14
|
|
|
15
15
|
// src/credentials/schemas.ts
|
|
16
16
|
import { z } from "zod";
|
|
@@ -43,6 +43,7 @@ var REQUEST_PROOF_JWS_TYP = "did:dfos:request-proof";
|
|
|
43
43
|
var IDENTITY_PROOF_JWS_TYP = "did:dfos:identity-proof";
|
|
44
44
|
var EMPTY_BODY_SHA256 = "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU";
|
|
45
45
|
var MAX_REQUEST_PROOF_SIZE = 4096;
|
|
46
|
+
var MAX_JTI_BYTES = 256;
|
|
46
47
|
var DEFAULT_PROOF_WINDOW_SECONDS = 60;
|
|
47
48
|
var DEFAULT_PROOF_SKEW_SECONDS = 60;
|
|
48
49
|
var MAX_PROOF_FRESHNESS_SPAN_SECONDS = 300;
|
|
@@ -66,6 +67,27 @@ var CTL_OR_SPACE = /[\u0000-\u0020\u007f]/;
|
|
|
66
67
|
var BASE64URL_32 = /^[A-Za-z0-9_-]{43}$/;
|
|
67
68
|
var EXTRA_MEMBER_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
68
69
|
var CANONICAL_MEMBERS = /* @__PURE__ */ new Set(["method", "host", "path", "bodyHash", "credentialCID", "iat"]);
|
|
70
|
+
var assertJtiBound = (jti, label) => {
|
|
71
|
+
if (jti === "" || encoder.encode(jti).length > MAX_JTI_BYTES) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`invalid ${label}: jti must be a non-empty string of at most ${MAX_JTI_BYTES} bytes`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var withJti = (extra, jti, label) => {
|
|
78
|
+
if (jti === void 0) return extra;
|
|
79
|
+
if (extra && "jti" in extra) {
|
|
80
|
+
throw new Error(`invalid ${label}: jti is named twice \u2014 pass it typed or in extraMembers`);
|
|
81
|
+
}
|
|
82
|
+
assertJtiBound(jti, label);
|
|
83
|
+
return { ...extra, jti };
|
|
84
|
+
};
|
|
85
|
+
var emittedExtraMembers = (parsed, extra, label) => canonicalExtraMembers(withJti(extra, parsed.jti, label), label);
|
|
86
|
+
var generateJti = () => {
|
|
87
|
+
const bytes = new Uint8Array(16);
|
|
88
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
89
|
+
return base64urlEncode(bytes);
|
|
90
|
+
};
|
|
69
91
|
var assertNoLoneSurrogate = (value, field, label) => {
|
|
70
92
|
if (LONE_SURROGATE.test(value)) {
|
|
71
93
|
throw new Error(`invalid ${label}: ${field} must be well-formed Unicode`);
|
|
@@ -113,7 +135,26 @@ var validateProofPayload = (value, shape) => {
|
|
|
113
135
|
if (typeof iat !== "number" || !Number.isSafeInteger(iat) || iat <= 0) {
|
|
114
136
|
throw new Error(`invalid ${label}: iat must be a positive integer`);
|
|
115
137
|
}
|
|
116
|
-
|
|
138
|
+
const rawJti = raw["jti"];
|
|
139
|
+
let jti;
|
|
140
|
+
if (rawJti !== void 0) {
|
|
141
|
+
if (typeof rawJti !== "string") {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`invalid ${label}: jti must be a non-empty string of at most ${MAX_JTI_BYTES} bytes`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
assertJtiBound(rawJti, label);
|
|
147
|
+
jti = rawJti;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
method,
|
|
151
|
+
host,
|
|
152
|
+
path,
|
|
153
|
+
bodyHash,
|
|
154
|
+
...shape.credentialed ? { credentialCID: raw["credentialCID"] } : {},
|
|
155
|
+
iat,
|
|
156
|
+
...jti !== void 0 ? { jti } : {}
|
|
157
|
+
};
|
|
117
158
|
};
|
|
118
159
|
var validateRequestProofPayload = (value) => {
|
|
119
160
|
const parsed = validateProofPayload(value, REQUEST_PROOF_SHAPE);
|
|
@@ -123,7 +164,8 @@ var validateRequestProofPayload = (value) => {
|
|
|
123
164
|
path: parsed.path,
|
|
124
165
|
bodyHash: parsed.bodyHash,
|
|
125
166
|
credentialCID: parsed.credentialCID,
|
|
126
|
-
iat: parsed.iat
|
|
167
|
+
iat: parsed.iat,
|
|
168
|
+
...parsed.jti !== void 0 ? { jti: parsed.jti } : {}
|
|
127
169
|
};
|
|
128
170
|
};
|
|
129
171
|
var validateIdentityProofPayload = (value) => {
|
|
@@ -133,7 +175,8 @@ var validateIdentityProofPayload = (value) => {
|
|
|
133
175
|
host: parsed.host,
|
|
134
176
|
path: parsed.path,
|
|
135
177
|
bodyHash: parsed.bodyHash,
|
|
136
|
-
iat: parsed.iat
|
|
178
|
+
iat: parsed.iat,
|
|
179
|
+
...parsed.jti !== void 0 ? { jti: parsed.jti } : {}
|
|
137
180
|
};
|
|
138
181
|
};
|
|
139
182
|
var canonicalExtraMembers = (extra, label) => {
|
|
@@ -171,16 +214,22 @@ var proofPayloadObject = (parsed, shape, extra) => {
|
|
|
171
214
|
for (const [name, value] of extra) out[name] = value;
|
|
172
215
|
return out;
|
|
173
216
|
};
|
|
174
|
-
var apiRequestSigningInput = (payload, extraMembers) =>
|
|
175
|
-
validateProofPayload(payload, REQUEST_PROOF_SHAPE)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
);
|
|
217
|
+
var apiRequestSigningInput = (payload, extraMembers) => {
|
|
218
|
+
const parsed = validateProofPayload(payload, REQUEST_PROOF_SHAPE);
|
|
219
|
+
return proofSigningInput(
|
|
220
|
+
parsed,
|
|
221
|
+
REQUEST_PROOF_SHAPE,
|
|
222
|
+
emittedExtraMembers(parsed, extraMembers, REQUEST_PROOF_SHAPE.label)
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
var apiIdentitySigningInput = (payload, extraMembers) => {
|
|
226
|
+
const parsed = validateProofPayload(payload, IDENTITY_PROOF_SHAPE);
|
|
227
|
+
return proofSigningInput(
|
|
228
|
+
parsed,
|
|
229
|
+
IDENTITY_PROOF_SHAPE,
|
|
230
|
+
emittedExtraMembers(parsed, extraMembers, IDENTITY_PROOF_SHAPE.label)
|
|
231
|
+
);
|
|
232
|
+
};
|
|
184
233
|
var sha256BodyHash = (body) => base64urlEncode(sha256(body));
|
|
185
234
|
var parseDfosAuthorization = (header) => {
|
|
186
235
|
if (!header) return null;
|
|
@@ -199,12 +248,13 @@ var signApiRequest = async (input) => {
|
|
|
199
248
|
path: input.path,
|
|
200
249
|
bodyHash: sha256BodyHash(input.body ?? EMPTY_BODY),
|
|
201
250
|
credentialCID: input.credentialCID,
|
|
202
|
-
iat: input.iat ?? Math.floor(Date.now() / 1e3)
|
|
251
|
+
iat: input.iat ?? Math.floor(Date.now() / 1e3),
|
|
252
|
+
...input.jti !== void 0 ? { jti: input.jti } : {}
|
|
203
253
|
});
|
|
204
254
|
if (!input.kid.includes("#")) {
|
|
205
255
|
throw new Error("invalid request proof: kid must be a DID URL");
|
|
206
256
|
}
|
|
207
|
-
const extra =
|
|
257
|
+
const extra = emittedExtraMembers(payload, input.extraMembers, REQUEST_PROOF_SHAPE.label);
|
|
208
258
|
const proof = await createJws({
|
|
209
259
|
header: { alg: "EdDSA", typ: REQUEST_PROOF_JWS_TYP, kid: input.kid },
|
|
210
260
|
payload: proofPayloadObject(payload, REQUEST_PROOF_SHAPE, extra),
|
|
@@ -221,12 +271,13 @@ var signApiIdentityRequest = async (input) => {
|
|
|
221
271
|
host: input.host,
|
|
222
272
|
path: input.path,
|
|
223
273
|
bodyHash: sha256BodyHash(input.body ?? EMPTY_BODY),
|
|
224
|
-
iat: input.iat ?? Math.floor(Date.now() / 1e3)
|
|
274
|
+
iat: input.iat ?? Math.floor(Date.now() / 1e3),
|
|
275
|
+
...input.jti !== void 0 ? { jti: input.jti } : {}
|
|
225
276
|
});
|
|
226
277
|
if (!input.kid.includes("#")) {
|
|
227
278
|
throw new Error("invalid identity proof: kid must be a DID URL");
|
|
228
279
|
}
|
|
229
|
-
const extra =
|
|
280
|
+
const extra = emittedExtraMembers(payload, input.extraMembers, IDENTITY_PROOF_SHAPE.label);
|
|
230
281
|
const proof = await createJws({
|
|
231
282
|
header: { alg: "EdDSA", typ: IDENTITY_PROOF_JWS_TYP, kid: input.kid },
|
|
232
283
|
payload: proofPayloadObject(payload, IDENTITY_PROOF_SHAPE, extra),
|
|
@@ -247,7 +298,7 @@ var buildApiIdentityHeaders = (input) => ({
|
|
|
247
298
|
var ApiRequestVerifyError = class extends Error {
|
|
248
299
|
reason;
|
|
249
300
|
phase;
|
|
250
|
-
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid, 503 unverifiable, 500 config. */
|
|
301
|
+
/** Recommended HTTP status: 401 proof-invalid, 403 credential-invalid or uncovered, 409 replayed, 503 unverifiable, 500 config. */
|
|
251
302
|
status;
|
|
252
303
|
constructor(reason, phase, status, message) {
|
|
253
304
|
super(message);
|
|
@@ -258,6 +309,8 @@ var ApiRequestVerifyError = class extends Error {
|
|
|
258
309
|
}
|
|
259
310
|
};
|
|
260
311
|
var invalidProof = (message) => new ApiRequestVerifyError("invalid", "proof", 401, message);
|
|
312
|
+
var replayedProof = (message) => new ApiRequestVerifyError("replayed", "proof", 409, message);
|
|
313
|
+
var uncoveredProof = (message) => new ApiRequestVerifyError("uncovered", "credential", 403, message);
|
|
261
314
|
var unverifiableProof = (message) => new ApiRequestVerifyError("unverifiable", "proof", 503, message);
|
|
262
315
|
var misconfiguredProof = (message) => new ApiRequestVerifyError("config", "config", 500, message);
|
|
263
316
|
var assertProofVerifierConfig = (input) => {
|
|
@@ -319,6 +372,9 @@ var verifyProofEnvelope = async (input, shape, resolvePresenter) => {
|
|
|
319
372
|
} catch (err) {
|
|
320
373
|
throw invalidProof(err instanceof Error ? err.message : `invalid ${shape.label} payload`);
|
|
321
374
|
}
|
|
375
|
+
if (input.requireJti === true && payload.jti === void 0) {
|
|
376
|
+
throw invalidProof(`invalid ${shape.label}: jti is required on this route`);
|
|
377
|
+
}
|
|
322
378
|
const now = Math.floor((input.now ? input.now() : Date.now()) / 1e3);
|
|
323
379
|
if (now - payload.iat > window) throw invalidProof(`${shape.label} is stale`);
|
|
324
380
|
if (payload.iat - now > skew) {
|
|
@@ -362,7 +418,15 @@ var verifyProofEnvelope = async (input, shape, resolvePresenter) => {
|
|
|
362
418
|
} catch (err) {
|
|
363
419
|
throw invalidProof(err instanceof Error ? err.message : `invalid ${shape.label} signature`);
|
|
364
420
|
}
|
|
365
|
-
return {
|
|
421
|
+
return {
|
|
422
|
+
payload,
|
|
423
|
+
rawPayload,
|
|
424
|
+
presenterDID,
|
|
425
|
+
kid,
|
|
426
|
+
now,
|
|
427
|
+
...payload.jti !== void 0 ? { jti: payload.jti } : {},
|
|
428
|
+
...key.roles !== void 0 ? { keyRoles: key.roles } : {}
|
|
429
|
+
};
|
|
366
430
|
};
|
|
367
431
|
var verifyIdentityProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, IDENTITY_PROOF_SHAPE, resolvePresenter);
|
|
368
432
|
var verifyRequestProofEnvelope = (input, resolvePresenter) => verifyProofEnvelope(input, REQUEST_PROOF_SHAPE, resolvePresenter);
|
|
@@ -538,6 +602,26 @@ var parseResource = (resource) => {
|
|
|
538
602
|
if (colonIdx < 0) return null;
|
|
539
603
|
return { type: resource.substring(0, colonIdx), id: resource.substring(colonIdx + 1) };
|
|
540
604
|
};
|
|
605
|
+
var SPACE_ID_RE = /^[2346789acdefhknrtvz]{31}$/;
|
|
606
|
+
var parseApiResource = (resource) => {
|
|
607
|
+
const parsed = parseResource(resource);
|
|
608
|
+
if (!parsed || parsed.type !== "api") return null;
|
|
609
|
+
const slash = parsed.id.indexOf("/");
|
|
610
|
+
if (slash < 0) return parsed.id === "" ? null : { host: parsed.id };
|
|
611
|
+
const host = parsed.id.substring(0, slash);
|
|
612
|
+
if (host === "") return null;
|
|
613
|
+
const child = parsed.id.substring(slash + 1);
|
|
614
|
+
if (!child.startsWith("spaces/")) return null;
|
|
615
|
+
const spaceId = child.substring("spaces/".length);
|
|
616
|
+
if (!SPACE_ID_RE.test(spaceId)) return null;
|
|
617
|
+
return { host, spaceId };
|
|
618
|
+
};
|
|
619
|
+
var apiResourceCovers = (entryResource, requiredResource) => {
|
|
620
|
+
const entry = parseApiResource(entryResource);
|
|
621
|
+
const required = parseApiResource(requiredResource);
|
|
622
|
+
if (!entry || !required || entry.host !== required.host) return false;
|
|
623
|
+
return entry.spaceId === void 0 || entry.spaceId === required.spaceId;
|
|
624
|
+
};
|
|
541
625
|
var ASCII_TRIM_RE = /^[\t\n\v\f\r ]+|[\t\n\v\f\r ]+$/g;
|
|
542
626
|
var parseActions = (action) => new Set(
|
|
543
627
|
action.split(",").map((a) => a.replace(ASCII_TRIM_RE, "")).filter((a) => a !== "")
|
|
@@ -563,6 +647,9 @@ var isAttenuated = (parentAtt, childAtt) => {
|
|
|
563
647
|
if (childRes.type === "chain" && parentRes.type === "chain") {
|
|
564
648
|
return childRes.id === parentRes.id;
|
|
565
649
|
}
|
|
650
|
+
if (childRes.type === "api" && parentRes.type === "api") {
|
|
651
|
+
return apiResourceCovers(parentEntry.resource, childEntry.resource) || childEntry.resource === parentEntry.resource;
|
|
652
|
+
}
|
|
566
653
|
if (childRes.type !== "chain" && parentRes.type !== "chain") {
|
|
567
654
|
return childEntry.resource === parentEntry.resource;
|
|
568
655
|
}
|
|
@@ -589,6 +676,10 @@ var matchesResource = async (att, resource, action) => {
|
|
|
589
676
|
if (entryRes.type === "chain" && entryRes.id === "*" && requestedRes.type === "chain") {
|
|
590
677
|
return true;
|
|
591
678
|
}
|
|
679
|
+
if (entryRes.type === "api" || requestedRes.type === "api") {
|
|
680
|
+
if (apiResourceCovers(entry.resource, resource)) return true;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
592
683
|
if (entryRes.type === requestedRes.type && entryRes.id === requestedRes.id) {
|
|
593
684
|
return true;
|
|
594
685
|
}
|
|
@@ -623,11 +714,13 @@ export {
|
|
|
623
714
|
IDENTITY_PROOF_JWS_TYP,
|
|
624
715
|
EMPTY_BODY_SHA256,
|
|
625
716
|
MAX_REQUEST_PROOF_SIZE,
|
|
717
|
+
MAX_JTI_BYTES,
|
|
626
718
|
DEFAULT_PROOF_WINDOW_SECONDS,
|
|
627
719
|
DEFAULT_PROOF_SKEW_SECONDS,
|
|
628
720
|
MAX_PROOF_FRESHNESS_SPAN_SECONDS,
|
|
629
721
|
MAX_BODY_BYTES,
|
|
630
722
|
DFOS_AUTH_SCHEME,
|
|
723
|
+
generateJti,
|
|
631
724
|
canonicalExtraMembers,
|
|
632
725
|
apiRequestSigningInput,
|
|
633
726
|
apiIdentitySigningInput,
|
|
@@ -639,6 +732,8 @@ export {
|
|
|
639
732
|
buildApiIdentityHeaders,
|
|
640
733
|
ApiRequestVerifyError,
|
|
641
734
|
invalidProof,
|
|
735
|
+
replayedProof,
|
|
736
|
+
uncoveredProof,
|
|
642
737
|
unverifiableProof,
|
|
643
738
|
misconfiguredProof,
|
|
644
739
|
assertProofVerifierConfig,
|
|
@@ -647,6 +742,8 @@ export {
|
|
|
647
742
|
createDFOSCredential,
|
|
648
743
|
verifyDFOSCredential,
|
|
649
744
|
verifyDelegationChain,
|
|
745
|
+
parseApiResource,
|
|
746
|
+
apiResourceCovers,
|
|
650
747
|
isAttenuated,
|
|
651
748
|
matchesResource,
|
|
652
749
|
decodeDFOSCredentialUnsafe,
|