@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as helpers from './helpers.js';
|
|
5
|
+
import {httpsAgent} from '@bedrock/https-agent';
|
|
6
|
+
import {createRequire} from 'module';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const {CapabilityAgent, KmsClient, KeystoreAgent} =
|
|
9
|
+
require('@digitalbazaar/webkms-client');
|
|
10
|
+
|
|
11
|
+
const ZCAP_ROOT_PREFIX = 'urn:zcap:root:';
|
|
12
|
+
|
|
13
|
+
describe('revocations API with ipAllowList', () => {
|
|
14
|
+
let aliceCapabilityAgent;
|
|
15
|
+
let aliceKeystoreConfig;
|
|
16
|
+
let aliceKeystoreAgent;
|
|
17
|
+
let bobCapabilityAgent;
|
|
18
|
+
let carolCapabilityAgent;
|
|
19
|
+
|
|
20
|
+
before(async () => {
|
|
21
|
+
const secret = '40762a17-1696-428f-a2b2-ddf9fe9b4987';
|
|
22
|
+
const handle = 'alice';
|
|
23
|
+
aliceCapabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
|
|
24
|
+
|
|
25
|
+
aliceKeystoreConfig = await helpers.createKeystore(
|
|
26
|
+
{capabilityAgent: aliceCapabilityAgent});
|
|
27
|
+
const kmsClient = new KmsClient({httpsAgent});
|
|
28
|
+
aliceKeystoreAgent = new KeystoreAgent({
|
|
29
|
+
capabilityAgent: aliceCapabilityAgent,
|
|
30
|
+
keystoreId: aliceKeystoreConfig.id,
|
|
31
|
+
kmsClient,
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// generate a capability agent for Bob
|
|
36
|
+
before(async () => {
|
|
37
|
+
const secret = '34f2afd1-34ef-4d46-a998-cdc5462dc0d2';
|
|
38
|
+
const handle = 'bob';
|
|
39
|
+
bobCapabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// generate a capability agent for Carol
|
|
43
|
+
before(async () => {
|
|
44
|
+
const secret = 'ae806cd9-2765-4232-b955-01e1024ac032';
|
|
45
|
+
const handle = 'carol';
|
|
46
|
+
carolCapabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns NotAllowedError for invalid source IP', async () => {
|
|
50
|
+
// first generate a new key for alice; use a did:key ID for its public ID
|
|
51
|
+
const aliceKey = await aliceKeystoreAgent.generateKey({
|
|
52
|
+
type: 'asymmetric',
|
|
53
|
+
publicAliasTemplate: 'did:key:{publicKeyMultibase}#${publicKeyMultibase}'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// next, delegate authority to bob to use alice's key
|
|
57
|
+
const rootCapability = ZCAP_ROOT_PREFIX +
|
|
58
|
+
encodeURIComponent(aliceKeystoreAgent.keystoreId);
|
|
59
|
+
const bobZcap = await helpers.delegate({
|
|
60
|
+
parentCapability: rootCapability,
|
|
61
|
+
controller: bobCapabilityAgent.id,
|
|
62
|
+
invocationTarget: aliceKey.kmsId,
|
|
63
|
+
allowedAction: 'sign',
|
|
64
|
+
delegator: aliceCapabilityAgent
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Bob now uses his delegated authority to sign a document with Alice's key
|
|
68
|
+
const bobSignedDocument = await helpers.signWithDelegatedKey({
|
|
69
|
+
capability: bobZcap,
|
|
70
|
+
// bob signs the invocation to use alice's key (and alice's key will
|
|
71
|
+
// sign the document)
|
|
72
|
+
invocationSigner: bobCapabilityAgent.getSigner()
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
bobSignedDocument.should.have.property('@context');
|
|
76
|
+
bobSignedDocument.should.have.property('example:foo');
|
|
77
|
+
bobSignedDocument.should.have.property('proof');
|
|
78
|
+
bobSignedDocument.proof.should.have.property('verificationMethod');
|
|
79
|
+
// the document was ultimately signed with alice's key
|
|
80
|
+
bobSignedDocument.proof.verificationMethod.should.equal(aliceKey.id);
|
|
81
|
+
|
|
82
|
+
// Bob has successfully used alice's key to sign a document!
|
|
83
|
+
|
|
84
|
+
// Bob now delegates the use of Alice's key to Carol
|
|
85
|
+
const carolZcap = await helpers.delegate({
|
|
86
|
+
parentCapability: bobZcap,
|
|
87
|
+
controller: carolCapabilityAgent.id,
|
|
88
|
+
delegator: bobCapabilityAgent
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Bob would then store record of the delegation to Carol in an EDV
|
|
92
|
+
|
|
93
|
+
// demonstrate that Carol can also sign with Alice's key
|
|
94
|
+
const carolSignedDocument = await helpers.signWithDelegatedKey({
|
|
95
|
+
capability: carolZcap,
|
|
96
|
+
// carol signs the invocation to use alice's key (and alice's key
|
|
97
|
+
// will sign the document)
|
|
98
|
+
invocationSigner: carolCapabilityAgent.getSigner()
|
|
99
|
+
});
|
|
100
|
+
carolSignedDocument.should.have.property('@context');
|
|
101
|
+
carolSignedDocument.should.have.property('example:foo');
|
|
102
|
+
carolSignedDocument.should.have.property('proof');
|
|
103
|
+
carolSignedDocument.proof.should.have.property('verificationMethod');
|
|
104
|
+
// the document was ultimately signed with alice's key
|
|
105
|
+
carolSignedDocument.proof.verificationMethod.should.equal(aliceKey.id);
|
|
106
|
+
|
|
107
|
+
// update Alice's keystore config to include ipAllowList
|
|
108
|
+
aliceKeystoreConfig.sequence++;
|
|
109
|
+
aliceKeystoreConfig.ipAllowList = ['8.8.8.8/32'];
|
|
110
|
+
|
|
111
|
+
const {config} = await aliceKeystoreAgent.updateConfig(
|
|
112
|
+
{config: aliceKeystoreConfig});
|
|
113
|
+
config.should.eql(aliceKeystoreConfig);
|
|
114
|
+
|
|
115
|
+
// Bob now submits a revocation to revoke the capability he gave to Carol.
|
|
116
|
+
|
|
117
|
+
// in practice bob is going to locate the capability he gave to carol
|
|
118
|
+
// by way of bedrock-web-zcap-storage
|
|
119
|
+
|
|
120
|
+
// this adds a revocation for Carol's `sign` capability on Alice's
|
|
121
|
+
// kms system
|
|
122
|
+
|
|
123
|
+
// this request should fails because the request does not originate from
|
|
124
|
+
// an IP in the `ipAllowList` on Alice's keystore.
|
|
125
|
+
let err;
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
result = await helpers.revokeDelegatedCapability({
|
|
129
|
+
// the `sign` capability that Bob gave to Carol
|
|
130
|
+
capabilityToRevoke: carolZcap,
|
|
131
|
+
// bob is revoking the capability he gave to carol
|
|
132
|
+
invocationSigner: bobCapabilityAgent.getSigner()
|
|
133
|
+
});
|
|
134
|
+
} catch(e) {
|
|
135
|
+
err = e;
|
|
136
|
+
}
|
|
137
|
+
should.not.exist(result);
|
|
138
|
+
should.exist(err);
|
|
139
|
+
err.status.should.equal(403);
|
|
140
|
+
err.data.type.should.equal('NotAllowedError');
|
|
141
|
+
err.data.message.should.contain('Source IP');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as helpers from './helpers.js';
|
|
5
|
+
import {httpsAgent} from '@bedrock/https-agent';
|
|
6
|
+
import {createRequire} from 'module';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const {CapabilityAgent, KmsClient, KeystoreAgent} =
|
|
9
|
+
require('@digitalbazaar/webkms-client');
|
|
10
|
+
const {httpClient, DEFAULT_HEADERS} = require('@digitalbazaar/http-client');
|
|
11
|
+
const {signCapabilityInvocation} = require(
|
|
12
|
+
'@digitalbazaar/http-signature-zcap-invoke');
|
|
13
|
+
|
|
14
|
+
describe('generateKey', () => {
|
|
15
|
+
it('generates a key', async () => {
|
|
16
|
+
const secret = '0628a44e-7599-11ec-989d-10bf48838a41';
|
|
17
|
+
const handle = 'testKey';
|
|
18
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
19
|
+
{handle, secret});
|
|
20
|
+
let err;
|
|
21
|
+
let result;
|
|
22
|
+
try {
|
|
23
|
+
result = await keystoreAgent.generateKey({type: 'hmac'});
|
|
24
|
+
} catch(e) {
|
|
25
|
+
err = e;
|
|
26
|
+
}
|
|
27
|
+
assertNoError(err);
|
|
28
|
+
should.exist(result);
|
|
29
|
+
result.should.include.keys([
|
|
30
|
+
'algorithm', 'capability', 'id', 'type', 'invocationSigner', 'kmsClient'
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
it('generates a key with ipAllowList', async () => {
|
|
34
|
+
// source of requests in the test suite are from 127.0.0.1
|
|
35
|
+
const secret = '22612679-05ce-4ffd-bf58-22b3c4bc1314';
|
|
36
|
+
const handle = 'testKeyAllowList';
|
|
37
|
+
const ipAllowList = ['127.0.0.1/32'];
|
|
38
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
39
|
+
{handle, ipAllowList, secret});
|
|
40
|
+
let err;
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await keystoreAgent.generateKey({type: 'hmac'});
|
|
44
|
+
} catch(e) {
|
|
45
|
+
err = e;
|
|
46
|
+
}
|
|
47
|
+
assertNoError(err);
|
|
48
|
+
should.exist(result);
|
|
49
|
+
result.should.include.keys([
|
|
50
|
+
'algorithm', 'capability', 'id', 'type', 'invocationSigner', 'kmsClient'
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
it('generates a key with x-forwarded-for header', async () => {
|
|
54
|
+
// source of requests in the test suite are from 127.0.0.1
|
|
55
|
+
const secret = '22612679-05ce-4ffd-bf58-22b3c4bc1314';
|
|
56
|
+
const handle = 'testKeyAllowList';
|
|
57
|
+
const ipAllowList = ['8.8.8.8/32'];
|
|
58
|
+
const keystoreAgent = await helpers.createKeystoreAgent({
|
|
59
|
+
handle, ipAllowList, secret, kmsClientHeaders: {
|
|
60
|
+
'x-forwarded-for': '8.8.8.8',
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
let err;
|
|
64
|
+
let result;
|
|
65
|
+
try {
|
|
66
|
+
result = await keystoreAgent.generateKey({type: 'hmac'});
|
|
67
|
+
} catch(e) {
|
|
68
|
+
err = e;
|
|
69
|
+
}
|
|
70
|
+
assertNoError(err);
|
|
71
|
+
should.exist(result);
|
|
72
|
+
result.should.include.keys([
|
|
73
|
+
'algorithm', 'capability', 'id', 'type', 'invocationSigner', 'kmsClient'
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
it('generates a key with multiple ipAllowList entries', async () => {
|
|
77
|
+
// source of requests in the test suite are from 127.0.0.1
|
|
78
|
+
const secret = 'efef2772-f7aa-4d25-9eac-6228f2a64b3b';
|
|
79
|
+
const handle = 'testKeyAllowList';
|
|
80
|
+
const ipAllowList = ['8.8.8.8/32', '127.0.0.1/32'];
|
|
81
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
82
|
+
{handle, ipAllowList, secret});
|
|
83
|
+
let err;
|
|
84
|
+
let result;
|
|
85
|
+
try {
|
|
86
|
+
result = await keystoreAgent.generateKey({type: 'hmac'});
|
|
87
|
+
} catch(e) {
|
|
88
|
+
err = e;
|
|
89
|
+
}
|
|
90
|
+
assertNoError(err);
|
|
91
|
+
should.exist(result);
|
|
92
|
+
result.should.include.keys([
|
|
93
|
+
'algorithm', 'capability', 'id', 'type', 'invocationSigner', 'kmsClient'
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
it('returns NotAllowedError for invalid source IP', async () => {
|
|
97
|
+
// source of requests in the test suite are from 127.0.0.1
|
|
98
|
+
const secret = '860a6bc8-74e1-4dfd-b701-7efc2a596e91';
|
|
99
|
+
const handle = 'testKeyAllowList';
|
|
100
|
+
const ipAllowList = ['8.8.8.8/32'];
|
|
101
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
102
|
+
{handle, ipAllowList, secret});
|
|
103
|
+
|
|
104
|
+
let err;
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = await keystoreAgent.generateKey({type: 'hmac'});
|
|
108
|
+
} catch(e) {
|
|
109
|
+
err = e;
|
|
110
|
+
}
|
|
111
|
+
should.exist(err);
|
|
112
|
+
should.not.exist(result);
|
|
113
|
+
err.status.should.equal(403);
|
|
114
|
+
err.data.type.should.equal('NotAllowedError');
|
|
115
|
+
});
|
|
116
|
+
it('generates a key with "maxCapabilityChainLength=1"', async () => {
|
|
117
|
+
const secret = '0ad59134-7583-11ec-b16e-10bf48838a41';
|
|
118
|
+
const handle = 'testKeyMaxCapabilityChainLength';
|
|
119
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
120
|
+
{handle, secret});
|
|
121
|
+
let err;
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = await keystoreAgent.generateKey({
|
|
125
|
+
type: 'asymmetric', maxCapabilityChainLength: 1
|
|
126
|
+
});
|
|
127
|
+
} catch(e) {
|
|
128
|
+
err = e;
|
|
129
|
+
}
|
|
130
|
+
assertNoError(err);
|
|
131
|
+
should.exist(result);
|
|
132
|
+
result.should.include.keys([
|
|
133
|
+
'capability', 'id', 'type', 'invocationSigner', 'kmsClient', 'kmsId'
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
}); // generateKey with ipAllowList
|
|
137
|
+
|
|
138
|
+
describe('get public key description', () => {
|
|
139
|
+
it('gets public key description', async () => {
|
|
140
|
+
const secret = '34f2afd1-34ef-4d46-a998-cdc5462dc0d2';
|
|
141
|
+
const handle = 'bobKey';
|
|
142
|
+
const capabilityAgent = await CapabilityAgent.fromSecret(
|
|
143
|
+
{secret, handle});
|
|
144
|
+
const {id: keystoreId} = await helpers.createKeystore(
|
|
145
|
+
{capabilityAgent});
|
|
146
|
+
const kmsClient = new KmsClient({keystoreId, httpsAgent});
|
|
147
|
+
let keystoreAgent;
|
|
148
|
+
try {
|
|
149
|
+
keystoreAgent = new KeystoreAgent(
|
|
150
|
+
{capabilityAgent, keystoreId, kmsClient});
|
|
151
|
+
} catch(e) {
|
|
152
|
+
assertNoError(e);
|
|
153
|
+
}
|
|
154
|
+
const key = await keystoreAgent.generateKey({type: 'asymmetric'});
|
|
155
|
+
|
|
156
|
+
const url = key.id;
|
|
157
|
+
const headers = await signCapabilityInvocation({
|
|
158
|
+
url, method: 'get',
|
|
159
|
+
headers: DEFAULT_HEADERS,
|
|
160
|
+
capability: 'urn:zcap:root:' + encodeURIComponent(url),
|
|
161
|
+
invocationSigner: capabilityAgent.getSigner(),
|
|
162
|
+
capabilityAction: 'read'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let err;
|
|
166
|
+
let result;
|
|
167
|
+
try {
|
|
168
|
+
result = await httpClient.get(url, {agent: httpsAgent, headers});
|
|
169
|
+
} catch(e) {
|
|
170
|
+
err = e;
|
|
171
|
+
}
|
|
172
|
+
assertNoError(err);
|
|
173
|
+
should.exist(result);
|
|
174
|
+
result.data.should.include.keys([
|
|
175
|
+
'@context', 'id', 'type', 'publicKeyMultibase', 'controller'
|
|
176
|
+
]);
|
|
177
|
+
result.data.id.should.equal(key.id);
|
|
178
|
+
result.data.type.should.equal(key.type);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
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 {httpsAgent} from '@bedrock/https-agent';
|
|
7
|
+
import {createRequire} from 'module';
|
|
8
|
+
import pMap from 'p-map';
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const {CapabilityAgent, Hmac, KmsClient} = require(
|
|
11
|
+
'@digitalbazaar/webkms-client');
|
|
12
|
+
|
|
13
|
+
const {util: {uuid}} = bedrock;
|
|
14
|
+
|
|
15
|
+
const ZCAP_ROOT_PREFIX = 'urn:zcap:root:';
|
|
16
|
+
|
|
17
|
+
describe('bedrock-kms-http HMAC operations', () => {
|
|
18
|
+
describe('Sha256HmacKey2019', () => {
|
|
19
|
+
let hmac;
|
|
20
|
+
before(async () => {
|
|
21
|
+
const secret = 'b07e6b31-d910-438e-9a5f-08d945a5f676';
|
|
22
|
+
const handle = 'testKey1';
|
|
23
|
+
const keystoreAgent = await helpers.createKeystoreAgent({handle, secret});
|
|
24
|
+
hmac = await keystoreAgent.generateKey({type: 'hmac'});
|
|
25
|
+
});
|
|
26
|
+
it('successfully signs', async () => {
|
|
27
|
+
const data = new TextEncoder('utf-8').encode('hello');
|
|
28
|
+
let err;
|
|
29
|
+
let result;
|
|
30
|
+
try {
|
|
31
|
+
result = await hmac.sign({data});
|
|
32
|
+
} catch(e) {
|
|
33
|
+
err = e;
|
|
34
|
+
}
|
|
35
|
+
assertNoError(err);
|
|
36
|
+
should.exist(result);
|
|
37
|
+
result.should.be.a('Uint8Array');
|
|
38
|
+
});
|
|
39
|
+
}); // end Sha256HmacKey2019
|
|
40
|
+
|
|
41
|
+
describe('Sha256HmacKey2019 with ipAllowList', () => {
|
|
42
|
+
it('successfully signs', async () => {
|
|
43
|
+
const secret = '22612679-05ce-4ffd-bf58-22b3c4bc1314';
|
|
44
|
+
const handle = 'testKeyAllowList';
|
|
45
|
+
const ipAllowList = ['127.0.0.1/32'];
|
|
46
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
47
|
+
{handle, ipAllowList, secret});
|
|
48
|
+
const hmac = await keystoreAgent.generateKey({type: 'hmac'});
|
|
49
|
+
const data = new TextEncoder('utf-8').encode('hello');
|
|
50
|
+
let err;
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = await hmac.sign({data});
|
|
54
|
+
} catch(e) {
|
|
55
|
+
err = e;
|
|
56
|
+
}
|
|
57
|
+
assertNoError(err);
|
|
58
|
+
should.exist(result);
|
|
59
|
+
result.should.be.a('Uint8Array');
|
|
60
|
+
});
|
|
61
|
+
it('fails when "maxCapabilityChainLength" is exceeded', async () => {
|
|
62
|
+
const secret = '22612679-05ce-4ffd-bf58-22b3c4bc1314';
|
|
63
|
+
const handle = 'testKeyMaxCapabilityChainLength';
|
|
64
|
+
const keystoreAgent = await helpers.createKeystoreAgent(
|
|
65
|
+
{handle, secret});
|
|
66
|
+
const hmac = await keystoreAgent.generateKey({
|
|
67
|
+
type: 'hmac', maxCapabilityChainLength: 1
|
|
68
|
+
});
|
|
69
|
+
const data = new TextEncoder('utf-8').encode('hello');
|
|
70
|
+
|
|
71
|
+
let err;
|
|
72
|
+
let result;
|
|
73
|
+
try {
|
|
74
|
+
result = await hmac.sign({data});
|
|
75
|
+
} catch(e) {
|
|
76
|
+
err = e;
|
|
77
|
+
}
|
|
78
|
+
assertNoError(err);
|
|
79
|
+
should.exist(result);
|
|
80
|
+
result.should.be.a('Uint8Array');
|
|
81
|
+
|
|
82
|
+
// now delegate and try to invoke
|
|
83
|
+
const delegatee = await CapabilityAgent.fromSecret({
|
|
84
|
+
secret: uuid(),
|
|
85
|
+
handle: uuid()
|
|
86
|
+
});
|
|
87
|
+
const rootCapability = ZCAP_ROOT_PREFIX +
|
|
88
|
+
encodeURIComponent(keystoreAgent.keystoreId);
|
|
89
|
+
const delegatedZcap = await helpers.delegate({
|
|
90
|
+
parentCapability: rootCapability,
|
|
91
|
+
controller: delegatee.id,
|
|
92
|
+
invocationTarget: hmac.id,
|
|
93
|
+
allowedAction: 'sign',
|
|
94
|
+
delegator: keystoreAgent.capabilityAgent
|
|
95
|
+
});
|
|
96
|
+
const hmac2 = await Hmac.fromCapability({
|
|
97
|
+
capability: delegatedZcap,
|
|
98
|
+
invocationSigner: delegatee.getSigner(),
|
|
99
|
+
kmsClient: new KmsClient({httpsAgent})
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let err2;
|
|
103
|
+
let result2;
|
|
104
|
+
try {
|
|
105
|
+
result2 = await hmac2.sign({data});
|
|
106
|
+
} catch(e) {
|
|
107
|
+
err2 = e;
|
|
108
|
+
}
|
|
109
|
+
should.exist(err2);
|
|
110
|
+
should.not.exist(result2);
|
|
111
|
+
should.exist(err2.data);
|
|
112
|
+
err2.data.type.should.equal('NotAllowedError');
|
|
113
|
+
should.exist(err2.data.cause);
|
|
114
|
+
err2.data.cause.message.should.equal(
|
|
115
|
+
'Maximum zcap invocation capability chain length (1) exceeded.');
|
|
116
|
+
});
|
|
117
|
+
it('successfully signs with x-forwarded-for header', async () => {
|
|
118
|
+
const secret = '2726f62d-31bb-4688-b54a-1a0b4e50329f';
|
|
119
|
+
const handle = 'testKeyAllowList';
|
|
120
|
+
const ipAllowList = ['8.8.8.8/32'];
|
|
121
|
+
const keystoreAgent = await helpers.createKeystoreAgent({
|
|
122
|
+
handle, ipAllowList, secret, kmsClientHeaders: {
|
|
123
|
+
'x-forwarded-for': '8.8.8.8',
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const hmac = await keystoreAgent.generateKey({type: 'hmac'});
|
|
127
|
+
const data = new TextEncoder('utf-8').encode('hello');
|
|
128
|
+
let err;
|
|
129
|
+
let result;
|
|
130
|
+
try {
|
|
131
|
+
result = await hmac.sign({data});
|
|
132
|
+
} catch(e) {
|
|
133
|
+
err = e;
|
|
134
|
+
}
|
|
135
|
+
assertNoError(err);
|
|
136
|
+
should.exist(result);
|
|
137
|
+
result.should.be.a('Uint8Array');
|
|
138
|
+
});
|
|
139
|
+
}); // end Sha256HmacKey2019 with ipAllowList
|
|
140
|
+
|
|
141
|
+
describe('bulk operations', () => {
|
|
142
|
+
const operationCount = 1000;
|
|
143
|
+
const vData = [];
|
|
144
|
+
let hmac;
|
|
145
|
+
|
|
146
|
+
before(async () => {
|
|
147
|
+
const secret = '9b5a0a63-aac2-447c-a60a-8cc79b46418d';
|
|
148
|
+
const handle = 'testKeyBulk';
|
|
149
|
+
const keystoreAgent = await helpers.createKeystoreAgent({handle, secret});
|
|
150
|
+
hmac = await keystoreAgent.generateKey({type: 'hmac'});
|
|
151
|
+
});
|
|
152
|
+
before(async () => {
|
|
153
|
+
for(let i = 0; i < operationCount; ++i) {
|
|
154
|
+
// uuids are 37 chars long, * 30 is ~1KiB
|
|
155
|
+
let v = '';
|
|
156
|
+
for(let n = 0; n < 30; ++n) {
|
|
157
|
+
v += uuid();
|
|
158
|
+
}
|
|
159
|
+
vData.push(new TextEncoder('utf-8').encode(v));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
it(`performs ${operationCount} signatures`, async function() {
|
|
163
|
+
this.timeout(0);
|
|
164
|
+
let result;
|
|
165
|
+
let err;
|
|
166
|
+
try {
|
|
167
|
+
result = await pMap(
|
|
168
|
+
vData, data => hmac.sign({data}), {concurrency: 30});
|
|
169
|
+
} catch(e) {
|
|
170
|
+
err = e;
|
|
171
|
+
}
|
|
172
|
+
assertNoError(err);
|
|
173
|
+
should.exist(result);
|
|
174
|
+
result.should.be.an('array');
|
|
175
|
+
result.should.have.length(operationCount);
|
|
176
|
+
});
|
|
177
|
+
}); // end bulk operations
|
|
178
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as bedrock from '@bedrock/core';
|
|
5
|
+
import {createRequire} from 'module';
|
|
6
|
+
import {documentLoader} from '@bedrock/jsonld-document-loader';
|
|
7
|
+
import {getAppIdentity} from '@bedrock/app-identity';
|
|
8
|
+
import {httpsAgent} from '@bedrock/https-agent';
|
|
9
|
+
import jsigs from 'jsonld-signatures';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const {AsymmetricKey, CapabilityAgent, KeystoreAgent, KmsClient} =
|
|
12
|
+
require('@digitalbazaar/webkms-client');
|
|
13
|
+
const {
|
|
14
|
+
CapabilityDelegation,
|
|
15
|
+
constants: zcapConstants
|
|
16
|
+
} = require('@digitalbazaar/zcap');
|
|
17
|
+
const {Ed25519Signature2020} = require('@digitalbazaar/ed25519-signature-2020');
|
|
18
|
+
const {ZcapClient} = require('@digitalbazaar/ezcap');
|
|
19
|
+
|
|
20
|
+
const {purposes: {AssertionProofPurpose}} = jsigs;
|
|
21
|
+
const {util: {uuid}} = bedrock;
|
|
22
|
+
const {ZCAP_CONTEXT_URL} = zcapConstants;
|
|
23
|
+
|
|
24
|
+
export async function createMeter({capabilityAgent} = {}) {
|
|
25
|
+
// create signer using the application's capability invocation key
|
|
26
|
+
const {keys: {capabilityInvocationKey}} = getAppIdentity();
|
|
27
|
+
|
|
28
|
+
const zcapClient = new ZcapClient({
|
|
29
|
+
agent: httpsAgent,
|
|
30
|
+
invocationSigner: capabilityInvocationKey.signer(),
|
|
31
|
+
SuiteClass: Ed25519Signature2020
|
|
32
|
+
});
|
|
33
|
+
// create a meter
|
|
34
|
+
const meterService = `${bedrock.config.server.baseUri}/meters`;
|
|
35
|
+
let meter = {
|
|
36
|
+
controller: capabilityAgent.id,
|
|
37
|
+
product: {
|
|
38
|
+
// mock ID for webkms service product
|
|
39
|
+
id: 'urn:uuid:80a82316-e8c2-11eb-9570-10bf48838a41'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
({data: {meter}} = await zcapClient.write({url: meterService, json: meter}));
|
|
43
|
+
|
|
44
|
+
// return full meter ID
|
|
45
|
+
const {id} = meter;
|
|
46
|
+
return {id: `${meterService}/${id}`};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function createKeystore({
|
|
50
|
+
capabilityAgent, ipAllowList, referenceId, meterId,
|
|
51
|
+
kmsBaseUrl = `${bedrock.config.server.baseUri}/kms`,
|
|
52
|
+
kmsModule = 'ssm-v1',
|
|
53
|
+
}) {
|
|
54
|
+
if(!meterId) {
|
|
55
|
+
// create a meter for the keystore
|
|
56
|
+
({id: meterId} = await createMeter({capabilityAgent}));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// create keystore
|
|
60
|
+
const config = {
|
|
61
|
+
sequence: 0,
|
|
62
|
+
controller: capabilityAgent.id,
|
|
63
|
+
meterId,
|
|
64
|
+
kmsModule
|
|
65
|
+
};
|
|
66
|
+
if(referenceId) {
|
|
67
|
+
config.referenceId = referenceId;
|
|
68
|
+
}
|
|
69
|
+
if(ipAllowList) {
|
|
70
|
+
config.ipAllowList = ipAllowList;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return KmsClient.createKeystore({
|
|
74
|
+
url: `${kmsBaseUrl}/keystores`,
|
|
75
|
+
config,
|
|
76
|
+
invocationSigner: capabilityAgent.getSigner(),
|
|
77
|
+
httpsAgent
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function createKeystoreAgent({
|
|
82
|
+
handle, ipAllowList, secret, kmsClientHeaders = {}
|
|
83
|
+
}) {
|
|
84
|
+
const capabilityAgent = await CapabilityAgent.fromSecret({secret, handle});
|
|
85
|
+
|
|
86
|
+
let err;
|
|
87
|
+
let keystore;
|
|
88
|
+
try {
|
|
89
|
+
keystore = await createKeystore({capabilityAgent, ipAllowList});
|
|
90
|
+
} catch(e) {
|
|
91
|
+
err = e;
|
|
92
|
+
}
|
|
93
|
+
assertNoError(err);
|
|
94
|
+
|
|
95
|
+
// create kmsClient only required because we need to use httpsAgent
|
|
96
|
+
// that accepts self-signed certs used in test suite
|
|
97
|
+
const kmsClient = new KmsClient(
|
|
98
|
+
{httpsAgent, defaultHeaders: kmsClientHeaders});
|
|
99
|
+
const keystoreAgent = new KeystoreAgent({
|
|
100
|
+
capabilityAgent,
|
|
101
|
+
keystoreId: keystore.id,
|
|
102
|
+
kmsClient
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return keystoreAgent;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function getKeystore({id, capabilityAgent}) {
|
|
109
|
+
const kmsClient = new KmsClient({keystoreId: id, httpsAgent});
|
|
110
|
+
const invocationSigner = capabilityAgent.getSigner();
|
|
111
|
+
return kmsClient.getKeystore({invocationSigner});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function delegate({
|
|
115
|
+
parentCapability, controller, invocationTarget, expires, allowedAction,
|
|
116
|
+
delegator, purposeOptions = {}
|
|
117
|
+
}) {
|
|
118
|
+
const newCapability = {
|
|
119
|
+
'@context': ZCAP_CONTEXT_URL,
|
|
120
|
+
id: `urn:zcap:${uuid()}`,
|
|
121
|
+
controller,
|
|
122
|
+
parentCapability: parentCapability.id || parentCapability,
|
|
123
|
+
invocationTarget: invocationTarget || parentCapability.invocationTarget,
|
|
124
|
+
expires: expires || parentCapability.expires ||
|
|
125
|
+
new Date(Date.now() + 5000).toISOString().slice(0, -5) + 'Z',
|
|
126
|
+
allowedAction: allowedAction || parentCapability.allowedAction
|
|
127
|
+
};
|
|
128
|
+
// attach capability delegation proof
|
|
129
|
+
return jsigs.sign(newCapability, {
|
|
130
|
+
documentLoader,
|
|
131
|
+
purpose: new CapabilityDelegation({parentCapability, ...purposeOptions}),
|
|
132
|
+
suite: new Ed25519Signature2020({signer: delegator.getSigner()}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function revokeDelegatedCapability({
|
|
137
|
+
capabilityToRevoke, invocationSigner
|
|
138
|
+
}) {
|
|
139
|
+
const kmsClient = new KmsClient({httpsAgent});
|
|
140
|
+
await kmsClient.revokeCapability({
|
|
141
|
+
capabilityToRevoke,
|
|
142
|
+
invocationSigner
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function signWithDelegatedKey({
|
|
147
|
+
capability, doc, invocationSigner
|
|
148
|
+
}) {
|
|
149
|
+
const delegatedSigningKey = await AsymmetricKey.fromCapability({
|
|
150
|
+
capability,
|
|
151
|
+
invocationSigner,
|
|
152
|
+
kmsClient: new KmsClient({httpsAgent})
|
|
153
|
+
});
|
|
154
|
+
const suite = new Ed25519Signature2020({signer: delegatedSigningKey});
|
|
155
|
+
|
|
156
|
+
doc = doc || {'example:foo': 'test'};
|
|
157
|
+
|
|
158
|
+
return jsigs.sign(doc, {
|
|
159
|
+
documentLoader,
|
|
160
|
+
purpose: new AssertionProofPurpose(),
|
|
161
|
+
suite
|
|
162
|
+
});
|
|
163
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {createRequire} from 'module';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const {constants: zcapConstants} = require('@digitalbazaar/zcap');
|
|
7
|
+
|
|
8
|
+
const {ZCAP_CONTEXT_URL} = zcapConstants;
|
|
9
|
+
|
|
10
|
+
export const mockData = {};
|
|
11
|
+
|
|
12
|
+
const zcaps = mockData.zcaps = {};
|
|
13
|
+
|
|
14
|
+
// Note: This zcap is only used to check JSON schema validators, it does not
|
|
15
|
+
// have valid proofs.
|
|
16
|
+
const zcap0 = {
|
|
17
|
+
'@context': ZCAP_CONTEXT_URL,
|
|
18
|
+
id: 'urn:zcap:z19vWhR8EsNbWqvazp5bg6BTu',
|
|
19
|
+
controller: 'did:key:z6Mkkt1BWYLPAAXwYBwyVHAZkL94tgT8QbQv2SUxeW1U3DaG',
|
|
20
|
+
expires: '2022-01-08T17:27:15Z',
|
|
21
|
+
invocationTarget: 'https://bedrock.localhost:18443/kms' +
|
|
22
|
+
'/keystores/z1AAWWM7Zd4YyyV3NfaCqFuzQ/keys/z19wxodgv1UhrToQMvSxGhQG6',
|
|
23
|
+
parentCapability: 'urn:zcap:root:' + encodeURIComponent(
|
|
24
|
+
'https://bedrock.localhost:18443/kms/keystores/z1AAWWM7Zd4YyyV3NfaCqFuzQ'),
|
|
25
|
+
allowedAction: 'sign',
|
|
26
|
+
proof: {
|
|
27
|
+
type: 'Ed25519Signature2020',
|
|
28
|
+
created: '2020-02-27T21:22:48Z',
|
|
29
|
+
verificationMethod: 'did:key:z6MkkrtV7wnBpXKBtiZjxaSghCo8ttb5kZUJTk8bEwT' +
|
|
30
|
+
'TTYvg#z6MkkrtV7wnBpXKBtiZjxaSghCo8ttb5kZUJTk8bEwTTTYvg',
|
|
31
|
+
proofPurpose: 'capabilityDelegation',
|
|
32
|
+
capabilityChain: [
|
|
33
|
+
'urn:zcap:root:' + encodeURIComponent(
|
|
34
|
+
'https://bedrock.localhost:18443/kms/keystores/z1AAWWM7Zd4YyyV3NfaCqFu')
|
|
35
|
+
],
|
|
36
|
+
proofValue: 'eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19'
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
zcaps.zero = zcap0;
|