@interop/was-client 0.96.0 → 0.97.1
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 +24 -10
- package/dist/Collection.d.ts +1 -1
- package/dist/Collection.js +7 -7
- package/dist/Collection.js.map +1 -1
- package/dist/Space.js +1 -1
- package/dist/Space.js.map +1 -1
- package/dist/WasClient.d.ts +32 -11
- package/dist/WasClient.d.ts.map +1 -1
- package/dist/WasClient.js +81 -32
- package/dist/WasClient.js.map +1 -1
- package/dist/edv/EdvCodec.d.ts +7 -4
- package/dist/edv/EdvCodec.d.ts.map +1 -1
- package/dist/edv/EdvCodec.js +101 -42
- package/dist/edv/EdvCodec.js.map +1 -1
- package/dist/internal/blob.d.ts +35 -21
- package/dist/internal/blob.d.ts.map +1 -1
- package/dist/internal/blob.js +78 -2
- package/dist/internal/blob.js.map +1 -1
- package/dist/internal/grant.js +6 -6
- package/dist/internal/grant.js.map +1 -1
- package/dist/internal/paths.d.ts +52 -18
- package/dist/internal/paths.d.ts.map +1 -1
- package/dist/internal/paths.js +137 -42
- package/dist/internal/paths.js.map +1 -1
- package/dist/internal/request.d.ts +3 -3
- package/dist/internal/request.d.ts.map +1 -1
- package/dist/internal/request.js +1 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/revoke.js +3 -3
- package/dist/internal/revoke.js.map +1 -1
- package/dist/internal/service.d.ts +1 -0
- package/dist/internal/service.d.ts.map +1 -1
- package/dist/internal/service.js +1 -0
- package/dist/internal/service.js.map +1 -1
- package/dist/paths.d.ts +4 -2
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -2
- package/dist/paths.js.map +1 -1
- package/package.json +14 -14
package/dist/WasClient.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { collectionItemsUrl, parseSpaceTarget } from './internal/paths.js';
|
|
1
|
+
import { baseUrlOfSpacesUrl, collectionItemsUrl, parseSpaceTarget, spacesUrl } from './internal/paths.js';
|
|
2
2
|
import { send, rawRequest, unsignedRequest } from './internal/request.js';
|
|
3
3
|
import { createdId, parseResource, readJsonData } from './internal/content.js';
|
|
4
4
|
import { buildPageWalk, collectPages, collectWalk, signedPageWalk, walkItems, walkPagesOrEmpty } from './internal/pagination.js';
|
|
@@ -6,19 +6,36 @@ import { delegateGrant } from './internal/grant.js';
|
|
|
6
6
|
import { spaceIdOf, submitRevocation } from './internal/revoke.js';
|
|
7
7
|
import { Memo } from './internal/memo.js';
|
|
8
8
|
import { discoverService, selectServiceVersion } from './internal/service.js';
|
|
9
|
-
import { NotSupportedError, ValidationError, WasServerError } from './errors.js';
|
|
9
|
+
import { IncompatibleServerError, NotSupportedError, ValidationError, WasServerError } from './errors.js';
|
|
10
10
|
import { zcapClientForSigner } from './zcapClient.js';
|
|
11
11
|
import { Space } from './Space.js';
|
|
12
12
|
import { Collection } from './Collection.js';
|
|
13
13
|
import { Resource } from './Resource.js';
|
|
14
14
|
export class WasClient {
|
|
15
|
+
/**
|
|
16
|
+
* The server's Spaces Repository URL, in canonical trailing-slash form
|
|
17
|
+
* (`https://host/spaces/`). Service discovery starts here.
|
|
18
|
+
*/
|
|
15
19
|
serverUrl;
|
|
20
|
+
/**
|
|
21
|
+
* The server base URL every Space path hangs off, without a trailing slash:
|
|
22
|
+
* the parent of {@link serverUrl} (`https://host`, or `https://host/was` on
|
|
23
|
+
* a sub-path mount). Zcap `invocationTarget`s are built beneath it.
|
|
24
|
+
*/
|
|
25
|
+
baseUrl;
|
|
16
26
|
zcapClient;
|
|
17
27
|
encryption;
|
|
18
28
|
/**
|
|
19
29
|
* @param options {object}
|
|
20
|
-
* @param options.serverUrl {string}
|
|
21
|
-
*
|
|
30
|
+
* @param options.serverUrl {string} the server's Spaces Repository URL
|
|
31
|
+
* (`https://host/spaces/`, or `https://host/was/spaces/` on a sub-path
|
|
32
|
+
* mount). The base URL for Space paths and zcap `invocationTarget`s is
|
|
33
|
+
* its parent. Service discovery starts from this URL rather than from the
|
|
34
|
+
* base, which is often a landing page with no `service` link. The value
|
|
35
|
+
* is normalized to canonical form; `baseUrlOfSpacesUrl` in `./paths`
|
|
36
|
+
* states what is accepted. The client is built from this URL only: a
|
|
37
|
+
* server with no Spaces Repository (the spec leaves the endpoint
|
|
38
|
+
* optional) has no URL this constructor accepts and is not supported.
|
|
22
39
|
* @param options.zcapClient {ZcapClient} an ezcap client holding the signer
|
|
23
40
|
* @param [options.encryption] {EncryptionProvider} the keystore that
|
|
24
41
|
* supplies keys for collections declared encrypted (by their `encryption`
|
|
@@ -31,11 +48,20 @@ export class WasClient {
|
|
|
31
48
|
* description the caller already holds (from a cache, say). The client
|
|
32
49
|
* selects its version from this copy instead of discovering the document
|
|
33
50
|
* from `serverUrl`, and still refuses it with `IncompatibleServerError` if
|
|
34
|
-
* it names no version this client speaks
|
|
35
|
-
* discards it and
|
|
51
|
+
* it names no version this client speaks or advertises a Spaces Repository
|
|
52
|
+
* other than `serverUrl`. `service({ refresh: true })` discards it and
|
|
53
|
+
* discovers afresh.
|
|
54
|
+
* @throws {ValidationError} when `serverUrl` is not a Spaces Repository URL
|
|
36
55
|
*/
|
|
37
56
|
constructor({ serverUrl, zcapClient, encryption, serviceDescription }) {
|
|
38
|
-
|
|
57
|
+
const baseUrl = baseUrlOfSpacesUrl(serverUrl);
|
|
58
|
+
if (baseUrl === null) {
|
|
59
|
+
throw new ValidationError(`serverUrl "${serverUrl}" is not a Spaces Repository URL. Pass the ` +
|
|
60
|
+
"server's `/spaces/` URL as an absolute URL, for example " +
|
|
61
|
+
'"https://host/spaces/".');
|
|
62
|
+
}
|
|
63
|
+
this.serverUrl = spacesUrl({ baseUrl });
|
|
64
|
+
this.baseUrl = baseUrl;
|
|
39
65
|
this.zcapClient = zcapClient;
|
|
40
66
|
this.encryption = encryption;
|
|
41
67
|
this.#serviceDescription = serviceDescription;
|
|
@@ -79,16 +105,36 @@ export class WasClient {
|
|
|
79
105
|
return signer.id.split('#')[0];
|
|
80
106
|
}
|
|
81
107
|
#serviceDescription;
|
|
82
|
-
#service = new Memo(async () =>
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
#service = new Memo(async () => {
|
|
109
|
+
const info = this.#serviceDescription === undefined
|
|
110
|
+
? await discoverService({ url: this.serverUrl })
|
|
111
|
+
: selectServiceVersion(this.#serviceDescription);
|
|
112
|
+
// Every Space path is built beneath the parent of `serverUrl`, so a server
|
|
113
|
+
// that advertises its Spaces Repository elsewhere would be addressed at
|
|
114
|
+
// the wrong mount. Refuse it by name rather than misroute. Both sides are
|
|
115
|
+
// compared in canonical base-URL form, so an advertised value that differs
|
|
116
|
+
// only in serialization (host case, default port, trailing slash) passes.
|
|
117
|
+
if (info.spacesUrl !== undefined) {
|
|
118
|
+
const advertisedBase = baseUrlOfSpacesUrl(info.spacesUrl);
|
|
119
|
+
if (advertisedBase !== this.baseUrl) {
|
|
120
|
+
const why = advertisedBase === null
|
|
121
|
+
? `which is not a Spaces Repository URL (an absolute http(s) ` +
|
|
122
|
+
`URL ending in a "spaces" segment, with no query or fragment).`
|
|
123
|
+
: `but this client was given "${this.serverUrl}".`;
|
|
124
|
+
throw new IncompatibleServerError(`The server advertises its Spaces Repository at ` +
|
|
125
|
+
`"${info.spacesUrl}", ${why}`, { requestUrl: this.serverUrl });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return info;
|
|
129
|
+
});
|
|
85
130
|
/**
|
|
86
131
|
* Discovers the server's service description and the WAS version this
|
|
87
132
|
* client speaks with it. Follows the `rel="service"` link on the response to
|
|
88
|
-
* an unsigned `HEAD` of `serverUrl
|
|
89
|
-
* unsigned `GET`. No capability is invoked. The
|
|
90
|
-
* life of the client and shared by every handle;
|
|
91
|
-
* one discovery, and a failure is not memoized, so
|
|
133
|
+
* an unsigned `HEAD` of `serverUrl` (the Spaces Repository URL), then reads
|
|
134
|
+
* the linked document with an unsigned `GET`. No capability is invoked. The
|
|
135
|
+
* result is memoized for the life of the client and shared by every handle;
|
|
136
|
+
* concurrent callers share one discovery, and a failure is not memoized, so
|
|
137
|
+
* the next call retries.
|
|
92
138
|
*
|
|
93
139
|
* Every signed request waits on this discovery, so a caller rarely needs to
|
|
94
140
|
* call it first. It is the way to read the server-wide affordances:
|
|
@@ -105,8 +151,9 @@ export class WasClient {
|
|
|
105
151
|
* advertising
|
|
106
152
|
* @returns {Promise<ServiceInfo>}
|
|
107
153
|
* @throws {IncompatibleServerError} when the server's responses carry no
|
|
108
|
-
* `service` link (it predates v0.5), the document is malformed,
|
|
109
|
-
*
|
|
154
|
+
* `service` link (it predates v0.5), the document is malformed, it lists
|
|
155
|
+
* no version this client speaks, or it advertises a Spaces Repository URL
|
|
156
|
+
* other than `serverUrl`
|
|
110
157
|
*/
|
|
111
158
|
async service(options = {}) {
|
|
112
159
|
if (options.refresh) {
|
|
@@ -116,30 +163,31 @@ export class WasClient {
|
|
|
116
163
|
return this.#service.get();
|
|
117
164
|
}
|
|
118
165
|
/**
|
|
119
|
-
*
|
|
166
|
+
* Waits for discovery and refuses a server whose service description has no
|
|
167
|
+
* Spaces Repository. Discovery has already checked that an advertised
|
|
168
|
+
* `spaces` URL names `serverUrl`, so callers address `serverUrl` directly.
|
|
120
169
|
*
|
|
121
|
-
* @returns {Promise<
|
|
170
|
+
* @returns {Promise<void>}
|
|
122
171
|
* @throws {NotSupportedError} when the server does not implement the
|
|
123
172
|
* Spaces Repository
|
|
124
173
|
*/
|
|
125
|
-
async #
|
|
174
|
+
async #assertSpacesRepository() {
|
|
126
175
|
const { spacesUrl, version } = await this.service();
|
|
127
176
|
if (spacesUrl === undefined) {
|
|
128
|
-
throw new NotSupportedError(`The server at "${this.
|
|
177
|
+
throw new NotSupportedError(`The server at "${this.baseUrl}" does not implement the Spaces ` +
|
|
129
178
|
`Repository: its WAS ${version} service description entry has no ` +
|
|
130
179
|
'`spaces` URL.');
|
|
131
180
|
}
|
|
132
|
-
return spacesUrl;
|
|
133
181
|
}
|
|
134
182
|
#cachedContext;
|
|
135
183
|
get #context() {
|
|
136
|
-
// `
|
|
184
|
+
// `baseUrl`, `zcapClient` and `encryption` are all constructor-fixed, so
|
|
137
185
|
// the context is derived once and reused rather than reallocating the
|
|
138
186
|
// object on every handle access.
|
|
139
187
|
if (this.#cachedContext === undefined) {
|
|
140
188
|
const readControllerDid = () => this.controllerDid;
|
|
141
189
|
this.#cachedContext = {
|
|
142
|
-
|
|
190
|
+
baseUrl: this.baseUrl,
|
|
143
191
|
zcapClient: this.zcapClient,
|
|
144
192
|
// Read lazily, so building a handle costs nothing and a
|
|
145
193
|
// delegation-only client (a `ZcapClient` with a delegation signer and
|
|
@@ -200,8 +248,9 @@ export class WasClient {
|
|
|
200
248
|
...(desc.name !== undefined && { name: desc.name }),
|
|
201
249
|
...(desc.type !== undefined && { type: desc.type })
|
|
202
250
|
};
|
|
251
|
+
await this.#assertSpacesRepository();
|
|
203
252
|
const response = await send(this.#context, {
|
|
204
|
-
url:
|
|
253
|
+
url: this.serverUrl,
|
|
205
254
|
method: 'POST',
|
|
206
255
|
json: body
|
|
207
256
|
});
|
|
@@ -229,9 +278,9 @@ export class WasClient {
|
|
|
229
278
|
* @throws {NotSupportedError} when the server has no Spaces Repository
|
|
230
279
|
*/
|
|
231
280
|
async listSpaces() {
|
|
232
|
-
|
|
281
|
+
await this.#assertSpacesRepository();
|
|
233
282
|
const walk = await signedPageWalk(this.#context, {
|
|
234
|
-
firstUrl:
|
|
283
|
+
firstUrl: this.serverUrl
|
|
235
284
|
});
|
|
236
285
|
// The first page always carries the listing body (an unauthorized caller
|
|
237
286
|
// still gets an empty `items` list, not an error), so a `null` walk is a
|
|
@@ -239,7 +288,7 @@ export class WasClient {
|
|
|
239
288
|
// body did not parse as JSON. Report it as one, rather than as a
|
|
240
289
|
// `TypeError` from destructuring `null`.
|
|
241
290
|
if (walk === null) {
|
|
242
|
-
throw new WasServerError(`The space listing at "${
|
|
291
|
+
throw new WasServerError(`The space listing at "${this.serverUrl}" answered with no JSON body.`);
|
|
243
292
|
}
|
|
244
293
|
const listing = await collectPages(walk);
|
|
245
294
|
// A paginating server omits `totalItems` on truncated pages; the walk has
|
|
@@ -353,8 +402,8 @@ export class WasClient {
|
|
|
353
402
|
catch (err) {
|
|
354
403
|
throw new ValidationError(`invocationTarget "${target}" is not a valid absolute URL.`, { cause: err });
|
|
355
404
|
}
|
|
356
|
-
// `parseSpaceTarget` owns the path grammar: it strips `
|
|
357
|
-
//
|
|
405
|
+
// `parseSpaceTarget` owns the path grammar: it strips `baseUrl`'s path
|
|
406
|
+
// first (so a WAS mounted under a sub-path -- `https://host/was/` --
|
|
358
407
|
// classifies just as a bare-origin deployment does, which `parseSpacePath`
|
|
359
408
|
// alone cannot) and percent-decodes each segment, since the path builders
|
|
360
409
|
// re-encode every id. Classifying the full segment list -- rather than
|
|
@@ -362,10 +411,10 @@ export class WasClient {
|
|
|
362
411
|
// (`/space/s/policy`, `/space/s/c/r/meta`, ...) from being silently
|
|
363
412
|
// truncated to the nearest containment handle, whose derived URLs would
|
|
364
413
|
// mismatch the capability's invocation target.
|
|
365
|
-
const parsed = parseSpaceTarget({
|
|
414
|
+
const parsed = parseSpaceTarget({ baseUrl: this.baseUrl, target });
|
|
366
415
|
if (parsed === null) {
|
|
367
416
|
throw new ValidationError(`Cannot derive a handle from invocationTarget "${target}": it does ` +
|
|
368
|
-
`not address a resource on "${this.
|
|
417
|
+
`not address a resource on "${this.baseUrl}".`);
|
|
369
418
|
}
|
|
370
419
|
if (parsed.kind === 'sub-resource') {
|
|
371
420
|
throw new ValidationError(`invocationTarget "${target}" addresses a reserved ` +
|
|
@@ -428,7 +477,7 @@ export class WasClient {
|
|
|
428
477
|
}
|
|
429
478
|
/**
|
|
430
479
|
* The signed escape hatch, mirroring ezcap's generic `request()`. Resolves
|
|
431
|
-
* `path` against `
|
|
480
|
+
* `path` against `baseUrl`, defaults `action` to `method`, and signs via the
|
|
432
481
|
* wrapped client. Returns the raw `HttpResponse` and throws raw ky/ezcap
|
|
433
482
|
* errors -- it does not apply the null-on-404 or typed-error conveniences.
|
|
434
483
|
* Like every signed request it waits on {@link service} first, and rejects
|
package/dist/WasClient.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasClient.js","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"AAeA,OAAO,
|
|
1
|
+
{"version":3,"file":"WasClient.js","sourceRoot":"","sources":["../src/WasClient.ts"],"names":[],"mappings":"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"}
|
package/dist/edv/EdvCodec.d.ts
CHANGED
|
@@ -157,10 +157,13 @@ export declare class EdvCodec implements ResourceCodec {
|
|
|
157
157
|
*
|
|
158
158
|
* Opens the envelope exactly as {@link decode} does, with the same checks.
|
|
159
159
|
* A chunked document's decrypt stream is handed back unbuffered, so its
|
|
160
|
-
* chunks are fetched and decrypted as the caller reads
|
|
161
|
-
* to fetch or verify then errors the stream rather than this call
|
|
162
|
-
*
|
|
163
|
-
*
|
|
160
|
+
* chunks are fetched and decrypted as the caller reads. A chunk that fails
|
|
161
|
+
* to fetch or verify then errors the stream rather than this call, with
|
|
162
|
+
* `NotFoundError` or `EncryptionError`. A caller that buffers the stream
|
|
163
|
+
* should do so with `streamToBlob`, which keeps that error where
|
|
164
|
+
* `new Response(stream)` would not. An inline document is decoded as
|
|
165
|
+
* {@link decode} decodes it and presented as a one-value stream.
|
|
166
|
+
* `contentType` is the sealed plaintext type.
|
|
164
167
|
*
|
|
165
168
|
* @param response {ResponseLike} the stored envelope
|
|
166
169
|
* @param [expectedId] {string} the resource id the read targeted
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EdvCodec.d.ts","sourceRoot":"","sources":["../../src/edv/EdvCodec.ts"],"names":[],"mappings":"AAuEA,OAAO,EAAE,aAAa,EAA0B,MAAM,0BAA0B,CAAA;AAChF,OAAO,KAAK,EACV,SAAS,EAGT,gBAAgB,EAChB,YAAY,EAEb,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAGV,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,aAAa,EAEb,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACb,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"EdvCodec.d.ts","sourceRoot":"","sources":["../../src/edv/EdvCodec.ts"],"names":[],"mappings":"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"}
|
package/dist/edv/EdvCodec.js
CHANGED
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
import { base64, base64urlnopad } from '@scure/base';
|
|
72
72
|
import { EdvClientCore, Transport, assertDocId } from '@interop/edv-client/core';
|
|
73
73
|
import { EMPTY_INDEX_SCHEMA, assertQueryAttributes } from '../internal/indexSchema.js';
|
|
74
|
-
import { ConflictError, EncryptionError, IntegrityError, KeyUnwrapError, NotFoundError, NotSupportedError, PreconditionFailedError, QuotaExceededError, UnknownEpochError, ValidationError } from '../errors.js';
|
|
75
|
-
import { blobBytes, bytesToStream } from '../internal/blob.js';
|
|
74
|
+
import { ConflictError, EncryptionError, IntegrityError, KeyUnwrapError, NotFoundError, NotSupportedError, PreconditionFailedError, QuotaExceededError, UnknownEpochError, ValidationError, WasError } from '../errors.js';
|
|
75
|
+
import { blobBytes, bytesToBlob, bytesToStream, streamToBlob } from '../internal/blob.js';
|
|
76
76
|
import { readEtag, writeHeaders } from '../internal/conditional.js';
|
|
77
77
|
import { isEncryptedEnvelope } from '../sync/envelope.js';
|
|
78
78
|
import { epochWriteStandIn, resolveEpochKeys } from './epochKeys.js';
|
|
@@ -141,22 +141,18 @@ function wasParam({ version, resource, collection, epoch }) {
|
|
|
141
141
|
* Whether a decrypt error means "this candidate key does not open the envelope"
|
|
142
142
|
* (so the loop should try the next key) rather than an integrity failure. True
|
|
143
143
|
* for the upstream typed key-miss error -- `@interop/minimal-cipher` /
|
|
144
|
-
* `@interop/edv-client` raise a `KeyMissError`
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
144
|
+
* `@interop/edv-client` raise a `KeyMissError` when a candidate key is simply
|
|
145
|
+
* the wrong or a rotated key, not a corrupted envelope. It is matched by
|
|
146
|
+
* `err.name === 'KeyMissError'` rather than `instanceof`: name-dispatch
|
|
147
|
+
* survives pnpm installing two copies of the cipher package (a second copy's
|
|
148
|
+
* `KeyMissError` is a distinct class but carries the same `name`), so
|
|
149
|
+
* was-client need not import the class at all.
|
|
150
150
|
*
|
|
151
151
|
* Also true for a {@link KeyUnwrapError} raised by a candidate itself -- a lazy
|
|
152
152
|
* epoch key whose recipient entry turned out corrupt when its first decrypt
|
|
153
153
|
* forced the unwrap (see `lazyEpochKey`): that says nothing about the stored
|
|
154
154
|
* envelope, so the loop must move on to the next candidate rather than report
|
|
155
|
-
* tampering.
|
|
156
|
-
* but the content's AEAD tag did not verify -- a data-integrity failure
|
|
157
|
-
* (WebCrypto's `OperationError` in browsers, Node's "unable to authenticate
|
|
158
|
-
* data") -- which must surface as an {@link IntegrityError}, not be masked as a
|
|
159
|
-
* key miss.
|
|
155
|
+
* tampering.
|
|
160
156
|
*
|
|
161
157
|
* @param err {unknown}
|
|
162
158
|
* @returns {boolean}
|
|
@@ -165,6 +161,24 @@ function isKeyMiss(err) {
|
|
|
165
161
|
return (err instanceof KeyUnwrapError ||
|
|
166
162
|
(err instanceof Error && err.name === 'KeyMissError'));
|
|
167
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Whether a decrypt error means the key DID select a recipient and unwrap the
|
|
166
|
+
* content key, but the content's AEAD tag did not verify: the stored
|
|
167
|
+
* ciphertext is corrupt or has been tampered with. `@interop/minimal-cipher`
|
|
168
|
+
* raises its `IntegrityError` for that (the runtime's raw error as `cause`),
|
|
169
|
+
* matched by `err.name` for the same reason {@link isKeyMiss} matches by name.
|
|
170
|
+
* Any other error is neither a key miss nor an integrity failure (a malformed
|
|
171
|
+
* envelope, a bug) and is not this library's to relabel.
|
|
172
|
+
*
|
|
173
|
+
* This library's own {@link IntegrityError} carries the same name, so a
|
|
174
|
+
* caller that may see both excludes `WasError` first.
|
|
175
|
+
*
|
|
176
|
+
* @param err {unknown}
|
|
177
|
+
* @returns {boolean}
|
|
178
|
+
*/
|
|
179
|
+
function isIntegrityFailure(err) {
|
|
180
|
+
return err instanceof Error && err.name === 'IntegrityError';
|
|
181
|
+
}
|
|
168
182
|
/**
|
|
169
183
|
* Extracts the JWE recipient key ids (`kid`) a stored envelope names (an epoch
|
|
170
184
|
* envelope carries one kid: the epoch key id). Returns `[]` for a malformed
|
|
@@ -814,17 +828,20 @@ export class EdvCodec {
|
|
|
814
828
|
...(contentType !== undefined && { type: contentType })
|
|
815
829
|
});
|
|
816
830
|
}
|
|
817
|
-
return this.#fromDocument(opened.content, opened.meta);
|
|
831
|
+
return await this.#fromDocument(opened.content, opened.meta);
|
|
818
832
|
}
|
|
819
833
|
/**
|
|
820
834
|
* @inheritdoc
|
|
821
835
|
*
|
|
822
836
|
* Opens the envelope exactly as {@link decode} does, with the same checks.
|
|
823
837
|
* A chunked document's decrypt stream is handed back unbuffered, so its
|
|
824
|
-
* chunks are fetched and decrypted as the caller reads
|
|
825
|
-
* to fetch or verify then errors the stream rather than this call
|
|
826
|
-
*
|
|
827
|
-
*
|
|
838
|
+
* chunks are fetched and decrypted as the caller reads. A chunk that fails
|
|
839
|
+
* to fetch or verify then errors the stream rather than this call, with
|
|
840
|
+
* `NotFoundError` or `EncryptionError`. A caller that buffers the stream
|
|
841
|
+
* should do so with `streamToBlob`, which keeps that error where
|
|
842
|
+
* `new Response(stream)` would not. An inline document is decoded as
|
|
843
|
+
* {@link decode} decodes it and presented as a one-value stream.
|
|
844
|
+
* `contentType` is the sealed plaintext type.
|
|
828
845
|
*
|
|
829
846
|
* @param response {ResponseLike} the stored envelope
|
|
830
847
|
* @param [expectedId] {string} the resource id the read targeted
|
|
@@ -845,7 +862,7 @@ export class EdvCodec {
|
|
|
845
862
|
return this.#chunkedStream(opened.read);
|
|
846
863
|
}
|
|
847
864
|
return decodedStream({
|
|
848
|
-
value: this.#fromDocument(opened.content, opened.meta),
|
|
865
|
+
value: await this.#fromDocument(opened.content, opened.meta),
|
|
849
866
|
contentType: sealedContentType(opened.meta)
|
|
850
867
|
});
|
|
851
868
|
}
|
|
@@ -943,7 +960,11 @@ export class EdvCodec {
|
|
|
943
960
|
* document envelope. Each chunk must also carry the envelope's own `was`
|
|
944
961
|
* binding in its protected header (the writer seals it into every chunk),
|
|
945
962
|
* so a genuine chunk of another resource, or one sealed under another
|
|
946
|
-
* epoch, is refused with {@link EncryptionError} before it is decrypted.
|
|
963
|
+
* epoch, is refused with {@link EncryptionError} before it is decrypted. A
|
|
964
|
+
* chunk that fails its own AEAD check (tampered ciphertext), or whose
|
|
965
|
+
* content key this reader's key does not unwrap, errors the stream with
|
|
966
|
+
* {@link EncryptionError} too, wrapping the cipher's named error, so the
|
|
967
|
+
* stream keeps the fail-closed contract {@link decode} documents.
|
|
947
968
|
*
|
|
948
969
|
* @param options {object}
|
|
949
970
|
* @param [options.id] {string} the AEAD-bound resource id (= WAS resource
|
|
@@ -996,11 +1017,14 @@ export class EdvCodec {
|
|
|
996
1017
|
const network = this.#transportFor(context);
|
|
997
1018
|
fetchChunk = options => network.getChunk(options);
|
|
998
1019
|
}
|
|
999
|
-
const stream = (
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1020
|
+
const stream = chunkDecryptErrors({
|
|
1021
|
+
id,
|
|
1022
|
+
stream: (await this.#edv.getStream({
|
|
1023
|
+
doc: { id, stream: { chunks } },
|
|
1024
|
+
keyAgreementKey,
|
|
1025
|
+
transport: new ChunkReadTransport({ fetchChunk, binding })
|
|
1026
|
+
}))
|
|
1027
|
+
});
|
|
1004
1028
|
const contentType = sealedContentType(meta);
|
|
1005
1029
|
return { stream, ...(contentType !== undefined && { contentType }) };
|
|
1006
1030
|
}
|
|
@@ -1115,12 +1139,14 @@ export class EdvCodec {
|
|
|
1115
1139
|
// unwrap its content-encryption key; try the next candidate.
|
|
1116
1140
|
continue;
|
|
1117
1141
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
//
|
|
1123
|
-
//
|
|
1142
|
+
if (!isIntegrityFailure(err)) {
|
|
1143
|
+
// Neither a key miss nor an AEAD failure: rethrow as is.
|
|
1144
|
+
throw err;
|
|
1145
|
+
}
|
|
1146
|
+
// Surface the AEAD failure immediately rather than masking it as a
|
|
1147
|
+
// membership/key miss by continuing into `rest` (a real key miss never
|
|
1148
|
+
// reaches AEAD, so this is a genuine integrity failure on a key that
|
|
1149
|
+
// matched).
|
|
1124
1150
|
throw new IntegrityError('Cannot decrypt this resource: its ciphertext failed to authenticate ' +
|
|
1125
1151
|
'(the AEAD integrity tag did not verify). This reader holds a key ' +
|
|
1126
1152
|
'that unwrapped the envelope, so this is not a key-epoch/membership ' +
|
|
@@ -1537,9 +1563,9 @@ export class EdvCodec {
|
|
|
1537
1563
|
*
|
|
1538
1564
|
* @param content {unknown}
|
|
1539
1565
|
* @param [meta] {Record<string, unknown>}
|
|
1540
|
-
* @returns {Json | Blob}
|
|
1566
|
+
* @returns {Promise<Json | Blob>}
|
|
1541
1567
|
*/
|
|
1542
|
-
#fromDocument(content, meta) {
|
|
1568
|
+
async #fromDocument(content, meta) {
|
|
1543
1569
|
const encoding = meta?.encoding;
|
|
1544
1570
|
const contentType = sealedContentType(meta);
|
|
1545
1571
|
if (encoding === 'utf-8') {
|
|
@@ -1558,8 +1584,9 @@ export class EdvCodec {
|
|
|
1558
1584
|
throw new EncryptionError('Malformed encrypted binary document: meta.encoding is "base64" but ' +
|
|
1559
1585
|
'content.bytes is not a string.');
|
|
1560
1586
|
}
|
|
1561
|
-
return
|
|
1562
|
-
|
|
1587
|
+
return await bytesToBlob({
|
|
1588
|
+
parts: [base64.decode(base64Text)],
|
|
1589
|
+
...(contentType !== undefined && { type: contentType })
|
|
1563
1590
|
});
|
|
1564
1591
|
}
|
|
1565
1592
|
if (encoding !== undefined) {
|
|
@@ -1582,17 +1609,49 @@ function sealedContentType(meta) {
|
|
|
1582
1609
|
return typeof meta?.contentType === 'string' ? meta.contentType : undefined;
|
|
1583
1610
|
}
|
|
1584
1611
|
/**
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1612
|
+
* The chunked decrypt stream of resource `id`, with the cipher's integrity
|
|
1613
|
+
* failure (see {@link isIntegrityFailure}) or key miss (see {@link isKeyMiss})
|
|
1614
|
+
* replaced by an {@link EncryptionError} that carries the original as `cause`.
|
|
1615
|
+
* Every other error passes through as it is, this library's own typed errors
|
|
1616
|
+
* included (a chunk source or lazy epoch key may raise one that the two
|
|
1617
|
+
* predicates would match by name). Cancellation is forwarded.
|
|
1587
1618
|
*
|
|
1588
1619
|
* @param options {object}
|
|
1620
|
+
* @param options.id {string} the resource id, for the error message
|
|
1589
1621
|
* @param options.stream {ReadableStream<Uint8Array>}
|
|
1590
|
-
* @
|
|
1591
|
-
* @returns {Promise<Blob>}
|
|
1622
|
+
* @returns {ReadableStream<Uint8Array>}
|
|
1592
1623
|
*/
|
|
1593
|
-
|
|
1594
|
-
const
|
|
1595
|
-
return new
|
|
1624
|
+
function chunkDecryptErrors({ id, stream }) {
|
|
1625
|
+
const reader = stream.getReader();
|
|
1626
|
+
return new ReadableStream({
|
|
1627
|
+
async pull(controller) {
|
|
1628
|
+
let result;
|
|
1629
|
+
try {
|
|
1630
|
+
result = await reader.read();
|
|
1631
|
+
}
|
|
1632
|
+
catch (err) {
|
|
1633
|
+
if (err instanceof WasError) {
|
|
1634
|
+
// Already one of this library's errors: not the cipher's.
|
|
1635
|
+
throw err;
|
|
1636
|
+
}
|
|
1637
|
+
if (!isIntegrityFailure(err) && !isKeyMiss(err)) {
|
|
1638
|
+
// Neither tampering nor a key the reader lacks: rethrow as is.
|
|
1639
|
+
throw err;
|
|
1640
|
+
}
|
|
1641
|
+
throw new EncryptionError(`Cannot read this resource: a chunk of resource "${id}" failed to ` +
|
|
1642
|
+
'decrypt. It was tampered with in storage or in transit, or ' +
|
|
1643
|
+
'was sealed under a key this reader does not hold.', { cause: err });
|
|
1644
|
+
}
|
|
1645
|
+
if (result.done) {
|
|
1646
|
+
controller.close();
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
controller.enqueue(result.value);
|
|
1650
|
+
},
|
|
1651
|
+
async cancel(reason) {
|
|
1652
|
+
await reader.cancel(reason);
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1596
1655
|
}
|
|
1597
1656
|
/**
|
|
1598
1657
|
* The edv-client `Transport` a chunked read runs on. It serves only
|