@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/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 jwt = require('jsonwebtoken');
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: "tdx-token",
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: "cloud.tdxvolt.com",
55
+ defaultDIDHostName: "tdxvolt.com",
61
56
  didServiceType: {
62
- voltConfig: "tdx-volt-config"
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 privateKeyToPem = (keyPair) => {
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] !== "tdx") {
143
+ if (did[1] !== "volt") {
199
144
  return "";
200
145
  }
201
146
 
202
- let hostName;
203
- let didGUID;
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
- privateKeyToPem: privateKeyToPem,
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 ca = credentials.config?.volt?.relay?.ca_pem || credentials.cache.ca;
311
- const cert = credentials.cache.cloud_cert || credentials.cache.cert;
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
- // Simply connect using the CA certificate.
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 = pki$1.decryptRsaPrivateKey(
558
- this._cryptoCache.key,
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
- this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
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 = forge__default["default"].pki.publicKeyToPem(voltCert.publicKey);
578
- this._voltFingerprint = createPublicKeyFingerprint(
579
- this._voltPublicKey
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 clientId() {
593
- return this._cryptoCache.client_id;
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 pki$1.publicKeyToPem(this.getKey().publicKey);
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 privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
610
- const publicKey = pki$1.rsa.setPublicKey(privateKey.n, privateKey.e);
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
- return createRSAKey()
625
- .then((keyInfo) => {
626
- log$5("successfully created key");
627
- this._cryptoCache.key = keyInfo.pem;
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 identityToken = this.getIdentityToken(
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
- static aesEncrypt(buffer, key, iv) {
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
- true
724
+ relaying
921
725
  );
922
726
 
923
727
  let invokeRequest;
924
728
  let callMethod = method;
925
729
 
926
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
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
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
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
- 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
+ );
979
802
 
980
803
  invokeRequest = {
981
- target_did: targetDID,
982
- token: methodToken.token,
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 = VoltCredential.aesEncrypt(
1022
- tunnelMethod.responseSerialize(tunnelResp),
1023
- invokeInfo.meta.sharedKey.key,
1024
- invokeInfo.meta.sharedKey.iv
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.payload) {
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 (meta.sharedKey.key) {
1049
- decryptedPayload = VoltCredential.aesDecrypt(
1050
- response.payload,
1051
- meta.sharedKey.key,
1052
- meta.sharedKey.iv
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 && meta.sharedKey.key) {
1093
- const decryptedPayload = VoltCredential.aesDecrypt(
1094
- response,
1095
- meta.sharedKey.key,
1096
- meta.sharedKey.iv
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.meta.metadata
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.payload) {
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
- !this._dying &&
1270
- (immediateReconnect || (this._autoRetry && !this._reconnectTimer))
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
- let helloPayload = connectHello;
1298
- if (!helloPayload) {
1299
- log$3("defaulting hello payload");
1300
- helloPayload = {
1301
- hello: {}
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
- #decodeToken(token) {
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
- if (this.#token.sk) {
1490
- // Decrypt the shared key and iv using the private key.
1491
- this.#encryptKey = VoltCredential.rsaDecrypt(
1492
- this.#voltClient.credential.cache.key,
1493
- this.#token.sk
1494
- );
1495
- this.#encryptIV = VoltCredential.rsaDecrypt(
1496
- this.#voltClient.credential.cache.key,
1497
- this.#token.iv
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
- return Promise.resolve(this.#token);
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
- return this.#decodeToken(invoke_request.token)
1508
- .then(() => {
1509
- // Parse the initial payload.
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 (this.#encryptKey) {
1440
+ if (encryptIV) {
1524
1441
  // Decrypt the actual payload using the shared key and iv.
1525
- decryptedPayload = VoltCredential.aesDecrypt(
1526
- invoke_request.payload,
1527
- this.#encryptKey,
1528
- this.#encryptIV
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.#encryptKey) {
1481
+ if (this.#derivedKey) {
1565
1482
  // Decrypt the actual payload using the shared key and iv.
1566
- decryptedPayload = VoltCredential.aesDecrypt(
1567
- Buffer.from(invoke_request.json_payload, "base64"),
1568
- this.#encryptKey,
1569
- this.#encryptIV
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
- if (this.#token.sk) {
1545
+ const iv = voltClientWeb.randomBytes(16);
1546
+ if (this.#derivedKey) {
1629
1547
  // Encrypt the response using the shared key and iv.
1630
- responsePayload = VoltCredential.aesEncrypt(
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
- if (this.#token.sk) {
1588
+ const iv = voltClientWeb.randomBytes(16);
1589
+ if (this.#derivedKey) {
1674
1590
  // Encrypt the response using the shared key and iv.
1675
- endPayload = VoltCredential.aesEncrypt(
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
- // Identity resource id is assigned by the volt.
1738
- this._credential.cache.client_id = authenticateResponse.session_id;
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
- async function authenticateInternal() {
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}/discovery`
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 = pki.publicKeyToPem(keyPair.publicKey);
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
- binding_name: this._config.client_name,
1824
- public_key: publicKeyPem,
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
- if (this._activeRPC[invokeId]) {
1922
- this._activeRPC[invokeId].parsePayload(invoke_request);
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
- const rpcInvocation = new RpcInvocation(this, this._voltConnection);
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
- this.emit("invoke_request", rpcInvocation);
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
- const configContents = await readFile(
2184
- 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)))
2185
- );
2186
- configJSON = JSON.parse(configContents);
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
- !configJSON.client_name ||
2205
- typeof configJSON.client_name !== "string"
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}/discovery`
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
- this._voltConfig.did = voltDID;
2244
- this._voltConfig.http_address = voltHttpAddress;
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
  */