@tdxvolt/volt-client-grpc 0.16.0-beta.1 → 0.16.0-beta.12
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/README.md +1 -1
- package/lib/index.cjs +444 -407
- package/package.json +2 -4
- package/protobuf/tdx/volt_api/data/v1/sqlite_database_api.proto +49 -13
- package/protobuf/tdx/volt_api/data/v1/sqlite_server_api.proto +3 -0
- package/protobuf/tdx/volt_api/volt/v1/discovery_api.proto +0 -4
- package/protobuf/tdx/volt_api/volt/v1/ssi.proto +31 -5
- package/protobuf/tdx/volt_api/volt/v1/ssi_api.proto +119 -31
- package/protobuf/tdx/volt_api/volt/v1/terminal_api.proto +42 -0
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +141 -134
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +138 -234
- package/src/constants.js +3 -5
- package/src/grpc-call.js +142 -51
- package/src/grpc-utils.js +8 -4
- package/src/rpc-invocation.js +94 -46
- package/src/utils.js +4 -119
- package/src/volt-client-internal.js +123 -216
- package/src/volt-client.js +33 -13
- package/src/volt-connection.js +25 -16
- package/src/volt-credential.js +46 -125
package/lib/index.cjs
CHANGED
|
@@ -4,37 +4,32 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var lodash = require('lodash');
|
|
6
6
|
var ip = require('ip');
|
|
7
|
-
var forge = require('node-forge');
|
|
8
|
-
var bs58 = require('bs58');
|
|
9
7
|
var debug = require('debug');
|
|
10
8
|
var EventEmitter = require('events');
|
|
11
9
|
var fs = require('fs');
|
|
12
|
-
var
|
|
13
|
-
var crypto = require('crypto');
|
|
10
|
+
var voltClientWeb = require('@tdxvolt/volt-client-web');
|
|
14
11
|
var path = require('path');
|
|
15
12
|
var protoLoader = require('@grpc/proto-loader');
|
|
16
13
|
var url = require('url');
|
|
17
14
|
var bent = require('bent');
|
|
15
|
+
var jwt = require('jsonwebtoken');
|
|
18
16
|
var uuid = require('uuid');
|
|
19
17
|
|
|
20
18
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
21
19
|
|
|
22
20
|
var lodash__default = /*#__PURE__*/_interopDefaultLegacy(lodash);
|
|
23
21
|
var ip__default = /*#__PURE__*/_interopDefaultLegacy(ip);
|
|
24
|
-
var forge__default = /*#__PURE__*/_interopDefaultLegacy(forge);
|
|
25
|
-
var bs58__default = /*#__PURE__*/_interopDefaultLegacy(bs58);
|
|
26
22
|
var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug);
|
|
27
23
|
var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
|
|
28
24
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
29
|
-
var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
|
|
30
|
-
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
|
|
31
25
|
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
32
26
|
var protoLoader__default = /*#__PURE__*/_interopDefaultLegacy(protoLoader);
|
|
33
27
|
var bent__default = /*#__PURE__*/_interopDefaultLegacy(bent);
|
|
28
|
+
var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
|
|
34
29
|
|
|
35
30
|
// eslint-disable-next-line import/prefer-default-export
|
|
36
31
|
const constants = {
|
|
37
|
-
authTokenName: "
|
|
32
|
+
authTokenName: "volt-token",
|
|
38
33
|
collectionOperation: {
|
|
39
34
|
add: "ADD",
|
|
40
35
|
remove: "REMOVE",
|
|
@@ -57,9 +52,9 @@ const constants = {
|
|
|
57
52
|
],
|
|
58
53
|
userIdOID: "0.9.2342.19200300.100.1.1"
|
|
59
54
|
},
|
|
60
|
-
defaultDIDHostName: "
|
|
55
|
+
defaultDIDHostName: "tdxvolt.com",
|
|
61
56
|
didServiceType: {
|
|
62
|
-
voltConfig: "
|
|
57
|
+
voltConfig: "volt-discovery"
|
|
63
58
|
},
|
|
64
59
|
identityType: {
|
|
65
60
|
did: "did",
|
|
@@ -70,8 +65,6 @@ const constants = {
|
|
|
70
65
|
online: "online",
|
|
71
66
|
unknown: "unknown"
|
|
72
67
|
},
|
|
73
|
-
publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
|
|
74
|
-
privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
|
|
75
68
|
privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
|
|
76
69
|
resource: {
|
|
77
70
|
identity: "ident",
|
|
@@ -108,8 +101,6 @@ const constants = {
|
|
|
108
101
|
tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
|
|
109
102
|
};
|
|
110
103
|
|
|
111
|
-
const { pki: pki$2 } = forge__default["default"];
|
|
112
|
-
|
|
113
104
|
const createSecureContextOptions = (cryptoOptions) => {
|
|
114
105
|
const tlsOptions = {
|
|
115
106
|
cert: Buffer.from(cryptoOptions.cert),
|
|
@@ -139,53 +130,7 @@ const getServiceAddress = (mdnsService) => {
|
|
|
139
130
|
return address;
|
|
140
131
|
};
|
|
141
132
|
|
|
142
|
-
const
|
|
143
|
-
return pki$2.privateKeyToPem(keyPair.privateKey);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const publicKeyToPem = (keyPair) => {
|
|
147
|
-
return pki$2.publicKeyToPem(keyPair.publicKey);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const deserialisePEM = (pem) => {
|
|
151
|
-
return pem.replace(/\\r\\n/g, "\r\n");
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const removeCarriageReturn = (inp) => {
|
|
155
|
-
return inp.replace(/\r\n/g, "\n");
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const createBase58Hash = (inp) => {
|
|
159
|
-
const md = forge__default["default"].md.sha256.create();
|
|
160
|
-
md.update(inp);
|
|
161
|
-
const hashBuf = Buffer.from(md.digest().getBytes(), "binary");
|
|
162
|
-
return bs58__default["default"].encode(hashBuf);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const createPublicKeyFingerprint = (key) => {
|
|
166
|
-
// Remove carriage returns before creating hash to bring into line with openSSL / volt format.
|
|
167
|
-
return createBase58Hash(removeCarriageReturn(key));
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const base64ToBase64Url = (base64) => {
|
|
171
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const signBase64 = (key, msg) => {
|
|
175
|
-
const md = forge__default["default"].md.sha256.create();
|
|
176
|
-
md.update(msg);
|
|
177
|
-
const signature = key.sign(md);
|
|
178
|
-
const base64Standard = forge__default["default"].util.encode64(signature);
|
|
179
|
-
return base64ToBase64Url(base64Standard);
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const verifyBase64 = (key, msg, signature) => {
|
|
183
|
-
const md = forge__default["default"].md.sha256.create();
|
|
184
|
-
md.update(msg);
|
|
185
|
-
return key.verify(md.digest().bytes(), forge__default["default"].util.decode64(signature));
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const getDIDResolutionURL = (didIn) => {
|
|
133
|
+
const getDIDResolutionURL = (didIn, registryUrl = "") => {
|
|
189
134
|
const did = didIn.toLowerCase().split(":");
|
|
190
135
|
if (did.length < 3) {
|
|
191
136
|
return "";
|
|
@@ -195,97 +140,19 @@ const getDIDResolutionURL = (didIn) => {
|
|
|
195
140
|
return "";
|
|
196
141
|
}
|
|
197
142
|
|
|
198
|
-
if (did[1] !== "
|
|
143
|
+
if (did[1] !== "volt") {
|
|
199
144
|
return "";
|
|
200
145
|
}
|
|
201
146
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (did.length === 3) {
|
|
205
|
-
hostName = constants.defaultDIDHostName;
|
|
206
|
-
didGUID = did[2];
|
|
207
|
-
} else {
|
|
208
|
-
hostName = did[2];
|
|
209
|
-
didGUID = did[3];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return `https://${hostName}/api/identity/${didGUID}`;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const forgePrivateKeyToPem = (decryptedKey) => {
|
|
216
|
-
// Convert a Forge private key to an ASN.1 RSAPrivateKey
|
|
217
|
-
const asnPrivateKey = pki$2.privateKeyToAsn1(decryptedKey);
|
|
218
|
-
|
|
219
|
-
// Wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
|
|
220
|
-
const privateKeyInfo = pki$2.wrapRsaPrivateKey(asnPrivateKey);
|
|
221
|
-
|
|
222
|
-
// Convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
|
|
223
|
-
return pki$2.privateKeyInfoToPem(privateKeyInfo);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const createRSAKey = (passphrase = "") => {
|
|
227
|
-
return new Promise((resolve, reject) => {
|
|
228
|
-
pki$2.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
|
|
229
|
-
if (err) {
|
|
230
|
-
reject(err);
|
|
231
|
-
} else {
|
|
232
|
-
let pem;
|
|
233
|
-
if (passphrase) {
|
|
234
|
-
pem = pki$2.encryptRsaPrivateKey(keypair.privateKey, passphrase);
|
|
235
|
-
} else {
|
|
236
|
-
pem = forgePrivateKeyToPem(keypair.privateKey);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
resolve({ keypair, pem });
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const sha256Base64 = (msg) => {
|
|
246
|
-
const md = forge__default["default"].md.sha256.create();
|
|
247
|
-
md.update(msg);
|
|
248
|
-
const base64Standard = forge__default["default"].util.encode64(md.digest().bytes());
|
|
249
|
-
return base64ToBase64Url(base64Standard);
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const decryptPrivateKey = (pem, passphrase) => {
|
|
253
|
-
const decryptedKey = pki$2.decryptRsaPrivateKey(pem, passphrase);
|
|
254
|
-
return forgePrivateKeyToPem(decryptedKey);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const jwsSignature = (signature) => {
|
|
258
|
-
//
|
|
259
|
-
// Use an unattached payload JWS signature for the DID document proof.
|
|
260
|
-
// This is a JWS with an empty payload and a detached payload digest.
|
|
261
|
-
//
|
|
262
|
-
// (https://tools.ietf.org/html/rfc7797#appendix-F) as used in the VC spec
|
|
263
|
-
// (https://www.w3.org/TR/vc-data-model/).
|
|
264
|
-
//
|
|
265
|
-
const jwsHeader = base64ToBase64Url(
|
|
266
|
-
forge__default["default"].util.encode64('{"alg":"RS256","b64": false}')
|
|
267
|
-
);
|
|
268
|
-
return jwsHeader + ".." + signature;
|
|
147
|
+
const lookupUrl = registryUrl || `https://${constants.defaultDIDHostName}`;
|
|
148
|
+
return `${lookupUrl}/did/${didIn}`;
|
|
269
149
|
};
|
|
270
150
|
|
|
271
151
|
var utils = /*#__PURE__*/Object.freeze({
|
|
272
152
|
__proto__: null,
|
|
273
153
|
createSecureContextOptions: createSecureContextOptions,
|
|
274
154
|
getServiceAddress: getServiceAddress,
|
|
275
|
-
|
|
276
|
-
publicKeyToPem: publicKeyToPem,
|
|
277
|
-
deserialisePEM: deserialisePEM,
|
|
278
|
-
removeCarriageReturn: removeCarriageReturn,
|
|
279
|
-
createBase58Hash: createBase58Hash,
|
|
280
|
-
createPublicKeyFingerprint: createPublicKeyFingerprint,
|
|
281
|
-
base64ToBase64Url: base64ToBase64Url,
|
|
282
|
-
signBase64: signBase64,
|
|
283
|
-
verifyBase64: verifyBase64,
|
|
284
|
-
getDIDResolutionURL: getDIDResolutionURL,
|
|
285
|
-
createRSAKey: createRSAKey,
|
|
286
|
-
sha256Base64: sha256Base64,
|
|
287
|
-
decryptPrivateKey: decryptPrivateKey,
|
|
288
|
-
jwsSignature: jwsSignature
|
|
155
|
+
getDIDResolutionURL: getDIDResolutionURL
|
|
289
156
|
});
|
|
290
157
|
|
|
291
158
|
/* eslint-disable no-underscore-dangle */
|
|
@@ -307,8 +174,11 @@ function createClient(
|
|
|
307
174
|
) {
|
|
308
175
|
let grpcCredentials;
|
|
309
176
|
if (credentials) {
|
|
310
|
-
const
|
|
311
|
-
const
|
|
177
|
+
const relaying = !!credentials.config?.volt?.relay;
|
|
178
|
+
const ca = relaying
|
|
179
|
+
? credentials.config?.volt?.relay?.ca_pem
|
|
180
|
+
: credentials.cache.ca;
|
|
181
|
+
const cert = relaying ? "" : credentials.cache.cert;
|
|
312
182
|
const { key } = credentials.cache;
|
|
313
183
|
|
|
314
184
|
if (key && cert) {
|
|
@@ -320,8 +190,9 @@ function createClient(
|
|
|
320
190
|
tlsOptions.cert
|
|
321
191
|
);
|
|
322
192
|
} else {
|
|
323
|
-
// Cache has no private key, this implies we are connecting without supplying a client certificate.
|
|
324
|
-
//
|
|
193
|
+
// Cache has no private key or certificate, this implies we are connecting without supplying a client certificate.
|
|
194
|
+
// We might be connecting to a relay, or making an initial Authenticate call.
|
|
195
|
+
// Simply connect using the CA certificate, and the a JWT will be used to authenticate.
|
|
325
196
|
grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
|
|
326
197
|
}
|
|
327
198
|
} else {
|
|
@@ -527,10 +398,7 @@ var grpcUtils = /*#__PURE__*/Object.freeze({
|
|
|
527
398
|
|
|
528
399
|
/* eslint-disable no-underscore-dangle */
|
|
529
400
|
|
|
530
|
-
const { pki: pki$1 } = forge__default["default"];
|
|
531
401
|
const log$5 = debug__default["default"]("volt-client-grpc:volt-credential");
|
|
532
|
-
const aesAlgorithm = "aes-256-cbc";
|
|
533
|
-
const rs256Algorithm = "RS256";
|
|
534
402
|
|
|
535
403
|
class VoltCredential {
|
|
536
404
|
constructor(config, configPath) {
|
|
@@ -554,14 +422,13 @@ class VoltCredential {
|
|
|
554
422
|
}
|
|
555
423
|
|
|
556
424
|
// Need to unencrypt the private key.
|
|
557
|
-
const decrypted =
|
|
558
|
-
|
|
559
|
-
config.p
|
|
560
|
-
);
|
|
425
|
+
const decrypted = voltClientWeb.keyFromPem(this._cryptoCache.key, config.p);
|
|
426
|
+
|
|
561
427
|
if (!decrypted) {
|
|
562
428
|
throw new Error("failed to decrypt key - check passphrase");
|
|
563
429
|
}
|
|
564
|
-
|
|
430
|
+
|
|
431
|
+
this._cryptoCache.key = voltClientWeb.privateKeyToPem(decrypted);
|
|
565
432
|
}
|
|
566
433
|
|
|
567
434
|
if (this._voltConfig.ca_pem) {
|
|
@@ -570,14 +437,11 @@ class VoltCredential {
|
|
|
570
437
|
this._cryptoCache.ca = this._voltConfig.ca_pem;
|
|
571
438
|
}
|
|
572
439
|
|
|
573
|
-
// Pre-cache the target Volt public key by extracting it from the signing certificate.
|
|
574
|
-
const voltCert = forge__default["default"].pki.certificateFromPem(this._voltConfig.ca_pem);
|
|
575
|
-
|
|
576
440
|
// Extract Volt public key (used for encrypting Relay payloads).
|
|
577
|
-
this._voltPublicKey =
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
);
|
|
441
|
+
this._voltPublicKey = this._voltConfig.public_key;
|
|
442
|
+
|
|
443
|
+
const voltKey = voltClientWeb.keyFromPem(this._voltPublicKey).publicKey;
|
|
444
|
+
this._voltFingerprint = voltClientWeb.fingerprintFromPublicKey(voltKey);
|
|
581
445
|
}
|
|
582
446
|
}
|
|
583
447
|
|
|
@@ -589,8 +453,16 @@ class VoltCredential {
|
|
|
589
453
|
return this._cryptoCache;
|
|
590
454
|
}
|
|
591
455
|
|
|
592
|
-
get
|
|
593
|
-
return this._cryptoCache.
|
|
456
|
+
get certificate() {
|
|
457
|
+
return this._cryptoCache.cert;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
get sessionId() {
|
|
461
|
+
return this._cryptoCache.session_id;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
get identityId() {
|
|
465
|
+
return this._cryptoCache.identity_did;
|
|
594
466
|
}
|
|
595
467
|
|
|
596
468
|
get voltPublicKey() {
|
|
@@ -598,7 +470,7 @@ class VoltCredential {
|
|
|
598
470
|
}
|
|
599
471
|
|
|
600
472
|
get publicKey() {
|
|
601
|
-
return
|
|
473
|
+
return voltClientWeb.publicKeyToPem(this.getKey().publicKey);
|
|
602
474
|
}
|
|
603
475
|
|
|
604
476
|
get voltFingerprint() {
|
|
@@ -606,9 +478,8 @@ class VoltCredential {
|
|
|
606
478
|
}
|
|
607
479
|
|
|
608
480
|
getKey() {
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
return { privateKey, publicKey };
|
|
481
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
482
|
+
return key;
|
|
612
483
|
}
|
|
613
484
|
|
|
614
485
|
/**
|
|
@@ -621,16 +492,10 @@ class VoltCredential {
|
|
|
621
492
|
return Promise.resolve(this.getKey());
|
|
622
493
|
} else {
|
|
623
494
|
log$5("creating key");
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
return keyInfo.keypair;
|
|
629
|
-
})
|
|
630
|
-
.catch((err) => {
|
|
631
|
-
log$5("failure creating key [%s]", err.message);
|
|
632
|
-
return Promise.reject(err);
|
|
633
|
-
});
|
|
495
|
+
this._cryptoCache.key = voltClientWeb.createKey();
|
|
496
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
497
|
+
log$5("successfully created key");
|
|
498
|
+
return Promise.resolve(key);
|
|
634
499
|
}
|
|
635
500
|
}
|
|
636
501
|
|
|
@@ -655,55 +520,6 @@ class VoltCredential {
|
|
|
655
520
|
return !!this._cryptoCache.cert;
|
|
656
521
|
}
|
|
657
522
|
|
|
658
|
-
/**
|
|
659
|
-
* Signs the identity resource id using the private key.
|
|
660
|
-
* @param {*} audience the token audience (usually the target volt)
|
|
661
|
-
* @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
|
|
662
|
-
* @param {*} ttl time to live in seconds (default to 1 minute)
|
|
663
|
-
*/
|
|
664
|
-
getIdentityToken(audience, publicKey, tunnelling = false, ttl = 60) {
|
|
665
|
-
if (!this._cryptoCache.key) {
|
|
666
|
-
throw new Error("crypto cache invalid");
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const keyPair = this.getKey();
|
|
670
|
-
const publicKeyPem = pki$1.publicKeyToPem(keyPair.publicKey);
|
|
671
|
-
const base58Key = createPublicKeyFingerprint(publicKeyPem);
|
|
672
|
-
|
|
673
|
-
log$5("base58 key is %s", base58Key);
|
|
674
|
-
|
|
675
|
-
// Allow TTL either side of the current time.
|
|
676
|
-
// @todo - fix with server time sync on volt connection.
|
|
677
|
-
const payload = {
|
|
678
|
-
aud: audience,
|
|
679
|
-
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
680
|
-
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
681
|
-
sub: this._cryptoCache.client_id
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
let sharedKey;
|
|
685
|
-
if (tunnelling) {
|
|
686
|
-
// Initialise the Relay encryption key.
|
|
687
|
-
sharedKey = VoltCredential.aesCreateKey();
|
|
688
|
-
|
|
689
|
-
// Encrypt the key details using the target Volt public key and include this in the JWT payload.
|
|
690
|
-
payload.sk = VoltCredential.rsaEncrypt(publicKey, sharedKey.key);
|
|
691
|
-
payload.iv = VoltCredential.rsaEncrypt(publicKey, sharedKey.iv);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Sign synchronously.
|
|
695
|
-
const token = jwt__default["default"].sign(payload, this._cryptoCache.key, {
|
|
696
|
-
algorithm: rs256Algorithm,
|
|
697
|
-
keyid: base58Key
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
// Return the token along with any encryption key details.
|
|
701
|
-
return {
|
|
702
|
-
token,
|
|
703
|
-
sharedKey
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
|
|
707
523
|
/**
|
|
708
524
|
* Creates the metadata required for a grpc call, including the Volt authentication token
|
|
709
525
|
* and optionally tunnel encryption parameters.
|
|
@@ -714,9 +530,12 @@ class VoltCredential {
|
|
|
714
530
|
* @returns
|
|
715
531
|
*/
|
|
716
532
|
getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
|
|
717
|
-
const
|
|
533
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
534
|
+
|
|
535
|
+
const identityToken = voltClientWeb.getIdentityToken(
|
|
536
|
+
this.sessionId,
|
|
537
|
+
key,
|
|
718
538
|
audience || this._voltConfig.id,
|
|
719
|
-
publicKey,
|
|
720
539
|
tunnelling,
|
|
721
540
|
ttl
|
|
722
541
|
);
|
|
@@ -726,48 +545,18 @@ class VoltCredential {
|
|
|
726
545
|
}
|
|
727
546
|
|
|
728
547
|
saveCache() {
|
|
548
|
+
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
549
|
+
if (this._config.p) {
|
|
550
|
+
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
551
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
552
|
+
clone.credential.key = voltClientWeb.privateKeyToPem(key.privateKey, this._config.p);
|
|
553
|
+
}
|
|
554
|
+
|
|
729
555
|
if (this._persistCache) {
|
|
730
|
-
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
731
|
-
if (this._config.p) {
|
|
732
|
-
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
733
|
-
const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
|
|
734
|
-
clone.credential.key = pki$1.encryptRsaPrivateKey(
|
|
735
|
-
privateKey,
|
|
736
|
-
this._config.p
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
556
|
fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
|
|
740
557
|
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
static aesCreateKey() {
|
|
744
|
-
// Generate random key for the AES-256 algorithm (32 bytes = 256 bits).
|
|
745
|
-
const key = crypto__default["default"].randomBytes(32);
|
|
746
|
-
const iv = crypto__default["default"].randomBytes(16);
|
|
747
|
-
return { key, iv };
|
|
748
|
-
}
|
|
749
558
|
|
|
750
|
-
|
|
751
|
-
const cipher = crypto__default["default"].createCipheriv(aesAlgorithm, key, iv);
|
|
752
|
-
const crypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
753
|
-
return crypted;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
static aesDecrypt(buffer, key, iv) {
|
|
757
|
-
const cipher = crypto__default["default"].createDecipheriv(aesAlgorithm, key, iv);
|
|
758
|
-
const decrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
759
|
-
return decrypted;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
static rsaEncrypt(key, buffer) {
|
|
763
|
-
const cipher = crypto__default["default"].publicEncrypt(key, buffer);
|
|
764
|
-
return cipher.toString("base64url");
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
static rsaDecrypt(key, buffer) {
|
|
768
|
-
const keyObj = crypto__default["default"].createPrivateKey(key);
|
|
769
|
-
const cipher = Buffer.from(buffer, "base64url");
|
|
770
|
-
return Buffer.from(crypto__default["default"].privateDecrypt(keyObj, cipher));
|
|
559
|
+
return clone;
|
|
771
560
|
}
|
|
772
561
|
}
|
|
773
562
|
|
|
@@ -910,6 +699,21 @@ function _prepareInvokeRequest(
|
|
|
910
699
|
this._service?.service_description.host_type ===
|
|
911
700
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
912
701
|
|
|
702
|
+
const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
|
|
703
|
+
|
|
704
|
+
let targetDID = [];
|
|
705
|
+
if (this._voltClient.isVoltRelay) {
|
|
706
|
+
targetDID.push(this._voltClient.voltConfig.id);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (isServiceRelayed) {
|
|
710
|
+
// Add another relay hop if the target service is relayed.
|
|
711
|
+
log$4("target service is relayed");
|
|
712
|
+
targetDID.push(this._service.service_description.host_client_id);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
log$4("target is %j", targetDID);
|
|
716
|
+
|
|
913
717
|
// Use the service's host client id and public key if the service is relayed.
|
|
914
718
|
const methodToken = this._voltClient.credential.getIdentityMetadata(
|
|
915
719
|
this._voltClient.grpc,
|
|
@@ -917,17 +721,52 @@ function _prepareInvokeRequest(
|
|
|
917
721
|
this._voltClient.voltConfig.id,
|
|
918
722
|
this._service?.service_description.host_public_key ||
|
|
919
723
|
this._voltClient.credential.voltPublicKey,
|
|
920
|
-
|
|
724
|
+
relaying
|
|
921
725
|
);
|
|
922
726
|
|
|
923
727
|
let invokeRequest;
|
|
924
728
|
let callMethod = method;
|
|
925
729
|
|
|
926
|
-
if (
|
|
730
|
+
if (relaying) {
|
|
927
731
|
//
|
|
928
732
|
// If the volt connection is via a relay (or the target service is relayed),
|
|
929
733
|
// we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
|
|
930
734
|
//
|
|
735
|
+
// Replace the target method with a call to Invoke on the relay Volt.
|
|
736
|
+
//
|
|
737
|
+
callMethod = "Invoke";
|
|
738
|
+
|
|
739
|
+
// But first we need to perform a key exchange with the target service.
|
|
740
|
+
//
|
|
741
|
+
if (!this._encryptionKey) {
|
|
742
|
+
// We haven't exchanged keys yet, so queue the payload.
|
|
743
|
+
this._pendingRequests.push(request);
|
|
744
|
+
|
|
745
|
+
if (this._keyExchangePending) {
|
|
746
|
+
// We've already sent the key exchange request, so do nothing.
|
|
747
|
+
log$4("key exchange pending, not sending payload");
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
this._keyExchangePending = true;
|
|
752
|
+
|
|
753
|
+
// Send the key exchange request.
|
|
754
|
+
invokeRequest = {
|
|
755
|
+
token: methodToken.token,
|
|
756
|
+
target_did: targetDID,
|
|
757
|
+
target_service_id: isServiceRelayed
|
|
758
|
+
? this._service.service_description.host_service_id
|
|
759
|
+
: undefined
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
request: invokeRequest,
|
|
764
|
+
method: callMethod,
|
|
765
|
+
methodName: method,
|
|
766
|
+
meta: methodToken
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
931
770
|
let requestPayload;
|
|
932
771
|
|
|
933
772
|
// Serialise the rpc request.
|
|
@@ -947,45 +786,24 @@ function _prepareInvokeRequest(
|
|
|
947
786
|
method_invoke: {
|
|
948
787
|
method_name: this._grpcClient[method].path,
|
|
949
788
|
method_type: methodType,
|
|
950
|
-
token: methodToken.token,
|
|
951
789
|
request: requestPayload
|
|
952
790
|
}
|
|
953
791
|
};
|
|
954
792
|
|
|
955
|
-
let targetDID = [];
|
|
956
793
|
let invokePayload;
|
|
957
|
-
|
|
958
|
-
// When sending via a relay we need to encrypt the payload.
|
|
959
|
-
invokePayload = VoltCredential.aesEncrypt(
|
|
960
|
-
tunnelMethod.responseSerialize(tunnelResp),
|
|
961
|
-
methodToken.sharedKey.key,
|
|
962
|
-
methodToken.sharedKey.iv
|
|
963
|
-
);
|
|
964
|
-
} else {
|
|
965
|
-
invokePayload = tunnelMethod.responseSerialize(tunnelResp);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (this._voltClient.isVoltRelay) {
|
|
969
|
-
targetDID.push(this._voltClient.voltConfig.id);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
if (isServiceRelayed) {
|
|
973
|
-
// Add another relay hop if the target service is relayed.
|
|
974
|
-
log$4("target service is relayed");
|
|
975
|
-
targetDID.push(this._service.service_description.host_client_id);
|
|
976
|
-
}
|
|
794
|
+
const iv = voltClientWeb.aesCreateKey().iv;
|
|
977
795
|
|
|
978
|
-
|
|
796
|
+
// When sending via a relay we need to encrypt the payload.
|
|
797
|
+
invokePayload = voltClientWeb.aesEncrypt(
|
|
798
|
+
this._encryptionKey,
|
|
799
|
+
iv,
|
|
800
|
+
tunnelMethod.responseSerialize(tunnelResp)
|
|
801
|
+
);
|
|
979
802
|
|
|
980
803
|
invokeRequest = {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
payload: invokePayload,
|
|
984
|
-
target_service_id: isServiceRelayed ? this._service.id : undefined
|
|
804
|
+
iv,
|
|
805
|
+
payload: invokePayload
|
|
985
806
|
};
|
|
986
|
-
|
|
987
|
-
// Replace the target method with a call to Invoke on the relay Volt.
|
|
988
|
-
callMethod = "Invoke";
|
|
989
807
|
} else {
|
|
990
808
|
invokeRequest = request;
|
|
991
809
|
}
|
|
@@ -1005,7 +823,10 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
1005
823
|
this._service?.service_description.host_type ===
|
|
1006
824
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
1007
825
|
|
|
826
|
+
let iv;
|
|
1008
827
|
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
828
|
+
iv = voltClientWeb.aesCreateKey().iv;
|
|
829
|
+
|
|
1009
830
|
const requestPayload =
|
|
1010
831
|
this._grpcClient[invokeInfo.methodName].requestSerialize(request);
|
|
1011
832
|
|
|
@@ -1018,13 +839,14 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
1018
839
|
}
|
|
1019
840
|
};
|
|
1020
841
|
|
|
1021
|
-
const invokePayload =
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
842
|
+
const invokePayload = voltClientWeb.aesEncrypt(
|
|
843
|
+
this._encryptionKey,
|
|
844
|
+
iv,
|
|
845
|
+
tunnelMethod.responseSerialize(tunnelResp)
|
|
1025
846
|
);
|
|
1026
847
|
|
|
1027
848
|
payloadRequest = {
|
|
849
|
+
iv,
|
|
1028
850
|
payload: invokePayload
|
|
1029
851
|
};
|
|
1030
852
|
} else {
|
|
@@ -1043,13 +865,38 @@ function _parseResponse(method, meta, response) {
|
|
|
1043
865
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
1044
866
|
|
|
1045
867
|
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
1046
|
-
if (response.
|
|
868
|
+
if (response.key_exchange) {
|
|
869
|
+
const encryptionKey = response.key_exchange.encryption_key;
|
|
870
|
+
const nonce = response.key_exchange.nonce;
|
|
871
|
+
const signature = response.key_exchange.signature;
|
|
872
|
+
|
|
873
|
+
// The signed message is the encryption key and the nonce.
|
|
874
|
+
const message = new Uint8Array(encryptionKey.length + nonce.length);
|
|
875
|
+
message.set(encryptionKey);
|
|
876
|
+
message.set(nonce, encryptionKey.length);
|
|
877
|
+
|
|
878
|
+
// Check the signature using the target service public key.
|
|
879
|
+
const targetKey = voltClientWeb.keyFromPem(
|
|
880
|
+
this._service?.service_description?.host_public_key ||
|
|
881
|
+
this._voltClient.credential.voltPublicKey
|
|
882
|
+
).publicKey;
|
|
883
|
+
|
|
884
|
+
if (!voltClientWeb.verify(targetKey, message, signature)) {
|
|
885
|
+
throw new Error("signature verification failed");
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
invokeResponse = {
|
|
889
|
+
key_exchange: {
|
|
890
|
+
encryption_key: voltClientWeb.formatPEM(voltClientWeb.toBase64(encryptionKey), "PUBLIC KEY")
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
} else if (response.payload) {
|
|
1047
894
|
let decryptedPayload;
|
|
1048
|
-
if (
|
|
1049
|
-
decryptedPayload =
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
895
|
+
if (this._encryptionKey) {
|
|
896
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
897
|
+
this._encryptionKey,
|
|
898
|
+
response.iv,
|
|
899
|
+
response.payload
|
|
1053
900
|
);
|
|
1054
901
|
} else {
|
|
1055
902
|
decryptedPayload = response.payload;
|
|
@@ -1089,11 +936,11 @@ function _parseResponse(method, meta, response) {
|
|
|
1089
936
|
invokeResponse.methodId = response.invoke_id;
|
|
1090
937
|
log$4("started method %d for %s", invokeResponse.methodId, method);
|
|
1091
938
|
}
|
|
1092
|
-
} else if (this._voltClient.isRemote &&
|
|
1093
|
-
const decryptedPayload =
|
|
1094
|
-
|
|
1095
|
-
meta.sharedKey.
|
|
1096
|
-
|
|
939
|
+
} else if (this._voltClient.isRemote && this._encryptionKey) {
|
|
940
|
+
const decryptedPayload = voltClientWeb.aesDecrypt(
|
|
941
|
+
this._encryptionKey,
|
|
942
|
+
meta.sharedKey.iv,
|
|
943
|
+
response
|
|
1097
944
|
);
|
|
1098
945
|
invokeResponse = {
|
|
1099
946
|
payload:
|
|
@@ -1114,6 +961,9 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1114
961
|
this._call = null;
|
|
1115
962
|
this._voltClient = voltClient;
|
|
1116
963
|
this._service = service;
|
|
964
|
+
this._keyExchangePending = false;
|
|
965
|
+
this._pendingRequests = [];
|
|
966
|
+
this._encryptionKey = null;
|
|
1117
967
|
}
|
|
1118
968
|
|
|
1119
969
|
start(grpcClient, request) {
|
|
@@ -1142,7 +992,7 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1142
992
|
}
|
|
1143
993
|
|
|
1144
994
|
this._call = callClient[this._initialRequest.method](
|
|
1145
|
-
this._initialRequest
|
|
995
|
+
this._initialRequest?.meta?.metadata
|
|
1146
996
|
);
|
|
1147
997
|
|
|
1148
998
|
this._call.on("data", (streamResponse) => {
|
|
@@ -1153,7 +1003,25 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1153
1003
|
this._initialRequest.meta,
|
|
1154
1004
|
streamResponse
|
|
1155
1005
|
);
|
|
1156
|
-
if (parsedResponse.
|
|
1006
|
+
if (parsedResponse.key_exchange) {
|
|
1007
|
+
const peerKey = voltClientWeb.keyFromPem(
|
|
1008
|
+
parsedResponse.key_exchange.encryption_key
|
|
1009
|
+
).publicKey;
|
|
1010
|
+
|
|
1011
|
+
this._encryptionKey = voltClientWeb.deriveSharedKey(
|
|
1012
|
+
this._initialRequest.meta.sharedKey.key,
|
|
1013
|
+
peerKey,
|
|
1014
|
+
voltClientWeb.x25519
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
// We've exchanged keys, so send any queued payloads on the next tick.
|
|
1018
|
+
process.nextTick(() => {
|
|
1019
|
+
this._pendingRequests.forEach((pendingRequest) => {
|
|
1020
|
+
this.write(pendingRequest);
|
|
1021
|
+
});
|
|
1022
|
+
this._pendingRequests = [];
|
|
1023
|
+
});
|
|
1024
|
+
} else if (parsedResponse.payload) {
|
|
1157
1025
|
// Interpret a non-empty status message as an error.
|
|
1158
1026
|
if (parsedResponse.payload?.status?.message) {
|
|
1159
1027
|
this.emit(
|
|
@@ -1228,7 +1096,7 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1228
1096
|
if (!this._call) {
|
|
1229
1097
|
log$4("ERROR - call not initialised");
|
|
1230
1098
|
throw new Error("call not initialised");
|
|
1231
|
-
} else if (this._initialRequest) {
|
|
1099
|
+
} else if (this._initialRequest && !this._keyExchangePending) {
|
|
1232
1100
|
const payloadRequest = _preparePayloadRequest.call(
|
|
1233
1101
|
this,
|
|
1234
1102
|
request,
|
|
@@ -1236,6 +1104,8 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1236
1104
|
);
|
|
1237
1105
|
return this._call.write(payloadRequest);
|
|
1238
1106
|
} else {
|
|
1107
|
+
this._keyExchangePending = false;
|
|
1108
|
+
|
|
1239
1109
|
this._initialRequest = _prepareInvokeRequest.call(
|
|
1240
1110
|
this,
|
|
1241
1111
|
request,
|
|
@@ -1265,10 +1135,9 @@ function _cleanUp(immediateReconnect) {
|
|
|
1265
1135
|
this._pingTimeoutTimer = 0;
|
|
1266
1136
|
}
|
|
1267
1137
|
|
|
1268
|
-
if (
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
) {
|
|
1138
|
+
if (this._dying) {
|
|
1139
|
+
clearTimeout(this._reconnectTimer);
|
|
1140
|
+
} else if (immediateReconnect || (this._autoRetry && !this._reconnectTimer)) {
|
|
1272
1141
|
this._reconnectTimer = setTimeout(
|
|
1273
1142
|
() => {
|
|
1274
1143
|
this._reconnectTimer = 0;
|
|
@@ -1294,13 +1163,18 @@ function _doConnect(connectHello) {
|
|
|
1294
1163
|
|
|
1295
1164
|
const grpcClient = this._voltClient.getVoltAPIClient();
|
|
1296
1165
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1166
|
+
// Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
|
|
1167
|
+
// within this time, it will assume the connection is dead.
|
|
1168
|
+
const defaultHello = {
|
|
1169
|
+
ping_interval: this._pingInterval,
|
|
1170
|
+
timestamp: Date.now()
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
const helloPayload = { ...connectHello } || {};
|
|
1174
|
+
|
|
1175
|
+
helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
|
|
1176
|
+
|
|
1177
|
+
log$3("hello is %j", helloPayload);
|
|
1304
1178
|
|
|
1305
1179
|
this.call.on("error", (err) => {
|
|
1306
1180
|
log$3("error on connection stream [%s]", err.message);
|
|
@@ -1318,17 +1192,22 @@ function _doConnect(connectHello) {
|
|
|
1318
1192
|
clearTimeout(this._pingTimeoutTimer);
|
|
1319
1193
|
}
|
|
1320
1194
|
|
|
1321
|
-
// Schedule the next ping.
|
|
1322
|
-
_startPingTimer.call(this);
|
|
1323
|
-
|
|
1324
1195
|
this.emit("ping", response.ping.timestamp);
|
|
1325
1196
|
break;
|
|
1326
1197
|
}
|
|
1327
1198
|
case "acknowledge": {
|
|
1328
1199
|
if (!this._connectionId) {
|
|
1329
1200
|
this._connectionId = response.acknowledge.connection_id;
|
|
1201
|
+
|
|
1202
|
+
// Set the ping timeout interval to the value returned by the Volt. If we don't
|
|
1203
|
+
// hear from the Volt in this time, we'll assume the connection is dead.
|
|
1204
|
+
this._timeoutInterval = response.acknowledge.ping_interval;
|
|
1205
|
+
|
|
1330
1206
|
log$3("connection id is %s", this._connectionId);
|
|
1331
1207
|
this.emit("connected", this._connectionId);
|
|
1208
|
+
|
|
1209
|
+
// Schedule the next ping.
|
|
1210
|
+
_startPingTimer.call(this);
|
|
1332
1211
|
}
|
|
1333
1212
|
break;
|
|
1334
1213
|
}
|
|
@@ -1356,8 +1235,6 @@ function _doConnect(connectHello) {
|
|
|
1356
1235
|
});
|
|
1357
1236
|
|
|
1358
1237
|
this.call.start(grpcClient, helloPayload);
|
|
1359
|
-
|
|
1360
|
-
_startPingTimer.call(this);
|
|
1361
1238
|
}
|
|
1362
1239
|
|
|
1363
1240
|
function _startPingTimer() {
|
|
@@ -1384,6 +1261,8 @@ function _startPingTimer() {
|
|
|
1384
1261
|
_connectionTimeoutHandler.bind(this),
|
|
1385
1262
|
this._timeoutInterval
|
|
1386
1263
|
);
|
|
1264
|
+
|
|
1265
|
+
_startPingTimer.call(this);
|
|
1387
1266
|
}, this._pingInterval);
|
|
1388
1267
|
}
|
|
1389
1268
|
|
|
@@ -1431,13 +1310,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1431
1310
|
#voltClient = null;
|
|
1432
1311
|
#voltConnection = null;
|
|
1433
1312
|
#token = null;
|
|
1434
|
-
#encryptKey = null;
|
|
1435
|
-
#encryptIV = null;
|
|
1436
1313
|
#invokeId = null;
|
|
1437
1314
|
#payload = null;
|
|
1438
1315
|
#isJSON = false;
|
|
1439
1316
|
#methodDescriptor = null;
|
|
1440
1317
|
#methodName = null;
|
|
1318
|
+
#derivedKey = null;
|
|
1319
|
+
#started = false;
|
|
1441
1320
|
|
|
1442
1321
|
constructor(voltClient, voltConnection) {
|
|
1443
1322
|
super();
|
|
@@ -1481,51 +1360,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1481
1360
|
this.#methodDescriptor = methodDescriptor;
|
|
1482
1361
|
}
|
|
1483
1362
|
|
|
1484
|
-
|
|
1363
|
+
get started() {
|
|
1364
|
+
return this.#started;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
#keyExchange(token) {
|
|
1485
1368
|
// We don't verify the token at this point, as we don't know the public key.
|
|
1486
1369
|
// The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
|
|
1487
1370
|
this.#token = jwt__default["default"].decode(token);
|
|
1488
1371
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1372
|
+
// Store the JWT key id (kid) for use in the response.
|
|
1373
|
+
const callerPublicKeyPem = voltClientWeb.formatPEM(this.#token.k, "PUBLIC KEY");
|
|
1374
|
+
const callerKey = voltClientWeb.keyFromPem(callerPublicKeyPem, "", voltClientWeb.x25519).publicKey;
|
|
1375
|
+
|
|
1376
|
+
const ephemeralKeyPem = voltClientWeb.createEncryptionKey();
|
|
1377
|
+
const ephemeralKey = voltClientWeb.keyFromPem(ephemeralKeyPem, "", voltClientWeb.x25519);
|
|
1378
|
+
const ephemeralPublicKey = voltClientWeb.fromBase64(
|
|
1379
|
+
voltClientWeb.stripPEMHeaders(voltClientWeb.publicKeyToPem(ephemeralKey.publicKey, voltClientWeb.x25519))
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
this.#derivedKey = voltClientWeb.deriveSharedKey(
|
|
1383
|
+
ephemeralKey.privateKey,
|
|
1384
|
+
callerKey,
|
|
1385
|
+
voltClientWeb.x25519
|
|
1386
|
+
);
|
|
1500
1387
|
|
|
1501
|
-
|
|
1388
|
+
const nonce = voltClientWeb.randomBytes(16);
|
|
1389
|
+
|
|
1390
|
+
// The signed message is the encryption key and the nonce.
|
|
1391
|
+
const message = new Uint8Array(ephemeralPublicKey.length + nonce.length);
|
|
1392
|
+
message.set(ephemeralPublicKey);
|
|
1393
|
+
message.set(nonce, ephemeralPublicKey.length);
|
|
1394
|
+
|
|
1395
|
+
const signature = voltClientWeb.signBase64(
|
|
1396
|
+
this.#voltClient.credential.getKey().privateKey,
|
|
1397
|
+
message
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
const keyExchangeResponse = {
|
|
1401
|
+
invoke_id: this.#invokeId,
|
|
1402
|
+
key_exchange: {
|
|
1403
|
+
nonce,
|
|
1404
|
+
encryption_key: ephemeralPublicKey,
|
|
1405
|
+
signature: voltClientWeb.fromBase64Url(signature)
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
this.#voltConnection.send({
|
|
1410
|
+
invoke_response: keyExchangeResponse
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
return Promise.resolve(keyExchangeResponse);
|
|
1502
1414
|
}
|
|
1503
1415
|
|
|
1504
1416
|
initialise(invoke_request) {
|
|
1505
1417
|
this.#invokeId = invoke_request.invoke_id;
|
|
1506
1418
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
return this.parsePayload(invoke_request);
|
|
1511
|
-
})
|
|
1512
|
-
.catch((err) => {
|
|
1419
|
+
if (!this.#derivedKey) {
|
|
1420
|
+
// We don't have the derived key yet, so we need to perform the key exchange.
|
|
1421
|
+
return this.#keyExchange(invoke_request.token).catch((err) => {
|
|
1513
1422
|
log$2("failure initialising invocation: %s", err.message);
|
|
1514
1423
|
return Promise.reject(err);
|
|
1515
1424
|
});
|
|
1425
|
+
} else {
|
|
1426
|
+
// We already have the derived key, so we can just parse the payload.
|
|
1427
|
+
return this.parsePayload(invoke_request);
|
|
1428
|
+
}
|
|
1516
1429
|
}
|
|
1517
1430
|
|
|
1518
1431
|
parsePayload(invoke_request) {
|
|
1432
|
+
this.#started = true;
|
|
1433
|
+
|
|
1519
1434
|
return new Promise((resolve, reject) => {
|
|
1435
|
+
const encryptIV = invoke_request.iv;
|
|
1436
|
+
|
|
1520
1437
|
if (invoke_request.payload) {
|
|
1521
1438
|
let decryptedPayload;
|
|
1522
1439
|
|
|
1523
|
-
if (
|
|
1440
|
+
if (encryptIV) {
|
|
1524
1441
|
// Decrypt the actual payload using the shared key and iv.
|
|
1525
|
-
decryptedPayload =
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1442
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
1443
|
+
this.#derivedKey,
|
|
1444
|
+
encryptIV,
|
|
1445
|
+
invoke_request.payload
|
|
1529
1446
|
);
|
|
1530
1447
|
} else {
|
|
1531
1448
|
// No encryption => just use the payload.
|
|
@@ -1561,12 +1478,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1561
1478
|
|
|
1562
1479
|
this.#isJSON = true;
|
|
1563
1480
|
|
|
1564
|
-
if (this.#
|
|
1481
|
+
if (this.#derivedKey) {
|
|
1565
1482
|
// Decrypt the actual payload using the shared key and iv.
|
|
1566
|
-
decryptedPayload =
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1483
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
1484
|
+
this.#derivedKey,
|
|
1485
|
+
invoke_request.iv,
|
|
1486
|
+
Buffer.from(invoke_request.json_payload, "base64")
|
|
1570
1487
|
);
|
|
1571
1488
|
} else {
|
|
1572
1489
|
// No encryption => just use the payload.
|
|
@@ -1625,17 +1542,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1625
1542
|
});
|
|
1626
1543
|
}
|
|
1627
1544
|
|
|
1628
|
-
|
|
1545
|
+
const iv = voltClientWeb.randomBytes(16);
|
|
1546
|
+
if (this.#derivedKey) {
|
|
1629
1547
|
// Encrypt the response using the shared key and iv.
|
|
1630
|
-
responsePayload =
|
|
1631
|
-
responsePayload,
|
|
1632
|
-
this.#encryptKey,
|
|
1633
|
-
this.#encryptIV
|
|
1634
|
-
);
|
|
1548
|
+
responsePayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, responsePayload);
|
|
1635
1549
|
}
|
|
1636
1550
|
|
|
1637
1551
|
const invokeResponse = {
|
|
1638
|
-
invoke_id: this.#invokeId
|
|
1552
|
+
invoke_id: this.#invokeId,
|
|
1553
|
+
iv
|
|
1639
1554
|
};
|
|
1640
1555
|
|
|
1641
1556
|
if (this.#isJSON) {
|
|
@@ -1670,18 +1585,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1670
1585
|
endPayload = tunnelMethod.requestSerialize(endPayload);
|
|
1671
1586
|
}
|
|
1672
1587
|
|
|
1673
|
-
|
|
1588
|
+
const iv = voltClientWeb.randomBytes(16);
|
|
1589
|
+
if (this.#derivedKey) {
|
|
1674
1590
|
// Encrypt the response using the shared key and iv.
|
|
1675
|
-
endPayload =
|
|
1676
|
-
endPayload,
|
|
1677
|
-
this.#encryptKey,
|
|
1678
|
-
this.#encryptIV
|
|
1679
|
-
);
|
|
1591
|
+
endPayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, endPayload);
|
|
1680
1592
|
}
|
|
1681
1593
|
|
|
1682
1594
|
const invokeResponse = {
|
|
1683
1595
|
invoke_id: this.#invokeId,
|
|
1684
|
-
server_end: true
|
|
1596
|
+
server_end: true,
|
|
1597
|
+
iv
|
|
1685
1598
|
};
|
|
1686
1599
|
|
|
1687
1600
|
if (this.#isJSON) {
|
|
@@ -1698,7 +1611,6 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1698
1611
|
|
|
1699
1612
|
/* eslint-disable no-underscore-dangle */
|
|
1700
1613
|
|
|
1701
|
-
const { pki } = forge__default["default"];
|
|
1702
1614
|
const { filter, pick } = lodash__default["default"];
|
|
1703
1615
|
|
|
1704
1616
|
const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
|
|
@@ -1734,8 +1646,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
1734
1646
|
// This is the signing CA used by the volt.
|
|
1735
1647
|
this._credential.cache.ca = authenticateResponse.chain;
|
|
1736
1648
|
|
|
1737
|
-
//
|
|
1738
|
-
this._credential.cache.
|
|
1649
|
+
// The session id is assigned by the volt.
|
|
1650
|
+
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
1651
|
+
|
|
1652
|
+
if (!this._credential.cache.identity_did) {
|
|
1653
|
+
// Identity DID has been assigned by the volt.
|
|
1654
|
+
this._credential.cache.identity_did =
|
|
1655
|
+
authenticateResponse.identity_did;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1739
1658
|
this._credential.saveCache();
|
|
1740
1659
|
break;
|
|
1741
1660
|
}
|
|
@@ -1746,6 +1665,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
1746
1665
|
case "POLICY_DECISION_PROMPT":
|
|
1747
1666
|
case "POLICY_DECISION_PENDING": {
|
|
1748
1667
|
log$1("access pending approval - waiting...");
|
|
1668
|
+
|
|
1669
|
+
// This is the signing CA used by the volt.
|
|
1670
|
+
this._credential.cache.ca = authenticateResponse.chain;
|
|
1671
|
+
|
|
1672
|
+
// The session id is assigned by the volt.
|
|
1673
|
+
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
1674
|
+
|
|
1675
|
+
if (!this._credential.cache.identity_did) {
|
|
1676
|
+
// Identity DID has been assigned by the volt.
|
|
1677
|
+
this._credential.cache.identity_did =
|
|
1678
|
+
authenticateResponse.identity_did;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
this._credential.saveCache();
|
|
1749
1682
|
break;
|
|
1750
1683
|
}
|
|
1751
1684
|
default:
|
|
@@ -1770,8 +1703,10 @@ function findDIDDocumentService(document, serviceType) {
|
|
|
1770
1703
|
return matches;
|
|
1771
1704
|
}
|
|
1772
1705
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1706
|
+
async function authenticateInternal(
|
|
1707
|
+
sessionOnly = false,
|
|
1708
|
+
ownDID = false
|
|
1709
|
+
) {
|
|
1775
1710
|
try {
|
|
1776
1711
|
// Check if we need to resolve a Relay volt.
|
|
1777
1712
|
if (
|
|
@@ -1783,7 +1718,7 @@ async function authenticateInternal() {
|
|
|
1783
1718
|
log$1("attempting to retrieve Relay information from %s", relayURL);
|
|
1784
1719
|
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
1785
1720
|
this,
|
|
1786
|
-
`${relayURL}/
|
|
1721
|
+
`${relayURL}/discover`
|
|
1787
1722
|
);
|
|
1788
1723
|
|
|
1789
1724
|
if (this._voltConfig.relay.ca_pem) {
|
|
@@ -1808,7 +1743,9 @@ async function authenticateInternal() {
|
|
|
1808
1743
|
}
|
|
1809
1744
|
|
|
1810
1745
|
const keyPair = this._credential.getKey();
|
|
1811
|
-
const publicKeyPem =
|
|
1746
|
+
const publicKeyPem = voltClientWeb.removeCarriageReturn(
|
|
1747
|
+
voltClientWeb.publicKeyToPem(keyPair.publicKey)
|
|
1748
|
+
);
|
|
1812
1749
|
|
|
1813
1750
|
if (!this._credential.cache.bindIp) {
|
|
1814
1751
|
// Default the IP address if none is specified. This is used to set the SAN in the
|
|
@@ -1820,13 +1757,12 @@ async function authenticateInternal() {
|
|
|
1820
1757
|
|
|
1821
1758
|
// Form the request message.
|
|
1822
1759
|
const authenticateRequest = {
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
host: this._credential.cache.bindIp,
|
|
1760
|
+
client_name: this._config.client_name,
|
|
1761
|
+
host: this._credential.cache.bindIp
|
|
1826
1762
|
};
|
|
1827
1763
|
|
|
1828
1764
|
if (this._voltConfig.challenge_code) {
|
|
1829
|
-
authenticateRequest.challenge = signBase64(
|
|
1765
|
+
authenticateRequest.challenge = voltClientWeb.signBase64(
|
|
1830
1766
|
keyPair.privateKey,
|
|
1831
1767
|
this._voltConfig.challenge_code
|
|
1832
1768
|
);
|
|
@@ -1836,6 +1772,60 @@ async function authenticateInternal() {
|
|
|
1836
1772
|
);
|
|
1837
1773
|
}
|
|
1838
1774
|
|
|
1775
|
+
if (sessionOnly) {
|
|
1776
|
+
// We want to create a session-based authentication based simply on our key, rather than a
|
|
1777
|
+
// fully-blown DID-based authentication.
|
|
1778
|
+
log$1("creating session-only request");
|
|
1779
|
+
authenticateRequest.public_key = publicKeyPem;
|
|
1780
|
+
} else if (!this._credential.cache.identity_did) {
|
|
1781
|
+
if (!ownDID) {
|
|
1782
|
+
// We don't want to have to manage our own DID, so we ask the Volt to do it for us.
|
|
1783
|
+
// The Volt will create a new DID and return it to us in the response.
|
|
1784
|
+
log$1("creating DID-based request");
|
|
1785
|
+
authenticateRequest.did_public_key = publicKeyPem;
|
|
1786
|
+
} else {
|
|
1787
|
+
// We want to create and manage our own DID.
|
|
1788
|
+
const did =
|
|
1789
|
+
(this._credential.cache.identity_did = `did:volt:${uuid.v4()}`);
|
|
1790
|
+
|
|
1791
|
+
// Order of properties in the DID document is important,
|
|
1792
|
+
// they must be in alphabetical order throughout the document.
|
|
1793
|
+
// This is because the DID document is signed and the signature
|
|
1794
|
+
// is over the entire document.
|
|
1795
|
+
const didDocument = {
|
|
1796
|
+
"@context": "https://www.w3.org/ns/did/v1",
|
|
1797
|
+
authentication: [`${did}#key-1`],
|
|
1798
|
+
controller: did,
|
|
1799
|
+
description: this._config.client_name,
|
|
1800
|
+
id: did,
|
|
1801
|
+
verificationMethod: [
|
|
1802
|
+
{
|
|
1803
|
+
id: `${did}#key-1`,
|
|
1804
|
+
publicKeyPem: publicKeyPem,
|
|
1805
|
+
type: "Ed25519VerificationKey2018"
|
|
1806
|
+
}
|
|
1807
|
+
]
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
// Set the DID document with proof in the authenticate request.
|
|
1811
|
+
authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
1812
|
+
|
|
1813
|
+
// Now sign the entire DID document, including the proof and set it in the
|
|
1814
|
+
// bind request. This is required by the Volt to verify that we have the private key for the DID.
|
|
1815
|
+
authenticateRequest.did_document_signature = voltClientWeb.signBase64(
|
|
1816
|
+
keyPair.privateKey,
|
|
1817
|
+
authenticateRequest.did_document
|
|
1818
|
+
);
|
|
1819
|
+
|
|
1820
|
+
log$1("did document is %s", authenticateRequest.did_document);
|
|
1821
|
+
log$1("did signature is %s", authenticateRequest.did_document_signature);
|
|
1822
|
+
}
|
|
1823
|
+
} else {
|
|
1824
|
+
// We already have a DID cached, so re-use it.
|
|
1825
|
+
log$1("using cached DID %s", this._credential.cache.identity_did);
|
|
1826
|
+
authenticateRequest.did = this._credential.cache.identity_did;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1839
1829
|
log$1("authenticate TTL is ", this._config.bindRequestTTL);
|
|
1840
1830
|
|
|
1841
1831
|
// This will block while the request is pending.
|
|
@@ -1906,6 +1896,15 @@ function connectInternal(helloPayload) {
|
|
|
1906
1896
|
onDisconnected();
|
|
1907
1897
|
this.emit("connected", false);
|
|
1908
1898
|
}
|
|
1899
|
+
|
|
1900
|
+
if (err.message.endsWith("session invalid")) {
|
|
1901
|
+
log$1("Volt session invalid - clearing session");
|
|
1902
|
+
delete this._credential.cache.session_id;
|
|
1903
|
+
delete this._credential.cache.cert;
|
|
1904
|
+
this._credential.saveCache();
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
this.emit("connectionError", err);
|
|
1909
1908
|
});
|
|
1910
1909
|
|
|
1911
1910
|
this._voltConnection.on("evt", (evt) => {
|
|
@@ -1918,10 +1917,24 @@ function connectInternal(helloPayload) {
|
|
|
1918
1917
|
|
|
1919
1918
|
this._voltConnection.on("invoke_request", (invoke_request) => {
|
|
1920
1919
|
const invokeId = invoke_request.invoke_id;
|
|
1921
|
-
|
|
1922
|
-
|
|
1920
|
+
|
|
1921
|
+
let rpcInvocation = this._activeRPC[invokeId];
|
|
1922
|
+
|
|
1923
|
+
if (rpcInvocation) {
|
|
1924
|
+
if (rpcInvocation.started) {
|
|
1925
|
+
rpcInvocation.parsePayload(invoke_request);
|
|
1926
|
+
} else {
|
|
1927
|
+
rpcInvocation
|
|
1928
|
+
.initialise(invoke_request)
|
|
1929
|
+
.then(() => {
|
|
1930
|
+
this.emit("invoke_request", rpcInvocation);
|
|
1931
|
+
})
|
|
1932
|
+
.catch((err) => {
|
|
1933
|
+
log$1("invoke_request - error [%s]", err.message);
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1923
1936
|
} else {
|
|
1924
|
-
|
|
1937
|
+
rpcInvocation = new RpcInvocation(this, this._voltConnection);
|
|
1925
1938
|
|
|
1926
1939
|
rpcInvocation.on("end", () => {
|
|
1927
1940
|
log$1("removing active RPC [%s]", invokeId);
|
|
@@ -1930,9 +1943,11 @@ function connectInternal(helloPayload) {
|
|
|
1930
1943
|
|
|
1931
1944
|
rpcInvocation
|
|
1932
1945
|
.initialise(invoke_request)
|
|
1933
|
-
.then(() => {
|
|
1946
|
+
.then((response) => {
|
|
1934
1947
|
this._activeRPC[invokeId] = rpcInvocation;
|
|
1935
|
-
|
|
1948
|
+
if (!response.key_exchange) {
|
|
1949
|
+
this.emit("invoke_request", rpcInvocation);
|
|
1950
|
+
}
|
|
1936
1951
|
})
|
|
1937
1952
|
.catch((err) => {
|
|
1938
1953
|
log$1("invoke_request - error [%s]", err.message);
|
|
@@ -2078,6 +2093,7 @@ async function fetchVoltConfig(discovery_url) {
|
|
|
2078
2093
|
"public_key",
|
|
2079
2094
|
"fingerprint",
|
|
2080
2095
|
"address",
|
|
2096
|
+
"http_address",
|
|
2081
2097
|
"ca_pem",
|
|
2082
2098
|
"challenge_code",
|
|
2083
2099
|
"cloud",
|
|
@@ -2165,6 +2181,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2165
2181
|
/**
|
|
2166
2182
|
* Initialises a binding and connection to the volt.
|
|
2167
2183
|
* @param {string} [config] - the location of the Volt configuration file, or a configuration object
|
|
2184
|
+
* @param {object} [extras] - additional configuration properties
|
|
2168
2185
|
*/
|
|
2169
2186
|
async initialise(config, extras = {}) {
|
|
2170
2187
|
try {
|
|
@@ -2180,10 +2197,15 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2180
2197
|
configPath = path__default["default"].resolve(config);
|
|
2181
2198
|
log("Attempt to load config from file %s", configPath);
|
|
2182
2199
|
try {
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2200
|
+
if (fs__default["default"].existsSync(configPath)) {
|
|
2201
|
+
const configContents = await readFile(
|
|
2202
|
+
new URL(configPath, (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)))
|
|
2203
|
+
);
|
|
2204
|
+
configJSON = JSON.parse(configContents);
|
|
2205
|
+
} else {
|
|
2206
|
+
// There is currently no file at the given configuration path.
|
|
2207
|
+
configJSON = {};
|
|
2208
|
+
}
|
|
2187
2209
|
} catch (err) {
|
|
2188
2210
|
log("failure loading config file %s [%s]", configPath, err.message);
|
|
2189
2211
|
throw new Error(
|
|
@@ -2200,15 +2222,15 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2200
2222
|
throw new Error("config argument is required to be an object");
|
|
2201
2223
|
}
|
|
2202
2224
|
|
|
2225
|
+
this._config = { ...extras, ...configJSON };
|
|
2226
|
+
|
|
2203
2227
|
if (
|
|
2204
|
-
!
|
|
2205
|
-
typeof
|
|
2228
|
+
!this._config.client_name ||
|
|
2229
|
+
typeof this._config.client_name !== "string"
|
|
2206
2230
|
) {
|
|
2207
2231
|
throw new Error("client_name property required in config");
|
|
2208
2232
|
}
|
|
2209
2233
|
|
|
2210
|
-
this._config = { ...configJSON, ...extras };
|
|
2211
|
-
|
|
2212
2234
|
let voltDID = "";
|
|
2213
2235
|
let voltHttpAddress = "";
|
|
2214
2236
|
if (typeof this._config.volt === "string") {
|
|
@@ -2217,6 +2239,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2217
2239
|
} else {
|
|
2218
2240
|
voltHttpAddress = this._config.volt;
|
|
2219
2241
|
}
|
|
2242
|
+
delete this._config.volt;
|
|
2220
2243
|
} else if (typeof this._config.volt !== "object") {
|
|
2221
2244
|
throw new Error("configuration is missing the 'volt' object");
|
|
2222
2245
|
} else {
|
|
@@ -2231,7 +2254,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2231
2254
|
} else if (voltHttpAddress && !this._config?.volt?.id) {
|
|
2232
2255
|
const discoConfig = await fetchVoltConfig.call(
|
|
2233
2256
|
this,
|
|
2234
|
-
`${voltHttpAddress}/
|
|
2257
|
+
`${voltHttpAddress}/discover`
|
|
2235
2258
|
);
|
|
2236
2259
|
this._config = {
|
|
2237
2260
|
...this._config,
|
|
@@ -2240,8 +2263,12 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2240
2263
|
}
|
|
2241
2264
|
|
|
2242
2265
|
this._voltConfig = this._config.volt;
|
|
2243
|
-
|
|
2244
|
-
|
|
2266
|
+
if (voltDID) {
|
|
2267
|
+
this._voltConfig.did = voltDID;
|
|
2268
|
+
}
|
|
2269
|
+
if (voltHttpAddress) {
|
|
2270
|
+
this._voltConfig.http_address = voltHttpAddress;
|
|
2271
|
+
}
|
|
2245
2272
|
|
|
2246
2273
|
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
2247
2274
|
throw new Error("'id' property is missing in Volt configuration");
|
|
@@ -2289,9 +2316,12 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2289
2316
|
if (!isBound) {
|
|
2290
2317
|
// This is usually because of request being denied.
|
|
2291
2318
|
throw new Error("Volt authentication failed");
|
|
2319
|
+
} else {
|
|
2320
|
+
// Perist the configuration.
|
|
2321
|
+
this._config = this._credential.saveCache();
|
|
2292
2322
|
}
|
|
2293
2323
|
|
|
2294
|
-
return Promise.resolve();
|
|
2324
|
+
return Promise.resolve(this._config);
|
|
2295
2325
|
} catch (err) {
|
|
2296
2326
|
log("Failure starting Volt client [%s]", err.message);
|
|
2297
2327
|
throw err;
|
|
@@ -2758,6 +2788,13 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2758
2788
|
});
|
|
2759
2789
|
}
|
|
2760
2790
|
|
|
2791
|
+
SqlTransaction(request) {
|
|
2792
|
+
const grpcClient = this.getVoltAPIClient();
|
|
2793
|
+
|
|
2794
|
+
const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
|
|
2795
|
+
return call.start(grpcClient, request);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2761
2798
|
/**
|
|
2762
2799
|
* WireAPI
|
|
2763
2800
|
*/
|