@calimero-network/mero-js 22.1.0 → 22.3.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.
@@ -97,6 +97,10 @@ export declare function nodeReleaseVersionFromImage(osImage: string): string;
97
97
  * `GET /admin-api/tee/info` reports. The node's own claim, and not trusted:
98
98
  * it only picks which signed release to fetch, and the quote must then match
99
99
  * that release's measurements.
100
+ *
101
+ * `/tee/info` is read in the clear, before any session exists. A node that
102
+ * requires sealing serves it unsealed from core#4196 on; an older one refuses
103
+ * it with `403 sealed_required`, and then this throws saying so.
100
104
  */
101
105
  export declare function fetchNodeReleaseVersion(baseUrl: string, options?: {
102
106
  fetch?: typeof fetch;
@@ -172,15 +176,22 @@ export interface SignedReleaseSealedFetchOptions extends Omit<SignedReleaseVerif
172
176
  * URL of a mirror serving {@link cloudNodeReleaseUrl}'s path, defaulting to
173
177
  * {@link DEFAULT_RELEASE_MIRROR}; a function gets the version and returns
174
178
  * the release, e.g. from a file bundled with the app. Whichever it is, it is
175
- * not trusted: the release is verified. `/tee/info` is read in the clear,
176
- * which a node with `[server.sealed] required` refuses: for one, pass a
177
- * function that ignores `version` and returns the release you expect.
179
+ * not trusted: the release is verified. `/tee/info` is read in the clear;
180
+ * for a node that refuses that, see `releaseVersion`.
178
181
  */
179
182
  releaseSource?: string | ((context: {
180
183
  baseUrl: string;
181
184
  version: string;
182
185
  fetch: typeof fetch;
183
186
  }) => Promise<SignedNodeRelease>);
187
+ /**
188
+ * The release to fetch when the node will not name one: a node that
189
+ * requires sealing (`[server.sealed] required`) and predates core#4196
190
+ * refuses `/tee/info` unsealed. Used only then; a node that answers is
191
+ * taken at its word. Either way untrusted: the quote must match the signed
192
+ * release, so a wrong version only fails the attestation.
193
+ */
194
+ releaseVersion?: string;
184
195
  /** Also require the node to run this application with this bytecode hash. */
185
196
  applicationId?: string;
186
197
  applicationHash?: string;
@@ -190,7 +201,8 @@ export interface SignedReleaseSealedFetchOptions extends Omit<SignedReleaseVerif
190
201
  /**
191
202
  * {@link createAttestedSealedFetch} for a TEE node known only by URL, trusting
192
203
  * whatever signed release at or above `minReleaseVersion` it runs: it reads
193
- * the release the node names from its `/tee/info`, gets that release from
204
+ * the release the node names from its `/tee/info` (or takes `releaseVersion`
205
+ * when a node too old to serve that unsealed requires sealing), gets that release from
194
206
  * `releaseSource` (the public mirror unless you say otherwise), verifies its
195
207
  * signature, builds the quote verifier from its measurements, attests the node
196
208
  * and seals everything to it.
@@ -127,10 +127,35 @@ export function nodeReleaseVersionFromImage(osImage) {
127
127
  * `GET /admin-api/tee/info` reports. The node's own claim, and not trusted:
128
128
  * it only picks which signed release to fetch, and the quote must then match
129
129
  * that release's measurements.
130
+ *
131
+ * `/tee/info` is read in the clear, before any session exists. A node that
132
+ * requires sealing serves it unsealed from core#4196 on; an older one refuses
133
+ * it with `403 sealed_required`, and then this throws saying so.
130
134
  */
131
135
  export async function fetchNodeReleaseVersion(baseUrl, options = {}) {
132
- const url = `${baseUrl.replace(/\/+$/, '')}/admin-api/tee/info`;
133
- const response = await (options.fetch ?? fetch)(url, { headers: { accept: 'application/json' } });
136
+ const version = await readNodeReleaseVersion(baseUrl, options.fetch ?? fetch);
137
+ if (version === undefined) {
138
+ throw new Error(`${infoUrl(baseUrl)} was refused unsealed: the node requires sealing and is too old to name its ` +
139
+ "release before a session exists. Name the release it runs yourself (createSignedReleaseSealedFetch's " +
140
+ '`releaseVersion`), or upgrade the node');
141
+ }
142
+ return version;
143
+ }
144
+ function infoUrl(baseUrl) {
145
+ return `${baseUrl.replace(/\/+$/, '')}/admin-api/tee/info`;
146
+ }
147
+ /** As {@link fetchNodeReleaseVersion}, but `undefined` when the node refuses `/tee/info` as `sealed_required`. */
148
+ async function readNodeReleaseVersion(baseUrl, givenFetch) {
149
+ const url = infoUrl(baseUrl);
150
+ const response = await givenFetch(url, { headers: { accept: 'application/json' } });
151
+ if (response.status === 403) {
152
+ const code = await response
153
+ .json()
154
+ .then((body) => body?.error?.code)
155
+ .catch(() => undefined);
156
+ if (code === 'sealed_required')
157
+ return undefined;
158
+ }
134
159
  if (!response.ok)
135
160
  throw new Error(`Fetching ${url} failed: HTTP ${response.status}`);
136
161
  let osImage;
@@ -211,7 +236,8 @@ export function createSignedReleaseVerifier(options) {
211
236
  /**
212
237
  * {@link createAttestedSealedFetch} for a TEE node known only by URL, trusting
213
238
  * whatever signed release at or above `minReleaseVersion` it runs: it reads
214
- * the release the node names from its `/tee/info`, gets that release from
239
+ * the release the node names from its `/tee/info` (or takes `releaseVersion`
240
+ * when a node too old to serve that unsealed requires sealing), gets that release from
215
241
  * `releaseSource` (the public mirror unless you say otherwise), verifies its
216
242
  * signature, builds the quote verifier from its measurements, attests the node
217
243
  * and seals everything to it.
@@ -232,10 +258,14 @@ export function createSignedReleaseVerifier(options) {
232
258
  * ```
233
259
  */
234
260
  export function createSignedReleaseSealedFetch(options) {
235
- const { baseUrl, releaseSource, applicationId, applicationHash, fetch: givenFetch, ...verifierOptions } = options;
261
+ const { baseUrl, releaseSource, releaseVersion: fallbackVersion, applicationId, applicationHash, fetch: givenFetch, ...verifierOptions } = options;
262
+ if (fallbackVersion !== undefined)
263
+ releaseVersion(fallbackVersion, 'releaseVersion');
236
264
  const baseFetch = givenFetch ?? ((input, init) => fetch(input, init));
237
265
  const release = async () => {
238
- const version = await fetchNodeReleaseVersion(baseUrl, { fetch: baseFetch });
266
+ const version = fallbackVersion === undefined
267
+ ? await fetchNodeReleaseVersion(baseUrl, { fetch: baseFetch })
268
+ : ((await readNodeReleaseVersion(baseUrl, baseFetch)) ?? fallbackVersion);
239
269
  return typeof releaseSource === 'function'
240
270
  ? releaseSource({ baseUrl, version, fetch: baseFetch })
241
271
  : fetchNodeRelease(cloudNodeReleaseUrl(releaseSource ?? DEFAULT_RELEASE_MIRROR, version), { fetch: baseFetch });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calimero-network/mero-js",
3
- "version": "22.1.0",
3
+ "version": "22.3.0",
4
4
  "description": "Pure JavaScript SDK for Calimero",
5
5
  "type": "module",
6
6
  "exports": {