@tdxvolt/volt-client-grpc 0.16.0-beta.8 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -4,37 +4,51 @@ 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');
7
+ var voltClientWeb = require('@tdxvolt/volt-client-web');
9
8
  var debug = require('debug');
10
9
  var EventEmitter = require('events');
11
10
  var fs = require('fs');
12
- var jwt = require('jsonwebtoken');
13
- var crypto = require('crypto');
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
 
20
+ function _interopNamespace(e) {
21
+ if (e && e.__esModule) return e;
22
+ var n = Object.create(null);
23
+ if (e) {
24
+ Object.keys(e).forEach(function (k) {
25
+ if (k !== 'default') {
26
+ var d = Object.getOwnPropertyDescriptor(e, k);
27
+ Object.defineProperty(n, k, d.get ? d : {
28
+ enumerable: true,
29
+ get: function () { return e[k]; }
30
+ });
31
+ }
32
+ });
33
+ }
34
+ n["default"] = e;
35
+ return Object.freeze(n);
36
+ }
37
+
22
38
  var lodash__default = /*#__PURE__*/_interopDefaultLegacy(lodash);
23
39
  var ip__default = /*#__PURE__*/_interopDefaultLegacy(ip);
24
- var forge__default = /*#__PURE__*/_interopDefaultLegacy(forge);
25
- var bs58__default = /*#__PURE__*/_interopDefaultLegacy(bs58);
40
+ var voltClientWeb__namespace = /*#__PURE__*/_interopNamespace(voltClientWeb);
26
41
  var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug);
27
42
  var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
28
43
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
29
- var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
30
- var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
31
44
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
32
45
  var protoLoader__default = /*#__PURE__*/_interopDefaultLegacy(protoLoader);
33
46
  var bent__default = /*#__PURE__*/_interopDefaultLegacy(bent);
47
+ var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
34
48
 
35
49
  // eslint-disable-next-line import/prefer-default-export
