@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,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bedrock-kms-http-test",
|
|
3
|
+
"version": "0.0.1-0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Bedrock KMS HTTP API test",
|
|
6
|
+
"private": true,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --preserve-symlinks test.js test",
|
|
9
|
+
"test:debug": "node --preserve-symlinks test.js test --log-level debug",
|
|
10
|
+
"test-node": "node --preserve-symlinks test.js test --framework mocha",
|
|
11
|
+
"test-karma": "node --preserve-symlinks test.js test --framework karma",
|
|
12
|
+
"coverage": "cross-env NODE_ENV=test c8 --reporter=lcov --reporter=text-summary node --preserve-symlinks test.js test --framework mocha",
|
|
13
|
+
"coverage-ci": "cross-env NODE_ENV=test c8 --reporter=lcovonly node --preserve-symlinks test.js test --framework mocha",
|
|
14
|
+
"coverage-report": "c8 report"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@bedrock/app-identity": "^2.0.0",
|
|
18
|
+
"@bedrock/core": "^5.0.0",
|
|
19
|
+
"@bedrock/did-context": "^3.0.0",
|
|
20
|
+
"@bedrock/did-io": "^7.0.0",
|
|
21
|
+
"@bedrock/express": "^7.0.0",
|
|
22
|
+
"@bedrock/https-agent": "^3.0.0",
|
|
23
|
+
"@bedrock/jsonld-document-loader": "^2.0.0",
|
|
24
|
+
"@bedrock/karma": "^3.0.0",
|
|
25
|
+
"@bedrock/kms": "^9.0.0",
|
|
26
|
+
"@bedrock/kms-http": "file:..",
|
|
27
|
+
"@bedrock/ledger-context": "^22.0.0",
|
|
28
|
+
"@bedrock/meter": "^2.0.0",
|
|
29
|
+
"@bedrock/meter-http": "^7.0.0",
|
|
30
|
+
"@bedrock/meter-usage-reporter": "^6.0.0",
|
|
31
|
+
"@bedrock/mongodb": "^9.0.0",
|
|
32
|
+
"@bedrock/package-manager": "^2.0.0",
|
|
33
|
+
"@bedrock/security-context": "^6.0.0",
|
|
34
|
+
"@bedrock/server": "^4.0.0",
|
|
35
|
+
"@bedrock/ssm-mongodb": "^8.0.0",
|
|
36
|
+
"@bedrock/test": "^7.0.0",
|
|
37
|
+
"@bedrock/validation": "^6.0.0",
|
|
38
|
+
"@bedrock/veres-one-context": "^13.0.0",
|
|
39
|
+
"@bedrock/zcap-storage": "^6.0.0",
|
|
40
|
+
"@digitalbazaar/did-method-key": "^2.0.0",
|
|
41
|
+
"@digitalbazaar/ed25519-signature-2020": "^3.0.0",
|
|
42
|
+
"@digitalbazaar/ezcap": "^2.0.0",
|
|
43
|
+
"@digitalbazaar/http-client": "^2.0.1",
|
|
44
|
+
"@digitalbazaar/http-signature-zcap-invoke": "^5.0.1",
|
|
45
|
+
"@digitalbazaar/webkms-client": "^10.0.0",
|
|
46
|
+
"@digitalbazaar/zcap": "^7.0.0",
|
|
47
|
+
"base58-universal": "^1.0.0",
|
|
48
|
+
"c8": "^7.11.0",
|
|
49
|
+
"cross-env": "^7.0.3",
|
|
50
|
+
"jsonld-signatures": "^9.3.0",
|
|
51
|
+
"p-map": "^4.0.0",
|
|
52
|
+
"sinon": "^9.0.2",
|
|
53
|
+
"uuid-random": "^1.3.2"
|
|
54
|
+
},
|
|
55
|
+
"c8": {
|
|
56
|
+
"excludeNodeModules": false,
|
|
57
|
+
"include": [
|
|
58
|
+
"node_modules/@bedrock/kms-http/**"
|
|
59
|
+
],
|
|
60
|
+
"exclude": [
|
|
61
|
+
"node_modules/@bedrock/kms-http/node_modules/**"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {config} from '@bedrock/core';
|
|
5
|
+
import {fileURLToPath} from 'url';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import '@bedrock/https-agent';
|
|
8
|
+
import '@bedrock/kms-http';
|
|
9
|
+
import '@bedrock/mongodb';
|
|
10
|
+
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
config.mocha.tests.push(path.join(__dirname, 'mocha'));
|
|
14
|
+
|
|
15
|
+
// MongoDB
|
|
16
|
+
config.mongodb.name = 'bedrock_kms_http_test';
|
|
17
|
+
config.mongodb.dropCollections.onInit = true;
|
|
18
|
+
config.mongodb.dropCollections.collections = [];
|
|
19
|
+
|
|
20
|
+
// do not require an authentication session for tests
|
|
21
|
+
config['kms-http'].requireAuthentication = false;
|
|
22
|
+
|
|
23
|
+
// allow self-signed certs in test framework
|
|
24
|
+
config['https-agent'].rejectUnauthorized = false;
|
|
25
|
+
|
|
26
|
+
// configure karma tests
|
|
27
|
+
config.karma.suites['bedrock-web-kms'] = path.join('web', '**', '*.js');
|
|
28
|
+
config.karma.config.proxies = {
|
|
29
|
+
'/': {
|
|
30
|
+
target: 'https://localhost:18443',
|
|
31
|
+
changeOrigin: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
config.karma.config.proxyValidateSSL = false;
|
|
35
|
+
config.karma.config.browserNoActivityTimeout = 120000;
|
|
36
|
+
config.karma.config.browserDisconnectTimeout = 120000;
|
package/test/test.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import * as bedrock from '@bedrock/core';
|
|
5
|
+
import '@bedrock/https-agent';
|
|
6
|
+
import '@bedrock/kms';
|
|
7
|
+
import '@bedrock/kms-http';
|
|
8
|
+
import '@bedrock/meter';
|
|
9
|
+
import {handlers} from '@bedrock/meter-http';
|
|
10
|
+
import '@bedrock/meter-usage-reporter';
|
|
11
|
+
import '@bedrock/security-context';
|
|
12
|
+
import '@bedrock/ssm-mongodb';
|
|
13
|
+
import '@bedrock/test';
|
|
14
|
+
import '@bedrock/karma';
|
|
15
|
+
|
|
16
|
+
bedrock.events.on('bedrock.init', async () => {
|
|
17
|
+
/* Handlers need to be added before `bedrock.start` is called. These are
|
|
18
|
+
no-op handlers to enable meter usage without restriction */
|
|
19
|
+
handlers.setCreateHandler({
|
|
20
|
+
handler({meter} = {}) {
|
|
21
|
+
// use configured meter usage reporter as service ID for tests
|
|
22
|
+
meter.serviceId = bedrock.config['app-identity'].seeds.services.webkms.id;
|
|
23
|
+
return {meter};
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
handlers.setUpdateHandler({handler: ({meter} = {}) => ({meter})});
|
|
27
|
+
handlers.setRemoveHandler({handler: ({meter} = {}) => ({meter})});
|
|
28
|
+
handlers.setUseHandler({handler: ({meter} = {}) => ({meter})});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
bedrock.start();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2019-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import pMap from 'p-map';
|
|
5
|
+
import uuid from 'uuid-random';
|
|
6
|
+
import {CapabilityAgent, KeystoreAgent, KmsClient}
|
|
7
|
+
from '@digitalbazaar/webkms-client';
|
|
8
|
+
import * as helpers from './helpers.js';
|
|
9
|
+
|
|
10
|
+
describe('bedrock-kms-http HMAC operations', () => {
|
|
11
|
+
describe('Sha256HmacKey2019', () => {
|
|
12
|
+
let hmac;
|
|
13
|
+
before(async () => {
|
|
14
|
+
const secret = 'b07e6b31-d910-438e-9a5f-08d945a5f676';
|
|
15
|
+
const handle = 'testKey1';
|
|
16
|
+
const capabilityAgent = await CapabilityAgent.fromSecret(
|
|
17
|
+
{secret, handle});
|
|
18
|
+
|
|
19
|
+
let err;
|
|
20
|
+
let keystore;
|
|
21
|
+
try {
|
|
22
|
+
keystore = await _createKeystore({capabilityAgent});
|
|
23
|
+
} catch(e) {
|
|
24
|
+
err = e;
|
|
25
|
+
}
|
|
26
|
+
should.not.exist(err);
|
|
27
|
+
|
|
28
|
+
// create kmsClient only required because we need to use httpsAgent
|
|
29
|
+
// that accepts self-signed certs used in test suite
|
|
30
|
+
const kmsClient = new KmsClient();
|
|
31
|
+
const keystoreAgent = new KeystoreAgent({
|
|
32
|
+
capabilityAgent,
|
|
33
|
+
keystoreId: keystore.id,
|
|
34
|
+
kmsClient
|
|
35
|
+
});
|
|
36
|
+
hmac = await keystoreAgent.generateKey({type: 'hmac'});
|
|
37
|
+
});
|
|
38
|
+
it('successfully signs', async () => {
|
|
39
|
+
const data = new TextEncoder('utf-8').encode('hello');
|
|
40
|
+
let err;
|
|
41
|
+
let result;
|
|
42
|
+
try {
|
|
43
|
+
result = await hmac.sign({data});
|
|
44
|
+
} catch(e) {
|
|
45
|
+
err = e;
|
|
46
|
+
}
|
|
47
|
+
should.not.exist(err);
|
|
48
|
+
should.exist(result);
|
|
49
|
+
result.should.be.a('Uint8Array');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('bulk operations', () => {
|
|
53
|
+
const operationCount = 1000;
|
|
54
|
+
const vData = [];
|
|
55
|
+
before(async () => {
|
|
56
|
+
for(let i = 0; i < operationCount; ++i) {
|
|
57
|
+
// uuids are 37 chars long, * 30 is ~1KiB
|
|
58
|
+
let v = '';
|
|
59
|
+
for(let n = 0; n < 30; ++n) {
|
|
60
|
+
v += uuid();
|
|
61
|
+
}
|
|
62
|
+
vData.push(new TextEncoder('utf-8').encode(v));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
it(`performs ${operationCount} signatures`, async function() {
|
|
66
|
+
this.timeout(0);
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
let result;
|
|
69
|
+
let err;
|
|
70
|
+
try {
|
|
71
|
+
result = await pMap(
|
|
72
|
+
vData, data => hmac.sign({data}), {concurrency: 30});
|
|
73
|
+
} catch(e) {
|
|
74
|
+
err = e;
|
|
75
|
+
}
|
|
76
|
+
should.not.exist(err);
|
|
77
|
+
should.exist(result);
|
|
78
|
+
result.should.be.an('array');
|
|
79
|
+
result.should.have.length(operationCount);
|
|
80
|
+
const elapsedTime = Date.now() - startTime;
|
|
81
|
+
// NOTE: reporter in karma does not report elapsed time for individual
|
|
82
|
+
// tests, this logging is intentional
|
|
83
|
+
console.log('ELAPSED TIME', elapsedTime);
|
|
84
|
+
});
|
|
85
|
+
}); // end bulk operations
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
async function _createKeystore({capabilityAgent, referenceId}) {
|
|
90
|
+
const {id: meterId} = await helpers.createMeter({capabilityAgent});
|
|
91
|
+
|
|
92
|
+
// create keystore
|
|
93
|
+
const config = {
|
|
94
|
+
sequence: 0,
|
|
95
|
+
controller: capabilityAgent.id,
|
|
96
|
+
kmsModule: 'ssm-v1',
|
|
97
|
+
meterId
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if(referenceId) {
|
|
101
|
+
config.referenceId = referenceId;
|
|
102
|
+
}
|
|
103
|
+
return KmsClient.createKeystore({
|
|
104
|
+
url: 'https://localhost:18443/kms/keystores',
|
|
105
|
+
config,
|
|
106
|
+
invocationSigner: capabilityAgent.getSigner()
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2021-2022 Digital Bazaar, Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
import {decode} from 'base58-universal';
|
|
5
|
+
import {driver} from '@digitalbazaar/did-method-key';
|
|
6
|
+
import {Ed25519Signature2020} from '@digitalbazaar/ed25519-signature-2020';
|
|
7
|
+
import {ZcapClient} from '@digitalbazaar/ezcap';
|
|
8
|
+
|
|
9
|
+
// found in bedrock-app-identity
|
|
10
|
+
const DEFAULT_APPLICATION_ID_SEED =
|
|
11
|
+
'z1AmMXgweztXscpTpxx19jsCLkPXUacTTBme2oxWGvuto9S';
|
|
12
|
+
|
|
13
|
+
// multibase base58-btc header
|
|
14
|
+
const MULTIBASE_BASE58BTC_HEADER = 'z';
|
|
15
|
+
// multihash identity function cdoe
|
|
16
|
+
const MULTIHASH_IDENTITY_FUNCTION_CODE = 0x00;
|
|
17
|
+
// seed byte size
|
|
18
|
+
const SEED_BYTE_SIZE = 32;
|
|
19
|
+
|
|
20
|
+
export async function createMeter({capabilityAgent} = {}) {
|
|
21
|
+
const invocationSigner = await getInvocationSigner();
|
|
22
|
+
const zcapClient = new ZcapClient({
|
|
23
|
+
invocationSigner,
|
|
24
|
+
SuiteClass: Ed25519Signature2020
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// create a meter
|
|
28
|
+
const meterService = 'https://localhost:18443/meters';
|
|
29
|
+
let meter = {
|
|
30
|
+
controller: capabilityAgent.id,
|
|
31
|
+
product: {
|
|
32
|
+
// mock ID for webkms service product
|
|
33
|
+
id: 'urn:uuid:80a82316-e8c2-11eb-9570-10bf48838a41'
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
({data: {meter}} = await zcapClient.write({url: meterService, json: meter}));
|
|
37
|
+
|
|
38
|
+
// return full meter ID
|
|
39
|
+
const {id} = meter;
|
|
40
|
+
return {id: `${meterService}/${id}`};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getInvocationSigner() {
|
|
44
|
+
// convert multibase seed to Uint8Array
|
|
45
|
+
const seed = _decodeMultibaseSeed({
|
|
46
|
+
seedMultibase: DEFAULT_APPLICATION_ID_SEED
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const didKeyDriver = driver();
|
|
50
|
+
const didKey = await didKeyDriver.generate({seed});
|
|
51
|
+
|
|
52
|
+
const {didDocument: {capabilityInvocation}} = didKey;
|
|
53
|
+
|
|
54
|
+
const capabilityInvocationKey = didKey.keyPairs.get(capabilityInvocation[0]);
|
|
55
|
+
return capabilityInvocationKey.signer();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _decodeMultibaseSeed({seedMultibase}) {
|
|
59
|
+
const prefix = seedMultibase[0];
|
|
60
|
+
if(prefix !== MULTIBASE_BASE58BTC_HEADER) {
|
|
61
|
+
throw new Error('Unsupported multibase encoding.');
|
|
62
|
+
}
|
|
63
|
+
const data = seedMultibase.substring(1);
|
|
64
|
+
// <varint hash fn code> <varint digest size in bytes> <hash fn output>
|
|
65
|
+
// <identity function> <32> <seed bytes>
|
|
66
|
+
const seedMultihash = decode(data);
|
|
67
|
+
// <varint hash fn code>: identity function
|
|
68
|
+
const [hashFnCode] = seedMultihash.slice(0, 1);
|
|
69
|
+
if(hashFnCode !== MULTIHASH_IDENTITY_FUNCTION_CODE) {
|
|
70
|
+
throw new Error('Invalid multihash function code.');
|
|
71
|
+
}
|
|
72
|
+
// <varint digest size in bytes>: 32
|
|
73
|
+
const [digestSize] = seedMultihash.slice(1, 2);
|
|
74
|
+
if(digestSize !== SEED_BYTE_SIZE) {
|
|
75
|
+
throw new Error('Invalid digest size.');
|
|
76
|
+
}
|
|
77
|
+
// <hash fn output>: seed bytes
|
|
78
|
+
const seedBytes = seedMultihash.slice(2, seedMultihash.length);
|
|
79
|
+
if(seedBytes.byteLength !== SEED_BYTE_SIZE) {
|
|
80
|
+
throw new Error('Invalid digest.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return seedBytes;
|
|
84
|
+
}
|