@tdxvolt/volt-client-grpc 0.16.0-beta.9 → 0.18.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,90 +4,36 @@ 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 js = require('@tdxvolt/volt-client-web/js');
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
 
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
- // eslint-disable-next-line import/prefer-default-export
36
30
  const constants = {
37
- authTokenName: "tdx-token",
38
- collectionOperation: {
39
- add: "ADD",
40
- remove: "REMOVE",
41
- update: "UPDATE"
42
- },
43
- crypto: {
44
- subjectDefaults: [
45
- {
46
- name: "countryName",
47
- value: "GB"
48
- },
49
- {
50
- name: "localityName",
51
- value: "Southampton"
52
- },
53
- {
54
- name: "organizationName",
55
- value: "nquiringminds Ltd"
56
- }
57
- ],
58
- userIdOID: "0.9.2342.19200300.100.1.1"
59
- },
60
- defaultDIDHostName: "cloud.tdxvolt.com",
31
+ authTokenName: "volt-token",
32
+ defaultDIDHostName: "tdxvolt.com",
61
33
  didServiceType: {
62
- voltConfig: "tdx-volt-config"
63
- },
64
- identityType: {
65
- did: "did",
66
- volt: "volt"
34
+ voltConfig: "volt-discovery"
67
35
  },
68
- onlineStatus: {
69
- offline: "offline",
70
- online: "online",
71
- unknown: "unknown"
72
- },
73
- publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
74
- privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
75
36
  privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
76
- resource: {
77
- identity: "ident",
78
- identityView: "ident-view",
79
- proxy: "proxy",
80
- proxyView: "proxy-view",
81
- vc: "vc",
82
- vcView: "vc-view"
83
- },
84
- voltStatus: {
85
- pending: "",
86
- online: "online",
87
- offline: "offline",
88
- deleted: "deleted",
89
- revoked: "revoked"
90
- },
91
37
  /**
92
38
  * These top-level service types should map to a protobuf definition
93
39
  * in @tdxvolt/volt-proto.
@@ -100,16 +46,9 @@ const constants = {
100
46
  relayAPI: "tdx.volt_api.relay.v1.RelayAPI",
101
47
  voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
102
48
  wireAPI: "tdx.volt_api.volt.v1.WireAPI"
103
- },
104
- tunnelInvokeMethod: "/tdx.volt_api.volt.v1.VoltAPI/Invoke",
105
- tunnelPingInterval: 10000,
106
- tunnelPingTimeout: 2500,
107
- tunnelReconnectAfter: 30000,
108
- tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
49
+ }
109
50
  };
110
51
 
111
- const { pki: pki$2 } = forge__default["default"];
112
-
113
52
  const createSecureContextOptions = (cryptoOptions) => {
114
53
  const tlsOptions = {
115
54
  cert: Buffer.from(cryptoOptions.cert),
@@ -139,53 +78,7 @@ const getServiceAddress = (mdnsService) => {
139
78
  return address;
140
79
  };
141
80
 
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) => {
81
+ const getDIDResolutionURL = (didIn, registryUrl = "") => {
189
82
  const did = didIn.toLowerCase().split(":");
190
83
  if (did.length < 3) {
191
84
  return "";
@@ -195,97 +88,20 @@ const getDIDResolutionURL = (didIn) => {
195
88
  return "";
196
89
  }
197
90
 
198
- if (did[1] !== "tdx") {
91
+ if (did[1] !== "volt") {
199
92
  return "";
200
93
  }
201
94
 
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;
95
+ const lookupUrl = registryUrl || `https://${constants.defaultDIDHostName}`;
96
+ return `${lookupUrl}/did/${didIn}`;
269
97
  };
270
98
 
271
99
  var utils = /*#__PURE__*/Object.freeze({
272
100
  __proto__: null,
273
101
  createSecureContextOptions: createSecureContextOptions,
274
102
  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
103
  getDIDResolutionURL: getDIDResolutionURL,
285
- createRSAKey: createRSAKey,
286
- sha256Base64: sha256Base64,
287
- decryptPrivateKey: decryptPrivateKey,
288
- jwsSignature: jwsSignature
104
+ crypto: js.voltUtils
289
105
  });
290
106
 
291
107
  /* eslint-disable no-underscore-dangle */
@@ -307,8 +123,11 @@ function createClient(
307
123
  ) {
308
124
  let grpcCredentials;
309
125
  if (credentials) {
310
- const ca = credentials.config?.volt?.relay?.ca_pem || credentials.cache.ca;
311
- const cert = credentials.cache.cloud_cert || credentials.cache.cert;
126
+ const relaying = !!credentials.config?.volt?.relay;
127
+ const ca = relaying
128
+ ? credentials.config?.volt?.relay?.ca_pem
129
+ : credentials.cache.ca;
130
+ const cert = relaying ? "" : credentials.cache.cert;
312
131
  const { key } = credentials.cache;
313
132
 
314
133
  if (key && cert) {
@@ -320,8 +139,9 @@ function createClient(
320
139
  tlsOptions.cert
321
140
  );
322
141
  } else {
323
- // Cache has no private key, this implies we are connecting without supplying a client certificate.
324
- // Simply connect using the CA certificate.
142
+ // Cache has no private key or certificate, this implies we are connecting without supplying a client certificate.
143
+ // We might be connecting to a relay, or making an initial Authenticate call.
144
+ // Simply connect using the CA certificate, and the a JWT will be used to authenticate.
325
145
  grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
326
146
  }
327
147
  } else {
@@ -527,10 +347,16 @@ var grpcUtils = /*#__PURE__*/Object.freeze({
527
347
 
528
348
  /* eslint-disable no-underscore-dangle */
529
349
 
530
- const { pki: pki$1 } = forge__default["default"];
350
+ const {
351
+ createSigningKey,
352
+ fingerprintFromPem,
353
+ getIdentityToken,
354
+ keyFromPem: keyFromPem$2,
355
+ privateKeyToPem,
356
+ publicKeyToPem: publicKeyToPem$2
357
+ } = js.voltUtils;
358
+
531
359
  const log$5 = debug__default["default"]("volt-client-grpc:volt-credential");
532
- const aesAlgorithm = "aes-256-cbc";
533
- const rs256Algorithm = "RS256";
534
360
 
535
361
  class VoltCredential {
536
362
  constructor(config, configPath) {
@@ -554,14 +380,13 @@ class VoltCredential {
554
380
  }
555
381
 
556
382
  // Need to unencrypt the private key.
557
- const decrypted = pki$1.decryptRsaPrivateKey(
558
- this._cryptoCache.key,
559
- config.p
560
- );
383
+ const decrypted = keyFromPem$2(this._cryptoCache.key, config.p);
384
+
561
385
  if (!decrypted) {
562
386
  throw new Error("failed to decrypt key - check passphrase");
563
387
  }
564
- this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
388
+
389
+ this._cryptoCache.key = privateKeyToPem(decrypted.privateKey);
565
390
  }
566
391
 
567
392
  if (this._voltConfig.ca_pem) {
@@ -570,14 +395,11 @@ class VoltCredential {
570
395
  this._cryptoCache.ca = this._voltConfig.ca_pem;
571
396
  }
572
397
 
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
398
  // 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
- );
399
+ this._voltPublicKey = this._voltConfig.public_key;
400
+
401
+ const voltKey = keyFromPem$2(this._voltPublicKey).publicKey;
402
+ this._voltFingerprint = fingerprintFromPem(voltKey);
581
403
  }
582
404
  }
583
405
 
@@ -598,7 +420,7 @@ class VoltCredential {
598
420
  }
599
421
 
600
422
  get identityId() {
601
- return this._cryptoCache.identity_id;
423
+ return this._cryptoCache.identity_did;
602
424
  }
603
425
 
604
426
  get voltPublicKey() {
@@ -606,7 +428,7 @@ class VoltCredential {
606
428
  }
607
429
 
608
430
  get publicKey() {
609
- return pki$1.publicKeyToPem(this.getKey().publicKey);
431
+ return publicKeyToPem$2(this.getKey().publicKey);
610
432
  }
611
433
 
612
434
  get voltFingerprint() {
@@ -614,9 +436,8 @@ class VoltCredential {
614
436
  }
615
437
 
616
438
  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 };
439
+ const key = keyFromPem$2(this._cryptoCache.key);
440
+ return key;
620
441
  }
621
442
 
622
443
  /**
@@ -629,16 +450,10 @@ class VoltCredential {
629
450
  return Promise.resolve(this.getKey());
630
451
  } else {
631
452
  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
- });
453
+ this._cryptoCache.key = createSigningKey();
454
+ const key = keyFromPem$2(this._cryptoCache.key);
455
+ log$5("successfully created key");
456
+ return Promise.resolve(key);
642
457
  }
643
458
  }
644
459
 
@@ -663,55 +478,6 @@ class VoltCredential {
663
478
  return !!this._cryptoCache.cert;
664
479
  }
665
480
 
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
481
  /**
716
482
  * Creates the metadata required for a grpc call, including the Volt authentication token
717
483
  * and optionally tunnel encryption parameters.
@@ -722,9 +488,12 @@ class VoltCredential {
722
488
  * @returns
723
489
  */
724
490
  getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
725
- const identityToken = this.getIdentityToken(
491
+ const key = keyFromPem$2(this._cryptoCache.key);
492
+
493
+ const identityToken = getIdentityToken(
494
+ this.sessionId,
495
+ key,
726
496
  audience || this._voltConfig.id,
727
- publicKey,
728
497
  tunnelling,
729
498
  ttl
730
499
  );
@@ -734,48 +503,18 @@ class VoltCredential {
734
503
  }
735
504
 
736
505
  saveCache() {
506
+ const clone = { ...this._config, credential: { ...this._cryptoCache } };
507
+ if (this._config.p) {
508
+ // A passphrase option exists => encrypt the key before writing the cache file.
509
+ const key = keyFromPem$2(this._cryptoCache.key);
510
+ clone.credential.key = privateKeyToPem(key.privateKey, this._config.p);
511
+ }
512
+
737
513
  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
514
  fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
748
515
  }
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
516
 
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));
517
+ return clone;
779
518
  }
780
519
  }
781
520
 
@@ -907,6 +646,18 @@ var protoUtils = /*#__PURE__*/Object.freeze({
907
646
 
908
647
  /* eslint-disable no-underscore-dangle */
909
648
 
649
+ const {
650
+ aesCreateKey,
651
+ aesEncrypt: aesEncrypt$1,
652
+ aesDecrypt: aesDecrypt$1,
653
+ toBase64,
654
+ formatPEM: formatPEM$1,
655
+ keyFromPem: keyFromPem$1,
656
+ verify,
657
+ deriveSharedKey: deriveSharedKey$1,
658
+ x25519: x25519$1
659
+ } = js.voltUtils;
660
+
910
661
  const log$4 = debug__default["default"]("volt-client-grpc:grpc-call");
911
662
 
912
663
  function _prepareInvokeRequest(
@@ -918,6 +669,21 @@ function _prepareInvokeRequest(
918
669
  this._service?.service_description.host_type ===
919
670
  "SERVICE_HOST_TYPE_RELAYED";
920
671
 
672
+ const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
673
+
674
+ let targetDID = [];
675
+ if (this._voltClient.isVoltRelay) {
676
+ targetDID.push(this._voltClient.voltConfig.id);
677
+ }
678
+
679
+ if (isServiceRelayed) {
680
+ // Add another relay hop if the target service is relayed.
681
+ log$4("target service is relayed");
682
+ targetDID.push(this._service.service_description.host_client_id);
683
+ }
684
+
685
+ log$4("target is %j", targetDID);
686
+
921
687
  // Use the service's host client id and public key if the service is relayed.
922
688
  const methodToken = this._voltClient.credential.getIdentityMetadata(
923
689
  this._voltClient.grpc,
@@ -925,17 +691,52 @@ function _prepareInvokeRequest(
925
691
  this._voltClient.voltConfig.id,
926
692
  this._service?.service_description.host_public_key ||
927
693
  this._voltClient.credential.voltPublicKey,
928
- true
694
+ relaying
929
695
  );
930
696
 
931
697
  let invokeRequest;
932
698
  let callMethod = method;
933
699
 
934
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
700
+ if (relaying) {
935
701
  //
936
702
  // If the volt connection is via a relay (or the target service is relayed),
937
703
  // we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
938
704
  //
705
+ // Replace the target method with a call to Invoke on the relay Volt.
706
+ //
707
+ callMethod = "Invoke";
708
+
709
+ // But first we need to perform a key exchange with the target service.
710
+ //
711
+ if (!this._encryptionKey) {
712
+ // We haven't exchanged keys yet, so queue the payload.
713
+ this._pendingRequests.push(request);
714
+
715
+ if (this._keyExchangePending) {
716
+ // We've already sent the key exchange request, so do nothing.
717
+ log$4("key exchange pending, not sending payload");
718
+ return;
719
+ }
720
+
721
+ this._keyExchangePending = true;
722
+
723
+ // Send the key exchange request.
724
+ invokeRequest = {
725
+ token: methodToken.token,
726
+ target_did: targetDID,
727
+ target_service_id: isServiceRelayed
728
+ ? this._service.service_description.host_service_id
729
+ : undefined
730
+ };
731
+
732
+ return {
733
+ request: invokeRequest,
734
+ method: callMethod,
735
+ methodName: method,
736
+ meta: methodToken
737
+ };
738
+ }
739
+
939
740
  let requestPayload;
940
741
 
941
742
  // Serialise the rpc request.
@@ -955,45 +756,24 @@ function _prepareInvokeRequest(
955
756
  method_invoke: {
956
757
  method_name: this._grpcClient[method].path,
957
758
  method_type: methodType,
958
- token: methodToken.token,
959
759
  request: requestPayload
960
760
  }
961
761
  };
962
762
 
963
- let targetDID = [];
964
763
  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
- }
764
+ const iv = aesCreateKey().iv;
975
765
 
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
- }
985
-
986
- log$4("target is %j", targetDID);
766
+ // When sending via a relay we need to encrypt the payload.
767
+ invokePayload = aesEncrypt$1(
768
+ this._encryptionKey,
769
+ iv,
770
+ tunnelMethod.responseSerialize(tunnelResp)
771
+ );
987
772
 
988
773
  invokeRequest = {
989
- target_did: targetDID,
990
- token: methodToken.token,
991
- payload: invokePayload,
992
- target_service_id: isServiceRelayed ? this._service.id : undefined
774
+ iv,
775
+ payload: invokePayload
993
776
  };
994
-
995
- // Replace the target method with a call to Invoke on the relay Volt.
996
- callMethod = "Invoke";
997
777
  } else {
998
778
  invokeRequest = request;
999
779
  }
@@ -1013,7 +793,10 @@ function _preparePayloadRequest(request, invokeInfo) {
1013
793
  this._service?.service_description.host_type ===
1014
794
  "SERVICE_HOST_TYPE_RELAYED";
1015
795
 
796
+ let iv;
1016
797
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
798
+ iv = aesCreateKey().iv;
799
+
1017
800
  const requestPayload =
1018
801
  this._grpcClient[invokeInfo.methodName].requestSerialize(request);
1019
802
 
@@ -1026,13 +809,14 @@ function _preparePayloadRequest(request, invokeInfo) {
1026
809
  }
1027
810
  };
1028
811
 
1029
- const invokePayload = VoltCredential.aesEncrypt(
1030
- tunnelMethod.responseSerialize(tunnelResp),
1031
- invokeInfo.meta.sharedKey.key,
1032
- invokeInfo.meta.sharedKey.iv
812
+ const invokePayload = aesEncrypt$1(
813
+ this._encryptionKey,
814
+ iv,
815
+ tunnelMethod.responseSerialize(tunnelResp)
1033
816
  );
1034
817
 
1035
818
  payloadRequest = {
819
+ iv,
1036
820
  payload: invokePayload
1037
821
  };
1038
822
  } else {
@@ -1051,13 +835,38 @@ function _parseResponse(method, meta, response) {
1051
835
  "SERVICE_HOST_TYPE_RELAYED";
1052
836
 
1053
837
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
1054
- if (response.payload) {
838
+ if (response.key_exchange) {
839
+ const encryptionKey = response.key_exchange.encryption_key;
840
+ const nonce = response.key_exchange.nonce;
841
+ const signature = response.key_exchange.signature;
842
+
843
+ // The signed message is the encryption key and the nonce.
844
+ const message = new Uint8Array(encryptionKey.length + nonce.length);
845
+ message.set(encryptionKey);
846
+ message.set(nonce, encryptionKey.length);
847
+
848
+ // Check the signature using the target service public key.
849
+ const targetKey = keyFromPem$1(
850
+ this._service?.service_description?.host_public_key ||
851
+ this._voltClient.credential.voltPublicKey
852
+ ).publicKey;
853
+
854
+ if (!verify(targetKey, message, signature)) {
855
+ throw new Error("signature verification failed");
856
+ }
857
+
858
+ invokeResponse = {
859
+ key_exchange: {
860
+ encryption_key: formatPEM$1(toBase64(encryptionKey), "PUBLIC KEY")
861
+ }
862
+ };
863
+ } else if (response.payload) {
1055
864
  let decryptedPayload;
1056
- if (meta.sharedKey.key) {
1057
- decryptedPayload = VoltCredential.aesDecrypt(
1058
- response.payload,
1059
- meta.sharedKey.key,
1060
- meta.sharedKey.iv
865
+ if (this._encryptionKey) {
866
+ decryptedPayload = aesDecrypt$1(
867
+ this._encryptionKey,
868
+ response.iv,
869
+ response.payload
1061
870
  );
1062
871
  } else {
1063
872
  decryptedPayload = response.payload;
@@ -1097,11 +906,11 @@ function _parseResponse(method, meta, response) {
1097
906
  invokeResponse.methodId = response.invoke_id;
1098
907
  log$4("started method %d for %s", invokeResponse.methodId, method);
1099
908
  }
1100
- } else if (this._voltClient.isRemote && meta.sharedKey.key) {
1101
- const decryptedPayload = VoltCredential.aesDecrypt(
1102
- response,
1103
- meta.sharedKey.key,
1104
- meta.sharedKey.iv
909
+ } else if (this._voltClient.isRemote && this._encryptionKey) {
910
+ const decryptedPayload = aesDecrypt$1(
911
+ this._encryptionKey,
912
+ meta.sharedKey.iv,
913
+ response
1105
914
  );
1106
915
  invokeResponse = {
1107
916
  payload:
@@ -1122,6 +931,9 @@ class GRPCCall extends EventEmitter__default["default"] {
1122
931
  this._call = null;
1123
932
  this._voltClient = voltClient;
1124
933
  this._service = service;
934
+ this._keyExchangePending = false;
935
+ this._pendingRequests = [];
936
+ this._encryptionKey = null;
1125
937
  }
1126
938
 
1127
939
  start(grpcClient, request) {
@@ -1150,7 +962,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1150
962
  }
1151
963
 
1152
964
  this._call = callClient[this._initialRequest.method](
1153
- this._initialRequest.meta.metadata
965
+ this._initialRequest?.meta?.metadata
1154
966
  );
1155
967
 
1156
968
  this._call.on("data", (streamResponse) => {
@@ -1161,7 +973,25 @@ class GRPCCall extends EventEmitter__default["default"] {
1161
973
  this._initialRequest.meta,
1162
974
  streamResponse
1163
975
  );
1164
- if (parsedResponse.payload) {
976
+ if (parsedResponse.key_exchange) {
977
+ const peerKey = keyFromPem$1(
978
+ parsedResponse.key_exchange.encryption_key
979
+ ).publicKey;
980
+
981
+ this._encryptionKey = deriveSharedKey$1(
982
+ this._initialRequest.meta.sharedKey.key,
983
+ peerKey,
984
+ x25519$1
985
+ );
986
+
987
+ // We've exchanged keys, so send any queued payloads on the next tick.
988
+ process.nextTick(() => {
989
+ this._pendingRequests.forEach((pendingRequest) => {
990
+ this.write(pendingRequest);
991
+ });
992
+ this._pendingRequests = [];
993
+ });
994
+ } else if (parsedResponse.payload) {
1165
995
  // Interpret a non-empty status message as an error.
1166
996
  if (parsedResponse.payload?.status?.message) {
1167
997
  this.emit(
@@ -1236,7 +1066,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1236
1066
  if (!this._call) {
1237
1067
  log$4("ERROR - call not initialised");
1238
1068
  throw new Error("call not initialised");
1239
- } else if (this._initialRequest) {
1069
+ } else if (this._initialRequest && !this._keyExchangePending) {
1240
1070
  const payloadRequest = _preparePayloadRequest.call(
1241
1071
  this,
1242
1072
  request,
@@ -1244,6 +1074,8 @@ class GRPCCall extends EventEmitter__default["default"] {
1244
1074
  );
1245
1075
  return this._call.write(payloadRequest);
1246
1076
  } else {
1077
+ this._keyExchangePending = false;
1078
+
1247
1079
  this._initialRequest = _prepareInvokeRequest.call(
1248
1080
  this,
1249
1081
  request,
@@ -1273,10 +1105,9 @@ function _cleanUp(immediateReconnect) {
1273
1105
  this._pingTimeoutTimer = 0;
1274
1106
  }
1275
1107
 
1276
- if (
1277
- !this._dying &&
1278
- (immediateReconnect || (this._autoRetry && !this._reconnectTimer))
1279
- ) {
1108
+ if (this._dying) {
1109
+ clearTimeout(this._reconnectTimer);
1110
+ } else if (immediateReconnect || (this._autoRetry && !this._reconnectTimer)) {
1280
1111
  this._reconnectTimer = setTimeout(
1281
1112
  () => {
1282
1113
  this._reconnectTimer = 0;
@@ -1302,13 +1133,18 @@ function _doConnect(connectHello) {
1302
1133
 
1303
1134
  const grpcClient = this._voltClient.getVoltAPIClient();
1304
1135
 
1305
- let helloPayload = connectHello;
1306
- if (!helloPayload) {
1307
- log$3("defaulting hello payload");
1308
- helloPayload = {
1309
- hello: {}
1310
- };
1311
- }
1136
+ // Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
1137
+ // within this time, it will assume the connection is dead.
1138
+ const defaultHello = {
1139
+ ping_interval: this._pingInterval,
1140
+ timestamp: Date.now()
1141
+ };
1142
+
1143
+ const helloPayload = { ...connectHello } || {};
1144
+
1145
+ helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
1146
+
1147
+ log$3("hello is %j", helloPayload);
1312
1148
 
1313
1149
  this.call.on("error", (err) => {
1314
1150
  log$3("error on connection stream [%s]", err.message);
@@ -1326,17 +1162,22 @@ function _doConnect(connectHello) {
1326
1162
  clearTimeout(this._pingTimeoutTimer);
1327
1163
  }
1328
1164
 
1329
- // Schedule the next ping.
1330
- _startPingTimer.call(this);
1331
-
1332
1165
  this.emit("ping", response.ping.timestamp);
1333
1166
  break;
1334
1167
  }
1335
1168
  case "acknowledge": {
1336
1169
  if (!this._connectionId) {
1337
1170
  this._connectionId = response.acknowledge.connection_id;
1171
+
1172
+ // Set the ping timeout interval to the value returned by the Volt. If we don't
1173
+ // hear from the Volt in this time, we'll assume the connection is dead.
1174
+ this._timeoutInterval = response.acknowledge.ping_interval;
1175
+
1338
1176
  log$3("connection id is %s", this._connectionId);
1339
1177
  this.emit("connected", this._connectionId);
1178
+
1179
+ // Schedule the next ping.
1180
+ _startPingTimer.call(this);
1340
1181
  }
1341
1182
  break;
1342
1183
  }
@@ -1364,8 +1205,6 @@ function _doConnect(connectHello) {
1364
1205
  });
1365
1206
 
1366
1207
  this.call.start(grpcClient, helloPayload);
1367
-
1368
- _startPingTimer.call(this);
1369
1208
  }
1370
1209
 
1371
1210
  function _startPingTimer() {
@@ -1392,6 +1231,8 @@ function _startPingTimer() {
1392
1231
  _connectionTimeoutHandler.bind(this),
1393
1232
  this._timeoutInterval
1394
1233
  );
1234
+
1235
+ _startPingTimer.call(this);
1395
1236
  }, this._pingInterval);
1396
1237
  }
1397
1238
 
@@ -1433,19 +1274,35 @@ class VoltConnection extends EventEmitter__default["default"] {
1433
1274
  }
1434
1275
  }
1435
1276
 
1277
+ const {
1278
+ aesDecrypt,
1279
+ aesEncrypt,
1280
+ createEncryptionKey,
1281
+ deriveSharedKey,
1282
+ formatPEM,
1283
+ fromBase64,
1284
+ fromBase64Url,
1285
+ keyFromPem,
1286
+ publicKeyToPem: publicKeyToPem$1,
1287
+ randomBytes,
1288
+ signBase64: signBase64$1,
1289
+ stripPEMHeaders,
1290
+ x25519
1291
+ } = js.voltUtils;
1292
+
1436
1293
  const log$2 = debug__default["default"]("rpc-invocation");
1437
1294
 
1438
1295
  class RpcInvocation extends EventEmitter__default["default"] {
1439
1296
  #voltClient = null;
1440
1297
  #voltConnection = null;
1441
1298
  #token = null;
1442
- #encryptKey = null;
1443
- #encryptIV = null;
1444
1299
  #invokeId = null;
1445
1300
  #payload = null;
1446
1301
  #isJSON = false;
1447
1302
  #methodDescriptor = null;
1448
1303
  #methodName = null;
1304
+ #derivedKey = null;
1305
+ #started = false;
1449
1306
 
1450
1307
  constructor(voltClient, voltConnection) {
1451
1308
  super();
@@ -1489,51 +1346,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
1489
1346
  this.#methodDescriptor = methodDescriptor;
1490
1347
  }
1491
1348
 
1492
- #decodeToken(token) {
1349
+ get started() {
1350
+ return this.#started;
1351
+ }
1352
+
1353
+ #keyExchange(token) {
1493
1354
  // We don't verify the token at this point, as we don't know the public key.
1494
1355
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
1495
1356
  this.#token = jwt__default["default"].decode(token);
1496
1357
 
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
- }
1358
+ // Store the JWT key id (kid) for use in the response.
1359
+ const callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
1360
+ const callerKey = keyFromPem(callerPublicKeyPem, "", x25519).publicKey;
1361
+
1362
+ const ephemeralKeyPem = createEncryptionKey();
1363
+ const ephemeralKey = keyFromPem(ephemeralKeyPem, "", x25519);
1364
+ const ephemeralPublicKey = fromBase64(
1365
+ stripPEMHeaders(publicKeyToPem$1(ephemeralKey.publicKey, x25519))
1366
+ );
1367
+
1368
+ this.#derivedKey = deriveSharedKey(
1369
+ ephemeralKey.privateKey,
1370
+ callerKey,
1371
+ x25519
1372
+ );
1373
+
1374
+ const nonce = randomBytes(16);
1508
1375
 
1509
- return Promise.resolve(this.#token);
1376
+ // The signed message is the encryption key and the nonce.
1377
+ const message = new Uint8Array(ephemeralPublicKey.length + nonce.length);
1378
+ message.set(ephemeralPublicKey);
1379
+ message.set(nonce, ephemeralPublicKey.length);
1380
+
1381
+ const signature = signBase64$1(
1382
+ this.#voltClient.credential.getKey().privateKey,
1383
+ message
1384
+ );
1385
+
1386
+ const keyExchangeResponse = {
1387
+ invoke_id: this.#invokeId,
1388
+ key_exchange: {
1389
+ nonce,
1390
+ encryption_key: ephemeralPublicKey,
1391
+ signature: fromBase64Url(signature)
1392
+ }
1393
+ };
1394
+
1395
+ this.#voltConnection.send({
1396
+ invoke_response: keyExchangeResponse
1397
+ });
1398
+
1399
+ return Promise.resolve(keyExchangeResponse);
1510
1400
  }
1511
1401
 
1512
1402
  initialise(invoke_request) {
1513
1403
  this.#invokeId = invoke_request.invoke_id;
1514
1404
 
1515
- return this.#decodeToken(invoke_request.token)
1516
- .then(() => {
1517
- // Parse the initial payload.
1518
- return this.parsePayload(invoke_request);
1519
- })
1520
- .catch((err) => {
1405
+ if (!this.#derivedKey) {
1406
+ // We don't have the derived key yet, so we need to perform the key exchange.
1407
+ return this.#keyExchange(invoke_request.token).catch((err) => {
1521
1408
  log$2("failure initialising invocation: %s", err.message);
1522
1409
  return Promise.reject(err);
1523
1410
  });
1411
+ } else {
1412
+ // We already have the derived key, so we can just parse the payload.
1413
+ return this.parsePayload(invoke_request);
1414
+ }
1524
1415
  }
1525
1416
 
1526
1417
  parsePayload(invoke_request) {
1418
+ this.#started = true;
1419
+
1527
1420
  return new Promise((resolve, reject) => {
1421
+ const encryptIV = invoke_request.iv;
1422
+
1528
1423
  if (invoke_request.payload) {
1529
1424
  let decryptedPayload;
1530
1425
 
1531
- if (this.#encryptKey) {
1426
+ if (encryptIV) {
1532
1427
  // Decrypt the actual payload using the shared key and iv.
1533
- decryptedPayload = VoltCredential.aesDecrypt(
1534
- invoke_request.payload,
1535
- this.#encryptKey,
1536
- this.#encryptIV
1428
+ decryptedPayload = aesDecrypt(
1429
+ this.#derivedKey,
1430
+ encryptIV,
1431
+ invoke_request.payload
1537
1432
  );
1538
1433
  } else {
1539
1434
  // No encryption => just use the payload.
@@ -1569,12 +1464,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
1569
1464
 
1570
1465
  this.#isJSON = true;
1571
1466
 
1572
- if (this.#encryptKey) {
1467
+ if (this.#derivedKey) {
1573
1468
  // 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
1469
+ decryptedPayload = aesDecrypt(
1470
+ this.#derivedKey,
1471
+ invoke_request.iv,
1472
+ Buffer.from(invoke_request.json_payload, "base64")
1578
1473
  );
1579
1474
  } else {
1580
1475
  // No encryption => just use the payload.
@@ -1633,17 +1528,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
1633
1528
  });
1634
1529
  }
1635
1530
 
1636
- if (this.#token.sk) {
1531
+ const iv = randomBytes(16);
1532
+ if (this.#derivedKey) {
1637
1533
  // Encrypt the response using the shared key and iv.
1638
- responsePayload = VoltCredential.aesEncrypt(
1639
- responsePayload,
1640
- this.#encryptKey,
1641
- this.#encryptIV
1642
- );
1534
+ responsePayload = aesEncrypt(this.#derivedKey, iv, responsePayload);
1643
1535
  }
1644
1536
 
1645
1537
  const invokeResponse = {
1646
- invoke_id: this.#invokeId
1538
+ invoke_id: this.#invokeId,
1539
+ iv
1647
1540
  };
1648
1541
 
1649
1542
  if (this.#isJSON) {
@@ -1678,18 +1571,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
1678
1571
  endPayload = tunnelMethod.requestSerialize(endPayload);
1679
1572
  }
1680
1573
 
1681
- if (this.#token.sk) {
1574
+ const iv = randomBytes(16);
1575
+ if (this.#derivedKey) {
1682
1576
  // Encrypt the response using the shared key and iv.
1683
- endPayload = VoltCredential.aesEncrypt(
1684
- endPayload,
1685
- this.#encryptKey,
1686
- this.#encryptIV
1687
- );
1577
+ endPayload = aesEncrypt(this.#derivedKey, iv, endPayload);
1688
1578
  }
1689
1579
 
1690
1580
  const invokeResponse = {
1691
1581
  invoke_id: this.#invokeId,
1692
- server_end: true
1582
+ server_end: true,
1583
+ iv
1693
1584
  };
1694
1585
 
1695
1586
  if (this.#isJSON) {
@@ -1706,7 +1597,8 @@ class RpcInvocation extends EventEmitter__default["default"] {
1706
1597
 
1707
1598
  /* eslint-disable no-underscore-dangle */
1708
1599
 
1709
- const { pki } = forge__default["default"];
1600
+ const { publicKeyToPem, removeCarriageReturn, signBase64 } = js.voltUtils;
1601
+
1710
1602
  const { filter, pick } = lodash__default["default"];
1711
1603
 
1712
1604
  const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
@@ -1742,9 +1634,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
1742
1634
  // This is the signing CA used by the volt.
1743
1635
  this._credential.cache.ca = authenticateResponse.chain;
1744
1636
 
1745
- // Identity resource id is assigned by the volt.
1637
+ // The session id is assigned by the volt.
1746
1638
  this._credential.cache.session_id = authenticateResponse.session_id;
1747
1639
 
1640
+ if (!this._credential.cache.identity_did) {
1641
+ // Identity DID has been assigned by the volt.
1642
+ this._credential.cache.identity_did =
1643
+ authenticateResponse.identity_did;
1644
+ }
1645
+
1748
1646
  this._credential.saveCache();
1749
1647
  break;
1750
1648
  }
@@ -1755,6 +1653,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
1755
1653
  case "POLICY_DECISION_PROMPT":
1756
1654
  case "POLICY_DECISION_PENDING": {
1757
1655
  log$1("access pending approval - waiting...");
1656
+
1657
+ // This is the signing CA used by the volt.
1658
+ this._credential.cache.ca = authenticateResponse.chain;
1659
+
1660
+ // The session id is assigned by the volt.
1661
+ this._credential.cache.session_id = authenticateResponse.session_id;
1662
+
1663
+ if (!this._credential.cache.identity_did) {
1664
+ // Identity DID has been assigned by the volt.
1665
+ this._credential.cache.identity_did =
1666
+ authenticateResponse.identity_did;
1667
+ }
1668
+
1669
+ this._credential.saveCache();
1758
1670
  break;
1759
1671
  }
1760
1672
  default:
@@ -1779,7 +1691,10 @@ function findDIDDocumentService(document, serviceType) {
1779
1691
  return matches;
1780
1692
  }
1781
1693
 
1782
- async function authenticateInternal(useDID = true) {
1694
+ async function authenticateInternal(
1695
+ sessionOnly = false,
1696
+ ownDID = false
1697
+ ) {
1783
1698
  try {
1784
1699
  // Check if we need to resolve a Relay volt.
1785
1700
  if (
@@ -1791,7 +1706,7 @@ async function authenticateInternal(useDID = true) {
1791
1706
  log$1("attempting to retrieve Relay information from %s", relayURL);
1792
1707
  this._voltConfig.relay = await fetchVoltConfig.call(
1793
1708
  this,
1794
- `${relayURL}/discovery`
1709
+ `${relayURL}/discover`
1795
1710
  );
1796
1711
 
1797
1712
  if (this._voltConfig.relay.ca_pem) {
@@ -1817,7 +1732,7 @@ async function authenticateInternal(useDID = true) {
1817
1732
 
1818
1733
  const keyPair = this._credential.getKey();
1819
1734
  const publicKeyPem = removeCarriageReturn(
1820
- pki.publicKeyToPem(keyPair.publicKey)
1735
+ publicKeyToPem(keyPair.publicKey)
1821
1736
  );
1822
1737
 
1823
1738
  if (!this._credential.cache.bindIp) {
@@ -1830,7 +1745,7 @@ async function authenticateInternal(useDID = true) {
1830
1745
 
1831
1746
  // Form the request message.
1832
1747
  const authenticateRequest = {
1833
- binding_name: this._config.client_name,
1748
+ client_name: this._config.client_name,
1834
1749
  host: this._credential.cache.bindIp
1835
1750
  };
1836
1751
 
@@ -1845,68 +1760,58 @@ async function authenticateInternal(useDID = true) {
1845
1760
  );
1846
1761
  }
1847
1762
 
1848
- if (!useDID) {
1763
+ if (sessionOnly) {
1764
+ // We want to create a session-based authentication based simply on our key, rather than a
1765
+ // fully-blown DID-based authentication.
1766
+ log$1("creating session-only request");
1849
1767
  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
- };
1768
+ } else if (!this._credential.cache.identity_did) {
1769
+ if (!ownDID) {
1770
+ // We don't want to have to manage our own DID, so we ask the Volt to do it for us.
1771
+ // The Volt will create a new DID and return it to us in the response.
1772
+ log$1("creating DID-based request");
1773
+ authenticateRequest.did_public_key = publicKeyPem;
1774
+ } else {
1775
+ // We want to create and manage our own DID.
1776
+ const did =
1777
+ (this._credential.cache.identity_did = `did:volt:${uuid.v4()}`);
1778
+
1779
+ // Order of properties in the DID document is important,
1780
+ // they must be in alphabetical order throughout the document.
1781
+ // This is because the DID document is signed and the signature
1782
+ // is over the entire document.
1783
+ const didDocument = {
1784
+ "@context": "https://www.w3.org/ns/did/v1",
1785
+ authentication: [`${did}#key-1`],
1786
+ controller: did,
1787
+ description: this._config.client_name,
1788
+ id: did,
1789
+ verificationMethod: [
1790
+ {
1791
+ id: `${did}#key-1`,
1792
+ publicKeyPem: publicKeyPem,
1793
+ type: "Ed25519VerificationKey2018"
1794
+ }
1795
+ ]
1796
+ };
1895
1797
 
1896
- // Set the DID document with proof in the authenticate request.
1897
- authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1798
+ // Set the DID document with proof in the authenticate request.
1799
+ authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1898
1800
 
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
- );
1801
+ // Now sign the entire DID document, including the proof and set it in the
1802
+ // bind request. This is required by the Volt to verify that we have the private key for the DID.
1803
+ authenticateRequest.did_document_signature = signBase64(
1804
+ keyPair.privateKey,
1805
+ authenticateRequest.did_document
1806
+ );
1905
1807
 
1906
- log$1("did document is %s", authenticateRequest.did_document);
1907
- log$1("did signature is %s", authenticateRequest.did_document_signature);
1808
+ log$1("did document is %s", authenticateRequest.did_document);
1809
+ log$1("did signature is %s", authenticateRequest.did_document_signature);
1810
+ }
1908
1811
  } else {
1909
- authenticateRequest.did = this._credential.cache.identity_id;
1812
+ // We already have a DID cached, so re-use it.
1813
+ log$1("using cached DID %s", this._credential.cache.identity_did);
1814
+ authenticateRequest.did = this._credential.cache.identity_did;
1910
1815
  }
1911
1816
 
1912
1817
  log$1("authenticate TTL is ", this._config.bindRequestTTL);
@@ -1975,6 +1880,11 @@ function connectInternal(helloPayload) {
1975
1880
  this._voltConnection.on("error", (err) => {
1976
1881
  // Should auto-reconnect.
1977
1882
  log$1("Volt connection error [%s]", err.message);
1883
+ if (this._connected) {
1884
+ onDisconnected();
1885
+ this.emit("connected", false);
1886
+ }
1887
+
1978
1888
  if (err.message.endsWith("session invalid")) {
1979
1889
  log$1("Volt session invalid - clearing session");
1980
1890
  delete this._credential.cache.session_id;
@@ -1982,11 +1892,6 @@ function connectInternal(helloPayload) {
1982
1892
  this._credential.saveCache();
1983
1893
  }
1984
1894
 
1985
- if (this._connected) {
1986
- onDisconnected();
1987
- this.emit("connected", false);
1988
- }
1989
-
1990
1895
  this.emit("connectionError", err);
1991
1896
  });
1992
1897
 
@@ -2000,10 +1905,24 @@ function connectInternal(helloPayload) {
2000
1905
 
2001
1906
  this._voltConnection.on("invoke_request", (invoke_request) => {
2002
1907
  const invokeId = invoke_request.invoke_id;
2003
- if (this._activeRPC[invokeId]) {
2004
- this._activeRPC[invokeId].parsePayload(invoke_request);
1908
+
1909
+ let rpcInvocation = this._activeRPC[invokeId];
1910
+
1911
+ if (rpcInvocation) {
1912
+ if (rpcInvocation.started) {
1913
+ rpcInvocation.parsePayload(invoke_request);
1914
+ } else {
1915
+ rpcInvocation
1916
+ .initialise(invoke_request)
1917
+ .then(() => {
1918
+ this.emit("invoke_request", rpcInvocation);
1919
+ })
1920
+ .catch((err) => {
1921
+ log$1("invoke_request - error [%s]", err.message);
1922
+ });
1923
+ }
2005
1924
  } else {
2006
- const rpcInvocation = new RpcInvocation(this, this._voltConnection);
1925
+ rpcInvocation = new RpcInvocation(this, this._voltConnection);
2007
1926
 
2008
1927
  rpcInvocation.on("end", () => {
2009
1928
  log$1("removing active RPC [%s]", invokeId);
@@ -2012,9 +1931,11 @@ function connectInternal(helloPayload) {
2012
1931
 
2013
1932
  rpcInvocation
2014
1933
  .initialise(invoke_request)
2015
- .then(() => {
1934
+ .then((response) => {
2016
1935
  this._activeRPC[invokeId] = rpcInvocation;
2017
- this.emit("invoke_request", rpcInvocation);
1936
+ if (!response.key_exchange) {
1937
+ this.emit("invoke_request", rpcInvocation);
1938
+ }
2018
1939
  })
2019
1940
  .catch((err) => {
2020
1941
  log$1("invoke_request - error [%s]", err.message);
@@ -2160,6 +2081,7 @@ async function fetchVoltConfig(discovery_url) {
2160
2081
  "public_key",
2161
2082
  "fingerprint",
2162
2083
  "address",
2084
+ "http_address",
2163
2085
  "ca_pem",
2164
2086
  "challenge_code",
2165
2087
  "cloud",
@@ -2247,6 +2169,7 @@ class VoltClient extends EventEmitter__default["default"] {
2247
2169
  /**
2248
2170
  * Initialises a binding and connection to the volt.
2249
2171
  * @param {string} [config] - the location of the Volt configuration file, or a configuration object
2172
+ * @param {object} [extras] - additional configuration properties
2250
2173
  */
2251
2174
  async initialise(config, extras = {}) {
2252
2175
  try {
@@ -2262,10 +2185,15 @@ class VoltClient extends EventEmitter__default["default"] {
2262
2185
  configPath = path__default["default"].resolve(config);
2263
2186
  log("Attempt to load config from file %s", configPath);
2264
2187
  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);
2188
+ if (fs__default["default"].existsSync(configPath)) {
2189
+ const configContents = await readFile(
2190
+ 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)))
2191
+ );
2192
+ configJSON = JSON.parse(configContents);
2193
+ } else {
2194
+ // There is currently no file at the given configuration path.
2195
+ configJSON = {};
2196
+ }
2269
2197
  } catch (err) {
2270
2198
  log("failure loading config file %s [%s]", configPath, err.message);
2271
2199
  throw new Error(
@@ -2282,15 +2210,15 @@ class VoltClient extends EventEmitter__default["default"] {
2282
2210
  throw new Error("config argument is required to be an object");
2283
2211
  }
2284
2212
 
2213
+ this._config = { ...extras, ...configJSON };
2214
+
2285
2215
  if (
2286
- !configJSON.client_name ||
2287
- typeof configJSON.client_name !== "string"
2216
+ !this._config.client_name ||
2217
+ typeof this._config.client_name !== "string"
2288
2218
  ) {
2289
2219
  throw new Error("client_name property required in config");
2290
2220
  }
2291
2221
 
2292
- this._config = { ...configJSON, ...extras };
2293
-
2294
2222
  let voltDID = "";
2295
2223
  let voltHttpAddress = "";
2296
2224
  if (typeof this._config.volt === "string") {
@@ -2299,6 +2227,7 @@ class VoltClient extends EventEmitter__default["default"] {
2299
2227
  } else {
2300
2228
  voltHttpAddress = this._config.volt;
2301
2229
  }
2230
+ delete this._config.volt;
2302
2231
  } else if (typeof this._config.volt !== "object") {
2303
2232
  throw new Error("configuration is missing the 'volt' object");
2304
2233
  } else {
@@ -2313,7 +2242,7 @@ class VoltClient extends EventEmitter__default["default"] {
2313
2242
  } else if (voltHttpAddress && !this._config?.volt?.id) {
2314
2243
  const discoConfig = await fetchVoltConfig.call(
2315
2244
  this,
2316
- `${voltHttpAddress}/discovery`
2245
+ `${voltHttpAddress}/discover`
2317
2246
  );
2318
2247
  this._config = {
2319
2248
  ...this._config,
@@ -2322,8 +2251,12 @@ class VoltClient extends EventEmitter__default["default"] {
2322
2251
  }
2323
2252
 
2324
2253
  this._voltConfig = this._config.volt;
2325
- this._voltConfig.did = voltDID;
2326
- this._voltConfig.http_address = voltHttpAddress;
2254
+ if (voltDID) {
2255
+ this._voltConfig.did = voltDID;
2256
+ }
2257
+ if (voltHttpAddress) {
2258
+ this._voltConfig.http_address = voltHttpAddress;
2259
+ }
2327
2260
 
2328
2261
  if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
2329
2262
  throw new Error("'id' property is missing in Volt configuration");
@@ -2371,9 +2304,12 @@ class VoltClient extends EventEmitter__default["default"] {
2371
2304
  if (!isBound) {
2372
2305
  // This is usually because of request being denied.
2373
2306
  throw new Error("Volt authentication failed");
2307
+ } else {
2308
+ // Perist the configuration.
2309
+ this._config = this._credential.saveCache();
2374
2310
  }
2375
2311
 
2376
- return Promise.resolve();
2312
+ return Promise.resolve(this._config);
2377
2313
  } catch (err) {
2378
2314
  log("Failure starting Volt client [%s]", err.message);
2379
2315
  throw err;
@@ -2840,6 +2776,13 @@ class VoltClient extends EventEmitter__default["default"] {
2840
2776
  });
2841
2777
  }
2842
2778
 
2779
+ SqlTransaction(request) {
2780
+ const grpcClient = this.getVoltAPIClient();
2781
+
2782
+ const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
2783
+ return call.start(grpcClient, request);
2784
+ }
2785
+
2843
2786
  /**
2844
2787
  * WireAPI
2845
2788
  */