@bedrock/kms-http 14.2.1 → 14.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # bedrock-kms-http ChangeLog
2
2
 
3
+ ## 14.4.0 - 2022-05-19
4
+
5
+ ### Added
6
+ - Add max age header for CORS, defaulting to the max acceptable time for
7
+ modern browsers of 86400 seconds (24 hours).
8
+
9
+ ## 14.3.1 - 2022-05-16
10
+
11
+ ### Fixed
12
+ - Use `@digitalbazaar/webkms-switch@10.2` to ensure that pre-cached `webkms`
13
+ request meta data is not overwritten and that a custom `getRootController`
14
+ function is used (to enable cache busting on KMS operations).
15
+
16
+ ## 14.3.0 - 2022-05-16
17
+
18
+ ### Added
19
+ - Add debug logging for keystore config cache busting.
20
+
3
21
  ## 14.2.1 - 2022-05-13
4
22
 
5
23
  ### Fixed
package/lib/http.js CHANGED
@@ -6,7 +6,6 @@ import * as brZCapStorage from '@bedrock/zcap-storage';
6
6
  import * as helpers from './helpers.js';
7
7
  import * as middleware from './middleware.js';
8
8
  import {asyncHandler} from '@bedrock/express';
9
- import cors from 'cors';
10
9
  import {createRequire} from 'node:module';
11
10
  import {createValidateMiddleware as validate} from '@bedrock/validation';