36
50
  const constants = {
37
- authTokenName: "tdx-token",
51
+ authTokenName: "volt-token",
38
52
  collectionOperation: {
39
53
  add: "ADD",
40
54
  remove: "REMOVE",
@@ -57,9 +71,9 @@ const constants = {
57
71
  ],
58
72
  userIdOID: "0.9.2342.19200300.100.1.1"
59
73
  },
60
- defaultDIDHostName: "cloud.tdxvolt.com",
74
+ defaultDIDHostName: "tdxvolt.com",
61
75
  didServiceType: {
62
- voltConfig: "tdx-volt-config"
76
+ voltConfig: "volt-discovery"
63
77
  },
64
78
  identityType: {
65
79
  did: "did",
@@ -70,8 +84,6 @@ const constants = {
70
84
  online: "online",
71
85
  unknown: "unknown"
72
86
  },
73
- publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
74
- privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
75
87
  privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
76
88
  resource: {
77
89
  identity: "ident",
@@ -108,8 +120,6 @@ const constants = {
108
120
  tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
109
121
  };
110
122
 
111
- const { pki: pki$2 } = forge__default["default"];
112
-
113
123
  const createSecureContextOptions = (cryptoOptions) => {
114
124
  const tlsOptions = {
115
125
  cert: Buffer.from(cryptoOptions.cert),
@@ -139,53 +149,7 @@ const getServiceAddress = (mdnsService) => {
139
149
  return address;
140
150
  };
141
151
 
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) => {
152
+ const getDIDResolutionURL = (didIn, registryUrl = "") => {
189
153
  const did = didIn.toLowerCase().split(":");
190
154
  if (did.length < 3) {
191
155
  return "";
@@ -195,97 +159,20 @@ const getDIDResolutionURL = (didIn) => {
195
159
  return "";
196
160
  }
197
161
 
198
- if (did[1] !== "tdx") {
162
+ if (did[1] !== "volt") {
199
163
  return "";
200
164
  }
201
165
 
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;
166
+ const lookupUrl = registryUrl || `https://${constants.defaultDIDHostName}`;
167
+ return `${lookupUrl}/did/${didIn}`;
269
168
  };
270
169
 
271
170
  var utils = /*#__PURE__*/Object.freeze({
272
171
  __proto__: null,
273
172
  createSecureContextOptions: createSecureContextOptions,
274
173
  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
174
  getDIDResolutionURL: getDIDResolutionURL,
285
- createRSAKey: createRSAKey,
286
- sha256Base64: sha256Base64,
287
- decryptPrivateKey: decryptPrivateKey,
288
- jwsSignature: jwsSignature
175
+ crypto: voltClientWeb__namespace
289
176
  });
290
177
 
291
178
  /* eslint-disable no-underscore-dangle */
@@ -307,8 +194,11 @@ function createClient(
307
194
  ) {
308
195
  let grpcCredentials;
309
196
  if (credentials) {
310
- const ca = credentials.config?.volt?.relay?.ca_pem || credentials.cache.ca;
311
- const cert = credentials.cache.cloud_cert || credentials.cache.cert;
197
+ const relaying = !!credentials.config?.volt?.relay;
198
+ const ca = relaying
199
+ ? credentials.config?.volt?.relay?.ca_pem
200
+ : credentials.cache.ca;
201
+ const cert = relaying ? "" : credentials.cache.cert;
312
202
  const { key } = credentials.cache;
313
203
 
314
204
  if (key && cert) {
@@ -320,8 +210,9 @@ function createClient(
320
210
  tlsOptions.cert
321
211
  );
322
212
  } else {
323
- // Cache has no private key, this implies we are connecting without supplying a client certificate.
324
- // Simply connect using the CA certificate.
213
+ // Cache has no private key or certificate, this implies we are connecting without supplying a client certificate.
214
+ // We might be connecting to a relay, or making an initial Authenticate call.
215
+ // Simply connect using the CA certificate, and the a JWT will be used to authenticate.
325
216
  grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
326
217
  }
327
218
  } else {
@@ -527,10 +418,7 @@ var grpcUtils = /*#__PURE__*/Object.freeze({
527
418
 
528
419
  /* eslint-disable no-underscore-dangle */
529
420
 
530
- const { pki: pki$1 } = forge__default["default"];
531
421
  const log$5 = debug__default["default"]("volt-client-grpc:volt-credential");
532
- const aesAlgorithm = "aes-256-cbc";
533
- const rs256Algorithm = "RS256";
534
422
 
535
423
  class VoltCredential {
536
424
  constructor(config, configPath) {
@@ -554,14 +442,13 @@ class VoltCredential {
554
442
  }
555
443
 
556
444
  // Need to unencrypt the private key.
557
- const decrypted = pki$1.decryptRsaPrivateKey(
558
- this._cryptoCache.key,
559
- config.p
560
- );
445
+ const decrypted = voltClientWeb.keyFromPem(this._cryptoCache.key, config.p);
446
+
561
447
  if (!decrypted) {
562
448
  throw new Error("failed to decrypt key - check passphrase");
563
449
  }
564
- this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
450
+
451
+ this._cryptoCache.key = voltClientWeb.privateKeyToPem(decrypted.privateKey);
565
452
  }
566
453
 
567
454
  if (this._voltConfig.ca_pem) {
@@ -570,14 +457,11 @@ class VoltCredential {
570
457
  this._cryptoCache.ca = this._voltConfig.ca_pem;
571
458
  }
572
459
 
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
460
  // 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
- );
461
+ this._voltPublicKey = this._voltConfig.public_key;
462
+
463
+ const voltKey = voltClientWeb.keyFromPem(this._voltPublicKey).publicKey;
464
+ this._voltFingerprint = voltClientWeb.fingerprintFromPublicKey(voltKey);
581
465
  }
582
466
  }
583
467
 
@@ -598,7 +482,7 @@ class VoltCredential {
598
482
  }
599
483
 
600
484
  get identityId() {
601
- return this._cryptoCache.identity_id;
485
+ return this._cryptoCache.identity_did;
602
486
  }
603
487
 
604
488
  get voltPublicKey() {
@@ -606,7 +490,7 @@ class VoltCredential {
606
490
  }
607
491
 
608
492
  get publicKey() {
609
- return pki$1.publicKeyToPem(this.getKey().publicKey);
493
+ return voltClientWeb.publicKeyToPem(this.getKey().publicKey);
610
494
  }
611
495
 
612
496
  get voltFingerprint() {
@@ -614,9 +498,8 @@ class VoltCredential {
614
498
  }
615
499
 
616
500
  getKey() {
617
- const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
618
- const publicKey = pki$1.rsa.setPublicKey(privateKey.n, privateKey.e);
619
- return { privateKey, publicKey };
501
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
502
+ return key;
620
503
  }
621
504
 
622
505
  /**
@@ -629,16 +512,10 @@ class VoltCredential {
629
512
  return Promise.resolve(this.getKey());
630
513
  } else {
631
514
  log$5("creating key");
632
- return createRSAKey()
633
- .then((keyInfo) => {
634
- log$5("successfully created key");
635
- this._cryptoCache.key = keyInfo.pem;
636
- return keyInfo.keypair;
637
- })
638
- .catch((err) => {
639
- log$5("failure creating key [%s]", err.message);
640
- return Promise.reject(err);
641
- });
515
+ this._cryptoCache.key = voltClientWeb.createKey();
516
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
517
+ log$5("successfully created key");
518
+ return Promise.resolve(key);
642
519
  }
643
520
  }
644
521
 
@@ -663,55 +540,6 @@ class VoltCredential {
663
540
  return !!this._cryptoCache.cert;
664
541
  }
665
542
 
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
543
  /**
716
544
  * Creates the metadata required for a grpc call, including the Volt authentication token
717
545
  * and optionally tunnel encryption parameters.
@@ -722,9 +550,12 @@ class VoltCredential {
722
550
  * @returns
723
551
  */
724
552
  getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
725
- const identityToken = this.getIdentityToken(
553
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
554
+
555
+ const identityToken = voltClientWeb.getIdentityToken(
556
+ this.sessionId,
557
+ key,
726
558
  audience || this._voltConfig.id,
727
- publicKey,
728
559
  tunnelling,
729
560
  ttl
730
561
  );
@@ -734,48 +565,18 @@ class VoltCredential {
734
565
  }
735
566
 
736
567
  saveCache() {
568
+ const clone = { ...this._config, credential: { ...this._cryptoCache } };
569
+ if (this._config.p) {
570
+ // A passphrase option exists => encrypt the key before writing the cache file.
571
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
572
+ clone.credential.key = voltClientWeb.privateKeyToPem(key.privateKey, this._config.p);
573
+ }
574
+
737
575
  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
576
  fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
748
577
  }
749
- }
750
-
751
- static aesCreateKey() {
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
578
 
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));
579
+ return clone;
779
580
  }
780
581
  }
781
582
 
@@ -918,6 +719,21 @@ function _prepareInvokeRequest(
918
719
  this._service?.service_description.host_type ===
919
720
  "SERVICE_HOST_TYPE_RELAYED";
920
721
 
722
+ const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
723
+
724
+ let targetDID = [];
725
+ if (this._voltClient.isVoltRelay) {
726
+ targetDID.push(this._voltClient.voltConfig.id);
727
+ }
728
+
729
+ if (isServiceRelayed) {
730
+ // Add another relay hop if the target service is relayed.
731
+ log$4("target service is relayed");
732
+ targetDID.push(this._service.service_description.host_client_id);
733
+ }
734
+
735
+ log$4("target is %j", targetDID);
736
+
921
737
  // Use the service's host client id and public key if the service is relayed.
922
738
  const methodToken = this._voltClient.credential.getIdentityMetadata(
923
739
  this._voltClient.grpc,
@@ -925,17 +741,52 @@ function _prepareInvokeRequest(
925
741
  this._voltClient.voltConfig.id,
926
742
  this._service?.service_description.host_public_key ||
927
743
  this._voltClient.credential.voltPublicKey,
928
- true
744
+ relaying
929
745
  );
930
746
 
931
747
  let invokeRequest;
932
748
  let callMethod = method;
933
749
 
934
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
750
+ if (relaying) {
935
751
  //
936
752
  // If the volt connection is via a relay (or the target service is relayed),
937
753
  // we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
938
754
  //
755
+ // Replace the target method with a call to Invoke on the relay Volt.
756
+ //
757
+ callMethod = "Invoke";
758
+
759
+ // But first we need to perform a key exchange with the target service.
760
+ //
761
+ if (!this._encryptionKey) {
762
+ // We haven't exchanged keys yet, so queue the payload.
763
+ this._pendingRequests.push(request);
764
+
765
+ if (this._keyExchangePending) {
766
+ // We've already sent the key exchange request, so do nothing.
767
+ log$4("key exchange pending, not sending payload");
768
+ return;
769
+ }
770
+
771
+ this._keyExchangePending = true;
772
+
773
+ // Send the key exchange request.
774
+ invokeRequest = {
775
+ token: methodToken.token,
776
+ target_did: targetDID,
777
+ target_service_id: isServiceRelayed
778
+ ? this._service.service_description.host_service_id
779
+ : undefined
780
+ };
781
+
782
+ return {
783
+ request: invokeRequest,
784
+ method: callMethod,
785
+ methodName: method,
786
+ meta: methodToken
787
+ };
788
+ }
789
+
939
790
  let requestPayload;
940
791
 
941
792
  // Serialise the rpc request.
@@ -955,45 +806,24 @@ function _prepareInvokeRequest(
955
806
  method_invoke: {
956
807
  method_name: this._grpcClient[method].path,
957
808
  method_type: methodType,
958
- token: methodToken.token,
959
809
  request: requestPayload
960
810
  }
961
811
  };
962
812
 
963
- let targetDID = [];
964
813
  let invokePayload;
965
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
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
- }
975
-
976
- if (this._voltClient.isVoltRelay) {
977
- targetDID.push(this._voltClient.voltConfig.id);
978
- }
979
-
980
- if (isServiceRelayed) {
981
- // Add another relay hop if the target service is relayed.
982
- log$4("target service is relayed");
983
- targetDID.push(this._service.service_description.host_client_id);
984
- }
814
+ const iv = voltClientWeb.aesCreateKey().iv;
985
815
 
986
- log$4("target is %j", targetDID);
816
+ // When sending via a relay we need to encrypt the payload.
817
+ invokePayload = voltClientWeb.aesEncrypt(
818
+ this._encryptionKey,
819
+ iv,
820
+ tunnelMethod.responseSerialize(tunnelResp)
821
+ );
987
822
 
988
823
  invokeRequest = {
989
- target_did: targetDID,
990
- token: methodToken.token,
991
- payload: invokePayload,
992
- target_service_id: isServiceRelayed ? this._service.id : undefined
824
+ iv,
825
+ payload: invokePayload
993
826
  };
994
-
995
- // Replace the target method with a call to Invoke on the relay Volt.
996
- callMethod = "Invoke";
997
827
  } else {
998
828
  invokeRequest = request;
999
829
  }
@@ -1013,7 +843,10 @@ function _preparePayloadRequest(request, invokeInfo) {
1013
843
  this._service?.service_description.host_type ===
1014
844
  "SERVICE_HOST_TYPE_RELAYED";
1015
845
 
846
+ let iv;
1016
847
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
848
+ iv = voltClientWeb.aesCreateKey().iv;
849
+
1017
850
  const requestPayload =
1018
851
  this._grpcClient[invokeInfo.methodName].requestSerialize(request);
1019
852
 
@@ -1026,13 +859,14 @@ function _preparePayloadRequest(request, invokeInfo) {
1026
859
  }
1027
860
  };
1028
861
 
1029
- const invokePayload = VoltCredential.aesEncrypt(
1030
- tunnelMethod.responseSerialize(tunnelResp),
1031
- invokeInfo.meta.sharedKey.key,
1032
- invokeInfo.meta.sharedKey.iv
862
+ const invokePayload = voltClientWeb.aesEncrypt(
863
+ this._encryptionKey,
864
+ iv,
865
+ tunnelMethod.responseSerialize(tunnelResp)
1033
866
  );
1034
867
 
1035
868
  payloadRequest = {
869
+ iv,
1036
870
  payload: invokePayload
1037
871
  };
1038
872
  } else {
@@ -1051,13 +885,38 @@ function _parseResponse(method, meta, response) {
1051
885
  "SERVICE_HOST_TYPE_RELAYED";
1052
886
 
1053
887
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
1054
- if (response.payload) {
888
+ if (response.key_exchange) {
889
+ const encryptionKey = response.key_exchange.encryption_key;
890
+ const nonce = response.key_exchange.nonce;
891
+ const signature = response.key_exchange.signature;
892
+
893
+ // The signed message is the encryption key and the nonce.
894
+ const message = new Uint8Array(encryptionKey.length + nonce.length);
895
+ message.set(encryptionKey);
896
+ message.set(nonce, encryptionKey.length);
897
+
898
+ // Check the signature using the target service public key.
899
+ const targetKey = voltClientWeb.keyFromPem(
900
+ this._service?.service_description?.host_public_key ||
901
+ this._voltClient.credential.voltPublicKey
902
+ ).publicKey;
903
+
904
+ if (!voltClientWeb.verify(targetKey, message, signature)) {
905
+ throw new Error("signature verification failed");
906
+ }
907
+
908
+ invokeResponse = {
909
+ key_exchange: {
910
+ encryption_key: voltClientWeb.formatPEM(voltClientWeb.toBase64(encryptionKey), "PUBLIC KEY")
911
+ }
912
+ };
913
+ } else if (response.payload) {
1055
914
  let decryptedPayload;
1056
- if (meta.sharedKey.key) {
1057
- decryptedPayload = VoltCredential.aesDecrypt(
1058
- response.payload,
1059
- meta.sharedKey.key,
1060
- meta.sharedKey.iv
915
+ if (this._encryptionKey) {
916
+ decryptedPayload = voltClientWeb.aesDecrypt(
917
+ this._encryptionKey,
918
+ response.iv,
919
+ response.payload
1061
920
  );
1062
921
  } else {
1063
922
  decryptedPayload = response.payload;
@@ -1097,11 +956,11 @@ function _parseResponse(method, meta, response) {
1097
956
  invokeResponse.methodId = response.invoke_id;
1098
957
  log$4("started method %d for %s", invokeResponse.methodId, method);
1099
958
  }
1100
- } else if (this._voltClient.isRemote && meta.sharedKey.key) {
1101
- const decryptedPayload = VoltCredential.aesDecrypt(
1102
- response,
1103
- meta.sharedKey.key,
1104
- meta.sharedKey.iv
959
+ } else if (this._voltClient.isRemote && this._encryptionKey) {
960
+ const decryptedPayload = voltClientWeb.aesDecrypt(
961
+ this._encryptionKey,
962
+ meta.sharedKey.iv,
963
+ response
1105
964
  );
1106
965
  invokeResponse = {
1107
966
  payload:
@@ -1122,6 +981,9 @@ class GRPCCall extends EventEmitter__default["default"] {
1122
981
  this._call = null;
1123
982
  this._voltClient = voltClient;
1124
983
  this._service = service;
984
+ this._keyExchangePending = false;
985
+ this._pendingRequests = [];
986
+ this._encryptionKey = null;
1125
987
  }
1126
988
 
1127
989
  start(grpcClient, request) {
@@ -1150,7 +1012,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1150
1012
  }
1151
1013
 
1152
1014
  this._call = callClient[this._initialRequest.method](
1153
- this._initialRequest.meta.metadata
1015
+ this._initialRequest?.meta?.metadata
1154
1016
  );
1155
1017
 
1156
1018
  this._call.on("data", (streamResponse) => {
@@ -1161,7 +1023,25 @@ class GRPCCall extends EventEmitter__default["default"] {
1161
1023
  this._initialRequest.meta,
1162
1024
  streamResponse
1163
1025
  );
1164
- if (parsedResponse.payload) {
1026
+ if (parsedResponse.key_exchange) {
1027
+ const peerKey = voltClientWeb.keyFromPem(
1028
+ parsedResponse.key_exchange.encryption_key
1029
+ ).publicKey;
1030
+
1031
+ this._encryptionKey = voltClientWeb.deriveSharedKey(
1032
+ this._initialRequest.meta.sharedKey.key,
1033
+ peerKey,
1034
+ voltClientWeb.x25519
1035
+ );
1036
+
1037
+ // We've exchanged keys, so send any queued payloads on the next tick.
1038
+ process.nextTick(() => {
1039
+ this._pendingRequests.forEach((pendingRequest) => {
1040
+ this.write(pendingRequest);
1041
+ });
1042
+ this._pendingRequests = [];
1043
+ });
1044
+ } else if (parsedResponse.payload) {
1165
1045
  // Interpret a non-empty status message as an error.
1166
1046
  if (parsedResponse.payload?.status?.message) {
1167
1047
  this.emit(
@@ -1236,7 +1116,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1236
1116
  if (!this._call) {
1237
1117
  log$4("ERROR - call not initialised");
1238
1118
  throw new Error("call not initialised");
1239
- } else if (this._initialRequest) {
1119
+ } else if (this._initialRequest && !this._keyExchangePending) {
1240
1120
  const payloadRequest = _preparePayloadRequest.call(
1241
1121
  this,
1242
1122
  request,
@@ -1244,6 +1124,8 @@ class GRPCCall extends EventEmitter__default["default"] {
1244
1124
  );
1245
1125
  return this._call.write(payloadRequest);
1246
1126
  } else {
1127
+ this._keyExchangePending = false;
1128
+
1247
1129
  this._initialRequest = _prepareInvokeRequest.call(
1248
1130
  this,
1249
1131
  request,
@@ -1273,10 +1155,9 @@ function _cleanUp(immediateReconnect) {
1273
1155
  this._pingTimeoutTimer = 0;
1274
1156
  }
1275
1157
 
1276
- if (
1277
- !this._dying &&
1278
- (immediateReconnect || (this._autoRetry && !this._reconnectTimer))
1279
- ) {
1158
+ if (this._dying) {
1159
+ clearTimeout(this._reconnectTimer);
1160
+ } else if (immediateReconnect || (this._autoRetry && !this._reconnectTimer)) {
1280
1161
  this._reconnectTimer = setTimeout(
1281
1162
  () => {
1282
1163
  this._reconnectTimer = 0;
@@ -1302,13 +1183,18 @@ function _doConnect(connectHello) {
1302
1183
 
1303
1184
  const grpcClient = this._voltClient.getVoltAPIClient();
1304
1185
 
1305
- let helloPayload = connectHello;
1306
- if (!helloPayload) {
1307
- log$3("defaulting hello payload");
1308
- helloPayload = {
1309
- hello: {}
1310
- };
1311
- }
1186
+ // Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
1187
+ // within this time, it will assume the connection is dead.
1188
+ const defaultHello = {
1189
+ ping_interval: this._pingInterval,
1190
+ timestamp: Date.now()
1191
+ };
1192
+
1193
+ const helloPayload = { ...connectHello } || {};
1194
+
1195
+ helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
1196
+
1197
+ log$3("hello is %j", helloPayload);
1312
1198
 
1313
1199
  this.call.on("error", (err) => {
1314
1200
  log$3("error on connection stream [%s]", err.message);
@@ -1326,17 +1212,22 @@ function _doConnect(connectHello) {
1326
1212
  clearTimeout(this._pingTimeoutTimer);
1327
1213
  }
1328
1214
 
1329
- // Schedule the next ping.
1330
- _startPingTimer.call(this);
1331
-
1332
1215
  this.emit("ping", response.ping.timestamp);
1333
1216
  break;
1334
1217
  }
1335
1218
  case "acknowledge": {
1336
1219
  if (!this._connectionId) {
1337
1220
  this._connectionId = response.acknowledge.connection_id;
1221
+
1222
+ // Set the ping timeout interval to the value returned by the Volt. If we don't
1223
+ // hear from the Volt in this time, we'll assume the connection is dead.
1224
+ this._timeoutInterval = response.acknowledge.ping_interval;
1225
+
1338
1226
  log$3("connection id is %s", this._connectionId);
1339
1227
  this.emit("connected", this._connectionId);
1228
+
1229
+ // Schedule the next ping.
1230
+ _startPingTimer.call(this);
1340
1231
  }
1341
1232
  break;
1342
1233
  }
@@ -1364,8 +1255,6 @@ function _doConnect(connectHello) {
1364
1255
  });
1365
1256
 
1366
1257
  this.call.start(grpcClient, helloPayload);
1367
-
1368
- _startPingTimer.call(this);
1369
1258
  }
1370
1259
 
1371
1260
  function _startPingTimer() {
@@ -1392,6 +1281,8 @@ function _startPingTimer() {
1392
1281
  _connectionTimeoutHandler.bind(this),
1393
1282
  this._timeoutInterval
1394
1283
  );
1284
+
1285
+ _startPingTimer.call(this);
1395
1286
  }, this._pingInterval);
1396
1287
  }
1397
1288
 
@@ -1439,13 +1330,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
1439
1330
  #voltClient = null;
1440
1331
  #voltConnection = null;
1441
1332
  #token = null;
1442
- #encryptKey = null;
1443
- #encryptIV = null;
1444
1333
  #invokeId = null;
1445
1334
  #payload = null;
1446
1335
  #isJSON = false;
1447
1336
  #methodDescriptor = null;
1448
1337
  #methodName = null;
1338
+ #derivedKey = null;
1339
+ #started = false;
1449
1340
 
1450
1341
  constructor(voltClient, voltConnection) {
1451
1342
  super();
@@ -1489,51 +1380,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
1489
1380
  this.#methodDescriptor = methodDescriptor;
1490
1381
  }
1491
1382
 
1492
- #decodeToken(token) {
1383
+ get started() {
1384
+ return this.#started;
1385
+ }
1386
+
1387
+ #keyExchange(token) {
1493
1388
  // We don't verify the token at this point, as we don't know the public key.
1494
1389
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
1495
1390
  this.#token = jwt__default["default"].decode(token);
1496
1391
 
1497
- if (this.#token.sk) {
1498
- // Decrypt the shared key and iv using the private key.
1499
- this.#encryptKey = VoltCredential.rsaDecrypt(
1500
- this.#voltClient.credential.cache.key,
1501
- this.#token.sk
1502
- );
1503
- this.#encryptIV = VoltCredential.rsaDecrypt(
1504
- this.#voltClient.credential.cache.key,
1505
- this.#token.iv
1506
- );
1507
- }
1392
+ // Store the JWT key id (kid) for use in the response.
1393
+ const callerPublicKeyPem = voltClientWeb.formatPEM(this.#token.k, "PUBLIC KEY");
1394
+ const callerKey = voltClientWeb.keyFromPem(callerPublicKeyPem, "", voltClientWeb.x25519).publicKey;
1395
+
1396
+ const ephemeralKeyPem = voltClientWeb.createEncryptionKey();
1397
+ const ephemeralKey = voltClientWeb.keyFromPem(ephemeralKeyPem, "", voltClientWeb.x25519);
1398
+ const ephemeralPublicKey = voltClientWeb.fromBase64(
1399
+ voltClientWeb.stripPEMHeaders(voltClientWeb.publicKeyToPem(ephemeralKey.publicKey, voltClientWeb.x25519))
1400
+ );
1401
+
1402
+ this.#derivedKey = voltClientWeb.deriveSharedKey(
1403
+ ephemeralKey.privateKey,
1404
+ callerKey,
1405
+ voltClientWeb.x25519
1406
+ );
1407
+
1408
+ const nonce = voltClientWeb.randomBytes(16);
1409
+
1410
+ // The signed message is the encryption key and the nonce.
1411
+ const message = new Uint8Array(ephemeralPublicKey.length + nonce.length);
1412
+ message.set(ephemeralPublicKey);
1413
+ message.set(nonce, ephemeralPublicKey.length);
1414
+
1415
+ const signature = voltClientWeb.signBase64(
1416
+ this.#voltClient.credential.getKey().privateKey,
1417
+ message
1418
+ );
1419
+
1420
+ const keyExchangeResponse = {
1421
+ invoke_id: this.#invokeId,
1422
+ key_exchange: {
1423
+ nonce,
1424
+ encryption_key: ephemeralPublicKey,
1425
+ signature: voltClientWeb.fromBase64Url(signature)
1426
+ }
1427
+ };
1428
+
1429
+ this.#voltConnection.send({
1430
+ invoke_response: keyExchangeResponse
1431
+ });
1508
1432
 
1509
- return Promise.resolve(this.#token);
1433
+ return Promise.resolve(keyExchangeResponse);
1510
1434
  }
1511
1435
 
1512
1436
  initialise(invoke_request) {
1513
1437
  this.#invokeId = invoke_request.invoke_id;
1514
1438
 
1515
- return this.#decodeToken(invoke_request.token)
1516
- .then(() => {
1517
- // Parse the initial payload.
1518
- return this.parsePayload(invoke_request);
1519
- })
1520
- .catch((err) => {
1439
+ if (!this.#derivedKey) {
1440
+ // We don't have the derived key yet, so we need to perform the key exchange.
1441
+ return this.#keyExchange(invoke_request.token).catch((err) => {
1521
1442
  log$2("failure initialising invocation: %s", err.message);
1522
1443
  return Promise.reject(err);
1523
1444
  });
1445
+ } else {
1446
+ // We already have the derived key, so we can just parse the payload.
1447
+ return this.parsePayload(invoke_request);
1448
+ }
1524
1449
  }
1525
1450
 
1526
1451
  parsePayload(invoke_request) {
1452
+ this.#started = true;
1453
+
1527
1454
  return new Promise((resolve, reject) => {
1455
+ const encryptIV = invoke_request.iv;
1456
+
1528
1457
  if (invoke_request.payload) {
1529
1458
  let decryptedPayload;
1530
1459
 
1531
- if (this.#encryptKey) {
1460
+ if (encryptIV) {
1532
1461
  // Decrypt the actual payload using the shared key and iv.
1533
- decryptedPayload = VoltCredential.aesDecrypt(
1534
- invoke_request.payload,
1535
- this.#encryptKey,
1536
- this.#encryptIV
1462
+ decryptedPayload = voltClientWeb.aesDecrypt(
1463
+ this.#derivedKey,
1464
+ encryptIV,
1465
+ invoke_request.payload
1537
1466
  );
1538
1467
  } else {
1539
1468
  // No encryption => just use the payload.
@@ -1569,12 +1498,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
1569
1498
 
1570
1499
  this.#isJSON = true;
1571
1500
 
1572
- if (this.#encryptKey) {
1501
+ if (this.#derivedKey) {
1573
1502
  // Decrypt the actual payload using the shared key and iv.
1574
- decryptedPayload = VoltCredential.aesDecrypt(
1575
- Buffer.from(invoke_request.json_payload, "base64"),
1576
- this.#encryptKey,
1577
- this.#encryptIV
1503
+ decryptedPayload = voltClientWeb.aesDecrypt(
1504
+ this.#derivedKey,
1505
+ invoke_request.iv,
1506
+ Buffer.from(invoke_request.json_payload, "base64")
1578
1507
  );
1579
1508
  } else {
1580
1509
  // No encryption => just use the payload.
@@ -1633,17 +1562,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
1633
1562
  });
1634
1563
  }
1635
1564
 
1636
- if (this.#token.sk) {
1565
+ const iv = voltClientWeb.randomBytes(16);
1566
+ if (this.#derivedKey) {
1637
1567
  // Encrypt the response using the shared key and iv.
1638
- responsePayload = VoltCredential.aesEncrypt(
1639
- responsePayload,
1640
- this.#encryptKey,
1641
- this.#encryptIV
1642
- );
1568
+ responsePayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, responsePayload);
1643
1569
  }
1644
1570
 
1645
1571
  const invokeResponse = {
1646
- invoke_id: this.#invokeId
1572
+ invoke_id: this.#invokeId,
1573
+ iv
1647
1574
  };
1648
1575
 
1649
1576
  if (this.#isJSON) {
@@ -1678,18 +1605,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
1678
1605
  endPayload = tunnelMethod.requestSerialize(endPayload);
1679
1606
  }
1680
1607
 
1681
- if (this.#token.sk) {
1608
+ const iv = voltClientWeb.randomBytes(16);
1609
+ if (this.#derivedKey) {
1682
1610
  // Encrypt the response using the shared key and iv.
1683
- endPayload = VoltCredential.aesEncrypt(
1684
- endPayload,
1685
- this.#encryptKey,
1686
- this.#encryptIV
1687
- );
1611
+ endPayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, endPayload);
1688
1612
  }
1689
1613
 
1690
1614
  const invokeResponse = {
1691
1615
  invoke_id: this.#invokeId,
1692
- server_end: true
1616
+ server_end: true,
1617
+ iv
1693
1618
  };
1694
1619
 
1695
1620
  if (this.#isJSON) {
@@ -1706,7 +1631,6 @@ class RpcInvocation extends EventEmitter__default["default"] {
1706
1631
 
1707
1632
  /* eslint-disable no-underscore-dangle */
1708
1633
 
1709
- const { pki } = forge__default["default"];
1710
1634
  const { filter, pick } = lodash__default["default"];
1711
1635
 
1712
1636
  const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
@@ -1742,9 +1666,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
1742
1666
  // This is the signing CA used by the volt.
1743
1667
  this._credential.cache.ca = authenticateResponse.chain;
1744
1668
 
1745
- // Identity resource id is assigned by the volt.
1669
+ // The session id is assigned by the volt.
1746
1670
  this._credential.cache.session_id = authenticateResponse.session_id;
1747
1671
 
1672
+ if (!this._credential.cache.identity_did) {
1673
+ // Identity DID has been assigned by the volt.
1674
+ this._credential.cache.identity_did =
1675
+ authenticateResponse.identity_did;
1676
+ }
1677
+
1748
1678
  this._credential.saveCache();
1749
1679
  break;
1750
1680
  }
@@ -1755,6 +1685,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
1755
1685
  case "POLICY_DECISION_PROMPT":
1756
1686
  case "POLICY_DECISION_PENDING": {
1757
1687
  log$1("access pending approval - waiting...");
1688
+
1689
+ // This is the signing CA used by the volt.
1690
+ this._credential.cache.ca = authenticateResponse.chain;
1691
+
1692
+ // The session id is assigned by the volt.
1693
+ this._credential.cache.session_id = authenticateResponse.session_id;
1694
+
1695
+ if (!this._credential.cache.identity_did) {
1696
+ // Identity DID has been assigned by the volt.
1697
+ this._credential.cache.identity_did =
1698
+ authenticateResponse.identity_did;
1699
+ }
1700
+
1701
+ this._credential.saveCache();
1758
1702
  break;
1759
1703
  }
1760
1704
  default:
@@ -1779,7 +1723,10 @@ function findDIDDocumentService(document, serviceType) {
1779
1723
  return matches;
1780
1724
  }
1781
1725
 
1782
- async function authenticateInternal(useDID = true) {
1726
+ async function authenticateInternal(
1727
+ sessionOnly = false,
1728
+ ownDID = false
1729
+ ) {
1783
1730
  try {
1784
1731
  // Check if we need to resolve a Relay volt.
1785
1732
  if (
@@ -1791,7 +1738,7 @@ async function authenticateInternal(useDID = true) {
1791
1738
  log$1("attempting to retrieve Relay information from %s", relayURL);
1792
1739
  this._voltConfig.relay = await fetchVoltConfig.call(
1793
1740
  this,
1794
- `${relayURL}/discovery`
1741
+ `${relayURL}/discover`
1795
1742
  );
1796
1743
 
1797
1744
  if (this._voltConfig.relay.ca_pem) {
@@ -1816,8 +1763,8 @@ async function authenticateInternal(useDID = true) {
1816
1763
  }
1817
1764
 
1818
1765
  const keyPair = this._credential.getKey();
1819
- const publicKeyPem = removeCarriageReturn(
1820
- pki.publicKeyToPem(keyPair.publicKey)
1766
+ const publicKeyPem = voltClientWeb.removeCarriageReturn(
1767
+ voltClientWeb.publicKeyToPem(keyPair.publicKey)
1821
1768
  );
1822
1769
 
1823
1770
  if (!this._credential.cache.bindIp) {
@@ -1830,12 +1777,12 @@ async function authenticateInternal(useDID = true) {
1830
1777
 
1831
1778
  // Form the request message.
1832
1779
  const authenticateRequest = {
1833
- binding_name: this._config.client_name,
1780
+ client_name: this._config.client_name,
1834
1781
  host: this._credential.cache.bindIp
1835
1782
  };
1836
1783
 
1837
1784
  if (this._voltConfig.challenge_code) {
1838
- authenticateRequest.challenge = signBase64(
1785
+ authenticateRequest.challenge = voltClientWeb.signBase64(
1839
1786
  keyPair.privateKey,
1840
1787
  this._voltConfig.challenge_code
1841
1788
  );
@@ -1845,68 +1792,58 @@ async function authenticateInternal(useDID = true) {
1845
1792
  );
1846
1793
  }
1847
1794
 
1848
- if (!useDID) {
1795
+ if (sessionOnly) {
1796
+ // We want to create a session-based authentication based simply on our key, rather than a
1797
+ // fully-blown DID-based authentication.
1798
+ log$1("creating session-only request");
1849
1799
  authenticateRequest.public_key = publicKeyPem;
1850
- } else if (!this._credential.cache.identity_id) {
1851
- // If we don't have an identity id, then we need to create a DID.
1852
- this._credential.cache.identity_id = `did:volt:${uuid.v4()}`;
1853
-
1854
- const did = this._credential.cache.identity_id;
1855
-
1856
- // Order of properties in the DID document is important,
1857
- // they must be in alphabetical order throughout the document.
1858
- // This is because the DID document is signed and the signature
1859
- // is over the entire document.
1860
- const didDocument = {
1861
- "@context": "https://www.w3.org/ns/did/v1",
1862
- authentication: [`${did}#key-1`],
1863
- description: this._config.client_name,
1864
- id: did,
1865
- verificationMethod: [
1866
- {
1867
- controller: did,
1868
- id: `${did}#key-1`,
1869
- publicKeyPem: publicKeyPem,
1870
- type: "RsaSignature2018"
1871
- }
1872
- ]
1873
- };
1874
-
1875
- const signDocument = JSON.stringify(didDocument, null, 2);
1876
- log$1("signing did document");
1877
- log$1(signDocument);
1878
-
1879
- // Sign the DID document.
1880
- const documentDigest = signBase64(
1881
- keyPair.privateKey,
1882
- signDocument
1883
- );
1884
-
1885
- const proofSignature = jwsSignature(documentDigest);
1886
-
1887
- // Create the DID document proof.
1888
- didDocument.proof = {
1889
- created: new Date().toISOString(),
1890
- jws: proofSignature,
1891
- proofPurpose: "assertionMethod",
1892
- type: "RsaSignature2018",
1893
- verificationMethod: `${did}#key-1`
1894
- };
1800
+ } else if (!this._credential.cache.identity_did) {
1801
+ if (!ownDID) {
1802
+ // We don't want to have to manage our own DID, so we ask the Volt to do it for us.
1803
+ // The Volt will create a new DID and return it to us in the response.
1804
+ log$1("creating DID-based request");
1805
+ authenticateRequest.did_public_key = publicKeyPem;
1806
+ } else {
1807
+ // We want to create and manage our own DID.
1808
+ const did =
1809
+ (this._credential.cache.identity_did = `did:volt:${uuid.v4()}`);
1810
+
1811
+ // Order of properties in the DID document is important,
1812
+ // they must be in alphabetical order throughout the document.
1813
+ // This is because the DID document is signed and the signature
1814
+ // is over the entire document.
1815
+ const didDocument = {
1816
+ "@context": "https://www.w3.org/ns/did/v1",
1817
+ authentication: [`${did}#key-1`],
1818
+ controller: did,
1819
+ description: this._config.client_name,
1820
+ id: did,
1821
+ verificationMethod: [
1822
+ {
1823
+ id: `${did}#key-1`,
1824
+ publicKeyPem: publicKeyPem,
1825
+ type: "Ed25519VerificationKey2018"
1826
+ }
1827
+ ]
1828
+ };
1895
1829
 
1896
- // Set the DID document with proof in the authenticate request.
1897
- authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1830
+ // Set the DID document with proof in the authenticate request.
1831
+ authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1898
1832
 
1899
- // Now sign the entire DID document, including the proof and set it in the
1900
- // bind request. This is required by the Volt to verify that we have the private key for the DID.
1901
- authenticateRequest.did_document_signature = signBase64(
1902
- keyPair.privateKey,
1903
- authenticateRequest.did_document
1904
- );
1833
+ // Now sign the entire DID document, including the proof and set it in the
1834
+ // bind request. This is required by the Volt to verify that we have the private key for the DID.
1835
+ authenticateRequest.did_document_signature = voltClientWeb.signBase64(
1836
+ keyPair.privateKey,
1837
+ authenticateRequest.did_document
1838
+ );
1905
1839
 
1906
- log$1("did document is %s", authenticateRequest.did_document);
1907
- log$1("did signature is %s", authenticateRequest.did_document_signature);
1840
+ log$1("did document is %s", authenticateRequest.did_document);
1841
+ log$1("did signature is %s", authenticateRequest.did_document_signature);
1842
+ }
1908
1843
  } else {
1909
- authenticateRequest.did = this._credential.cache.identity_id;
1844
+ // We already have a DID cached, so re-use it.
1845
+ log$1("using cached DID %s", this._credential.cache.identity_did);
1846
+ authenticateRequest.did = this._credential.cache.identity_did;
1910
1847
  }
1911
1848
 
1912
1849
  log$1("authenticate TTL is ", this._config.bindRequestTTL);
@@ -1975,18 +1912,18 @@ function connectInternal(helloPayload) {
1975
1912
  this._voltConnection.on("error", (err) => {
1976
1913
  // Should auto-reconnect.
1977
1914
  log$1("Volt connection error [%s]", err.message);
1978
- if (err.message.endsWith("session invalid")) {
1979
- log$1("Volt session invalid - clearing session");
1980
- this._credential.cache.session_id = null;
1981
- this._credential.cache.cert = null;
1982
- this._credential.saveCache();
1983
- }
1984
-
1985
1915
  if (this._connected) {
1986
1916
  onDisconnected();
1987
1917
  this.emit("connected", false);
1988
1918
  }
1989
1919
 
1920
+ if (err.message.endsWith("session invalid")) {
1921
+ log$1("Volt session invalid - clearing session");
1922
+ delete this._credential.cache.session_id;
1923
+ delete this._credential.cache.cert;
1924
+ this._credential.saveCache();
1925
+ }
1926
+
1990
1927
  this.emit("connectionError", err);
1991
1928
  });
1992
1929
 
@@ -2000,10 +1937,24 @@ function connectInternal(helloPayload) {
2000
1937
 
2001
1938
  this._voltConnection.on("invoke_request", (invoke_request) => {
2002
1939
  const invokeId = invoke_request.invoke_id;
2003
- if (this._activeRPC[invokeId]) {
2004
- this._activeRPC[invokeId].parsePayload(invoke_request);
1940
+
1941
+ let rpcInvocation = this._activeRPC[invokeId];
1942
+
1943
+ if (rpcInvocation) {
1944
+ if (rpcInvocation.started) {
1945
+ rpcInvocation.parsePayload(invoke_request);
1946
+ } else {
1947
+ rpcInvocation
1948
+ .initialise(invoke_request)
1949
+ .then(() => {
1950
+ this.emit("invoke_request", rpcInvocation);
1951
+ })
1952
+ .catch((err) => {
1953
+ log$1("invoke_request - error [%s]", err.message);
1954
+ });
1955
+ }
2005
1956
  } else {
2006
- const rpcInvocation = new RpcInvocation(this, this._voltConnection);
1957
+ rpcInvocation = new RpcInvocation(this, this._voltConnection);
2007
1958
 
2008
1959
  rpcInvocation.on("end", () => {
2009
1960
  log$1("removing active RPC [%s]", invokeId);
@@ -2012,9 +1963,11 @@ function connectInternal(helloPayload) {
2012
1963
 
2013
1964
  rpcInvocation
2014
1965
  .initialise(invoke_request)
2015
- .then(() => {
1966
+ .then((response) => {
2016
1967
  this._activeRPC[invokeId] = rpcInvocation;
2017
- this.emit("invoke_request", rpcInvocation);
1968
+ if (!response.key_exchange) {
1969
+ this.emit("invoke_request", rpcInvocation);
1970
+ }
2018
1971
  })
2019
1972
  .catch((err) => {
2020
1973
  log$1("invoke_request - error [%s]", err.message);
@@ -2160,6 +2113,7 @@ async function fetchVoltConfig(discovery_url) {
2160
2113
  "public_key",
2161
2114
  "fingerprint",
2162
2115
  "address",
2116
+ "http_address",
2163
2117
  "ca_pem",
2164
2118
  "challenge_code",
2165
2119
  "cloud",
@@ -2247,6 +2201,7 @@ class VoltClient extends EventEmitter__default["default"] {
2247
2201
  /**
2248
2202
  * Initialises a binding and connection to the volt.
2249
2203
  * @param {string} [config] - the location of the Volt configuration file, or a configuration object
2204
+ * @param {object} [extras] - additional configuration properties
2250
2205
  */
2251
2206
  async initialise(config, extras = {}) {
2252
2207
  try {
@@ -2262,10 +2217,15 @@ class VoltClient extends EventEmitter__default["default"] {
2262
2217
  configPath = path__default["default"].resolve(config);
2263
2218
  log("Attempt to load config from file %s", configPath);
2264
2219
  try {
2265
- const configContents = await readFile(
2266
- 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)))
2267
- );
2268
- configJSON = JSON.parse(configContents);
2220
+ if (fs__default["default"].existsSync(configPath)) {
2221
+ const configContents = await readFile(
2222
+ 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)))
2223
+ );
2224
+ configJSON = JSON.parse(configContents);
2225
+ } else {
2226
+ // There is currently no file at the given configuration path.
2227
+ configJSON = {};
2228
+ }
2269
2229
  } catch (err) {
2270
2230
  log("failure loading config file %s [%s]", configPath, err.message);
2271
2231
  throw new Error(
@@ -2282,15 +2242,15 @@ class VoltClient extends EventEmitter__default["default"] {
2282
2242
  throw new Error("config argument is required to be an object");
2283
2243
  }
2284
2244
 
2245
+ this._config = { ...extras, ...configJSON };
2246
+
2285
2247
  if (
2286
- !configJSON.client_name ||
2287
- typeof configJSON.client_name !== "string"
2248
+ !this._config.client_name ||
2249
+ typeof this._config.client_name !== "string"
2288
2250
  ) {
2289
2251
  throw new Error("client_name property required in config");
2290
2252
  }
2291
2253
 
2292
- this._config = { ...configJSON, ...extras };
2293
-
2294
2254
  let voltDID = "";
2295
2255
  let voltHttpAddress = "";
2296
2256
  if (typeof this._config.volt === "string") {
@@ -2299,6 +2259,7 @@ class VoltClient extends EventEmitter__default["default"] {
2299
2259
  } else {
2300
2260
  voltHttpAddress = this._config.volt;
2301
2261
  }
2262
+ delete this._config.volt;
2302
2263
  } else if (typeof this._config.volt !== "object") {
2303
2264
  throw new Error("configuration is missing the 'volt' object");
2304
2265
  } else {
@@ -2313,7 +2274,7 @@ class VoltClient extends EventEmitter__default["default"] {
2313
2274
  } else if (voltHttpAddress && !this._config?.volt?.id) {
2314
2275
  const discoConfig = await fetchVoltConfig.call(
2315
2276
  this,
2316
- `${voltHttpAddress}/discovery`
2277
+ `${voltHttpAddress}/discover`
2317
2278
  );
2318
2279
  this._config = {
2319
2280
  ...this._config,
@@ -2322,8 +2283,12 @@ class VoltClient extends EventEmitter__default["default"] {
2322
2283
  }
2323
2284
 
2324
2285
  this._voltConfig = this._config.volt;
2325
- this._voltConfig.did = voltDID;
2326
- this._voltConfig.http_address = voltHttpAddress;
2286
+ if (voltDID) {
2287
+ this._voltConfig.did = voltDID;
2288
+ }
2289
+ if (voltHttpAddress) {
2290
+ this._voltConfig.http_address = voltHttpAddress;
2291
+ }
2327
2292
 
2328
2293
  if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
2329
2294
  throw new Error("'id' property is missing in Volt configuration");
@@ -2371,9 +2336,12 @@ class VoltClient extends EventEmitter__default["default"] {
2371
2336
  if (!isBound) {
2372
2337
  // This is usually because of request being denied.
2373
2338
  throw new Error("Volt authentication failed");
2339
+ } else {
2340
+ // Perist the configuration.
2341
+ this._config = this._credential.saveCache();
2374
2342
  }
2375
2343
 
2376
- return Promise.resolve();
2344
+ return Promise.resolve(this._config);
2377
2345
  } catch (err) {
2378
2346
  log("Failure starting Volt client [%s]", err.message);
2379
2347
  throw err;
@@ -2840,6 +2808,13 @@ class VoltClient extends EventEmitter__default["default"] {
2840
2808
  });
2841
2809
  }
2842
2810
 
2811
+ SqlTransaction(request) {
2812
+ const grpcClient = this.getVoltAPIClient();
2813
+
2814
+ const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
2815
+ return call.start(grpcClient, request);
2816
+ }
2817
+
2843
2818
  /**
2844
2819
  * WireAPI
2845
2820
  */