@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 +5 -0
- package/lib/middleware.js +29 -17
- package/package.json +2 -2
- package/test/mocha/11-keystore-kms-client.js +107 -0
package/CHANGELOG.md
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`
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
//
|
|
154
|
-
|
|
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
|
-
|
|
161
|
-
if(
|
|
162
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
});
|