@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.mjs
CHANGED
|
@@ -823,6 +823,14 @@ var AdminApiClient = class {
|
|
|
823
823
|
async healthCheck() {
|
|
824
824
|
return unwrap(await this.httpClient.get("/admin-api/health"));
|
|
825
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Whether the node has finished starting and its store answers. Public, like
|
|
828
|
+
* {@link healthCheck}; unlike it, a node that is up but not ready answers
|
|
829
|
+
* `503`, which this surfaces as an `HTTPError` whose body names the stage.
|
|
830
|
+
*/
|
|
831
|
+
async readinessCheck() {
|
|
832
|
+
return unwrap(await this.httpClient.get("/admin-api/ready"));
|
|
833
|
+
}
|
|
826
834
|
async isAuthed() {
|
|
827
835
|
return this.httpClient.get("/admin-api/is-authed");
|
|
828
836
|
}
|
|
@@ -1217,6 +1225,23 @@ var AdminApiClient = class {
|
|
|
1217
1225
|
)
|
|
1218
1226
|
);
|
|
1219
1227
|
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Read a context as the session's ACCOUNT, without minting a warrant.
|
|
1230
|
+
*
|
|
1231
|
+
* Needs an account-authenticated session — one from {@link login}, not the
|
|
1232
|
+
* admin password login — and refuses otherwise with a `401`. The account must
|
|
1233
|
+
* be a member of the group owning the context (`403` if not), and the method
|
|
1234
|
+
* must be declared read-only in the app's ABI (`409` if not; a write needs
|
|
1235
|
+
* {@link performIntent}).
|
|
1236
|
+
*/
|
|
1237
|
+
async queryContext(contextId, request) {
|
|
1238
|
+
return unwrap(
|
|
1239
|
+
await this.httpClient.post(
|
|
1240
|
+
`/admin-api/contexts/${contextId}/query`,
|
|
1241
|
+
request
|
|
1242
|
+
)
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1220
1245
|
/**
|
|
1221
1246
|
* Ask what this node can do for a member in `contextId`, before they sign
|
|
1222
1247
|
* anything.
|
|
@@ -1539,6 +1564,32 @@ var AdminApiClient = class {
|
|
|
1539
1564
|
}
|
|
1540
1565
|
return response;
|
|
1541
1566
|
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Member accounts in the group and the devices bound to each. An admin node
|
|
1569
|
+
* sees every account; a plain member only its own. `403` if this node is not a
|
|
1570
|
+
* member of the group.
|
|
1571
|
+
*/
|
|
1572
|
+
async listMemberDevices(groupId, options = {}) {
|
|
1573
|
+
const params = new URLSearchParams();
|
|
1574
|
+
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
1575
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
1576
|
+
const query = params.toString();
|
|
1577
|
+
const path = `/admin-api/groups/${groupId}/member-devices${query ? `?${query}` : ""}`;
|
|
1578
|
+
return this.httpClient.get(path);
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Seal a small payload to a member account's root key. This node must be a
|
|
1582
|
+
* member of the group, and `account` must be an account the group knows, so
|
|
1583
|
+
* the route reaches no further than the caller already does.
|
|
1584
|
+
*/
|
|
1585
|
+
async sealToAccount(groupId, account, request) {
|
|
1586
|
+
return unwrap(
|
|
1587
|
+
await this.httpClient.post(
|
|
1588
|
+
`/admin-api/groups/${groupId}/accounts/${account}/seal`,
|
|
1589
|
+
request
|
|
1590
|
+
)
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1542
1593
|
async listGroupContexts(groupId) {
|
|
1543
1594
|
return unwrap(await this.httpClient.get(`/admin-api/groups/${groupId}/contexts`));
|
|
1544
1595
|
}
|
|
@@ -1797,6 +1848,10 @@ var AdminApiClient = class {
|
|
|
1797
1848
|
async teeAttest(request) {
|
|
1798
1849
|
return unwrap(await this.httpClient.post("/admin-api/tee/attest", request));
|
|
1799
1850
|
}
|
|
1851
|
+
/**
|
|
1852
|
+
* @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in
|
|
1853
|
+
* core#3262), so this fails. Verify quotes with `createQuoteVerifier`.
|
|
1854
|
+
*/
|
|
1800
1855
|
async teeVerifyQuote(request) {
|
|
1801
1856
|
return unwrap(
|
|
1802
1857
|
await this.httpClient.post("/admin-api/tee/verify-quote", request)
|
|
@@ -6595,9 +6650,16 @@ async function initiate(remoteStatic, prologue, fixedEphemeral) {
|
|
|
6595
6650
|
}
|
|
6596
6651
|
async function x25519KeyPair(secret) {
|
|
6597
6652
|
if (!secret) {
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6653
|
+
let pair;
|
|
6654
|
+
try {
|
|
6655
|
+
pair = await crypto.subtle.generateKey({ name: "X25519" }, true, [
|
|
6656
|
+
"deriveBits"
|
|
6657
|
+
]);
|
|
6658
|
+
} catch (error) {
|
|
6659
|
+
throw new Error(
|
|
6660
|
+
`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.`
|
|
6661
|
+
);
|
|
6662
|
+
}
|
|
6601
6663
|
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
6602
6664
|
return { privateKey: pair.privateKey, publicKey };
|
|
6603
6665
|
}
|
|
@@ -6690,7 +6752,8 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
6690
6752
|
const attested = await admin.teeAttest({
|
|
6691
6753
|
nonce: nonce2,
|
|
6692
6754
|
applicationId: options.applicationId,
|
|
6693
|
-
bindTransportKey: true
|
|
6755
|
+
bindTransportKey: true,
|
|
6756
|
+
...verify.includeCollateral ? { includeCollateral: true } : {}
|
|
6694
6757
|
});
|
|
6695
6758
|
if (!attested.transportPublicKey) {
|
|
6696
6759
|
throw new Error("The node did not report a transport key; it predates sealed transport");
|
|
@@ -6698,7 +6761,13 @@ async function fetchAttestedTransportKey(admin, verify, options = {}) {
|
|
|
6698
6761
|
const transportKey = fromHex(attested.transportPublicKey, "transportPublicKey", 32);
|
|
6699
6762
|
const inner = options.applicationHash ? fromHex(options.applicationHash, "applicationHash", 32) : new Uint8Array(32);
|
|
6700
6763
|
const reportDataSuffix = hex(await transportKeyBinding(inner, transportKey));
|
|
6701
|
-
|
|
6764
|
+
const verified = await verify({
|
|
6765
|
+
quoteB64: attested.quoteB64,
|
|
6766
|
+
nonce: nonce2,
|
|
6767
|
+
reportDataSuffix,
|
|
6768
|
+
collateral: attested.collateral
|
|
6769
|
+
});
|
|
6770
|
+
if (!verified) {
|
|
6702
6771
|
throw new Error("The attestation did not verify, so its transport key is not trusted");
|
|
6703
6772
|
}
|
|
6704
6773
|
return transportKey;
|
|
@@ -6960,6 +7029,83 @@ async function envelopeRefusal(response) {
|
|
|
6960
7029
|
return new SealedTransportError(response.status, code, message, retryAfterMs);
|
|
6961
7030
|
}
|
|
6962
7031
|
|
|
7032
|
+
// src/sealed/verify.ts
|
|
7033
|
+
var MEASUREMENT_HEX = /^[0-9a-f]{96}$/;
|
|
7034
|
+
function createQuoteVerifier(options) {
|
|
7035
|
+
const allowedMrtd = measurements(options.allowedMrtd, "allowedMrtd");
|
|
7036
|
+
if (allowedMrtd.length === 0) {
|
|
7037
|
+
throw new Error("allowedMrtd is empty, so no quote would ever be accepted");
|
|
7038
|
+
}
|
|
7039
|
+
const allowedRtmr = [
|
|
7040
|
+
measurements(options.allowedRtmr0 ?? [], "allowedRtmr0"),
|
|
7041
|
+
measurements(options.allowedRtmr1 ?? [], "allowedRtmr1"),
|
|
7042
|
+
measurements(options.allowedRtmr2 ?? [], "allowedRtmr2"),
|
|
7043
|
+
measurements(options.allowedRtmr3 ?? [], "allowedRtmr3")
|
|
7044
|
+
];
|
|
7045
|
+
const allowedStatuses = options.allowedTcbStatuses ?? ["UpToDate"];
|
|
7046
|
+
const now = options.now ?? Date.now;
|
|
7047
|
+
const verifier = async ({
|
|
7048
|
+
quoteB64,
|
|
7049
|
+
nonce: nonce2,
|
|
7050
|
+
reportDataSuffix,
|
|
7051
|
+
collateral
|
|
7052
|
+
}) => {
|
|
7053
|
+
const quote = fromBase64(quoteB64);
|
|
7054
|
+
const against = collateral ?? await options.fetchCollateral?.(quote);
|
|
7055
|
+
if (!against) {
|
|
7056
|
+
throw new Error(
|
|
7057
|
+
"The node sent no collateral for its quote, and no fetchCollateral was given to get it"
|
|
7058
|
+
);
|
|
7059
|
+
}
|
|
7060
|
+
let verified;
|
|
7061
|
+
try {
|
|
7062
|
+
verified = options.dcapVerify(quote, against, Math.floor(now() / 1e3));
|
|
7063
|
+
} catch (error) {
|
|
7064
|
+
throw new Error(`The quote did not verify: ${error?.message ?? String(error)}`);
|
|
7065
|
+
}
|
|
7066
|
+
if (verified.status === "Revoked" || !allowedStatuses.includes(verified.status)) {
|
|
7067
|
+
throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
|
|
7068
|
+
}
|
|
7069
|
+
const td = tdReport(verified.report);
|
|
7070
|
+
if (!allowedMrtd.includes(hex(td.mrTd))) {
|
|
7071
|
+
throw new Error(`MRTD ${hex(td.mrTd)} is not an image this verifier trusts`);
|
|
7072
|
+
}
|
|
7073
|
+
const registers = [td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3];
|
|
7074
|
+
registers.forEach((register, index) => {
|
|
7075
|
+
const allowed = allowedRtmr[index];
|
|
7076
|
+
if (allowed.length > 0 && !allowed.includes(hex(register))) {
|
|
7077
|
+
throw new Error(`RTMR${index} ${hex(register)} is not one this verifier trusts`);
|
|
7078
|
+
}
|
|
7079
|
+
});
|
|
7080
|
+
if (hex(td.reportData) !== `${nonce2}${reportDataSuffix}`.toLowerCase()) {
|
|
7081
|
+
throw new Error("The quote does not commit to this nonce and binding");
|
|
7082
|
+
}
|
|
7083
|
+
return true;
|
|
7084
|
+
};
|
|
7085
|
+
return Object.assign(verifier, { includeCollateral: true });
|
|
7086
|
+
}
|
|
7087
|
+
function measurements(values, label) {
|
|
7088
|
+
return values.map((value) => {
|
|
7089
|
+
const normalized = value.trim().toLowerCase();
|
|
7090
|
+
if (!MEASUREMENT_HEX.test(normalized)) {
|
|
7091
|
+
throw new Error(`${label} holds ${JSON.stringify(value)}, which is not a 48-byte hex measurement`);
|
|
7092
|
+
}
|
|
7093
|
+
return normalized;
|
|
7094
|
+
});
|
|
7095
|
+
}
|
|
7096
|
+
function tdReport(report) {
|
|
7097
|
+
const data = report.data;
|
|
7098
|
+
if (report.type === "td10" && data) return data;
|
|
7099
|
+
if (report.type === "td15" && data?.base) return data.base;
|
|
7100
|
+
throw new Error(`The quote is a ${report.type} quote, not a TDX one`);
|
|
7101
|
+
}
|
|
7102
|
+
function fromBase64(value) {
|
|
7103
|
+
const binary = atob(value);
|
|
7104
|
+
const bytes = new Uint8Array(binary.length);
|
|
7105
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
7106
|
+
return bytes;
|
|
7107
|
+
}
|
|
7108
|
+
|
|
6963
7109
|
// src/namespace-op/namespace-op.ts
|
|
6964
7110
|
var SIGNED_NAMESPACE_OP_SCHEMA_VERSION = 8;
|
|
6965
7111
|
var NAMESPACE_SIGN_DOMAIN = new TextEncoder().encode("calimero.namespace.v1");
|
|
@@ -7335,6 +7481,7 @@ export {
|
|
|
7335
7481
|
createNodeAuthApiClient,
|
|
7336
7482
|
createNodeHttpClient,
|
|
7337
7483
|
createProofSigner,
|
|
7484
|
+
createQuoteVerifier,
|
|
7338
7485
|
createRetryableMethod,
|
|
7339
7486
|
createSealedFetch,
|
|
7340
7487
|
createTimeoutSignal,
|