was-teaching-server 0.9.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/CHANGELOG.md +1432 -0
- package/LICENSE +661 -0
- package/README.md +305 -0
- package/common/css/materialize.1.0.0.min.css +13 -0
- package/dist/auth-header-hooks.d.ts +84 -0
- package/dist/auth-header-hooks.d.ts.map +1 -0
- package/dist/auth-header-hooks.js +136 -0
- package/dist/auth-header-hooks.js.map +1 -0
- package/dist/authorize.d.ts +45 -0
- package/dist/authorize.d.ts.map +1 -0
- package/dist/authorize.js +76 -0
- package/dist/authorize.js.map +1 -0
- package/dist/backends/filesystem.d.ts +1179 -0
- package/dist/backends/filesystem.d.ts.map +1 -0
- package/dist/backends/filesystem.js +2727 -0
- package/dist/backends/filesystem.js.map +1 -0
- package/dist/backends/postgres.d.ts +833 -0
- package/dist/backends/postgres.d.ts.map +1 -0
- package/dist/backends/postgres.js +2164 -0
- package/dist/backends/postgres.js.map +1 -0
- package/dist/backends/postgresSchema.d.ts +36 -0
- package/dist/backends/postgresSchema.d.ts.map +1 -0
- package/dist/backends/postgresSchema.js +203 -0
- package/dist/backends/postgresSchema.js.map +1 -0
- package/dist/config.default.d.ts +360 -0
- package/dist/config.default.d.ts.map +1 -0
- package/dist/config.default.js +493 -0
- package/dist/config.default.js.map +1 -0
- package/dist/corsProxy.d.ts +45 -0
- package/dist/corsProxy.d.ts.map +1 -0
- package/dist/corsProxy.js +278 -0
- package/dist/corsProxy.js.map +1 -0
- package/dist/digest.d.ts +45 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +174 -0
- package/dist/digest.js.map +1 -0
- package/dist/errors.d.ts +654 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +960 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/atomicFile.d.ts +61 -0
- package/dist/lib/atomicFile.d.ts.map +1 -0
- package/dist/lib/atomicFile.js +150 -0
- package/dist/lib/atomicFile.js.map +1 -0
- package/dist/lib/backendRegistry.d.ts +59 -0
- package/dist/lib/backendRegistry.d.ts.map +1 -0
- package/dist/lib/backendRegistry.js +92 -0
- package/dist/lib/backendRegistry.js.map +1 -0
- package/dist/lib/backendUsage.d.ts +28 -0
- package/dist/lib/backendUsage.d.ts.map +1 -0
- package/dist/lib/backendUsage.js +55 -0
- package/dist/lib/backendUsage.js.map +1 -0
- package/dist/lib/backends.d.ts +142 -0
- package/dist/lib/backends.d.ts.map +1 -0
- package/dist/lib/backends.js +272 -0
- package/dist/lib/backends.js.map +1 -0
- package/dist/lib/blindedIndex.d.ts +156 -0
- package/dist/lib/blindedIndex.d.ts.map +1 -0
- package/dist/lib/blindedIndex.js +338 -0
- package/dist/lib/blindedIndex.js.map +1 -0
- package/dist/lib/collectionDescription.d.ts +33 -0
- package/dist/lib/collectionDescription.d.ts.map +1 -0
- package/dist/lib/collectionDescription.js +21 -0
- package/dist/lib/collectionDescription.js.map +1 -0
- package/dist/lib/cursor.d.ts +18 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +50 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/edvEnvelope.d.ts +50 -0
- package/dist/lib/edvEnvelope.d.ts.map +1 -0
- package/dist/lib/edvEnvelope.js +106 -0
- package/dist/lib/edvEnvelope.js.map +1 -0
- package/dist/lib/encryption.d.ts +192 -0
- package/dist/lib/encryption.d.ts.map +1 -0
- package/dist/lib/encryption.js +383 -0
- package/dist/lib/encryption.js.map +1 -0
- package/dist/lib/etag.d.ts +33 -0
- package/dist/lib/etag.d.ts.map +1 -0
- package/dist/lib/etag.js +36 -0
- package/dist/lib/etag.js.map +1 -0
- package/dist/lib/exportManifest.d.ts +29 -0
- package/dist/lib/exportManifest.d.ts.map +1 -0
- package/dist/lib/exportManifest.js +77 -0
- package/dist/lib/exportManifest.js.map +1 -0
- package/dist/lib/importTar.d.ts +81 -0
- package/dist/lib/importTar.d.ts.map +1 -0
- package/dist/lib/importTar.js +301 -0
- package/dist/lib/importTar.js.map +1 -0
- package/dist/lib/isJson.d.ts +16 -0
- package/dist/lib/isJson.d.ts.map +1 -0
- package/dist/lib/isJson.js +17 -0
- package/dist/lib/isJson.js.map +1 -0
- package/dist/lib/keyEpoch.d.ts +38 -0
- package/dist/lib/keyEpoch.d.ts.map +1 -0
- package/dist/lib/keyEpoch.js +75 -0
- package/dist/lib/keyEpoch.js.map +1 -0
- package/dist/lib/keyedMutex.d.ts +28 -0
- package/dist/lib/keyedMutex.d.ts.map +1 -0
- package/dist/lib/keyedMutex.js +45 -0
- package/dist/lib/keyedMutex.js.map +1 -0
- package/dist/lib/kmsModule.d.ts +62 -0
- package/dist/lib/kmsModule.d.ts.map +1 -0
- package/dist/lib/kmsModule.js +327 -0
- package/dist/lib/kmsModule.js.map +1 -0
- package/dist/lib/kmsRecordCipher.d.ts +82 -0
- package/dist/lib/kmsRecordCipher.d.ts.map +1 -0
- package/dist/lib/kmsRecordCipher.js +226 -0
- package/dist/lib/kmsRecordCipher.js.map +1 -0
- package/dist/lib/pagination.d.ts +19 -0
- package/dist/lib/pagination.d.ts.map +1 -0
- package/dist/lib/pagination.js +21 -0
- package/dist/lib/pagination.js.map +1 -0
- package/dist/lib/paths.d.ts +273 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +270 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/preconditions.d.ts +65 -0
- package/dist/lib/preconditions.d.ts.map +1 -0
- package/dist/lib/preconditions.js +112 -0
- package/dist/lib/preconditions.js.map +1 -0
- package/dist/lib/resourceFileName.d.ts +41 -0
- package/dist/lib/resourceFileName.d.ts.map +1 -0
- package/dist/lib/resourceFileName.js +58 -0
- package/dist/lib/resourceFileName.js.map +1 -0
- package/dist/lib/revocations.d.ts +49 -0
- package/dist/lib/revocations.d.ts.map +1 -0
- package/dist/lib/revocations.js +86 -0
- package/dist/lib/revocations.js.map +1 -0
- package/dist/lib/validateDid.d.ts +19 -0
- package/dist/lib/validateDid.d.ts.map +1 -0
- package/dist/lib/validateDid.js +35 -0
- package/dist/lib/validateDid.js.map +1 -0
- package/dist/lib/validateId.d.ts +66 -0
- package/dist/lib/validateId.d.ts.map +1 -0
- package/dist/lib/validateId.js +126 -0
- package/dist/lib/validateId.js.map +1 -0
- package/dist/plugin.d.ts +115 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +134 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy.d.ts +78 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +115 -0
- package/dist/policy.js.map +1 -0
- package/dist/provisioning.d.ts +38 -0
- package/dist/provisioning.d.ts.map +1 -0
- package/dist/provisioning.js +72 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/requests/BackendRequest.d.ts +61 -0
- package/dist/requests/BackendRequest.d.ts.map +1 -0
- package/dist/requests/BackendRequest.js +152 -0
- package/dist/requests/BackendRequest.js.map +1 -0
- package/dist/requests/CollectionRequest.d.ts +234 -0
- package/dist/requests/CollectionRequest.d.ts.map +1 -0
- package/dist/requests/CollectionRequest.js +705 -0
- package/dist/requests/CollectionRequest.js.map +1 -0
- package/dist/requests/KeyRequest.d.ts +115 -0
- package/dist/requests/KeyRequest.d.ts.map +1 -0
- package/dist/requests/KeyRequest.js +468 -0
- package/dist/requests/KeyRequest.js.map +1 -0
- package/dist/requests/KeystoreRequest.d.ts +99 -0
- package/dist/requests/KeystoreRequest.d.ts.map +1 -0
- package/dist/requests/KeystoreRequest.js +247 -0
- package/dist/requests/KeystoreRequest.js.map +1 -0
- package/dist/requests/PolicyRequest.d.ts +53 -0
- package/dist/requests/PolicyRequest.d.ts.map +1 -0
- package/dist/requests/PolicyRequest.js +124 -0
- package/dist/requests/PolicyRequest.js.map +1 -0
- package/dist/requests/ResourceRequest.d.ts +134 -0
- package/dist/requests/ResourceRequest.d.ts.map +1 -0
- package/dist/requests/ResourceRequest.js +548 -0
- package/dist/requests/ResourceRequest.js.map +1 -0
- package/dist/requests/RevocationRequest.d.ts +67 -0
- package/dist/requests/RevocationRequest.d.ts.map +1 -0
- package/dist/requests/RevocationRequest.js +187 -0
- package/dist/requests/RevocationRequest.js.map +1 -0
- package/dist/requests/SpaceRequest.d.ts +204 -0
- package/dist/requests/SpaceRequest.d.ts.map +1 -0
- package/dist/requests/SpaceRequest.js +520 -0
- package/dist/requests/SpaceRequest.js.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts +50 -0
- package/dist/requests/SpacesRepositoryRequest.d.ts.map +1 -0
- package/dist/requests/SpacesRepositoryRequest.js +159 -0
- package/dist/requests/SpacesRepositoryRequest.js.map +1 -0
- package/dist/requests/collectionContext.d.ts +21 -0
- package/dist/requests/collectionContext.d.ts.map +1 -0
- package/dist/requests/collectionContext.js +30 -0
- package/dist/requests/collectionContext.js.map +1 -0
- package/dist/requests/controllerConsent.d.ts +58 -0
- package/dist/requests/controllerConsent.d.ts.map +1 -0
- package/dist/requests/controllerConsent.js +73 -0
- package/dist/requests/controllerConsent.js.map +1 -0
- package/dist/requests/keystoreContext.d.ts +68 -0
- package/dist/requests/keystoreContext.d.ts.map +1 -0
- package/dist/requests/keystoreContext.js +87 -0
- package/dist/requests/keystoreContext.js.map +1 -0
- package/dist/requests/resourceInput.d.ts +19 -0
- package/dist/requests/resourceInput.d.ts.map +1 -0
- package/dist/requests/resourceInput.js +102 -0
- package/dist/requests/resourceInput.js.map +1 -0
- package/dist/requests/spaceContext.d.ts +127 -0
- package/dist/requests/spaceContext.d.ts.map +1 -0
- package/dist/requests/spaceContext.js +201 -0
- package/dist/requests/spaceContext.js.map +1 -0
- package/dist/routes.d.ts +55 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +306 -0
- package/dist/routes.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +60 -0
- package/dist/server.js.map +1 -0
- package/dist/start.d.ts +7 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +61 -0
- package/dist/start.js.map +1 -0
- package/dist/storage.d.ts +29 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +47 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +915 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/home.hbs +9 -0
- package/dist/views/templates/main.hbs +12 -0
- package/dist/zcap.d.ts +267 -0
- package/dist/zcap.d.ts.map +1 -0
- package/dist/zcap.js +478 -0
- package/dist/zcap.js.map +1 -0
- package/package.json +109 -0
- package/src/auth-header-hooks.ts +163 -0
- package/src/authorize.ts +117 -0
- package/src/backends/filesystem.ts +3626 -0
- package/src/backends/postgres.ts +3073 -0
- package/src/backends/postgresSchema.ts +231 -0
- package/src/config.default.ts +657 -0
- package/src/corsProxy.ts +326 -0
- package/src/declarations.d.ts +28 -0
- package/src/digest.ts +223 -0
- package/src/errors.ts +1122 -0
- package/src/index.ts +21 -0
- package/src/lib/atomicFile.ts +176 -0
- package/src/lib/backendRegistry.ts +139 -0
- package/src/lib/backendUsage.ts +79 -0
- package/src/lib/backends.ts +351 -0
- package/src/lib/blindedIndex.ts +466 -0
- package/src/lib/collectionDescription.ts +43 -0
- package/src/lib/cursor.ts +52 -0
- package/src/lib/edvEnvelope.ts +120 -0
- package/src/lib/encryption.ts +472 -0
- package/src/lib/etag.ts +40 -0
- package/src/lib/exportManifest.ts +115 -0
- package/src/lib/importTar.ts +381 -0
- package/src/lib/isJson.ts +18 -0
- package/src/lib/keyEpoch.ts +89 -0
- package/src/lib/keyedMutex.ts +45 -0
- package/src/lib/kmsModule.ts +451 -0
- package/src/lib/kmsRecordCipher.ts +292 -0
- package/src/lib/pagination.ts +22 -0
- package/src/lib/paths.ts +382 -0
- package/src/lib/preconditions.ts +145 -0
- package/src/lib/resourceFileName.ts +69 -0
- package/src/lib/revocations.ts +116 -0
- package/src/lib/validateDid.ts +41 -0
- package/src/lib/validateId.ts +148 -0
- package/src/plugin.ts +288 -0
- package/src/policy.ts +176 -0
- package/src/provisioning.ts +98 -0
- package/src/requests/BackendRequest.ts +219 -0
- package/src/requests/CollectionRequest.ts +899 -0
- package/src/requests/KeyRequest.ts +617 -0
- package/src/requests/KeystoreRequest.ts +325 -0
- package/src/requests/PolicyRequest.ts +169 -0
- package/src/requests/ResourceRequest.ts +649 -0
- package/src/requests/RevocationRequest.ts +267 -0
- package/src/requests/SpaceRequest.ts +686 -0
- package/src/requests/SpacesRepositoryRequest.ts +194 -0
- package/src/requests/collectionContext.ts +41 -0
- package/src/requests/controllerConsent.ts +108 -0
- package/src/requests/keystoreContext.ts +124 -0
- package/src/requests/resourceInput.ts +118 -0
- package/src/requests/spaceContext.ts +311 -0
- package/src/routes.ts +420 -0
- package/src/server.ts +69 -0
- package/src/start.ts +71 -0
- package/src/storage.ts +60 -0
- package/src/types.ts +1016 -0
- package/src/views/home.hbs +9 -0
- package/src/views/templates/main.hbs +12 -0
- package/src/zcap.ts +657 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for the client-declared key-epoch stamp on encrypted-Collection
|
|
3
|
+
* Resources (the `key-epochs` feature). A writer declares which epoch key
|
|
4
|
+
* encrypted a Resource's content so a reader can pick the right key BEFORE
|
|
5
|
+
* attempting decryption; the value is advisory metadata the server stores
|
|
6
|
+
* opaquely and never computes or verifies (a write may race a rotation).
|
|
7
|
+
*
|
|
8
|
+
* On a **content** write the epoch is declared via the `WAS-Key-Epoch` request
|
|
9
|
+
* header -- one mechanism that works uniformly for JSON, raw-stream binary, and
|
|
10
|
+
* multipart writes. It is deliberately NOT signature-covered (like `If-Match`):
|
|
11
|
+
* it is advisory client-declared metadata, not part of the capability
|
|
12
|
+
* invocation. A content write stores the header's value when present and CLEARS
|
|
13
|
+
* the stamp when absent -- the new ciphertext's epoch is unknown, and a stale
|
|
14
|
+
* stamp is worse than none (a reader falls back to `currentEpoch` and can try
|
|
15
|
+
* other epochs). A `PUT .../meta` may also declare `epoch` as a top-level member
|
|
16
|
+
* of the body (a sibling of `custom`), where omitting it PRESERVES the stored
|
|
17
|
+
* value -- see the `putMeta` handler.
|
|
18
|
+
*
|
|
19
|
+
* The only validation is that a present value is a non-empty string (400
|
|
20
|
+
* otherwise); the server never checks it against the Collection marker's epochs.
|
|
21
|
+
*/
|
|
22
|
+
import { InvalidRequestBodyError } from '../errors.js';
|
|
23
|
+
/** The request header carrying the client-declared key-epoch on a content write. */
|
|
24
|
+
export const KEY_EPOCH_HEADER = 'was-key-epoch';
|
|
25
|
+
/**
|
|
26
|
+
* Parses the OPTIONAL `WAS-Key-Epoch` request header into the epoch stamp for a
|
|
27
|
+
* content write. Resolves `{ epoch: string }` for a non-empty single-valued
|
|
28
|
+
* header, `{ epoch: undefined }` when absent (the content write clears any
|
|
29
|
+
* stored stamp), and throws `invalid-request-body` (400) for an empty or
|
|
30
|
+
* array-valued header.
|
|
31
|
+
* @param options {object}
|
|
32
|
+
* @param options.headers {object} the Fastify request headers
|
|
33
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
34
|
+
* @returns {{ epoch?: string }}
|
|
35
|
+
*/
|
|
36
|
+
export function parseKeyEpochHeader({ headers, requestName }) {
|
|
37
|
+
const value = headers[KEY_EPOCH_HEADER];
|
|
38
|
+
if (value === undefined) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
42
|
+
throw new InvalidRequestBodyError({
|
|
43
|
+
requestName,
|
|
44
|
+
detail: 'The "WAS-Key-Epoch" header must be a non-empty string.'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return { epoch: value };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validates and extracts the OPTIONAL top-level `epoch` member of an Update
|
|
51
|
+
* Resource Metadata (`PUT .../meta`) body. Unlike a content write, a metadata
|
|
52
|
+
* write PRESERVES the stored epoch when the member is omitted (the stamp
|
|
53
|
+
* describes the content write, not the metadata write); a present value must be
|
|
54
|
+
* a non-empty string (400 otherwise). Returns `{ epoch: string }` when supplied,
|
|
55
|
+
* or `{}` when the member is absent (preserve).
|
|
56
|
+
* @param options {object}
|
|
57
|
+
* @param options.body {object} the parsed request body (already known to be an object)
|
|
58
|
+
* @param [options.requestName] {string} request name for the 400 error title
|
|
59
|
+
* @returns {{ epoch?: string }}
|
|
60
|
+
*/
|
|
61
|
+
export function parseMetaEpoch({ body, requestName }) {
|
|
62
|
+
if (!Object.hasOwn(body, 'epoch')) {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
const value = body.epoch;
|
|
66
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
67
|
+
throw new InvalidRequestBodyError({
|
|
68
|
+
requestName,
|
|
69
|
+
detail: 'The "epoch" property must be a non-empty string.',
|
|
70
|
+
pointer: '/epoch'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { epoch: value };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=keyEpoch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyEpoch.js","sourceRoot":"","sources":["../../src/lib/keyEpoch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAEtD,oFAAoF;AACpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,eAAe,CAAA;AAE/C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAClC,OAAO,EACP,WAAW,EAIZ;IACC,MAAM,KAAK,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACvC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,uBAAuB,CAAC;YAChC,WAAW;YACX,MAAM,EAAE,wDAAwD;SACjE,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACzB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,EAC7B,IAAI,EACJ,WAAW,EAIZ;IACC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAClC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;IACxB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,uBAAuB,CAAC;YAChC,WAAW;YACX,MAAM,EAAE,kDAAkD;YAC1D,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACzB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A keyed in-process mutex that serializes async functions per key, ported from
|
|
3
|
+
* the `@interop/edv-server` storage layer. The conditional-write path uses it to
|
|
4
|
+
* make a Resource's read-check-write atomic: two concurrent writers of the same
|
|
5
|
+
* Resource cannot both observe the same prior version and both succeed.
|
|
6
|
+
*
|
|
7
|
+
* This is a single-instance lock only. It does NOT coordinate writes across
|
|
8
|
+
* multiple server processes or a horizontally-scaled deployment -- that is out
|
|
9
|
+
* of scope for the reference server (see the spec's Conditional Requests note).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Serializes async functions per key. `run` chains the given function onto the
|
|
13
|
+
* tail of the key's promise queue, so all functions for the same key execute
|
|
14
|
+
* strictly one at a time, in call order. Distinct keys run concurrently.
|
|
15
|
+
*/
|
|
16
|
+
export declare class KeyedMutex {
|
|
17
|
+
private readonly queues;
|
|
18
|
+
/**
|
|
19
|
+
* Runs `fn` once all previously-queued functions for `key` have settled,
|
|
20
|
+
* resolving (or rejecting) with `fn`'s result. The key's queue entry is
|
|
21
|
+
* cleaned up once it drains so the map does not grow without bound.
|
|
22
|
+
* @param key {string} the serialization key (e.g. a per-Resource path)
|
|
23
|
+
* @param fn {() => Promise<T>} the critical section to run under the lock
|
|
24
|
+
* @returns {Promise<T>}
|
|
25
|
+
*/
|
|
26
|
+
run<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=keyedMutex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyedMutex.d.ts","sourceRoot":"","sources":["../../src/lib/keyedMutex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAE7D;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAiB5D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A keyed in-process mutex that serializes async functions per key, ported from
|
|
3
|
+
* the `@interop/edv-server` storage layer. The conditional-write path uses it to
|
|
4
|
+
* make a Resource's read-check-write atomic: two concurrent writers of the same
|
|
5
|
+
* Resource cannot both observe the same prior version and both succeed.
|
|
6
|
+
*
|
|
7
|
+
* This is a single-instance lock only. It does NOT coordinate writes across
|
|
8
|
+
* multiple server processes or a horizontally-scaled deployment -- that is out
|
|
9
|
+
* of scope for the reference server (see the spec's Conditional Requests note).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Serializes async functions per key. `run` chains the given function onto the
|
|
13
|
+
* tail of the key's promise queue, so all functions for the same key execute
|
|
14
|
+
* strictly one at a time, in call order. Distinct keys run concurrently.
|
|
15
|
+
*/
|
|
16
|
+
export class KeyedMutex {
|
|
17
|
+
queues = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Runs `fn` once all previously-queued functions for `key` have settled,
|
|
20
|
+
* resolving (or rejecting) with `fn`'s result. The key's queue entry is
|
|
21
|
+
* cleaned up once it drains so the map does not grow without bound.
|
|
22
|
+
* @param key {string} the serialization key (e.g. a per-Resource path)
|
|
23
|
+
* @param fn {() => Promise<T>} the critical section to run under the lock
|
|
24
|
+
* @returns {Promise<T>}
|
|
25
|
+
*/
|
|
26
|
+
async run(key, fn) {
|
|
27
|
+
const previous = this.queues.get(key) ?? Promise.resolve();
|
|
28
|
+
const run = previous.then(fn, fn);
|
|
29
|
+
// Keep the chain alive even if `fn` rejects, so a failed critical section
|
|
30
|
+
// does not wedge the key's queue; track this swallowed-rejection tail so the
|
|
31
|
+
// queue entry can be reclaimed once it drains (unless a later call replaced
|
|
32
|
+
// it).
|
|
33
|
+
const tail = run.catch(() => { });
|
|
34
|
+
this.queues.set(key, tail);
|
|
35
|
+
try {
|
|
36
|
+
return await run;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
if (this.queues.get(key) === tail) {
|
|
40
|
+
this.queues.delete(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=keyedMutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyedMutex.js","sourceRoot":"","sources":["../../src/lib/keyedMutex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACJ,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAA;IAE7D;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,EAAoB;QAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAA;QAC1D,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QACjC,0EAA0E;QAC1E,6EAA6E;QAC7E,4EAA4E;QAC5E,OAAO;QACP,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAC1B,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,CAAA;QAClB,CAAC;gBAAS,CAAC;YACT,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;gBAClC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { IDID, KmsKeyDescription, KmsStoredKey } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the public key-description projection of a stored key: the
|
|
4
|
+
* allowlisted public fields plus the live keystore `controller` (never any
|
|
5
|
+
* secret field), with the `publicAlias` / expanded `publicAliasTemplate`
|
|
6
|
+
* override applied to `id`. Applied identically at generate time and on every
|
|
7
|
+
* subsequent read, so descriptions are stable (the client caches them).
|
|
8
|
+
*
|
|
9
|
+
* @param options {object}
|
|
10
|
+
* @param options.key {KmsStoredKey} the stored (secret-bearing) key
|
|
11
|
+
* @param options.controller {IDID} the keystore's current controller
|
|
12
|
+
* @returns {KmsKeyDescription}
|
|
13
|
+
*/
|
|
14
|
+
export declare function describeKmsKey({ key, controller }: {
|
|
15
|
+
key: KmsStoredKey;
|
|
16
|
+
controller: IDID;
|
|
17
|
+
}): KmsKeyDescription;
|
|
18
|
+
/**
|
|
19
|
+
* Generates key material for a supported key type and assembles the stored
|
|
20
|
+
* key (full serialized form, secret material included) plus its public
|
|
21
|
+
* description. Field names are protocol-fixed: the asymmetric pairs carry
|
|
22
|
+
* `publicKeyMultibase` / `privateKeyMultibase`; the symmetric keys carry a
|
|
23
|
+
* base64url 256-bit `secret` (the webkms per-type key generators).
|
|
24
|
+
*
|
|
25
|
+
* @param options {object}
|
|
26
|
+
* @param options.keyId {string} the full key URL (`<keystoreId>/keys/<localId>`)
|
|
27
|
+
* @param options.type {string} the webkms key type
|
|
28
|
+
* @param options.controller {IDID} the keystore's current controller (for
|
|
29
|
+
* the returned description only -- never stored on the key)
|
|
30
|
+
* @param [options.maxCapabilityChainLength] {number}
|
|
31
|
+
* @param [options.publicAlias] {string}
|
|
32
|
+
* @param [options.publicAliasTemplate] {string}
|
|
33
|
+
* @returns {Promise<{ key: KmsStoredKey, keyDescription: KmsKeyDescription }>}
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateKmsKey({ keyId, type, controller, maxCapabilityChainLength, publicAlias, publicAliasTemplate }: {
|
|
36
|
+
keyId: string;
|
|
37
|
+
type: string;
|
|
38
|
+
controller: IDID;
|
|
39
|
+
maxCapabilityChainLength?: number;
|
|
40
|
+
publicAlias?: string;
|
|
41
|
+
publicAliasTemplate?: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
key: KmsStoredKey;
|
|
44
|
+
keyDescription: KmsKeyDescription;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Runs a key operation envelope against a stored key, dispatching on
|
|
48
|
+
* `(key.type, operation.type)`. An operation this module does not serve for
|
|
49
|
+
* the key's type -- or does not recognize at all -- is a clean 400
|
|
50
|
+
* `UnsupportedKeyOperationError`.
|
|
51
|
+
*
|
|
52
|
+
* @param options {object}
|
|
53
|
+
* @param options.key {KmsStoredKey} the stored (secret-bearing) key
|
|
54
|
+
* @param options.operation {Record<string, unknown>} the validated envelope
|
|
55
|
+
* @returns {Promise<object>} the operation's wire result (`signatureValue` /
|
|
56
|
+
* `verified` / `secret` / `wrappedKey` / `unwrappedKey`)
|
|
57
|
+
*/
|
|
58
|
+
export declare function runKeyOperation({ key, operation }: {
|
|
59
|
+
key: KmsStoredKey;
|
|
60
|
+
operation: Record<string, unknown>;
|
|
61
|
+
}): Promise<object>;
|
|
62
|
+
//# sourceMappingURL=kmsModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kmsModule.d.ts","sourceRoot":"","sources":["../../src/lib/kmsModule.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAsExE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,UAAU,EACX,EAAE;IACD,GAAG,EAAE,YAAY,CAAA;IACjB,UAAU,EAAE,IAAI,CAAA;CACjB,GAAG,iBAAiB,CAmBpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,EACnC,KAAK,EACL,IAAI,EACJ,UAAU,EACV,wBAAwB,EACxB,WAAW,EACX,mBAAmB,EACpB,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,IAAI,CAAA;IAChB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,YAAY,CAAC;IAAC,cAAc,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAoCpE;AAgND;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,EACpC,GAAG,EACH,SAAS,EACV,EAAE;IACD,GAAG,EAAE,YAAY,CAAA;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBlB"}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single in-process WebKMS module this server hard-wires (`local-v1`),
|
|
3
|
+
* collecting the module's parts in one file: a pure-crypto core per key type,
|
|
4
|
+
* an operation dispatch table keyed by `(key type, operation type)`, and the
|
|
5
|
+
* generate/describe orchestration (record assembly, `publicAlias` /
|
|
6
|
+
* `publicAliasTemplate` expansion). The module never touches storage -- the
|
|
7
|
+
* request layer loads/inserts `KmsKeyRecord`s through the storage backend and
|
|
8
|
+
* hands this module the (secret-bearing) stored key.
|
|
9
|
+
*
|
|
10
|
+
* Custody draws the line on what is served (see the roadmap's wire contract):
|
|
11
|
+
* every operation here requires the custodial secret -- Ed25519 sign, X25519
|
|
12
|
+
* deriveSecret, HMAC sign/verify, AES-KW wrap/unwrap. Asymmetric verify needs
|
|
13
|
+
* only the public key and is deliberately not served (client-local instead);
|
|
14
|
+
* requesting it is a clean 400.
|
|
15
|
+
*/
|
|
16
|
+
import { createCipheriv, createDecipheriv, createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
17
|
+
import { Ed25519VerificationKey } from '@interop/ed25519-verification-key';
|
|
18
|
+
import { X25519KeyAgreementKey2020 } from '@interop/x25519-key-agreement-key';
|
|
19
|
+
import { InvalidRequestBodyError, UnsupportedKeyOperationError } from '../errors.js';
|
|
20
|
+
/** The JSON-LD context each supported key type's description carries. */
|
|
21
|
+
const KEY_TYPE_CONTEXTS = {
|
|
22
|
+
Ed25519VerificationKey2020: 'https://w3id.org/security/suites/ed25519-2020/v1',
|
|
23
|
+
X25519KeyAgreementKey2020: 'https://w3id.org/security/suites/x25519-2020/v1',
|
|
24
|
+
Sha256HmacKey2019: 'https://w3id.org/security/suites/hmac-2019/v1',
|
|
25
|
+
AesKeyWrappingKey2019: 'https://w3id.org/security/suites/aes-2019/v1'
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* AES Key Wrap (RFC 3394) parameters: AES-256 in key-wrap mode with the RFC's
|
|
29
|
+
* default initial value. The IV is the integrity check -- `decipher.final()`
|
|
30
|
+
* throws when the unwrapped output would not reproduce it (i.e. the wrong KEK
|
|
31
|
+
* or corrupted ciphertext).
|
|
32
|
+
*/
|
|
33
|
+
const AES_KW_ALGORITHM = 'id-aes256-wrap';
|
|
34
|
+
const AES_KW_DEFAULT_IV = Buffer.alloc(8, 0xa6);
|
|
35
|
+
/** Decodes a base64url (no padding) operation field into bytes. */
|
|
36
|
+
function decodeBase64url({ value, field, requestName }) {
|
|
37
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
38
|
+
throw new InvalidRequestBodyError({
|
|
39
|
+
requestName,
|
|
40
|
+
detail: `Operation "${field}" must be a non-empty base64url string.`,
|
|
41
|
+
pointer: `#/${field}`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return Buffer.from(value, 'base64url');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Expands a `publicAliasTemplate` against the key description, using the
|
|
48
|
+
* `url-template` package (RFC 6570): `{var}` substitutes the
|
|
49
|
+
* percent-encoded description field, `{+var}` substitutes it raw (a typical
|
|
50
|
+
* template is `{+controller}#{publicKeyMultibase}`). An unknown
|
|
51
|
+
* variable expands to the empty string, as in RFC 6570.
|
|
52
|
+
*
|
|
53
|
+
* @param options {object}
|
|
54
|
+
* @param options.template {string}
|
|
55
|
+
* @param options.description {KmsKeyDescription} expansion variables
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function expandAliasTemplate({ template, description }) {
|
|
59
|
+
const variables = description;
|
|
60
|
+
return template.replace(/\{(\+?)([A-Za-z0-9_]+)\}/g, (_match, rawModifier, name) => {
|
|
61
|
+
const value = variables[name];
|
|
62
|
+
const expanded = typeof value === 'string' ? value : '';
|
|
63
|
+
return rawModifier === '+' ? expanded : encodeURIComponent(expanded);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Builds the public key-description projection of a stored key: the
|
|
68
|
+
* allowlisted public fields plus the live keystore `controller` (never any
|
|
69
|
+
* secret field), with the `publicAlias` / expanded `publicAliasTemplate`
|
|
70
|
+
* override applied to `id`. Applied identically at generate time and on every
|
|
71
|
+
* subsequent read, so descriptions are stable (the client caches them).
|
|
72
|
+
*
|
|
73
|
+
* @param options {object}
|
|
74
|
+
* @param options.key {KmsStoredKey} the stored (secret-bearing) key
|
|
75
|
+
* @param options.controller {IDID} the keystore's current controller
|
|
76
|
+
* @returns {KmsKeyDescription}
|
|
77
|
+
*/
|
|
78
|
+
export function describeKmsKey({ key, controller }) {
|
|
79
|
+
const description = {
|
|
80
|
+
'@context': key['@context'],
|
|
81
|
+
id: key.id,
|
|
82
|
+
type: key.type,
|
|
83
|
+
...(key.publicKeyMultibase !== undefined && {
|
|
84
|
+
publicKeyMultibase: key.publicKeyMultibase
|
|
85
|
+
}),
|
|
86
|
+
controller
|
|
87
|
+
};
|
|
88
|
+
if (key.publicAlias !== undefined) {
|
|
89
|
+
description.id = key.publicAlias;
|
|
90
|
+
}
|
|
91
|
+
else if (key.publicAliasTemplate !== undefined) {
|
|
92
|
+
description.id = expandAliasTemplate({
|
|
93
|
+
template: key.publicAliasTemplate,
|
|
94
|
+
description
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return description;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Generates key material for a supported key type and assembles the stored
|
|
101
|
+
* key (full serialized form, secret material included) plus its public
|
|
102
|
+
* description. Field names are protocol-fixed: the asymmetric pairs carry
|
|
103
|
+
* `publicKeyMultibase` / `privateKeyMultibase`; the symmetric keys carry a
|
|
104
|
+
* base64url 256-bit `secret` (the webkms per-type key generators).
|
|
105
|
+
*
|
|
106
|
+
* @param options {object}
|
|
107
|
+
* @param options.keyId {string} the full key URL (`<keystoreId>/keys/<localId>`)
|
|
108
|
+
* @param options.type {string} the webkms key type
|
|
109
|
+
* @param options.controller {IDID} the keystore's current controller (for
|
|
110
|
+
* the returned description only -- never stored on the key)
|
|
111
|
+
* @param [options.maxCapabilityChainLength] {number}
|
|
112
|
+
* @param [options.publicAlias] {string}
|
|
113
|
+
* @param [options.publicAliasTemplate] {string}
|
|
114
|
+
* @returns {Promise<{ key: KmsStoredKey, keyDescription: KmsKeyDescription }>}
|
|
115
|
+
*/
|
|
116
|
+
export async function generateKmsKey({ keyId, type, controller, maxCapabilityChainLength, publicAlias, publicAliasTemplate }) {
|
|
117
|
+
// `Object.hasOwn`, not `KEY_TYPE_CONTEXTS[type] === undefined`: `type` is
|
|
118
|
+
// client-supplied, so a prototype-chain name (`constructor`, `toString`,
|
|
119
|
+
// `hasOwnProperty`, ...) would otherwise resolve to an inherited `Object`
|
|
120
|
+
// member and slip past the fail-closed rejection into a stored key record.
|
|
121
|
+
if (!Object.hasOwn(KEY_TYPE_CONTEXTS, type)) {
|
|
122
|
+
throw new InvalidRequestBodyError({
|
|
123
|
+
requestName: 'Generate Key',
|
|
124
|
+
detail: `Unsupported key type "${type}".`,
|
|
125
|
+
pointer: '#/invocationTarget/type'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const context = KEY_TYPE_CONTEXTS[type];
|
|
129
|
+
const key = { '@context': context, id: keyId, type };
|
|
130
|
+
if (type === 'Ed25519VerificationKey2020') {
|
|
131
|
+
const keyPair = await Ed25519VerificationKey.generate();
|
|
132
|
+
key.publicKeyMultibase = keyPair.publicKeyMultibase;
|
|
133
|
+
key.privateKeyMultibase = keyPair.privateKeyMultibase;
|
|
134
|
+
}
|
|
135
|
+
else if (type === 'X25519KeyAgreementKey2020') {
|
|
136
|
+
const keyPair = await X25519KeyAgreementKey2020.generate();
|
|
137
|
+
key.publicKeyMultibase = keyPair.publicKeyMultibase;
|
|
138
|
+
key.privateKeyMultibase = keyPair.privateKeyMultibase;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Sha256HmacKey2019 / AesKeyWrappingKey2019: a 256-bit symmetric secret.
|
|
142
|
+
key.secret = randomBytes(32).toString('base64url');
|
|
143
|
+
}
|
|
144
|
+
if (maxCapabilityChainLength !== undefined) {
|
|
145
|
+
key.maxCapabilityChainLength = maxCapabilityChainLength;
|
|
146
|
+
}
|
|
147
|
+
if (publicAlias !== undefined) {
|
|
148
|
+
key.publicAlias = publicAlias;
|
|
149
|
+
}
|
|
150
|
+
if (publicAliasTemplate !== undefined) {
|
|
151
|
+
key.publicAliasTemplate = publicAliasTemplate;
|
|
152
|
+
}
|
|
153
|
+
return { key, keyDescription: describeKmsKey({ key, controller }) };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* SignOperation on an Ed25519 key: signs the `verifyData` bytes with the
|
|
157
|
+
* custodial private key. The signature is not a secret, so serving it is the
|
|
158
|
+
* custody model WebKMS is built for.
|
|
159
|
+
*/
|
|
160
|
+
async function ed25519Sign({ key, operation }) {
|
|
161
|
+
const data = decodeBase64url({
|
|
162
|
+
value: operation.verifyData,
|
|
163
|
+
field: 'verifyData',
|
|
164
|
+
requestName: 'Sign Operation'
|
|
165
|
+
});
|
|
166
|
+
const keyPair = await Ed25519VerificationKey.from({ ...key });
|
|
167
|
+
const signature = await keyPair.signer().sign({ data });
|
|
168
|
+
return { signatureValue: Buffer.from(signature).toString('base64url') };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* DeriveSecretOperation on an X25519 key: raw ECDH against the submitted peer
|
|
172
|
+
* public key -- no KDF is applied; the caller runs the shared secret through
|
|
173
|
+
* one. A peer whose `type` does not match the key's is a clean 400 (the client
|
|
174
|
+
* pre-checks, so only non-client callers hit this).
|
|
175
|
+
*/
|
|
176
|
+
async function x25519DeriveSecret({ key, operation }) {
|
|
177
|
+
const requestName = 'Derive Secret Operation';
|
|
178
|
+
const publicKey = operation.publicKey;
|
|
179
|
+
if (typeof publicKey !== 'object' || publicKey === null) {
|
|
180
|
+
throw new InvalidRequestBodyError({
|
|
181
|
+
requestName,
|
|
182
|
+
detail: 'Operation "publicKey" must be a public key object.',
|
|
183
|
+
pointer: '#/publicKey'
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (publicKey.type !== key.type) {
|
|
187
|
+
throw new InvalidRequestBodyError({
|
|
188
|
+
requestName,
|
|
189
|
+
detail: `The given public key type "${publicKey.type}" does not match the` +
|
|
190
|
+
` key agreement key's type "${key.type}".`,
|
|
191
|
+
pointer: '#/publicKey/type'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const keyPair = await X25519KeyAgreementKey2020.from({ ...key });
|
|
195
|
+
let secret;
|
|
196
|
+
try {
|
|
197
|
+
secret = await keyPair.deriveSecret({ publicKey });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// A malformed / wrong-header `publicKeyMultibase` is a client error.
|
|
201
|
+
throw new InvalidRequestBodyError({
|
|
202
|
+
requestName,
|
|
203
|
+
detail: 'Operation "publicKey.publicKeyMultibase" is not a valid key.',
|
|
204
|
+
pointer: '#/publicKey/publicKeyMultibase'
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return { secret: Buffer.from(secret).toString('base64url') };
|
|
208
|
+
}
|
|
209
|
+
/** Computes the HMAC-SHA-256 of `verifyData` under the key's secret. */
|
|
210
|
+
function hmacDigest({ key, operation, requestName }) {
|
|
211
|
+
const data = decodeBase64url({
|
|
212
|
+
value: operation.verifyData,
|
|
213
|
+
field: 'verifyData',
|
|
214
|
+
requestName
|
|
215
|
+
});
|
|
216
|
+
const secret = Buffer.from(key.secret, 'base64url');
|
|
217
|
+
return createHmac('sha256', secret).update(data).digest();
|
|
218
|
+
}
|
|
219
|
+
/** SignOperation on an HMAC key. */
|
|
220
|
+
async function hmacSign({ key, operation }) {
|
|
221
|
+
const digest = hmacDigest({ key, operation, requestName: 'Sign Operation' });
|
|
222
|
+
return { signatureValue: digest.toString('base64url') };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* VerifyOperation on an HMAC key -- the one served verify (a client cannot
|
|
226
|
+
* check an HMAC without the symmetric secret). A wrong-*length*
|
|
227
|
+
* `signatureValue` is an ordinary `verified: false`, checked before
|
|
228
|
+
* `timingSafeEqual` (which throws on mismatched lengths).
|
|
229
|
+
*/
|
|
230
|
+
async function hmacVerify({ key, operation }) {
|
|
231
|
+
const requestName = 'Verify Operation';
|
|
232
|
+
const signature = decodeBase64url({
|
|
233
|
+
value: operation.signatureValue,
|
|
234
|
+
field: 'signatureValue',
|
|
235
|
+
requestName
|
|
236
|
+
});
|
|
237
|
+
const expected = hmacDigest({ key, operation, requestName });
|
|
238
|
+
const verified = signature.length === expected.length && timingSafeEqual(signature, expected);
|
|
239
|
+
return { verified };
|
|
240
|
+
}
|
|
241
|
+
/** WrapKeyOperation on an AES-KW key encryption key. */
|
|
242
|
+
async function aesWrapKey({ key, operation }) {
|
|
243
|
+
const unwrapped = decodeBase64url({
|
|
244
|
+
value: operation.unwrappedKey,
|
|
245
|
+
field: 'unwrappedKey',
|
|
246
|
+
requestName: 'Wrap Key Operation'
|
|
247
|
+
});
|
|
248
|
+
const secret = Buffer.from(key.secret, 'base64url');
|
|
249
|
+
const cipher = createCipheriv(AES_KW_ALGORITHM, secret, AES_KW_DEFAULT_IV);
|
|
250
|
+
const wrapped = Buffer.concat([cipher.update(unwrapped), cipher.final()]);
|
|
251
|
+
return { wrappedKey: wrapped.toString('base64url') };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* UnwrapKeyOperation on an AES-KW key encryption key. A failed unwrap -- the
|
|
255
|
+
* RFC 3394 integrity check rejecting the ciphertext (wrong KEK, corrupted
|
|
256
|
+
* `wrappedKey`) -- resolves `unwrappedKey: null` rather than erroring: that is
|
|
257
|
+
* the client's documented contract (`Kek.unwrapKey` resolves null when the key
|
|
258
|
+
* does not match).
|
|
259
|
+
*/
|
|
260
|
+
async function aesUnwrapKey({ key, operation }) {
|
|
261
|
+
const wrapped = decodeBase64url({
|
|
262
|
+
value: operation.wrappedKey,
|
|
263
|
+
field: 'wrappedKey',
|
|
264
|
+
requestName: 'Unwrap Key Operation'
|
|
265
|
+
});
|
|
266
|
+
const secret = Buffer.from(key.secret, 'base64url');
|
|
267
|
+
try {
|
|
268
|
+
const decipher = createDecipheriv(AES_KW_ALGORITHM, secret, AES_KW_DEFAULT_IV);
|
|
269
|
+
const unwrapped = Buffer.concat([
|
|
270
|
+
decipher.update(wrapped),
|
|
271
|
+
decipher.final()
|
|
272
|
+
]);
|
|
273
|
+
return { unwrappedKey: unwrapped.toString('base64url') };
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return { unwrappedKey: null };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The operation dispatch table: which operation envelope `type`s this module
|
|
281
|
+
* serves for each key type, and the pure-crypto implementation for each pair.
|
|
282
|
+
* Deliberate omissions (custody is the criterion): `VerifyOperation` on the
|
|
283
|
+
* asymmetric and key-agreement types (verify client-locally against the
|
|
284
|
+
* description's `publicKeyMultibase`), and every cross-type combination.
|
|
285
|
+
*/
|
|
286
|
+
const KEY_OPERATIONS = {
|
|
287
|
+
Ed25519VerificationKey2020: { SignOperation: ed25519Sign },
|
|
288
|
+
X25519KeyAgreementKey2020: { DeriveSecretOperation: x25519DeriveSecret },
|
|
289
|
+
Sha256HmacKey2019: { SignOperation: hmacSign, VerifyOperation: hmacVerify },
|
|
290
|
+
AesKeyWrappingKey2019: {
|
|
291
|
+
WrapKeyOperation: aesWrapKey,
|
|
292
|
+
UnwrapKeyOperation: aesUnwrapKey
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Runs a key operation envelope against a stored key, dispatching on
|
|
297
|
+
* `(key.type, operation.type)`. An operation this module does not serve for
|
|
298
|
+
* the key's type -- or does not recognize at all -- is a clean 400
|
|
299
|
+
* `UnsupportedKeyOperationError`.
|
|
300
|
+
*
|
|
301
|
+
* @param options {object}
|
|
302
|
+
* @param options.key {KmsStoredKey} the stored (secret-bearing) key
|
|
303
|
+
* @param options.operation {Record<string, unknown>} the validated envelope
|
|
304
|
+
* @returns {Promise<object>} the operation's wire result (`signatureValue` /
|
|
305
|
+
* `verified` / `secret` / `wrappedKey` / `unwrappedKey`)
|
|
306
|
+
*/
|
|
307
|
+
export async function runKeyOperation({ key, operation }) {
|
|
308
|
+
const operationType = operation.type;
|
|
309
|
+
// `Object.hasOwn` at both levels: `key.type` and `operationType` are both
|
|
310
|
+
// client-influenced, so plain-object indexing could otherwise resolve an
|
|
311
|
+
// inherited `Object` member (`constructor`, `toString`, ...) to a truthy
|
|
312
|
+
// function and bypass this fail-closed gate.
|
|
313
|
+
const operations = Object.hasOwn(KEY_OPERATIONS, key.type)
|
|
314
|
+
? KEY_OPERATIONS[key.type]
|
|
315
|
+
: undefined;
|
|
316
|
+
const runOperation = operations && Object.hasOwn(operations, operationType)
|
|
317
|
+
? operations[operationType]
|
|
318
|
+
: undefined;
|
|
319
|
+
if (runOperation === undefined) {
|
|
320
|
+
throw new UnsupportedKeyOperationError({
|
|
321
|
+
operationType,
|
|
322
|
+
keyType: key.type
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return await runOperation({ key, operation });
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=kmsModule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kmsModule.js","sourceRoot":"","sources":["../../src/lib/kmsModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { KmsKeyRecord, RecordKek, KmsRecordKekRegistry } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The plaintext allowlist: the `KmsStoredKey` fields that stay in the clear (and
|
|
4
|
+
* queryable) at rest. EVERY other field of `key` -- `privateKeyMultibase`,
|
|
5
|
+
* `secret`, and any secret-bearing field added later -- is swept into the
|
|
6
|
+
* envelope, so new secrets are protected by default unless explicitly added
|
|
7
|
+
* here. (Bedrock additionally lists `controller`; this server never stores
|
|
8
|
+
* `controller` on the key -- it is read from the live keystore config at
|
|
9
|
+
* description time -- so it is simply absent.)
|
|
10
|
+
*/
|
|
11
|
+
export declare const PLAINTEXT_KEY_FIELDS: ReadonlySet<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Derives a KEK's non-secret, stable id from its raw key bytes: a SHA-256 digest
|
|
14
|
+
* (one-way, so storing it per record leaks nothing about the key). Self-
|
|
15
|
+
* describing and deterministic, so a rotated-in KEK gets a stable id from its
|
|
16
|
+
* material alone -- the convention bedrock follows.
|
|
17
|
+
* @param key {Buffer} the 32-byte AES-256 KEK
|
|
18
|
+
* @returns {string} the derived `urn:kek:sha256:<hex>` id
|
|
19
|
+
*/
|
|
20
|
+
export declare function deriveKekId(key: Buffer): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parses a `secretKeyMultibase` (base58btc Multikey) AES-256 KEK into a
|
|
23
|
+
* `RecordKek` (its raw key plus a derived id). Throws a plain `Error` on a
|
|
24
|
+
* malformed value -- callers are config parsing (fails startup) and tests, not
|
|
25
|
+
* the request path. The error messages name `label` (the offending config
|
|
26
|
+
* variable, e.g. a `KMS_RECORD_KEKS entry N` position) but NEVER echo the secret
|
|
27
|
+
* value itself.
|
|
28
|
+
* @param secretKeyMultibase {string} the multibase (`z...`) KEK value
|
|
29
|
+
* @param [label] {string} the config source named in error messages
|
|
30
|
+
* (defaults to `KMS_RECORD_KEK`)
|
|
31
|
+
* @returns {RecordKek}
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseKekMultibase(secretKeyMultibase: string, label?: string): RecordKek;
|
|
34
|
+
/**
|
|
35
|
+
* The KEK that wraps NEW records, or `undefined` when encryption is disabled
|
|
36
|
+
* (no registry, or `currentKekId: null`).
|
|
37
|
+
* @param [registry] {KmsRecordKekRegistry}
|
|
38
|
+
* @returns {RecordKek | undefined}
|
|
39
|
+
*/
|
|
40
|
+
export declare function currentRecordKek(registry?: KmsRecordKekRegistry): RecordKek | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* A KEK-lookup function over a registry, resolving a record's stored `kekId` to
|
|
43
|
+
* its `RecordKek` for decryption (`undefined` when the KEK is not registered, or
|
|
44
|
+
* there is no registry at all). The decrypt seam for the pass-through /
|
|
45
|
+
* rotation upgrade paths.
|
|
46
|
+
* @param [registry] {KmsRecordKekRegistry}
|
|
47
|
+
* @returns {(kekId: string) => RecordKek | undefined}
|
|
48
|
+
*/
|
|
49
|
+
export declare function recordKekLoader(registry?: KmsRecordKekRegistry): (kekId: string) => RecordKek | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Encrypts the secret-bearing fields of a key record under a KEK, returning a
|
|
52
|
+
* record whose `key` carries the allowlisted plaintext fields plus an
|
|
53
|
+
* `encrypted` envelope in place of the secrets. Pure: the KEK is injected, no
|
|
54
|
+
* storage or env access. The record's non-`key` members (`keystoreId`,
|
|
55
|
+
* `localId`, `meta`) are unchanged.
|
|
56
|
+
* @param options {object}
|
|
57
|
+
* @param options.record {KmsKeyRecord} the plaintext (secret-bearing) record
|
|
58
|
+
* @param options.kek {RecordKek} the KEK to wrap the fresh CEK under
|
|
59
|
+
* @returns {KmsKeyRecord} the at-rest record (secrets replaced by `encrypted`)
|
|
60
|
+
*/
|
|
61
|
+
export declare function encryptKeyRecord({ record, kek }: {
|
|
62
|
+
record: KmsKeyRecord;
|
|
63
|
+
kek: RecordKek;
|
|
64
|
+
}): KmsKeyRecord;
|
|
65
|
+
/**
|
|
66
|
+
* Decrypts a key record: pass-through when `key.encrypted` is absent (a
|
|
67
|
+
* plaintext record -- the default, and the pass-through upgrade path), else
|
|
68
|
+
* looks up the KEK by the record's stored `kekId`, unwraps the CEK, decrypts the
|
|
69
|
+
* secret subset, and splices the secret fields back onto the allowlisted ones
|
|
70
|
+
* (dropping the envelope). Pure: KEK(s) injected via `kekLoader`, no storage or
|
|
71
|
+
* env access. Throws a plain `Error` when the record's KEK is not registered or
|
|
72
|
+
* the ciphertext fails to authenticate; the request seam wraps that as a 500.
|
|
73
|
+
* @param options {object}
|
|
74
|
+
* @param options.record {KmsKeyRecord} the at-rest (or plaintext) record
|
|
75
|
+
* @param options.kekLoader {(kekId: string) => RecordKek | undefined} KEK lookup
|
|
76
|
+
* @returns {KmsKeyRecord} the decrypted record (secrets restored on `key`)
|
|
77
|
+
*/
|
|
78
|
+
export declare function decryptKeyRecord({ record, kekLoader }: {
|
|
79
|
+
record: KmsKeyRecord;
|
|
80
|
+
kekLoader: (kekId: string) => RecordKek | undefined;
|
|
81
|
+
}): KmsKeyRecord;
|
|
82
|
+
//# sourceMappingURL=kmsRecordCipher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kmsRecordCipher.d.ts","sourceRoot":"","sources":["../../src/lib/kmsRecordCipher.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAEV,YAAY,EAEZ,SAAS,EACT,oBAAoB,EACrB,MAAM,aAAa,CAAA;AAEpB;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAQnD,CAAA;AAsBF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,kBAAkB,EAAE,MAAM,EAC1B,KAAK,SAAmB,GACvB,SAAS,CAoBX;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAE1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,GAAG,EACJ,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,GAAG,EAAE,SAAS,CAAA;CACf,GAAG,YAAY,CAmDf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,SAAS,EACV,EAAE;IACD,MAAM,EAAE,YAAY,CAAA;IACpB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAAA;CACpD,GAAG,YAAY,CAkDf"}
|