@bedrock/kms-http 13.0.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/README.md ADDED
@@ -0,0 +1,292 @@
1
+ # Bedrock Key Management System API module _(bedrock-kms-http)_
2
+ > HTTP APIs for Bedrock Key Management.
3
+
4
+ ## Table of Contents
5
+
6
+ - [Install](#install)
7
+ - [Usage](#usage)
8
+ - [Contribute](#contribute)
9
+ - [Commercial Support](#commercial-support)
10
+ - [License](#license)
11
+
12
+ ## Install
13
+
14
+ - Node.js 12+ is required.
15
+
16
+ ### NPM
17
+
18
+ To install via NPM:
19
+
20
+ ```
21
+ npm install --save bedrock-kms-http
22
+ ```
23
+
24
+ ### Development
25
+
26
+ To install locally (for development):
27
+
28
+ ```
29
+ git clone https://github.com/digitalbazaar/bedrock-kms-http.git
30
+ cd bedrock-kms-http
31
+ npm install
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ In `lib/index.js` (or `main.js`, as appropriate):
37
+
38
+ ```js
39
+ import 'bedrock-kms-http';
40
+ ```
41
+
42
+ ### KMS HTTP API
43
+
44
+ This module exposes the following API endpoints.
45
+
46
+ #### Create a Keystore - `POST /kms/keystores`
47
+
48
+ #### Request Example:
49
+ ```
50
+ {
51
+ sequence: 0,
52
+ controller: 'did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54',
53
+ meterId: 'https://localhost:18443/meters/z3pYurSyUQ5BTBcahRKuzri',
54
+ kmsModule: 'ssm-v1'
55
+ }
56
+ ```
57
+
58
+ #### Headers
59
+ ```
60
+ {
61
+ accept: 'application/ld+json, application/json',
62
+ host: 'localhost:18443',
63
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores",action="write"',
64
+ digest: 'mh=uEiAWwH_YYtljJn-tba5XZ_s5k9lcFoJW9Mg8EdPY3ShEvQ',
65
+ 'content-type': 'application/json',
66
+ authorization: 'Signature keyId="did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54#z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54",headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",signature="hTFuyAyfdwmTRJ1DmK2AVgDZ+RCQYsc5n9YF///5t4QY3VaTtUGWRDWQHRTSMGHybda782fCW9XczmIJEMfFDQ==",created="1638504433",expires="1638505033"'
67
+ }
68
+ ```
69
+
70
+ #### Response Example:
71
+ ```
72
+ {
73
+ id: 'https://localhost:18443/kms/keystores/z1AByGXEmfZnyG8rgM3aVpYxG',
74
+ meterId: 'https://localhost:18443/meters/z3pYurSyUQ5BTBcahRKuzri',
75
+ sequence: 0,
76
+ controller: 'did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54',
77
+ kmsModule: 'ssm-v1'
78
+ }
79
+ ```
80
+
81
+ #### Update Keystore Config - `POST /kms/keystores/:keystoreId`
82
+
83
+ #### Request Example:
84
+ ```
85
+ {
86
+ controller: 'did:key:z6MknP29cPcQ7G76MWmnsuEEdeFya8ij3fXvJcTJYLXadmp9',
87
+ id: 'https://localhost:18443/kms/keystores/z19xPXyqB3dnqKZ7BtVFvqSLH',
88
+ sequence: 1
89
+ }
90
+ ```
91
+
92
+ #### Headers
93
+ ```
94
+ {
95
+ accept: 'application/ld+json, application/json',
96
+ host: 'localhost:18443',
97
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz19xPXyqB3dnqKZ7BtVFvqSLH",action="write"',
98
+ digest: 'mh=uEiDiFdv-C0VNNIvltnoIxwqUA5crG1H42OyvQtPRa7mjvw',
99
+ 'content-type': 'application/json',
100
+ authorization: 'Signature keyId="did:key:z6MkqnGN2jgckeejguN28RE1SM5a1rwu9nigkY5PhSQuTybX#z6MkqnGN2jgckeejguN28RE1SM5a1rwu9nigkY5PhSQuTybX",headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",signature="6i3yXShQMQ8H+XFJpUEi0WcO7hcrcpdApuyU2WrSAm0bMuAby+pZKSt+ACBWRrpocEaxAa6lwefvtGVYVXO7BQ==",created="1638505029",expires="1638505629"'
101
+ }
102
+ ```
103
+
104
+ #### Response Example:
105
+ ```
106
+ {
107
+ success: true,
108
+ config: {
109
+ controller: 'did:key:z6MknP29cPcQ7G76MWmnsuEEdeFya8ij3fXvJcTJYLXadmp9',
110
+ id: 'https://localhost:18443/kms/keystores/z19xPXyqB3dnqKZ7BtVFvqSLH',
111
+ sequence: 1,
112
+ meterId: 'https://localhost:18443/meters/zD3E9XJxyUUuJ5ZW14EhCFy',
113
+ kmsModule: 'ssm-v1'
114
+ }
115
+ }
116
+ ```
117
+
118
+ #### Get Keystore Config - `GET /kms/keystores/:keystoreId`
119
+
120
+ #### Request Example:
121
+
122
+ #### Headers
123
+ ```
124
+ {
125
+ accept: 'application/ld+json, application/json',
126
+ host: 'localhost:18443',
127
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz1A2Dahs56An4rttS3t2QKi69",action="read"',
128
+ authorization: 'Signature keyId="did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54#z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54",headers="(key-id) (created) (expires) (request-target) host capability-invocation",signature="P8ZYsAO/oQPllqRCW4GJdKwHhGfk53li88pzySd2jJVhNJCkxwPVNTGS0CsK/tK1cLvwjUABUqO2VUlP//T3DA==",created="1638505290",expires="1638505890"'
129
+ }
130
+ ```
131
+
132
+ #### Response Example:
133
+ ```
134
+ {
135
+ id: 'https://localhost:18443/kms/keystores/z1A2Dahs56An4rttS3t2QKi69',
136
+ meterId: 'https://localhost:18443/meters/zC3vXcEbC3iYEntNVZxvq6E',
137
+ sequence: 0,
138
+ controller: 'did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54',
139
+ kmsModule: 'ssm-v1'
140
+ }
141
+ ```
142
+
143
+ #### Generate a new Key - `POST /kms/keystores/:keystoreId/keys`
144
+
145
+ #### Request Example:
146
+ ```
147
+ {
148
+ '@context': [
149
+ 'https://w3id.org/webkms/v1',
150
+ 'https://w3id.org/security/suites/ed25519-2020/v1'
151
+ ],
152
+ type: 'GenerateKeyOperation',
153
+ invocationTarget: { type: 'Ed25519VerificationKey2020' }
154
+ }
155
+ ```
156
+
157
+ #### Headers:
158
+ ```
159
+ {
160
+ accept: 'application/ld+json, application/json',
161
+ host: 'localhost:18443',
162
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz19mH7zKauTKBWS3831MyWM2f",action="generateKey"',
163
+ digest: 'mh=uEiC4fs8V2JzjgB25wxRhyKGc17b0yu-JvjnYF6IutYMEzQ',
164
+ 'content-type': 'application/json',
165
+ authorization: 'Signature keyId="did:key:z6MkoSN3SbRHZAFdYwphhgHhuLXdBCMu7Yoam5H436b1PZAp#z6MkoSN3SbRHZAFdYwphhgHhuLXdBCMu7Yoam5H436b1PZAp",headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",signature="fntyQjEsGMIOwh7OftKS76yJ/1wpx+3eVWJjIToqcfJPOlmuU3DZmbag4bZ3cw3weLg4kDYQNxsJ8nNVtpd4DQ==",created="1638506601",expires="1638507201"'
166
+ }
167
+ ```
168
+ #### Response Example:
169
+ ```
170
+ {
171
+ id: 'https://localhost:18443/kms/keystores/z19mH7zKauTKBWS3831MyWM2f/keys/z1A3XvbwPFZ5WceoeXcQMzBZ5',
172
+ type: 'Ed25519VerificationKey2020',
173
+ '@context': 'https://w3id.org/security/suites/ed25519-2020/v1',
174
+ publicKeyMultibase: 'z6MknYsXcwi19ohJKGu5RHzxAkyVDGCP3ggpHY2FqxNFFyZb'
175
+ }
176
+ ```
177
+
178
+ #### Invoke KMS Operation on Existing Key - `POST /kms/keystores/:keystoreId/keys/:keyId`
179
+
180
+ #### Request Example:
181
+ ```
182
+ {
183
+ '@context': 'https://w3id.org/webkms/v1',
184
+ type: 'SignOperation',
185
+ invocationTarget: 'https://localhost:18443/kms/keystores/z19zhh4xhWEeBn7u5NXQaeU2e/keys/z1A4nmgJS9XkDAsDDHFhAEba3',
186
+ verifyData: 'aGVsbG8'
187
+ }
188
+ ```
189
+
190
+ #### Headers
191
+ ```
192
+ {
193
+ accept: 'application/ld+json, application/json',
194
+ host: 'localhost:18443',
195
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz19zhh4xhWEeBn7u5NXQaeU2e",action="sign"',
196
+ digest: 'mh=uEiADFF5OnUq5SCTrVVBSAMP5DpXfSF7f1bIH-SrX6S9Fdw',
197
+ 'content-type': 'application/json',
198
+ authorization: 'Signature keyId="did:key:z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54#z6MkoyZVQSetDxU66AsfinUPzgSEu5N9Jf1anHEY4YCzcE54",headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",signature="upDUCqfbaIqixWmFjnHq/NIY2/r5TserYAQMOEkiwoUIyHgww/auN+cMYqH7x23cmymfmDIlDqqoE+Pi06j+AQ==",created="1638508033",expires="1638508633"'
199
+ }
200
+ ```
201
+
202
+
203
+ #### Response Example:
204
+ ```
205
+ { signatureValue: 'qtL549jF562rlea6oQBzIKYCZeOPFX9OBzmn3iSzOpI' }
206
+ ```
207
+
208
+ #### Get Public Key Description - `GET /kms/keystores/:keystoreId/keys/:keyId`
209
+
210
+ #### Request Example:
211
+
212
+ #### Headers
213
+ ```
214
+ {
215
+ accept: 'application/ld+json, application/json',
216
+ host: 'localhost:18443',
217
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz1AEdRUJm7Ld4xBNBKkAGHjzZ%2Fkeys%2Fz1A5vBtppSSaS1svFyQb6Ezwj",action="read"',
218
+ authorization: 'Signature keyId="did:key:z6MkhRjrvpEAxh8oSWsPgMdP28xvrotsDBCvcwTtbUuUEp38#z6MkhRjrvpEAxh8oSWsPgMdP28xvrotsDBCvcwTtbUuUEp38",headers="(key-id) (created) (expires) (request-target) host capability-invocation",signature="gmB9DXTu29QKO4FlHXcEJGHMjaRpvXosRw+KeShiDB9YU2kwjMdd7eMT15TgeKzN3K5eqbEyEWh0NCgT/ULFAA==",created="1638539438",expires="1638540038"'
219
+ }
220
+ ```
221
+
222
+ #### Response Example:
223
+ ```
224
+ {
225
+ '@context': 'https://w3id.org/security/suites/ed25519-2020/v1',
226
+ id: 'https://localhost:18443/kms/keystores/z1AEdRUJm7Ld4xBNBKkAGHjzZ/keys/z1A5vBtppSSaS1svFyQb6Ezwj',
227
+ type: 'Ed25519VerificationKey2020',
228
+ publicKeyMultibase: 'z6MkuP4PkTPZy2KvictPAsAHAbH6Umw9tsRJzmpGR65brddP'
229
+ }
230
+ ```
231
+
232
+ #### Insert a Revocation - `POST /kms/keystores/:keystoreId/revocations/:zcapId`
233
+
234
+ #### Request Example:
235
+ ```
236
+ {
237
+ '@context': [
238
+ 'https://w3id.org/zcap/v1',
239
+ 'https://w3id.org/security/suites/ed25519-2020/v1'
240
+ ],
241
+ id: 'urn:zcap:38c42aff-76e0-4624-88dc-0341fe3f9ccd',
242
+ invoker: 'did:key:z6MkootHWoSfwsPz1E3pE3aALJCEiawN1XNf4CPoUNeUEgY8#z6MkootHWoSfwsPz1E3pE3aALJCEiawN1XNf4CPoUNeUEgY8',
243
+ parentCapability: 'urn:zcap:45bbf83c-bb97-474d-9a54-f4dac1e257ad',
244
+ allowedAction: 'sign',
245
+ invocationTarget: {
246
+ publicAlias: 'did:key:z6Mkf8beJEDMQM77i12Siuw8UwuidEin6qvUQH65EBs3VqcW#z6Mkf8beJEDMQM77i12Siuw8UwuidEin6qvUQH65EBs3VqcW',
247
+ id: 'https://localhost:18443/kms/keystores/z19zzGxGeo6Z4FhqhP1ZJZhNp/keys/z19qdYFKqymy8BoPpdfSkeRrZ',
248
+ type: 'Ed25519VerificationKey2020'
249
+ },
250
+ proof: {
251
+ type: 'Ed25519Signature2020',
252
+ created: '2021-12-03T00:54:36Z',
253
+ verificationMethod: 'did:key:z6MkfrMHgiLotA5tX5JqtmQw1WmtmHCay7eY651TQzKdHAcB#z6MkfrMHgiLotA5tX5JqtmQw1WmtmHCay7eY651TQzKdHAcB',
254
+ proofPurpose: 'capabilityDelegation',
255
+ capabilityChain: [
256
+ 'urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz19zzGxGeo6Z4FhqhP1ZJZhNp',
257
+ [Object]
258
+ ],
259
+ proofValue: 'z5UMPNnR2sXv5tZ4XVrRjGqwh6zPchrcNBgedXDS4XEnSgeQAFEuiAx89nUcxDZV2e2Tb17cZKoLvyvSdRorKHoHw'
260
+ }
261
+ }
262
+ ```
263
+
264
+ #### Headers
265
+ ```
266
+ {
267
+ accept: 'application/ld+json, application/json',
268
+ host: 'localhost:18443',
269
+ 'capability-invocation': 'zcap id="urn:zcap:root:https%3A%2F%2Flocalhost%3A18443%2Fkms%2Fkeystores%2Fz1ADWeUn1vp2oTr33SaSoiiDj%2Frevocations%2Furn%253Azcap%253A61f9fce0-595d-4763-9854-c5db561e6981",action="write"',
270
+ digest: 'mh=uEiCQYBu8dlESvD_MO4VxN3FcJMuSRg1HRh3B-ciaEHpsxQ',
271
+ 'content-type': 'application/json',
272
+ authorization: 'Signature keyId="did:key:z6MkprpMfcYbjbCmhb84TWwhkwevqTX67EQvd7bdLTJ3DkA6#z6MkprpMfcYbjbCmhb84TWwhkwevqTX67EQvd7bdLTJ3DkA6",headers="(key-id) (created) (expires) (request-target) host capability-invocation content-type digest",signature="Z5DUpumlZYK3UplUDjz4szzWf+XO3zHd/Y6/wPMtUQn+7ZxNoLRa7Ny589CNASxYS7kg9Hv9KDF+R8wtiCJhAg==",created="1638492060",expires="1638492660"'
273
+ }
274
+ ```
275
+
276
+ ## Contribute
277
+
278
+ See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)!
279
+
280
+ PRs accepted.
281
+
282
+ If editing the Readme, please conform to the
283
+ [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
284
+
285
+ ## Commercial Support
286
+
287
+ Commercial support for this library is available upon request from
288
+ Digital Bazaar: support@digitalbazaar.com
289
+
290
+ ## License
291
+
292
+ [Bedrock Non-Commercial License v1.0](LICENSE.md) © Digital Bazaar
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import * as helpers from './helpers.js';
6
+ import {createRequire} from 'module';
7
+ import {keystores} from '@bedrock/kms';
8
+ const require = createRequire(import.meta.url);
9
+ const {KeystoreConfigStorage} = require('@digitalbazaar/webkms-switch');
10
+
11
+ const {util: {BedrockError}} = bedrock;
12
+
13
+ // load config defaults
14
+ import './config.js';
15
+
16
+ export class BedrockKeystoreConfigStorage extends KeystoreConfigStorage {
17
+ async get({id, req} = {}) {
18
+ const {config: keystoreConfig} = await keystores.get({id});
19
+
20
+ // skip request checks if specifically requested
21
+ if(req === false) {
22
+ return keystoreConfig;
23
+ }
24
+
25
+ // verify that request is from an IP that is allowed to access the config
26
+ const {verified} = helpers.verifyRequestIp({keystoreConfig, req});
27
+ if(!verified) {
28
+ throw new BedrockError(
29
+ 'Permission denied. Source IP is not allowed.', 'NotAllowedError', {
30
+ httpStatusCode: 403,
31
+ public: true,
32
+ });
33
+ }
34
+
35
+ return keystoreConfig;
36
+ }
37
+ }
package/lib/config.js ADDED
@@ -0,0 +1,37 @@
1
+ /*!
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import {config} from '@bedrock/core';
5
+ import '@bedrock/app-identity';
6
+ import '@bedrock/meter-usage-reporter';
7
+ import '@bedrock/validation';
8
+
9
+ const namespace = 'kms-http';
10
+ const cfg = config[namespace] = {};
11
+
12
+ const basePath = '/kms';
13
+ cfg.routes = {
14
+ basePath
15
+ };
16
+
17
+ // storage size to report to meter service
18
+ cfg.storageCost = {
19
+ revocation: 1
20
+ };
21
+
22
+ // create dev application key for webkms (must be overridden in deployments)
23
+ // ...and `ensureConfigOverride` has already been set via
24
+ // `bedrock-app-identity` so it doesn't have to be set here
25
+ config['app-identity'].seeds.services.webkms = {
26
+ id: 'did:key:z6MkwZ7AXrDpuVi5duY2qvVSx1tBkGmVnmRjDvvwzoVnAzC4',
27
+ seedMultibase: 'z1AWrfBoQx1mbiWBfWT7eksbtJf91v2pvEpwhoHDzezfaiH',
28
+ serviceType: 'webkms'
29
+ };
30
+
31
+ cfg.kmsOperationOptions = {
32
+ maxChainLength: 10,
33
+ // 300 second clock skew permitted by default
34
+ maxClockSkew: 300,
35
+ // 1000 year max TTL by default
36
+ maxDelegationTtl: 1000 * 60 * 60 * 24 * 365 * 1000
37
+ };
package/lib/helpers.js ADDED
@@ -0,0 +1,36 @@
1
+ /*!
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import forwarded from 'forwarded';
5
+ import {Netmask} from 'netmask';
6
+
7
+ export function getKeystoreId({host, req, localId, routes}) {
8
+ if(!host) {
9
+ host = req.get('host');
10
+ }
11
+ return `https://${host}${routes.keystores}/${localId}`;
12
+ }
13
+
14
+ export function verifyRequestIp({keystoreConfig, req}) {
15
+ const {ipAllowList} = keystoreConfig;
16
+ if(!ipAllowList) {
17
+ return {verified: true};
18
+ }
19
+
20
+ // the first IP in the sourceAddresses array will *always* be the IP
21
+ // reported by Express.js via `req.connection.remoteAddress`. Any additional
22
+ // IPs will be from the `x-forwarded-for` header.
23
+ const sourceAddresses = forwarded(req);
24
+
25
+ // ipAllowList is an array of CIDRs
26
+ for(const cidr of ipAllowList) {
27
+ const netmask = new Netmask(cidr);
28
+ for(const address of sourceAddresses) {
29
+ if(netmask.contains(address)) {
30
+ return {verified: true};
31
+ }
32
+ }
33
+ }
34
+
35
+ return {verified: false};
36
+ }
package/lib/http.js ADDED
@@ -0,0 +1,263 @@
1
+ /*!
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import * as bedrock from '@bedrock/core';
5
+ import * as brZCapStorage from '@bedrock/zcap-storage';
6
+ import * as helpers from './helpers.js';
7
+ import * as middleware from './middleware.js';
8
+ import {asyncHandler} from '@bedrock/express';
9
+ import cors from 'cors';
10
+ import {createRequire} from 'module';
11
+ import {createValidateMiddleware as validate} from '@bedrock/validation';
12
+ import {
13
+ defaultModuleManager as moduleManager,
14
+ keystores
15
+ } from '@bedrock/kms';
16
+ import {meters} from '@bedrock/meter-usage-reporter';
17
+ import {
18
+ postKeystoreBody,
19
+ updateKeystoreConfigBody,
20
+ postRevocationBody
21
+ } from '../schemas/bedrock-kms-http.js';
22
+ import {reportOperationUsage, SERVICE_TYPE} from './metering.js';
23
+ const require = createRequire(import.meta.url);
24
+ const {generateRandom} = require('@digitalbazaar/webkms-switch');
25
+
26
+ const {config, util: {BedrockError}} = bedrock;
27
+
28
+ bedrock.events.on('bedrock-express.configure.routes', app => {
29
+ const cfg = config['kms-http'];
30
+
31
+ // get storage cost for low-level primitives of keystores and keys as well
32
+ // as higher-level revoked zcaps
33
+ const storageCost = {
34
+ ...config.kms.storageCost,
35
+ ...cfg.storageCost
36
+ };
37
+
38
+ // WebKMS paths are fixed off of the base path per the spec
39
+ const routes = {...cfg.routes};
40
+ routes.keystores = `${routes.basePath}/keystores`;
41
+ routes.keystore = `${routes.keystores}/:keystoreId`;
42
+ routes.keys = `${routes.keystore}/keys`;
43
+ routes.key = `${routes.keys}/:keyId`;
44
+ routes.revocations = `${routes.keystore}/zcaps/revocations/:revocationId`;
45
+
46
+ // create middleware for handling KMS operations
47
+ const handleOperation = middleware.createKmsOperationMiddleware();
48
+
49
+ // create middleware for getting consistent view of the keystore config
50
+ // associated with a request
51
+ const getKeystoreConfig = middleware.createGetKeystoreConfig({routes});
52
+
53
+ /* Note: CORS is used on all endpoints. This is safe because authorization
54
+ uses HTTP signatures + capabilities, not cookies; CSRF is not possible. */
55
+
56
+ // create a new keystore
57
+ app.options(routes.keystores, cors());
58
+ app.post(
59
+ routes.keystores,
60
+ cors(),
61
+ validate({bodySchema: postKeystoreBody}),
62
+ // meter must be checked for available usage and to obtain the meter's
63
+ // controller prior to checking the zcap invocation (as the invocation
64
+ // will use the meter's controller as the root controller for keystore
65
+ // creation)
66
+ asyncHandler(async (req, res, next) => {
67
+ const {body: {meterId: id}} = req;
68
+ const {meter, hasAvailable} = await meters.hasAvailable({
69
+ id, serviceType: SERVICE_TYPE,
70
+ resources: {storage: storageCost.keystore}
71
+ });
72
+ // store meter information on `req` and call next middleware
73
+ req.meterCheck = {meter, hasAvailable};
74
+ next();
75
+ }),
76
+ // now that meter information has been obtained, authorize zcap invocation
77
+ middleware.authorizeZcapInvocation({
78
+ async getExpectedValues() {
79
+ const expectedHost = config.server.host;
80
+ return {
81
+ host: expectedHost,
82
+ // expect root invocation target to match this route; the root zcap
83
+ // will have its controller dynamically set to the controller of the
84
+ // meter used as below in `getRootController`
85
+ rootInvocationTarget: `https://${expectedHost}${routes.keystores}`
86
+ };
87
+ },
88
+ async getRootController({req}) {
89
+ // use meter's controller as the root controller for the keystore
90
+ // creation endpoint
91
+ return req.meterCheck.meter.controller;
92
+ }
93
+ }),
94
+ asyncHandler(async (req, res) => {
95
+ const {body: {meterId}, meterCheck: {hasAvailable}} = req;
96
+ if(!hasAvailable) {
97
+ // insufficient remaining storage
98
+ throw new BedrockError('Permission denied.', 'NotAllowedError', {
99
+ httpStatusCode: 403,
100
+ public: true,
101
+ });
102
+ }
103
+
104
+ // FIXME: this is a high-latency call -- consider adding the meter
105
+ // in parallel with inserting the keystore, optimistically presuming it
106
+ // will be added; we could decide that the case of a missing/invalid
107
+ // meter is a possible state we have to deal in other cases anyway
108
+ // https://github.com/digitalbazaar/bedrock-kms-http/issues/57
109
+
110
+ // add meter
111
+ await meters.upsert({id: meterId, serviceType: SERVICE_TYPE});
112
+
113
+ // create a keystore for the controller
114
+ const random = await generateRandom();
115
+ const id = helpers.getKeystoreId({req, localId: random, routes});
116
+ const config = {id, meterId, ...req.body};
117
+ const record = await keystores.insert({config});
118
+ res.status(201).location(id).json(record.config);
119
+ }));
120
+
121
+ // update keystore config
122
+ app.options(routes.keystore, cors());
123
+ app.post(
124
+ routes.keystore,
125
+ cors(),
126
+ validate({bodySchema: updateKeystoreConfigBody}),
127
+ getKeystoreConfig,
128
+ // FIXME: if a new meter is sent, set the root controller to be that of
129
+ // the meter; otherwise set it to be that of the keystore
130
+ middleware.authorizeKeystoreZcapInvocation(),
131
+ asyncHandler(async (req, res) => {
132
+ const {body: config} = req;
133
+ const {keystore: existingConfig} = req.webkms;
134
+ if(existingConfig.id !== config.id) {
135
+ throw new BedrockError(
136
+ 'Configuration "id" does not match request URL.',
137
+ 'URLMismatchError', {
138
+ httpStatusCode: 400,
139
+ public: true,
140
+ requestUrl: existingConfig.id,
141
+ configId: config.id,
142
+ expected: existingConfig.id,
143
+ actual: config.id
144
+ });
145
+ }
146
+
147
+ // add meter if a new one was given
148
+ let {meterId} = config;
149
+ if(meterId && meterId !== existingConfig.meterId) {
150
+ // FIXME: only enable once root controller FIXME is addressed above
151
+ // for the case where a new meter is sent
152
+ throw new Error('Not implemented; meter cannot be changed.');
153
+ // await meters.upsert({id: meterId, serviceType: SERVICE_TYPE});
154
+ } else {
155
+ ({meterId} = existingConfig);
156
+ }
157
+
158
+ // ensure `meterId` is set on config (using either existing one or new
159
+ // one)
160
+ config.meterId = meterId;
161
+
162
+ // ensure `kmsModule` is set; if already set, allow `update` to proceed
163
+ // as it will throw an error if it does not match the existing config
164
+ if(!config.kmsModule) {
165
+ config.kmsModule = existingConfig.kmsModule;
166
+ }
167
+
168
+ // the `update` API will not apply the change and will throw if
169
+ // `config.sequence` is not valid, no need to check it here
170
+ await keystores.update({config});
171
+
172
+ // return new config
173
+ res.json({config});
174
+
175
+ // report updating config as operation usage
176
+ reportOperationUsage({req});
177
+ }));
178
+
179
+ // get a keystore config
180
+ app.get(
181
+ routes.keystore,
182
+ cors(),
183
+ getKeystoreConfig,
184
+ middleware.authorizeKeystoreZcapInvocation(),
185
+ asyncHandler(async (req, res) => {
186
+ res.json(req.webkms.keystore);
187
+ }));
188
+
189
+ // invoke a generate key KMS operation to generate a new key
190
+ app.options(routes.keys, cors());
191
+ app.post(
192
+ routes.keys,
193
+ cors(),
194
+ handleOperation);
195
+
196
+ // invoke KMS operation on an existing key
197
+ app.options(routes.key, cors());
198
+ app.post(
199
+ routes.key,
200
+ cors(),
201
+ handleOperation);
202
+
203
+ // TODO: consider whether this should be exposed w/o authorization
204
+ // https://github.com/digitalbazaar/bedrock-kms-http/issues/56
205
+
206
+ // return a (public) key description
207
+ app.get(
208
+ routes.key,
209
+ cors(),
210
+ getKeystoreConfig,
211
+ asyncHandler(async (req, res) => {
212
+ const {webkms: {keystore}} = req;
213
+ const keyId = `${keystore.id}/keys/${req.params.keyId}`;
214
+ const moduleApi = await moduleManager.get({id: keystore.kmsModule});
215
+ const keyDescription = await moduleApi.getKeyDescription({
216
+ keyId, controller: keystore.controller
217
+ });
218
+ res.json(keyDescription);
219
+ }));
220
+
221
+ // insert a revocation
222
+ app.options(routes.revocations, cors());
223
+ app.post(
224
+ routes.revocations,
225
+ cors(),
226
+ validate({bodySchema: postRevocationBody}),
227
+ getKeystoreConfig,
228
+ middleware.authorizeZcapRevocation(),
229
+ asyncHandler(async (req, res) => {
230
+ const {
231
+ body: capability,
232
+ webkms: {keystore},
233
+ zcapRevocation: {delegator}
234
+ } = req;
235
+
236
+ // check meter revocation usage; but only check to see if the meter
237
+ // is disabled or not; allow storage overflow with revocations to
238
+ // ensure security can be locked down; presumption is this endpoint
239
+ // will be heavily rate limited
240
+ const {meterId} = keystore;
241
+ const {meter: {disabled}} = await meters.hasAvailable({
242
+ id: meterId, serviceType: SERVICE_TYPE,
243
+ resources: {storage: cfg.storageCost.revocation}
244
+ });
245
+ if(disabled) {
246
+ // meter is disabled, do not allow storage
247
+ throw new BedrockError('Permission denied.', 'NotAllowedError', {
248
+ httpStatusCode: 403,
249
+ public: true,
250
+ });
251
+ }
252
+
253
+ // record revocation
254
+ await brZCapStorage.revocations.insert(
255
+ {delegator, rootTarget: keystore.id, capability});
256
+
257
+ // success, no response body
258
+ res.status(204).end();
259
+
260
+ // report revocation usage as operation usage
261
+ reportOperationUsage({req});
262
+ }));
263
+ });
package/lib/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import '@bedrock/express';
5
+
6
+ import './http.js';
7
+
8
+ // load config defaults
9
+ import './config.js';
package/lib/logger.js ADDED
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
3
+ */
4
+ import {loggers} from '@bedrock/core';
5
+
6
+ export const logger = loggers.get('app').child('bedrock-kms-http');