12
11
  import {
@@ -24,6 +23,7 @@ const require = createRequire(import.meta.url);
24
23
  const {generateRandom} = require('@digitalbazaar/webkms-switch');
25
24
 
26
25
  const {config, util: {BedrockError}} = bedrock;
26
+ const {cors} = middleware;
27
27
 
28
28
  bedrock.events.on('bedrock-express.configure.routes', app => {
29
29
  const cfg = config['kms-http'];
@@ -191,6 +191,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
191
191
  app.post(
192
192
  routes.keys,
193
193
  cors(),
194
+ getKeystoreConfig,
194
195
  handleOperation);
195
196
 
196
197
  // invoke KMS operation on an existing key
@@ -198,6 +199,7 @@ bedrock.events.on('bedrock-express.configure.routes', app => {
198
199
  app.post(
199
200
  routes.key,
200
201
  cors(),
202
+ getKeystoreConfig,
201
203
  handleOperation);
202
204
 
203
205
  // TODO: consider whether this should be exposed w/o authorization
package/lib/middleware.js CHANGED
@@ -7,10 +7,12 @@ import * as helpers from './helpers.js';
7
7
  import {asyncHandler} from '@bedrock/express';
8
8
  import {BedrockKeystoreConfigStorage} from './BedrockKeystoreConfigStorage.js';
9
9
  import {createRequire} from 'node:module';
10
+ import {default as _cors} from 'cors';
10
11
  import {
11
12
  defaultDocumentLoader as documentLoader,
12
13
  defaultModuleManager as moduleManager
13
14
  } from '@bedrock/kms';
15
+ import {logger} from './logger.js';
14
16
  import {reportOperationUsage} from './metering.js';
15
17
  const require = createRequire(import.meta.url);
16
18
  const {
@@ -45,7 +47,7 @@ export function createGetKeystoreConfig({routes}) {
45
47
  id: keystoreId, req, returnRecord: true
46
48
  });
47
49
  const {config: keystore} = configRecord;
48
- req.webkms = {keystore, configRecord};
50
+ req.webkms = {...req.webkms, keystore, configRecord};
49
51
  }
50
52
  next();
51
53
  });
@@ -57,7 +59,10 @@ export function createKmsOperationMiddleware() {
57
59
  return createOperationMiddleware({
58
60
  ...kmsOperationOptions,
59
61
  storage, moduleManager, documentLoader,
60
- expectedHost: config.server.host, getVerifier,
62
+ expectedHost: config.server.host,
63
+ // override `getRootController` to provide cache busting capability
64
+ getRootController: getKeystoreController,
65
+ getVerifier,
61
66
  inspectCapabilityChain,
62
67
  onError, onSuccess: reportOperationUsage, suiteFactory
63
68
  });
@@ -103,6 +108,11 @@ export function authorizeZcapRevocation() {
103
108
  });
104
109
  }
105
110
 
111
+ export function cors() {
112
+ // `86400` is the max acceptable cache age for modern browsers
113
+ return _cors({maxAge: 86400});
114
+ }
115
+
106
116
  async function getKeystoreController({req}) {
107
117
  /* The following code is to prevent false-negative authz errors that are the
108
118
  result of stale cached keystore configs.
@@ -166,6 +176,10 @@ async function getKeystoreController({req}) {
166
176
  }
167
177
 
168
178
  if(!keyId) {
179
+ logger.debug(
180
+ 'No verification method found in zcap when trying to bust keystore ' +
181
+ 'config cache.');
182
+
169
183
  // no verification method to check, use cached keystore controller
170
184
  return req.webkms.keystore.controller;
171
185
  }
@@ -180,7 +194,8 @@ async function getKeystoreController({req}) {
180
194
  id: req.webkms.keystore.id, req, returnRecord: true, fresh: true
181
195
  });
182
196
  const {config: keystore} = configRecord;
183
- req.webkms = {keystore, configRecord};
197
+ req.webkms = {...req.webkms, keystore, configRecord};
198
+ logger.debug('Forced fresh keystore config retrieval during zcap check.');
184
199
  }
185
200
 
186
201
  return req.webkms.keystore.controller;
@@ -237,10 +252,7 @@ function onError({error}) {
237
252
  if(!(error instanceof BedrockError)) {
238
253
  // always expose cause message and name; expose cause details as
239
254
  // BedrockError if error is marked public
240
- let details = {};
241
- if(error.details && error.details.public) {
242
- details = error.details;
243
- }
255
+ const details = error.details?.public ? error.details : {};
244
256
  error = new BedrockError(
245
257
  error.message,
246
258
  error.name || 'NotAllowedError', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/kms-http",
3
- "version": "14.2.1",
3
+ "version": "14.4.0",
4
4
  "type": "module",
5
5
  "description": "Bedrock KMS HTTP API",
6
6
  "main": "./lib/index.js",
@@ -27,7 +27,7 @@
27
27
  "@digitalbazaar/ed25519-signature-2020": "^3.0.0",
28
28
  "@digitalbazaar/ed25519-verification-key-2020": "^3.2.0",
29
29
  "@digitalbazaar/ezcap-express": "^6.2.0",
30
- "@digitalbazaar/webkms-switch": "^10.0.0",
30
+ "@digitalbazaar/webkms-switch": "^10.2.0",
31
31
  "cidr-regex": "^3.1.1",
32
32
  "cors": "^2.8.5",
33
33
  "crypto-ld": "^6.0.0",
@@ -67,7 +67,7 @@ describe('keystore API interactions using webkms-client', () => {
67
67
  bobKeystoreAgent.keystoreId);
68
68
  });
69
69
 
70
- it('updates config controller twice proving cache busting', async () => {
70
+ it('updates config and uses key proving cache busting', async () => {
71
71
  const keystoreId = aliceKeystoreConfig.id;
72
72
  const kmsClient = new KmsClient({httpsAgent});
73
73
 
@@ -88,6 +88,9 @@ describe('keystore API interactions using webkms-client', () => {
88
88
  const agent3KeystoreAgent = new KeystoreAgent(
89
89
  {capabilityAgent: agent3, keystoreId, kmsClient});
90
90
 
91
+ // generate a key for use
92
+ const hmacKey = await aliceKeystoreAgent.generateKey({type: 'hmac'});
93
+
91
94
  // delegate zcap for updating config from agent2 to agent3 to test
92
95
  // cache busting with delegated zcap
93
96
  const rootCapability = ZCAP_ROOT_PREFIX + encodeURIComponent(keystoreId);
@@ -120,7 +123,7 @@ describe('keystore API interactions using webkms-client', () => {
120
123
  // disable cache delete functionality on update
121
124
  keystores._disableClearCacheOnUpdate(true);
122
125
 
123
- // update controller alice => agent1 (no cache busting required
126
+ // update controller alice => agent1 (no cache busting required)
124
127
  {
125
128
  config.sequence++;
126
129
  config.controller = agent1.id;
@@ -164,6 +167,72 @@ describe('keystore API interactions using webkms-client', () => {
164
167
  result.config.controller.should.eql(agent1.id);
165
168
  ({config} = result);
166
169
  }
170
+
171
+ // update controller back to alice to test key op cache busting
172
+ {
173
+ config.sequence++;
174
+ config.controller = aliceCapabilityAgent.id;
175
+ const result = await agent1KeystoreAgent.updateConfig({config});
176
+ result.config.id.should.eql(config.id);
177
+ result.config.controller.should.eql(aliceCapabilityAgent.id);
178
+ ({config} = result);
179
+ }
180
+
181
+ // use hmac key as alice (requires cache busting by inspecting root
182
+ // zcap)
183
+ await hmacKey.sign({data: new Uint8Array([0])});
184
+
185
+ // update controller alice => agent1 (no cache busting required)
186
+ {
187
+ config.sequence++;
188
+ config.controller = agent1.id;
189
+ const result = await aliceKeystoreAgent.updateConfig({config});
190
+ result.config.id.should.eql(config.id);
191
+ result.config.controller.should.eql(agent1.id);
192
+ ({config} = result);
193
+ }
194
+
195
+ // use hmac key as agent1 (requires cache busting by
196
+ // inspecting the root zcap)
197
+ hmacKey.invocationSigner = agent1.getSigner();
198
+ // use different data each time to avoid hmac caching
199
+ await hmacKey.sign({data: new Uint8Array([1])});
200
+
201
+ // update controller agent1 => agent2 (requires cache busting by
202
+ // inspecting the root zcap)
203
+ {
204
+ config.sequence++;
205
+ config.controller = agent2.id;
206
+ const result = await agent1KeystoreAgent.updateConfig({config});
207
+ result.config.id.should.eql(config.id);
208
+ result.config.controller.should.eql(agent2.id);
209
+ ({config} = result);
210
+ }
211
+
212
+ // use hmac key as agent3 (requires cache busting by
213
+ // inspecting a delegated zcap)
214
+ hmacKey.capability = agent3Zcap;
215
+ hmacKey.invocationSigner = agent3.getSigner();
216
+ // use different data each time to avoid hmac caching
217
+ await hmacKey.sign({data: new Uint8Array([1])});
218
+
219
+ // update controller agent2 => agent3 (cache should already be fresh)
220
+ {
221
+ config.sequence++;
222
+ config.controller = agent3.id;
223
+ const result = await agent3KeystoreAgent.updateConfig(
224
+ {capability: agent3Zcap, config});
225
+ result.config.id.should.eql(config.id);
226
+ result.config.controller.should.eql(agent3.id);
227
+ ({config} = result);
228
+ }
229
+
230
+ // use hmac key as agent1 (requires cache busting by
231
+ // inspecting a deep delegated zcap)
232
+ hmacKey.capability = agent1Zcap;
233
+ hmacKey.invocationSigner = agent1.getSigner();
234
+ // use different data each time to avoid hmac caching
235
+ await hmacKey.sign({data: new Uint8Array([1])});
167
236
  } catch(e) {
168
237
  assertNoError(e);
169
238
  } finally {