@forestrie/mcp-register 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +126 -2
- package/bin/mcp-register.mjs +2 -0
- package/dist/core/base64.d.ts +7 -0
- package/dist/core/base64.d.ts.map +1 -0
- package/dist/core/base64.js +20 -0
- package/dist/core/base64.js.map +1 -0
- package/dist/core/delegation.d.ts +56 -0
- package/dist/core/delegation.d.ts.map +1 -0
- package/dist/core/delegation.js +130 -0
- package/dist/core/delegation.js.map +1 -0
- package/dist/core/grant.d.ts +41 -0
- package/dist/core/grant.d.ts.map +1 -0
- package/dist/core/grant.js +78 -0
- package/dist/core/grant.js.map +1 -0
- package/dist/core/hex.d.ts +4 -0
- package/dist/core/hex.d.ts.map +1 -0
- package/dist/core/hex.js +18 -0
- package/dist/core/hex.js.map +1 -0
- package/dist/core/index.d.ts +28 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +21 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/issue.d.ts +38 -0
- package/dist/core/issue.d.ts.map +1 -0
- package/dist/core/issue.js +135 -0
- package/dist/core/issue.js.map +1 -0
- package/dist/core/limits.d.ts +9 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +9 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/scrapi.d.ts +66 -0
- package/dist/core/scrapi.d.ts.map +1 -0
- package/dist/core/scrapi.js +174 -0
- package/dist/core/scrapi.js.map +1 -0
- package/dist/core/signer.d.ts +24 -0
- package/dist/core/signer.d.ts.map +1 -0
- package/dist/core/signer.js +2 -0
- package/dist/core/signer.js.map +1 -0
- package/dist/core/statement.d.ts +26 -0
- package/dist/core/statement.d.ts.map +1 -0
- package/dist/core/statement.js +70 -0
- package/dist/core/statement.js.map +1 -0
- package/dist/core/version.d.ts +23 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +23 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/x402.d.ts +99 -0
- package/dist/core/x402.d.ts.map +1 -0
- package/dist/core/x402.js +202 -0
- package/dist/core/x402.js.map +1 -0
- package/dist/net/delegate.d.ts +56 -0
- package/dist/net/delegate.d.ts.map +1 -0
- package/dist/net/delegate.js +182 -0
- package/dist/net/delegate.js.map +1 -0
- package/dist/net/http.d.ts +17 -0
- package/dist/net/http.d.ts.map +1 -0
- package/dist/net/http.js +52 -0
- package/dist/net/http.js.map +1 -0
- package/dist/net/index.d.ts +14 -0
- package/dist/net/index.d.ts.map +1 -0
- package/dist/net/index.js +13 -0
- package/dist/net/index.js.map +1 -0
- package/dist/net/write.d.ts +55 -0
- package/dist/net/write.d.ts.map +1 -0
- package/dist/net/write.js +46 -0
- package/dist/net/write.js.map +1 -0
- package/dist/node/cli.d.ts +8 -0
- package/dist/node/cli.d.ts.map +1 -0
- package/dist/node/cli.js +143 -0
- package/dist/node/cli.js.map +1 -0
- package/dist/node/grant-store.d.ts +49 -0
- package/dist/node/grant-store.d.ts.map +1 -0
- package/dist/node/grant-store.js +95 -0
- package/dist/node/grant-store.js.map +1 -0
- package/dist/node/grant-tools.d.ts +29 -0
- package/dist/node/grant-tools.d.ts.map +1 -0
- package/dist/node/grant-tools.js +498 -0
- package/dist/node/grant-tools.js.map +1 -0
- package/dist/node/ledger.d.ts +26 -0
- package/dist/node/ledger.d.ts.map +1 -0
- package/dist/node/ledger.js +78 -0
- package/dist/node/ledger.js.map +1 -0
- package/dist/node/payer.d.ts +48 -0
- package/dist/node/payer.d.ts.map +1 -0
- package/dist/node/payer.js +170 -0
- package/dist/node/payer.js.map +1 -0
- package/dist/node/resolve-input.d.ts +19 -0
- package/dist/node/resolve-input.d.ts.map +1 -0
- package/dist/node/resolve-input.js +76 -0
- package/dist/node/resolve-input.js.map +1 -0
- package/dist/node/server.d.ts +47 -0
- package/dist/node/server.d.ts.map +1 -0
- package/dist/node/server.js +212 -0
- package/dist/node/server.js.map +1 -0
- package/dist/node/signer-file.d.ts +34 -0
- package/dist/node/signer-file.d.ts.map +1 -0
- package/dist/node/signer-file.js +183 -0
- package/dist/node/signer-file.js.map +1 -0
- package/dist/node/store.d.ts +40 -0
- package/dist/node/store.d.ts.map +1 -0
- package/dist/node/store.js +117 -0
- package/dist/node/store.js.map +1 -0
- package/dist/node/text.d.ts +29 -0
- package/dist/node/text.d.ts.map +1 -0
- package/dist/node/text.js +124 -0
- package/dist/node/text.js.map +1 -0
- package/dist/node/tools.d.ts +118 -0
- package/dist/node/tools.d.ts.map +1 -0
- package/dist/node/tools.js +548 -0
- package/dist/node/tools.js.map +1 -0
- package/package.json +108 -3
- package/src/core/base64.ts +18 -0
- package/src/core/delegation.ts +239 -0
- package/src/core/grant.ts +127 -0
- package/src/core/hex.ts +18 -0
- package/src/core/index.ts +84 -0
- package/src/core/issue.ts +209 -0
- package/src/core/limits.ts +8 -0
- package/src/core/scrapi.ts +236 -0
- package/src/core/signer.ts +23 -0
- package/src/core/statement.ts +133 -0
- package/src/core/version.ts +25 -0
- package/src/core/x402.ts +314 -0
- package/src/net/delegate.ts +274 -0
- package/src/net/http.ts +71 -0
- package/src/net/index.ts +26 -0
- package/src/net/write.ts +124 -0
- package/src/node/cli.ts +170 -0
- package/src/node/grant-store.ts +154 -0
- package/src/node/grant-tools.ts +707 -0
- package/src/node/ledger.ts +98 -0
- package/src/node/payer.ts +241 -0
- package/src/node/resolve-input.ts +96 -0
- package/src/node/server.ts +332 -0
- package/src/node/signer-file.ts +251 -0
- package/src/node/store.ts +175 -0
- package/src/node/text.ts +153 -0
- package/src/node/tools.ts +810 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type GrantView } from "./grant.js";
|
|
2
|
+
import type { Es256Signer } from "./signer.js";
|
|
3
|
+
/** canopy reads at most this much register-grant body (`auth-grant.ts`). */
|
|
4
|
+
export declare const MAX_PARENT_GRANT_BYTES: number;
|
|
5
|
+
/** GF_CREATE|GF_EXTEND (byte 3), GF_DATA_LOG (byte 7): a data-log creation
|
|
6
|
+
* grant, the shape that both opens the child log and carries statements. */
|
|
7
|
+
export declare function dataLogCreateExtendFlags(): Uint8Array;
|
|
8
|
+
export interface IssueGrantInput {
|
|
9
|
+
/** The child data log the grant creates (UUID). */
|
|
10
|
+
logId: string;
|
|
11
|
+
/** The requester's authority log the grant is sequenced into (UUID). */
|
|
12
|
+
ownerLogId: string;
|
|
13
|
+
/** The key the grant names: 64 bytes x‖y of the performer's log key. */
|
|
14
|
+
grantData: Uint8Array;
|
|
15
|
+
/** The batch ceiling canopy prices a paid child grant by. At least 1. */
|
|
16
|
+
maxHeight: number;
|
|
17
|
+
}
|
|
18
|
+
export interface IssuedGrant {
|
|
19
|
+
/** The signed grant, untagged COSE Sign1. */
|
|
20
|
+
grant: Uint8Array;
|
|
21
|
+
/** The canonical grant v0 CBOR that was signed (its SHA-256 is the payload). */
|
|
22
|
+
grantPayload: Uint8Array;
|
|
23
|
+
flags: Uint8Array;
|
|
24
|
+
}
|
|
25
|
+
export declare function issueChildGrant(input: IssueGrantInput, authority: Es256Signer): Promise<IssuedGrant>;
|
|
26
|
+
/**
|
|
27
|
+
* Why `parent` cannot authorise a child grant signed by `authorityXY`, or
|
|
28
|
+
* `[]`. The parent is the requester's own authority grant: an auth-log
|
|
29
|
+
* grant naming the authority key, whose log becomes the child's owner.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parentGrantProblems(parent: GrantView, authorityXY: Uint8Array): string[];
|
|
32
|
+
/** The register-grant request body: canonical CBOR `{parentGrant: <bytes>}`. */
|
|
33
|
+
export declare function registerGrantBody(parentGrant: Uint8Array): Uint8Array;
|
|
34
|
+
/** Merge the sealed receipt and the entry's idtimestamp into the grant. */
|
|
35
|
+
export declare function completeGrant(grant: Uint8Array, receipt: Uint8Array, entryIdHex: string): Uint8Array;
|
|
36
|
+
/** The grant v0 CBOR a grant carries at unprotected -65538: its content. */
|
|
37
|
+
export declare function grantContent(grant: Uint8Array): Uint8Array;
|
|
38
|
+
//# sourceMappingURL=issue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../../src/core/issue.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW/C,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAEhD;6EAC6E;AAC7E,wBAAgB,wBAAwB,IAAI,UAAU,CAKrD;AAED,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,SAAS,EAAE,UAAU,CAAC;IACtB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,KAAK,EAAE,UAAU,CAAC;IAClB,gFAAgF;IAChF,YAAY,EAAE,UAAU,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,eAAe,EACtB,SAAS,EAAE,WAAW,GACrB,OAAO,CAAC,WAAW,CAAC,CAwDtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,UAAU,GACtB,MAAM,EAAE,CAeV;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,CAIrE;AAID,2EAA2E;AAC3E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,MAAM,GACjB,UAAU,CAeZ;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAU1D"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue a child grant as the requester, and complete it once canopy has
|
|
3
|
+
* sealed it.
|
|
4
|
+
*
|
|
5
|
+
* The requester owns an authority log (ARC-0028: the requester funds, the
|
|
6
|
+
* performer writes) and is its own grant issuer: it signs a child grant
|
|
7
|
+
* with its authority key, naming the performer's log key in `grantData`.
|
|
8
|
+
* Forestrie runs no issuer.
|
|
9
|
+
*
|
|
10
|
+
* The bytes are the ES256 profile of `@forestrie/grant-builder`'s
|
|
11
|
+
* `signGrantPayloadWithEs256Pem`, which univocity verifies
|
|
12
|
+
* (`verifyCoseSign1ES256`), with the PEM replaced by the signer seam:
|
|
13
|
+
*
|
|
14
|
+
* - untagged COSE Sign1, protected `{1: -7}` (wire `a10126`);
|
|
15
|
+
* - payload the 32-byte SHA-256 of the canonical grant v0 CBOR;
|
|
16
|
+
* - signature over `["Signature1", protected, h'', payload]`, r‖s;
|
|
17
|
+
* - unprotected `{-65537: 8 zero bytes, -65538: grant v0 CBOR}`, in that
|
|
18
|
+
* canonical order.
|
|
19
|
+
*
|
|
20
|
+
* grant-builder itself is not imported: its index pulls in its node-only
|
|
21
|
+
* PEM module, and this file is browser-safe.
|
|
22
|
+
*
|
|
23
|
+
* Completing a grant merges the receipt (396) and the entry's idtimestamp
|
|
24
|
+
* (-65537) into the unprotected header without re-signing, as forestrie-cli
|
|
25
|
+
* `register-grant-complete.ts` does. The completed grant is the
|
|
26
|
+
* `Forestrie-Grant` credential register_statement and delegate_sealing take.
|
|
27
|
+
*/
|
|
28
|
+
import { appendCborBstr, appendCborText, decodeCoseSign1, encodeCborDeterministic, encodeGrantPayloadV0Canonical, mergeUnprotectedIntoCoseSign1, uuidToBytes, } from "@forestrie/encoding";
|
|
29
|
+
import { GrantError } from "./grant.js";
|
|
30
|
+
import { bytesEqual, sha256 } from "./hex.js";
|
|
31
|
+
const ES256_PROTECTED_HEADER = new Uint8Array([0xa1, 0x01, 0x26]);
|
|
32
|
+
// CBOR negative-int keys (major type 1): -65537, then -65538.
|
|
33
|
+
const CBOR_KEY_IDTIMESTAMP = [0x3a, 0x00, 0x01, 0x00, 0x00];
|
|
34
|
+
const CBOR_KEY_FORESTRIE_GRANT_V0 = [0x3a, 0x00, 0x01, 0x00, 0x01];
|
|
35
|
+
const HEADER_RECEIPT = 396;
|
|
36
|
+
const HEADER_IDTIMESTAMP = -65537;
|
|
37
|
+
/** canopy's register-grant body key for the parent grant evidence. */
|
|
38
|
+
const PARENT_GRANT_BODY_KEY = "parentGrant";
|
|
39
|
+
/** canopy reads at most this much register-grant body (`auth-grant.ts`). */
|
|
40
|
+
export const MAX_PARENT_GRANT_BYTES = 16 * 1024;
|
|
41
|
+
/** GF_CREATE|GF_EXTEND (byte 3), GF_DATA_LOG (byte 7): a data-log creation
|
|
42
|
+
* grant, the shape that both opens the child log and carries statements. */
|
|
43
|
+
export function dataLogCreateExtendFlags() {
|
|
44
|
+
const flags = new Uint8Array(8);
|
|
45
|
+
flags[3] = 0x03;
|
|
46
|
+
flags[7] = 0x02;
|
|
47
|
+
return flags;
|
|
48
|
+
}
|
|
49
|
+
export async function issueChildGrant(input, authority) {
|
|
50
|
+
let logId;
|
|
51
|
+
let ownerLogId;
|
|
52
|
+
try {
|
|
53
|
+
logId = uuidToBytes(input.logId);
|
|
54
|
+
ownerLogId = uuidToBytes(input.ownerLogId);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new GrantError(`logId and ownerLogId must be UUIDs (got ${input.logId}, ${input.ownerLogId})`);
|
|
58
|
+
}
|
|
59
|
+
if (bytesEqual(logId, ownerLogId))
|
|
60
|
+
throw new GrantError("a child grant must name a log other than its owner (logId equals ownerLogId)");
|
|
61
|
+
if (input.grantData.length !== 64)
|
|
62
|
+
throw new GrantError(`grantData must be a 64-byte ES256 x‖y (got ${input.grantData.length} bytes): canopy forwards only ES256 child roots`);
|
|
63
|
+
if (!Number.isSafeInteger(input.maxHeight) || input.maxHeight < 1)
|
|
64
|
+
throw new GrantError(`maxHeight must be a positive integer (got ${input.maxHeight})`);
|
|
65
|
+
const flags = dataLogCreateExtendFlags();
|
|
66
|
+
const grantPayload = encodeGrantPayloadV0Canonical({
|
|
67
|
+
logId,
|
|
68
|
+
ownerLogId,
|
|
69
|
+
grant: flags,
|
|
70
|
+
maxHeight: input.maxHeight,
|
|
71
|
+
minGrowth: 0,
|
|
72
|
+
grantData: input.grantData,
|
|
73
|
+
});
|
|
74
|
+
const payload = await sha256(grantPayload);
|
|
75
|
+
const sig = [0x84];
|
|
76
|
+
appendCborText(sig, "Signature1");
|
|
77
|
+
appendCborBstr(sig, ES256_PROTECTED_HEADER);
|
|
78
|
+
appendCborBstr(sig, new Uint8Array(0));
|
|
79
|
+
appendCborBstr(sig, payload);
|
|
80
|
+
const signature = await authority.sign(new Uint8Array(sig));
|
|
81
|
+
if (signature.length !== 64)
|
|
82
|
+
throw new GrantError(`the authority signer returned ${signature.length} bytes; ES256 needs a 64-byte r‖s`);
|
|
83
|
+
const out = [0x84];
|
|
84
|
+
appendCborBstr(out, ES256_PROTECTED_HEADER);
|
|
85
|
+
out.push(0xa2);
|
|
86
|
+
out.push(...CBOR_KEY_IDTIMESTAMP);
|
|
87
|
+
appendCborBstr(out, new Uint8Array(8));
|
|
88
|
+
out.push(...CBOR_KEY_FORESTRIE_GRANT_V0);
|
|
89
|
+
appendCborBstr(out, grantPayload);
|
|
90
|
+
appendCborBstr(out, payload);
|
|
91
|
+
appendCborBstr(out, signature);
|
|
92
|
+
return { grant: new Uint8Array(out), grantPayload, flags };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Why `parent` cannot authorise a child grant signed by `authorityXY`, or
|
|
96
|
+
* `[]`. The parent is the requester's own authority grant: an auth-log
|
|
97
|
+
* grant naming the authority key, whose log becomes the child's owner.
|
|
98
|
+
*/
|
|
99
|
+
export function parentGrantProblems(parent, authorityXY) {
|
|
100
|
+
const problems = [];
|
|
101
|
+
if (!parent.authLog)
|
|
102
|
+
problems.push(`the parent grant's flags ${parent.flagsHex} are not an authority-log grant (GF_AUTH_LOG); only an authority log can own a child log`);
|
|
103
|
+
if (parent.grantData.length !== 64)
|
|
104
|
+
problems.push(`the parent grant names a ${parent.grantData.length}-byte key; canopy's prepare refuses non-ES256 parents, so the authority key must be ES256`);
|
|
105
|
+
else if (!bytesEqual(parent.grantData, authorityXY))
|
|
106
|
+
problems.push("the parent grant names a different key than this server's authority key, so this key does not own that authority log and cannot issue under it");
|
|
107
|
+
return problems;
|
|
108
|
+
}
|
|
109
|
+
/** The register-grant request body: canonical CBOR `{parentGrant: <bytes>}`. */
|
|
110
|
+
export function registerGrantBody(parentGrant) {
|
|
111
|
+
return encodeCborDeterministic(new Map([[PARENT_GRANT_BODY_KEY, parentGrant]]));
|
|
112
|
+
}
|
|
113
|
+
const ENTRY_ID = /^[0-9a-f]{32}$/;
|
|
114
|
+
/** Merge the sealed receipt and the entry's idtimestamp into the grant. */
|
|
115
|
+
export function completeGrant(grant, receipt, entryIdHex) {
|
|
116
|
+
if (!ENTRY_ID.test(entryIdHex))
|
|
117
|
+
throw new GrantError(`entryId must be 32 lowercase hex (got ${entryIdHex})`);
|
|
118
|
+
const idtimestamp = new Uint8Array(8);
|
|
119
|
+
for (let i = 0; i < 8; i++)
|
|
120
|
+
idtimestamp[i] = parseInt(entryIdHex.slice(i * 2, i * 2 + 2), 16);
|
|
121
|
+
return mergeUnprotectedIntoCoseSign1(grant, new Map([
|
|
122
|
+
[HEADER_RECEIPT, receipt],
|
|
123
|
+
[HEADER_IDTIMESTAMP, idtimestamp],
|
|
124
|
+
]));
|
|
125
|
+
}
|
|
126
|
+
/** The grant v0 CBOR a grant carries at unprotected -65538: its content. */
|
|
127
|
+
export function grantContent(grant) {
|
|
128
|
+
const sign1 = decodeCoseSign1(grant);
|
|
129
|
+
const unprotected = sign1?.unprotected;
|
|
130
|
+
const content = unprotected instanceof Map ? unprotected.get(-65538) : undefined;
|
|
131
|
+
if (!(content instanceof Uint8Array) || content.length === 0)
|
|
132
|
+
throw new GrantError("not a Forestrie-Grant: no grant v0 CBOR at unprotected label -65538");
|
|
133
|
+
return content;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../src/core/issue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canopy's statement size limit, `maxStatementSize` in its SCITT
|
|
3
|
+
* configuration (canopy `scrapi/transparency-configuration.ts`): 4 MiB,
|
|
4
|
+
* checked against the signed statement's Content-Length. The limit is on
|
|
5
|
+
* the COSE Sign1 as sent, so the payload must leave room for the headers
|
|
6
|
+
* and the signature.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_STATEMENT_BYTES: number;
|
|
9
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,QAAkB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canopy's statement size limit, `maxStatementSize` in its SCITT
|
|
3
|
+
* configuration (canopy `scrapi/transparency-configuration.ts`): 4 MiB,
|
|
4
|
+
* checked against the signed statement's Content-Length. The limit is on
|
|
5
|
+
* the COSE Sign1 as sent, so the payload must leave room for the headers
|
|
6
|
+
* and the signature.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_STATEMENT_BYTES = 4 * 1024 * 1024;
|
|
9
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The parts of a response these readers look at. Headers keyed lowercase. */
|
|
2
|
+
export interface ResponseView {
|
|
3
|
+
url: string;
|
|
4
|
+
status: number;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
body: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
export interface Problem {
|
|
9
|
+
status: number;
|
|
10
|
+
title: string;
|
|
11
|
+
detail?: string;
|
|
12
|
+
reason?: string;
|
|
13
|
+
/** From the response's Retry-After, e.g. canopy's queue-full 503. */
|
|
14
|
+
retryAfterSeconds?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function readProblem(res: ResponseView): Problem;
|
|
17
|
+
/** The origin of a base URL, or `undefined` when it does not parse. */
|
|
18
|
+
export declare function originOf(baseUrl: string): string | undefined;
|
|
19
|
+
export type RegisterOutcome = {
|
|
20
|
+
kind: "accepted";
|
|
21
|
+
statusUrl: string;
|
|
22
|
+
retryAfterSeconds?: number;
|
|
23
|
+
} | {
|
|
24
|
+
kind: "problem";
|
|
25
|
+
problem: Problem;
|
|
26
|
+
};
|
|
27
|
+
export declare function readRegisterResponse(res: ResponseView, origin?: string): RegisterOutcome;
|
|
28
|
+
export type RegisterGrantOutcome = RegisterOutcome | {
|
|
29
|
+
kind: "payment_required";
|
|
30
|
+
/** The raw `X-PAYMENT-REQUIRED` header (base64 JSON), undecoded. */
|
|
31
|
+
paymentRequiredHeader: string;
|
|
32
|
+
problem: Problem;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* canopy's answer to `POST /register/{R}/grants`: 303 to the grant's status
|
|
36
|
+
* URL, or 402 with an `X-PAYMENT-REQUIRED` challenge when the parent grant
|
|
37
|
+
* makes this child payment-gated (and again, with canopy's reason, when a
|
|
38
|
+
* presented `X-PAYMENT` is refused). A 402 without the header is a problem.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readRegisterGrantResponse(res: ResponseView, origin?: string): RegisterGrantOutcome;
|
|
41
|
+
export type StatusOutcome = {
|
|
42
|
+
kind: "pending";
|
|
43
|
+
retryAfterSeconds: number;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "sequenced";
|
|
46
|
+
receiptUrl: string;
|
|
47
|
+
massifHeight: number;
|
|
48
|
+
entryId: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: "problem";
|
|
51
|
+
problem: Problem;
|
|
52
|
+
};
|
|
53
|
+
export declare function readStatusResponse(res: ResponseView, origin?: string): StatusOutcome;
|
|
54
|
+
export type ReceiptOutcome = {
|
|
55
|
+
kind: "sealed";
|
|
56
|
+
receipt: Uint8Array;
|
|
57
|
+
contentType?: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "awaiting_seal";
|
|
60
|
+
reason: string;
|
|
61
|
+
} | {
|
|
62
|
+
kind: "problem";
|
|
63
|
+
problem: Problem;
|
|
64
|
+
};
|
|
65
|
+
export declare function readReceiptResponse(res: ResponseView): ReceiptOutcome;
|
|
66
|
+
//# sourceMappingURL=scrapi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapi.d.ts","sourceRoot":"","sources":["../../src/core/scrapi.ts"],"names":[],"mappings":"AAmBA,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAkCtD;AAqCD,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM5D;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1C,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,eAAe,CAYjB;AAED,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,oEAAoE;IACpE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEN;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,oBAAoB,CAStB;AAID,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAC9C;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1C,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,aAAa,CAcf;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,UAAU,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAY1C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,cAAc,CAgBrE"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read canopy's answers to the write path, over already-fetched responses.
|
|
3
|
+
*
|
|
4
|
+
* The registration contract (canopy `scrapi/register-signed-statement.ts`,
|
|
5
|
+
* `scrapi/query-registration-status.ts`, `scrapi/resolve-receipt.ts`):
|
|
6
|
+
*
|
|
7
|
+
* - `POST /register/{R}/entries` answers 303 with `Location` = the
|
|
8
|
+
* transient status URL `…/logs/{R}/{T}/entries/{sha256(statement)}`.
|
|
9
|
+
* - The status URL answers 303 to itself (`Retry-After`) until the entry is
|
|
10
|
+
* sequenced, then 303 to the permanent receipt URL
|
|
11
|
+
* `…/logs/{R}/{T}/{massifHeight}/entries/{entryId}/receipt`.
|
|
12
|
+
* - The receipt URL answers 404 "checkpoint missing" (or "checkpoint does
|
|
13
|
+
* not cover entry") until a checkpoint covering the entry is sealed,
|
|
14
|
+
* then 200 with the receipt. Other 404s are faults.
|
|
15
|
+
* - Failures are `application/problem+cbor` maps `{title, status, detail?,
|
|
16
|
+
* reason?}`.
|
|
17
|
+
*/
|
|
18
|
+
import { decodeCborDeterministic } from "@forestrie/encoding";
|
|
19
|
+
export function readProblem(res) {
|
|
20
|
+
const retry = retryAfter(res);
|
|
21
|
+
const base = {
|
|
22
|
+
status: res.status,
|
|
23
|
+
title: `HTTP ${res.status}`,
|
|
24
|
+
...(retry !== undefined ? { retryAfterSeconds: retry } : {}),
|
|
25
|
+
};
|
|
26
|
+
if (res.body.length === 0)
|
|
27
|
+
return base;
|
|
28
|
+
try {
|
|
29
|
+
const decoded = decodeCborDeterministic(res.body);
|
|
30
|
+
const get = (k) => decoded instanceof Map
|
|
31
|
+
? decoded.get(k)
|
|
32
|
+
: typeof decoded === "object" && decoded !== null
|
|
33
|
+
? decoded[k]
|
|
34
|
+
: undefined;
|
|
35
|
+
const title = get("title");
|
|
36
|
+
const detail = get("detail");
|
|
37
|
+
const reason = get("reason");
|
|
38
|
+
if (typeof title === "string")
|
|
39
|
+
return {
|
|
40
|
+
...base,
|
|
41
|
+
title,
|
|
42
|
+
...(typeof detail === "string" ? { detail } : {}),
|
|
43
|
+
...(typeof reason === "string" ? { reason } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Not CBOR: fall through to printable text.
|
|
48
|
+
}
|
|
49
|
+
const text = new TextDecoder()
|
|
50
|
+
.decode(res.body)
|
|
51
|
+
.replace(/[^\x20-\x7e]+/g, " ")
|
|
52
|
+
.trim();
|
|
53
|
+
return text ? { ...base, detail: text.slice(0, 200) } : base;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a Location against the URL that answered, and keep it on the
|
|
57
|
+
* expected origin: a status or receipt URL that points elsewhere is not
|
|
58
|
+
* followed (the caller would otherwise fetch wherever a server says).
|
|
59
|
+
*/
|
|
60
|
+
function follow(location, res, origin) {
|
|
61
|
+
let url;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(location, res.url);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
problem: {
|
|
69
|
+
status: res.status,
|
|
70
|
+
title: "Invalid Location",
|
|
71
|
+
detail: `the Location header is not a URL: ${location.slice(0, 200)}`,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (origin !== undefined && url.origin !== origin)
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
problem: {
|
|
79
|
+
status: res.status,
|
|
80
|
+
title: "Unexpected Location",
|
|
81
|
+
detail: `the Location header points to ${url.origin}, not ${origin}; not following it`,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
return { ok: true, url: url.toString() };
|
|
85
|
+
}
|
|
86
|
+
/** The origin of a base URL, or `undefined` when it does not parse. */
|
|
87
|
+
export function originOf(baseUrl) {
|
|
88
|
+
try {
|
|
89
|
+
return new URL(baseUrl).origin;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function readRegisterResponse(res, origin) {
|
|
96
|
+
if (res.status === 303 && res.headers["location"]) {
|
|
97
|
+
const next = follow(res.headers["location"], res, origin);
|
|
98
|
+
if (!next.ok)
|
|
99
|
+
return { kind: "problem", problem: next.problem };
|
|
100
|
+
const retry = retryAfter(res);
|
|
101
|
+
return {
|
|
102
|
+
kind: "accepted",
|
|
103
|
+
statusUrl: next.url,
|
|
104
|
+
...(retry !== undefined ? { retryAfterSeconds: retry } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { kind: "problem", problem: readProblem(res) };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* canopy's answer to `POST /register/{R}/grants`: 303 to the grant's status
|
|
111
|
+
* URL, or 402 with an `X-PAYMENT-REQUIRED` challenge when the parent grant
|
|
112
|
+
* makes this child payment-gated (and again, with canopy's reason, when a
|
|
113
|
+
* presented `X-PAYMENT` is refused). A 402 without the header is a problem.
|
|
114
|
+
*/
|
|
115
|
+
export function readRegisterGrantResponse(res, origin) {
|
|
116
|
+
const header = res.headers["x-payment-required"];
|
|
117
|
+
if (res.status === 402 && header)
|
|
118
|
+
return {
|
|
119
|
+
kind: "payment_required",
|
|
120
|
+
paymentRequiredHeader: header,
|
|
121
|
+
problem: readProblem(res),
|
|
122
|
+
};
|
|
123
|
+
return readRegisterResponse(res, origin);
|
|
124
|
+
}
|
|
125
|
+
const RECEIPT_PATH = /\/(\d+)\/entries\/([0-9a-f]{32})\/receipt$/;
|
|
126
|
+
export function readStatusResponse(res, origin) {
|
|
127
|
+
if (res.status !== 303)
|
|
128
|
+
return { kind: "problem", problem: readProblem(res) };
|
|
129
|
+
const next = follow(res.headers["location"] ?? res.url, res, origin);
|
|
130
|
+
if (!next.ok)
|
|
131
|
+
return { kind: "problem", problem: next.problem };
|
|
132
|
+
const match = RECEIPT_PATH.exec(new URL(next.url).pathname);
|
|
133
|
+
if (match)
|
|
134
|
+
return {
|
|
135
|
+
kind: "sequenced",
|
|
136
|
+
receiptUrl: next.url,
|
|
137
|
+
massifHeight: Number(match[1]),
|
|
138
|
+
entryId: match[2],
|
|
139
|
+
};
|
|
140
|
+
return { kind: "pending", retryAfterSeconds: retryAfter(res) ?? 1 };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The 404s that mean "sequenced, not yet sealed": no checkpoint yet, or one
|
|
144
|
+
* that predates the entry (canopy `scrapi/resolve-receipt.ts`). canopy
|
|
145
|
+
* answers 404 for other reasons too — a missing massif, a height or index
|
|
146
|
+
* mismatch, an unknown forest — and those will not fix themselves by
|
|
147
|
+
* waiting, so they are problems, with canopy's reason.
|
|
148
|
+
*/
|
|
149
|
+
const AWAITING_SEAL = /checkpoint missing\)$|checkpoint does not cover entry\)$/;
|
|
150
|
+
export function readReceiptResponse(res) {
|
|
151
|
+
if (res.status === 404) {
|
|
152
|
+
const problem = readProblem(res);
|
|
153
|
+
if (AWAITING_SEAL.test(problem.title))
|
|
154
|
+
return { kind: "awaiting_seal", reason: problem.title };
|
|
155
|
+
return { kind: "problem", problem };
|
|
156
|
+
}
|
|
157
|
+
if (res.status === 200 && res.body.length > 0)
|
|
158
|
+
return {
|
|
159
|
+
kind: "sealed",
|
|
160
|
+
receipt: res.body,
|
|
161
|
+
...(res.headers["content-type"]
|
|
162
|
+
? { contentType: res.headers["content-type"] }
|
|
163
|
+
: {}),
|
|
164
|
+
};
|
|
165
|
+
return { kind: "problem", problem: readProblem(res) };
|
|
166
|
+
}
|
|
167
|
+
function retryAfter(res) {
|
|
168
|
+
const raw = res.headers["retry-after"];
|
|
169
|
+
if (raw === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
const n = Number(raw);
|
|
172
|
+
return Number.isFinite(n) && n >= 0 ? n : undefined;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=scrapi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrapi.js","sourceRoot":"","sources":["../../src/core/scrapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signer seam: every signature this package makes goes through one
|
|
3
|
+
* `Es256Signer`. Core code builds the exact bytes to be signed and hands
|
|
4
|
+
* them over; it never sees a private key, and no signature byte ever
|
|
5
|
+
* arrives from a tool argument. Where the key lives — a local key file
|
|
6
|
+
* (the default, `src/node/signer-file.ts`), a KMS, a remote signer — is the
|
|
7
|
+
* backend's business.
|
|
8
|
+
*
|
|
9
|
+
* ES256 only: canopy's child-log paths (register-grant auto-forward and
|
|
10
|
+
* `POST /api/forest/{child}/prepare`) accept 64-byte P-256 roots only, so a
|
|
11
|
+
* KS256 log key could not delegate its own sealing without an operator.
|
|
12
|
+
*/
|
|
13
|
+
export interface Es256Signer {
|
|
14
|
+
/** The P-256 public key as 64 bytes x‖y — what a grant's `grantData` names. */
|
|
15
|
+
publicKeyXY(): Promise<Uint8Array>;
|
|
16
|
+
/**
|
|
17
|
+
* ECDSA P-256 with SHA-256 over `message` (the signer hashes), returned as
|
|
18
|
+
* IEEE P1363 r‖s, 64 bytes. For COSE that message is the Sig_structure.
|
|
19
|
+
*/
|
|
20
|
+
sign(message: Uint8Array): Promise<Uint8Array>;
|
|
21
|
+
/** Where the key lives, for humans: never key material. */
|
|
22
|
+
readonly description: string;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=signer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/core/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IACnC;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/core/signer.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Es256Signer } from "./signer.js";
|
|
2
|
+
export declare class StatementError extends Error {
|
|
3
|
+
readonly code: "invalid_input" | "too_large" | "signer_failed";
|
|
4
|
+
constructor(code: "invalid_input" | "too_large" | "signer_failed", message: string);
|
|
5
|
+
}
|
|
6
|
+
export interface SignStatementInput {
|
|
7
|
+
payload: Uint8Array;
|
|
8
|
+
/** The payload's media type, carried in protected header 3. */
|
|
9
|
+
contentType: string;
|
|
10
|
+
/** CWT iss; defaults to the hex of the kid. */
|
|
11
|
+
issuer?: string;
|
|
12
|
+
/** CWT sub; defaults to `sha-256:<hex of payload>`, as forestrie-cli does. */
|
|
13
|
+
subject?: string;
|
|
14
|
+
/** CWT iat, seconds since the epoch; omitted when absent. */
|
|
15
|
+
issuedAt?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface SignedStatement {
|
|
18
|
+
statement: Uint8Array;
|
|
19
|
+
/** sha256 of the statement: the id in canopy's status URL. */
|
|
20
|
+
contentHash: string;
|
|
21
|
+
kid: Uint8Array;
|
|
22
|
+
issuer: string;
|
|
23
|
+
subject: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function signStatement(input: SignStatementInput, signer: Es256Signer): Promise<SignedStatement>;
|
|
26
|
+
//# sourceMappingURL=statement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statement.d.ts","sourceRoot":"","sources":["../../src/core/statement.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,GAAG,eAAe;gBAArD,IAAI,EAAE,eAAe,GAAG,WAAW,GAAG,eAAe,EAC9D,OAAO,EAAE,MAAM;CAIlB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,UAAU,CAAC;IACpB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,UAAU,CAAC;IACtB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,UAAU,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CA0E1B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build and sign the statement canopy registers: an untagged COSE Sign1,
|
|
3
|
+
* payload attached, protected header `{1: -7, 3: cty, 4: kid, 15: {1: iss,
|
|
4
|
+
* 2: sub}}`, empty unprotected header.
|
|
5
|
+
*
|
|
6
|
+
* Byte-for-byte the construction of `@forestrie/encoding`'s
|
|
7
|
+
* `signCoseSign1Statement` (the one forestrie-cli uses), with the
|
|
8
|
+
* WebCrypto call replaced by the signer seam: the same protected map, the
|
|
9
|
+
* same Sig_structure, the same envelope encoder.
|
|
10
|
+
*
|
|
11
|
+
* kid is the key's 32-byte x coordinate, the binding canopy checks first
|
|
12
|
+
* (canopy `grant/statement-signer-binding.ts`).
|
|
13
|
+
*/
|
|
14
|
+
import { COSE_ALG_ES256, encodeCoseProtectedMapBytes, encodeCoseSign1Statement, encodeSigStructure, verifyCoseSign1WithParsedKey, } from "@forestrie/encoding";
|
|
15
|
+
import { MAX_STATEMENT_BYTES } from "./limits.js";
|
|
16
|
+
import { sha256, toHex } from "./hex.js";
|
|
17
|
+
export class StatementError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
constructor(code, message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function signStatement(input, signer) {
|
|
25
|
+
if (input.payload.length === 0)
|
|
26
|
+
throw new StatementError("invalid_input", "payload is empty");
|
|
27
|
+
if (input.payload.length > MAX_STATEMENT_BYTES)
|
|
28
|
+
throw new StatementError("too_large", `payload is ${input.payload.length} bytes; canopy accepts signed statements up to ${MAX_STATEMENT_BYTES} bytes. Register a small manifest naming the artefact's digest instead`);
|
|
29
|
+
if (!input.contentType.trim())
|
|
30
|
+
throw new StatementError("invalid_input", "contentType is empty");
|
|
31
|
+
const xy = await signer.publicKeyXY();
|
|
32
|
+
if (xy.length !== 64)
|
|
33
|
+
throw new StatementError("signer_failed", `signer returned a ${xy.length}-byte public key; ES256 needs 64 bytes x‖y`);
|
|
34
|
+
const kid = xy.slice(0, 32);
|
|
35
|
+
const issuer = input.issuer ?? toHex(kid);
|
|
36
|
+
const subject = input.subject ?? `sha-256:${toHex(await sha256(input.payload))}`;
|
|
37
|
+
const options = {
|
|
38
|
+
alg: COSE_ALG_ES256,
|
|
39
|
+
cty: input.contentType,
|
|
40
|
+
cwtClaims: {
|
|
41
|
+
iss: issuer,
|
|
42
|
+
sub: subject,
|
|
43
|
+
...(input.issuedAt !== undefined ? { iat: input.issuedAt } : {}),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const sigStructure = encodeSigStructure(encodeCoseProtectedMapBytes(kid, options), new Uint8Array(0), input.payload);
|
|
47
|
+
const signature = await signer.sign(sigStructure);
|
|
48
|
+
if (signature.length !== 64)
|
|
49
|
+
throw new StatementError("signer_failed", `signer returned a ${signature.length}-byte signature; ES256 is 64 bytes r‖s`);
|
|
50
|
+
const statement = encodeCoseSign1Statement(input.payload, kid, signature, options);
|
|
51
|
+
// Check our own work before canopy does: a signer that signs with a key
|
|
52
|
+
// other than the one it reports would otherwise surface as a bare 403.
|
|
53
|
+
const verified = await verifyCoseSign1WithParsedKey(statement, {
|
|
54
|
+
x: xy.slice(0, 32),
|
|
55
|
+
y: xy.slice(32),
|
|
56
|
+
curve: "P-256",
|
|
57
|
+
});
|
|
58
|
+
if (!verified)
|
|
59
|
+
throw new StatementError("signer_failed", "the signature does not verify under the signer's own public key");
|
|
60
|
+
if (statement.length > MAX_STATEMENT_BYTES)
|
|
61
|
+
throw new StatementError("too_large", `the signed statement is ${statement.length} bytes; canopy's limit is ${MAX_STATEMENT_BYTES}`);
|
|
62
|
+
return {
|
|
63
|
+
statement,
|
|
64
|
+
contentHash: toHex(await sha256(statement)),
|
|
65
|
+
kid,
|
|
66
|
+
issuer,
|
|
67
|
+
subject,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=statement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statement.js","sourceRoot":"","sources":["../../src/core/statement.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version strings, as literals.
|
|
3
|
+
*
|
|
4
|
+
* Not read from `package.json`: `src/core` is browser-safe, so it has no
|
|
5
|
+
* `node:fs`, and a JSON import would bake a resolveJsonModule edge into the
|
|
6
|
+
* `"."` bundle for a handful of strings. The MCP smoke test asserts
|
|
7
|
+
* `PACKAGE_VERSION === package.json#version`, so a forgotten bump is a red
|
|
8
|
+
* test rather than a silent lie in `initialize`'s serverInfo.
|
|
9
|
+
*
|
|
10
|
+
* Only the versions this package reports (`--help`, `initialize`) live here.
|
|
11
|
+
* Bump each one in the same commit as the dependency it names.
|
|
12
|
+
*/
|
|
13
|
+
/** Keep in sync with package.json#version. Asserted by test/node/mcp-smoke. */
|
|
14
|
+
export declare const PACKAGE_VERSION = "0.1.0";
|
|
15
|
+
/** Keep in sync with package.json#dependencies["@forestrie/mcp-verify"] — the
|
|
16
|
+
* verifier this package composes with. Asserted by
|
|
17
|
+
* test/core/version.test.ts. */
|
|
18
|
+
export declare const VERIFIER_VERSION = "0.5.0";
|
|
19
|
+
/** Keep in sync with package.json#dependencies["@forestrie/mcp-resolve"] —
|
|
20
|
+
* the resolver this package composes with. Asserted by
|
|
21
|
+
* test/core/version.test.ts. */
|
|
22
|
+
export declare const RESOLVE_VERSION = "0.3.0";
|
|
23
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC;;iCAEiC;AACjC,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC;;iCAEiC;AACjC,eAAO,MAAM,eAAe,UAAU,CAAC"}
|