@tdxvolt/volt-client-grpc 0.16.0-beta.10 → 0.16.0-beta.13

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
578
 
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
-
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,
@@ -1301,13 +1183,18 @@ function _doConnect(connectHello) {
1301
1183
 
1302
1184
  const grpcClient = this._voltClient.getVoltAPIClient();
1303
1185
 
1304
- let helloPayload = connectHello;
1305
- if (!helloPayload) {
1306
- log$3("defaulting hello payload");
1307
- helloPayload = {
1308
- hello: {}
1309
- };
1310
- }
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);
1311
1198
 
1312
1199
  this.call.on("error", (err) => {
1313
1200
  log$3("error on connection stream [%s]", err.message);
@@ -1325,17 +1212,22 @@ function _doConnect(connectHello) {
1325
1212
  clearTimeout(this._pingTimeoutTimer);
1326
1213
  }
1327
1214
 
1328
- // Schedule the next ping.
1329
- _startPingTimer.call(this);
1330
-
1331
1215
  this.emit("ping", response.ping.timestamp);
1332
1216
  break;
1333
1217
  }
1334
1218
  case "acknowledge": {
1335
1219
  if (!this._connectionId) {
1336
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
+
1337
1226
  log$3("connection id is %s", this._connectionId);
1338
1227
  this.emit("connected", this._connectionId);
1228
+
1229
+ // Schedule the next ping.
1230
+ _startPingTimer.call(this);
1339
1231
  }
1340
1232
  break;
1341
1233
  }
@@ -1363,8 +1255,6 @@ function _doConnect(connectHello) {
1363
1255
  });
1364
1256
 
1365
1257
  this.call.start(grpcClient, helloPayload);
1366
-
1367
- _startPingTimer.call(this);
1368
1258
  }
1369
1259
 
1370
1260
  function _startPingTimer() {
@@ -1391,6 +1281,8 @@ function _startPingTimer() {
1391
1281
  _connectionTimeoutHandler.bind(this),
1392
1282
  this._timeoutInterval
1393
1283
  );
1284
+
1285
+ _startPingTimer.call(this);
1394
1286
  }, this._pingInterval);
1395
1287
  }
1396
1288
 
@@ -1438,13 +1330,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
1438
1330
  #voltClient = null;
1439
1331
  #voltConnection = null;
1440
1332
  #token = null;
1441
- #encryptKey = null;
1442
- #encryptIV = null;
1443
1333
  #invokeId = null;
1444
1334
  #payload = null;
1445
1335
  #isJSON = false;
1446
1336
  #methodDescriptor = null;
1447
1337
  #methodName = null;
1338
+ #derivedKey = null;
1339
+ #started = false;
1448
1340
 
1449
1341
  constructor(voltClient, voltConnection) {
1450
1342
  super();
@@ -1488,51 +1380,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
1488
1380
  this.#methodDescriptor = methodDescriptor;
1489
1381
  }
1490
1382
 
1491
- #decodeToken(token) {
1383
+ get started() {
1384
+ return this.#started;
1385
+ }
1386
+
1387
+ #keyExchange(token) {
1492
1388
  // We don't verify the token at this point, as we don't know the public key.
1493
1389
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
1494
1390
  this.#token = jwt__default["default"].decode(token);
1495
1391
 
1496
- if (this.#token.sk) {
1497
- // Decrypt the shared key and iv using the private key.
1498
- this.#encryptKey = VoltCredential.rsaDecrypt(
1499
- this.#voltClient.credential.cache.key,
1500
- this.#token.sk
1501
- );
1502
- this.#encryptIV = VoltCredential.rsaDecrypt(
1503
- this.#voltClient.credential.cache.key,
1504
- this.#token.iv
1505
- );
1506
- }
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
+ );
1507
1407
 
1508
- return Promise.resolve(this.#token);
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
+ });
1432
+
1433
+ return Promise.resolve(keyExchangeResponse);
1509
1434
  }
1510
1435
 
