@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/.eslintrc.cjs +12 -0
- package/.github/workflows/main.yml +103 -0
- package/CHANGELOG.md +332 -0
- package/LICENSE.md +115 -0
- package/README.md +292 -0
- package/lib/BedrockKeystoreConfigStorage.js +37 -0
- package/lib/config.js +37 -0
- package/lib/helpers.js +36 -0
- package/lib/http.js +263 -0
- package/lib/index.js +9 -0
- package/lib/logger.js +6 -0
- package/lib/metering.js +42 -0
- package/lib/middleware.js +181 -0
- package/package.json +58 -0
- package/schemas/bedrock-kms-http.js +176 -0
- package/test/mocha/.eslintrc +9 -0
- package/test/mocha/10-keystore.js +670 -0
- package/test/mocha/11-keystore-kms-client.js +66 -0
- package/test/mocha/20-revocation.js +545 -0
- package/test/mocha/21-revocation-ip-allow-list.js +143 -0
- package/test/mocha/30-operations-generate-key.js +180 -0
- package/test/mocha/31-operations-hmac.js +178 -0
- package/test/mocha/helpers.js +163 -0
- package/test/mocha/mock.data.js +40 -0
- package/test/package.json +64 -0
- package/test/test.config.js +36 -0
- package/test/test.js +31 -0
- package/test/web/.eslintrc +9 -0
- package/test/web/10-api.js +108 -0
- package/test/web/helpers.js +84 -0
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