@tdxvolt/volt-client-grpc 0.16.0-beta.10 → 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 +412 -456
- 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 +106 -73
- package/src/volt-client.js +33 -12
- package/src/volt-connection.js +22 -12
- package/src/volt-credential.js +37 -124
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
|
|
|
@@ -598,7 +462,7 @@ class VoltCredential {
|
|
|
598
462
|
}
|
|
599
463
|
|
|
600
464
|
get identityId() {
|
|
601
|
-
return this._cryptoCache.
|
|
465
|
+
return this._cryptoCache.identity_did;
|
|
602
466
|
}
|
|
603
467
|
|
|
604
468
|
get voltPublicKey() {
|
|
@@ -606,7 +470,7 @@ class VoltCredential {
|
|
|
606
470
|
}
|
|
607
471
|
|
|
608
472
|
get publicKey() {
|
|
609
|
-
return
|
|
473
|
+
return voltClientWeb.publicKeyToPem(this.getKey().publicKey);
|
|
610
474
|
}
|
|
611
475
|
|
|
612
476
|
get voltFingerprint() {
|
|
@@ -614,9 +478,8 @@ class VoltCredential {
|
|
|
614
478
|
}
|
|
615
479
|
|
|
616
480
|
getKey() {
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
return { privateKey, publicKey };
|
|
481
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
482
|
+
return key;
|
|
620
483
|
}
|
|
621
484
|
|
|
622
485
|
/**
|
|
@@ -629,16 +492,10 @@ class VoltCredential {
|
|
|
629
492
|
return Promise.resolve(this.getKey());
|
|
630
493
|
} else {
|
|
631
494
|
log$5("creating key");
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
return keyInfo.keypair;
|
|
637
|
-
})
|
|
638
|
-
.catch((err) => {
|
|
639
|
-
log$5("failure creating key [%s]", err.message);
|
|
640
|
-
return Promise.reject(err);
|
|
641
|
-
});
|
|
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);
|
|
642
499
|
}
|
|
643
500
|
}
|
|
644
501
|
|
|
@@ -663,55 +520,6 @@ class VoltCredential {
|
|
|
663
520
|
return !!this._cryptoCache.cert;
|
|
664
521
|
}
|
|
665
522
|
|
|
666
|
-
/**
|
|
667
|
-
* Signs the identity resource id using the private key.
|
|
668
|
-
* @param {*} audience the token audience (usually the target volt)
|
|
669
|
-
* @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
|
|
670
|
-
* @param {*} ttl time to live in seconds (default to 1 minute)
|
|
671
|
-
*/
|
|
672
|
-
getIdentityToken(audience, publicKey, tunnelling = false, ttl = 60) {
|
|
673
|
-
if (!this._cryptoCache.key) {
|
|
674
|
-
throw new Error("crypto cache invalid");
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const keyPair = this.getKey();
|
|
678
|
-
const publicKeyPem = pki$1.publicKeyToPem(keyPair.publicKey);
|
|
679
|
-
const base58Key = createPublicKeyFingerprint(publicKeyPem);
|
|
680
|
-
|
|
681
|
-
log$5("base58 key is %s", base58Key);
|
|
682
|
-
|
|
683
|
-
// Allow TTL either side of the current time.
|
|
684
|
-
// @todo - fix with server time sync on volt connection.
|
|
685
|
-
const payload = {
|
|
686
|
-
aud: audience,
|
|
687
|
-
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
688
|
-
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
689
|
-
sub: this._cryptoCache.session_id
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
let sharedKey;
|
|
693
|
-
if (tunnelling) {
|
|
694
|
-
// Initialise the Relay encryption key.
|
|
695
|
-
sharedKey = VoltCredential.aesCreateKey();
|
|
696
|
-
|
|
697
|
-
// Encrypt the key details using the target Volt public key and include this in the JWT payload.
|
|
698
|
-
payload.sk = VoltCredential.rsaEncrypt(publicKey, sharedKey.key);
|
|
699
|
-
payload.iv = VoltCredential.rsaEncrypt(publicKey, sharedKey.iv);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// Sign synchronously.
|
|
703
|
-
const token = jwt__default["default"].sign(payload, this._cryptoCache.key, {
|
|
704
|
-
algorithm: rs256Algorithm,
|
|
705
|
-
keyid: base58Key
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
// Return the token along with any encryption key details.
|
|
709
|
-
return {
|
|
710
|
-
token,
|
|
711
|
-
sharedKey
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
|
|
715
523
|
/**
|
|
716
524
|
* Creates the metadata required for a grpc call, including the Volt authentication token
|
|
717
525
|
* and optionally tunnel encryption parameters.
|
|
@@ -722,9 +530,12 @@ class VoltCredential {
|
|
|
722
530
|
* @returns
|
|
723
531
|
*/
|
|
724
532
|
getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
|
|
725
|
-
const
|
|
533
|
+
const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
|
|
534
|
+
|
|
535
|
+
const identityToken = voltClientWeb.getIdentityToken(
|
|
536
|
+
this.sessionId,
|
|
537
|
+
key,
|
|
726
538
|
audience || this._voltConfig.id,
|
|
727
|
-
publicKey,
|
|
728
539
|
tunnelling,
|
|
729
540
|
ttl
|
|
730
541
|
);
|
|
@@ -734,48 +545,18 @@ class VoltCredential {
|
|
|
734
545
|
}
|
|
735
546
|
|
|
736
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
|
+
|
|
737
555
|
if (this._persistCache) {
|
|
738
|
-
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
739
|
-
if (this._config.p) {
|
|
740
|
-
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
741
|
-
const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
|
|
742
|
-
clone.credential.key = pki$1.encryptRsaPrivateKey(
|
|
743
|
-
privateKey,
|
|
744
|
-
this._config.p
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
556
|
fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
|
|
748
557
|
}
|
|
749
|
-
}
|
|
750
558
|
|
|
751
|
-
|
|
752
|
-
// Generate random key for the AES-256 algorithm (32 bytes = 256 bits).
|
|
753
|
-
const key = crypto__default["default"].randomBytes(32);
|
|
754
|
-
const iv = crypto__default["default"].randomBytes(16);
|
|
755
|
-
return { key, iv };
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
static aesEncrypt(buffer, key, iv) {
|
|
759
|
-
const cipher = crypto__default["default"].createCipheriv(aesAlgorithm, key, iv);
|
|
760
|
-
const crypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
761
|
-
return crypted;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
static aesDecrypt(buffer, key, iv) {
|
|
765
|
-
const cipher = crypto__default["default"].createDecipheriv(aesAlgorithm, key, iv);
|
|
766
|
-
const decrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
767
|
-
return decrypted;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
static rsaEncrypt(key, buffer) {
|
|
771
|
-
const cipher = crypto__default["default"].publicEncrypt(key, buffer);
|
|
772
|
-
return cipher.toString("base64url");
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
static rsaDecrypt(key, buffer) {
|
|
776
|
-
const keyObj = crypto__default["default"].createPrivateKey(key);
|
|
777
|
-
const cipher = Buffer.from(buffer, "base64url");
|
|
778
|
-
return Buffer.from(crypto__default["default"].privateDecrypt(keyObj, cipher));
|
|
559
|
+
return clone;
|
|
779
560
|
}
|
|
780
561
|
}
|
|
781
562
|
|
|
@@ -918,6 +699,21 @@ function _prepareInvokeRequest(
|
|
|
918
699
|
this._service?.service_description.host_type ===
|
|
919
700
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
920
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
|
+
|
|
921
717
|
// Use the service's host client id and public key if the service is relayed.
|
|
922
718
|
const methodToken = this._voltClient.credential.getIdentityMetadata(
|
|
923
719
|
this._voltClient.grpc,
|
|
@@ -925,17 +721,52 @@ function _prepareInvokeRequest(
|
|
|
925
721
|
this._voltClient.voltConfig.id,
|
|
926
722
|
this._service?.service_description.host_public_key ||
|
|
927
723
|
this._voltClient.credential.voltPublicKey,
|
|
928
|
-
|
|
724
|
+
relaying
|
|
929
725
|
);
|
|
930
726
|
|
|
931
727
|
let invokeRequest;
|
|
932
728
|
let callMethod = method;
|
|
933
729
|
|
|
934
|
-
if (
|
|
730
|
+
if (relaying) {
|
|
935
731
|
//
|
|
936
732
|
// If the volt connection is via a relay (or the target service is relayed),
|
|
937
733
|
// we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
|
|
938
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
|
+
|
|
939
770
|
let requestPayload;
|
|
940
771
|
|
|
941
772
|
// Serialise the rpc request.
|
|
@@ -955,45 +786,24 @@ function _prepareInvokeRequest(
|
|
|
955
786
|
method_invoke: {
|
|
956
787
|
method_name: this._grpcClient[method].path,
|
|
957
788
|
method_type: methodType,
|
|
958
|
-
token: methodToken.token,
|
|
959
789
|
request: requestPayload
|
|
960
790
|
}
|
|
961
791
|
};
|
|
962
792
|
|
|
963
|
-
let targetDID = [];
|
|
964
793
|
let invokePayload;
|
|
965
|
-
|
|
966
|
-
// When sending via a relay we need to encrypt the payload.
|
|
967
|
-
invokePayload = VoltCredential.aesEncrypt(
|
|
968
|
-
tunnelMethod.responseSerialize(tunnelResp),
|
|
969
|
-
methodToken.sharedKey.key,
|
|
970
|
-
methodToken.sharedKey.iv
|
|
971
|
-
);
|
|
972
|
-
} else {
|
|
973
|
-
invokePayload = tunnelMethod.responseSerialize(tunnelResp);
|
|
974
|
-
}
|
|
794
|
+
const iv = voltClientWeb.aesCreateKey().iv;
|
|
975
795
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
log$4("target service is relayed");
|
|
983
|
-
targetDID.push(this._service.service_description.host_client_id);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
log$4("target is %j", targetDID);
|
|
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
|
+
);
|
|
987
802
|
|
|
988
803
|
invokeRequest = {
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
payload: invokePayload,
|
|
992
|
-
target_service_id: isServiceRelayed ? this._service.id : undefined
|
|
804
|
+
iv,
|
|
805
|
+
payload: invokePayload
|
|
993
806
|
};
|
|
994
|
-
|
|
995
|
-
// Replace the target method with a call to Invoke on the relay Volt.
|
|
996
|
-
callMethod = "Invoke";
|
|
997
807
|
} else {
|
|
998
808
|
invokeRequest = request;
|
|
999
809
|
}
|
|
@@ -1013,7 +823,10 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
1013
823
|
this._service?.service_description.host_type ===
|
|
1014
824
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
1015
825
|
|
|
826
|
+
let iv;
|
|
1016
827
|
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
828
|
+
iv = voltClientWeb.aesCreateKey().iv;
|
|
829
|
+
|
|
1017
830
|
const requestPayload =
|
|
1018
831
|
this._grpcClient[invokeInfo.methodName].requestSerialize(request);
|
|
1019
832
|
|
|
@@ -1026,13 +839,14 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
1026
839
|
}
|
|
1027
840
|
};
|
|
1028
841
|
|
|
1029
|
-
const invokePayload =
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
842
|
+
const invokePayload = voltClientWeb.aesEncrypt(
|
|
843
|
+
this._encryptionKey,
|
|
844
|
+
iv,
|
|
845
|
+
tunnelMethod.responseSerialize(tunnelResp)
|
|
1033
846
|
);
|
|
1034
847
|
|
|
1035
848
|
payloadRequest = {
|
|
849
|
+
iv,
|
|
1036
850
|
payload: invokePayload
|
|
1037
851
|
};
|
|
1038
852
|
} else {
|
|
@@ -1051,13 +865,38 @@ function _parseResponse(method, meta, response) {
|
|
|
1051
865
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
1052
866
|
|
|
1053
867
|
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
1054
|
-
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) {
|
|
1055
894
|
let decryptedPayload;
|
|
1056
|
-
if (
|
|
1057
|
-
decryptedPayload =
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
895
|
+
if (this._encryptionKey) {
|
|
896
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
897
|
+
this._encryptionKey,
|
|
898
|
+
response.iv,
|
|
899
|
+
response.payload
|
|
1061
900
|
);
|
|
1062
901
|
} else {
|
|
1063
902
|
decryptedPayload = response.payload;
|
|
@@ -1097,11 +936,11 @@ function _parseResponse(method, meta, response) {
|
|
|
1097
936
|
invokeResponse.methodId = response.invoke_id;
|
|
1098
937
|
log$4("started method %d for %s", invokeResponse.methodId, method);
|
|
1099
938
|
}
|
|
1100
|
-
} else if (this._voltClient.isRemote &&
|
|
1101
|
-
const decryptedPayload =
|
|
1102
|
-
|
|
1103
|
-
meta.sharedKey.
|
|
1104
|
-
|
|
939
|
+
} else if (this._voltClient.isRemote && this._encryptionKey) {
|
|
940
|
+
const decryptedPayload = voltClientWeb.aesDecrypt(
|
|
941
|
+
this._encryptionKey,
|
|
942
|
+
meta.sharedKey.iv,
|
|
943
|
+
response
|
|
1105
944
|
);
|
|
1106
945
|
invokeResponse = {
|
|
1107
946
|
payload:
|
|
@@ -1122,6 +961,9 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1122
961
|
this._call = null;
|
|
1123
962
|
this._voltClient = voltClient;
|
|
1124
963
|
this._service = service;
|
|
964
|
+
this._keyExchangePending = false;
|
|
965
|
+
this._pendingRequests = [];
|
|
966
|
+
this._encryptionKey = null;
|
|
1125
967
|
}
|
|
1126
968
|
|
|
1127
969
|
start(grpcClient, request) {
|
|
@@ -1150,7 +992,7 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1150
992
|
}
|
|
1151
993
|
|
|
1152
994
|
this._call = callClient[this._initialRequest.method](
|
|
1153
|
-
this._initialRequest
|
|
995
|
+
this._initialRequest?.meta?.metadata
|
|
1154
996
|
);
|
|
1155
997
|
|
|
1156
998
|
this._call.on("data", (streamResponse) => {
|
|
@@ -1161,7 +1003,25 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1161
1003
|
this._initialRequest.meta,
|
|
1162
1004
|
streamResponse
|
|
1163
1005
|
);
|
|
1164
|
-
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) {
|
|
1165
1025
|
// Interpret a non-empty status message as an error.
|
|
1166
1026
|
if (parsedResponse.payload?.status?.message) {
|
|
1167
1027
|
this.emit(
|
|
@@ -1236,7 +1096,7 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1236
1096
|
if (!this._call) {
|
|
1237
1097
|
log$4("ERROR - call not initialised");
|
|
1238
1098
|
throw new Error("call not initialised");
|
|
1239
|
-
} else if (this._initialRequest) {
|
|
1099
|
+
} else if (this._initialRequest && !this._keyExchangePending) {
|
|
1240
1100
|
const payloadRequest = _preparePayloadRequest.call(
|
|
1241
1101
|
this,
|
|
1242
1102
|
request,
|
|
@@ -1244,6 +1104,8 @@ class GRPCCall extends EventEmitter__default["default"] {
|
|
|
1244
1104
|
);
|
|
1245
1105
|
return this._call.write(payloadRequest);
|
|
1246
1106
|
} else {
|
|
1107
|
+
this._keyExchangePending = false;
|
|
1108
|
+
|
|
1247
1109
|
this._initialRequest = _prepareInvokeRequest.call(
|
|
1248
1110
|
this,
|
|
1249
1111
|
request,
|
|
@@ -1301,13 +1163,18 @@ function _doConnect(connectHello) {
|
|
|
1301
1163
|
|
|
1302
1164
|
const grpcClient = this._voltClient.getVoltAPIClient();
|
|
1303
1165
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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);
|
|
1311
1178
|
|
|
1312
1179
|
this.call.on("error", (err) => {
|
|
1313
1180
|
log$3("error on connection stream [%s]", err.message);
|
|
@@ -1325,17 +1192,22 @@ function _doConnect(connectHello) {
|
|
|
1325
1192
|
clearTimeout(this._pingTimeoutTimer);
|
|
1326
1193
|
}
|
|
1327
1194
|
|
|
1328
|
-
// Schedule the next ping.
|
|
1329
|
-
_startPingTimer.call(this);
|
|
1330
|
-
|
|
1331
1195
|
this.emit("ping", response.ping.timestamp);
|
|
1332
1196
|
break;
|
|
1333
1197
|
}
|
|
1334
1198
|
case "acknowledge": {
|
|
1335
1199
|
if (!this._connectionId) {
|
|
1336
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
|
+
|
|
1337
1206
|
log$3("connection id is %s", this._connectionId);
|
|
1338
1207
|
this.emit("connected", this._connectionId);
|
|
1208
|
+
|
|
1209
|
+
// Schedule the next ping.
|
|
1210
|
+
_startPingTimer.call(this);
|
|
1339
1211
|
}
|
|
1340
1212
|
break;
|
|
1341
1213
|
}
|
|
@@ -1363,8 +1235,6 @@ function _doConnect(connectHello) {
|
|
|
1363
1235
|
});
|
|
1364
1236
|
|
|
1365
1237
|
this.call.start(grpcClient, helloPayload);
|
|
1366
|
-
|
|
1367
|
-
_startPingTimer.call(this);
|
|
1368
1238
|
}
|
|
1369
1239
|
|
|
1370
1240
|
function _startPingTimer() {
|
|
@@ -1391,6 +1261,8 @@ function _startPingTimer() {
|
|
|
1391
1261
|
_connectionTimeoutHandler.bind(this),
|
|
1392
1262
|
this._timeoutInterval
|
|
1393
1263
|
);
|
|
1264
|
+
|
|
1265
|
+
_startPingTimer.call(this);
|
|
1394
1266
|
}, this._pingInterval);
|
|
1395
1267
|
}
|
|
1396
1268
|
|
|
@@ -1438,13 +1310,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1438
1310
|
#voltClient = null;
|
|
1439
1311
|
#voltConnection = null;
|
|
1440
1312
|
#token = null;
|
|
1441
|
-
#encryptKey = null;
|
|
1442
|
-
#encryptIV = null;
|
|
1443
1313
|
#invokeId = null;
|
|
1444
1314
|
#payload = null;
|
|
1445
1315
|
#isJSON = false;
|
|
1446
1316
|
#methodDescriptor = null;
|
|
1447
1317
|
#methodName = null;
|
|
1318
|
+
#derivedKey = null;
|
|
1319
|
+
#started = false;
|
|
1448
1320
|
|
|
1449
1321
|
constructor(voltClient, voltConnection) {
|
|
1450
1322
|
super();
|
|
@@ -1488,51 +1360,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1488
1360
|
this.#methodDescriptor = methodDescriptor;
|
|
1489
1361
|
}
|
|
1490
1362
|
|
|
1491
|
-
|
|
1363
|
+
get started() {
|
|
1364
|
+
return this.#started;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
#keyExchange(token) {
|
|
1492
1368
|
// We don't verify the token at this point, as we don't know the public key.
|
|
1493
1369
|
// The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
|
|
1494
1370
|
this.#token = jwt__default["default"].decode(token);
|
|
1495
1371
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
+
);
|
|
1387
|
+
|
|
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);
|
|
1507
1394
|
|
|
1508
|
-
|
|
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);
|
|
1509
1414
|
}
|
|
1510
1415
|
|
|
1511
1416
|
initialise(invoke_request) {
|
|
1512
1417
|
this.#invokeId = invoke_request.invoke_id;
|
|
1513
1418
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
return this.parsePayload(invoke_request);
|
|
1518
|
-
})
|
|
1519
|
-
.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) => {
|
|
1520
1422
|
log$2("failure initialising invocation: %s", err.message);
|
|
1521
1423
|
return Promise.reject(err);
|
|
1522
1424
|
});
|
|
1425
|
+
} else {
|
|
1426
|
+
// We already have the derived key, so we can just parse the payload.
|
|
1427
|
+
return this.parsePayload(invoke_request);
|
|
1428
|
+
}
|
|
1523
1429
|
}
|
|
1524
1430
|
|
|
1525
1431
|
parsePayload(invoke_request) {
|
|
1432
|
+
this.#started = true;
|
|
1433
|
+
|
|
1526
1434
|
return new Promise((resolve, reject) => {
|
|
1435
|
+
const encryptIV = invoke_request.iv;
|
|
1436
|
+
|
|
1527
1437
|
if (invoke_request.payload) {
|
|
1528
1438
|
let decryptedPayload;
|
|
1529
1439
|
|
|
1530
|
-
if (
|
|
1440
|
+
if (encryptIV) {
|
|
1531
1441
|
// Decrypt the actual payload using the shared key and iv.
|
|
1532
|
-
decryptedPayload =
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1442
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
1443
|
+
this.#derivedKey,
|
|
1444
|
+
encryptIV,
|
|
1445
|
+
invoke_request.payload
|
|
1536
1446
|
);
|
|
1537
1447
|
} else {
|
|
1538
1448
|
// No encryption => just use the payload.
|
|
@@ -1568,12 +1478,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1568
1478
|
|
|
1569
1479
|
this.#isJSON = true;
|
|
1570
1480
|
|
|
1571
|
-
if (this.#
|
|
1481
|
+
if (this.#derivedKey) {
|
|
1572
1482
|
// Decrypt the actual payload using the shared key and iv.
|
|
1573
|
-
decryptedPayload =
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1483
|
+
decryptedPayload = voltClientWeb.aesDecrypt(
|
|
1484
|
+
this.#derivedKey,
|
|
1485
|
+
invoke_request.iv,
|
|
1486
|
+
Buffer.from(invoke_request.json_payload, "base64")
|
|
1577
1487
|
);
|
|
1578
1488
|
} else {
|
|
1579
1489
|
// No encryption => just use the payload.
|
|
@@ -1632,17 +1542,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1632
1542
|
});
|
|
1633
1543
|
}
|
|
1634
1544
|
|
|
1635
|
-
|
|
1545
|
+
const iv = voltClientWeb.randomBytes(16);
|
|
1546
|
+
if (this.#derivedKey) {
|
|
1636
1547
|
// Encrypt the response using the shared key and iv.
|
|
1637
|
-
responsePayload =
|
|
1638
|
-
responsePayload,
|
|
1639
|
-
this.#encryptKey,
|
|
1640
|
-
this.#encryptIV
|
|
1641
|
-
);
|
|
1548
|
+
responsePayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, responsePayload);
|
|
1642
1549
|
}
|
|
1643
1550
|
|
|
1644
1551
|
const invokeResponse = {
|
|
1645
|
-
invoke_id: this.#invokeId
|
|
1552
|
+
invoke_id: this.#invokeId,
|
|
1553
|
+
iv
|
|
1646
1554
|
};
|
|
1647
1555
|
|
|
1648
1556
|
if (this.#isJSON) {
|
|
@@ -1677,18 +1585,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1677
1585
|
endPayload = tunnelMethod.requestSerialize(endPayload);
|
|
1678
1586
|
}
|
|
1679
1587
|
|
|
1680
|
-
|
|
1588
|
+
const iv = voltClientWeb.randomBytes(16);
|
|
1589
|
+
if (this.#derivedKey) {
|
|
1681
1590
|
// Encrypt the response using the shared key and iv.
|
|
1682
|
-
endPayload =
|
|
1683
|
-
endPayload,
|
|
1684
|
-
this.#encryptKey,
|
|
1685
|
-
this.#encryptIV
|
|
1686
|
-
);
|
|
1591
|
+
endPayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, endPayload);
|
|
1687
1592
|
}
|
|
1688
1593
|
|
|
1689
1594
|
const invokeResponse = {
|
|
1690
1595
|
invoke_id: this.#invokeId,
|
|
1691
|
-
server_end: true
|
|
1596
|
+
server_end: true,
|
|
1597
|
+
iv
|
|
1692
1598
|
};
|
|
1693
1599
|
|
|
1694
1600
|
if (this.#isJSON) {
|
|
@@ -1705,7 +1611,6 @@ class RpcInvocation extends EventEmitter__default["default"] {
|
|
|
1705
1611
|
|
|
1706
1612
|
/* eslint-disable no-underscore-dangle */
|
|
1707
1613
|
|
|
1708
|
-
const { pki } = forge__default["default"];
|
|
1709
1614
|
const { filter, pick } = lodash__default["default"];
|
|
1710
1615
|
|
|
1711
1616
|
const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
|
|
@@ -1741,9 +1646,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
1741
1646
|
// This is the signing CA used by the volt.
|
|
1742
1647
|
this._credential.cache.ca = authenticateResponse.chain;
|
|
1743
1648
|
|
|
1744
|
-
//
|
|
1649
|
+
// The session id is assigned by the volt.
|
|
1745
1650
|
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
1746
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
|
+
|
|
1747
1658
|
this._credential.saveCache();
|
|
1748
1659
|
break;
|
|
1749
1660
|
}
|
|
@@ -1754,6 +1665,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
1754
1665
|
case "POLICY_DECISION_PROMPT":
|
|
1755
1666
|
case "POLICY_DECISION_PENDING": {
|
|
1756
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();
|
|
1757
1682
|
break;
|
|
1758
1683
|
}
|
|
1759
1684
|
default:
|
|
@@ -1778,7 +1703,10 @@ function findDIDDocumentService(document, serviceType) {
|
|
|
1778
1703
|
return matches;
|
|
1779
1704
|
}
|
|
1780
1705
|
|
|
1781
|
-
async function authenticateInternal(
|
|
1706
|
+
async function authenticateInternal(
|
|
1707
|
+
sessionOnly = false,
|
|
1708
|
+
ownDID = false
|
|
1709
|
+
) {
|
|
1782
1710
|
try {
|
|
1783
1711
|
// Check if we need to resolve a Relay volt.
|
|
1784
1712
|
if (
|
|
@@ -1790,7 +1718,7 @@ async function authenticateInternal(useDID = true) {
|
|
|
1790
1718
|
log$1("attempting to retrieve Relay information from %s", relayURL);
|
|
1791
1719
|
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
1792
1720
|
this,
|
|
1793
|
-
`${relayURL}/
|
|
1721
|
+
`${relayURL}/discover`
|
|
1794
1722
|
);
|
|
1795
1723
|
|
|
1796
1724
|
if (this._voltConfig.relay.ca_pem) {
|
|
@@ -1815,8 +1743,8 @@ async function authenticateInternal(useDID = true) {
|
|
|
1815
1743
|
}
|
|
1816
1744
|
|
|
1817
1745
|
const keyPair = this._credential.getKey();
|
|
1818
|
-
const publicKeyPem = removeCarriageReturn(
|
|
1819
|
-
|
|
1746
|
+
const publicKeyPem = voltClientWeb.removeCarriageReturn(
|
|
1747
|
+
voltClientWeb.publicKeyToPem(keyPair.publicKey)
|
|
1820
1748
|
);
|
|
1821
1749
|
|
|
1822
1750
|
if (!this._credential.cache.bindIp) {
|
|
@@ -1829,12 +1757,12 @@ async function authenticateInternal(useDID = true) {
|
|
|
1829
1757
|
|
|
1830
1758
|
// Form the request message.
|
|
1831
1759
|
const authenticateRequest = {
|
|
1832
|
-
|
|
1760
|
+
client_name: this._config.client_name,
|
|
1833
1761
|
host: this._credential.cache.bindIp
|
|
1834
1762
|
};
|
|
1835
1763
|
|
|
1836
1764
|
if (this._voltConfig.challenge_code) {
|
|
1837
|
-
authenticateRequest.challenge = signBase64(
|
|
1765
|
+
authenticateRequest.challenge = voltClientWeb.signBase64(
|
|
1838
1766
|
keyPair.privateKey,
|
|
1839
1767
|
this._voltConfig.challenge_code
|
|
1840
1768
|
);
|
|
@@ -1844,68 +1772,58 @@ async function authenticateInternal(useDID = true) {
|
|
|
1844
1772
|
);
|
|
1845
1773
|
}
|
|
1846
1774
|
|
|
1847
|
-
if (
|
|
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");
|
|
1848
1779
|
authenticateRequest.public_key = publicKeyPem;
|
|
1849
|
-
} else if (!this._credential.cache.
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
// Sign the DID document.
|
|
1879
|
-
const documentDigest = signBase64(
|
|
1880
|
-
keyPair.privateKey,
|
|
1881
|
-
signDocument
|
|
1882
|
-
);
|
|
1883
|
-
|
|
1884
|
-
const proofSignature = jwsSignature(documentDigest);
|
|
1885
|
-
|
|
1886
|
-
// Create the DID document proof.
|
|
1887
|
-
didDocument.proof = {
|
|
1888
|
-
created: new Date().toISOString(),
|
|
1889
|
-
jws: proofSignature,
|
|
1890
|
-
proofPurpose: "assertionMethod",
|
|
1891
|
-
type: "RsaSignature2018",
|
|
1892
|
-
verificationMethod: `${did}#key-1`
|
|
1893
|
-
};
|
|
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
|
+
};
|
|
1894
1809
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1810
|
+
// Set the DID document with proof in the authenticate request.
|
|
1811
|
+
authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
1897
1812
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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
|
+
);
|
|
1904
1819
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1820
|
+
log$1("did document is %s", authenticateRequest.did_document);
|
|
1821
|
+
log$1("did signature is %s", authenticateRequest.did_document_signature);
|
|
1822
|
+
}
|
|
1907
1823
|
} else {
|
|
1908
|
-
|
|
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;
|
|
1909
1827
|
}
|
|
1910
1828
|
|
|
1911
1829
|
log$1("authenticate TTL is ", this._config.bindRequestTTL);
|
|
@@ -1999,10 +1917,24 @@ function connectInternal(helloPayload) {
|
|
|
1999
1917
|
|
|
2000
1918
|
this._voltConnection.on("invoke_request", (invoke_request) => {
|
|
2001
1919
|
const invokeId = invoke_request.invoke_id;
|
|
2002
|
-
|
|
2003
|
-
|
|
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
|
+
}
|
|
2004
1936
|
} else {
|
|
2005
|
-
|
|
1937
|
+
rpcInvocation = new RpcInvocation(this, this._voltConnection);
|
|
2006
1938
|
|
|
2007
1939
|
rpcInvocation.on("end", () => {
|
|
2008
1940
|
log$1("removing active RPC [%s]", invokeId);
|
|
@@ -2011,9 +1943,11 @@ function connectInternal(helloPayload) {
|
|
|
2011
1943
|
|
|
2012
1944
|
rpcInvocation
|
|
2013
1945
|
.initialise(invoke_request)
|
|
2014
|
-
.then(() => {
|
|
1946
|
+
.then((response) => {
|
|
2015
1947
|
this._activeRPC[invokeId] = rpcInvocation;
|
|
2016
|
-
|
|
1948
|
+
if (!response.key_exchange) {
|
|
1949
|
+
this.emit("invoke_request", rpcInvocation);
|
|
1950
|
+
}
|
|
2017
1951
|
})
|
|
2018
1952
|
.catch((err) => {
|
|
2019
1953
|
log$1("invoke_request - error [%s]", err.message);
|
|
@@ -2159,6 +2093,7 @@ async function fetchVoltConfig(discovery_url) {
|
|
|
2159
2093
|
"public_key",
|
|
2160
2094
|
"fingerprint",
|
|
2161
2095
|
"address",
|
|
2096
|
+
"http_address",
|
|
2162
2097
|
"ca_pem",
|
|
2163
2098
|
"challenge_code",
|
|
2164
2099
|
"cloud",
|
|
@@ -2246,6 +2181,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2246
2181
|
/**
|
|
2247
2182
|
* Initialises a binding and connection to the volt.
|
|
2248
2183
|
* @param {string} [config] - the location of the Volt configuration file, or a configuration object
|
|
2184
|
+
* @param {object} [extras] - additional configuration properties
|
|
2249
2185
|
*/
|
|
2250
2186
|
async initialise(config, extras = {}) {
|
|
2251
2187
|
try {
|
|
@@ -2261,10 +2197,15 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2261
2197
|
configPath = path__default["default"].resolve(config);
|
|
2262
2198
|
log("Attempt to load config from file %s", configPath);
|
|
2263
2199
|
try {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
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
|
+
}
|
|
2268
2209
|
} catch (err) {
|
|
2269
2210
|
log("failure loading config file %s [%s]", configPath, err.message);
|
|
2270
2211
|
throw new Error(
|
|
@@ -2281,15 +2222,15 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2281
2222
|
throw new Error("config argument is required to be an object");
|
|
2282
2223
|
}
|
|
2283
2224
|
|
|
2225
|
+
this._config = { ...extras, ...configJSON };
|
|
2226
|
+
|
|
2284
2227
|
if (
|
|
2285
|
-
!
|
|
2286
|
-
typeof
|
|
2228
|
+
!this._config.client_name ||
|
|
2229
|
+
typeof this._config.client_name !== "string"
|
|
2287
2230
|
) {
|
|
2288
2231
|
throw new Error("client_name property required in config");
|
|
2289
2232
|
}
|
|
2290
2233
|
|
|
2291
|
-
this._config = { ...configJSON, ...extras };
|
|
2292
|
-
|
|
2293
2234
|
let voltDID = "";
|
|
2294
2235
|
let voltHttpAddress = "";
|
|
2295
2236
|
if (typeof this._config.volt === "string") {
|
|
@@ -2298,6 +2239,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2298
2239
|
} else {
|
|
2299
2240
|
voltHttpAddress = this._config.volt;
|
|
2300
2241
|
}
|
|
2242
|
+
delete this._config.volt;
|
|
2301
2243
|
} else if (typeof this._config.volt !== "object") {
|
|
2302
2244
|
throw new Error("configuration is missing the 'volt' object");
|
|
2303
2245
|
} else {
|
|
@@ -2312,7 +2254,7 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2312
2254
|
} else if (voltHttpAddress && !this._config?.volt?.id) {
|
|
2313
2255
|
const discoConfig = await fetchVoltConfig.call(
|
|
2314
2256
|
this,
|
|
2315
|
-
`${voltHttpAddress}/
|
|
2257
|
+
`${voltHttpAddress}/discover`
|
|
2316
2258
|
);
|
|
2317
2259
|
this._config = {
|
|
2318
2260
|
...this._config,
|
|
@@ -2321,8 +2263,12 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2321
2263
|
}
|
|
2322
2264
|
|
|
2323
2265
|
this._voltConfig = this._config.volt;
|
|
2324
|
-
|
|
2325
|
-
|
|
2266
|
+
if (voltDID) {
|
|
2267
|
+
this._voltConfig.did = voltDID;
|
|
2268
|
+
}
|
|
2269
|
+
if (voltHttpAddress) {
|
|
2270
|
+
this._voltConfig.http_address = voltHttpAddress;
|
|
2271
|
+
}
|
|
2326
2272
|
|
|
2327
2273
|
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
2328
2274
|
throw new Error("'id' property is missing in Volt configuration");
|
|
@@ -2370,9 +2316,12 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2370
2316
|
if (!isBound) {
|
|
2371
2317
|
// This is usually because of request being denied.
|
|
2372
2318
|
throw new Error("Volt authentication failed");
|
|
2319
|
+
} else {
|
|
2320
|
+
// Perist the configuration.
|
|
2321
|
+
this._config = this._credential.saveCache();
|
|
2373
2322
|
}
|
|
2374
2323
|
|
|
2375
|
-
return Promise.resolve();
|
|
2324
|
+
return Promise.resolve(this._config);
|
|
2376
2325
|
} catch (err) {
|
|
2377
2326
|
log("Failure starting Volt client [%s]", err.message);
|
|
2378
2327
|
throw err;
|
|
@@ -2839,6 +2788,13 @@ class VoltClient extends EventEmitter__default["default"] {
|
|
|
2839
2788
|
});
|
|
2840
2789
|
}
|
|
2841
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
|
+
|
|
2842
2798
|
/**
|
|
2843
2799
|
* WireAPI
|
|
2844
2800
|
*/
|