1511
1436
  initialise(invoke_request) {
1512
1437
  this.#invokeId = invoke_request.invoke_id;
1513
1438
 
1514
- return this.#decodeToken(invoke_request.token)
1515
- .then(() => {
1516
- // Parse the initial payload.
1517
- return this.parsePayload(invoke_request);
1518
- })
1519
- .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) => {
1520
1442
  log$2("failure initialising invocation: %s", err.message);
1521
1443
  return Promise.reject(err);
1522
1444
  });
1445
+ } else {
1446
+ // We already have the derived key, so we can just parse the payload.
1447
+ return this.parsePayload(invoke_request);
1448
+ }
1523
1449
  }
1524
1450
 
1525
1451
  parsePayload(invoke_request) {
1452
+ this.#started = true;
1453
+
1526
1454
  return new Promise((resolve, reject) => {
1455
+ const encryptIV = invoke_request.iv;
1456
+
1527
1457
  if (invoke_request.payload) {
1528
1458
  let decryptedPayload;
1529
1459
 
1530
- if (this.#encryptKey) {
1460
+ if (encryptIV) {
1531
1461
  // Decrypt the actual payload using the shared key and iv.
1532
- decryptedPayload = VoltCredential.aesDecrypt(
1533
- invoke_request.payload,
1534
- this.#encryptKey,
1535
- this.#encryptIV
1462
+ decryptedPayload = voltClientWeb.aesDecrypt(
1463
+ this.#derivedKey,
1464
+ encryptIV,
1465
+ invoke_request.payload
1536
1466
  );
1537
1467
  } else {
1538
1468
  // No encryption => just use the payload.
@@ -1568,12 +1498,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
1568
1498
 
1569
1499
  this.#isJSON = true;
1570
1500
 
1571
- if (this.#encryptKey) {
1501
+ if (this.#derivedKey) {
1572
1502
  // Decrypt the actual payload using the shared key and iv.
1573
- decryptedPayload = VoltCredential.aesDecrypt(
1574
- Buffer.from(invoke_request.json_payload, "base64"),
1575
- this.#encryptKey,
1576
- this.#encryptIV
1503
+ decryptedPayload = voltClientWeb.aesDecrypt(
1504
+ this.#derivedKey,
1505
+ invoke_request.iv,
1506
+ Buffer.from(invoke_request.json_payload, "base64")
1577
1507
  );
1578
1508
  } else {
1579
1509
  // No encryption => just use the payload.
@@ -1632,17 +1562,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
1632
1562
  });
1633
1563
  }
1634
1564
 
1635
- if (this.#token.sk) {
1565
+ const iv = voltClientWeb.randomBytes(16);
1566
+ if (this.#derivedKey) {
1636
1567
  // Encrypt the response using the shared key and iv.
1637
- responsePayload = VoltCredential.aesEncrypt(
1638
- responsePayload,
1639
- this.#encryptKey,
1640
- this.#encryptIV
1641
- );
1568
+ responsePayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, responsePayload);
1642
1569
  }
1643
1570
 
1644
1571
  const invokeResponse = {
1645
- invoke_id: this.#invokeId
1572
+ invoke_id: this.#invokeId,
1573
+ iv
1646
1574
  };
1647
1575
 
1648
1576
  if (this.#isJSON) {
@@ -1677,18 +1605,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
1677
1605
  endPayload = tunnelMethod.requestSerialize(endPayload);
1678
1606
  }
1679
1607
 
1680
- if (this.#token.sk) {
1608
+ const iv = voltClientWeb.randomBytes(16);
1609
+ if (this.#derivedKey) {
1681
1610
  // Encrypt the response using the shared key and iv.
1682
- endPayload = VoltCredential.aesEncrypt(
1683
- endPayload,
1684
- this.#encryptKey,
1685
- this.#encryptIV
1686
- );
1611
+ endPayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, endPayload);
1687
1612
  }
1688
1613
 
1689
1614
  const invokeResponse = {
1690
1615
  invoke_id: this.#invokeId,
1691
- server_end: true
1616
+ server_end: true,
1617
+ iv
1692
1618
  };
1693
1619
 
1694
1620
  if (this.#isJSON) {
@@ -1705,7 +1631,6 @@ class RpcInvocation extends EventEmitter__default["default"] {
1705
1631
 
1706
1632
  /* eslint-disable no-underscore-dangle */
1707
1633
 
1708
- const { pki } = forge__default["default"];
1709
1634
  const { filter, pick } = lodash__default["default"];
1710
1635
 
1711
1636
  const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
@@ -1741,9 +1666,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
1741
1666
  // This is the signing CA used by the volt.
1742
1667
  this._credential.cache.ca = authenticateResponse.chain;
1743
1668
 
1744
- // Identity resource id is assigned by the volt.
1669
+ // The session id is assigned by the volt.
1745
1670
  this._credential.cache.session_id = authenticateResponse.session_id;
1746
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
+
1747
1678
  this._credential.saveCache();
1748
1679
  break;
1749
1680
  }
@@ -1754,6 +1685,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
1754
1685
  case "POLICY_DECISION_PROMPT":
1755
1686
  case "POLICY_DECISION_PENDING": {
1756
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();
1757
1702
  break;
1758
1703
  }
1759
1704
  default:
@@ -1778,7 +1723,10 @@ function findDIDDocumentService(document, serviceType) {
1778
1723
  return matches;
1779
1724
  }
1780
1725
 
1781
- async function authenticateInternal(useDID = true) {
1726
+ async function authenticateInternal(
1727
+ sessionOnly = false,
1728
+ ownDID = false
1729
+ ) {
1782
1730
  try {
1783
1731
  // Check if we need to resolve a Relay volt.
1784
1732
  if (
@@ -1790,7 +1738,7 @@ async function authenticateInternal(useDID = true) {
1790
1738
  log$1("attempting to retrieve Relay information from %s", relayURL);
1791
1739
  this._voltConfig.relay = await fetchVoltConfig.call(
1792
1740
  this,
1793
- `${relayURL}/discovery`
1741
+ `${relayURL}/discover`
1794
1742
  );
1795
1743
 
1796
1744
  if (this._voltConfig.relay.ca_pem) {
@@ -1815,8 +1763,8 @@ async function authenticateInternal(useDID = true) {
1815
1763
  }
1816
1764
 
1817
1765
  const keyPair = this._credential.getKey();
1818
- const publicKeyPem = removeCarriageReturn(
1819
- pki.publicKeyToPem(keyPair.publicKey)
1766
+ const publicKeyPem = voltClientWeb.removeCarriageReturn(
1767
+ voltClientWeb.publicKeyToPem(keyPair.publicKey)
1820
1768
  );
1821
1769
 
1822
1770
  if (!this._credential.cache.bindIp) {
@@ -1829,12 +1777,12 @@ async function authenticateInternal(useDID = true) {
1829
1777
 
1830
1778
  // Form the request message.
1831
1779
  const authenticateRequest = {
1832
- binding_name: this._config.client_name,
1780
+ client_name: this._config.client_name,
1833
1781
  host: this._credential.cache.bindIp
1834
1782
  };
1835
1783
 
1836
1784
  if (this._voltConfig.challenge_code) {
1837
- authenticateRequest.challenge = signBase64(
1785
+ authenticateRequest.challenge = voltClientWeb.signBase64(
1838
1786
  keyPair.privateKey,
1839
1787
  this._voltConfig.challenge_code
1840
1788
  );
@@ -1844,68 +1792,58 @@ async function authenticateInternal(useDID = true) {
1844
1792
  );
1845
1793
  }
1846
1794
 
1847
- 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");
1848
1799
  authenticateRequest.public_key = publicKeyPem;
1849
- } else if (!this._credential.cache.identity_id) {
1850
- // If we don't have an identity id, then we need to create a DID.
1851
- this._credential.cache.identity_id = `did:volt:${uuid.v4()}`;
1852
-
1853
- const did = this._credential.cache.identity_id;
1854
-
1855
- // Order of properties in the DID document is important,
1856
- // they must be in alphabetical order throughout the document.
1857
- // This is because the DID document is signed and the signature
1858
- // is over the entire document.
1859
- const didDocument = {
1860
- "@context": "https://www.w3.org/ns/did/v1",
1861
- authentication: [`${did}#key-1`],
1862
- description: this._config.client_name,
1863
- id: did,
1864
- verificationMethod: [
1865
- {
1866
- controller: did,
1867
- id: `${did}#key-1`,
1868
- publicKeyPem: publicKeyPem,
1869
- type: "RsaSignature2018"
1870
- }
1871
- ]
1872
- };
1873
-
1874
- const signDocument = JSON.stringify(didDocument, null, 2);
1875
- log$1("signing did document");
1876
- log$1(signDocument);
1877
-
1878
- // Sign the DID document.
1879
- const documentDigest = signBase64(
1880
- keyPair.privateKey,
1881
- signDocument
1882
- );
1883
-
1884
- const proofSignature = jwsSignature(documentDigest);
1885
-
1886
- // Create the DID document proof.
1887
- didDocument.proof = {
1888
- created: new Date().toISOString(),
1889
- jws: proofSignature,
1890
- proofPurpose: "assertionMethod",
1891
- type: "RsaSignature2018",
1892
- verificationMethod: `${did}#key-1`
1893
- };
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
+ };
1894
1829
 
1895
- // Set the DID document with proof in the authenticate request.
1896
- 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);
1897
1832
 
1898
- // Now sign the entire DID document, including the proof and set it in the
1899
- // bind request. This is required by the Volt to verify that we have the private key for the DID.
1900
- authenticateRequest.did_document_signature = signBase64(
1901
- keyPair.privateKey,
1902
- authenticateRequest.did_document
1903
- );
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
+ );
1904
1839
 
1905
- log$1("did document is %s", authenticateRequest.did_document);
1906
- 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
+ }
1907
1843
  } else {
1908
- 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;
1909
1847
  }
1910
1848
 
1911
1849
  log$1("authenticate TTL is ", this._config.bindRequestTTL);
@@ -1999,10 +1937,24 @@ function connectInternal(helloPayload) {
1999
1937
 
2000
1938
  this._voltConnection.on("invoke_request", (invoke_request) => {
2001
1939
  const invokeId = invoke_request.invoke_id;
2002
- if (this._activeRPC[invokeId]) {
2003
- 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
+ }
2004
1956
  } else {
2005
- const rpcInvocation = new RpcInvocation(this, this._voltConnection);
1957
+ rpcInvocation = new RpcInvocation(this, this._voltConnection);
2006
1958
 
2007
1959
  rpcInvocation.on("end", () => {
2008
1960
  log$1("removing active RPC [%s]", invokeId);
@@ -2011,9 +1963,11 @@ function connectInternal(helloPayload) {
2011
1963
 
2012
1964
  rpcInvocation
2013
1965
  .initialise(invoke_request)
2014
- .then(() => {
1966
+ .then((response) => {
2015
1967
  this._activeRPC[invokeId] = rpcInvocation;
2016
- this.emit("invoke_request", rpcInvocation);
1968
+ if (!response.key_exchange) {
1969
+ this.emit("invoke_request", rpcInvocation);
1970
+ }
2017
1971
  })
2018
1972
  .catch((err) => {
2019
1973
  log$1("invoke_request - error [%s]", err.message);
@@ -2159,6 +2113,7 @@ async function fetchVoltConfig(discovery_url) {
2159
2113
  "public_key",
2160
2114
  "fingerprint",
2161
2115
  "address",
2116
+ "http_address",
2162
2117
  "ca_pem",
2163
2118
  "challenge_code",
2164
2119
  "cloud",
@@ -2246,6 +2201,7 @@ class VoltClient extends EventEmitter__default["default"] {
2246
2201
  /**
2247
2202
  * Initialises a binding and connection to the volt.
2248
2203
  * @param {string} [config] - the location of the Volt configuration file, or a configuration object
2204
+ * @param {object} [extras] - additional configuration properties
2249
2205
  */
2250
2206
  async initialise(config, extras = {}) {
2251
2207
  try {
@@ -2261,10 +2217,15 @@ class VoltClient extends EventEmitter__default["default"] {
2261
2217
  configPath = path__default["default"].resolve(config);
2262
2218
  log("Attempt to load config from file %s", configPath);
2263
2219
  try {
2264
- const configContents = await readFile(
2265
- 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)))
2266
- );
2267
- 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
+ }
2268
2229
  } catch (err) {
2269
2230
  log("failure loading config file %s [%s]", configPath, err.message);
2270
2231
  throw new Error(
@@ -2281,15 +2242,15 @@ class VoltClient extends EventEmitter__default["default"] {
2281
2242
  throw new Error("config argument is required to be an object");
2282
2243
  }
2283
2244
 
2245
+ this._config = { ...extras, ...configJSON };
2246
+
2284
2247
  if (
2285
- !configJSON.client_name ||
2286
- typeof configJSON.client_name !== "string"
2248
+ !this._config.client_name ||
2249
+ typeof this._config.client_name !== "string"
2287
2250
  ) {
2288
2251
  throw new Error("client_name property required in config");
2289
2252
  }
2290
2253
 
2291
- this._config = { ...configJSON, ...extras };
2292
-
2293
2254
  let voltDID = "";
2294
2255
  let voltHttpAddress = "";
2295
2256
  if (typeof this._config.volt === "string") {
@@ -2298,6 +2259,7 @@ class VoltClient extends EventEmitter__default["default"] {
2298
2259
  } else {
2299
2260
  voltHttpAddress = this._config.volt;
2300
2261
  }
2262
+ delete this._config.volt;
2301
2263
  } else if (typeof this._config.volt !== "object") {
2302
2264
  throw new Error("configuration is missing the 'volt' object");
2303
2265
  } else {
@@ -2312,7 +2274,7 @@ class VoltClient extends EventEmitter__default["default"] {
2312
2274
  } else if (voltHttpAddress && !this._config?.volt?.id) {
2313
2275
  const discoConfig = await fetchVoltConfig.call(
2314
2276
  this,
2315
- `${voltHttpAddress}/discovery`
2277
+ `${voltHttpAddress}/discover`
2316
2278
  );
2317
2279
  this._config = {
2318
2280
  ...this._config,
@@ -2321,8 +2283,12 @@ class VoltClient extends EventEmitter__default["default"] {
2321
2283
  }
2322
2284
 
2323
2285
  this._voltConfig = this._config.volt;
2324
- this._voltConfig.did = voltDID;
2325
- this._voltConfig.http_address = voltHttpAddress;
2286
+ if (voltDID) {
2287
+ this._voltConfig.did = voltDID;
2288
+ }
2289
+ if (voltHttpAddress) {
2290
+ this._voltConfig.http_address = voltHttpAddress;
2291
+ }
2326
2292
 
2327
2293
  if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
2328
2294
  throw new Error("'id' property is missing in Volt configuration");
@@ -2370,9 +2336,12 @@ class VoltClient extends EventEmitter__default["default"] {
2370
2336
  if (!isBound) {
2371
2337
  // This is usually because of request being denied.
2372
2338
  throw new Error("Volt authentication failed");
2339
+ } else {
2340
+ // Perist the configuration.
2341
+ this._config = this._credential.saveCache();
2373
2342
  }
2374
2343
 
2375
- return Promise.resolve();
2344
+ return Promise.resolve(this._config);
2376
2345
  } catch (err) {
2377
2346
  log("Failure starting Volt client [%s]", err.message);
2378
2347
  throw err;
@@ -2839,6 +2808,13 @@ class VoltClient extends EventEmitter__default["default"] {
2839
2808
  });
2840
2809
  }
2841
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
+
2842
2818
  /**
2843
2819
  * WireAPI
2844
2820
  */