@bedrock/kms-http 14.2.0 → 14.2.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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # bedrock-kms-http ChangeLog
2
2
 
3
+ ## 14.2.1 - 2022-05-13
4
+
5
+ ### Fixed
6
+ - Fix cache busting for invoked delegated zcap cases.
7
+
3
8
  ## 14.2.0 - 2022-05-13
4
9
 
5
10
  ### Changed
package/lib/middleware.js CHANGED
@@ -120,12 +120,15 @@ async function getKeystoreController({req}) {
120
120
  is retrieved.
121
121
 
122
122
  Algorithm: If the keystore config is less than five minutes old, then inspect
123
- `req.ezcap.invocationParameters.capability` to ensure that either:
123
+ `req.ezcap.invocationParameters.capability` or `req.ezcap.capabilityToRevoke`
124
+ to ensure that:
124
125
 
125
- 1. The `capability` is the root zcap and the verification method ID or
126
- controller match the keystore controller.
127
- 2. The `capability` is delegated and the controller of the first delegated
128
- zcap in the chain is the keystore controller.
126
+ 1. The `capability` is the root zcap and the ID or the controller of the
127
+ invoking verification method matches the keystore controller (note: can't
128
+ revoke a root zcap so this is an invoke-only case).
129
+ 2. The `capability` (being invoked or revoked) is delegated and the
130
+ ID or controller of the delegating verification method for the first
131
+ delegated zcap in the chain matches the keystore controller.
129
132
 
130
133
  If neither condition is true, fetch a fresh version of the keystore config
131
134
  record and update `req.webkms`. */
@@ -144,26 +147,35 @@ async function getKeystoreController({req}) {
144
147
  const capability = req.ezcap?.invocationParameters?.capability ||
145
148
  req.ezcap.capabilityToRevoke;
146
149
 
147
- let getFreshConfigRecord = false;
148
-
149
150
  // capability is root if it is a string; and it must be invoked because you
150
151
  // can't revoke a root zcap
151
152
  const isRoot = typeof capability === 'string';
153
+ let keyId;
152
154
  if(isRoot && req.ezcap.invocationParameters) {
153
- // fetch verification method early to compare against keystore controller
154
- const {signature: {params: {keyId}}} = req.ezcap;
155
- const {verificationMethod} = await getVerifier({keyId, documentLoader});
156
- getFreshConfigRecord =
157
- !(verificationMethod.controller === req.webkms.keystore.controller ||
158
- verificationMethod.id === req.webkms.keystore.controller);
155
+ // get verification method (key) ID from invocation params
156
+ ({signature: {params: {keyId}}} = req.ezcap);
159
157
  } else {
160
- const second = capability?.proof?.capabilityChain[1];
161
- if(second && second.controller !== req.webkms.keystore.controller) {
162
- getFreshConfigRecord = true;
158
+ // get verification method (key) ID from first delegation proof
159
+ if(capability?.proof?.capabilityChain.length === 1) {
160
+ // capability *is* the delegated zcap
161
+ keyId = capability.proof?.verificationMethod;
162
+ } else {
163
+ // capability is delegated further down the chain
164
+ keyId = capability?.proof?.capabilityChain[1]?.proof?.verificationMethod;
163
165
  }
164
166
  }
165
167
 
166
- if(getFreshConfigRecord) {
168
+ if(!keyId) {
169
+ // no verification method to check, use cached keystore controller
170
+ return req.webkms.keystore.controller;
171
+ }
172
+
173
+ // fetch verification method early to compare against keystore controller
174
+ const {verificationMethod} = await getVerifier({keyId, documentLoader});
175
+
176
+ // get fresh record if VM ID nor controller matches keystore controller
177
+ if(!(verificationMethod.controller === req.webkms.keystore.controller ||
178
+ verificationMethod.id === req.webkms.keystore.controller)) {
167
179
  const configRecord = await storage.get({
168
180
  id: req.webkms.keystore.id, req, returnRecord: true, fresh: true
169
181
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock/kms-http",
3
- "version": "14.2.0",
3
+ "version": "14.2.1",
4
4
  "type": "module",
5
5
  "description": "Bedrock KMS HTTP API",
6
6
  "main": "./lib/index.js",
@@ -38,7 +38,7 @@
38
38
  "@bedrock/app-identity": "^3.0.0",
39
39
  "@bedrock/core": "^6.0.0",
40
40
  "@bedrock/express": "^8.0.0",
41
- "@bedrock/kms": "^10.1.0",
41
+ "@bedrock/kms": "^10.2.0",
42
42
  "@bedrock/meter-usage-reporter": "^7.0.0",
43
43
  "@bedrock/validation": "^7.0.0",
44
44
  "@bedrock/zcap-storage": "^7.0.0"
@@ -3,11 +3,14 @@
3
3
  */
4
4
  import * as helpers from './helpers.js';
5
5
  import {httpsAgent} from '@bedrock/https-agent';
6
+ import {keystores} from '@bedrock/kms';
6
7
  import {createRequire} from 'node:module';
7
8
  const require = createRequire(import.meta.url);
8
9
  const {CapabilityAgent, KmsClient, KeystoreAgent} =
9
10
  require('@digitalbazaar/webkms-client');
10
11
 
12
+ const ZCAP_ROOT_PREFIX = 'urn:zcap:root:';
13
+
11
14
  describe('keystore API interactions using webkms-client', () => {
12
15
  let aliceCapabilityAgent;
13
16
  let aliceKeystoreConfig;
@@ -63,4 +66,108 @@ describe('keystore API interactions using webkms-client', () => {
63
66
  err.data.details.expected.should.equal(
64
67
  bobKeystoreAgent.keystoreId);
65
68
  });
69
+
70
+ it('updates config controller twice proving cache busting', async () => {
71
+ const keystoreId = aliceKeystoreConfig.id;
72
+ const kmsClient = new KmsClient({httpsAgent});
73
+
74
+ // create zcap agents to take over the keystore
75
+ const agent1 = await CapabilityAgent.fromSecret(
76
+ {secret: 'bd926f4c-38e4-4c3a-9a17-0e2608ee0a01', handle: 'primary'});
77
+ const agent2 = await CapabilityAgent.fromSecret(
78
+ {secret: 'd830baa9-02a6-4bd3-9072-de256b0b6c2d', handle: 'primary'});
79
+ const agent3 = await CapabilityAgent.fromSecret(
80
+ {secret: '37224754-647e-469a-9639-193a2833c69a', handle: 'primary'});
81
+
82
+ const aliceKeystoreAgent = new KeystoreAgent(
83
+ {capabilityAgent: aliceCapabilityAgent, keystoreId, kmsClient});
84
+ const agent1KeystoreAgent = new KeystoreAgent(
85
+ {capabilityAgent: agent1, keystoreId, kmsClient});
86
+ // const agent2KeystoreAgent = new KeystoreAgent(
87
+ // {capabilityAgent: agent2, keystoreId, kmsClient});
88
+ const agent3KeystoreAgent = new KeystoreAgent(
89
+ {capabilityAgent: agent3, keystoreId, kmsClient});
90
+
91
+ // delegate zcap for updating config from agent2 to agent3 to test
92
+ // cache busting with delegated zcap
93
+ const rootCapability = ZCAP_ROOT_PREFIX + encodeURIComponent(keystoreId);
94
+ const agent3Zcap = await helpers.delegate({
95
+ parentCapability: rootCapability,
96
+ controller: agent3.id,
97
+ invocationTarget: keystoreId,
98
+ delegator: agent2
99
+ });
100
+
101
+ // delegate zcap for updating config from agent3 to agent2 to agent1 to
102
+ // test cache busting with deep delegated zcap
103
+ const agent2Zcap = await helpers.delegate({
104
+ parentCapability: rootCapability,
105
+ controller: agent2.id,
106
+ invocationTarget: keystoreId,
107
+ delegator: agent3
108
+ });
109
+ const agent1Zcap = await helpers.delegate({
110
+ parentCapability: agent2Zcap,
111
+ controller: agent1.id,
112
+ invocationTarget: keystoreId,
113
+ delegator: agent2
114
+ });
115
+
116
+ // update Alice's keystore config controller to agent1
117
+ let config = {...aliceKeystoreConfig};
118
+
119
+ try {
120
+ // disable cache delete functionality on update
121
+ keystores._disableClearCacheOnUpdate(true);
122
+
123
+ // update controller alice => agent1 (no cache busting required
124
+ {
125
+ config.sequence++;
126
+ config.controller = agent1.id;
127
+ const result = await aliceKeystoreAgent.updateConfig({config});
128
+ result.config.id.should.eql(config.id);
129
+ result.config.controller.should.eql(agent1.id);
130
+ ({config} = result);
131
+ }
132
+
133
+ // update controller agent1 => agent2 (requires cache busting by
134
+ // inspecting the root zcap)
135
+ {
136
+ config.sequence++;
137
+ config.controller = agent2.id;
138
+ const result = await agent1KeystoreAgent.updateConfig({config});
139
+ result.config.id.should.eql(config.id);
140
+ result.config.controller.should.eql(agent2.id);
141
+ ({config} = result);
142
+ }
143
+
144
+ // update controller agent2 => agent3 (requires cache busting by
145
+ // inspecting a delegated zcap)
146
+ {
147
+ config.sequence++;
148
+ config.controller = agent3.id;
149
+ const result = await agent3KeystoreAgent.updateConfig(
150
+ {capability: agent3Zcap, config});
151
+ result.config.id.should.eql(config.id);
152
+ result.config.controller.should.eql(agent3.id);
153
+ ({config} = result);
154
+ }
155
+
156
+ // update controller agent3 => agent1 (requires cache busting by
157
+ // inspecting a deep delegated zcap)
158
+ {
159
+ config.sequence++;
160
+ config.controller = agent1.id;
161
+ const result = await agent1KeystoreAgent.updateConfig(
162
+ {capability: agent1Zcap, config});
163
+ result.config.id.should.eql(config.id);
164
+ result.config.controller.should.eql(agent1.id);
165
+ ({config} = result);
166
+ }
167
+ } catch(e) {
168
+ assertNoError(e);
169
+ } finally {
170
+ keystores._disableClearCacheOnUpdate(false);
171
+ }
172
+ });
66
173
  });