@calimero-network/mero-js 19.24.1 → 19.26.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/admin-api/admin-client.d.ts +33 -1
- package/dist/admin-api/admin-client.js +47 -0
- package/dist/admin-api/admin-types.d.ts +96 -0
- package/dist/admin-api/admin-types.js +0 -2
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +152 -5
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +152 -5
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +2 -0
- package/dist/sealed/index.js +1 -0
- package/dist/sealed/noise.js +11 -3
- package/dist/sealed/sealed.d.ts +14 -6
- package/dist/sealed/sealed.js +8 -1
- package/dist/sealed/verify.d.ts +109 -0
- package/dist/sealed/verify.js +116 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -67,6 +67,7 @@ __export(index_exports, {
|
|
|
67
67
|
createNodeAuthApiClient: () => createNodeAuthApiClient,
|
|
68
68
|
createNodeHttpClient: () => createNodeHttpClient,
|
|
69
69
|
createProofSigner: () => createProofSigner,
|
|
70
|
+
createQuoteVerifier: () => createQuoteVerifier,
|
|
70
71
|
createRetryableMethod: () => createRetryableMethod,
|
|
71
72
|
createSealedFetch: () => createSealedFetch,
|
|
72
73
|
createTimeoutSignal: () => createTimeoutSignal,
|
|
@@ -929,6 +930,14 @@ var AdminApiClient = class {
|
|
|
929
930
|
async healthCheck() {
|
|
930
931
|
return unwrap(await this.httpClient.get("/admin-api/health"));
|
|
931
932
|
}
|
|
933
|
+
/**
|
|
934
|
+
* Whether the node has finished starting and its store answers. Public, like
|
|
935
|
+
* {@link healthCheck}; unlike it, a node that is up but not ready answers
|
|
936
|
+
* `503`, which this surfaces as an `HTTPError` whose body names the stage.
|
|
937
|
+
*/
|
|
938
|
+
async readinessCheck() {
|
|
939
|
+
return unwrap(await this.httpClient.get("/admin-api/ready"));
|
|
940
|
+
}
|
|
932
941
|
async isAuthed() {
|
|
933
942
|
return this.httpClient.get("/admin-api/is-authed");
|
|
934
943
|
}
|
|
@@ -1323,6 +1332,23 @@ var AdminApiClient = class {
|
|
|
1323
1332
|
)
|
|
1324
1333
|
);
|
|
1325
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Read a context as the session's ACCOUNT, without minting a warrant.
|
|
1337
|
+
*
|
|
1338
|
+
* Needs an account-authenticated session — one from {@link login}, not the
|
|
1339
|
+
* admin password login — and refuses otherwise with a `401`. The account must
|
|
1340
|
+
* be a member of the group owning the context (`403` if not), and the method
|
|
1341
|
+
* must be declared read-only in the app's ABI (`409` if not; a write needs
|
|
1342
|
+
* {@link performIntent}).
|
|
1343
|
+
*/
|
|
1344
|
+
async queryContext(contextId, request) {
|
|
1345
|
+
return unwrap(
|
|
1346
|
+
await this.httpClient.post(
|
|
1347
|
+
`/admin-api/contexts/${contextId}/query`,
|
|
1348
|
+
request
|
|
1349
|
+
)
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1326
1352
|
/**
|
|
1327
1353
|
* Ask what this node can do for a member in `contextId`, before they sign
|
|
1328
1354
|
* anything.
|
|
@@ -1645,6 +1671,32 @@ var AdminApiClient = class {
|
|
|
1645
1671
|
}
|
|
1646
1672
|
return response;
|
|
1647
1673
|
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Member accounts in the group and the devices bound to each. An admin node
|
|
1676
|
+
* sees every account; a plain member only its own. `403` if this node is not a
|
|
1677
|
+
* member of the group.
|
|
1678
|
+
*/
|
|
1679
|
+
async listMemberDevices(groupId, options = {}) {
|
|
1680
|
+
const params = new URLSearchParams();
|
|
1681
|
+
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
1682
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
1683
|
+
const query = params.toString();
|
|
1684
|
+
const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ""}`;
|
|
1685
|
+
return this.httpClient.get(path);
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Seal a small payload to a member account's root key. This node must be a
|
|
1689
|
+
* member of the group, and `account` must be an account the group knows, so
|
|
1690
|
+
* the route reaches no further than the caller already does.
|
|
1691
|
+
*/
|
|
1692
|
+
async sealToAccount(groupId, account, request) {
|
|
1693
|
+
return unwrap(
|
|
1694
|
+
await this.httpClient.post(
|
|
1695
|
+
`/admin-api/groups/${groupId}/accounts/${account}/seal`,
|
|
1696
|
+
request
|
|
1697
|
+
)
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1648
1700
|
async listGroupContexts(groupId) {
|
|
1649
1701
|
return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}/contexts`));
|
|
1650
1702
|
}
|
|
@@ -1903,6 +1955,10 @@ var AdminApiClient = class {
|
|
|
1903
1955
|
async teeAttest(request) {
|
|
1904
1956
|
return unwrap(await this.httpClient.post("/admin-api/tee/attest", request));
|
|
1905
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in
|
|
1960
|
+
* core#3262), so this fails. Verify quotes with `createQuoteVerifier`.
|
|
1961
|
+
*/
|
|
1906
1962
|
async teeVerifyQuote(request) {
|
|
1907
1963
|
return unwrap(
|
|
1908
1964
|
await this.httpClient.post("/admin-api/tee/verify-quote", request)
|
|
@@ -6701,9 +6757,16 @@ async function initiate(remoteStatic, prologue, fixedEphemeral) {
|
|
|
6701
6757
|
}
|
|
6702
6758
|
async function x25519KeyPair(secret) {
|
|
6703
6759
|
if (!secret) {
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6760
|
+
let pair;
|
|
6761
|
+
try {
|
|
6762
|
+
pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
|
|
6763
|
+
"deriveBits"
|
|
6764
|
+
]);
|
|
6765
|
+
} catch (error) {
|
|
6766
|
+
throw new Error(
|
|
6767
|
+
`Sealed transport needs WebCrypto with X25519, which this runtime lacks (${error?.name ?? "no crypto.subtle"}). Current browsers and Node have it; in React Native, install react-native-quick-crypto as the global crypto.`
|
|
6768
|
+
);
|
|
6769
|
+
}
|
|
6707
6770
|
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
6708
6771
|
return { privateKey: pair.privateKey, publicKey };
|
|
6709
6772
|
}
|
|
@@ -6796,7 +6859,8 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
6796
6859
|
const attested = await admin.teeAttest({
|
|
6797
6860
|
nonce: nonce2,
|
|
6798
6861
|
applicationId: options.applicationId,
|
|
6799
|
-
bindTransportKey: true
|
|
6862
|
+
bindTransportKey: true,
|
|
6863
|
+
...verify.includeCollateral ? { includeCollateral: true } : {}
|
|
6800
6864
|
});
|
|
6801
6865
|
if (!attested.transportPublicKey) {
|
|
6802
6866
|
throw new Error("The node did not report a transport key; it predates sealed transport");
|
|
@@ -6804,7 +6868,13 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
6804
6868
|
const transportKey = fromHex(attested.transportPublicKey, "transportPublicKey", 32);
|
|
6805
6869
|
const inner = options.applicationHash ? fromHex(options.applicationHash, "applicationHash", 32) : new Uint8Array(32);
|
|
6806
6870
|
const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));
|
|
6807
|
-
|
|
6871
|
+
const verified = await verify({
|
|
6872
|
+
quoteB64: attested.quoteB64,
|
|
6873
|
+
nonce: nonce2,
|
|
6874
|
+
reportDataSuffix,
|
|
6875
|
+
collateral: attested.collateral
|
|
6876
|
+
});
|
|
6877
|
+
if (!verified) {
|
|
6808
6878
|
throw new Error("The attestation did not verify, so its transport key is not trusted");
|
|
6809
6879
|
}
|
|
6810
6880
|
return transportKey;
|
|
@@ -7066,6 +7136,83 @@ async function envelopeRefusal(response) {
|
|
|
7066
7136
|
return new SealedTransportError(response.status, code, message, retryAfterMs);
|
|
7067
7137
|
}
|
|
7068
7138
|
|
|
7139
|
+
// src/sealed/verify.ts
|
|
7140
|
+
var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
|
|
7141
|
+
function createQuoteVerifier(options) {
|
|
7142
|
+
const allowedMrtd = measurements(options.allowedMrtd, "allowedMrtd");
|
|
7143
|
+
if (allowedMrtd.length === 0) {
|
|
7144
|
+
throw new Error("allowedMrtd is empty, so no quote would ever be accepted");
|
|
7145
|
+
}
|
|
7146
|
+
const allowedRtmr = [
|
|
7147
|
+
measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
|
|
7148
|
+
measurements(options.allowedRtmr1 ?? [], "allowedRtmr1"),
|
|
7149
|
+
measurements(options.allowedRtmr2 ?? [], "allowedRtmr2"),
|
|
7150
|
+
measurements(options.allowedRtmr3 ?? [], "allowedRtmr3")
|
|
7151
|
+
];
|
|
7152
|
+
const allowedStatuses = options.allowedTcbStatuses ?? ["UpToDate"];
|
|
7153
|
+
const now = options.now ?? Date.now;
|
|
7154
|
+
const verifier = async ({
|
|
7155
|
+
quoteB64,
|
|
7156
|
+
nonce: nonce2,
|
|
7157
|
+
reportDataSuffix,
|
|
7158
|
+
collateral
|
|
7159
|
+
}) => {
|
|
7160
|
+
const quote = fromBase64(quoteB64);
|
|
7161
|
+
const against = collateral ?? await options.fetchCollateral?.(quote);
|
|
7162
|
+
if (!against) {
|
|
7163
|
+
throw new Error(
|
|
7164
|
+
"The node sent no collateral for its quote, and no fetchCollateral was given to get it"
|
|
7165
|
+
);
|
|
7166
|
+
}
|
|
7167
|
+
let verified;
|
|
7168
|
+
try {
|
|
7169
|
+
verified = options.dcapVerify(quote, against, Math.floor(now() / 1e3));
|
|
7170
|
+
} catch (error) {
|
|
7171
|
+
throw new Error(`The quote did not verify: ${error?.message ?? String(error)}`);
|
|
7172
|
+
}
|
|
7173
|
+
if (verified.status === "Revoked" || !allowedStatuses.includes(verified.status)) {
|
|
7174
|
+
throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
|
|
7175
|
+
}
|
|
7176
|
+
const td = tdReport(verified.report);
|
|
7177
|
+
if (!allowedMrtd.includes(hex(td.mrTd))) {
|
|
7178
|
+
throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);
|
|
7179
|
+
}
|
|
7180
|
+
const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
|
|
7181
|
+
registers.forEach((register, index) => {
|
|
7182
|
+
const allowed = allowedRtmr[index];
|
|
7183
|
+
if (allowed.length > 0 && !allowed.includes(hex(register))) {
|
|
7184
|
+
throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);
|
|
7185
|
+
}
|
|
7186
|
+
});
|
|
7187
|
+
if (hex(td.reportData) !== `${nonce2}${reportDataSuffix}`.toLowerCase()) {
|
|
7188
|
+
throw new Error("The quote does not commit to this nonce and binding");
|
|
7189
|
+
}
|
|
7190
|
+
return true;
|
|
7191
|
+
};
|
|
7192
|
+
return Object.assign(verifier, { includeCollateral: true });
|
|
7193
|
+
}
|
|
7194
|
+
function measurements(values, label) {
|
|
7195
|
+
return values.map((value) => {
|
|
7196
|
+
const normalized = value.trim().toLowerCase();
|
|
7197
|
+
if (!MEASUREMENT_HEX.test(normalized)) {
|
|
7198
|
+
throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
|
|
7199
|
+
}
|
|
7200
|
+
return normalized;
|
|
7201
|
+
});
|
|
7202
|
+
}
|
|
7203
|
+
function tdReport(report) {
|
|
7204
|
+
const data = report.data;
|
|
7205
|
+
if (report.type === "td10" && data) return data;
|
|
7206
|
+
if (report.type === "td15" && data?.base) return data.base;
|
|
7207
|
+
throw new Error(`The quote is a ${report.type} quote, not a TDX one`);
|
|
7208
|
+
}
|
|
7209
|
+
function fromBase64(value) {
|
|
7210
|
+
const binary = atob(value);
|
|
7211
|
+
const bytes = new Uint8Array(binary.length);
|
|
7212
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
7213
|
+
return bytes;
|
|
7214
|
+
}
|
|
7215
|
+
|
|
7069
7216
|
// src/namespace-op/namespace-op.ts
|
|
7070
7217
|
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;
|
|
7071
7218
|
var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
|