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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -4,37 +4,32 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var lodash = require('lodash');
6
6
  var ip = require('ip');
7
- var forge = require('node-forge');
8
- var bs58 = require('bs58');
9
7
  var debug = require('debug');
10
8
  var EventEmitter = require('events');
11
9
  var fs = require('fs');
12
- var jwt = require('jsonwebtoken');
13
- var crypto = require('crypto');
10
+ var voltClientWeb = require('@tdxvolt/volt-client-web');
14
11
  var path = require('path');
15
12
  var protoLoader = require('@grpc/proto-loader');
16
13
  var url = require('url');
17
14
  var bent = require('bent');
15
+ var jwt = require('jsonwebtoken');
18
16
  var uuid = require('uuid');
19
17
 
20
18
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
21
19
 
22
20
  var lodash__default = /*#__PURE__*/_interopDefaultLegacy(lodash);
23
21
  var ip__default = /*#__PURE__*/_interopDefaultLegacy(ip);
24
- var forge__default = /*#__PURE__*/_interopDefaultLegacy(forge);
25
- var bs58__default = /*#__PURE__*/_interopDefaultLegacy(bs58);
26
22
  var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug);
27
23
  var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
28
24
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
29
- var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
30
- var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
31
25
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
32
26
  var protoLoader__default = /*#__PURE__*/_interopDefaultLegacy(protoLoader);
33
27
  var bent__default = /*#__PURE__*/_interopDefaultLegacy(bent);
28
+ var jwt__default = /*#__PURE__*/_interopDefaultLegacy(jwt);
34
29
 
35
30
  // eslint-disable-next-line import/prefer-default-export
36
31
  const constants = {
37
- authTokenName: "tdx-token",
32
+ authTokenName: "volt-token",
38
33
  collectionOperation: {
39
34
  add: "ADD",
40
35
  remove: "REMOVE",
@@ -57,9 +52,9 @@ const constants = {
57
52
  ],
58
53
  userIdOID: "0.9.2342.19200300.100.1.1"
59
54
  },
60
- defaultDIDHostName: "cloud.tdxvolt.com",
55
+ defaultDIDHostName: "tdxvolt.com",
61
56
  didServiceType: {
62
- voltConfig: "tdx-volt-config"
57
+ voltConfig: "volt-discovery"
63
58
  },
64
59
  identityType: {
65
60
  did: "did",
@@ -70,8 +65,6 @@ const constants = {
70
65
  online: "online",
71
66
  unknown: "unknown"
72
67
  },
73
- publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
74
- privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
75
68
  privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
76
69
  resource: {
77
70
  identity: "ident",
@@ -108,8 +101,6 @@ const constants = {
108
101
  tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
109
102
  };
110
103
 
111
- const { pki: pki$2 } = forge__default["default"];
112
-
113
104
  const createSecureContextOptions = (cryptoOptions) => {
114
105
  const tlsOptions = {
115
106
  cert: Buffer.from(cryptoOptions.cert),
@@ -139,53 +130,7 @@ const getServiceAddress = (mdnsService) => {
139
130
  return address;
140
131
  };
141
132
 
142
- const privateKeyToPem = (keyPair) => {
143
- return pki$2.privateKeyToPem(keyPair.privateKey);
144
- };
145
-
146
- const publicKeyToPem = (keyPair) => {
147
- return pki$2.publicKeyToPem(keyPair.publicKey);
148
- };
149
-
150
- const deserialisePEM = (pem) => {
151
- return pem.replace(/\\r\\n/g, "\r\n");
152
- };
153
-
154
- const removeCarriageReturn = (inp) => {
155
- return inp.replace(/\r\n/g, "\n");
156
- };
157
-
158
- const createBase58Hash = (inp) => {
159
- const md = forge__default["default"].md.sha256.create();
160
- md.update(inp);
161
- const hashBuf = Buffer.from(md.digest().getBytes(), "binary");
162
- return bs58__default["default"].encode(hashBuf);
163
- };
164
-
165
- const createPublicKeyFingerprint = (key) => {
166
- // Remove carriage returns before creating hash to bring into line with openSSL / volt format.
167
- return createBase58Hash(removeCarriageReturn(key));
168
- };
169
-
170
- const base64ToBase64Url = (base64) => {
171
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
172
- };
173
-
174
- const signBase64 = (key, msg) => {
175
- const md = forge__default["default"].md.sha256.create();
176
- md.update(msg);
177
- const signature = key.sign(md);
178
- const base64Standard = forge__default["default"].util.encode64(signature);
179
- return base64ToBase64Url(base64Standard);
180
- };
181
-
182
- const verifyBase64 = (key, msg, signature) => {
183
- const md = forge__default["default"].md.sha256.create();
184
- md.update(msg);
185
- return key.verify(md.digest().bytes(), forge__default["default"].util.decode64(signature));
186
- };
187
-
188
- const getDIDResolutionURL = (didIn) => {
133
+ const getDIDResolutionURL = (didIn, registryUrl = "") => {
189
134
  const did = didIn.toLowerCase().split(":");
190
135
  if (did.length < 3) {
191
136
  return "";
@@ -195,97 +140,19 @@ const getDIDResolutionURL = (didIn) => {
195
140
  return "";
196
141
  }
197
142
 
198
- if (did[1] !== "tdx") {
143
+ if (did[1] !== "volt") {
199
144
  return "";
200
145
  }
201
146
 
202
- let hostName;
203
- let didGUID;
204
- if (did.length === 3) {
205
- hostName = constants.defaultDIDHostName;
206
- didGUID = did[2];
207
- } else {
208
- hostName = did[2];
209
- didGUID = did[3];
210
- }
211
-
212
- return `https://${hostName}/api/identity/${didGUID}`;
213
- };
214
-
215
- const forgePrivateKeyToPem = (decryptedKey) => {
216
- // Convert a Forge private key to an ASN.1 RSAPrivateKey
217
- const asnPrivateKey = pki$2.privateKeyToAsn1(decryptedKey);
218
-
219
- // Wrap an RSAPrivateKey ASN.1 object in a PKCS#8 ASN.1 PrivateKeyInfo
220
- const privateKeyInfo = pki$2.wrapRsaPrivateKey(asnPrivateKey);
221
-
222
- // Convert a PKCS#8 ASN.1 PrivateKeyInfo to PEM
223
- return pki$2.privateKeyInfoToPem(privateKeyInfo);
224
- };
225
-
226
- const createRSAKey = (passphrase = "") => {
227
- return new Promise((resolve, reject) => {
228
- pki$2.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
229
- if (err) {
230
- reject(err);
231
- } else {
232
- let pem;
233
- if (passphrase) {
234
- pem = pki$2.encryptRsaPrivateKey(keypair.privateKey, passphrase);
235
- } else {
236
- pem = forgePrivateKeyToPem(keypair.privateKey);
237
- }
238
-
239
- resolve({ keypair, pem });
240
- }
241
- });
242
- });
243
- };
244
-
245
- const sha256Base64 = (msg) => {
246
- const md = forge__default["default"].md.sha256.create();
247
- md.update(msg);
248
- const base64Standard = forge__default["default"].util.encode64(md.digest().bytes());
249
- return base64ToBase64Url(base64Standard);
250
- };
251
-
252
- const decryptPrivateKey = (pem, passphrase) => {
253
- const decryptedKey = pki$2.decryptRsaPrivateKey(pem, passphrase);
254
- return forgePrivateKeyToPem(decryptedKey);
255
- };
256
-
257
- const jwsSignature = (signature) => {
258
- //
259
- // Use an unattached payload JWS signature for the DID document proof.
260
- // This is a JWS with an empty payload and a detached payload digest.
261
- //
262
- // (https://tools.ietf.org/html/rfc7797#appendix-F) as used in the VC spec
263
- // (https://www.w3.org/TR/vc-data-model/).
264
- //
265
- const jwsHeader = base64ToBase64Url(
266
- forge__default["default"].util.encode64('{"alg":"RS256","b64": false}')
267
- );
268
- return jwsHeader + ".." + signature;
147
+ const lookupUrl = registryUrl || `https://${constants.defaultDIDHostName}`;
148
+ return `${lookupUrl}/did/${didIn}`;
269
149
  };
270
150
 
271
151
  var utils = /*#__PURE__*/Object.freeze({
272
152
  __proto__: null,
273
153
  createSecureContextOptions: createSecureContextOptions,
274
154
  getServiceAddress: getServiceAddress,
275
- privateKeyToPem: privateKeyToPem,
276
- publicKeyToPem: publicKeyToPem,
277
- deserialisePEM: deserialisePEM,
278
- removeCarriageReturn: removeCarriageReturn,
279
- createBase58Hash: createBase58Hash,
280
- createPublicKeyFingerprint: createPublicKeyFingerprint,
281
- base64ToBase64Url: base64ToBase64Url,
282
- signBase64: signBase64,
283
- verifyBase64: verifyBase64,
284
- getDIDResolutionURL: getDIDResolutionURL,
285
- createRSAKey: createRSAKey,
286
- sha256Base64: sha256Base64,
287
- decryptPrivateKey: decryptPrivateKey,
288
- jwsSignature: jwsSignature
155
+ getDIDResolutionURL: getDIDResolutionURL
289
156
  });
290
157
 
291
158
  /* eslint-disable no-underscore-dangle */
@@ -307,8 +174,11 @@ function createClient(
307
174
  ) {
308
175
  let grpcCredentials;
309
176
  if (credentials) {
310
- const ca = credentials.config?.volt?.relay?.ca_pem || credentials.cache.ca;
311
- const cert = credentials.cache.cloud_cert || credentials.cache.cert;
177
+ const relaying = !!credentials.config?.volt?.relay;
178
+ const ca = relaying
179
+ ? credentials.config?.volt?.relay?.ca_pem
180
+ : credentials.cache.ca;
181
+ const cert = relaying ? "" : credentials.cache.cert;
312
182
  const { key } = credentials.cache;
313
183
 
314
184
  if (key && cert) {
@@ -320,8 +190,9 @@ function createClient(
320
190
  tlsOptions.cert
321
191
  );
322
192
  } else {
323
- // Cache has no private key, this implies we are connecting without supplying a client certificate.
324
- // Simply connect using the CA certificate.
193
+ // Cache has no private key or certificate, this implies we are connecting without supplying a client certificate.
194
+ // We might be connecting to a relay, or making an initial Authenticate call.
195
+ // Simply connect using the CA certificate, and the a JWT will be used to authenticate.
325
196
  grpcCredentials = grpc.credentials.createSsl(Buffer.from(ca));
326
197
  }
327
198
  } else {
@@ -527,10 +398,7 @@ var grpcUtils = /*#__PURE__*/Object.freeze({
527
398
 
528
399
  /* eslint-disable no-underscore-dangle */
529
400
 
530
- const { pki: pki$1 } = forge__default["default"];
531
401
  const log$5 = debug__default["default"]("volt-client-grpc:volt-credential");
532
- const aesAlgorithm = "aes-256-cbc";
533
- const rs256Algorithm = "RS256";
534
402
 
535
403
  class VoltCredential {
536
404
  constructor(config, configPath) {
@@ -554,14 +422,13 @@ class VoltCredential {
554
422
  }
555
423
 
556
424
  // Need to unencrypt the private key.
557
- const decrypted = pki$1.decryptRsaPrivateKey(
558
- this._cryptoCache.key,
559
- config.p
560
- );
425
+ const decrypted = voltClientWeb.keyFromPem(this._cryptoCache.key, config.p);
426
+
561
427
  if (!decrypted) {
562
428
  throw new Error("failed to decrypt key - check passphrase");
563
429
  }
564
- this._cryptoCache.key = pki$1.privateKeyToPem(decrypted);
430
+
431
+ this._cryptoCache.key = voltClientWeb.privateKeyToPem(decrypted);
565
432
  }
566
433
 
567
434
  if (this._voltConfig.ca_pem) {
@@ -570,14 +437,11 @@ class VoltCredential {
570
437
  this._cryptoCache.ca = this._voltConfig.ca_pem;
571
438
  }
572
439
 
573
- // Pre-cache the target Volt public key by extracting it from the signing certificate.
574
- const voltCert = forge__default["default"].pki.certificateFromPem(this._voltConfig.ca_pem);
575
-
576
440
  // Extract Volt public key (used for encrypting Relay payloads).
577
- this._voltPublicKey = forge__default["default"].pki.publicKeyToPem(voltCert.publicKey);
578
- this._voltFingerprint = createPublicKeyFingerprint(
579
- this._voltPublicKey
580
- );
441
+ this._voltPublicKey = this._voltConfig.public_key;
442
+
443
+ const voltKey = voltClientWeb.keyFromPem(this._voltPublicKey).publicKey;
444
+ this._voltFingerprint = voltClientWeb.fingerprintFromPublicKey(voltKey);
581
445
  }
582
446
  }
583
447
 
@@ -598,7 +462,7 @@ class VoltCredential {
598
462
  }
599
463
 
600
464
  get identityId() {
601
- return this._cryptoCache.identity_id;
465
+ return this._cryptoCache.identity_did;
602
466
  }
603
467
 
604
468
  get voltPublicKey() {
@@ -606,7 +470,7 @@ class VoltCredential {
606
470
  }
607
471
 
608
472
  get publicKey() {
609
- return pki$1.publicKeyToPem(this.getKey().publicKey);
473
+ return voltClientWeb.publicKeyToPem(this.getKey().publicKey);
610
474
  }
611
475
 
612
476
  get voltFingerprint() {
@@ -614,9 +478,8 @@ class VoltCredential {
614
478
  }
615
479
 
616
480
  getKey() {
617
- const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
618
- const publicKey = pki$1.rsa.setPublicKey(privateKey.n, privateKey.e);
619
- return { privateKey, publicKey };
481
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
482
+ return key;
620
483
  }
621
484
 
622
485
  /**
@@ -629,16 +492,10 @@ class VoltCredential {
629
492
  return Promise.resolve(this.getKey());
630
493
  } else {
631
494
  log$5("creating key");
632
- 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
- });
495
+ this._cryptoCache.key = voltClientWeb.createKey();
496
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
497
+ log$5("successfully created key");
498
+ return Promise.resolve(key);
642
499
  }
643
500
  }
644
501
 
@@ -663,55 +520,6 @@ class VoltCredential {
663
520
  return !!this._cryptoCache.cert;
664
521
  }
665
522
 
666
- /**
667
- * Signs the identity resource id using the private key.
668
- * @param {*} audience the token audience (usually the target volt)
669
- * @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
670
- * @param {*} ttl time to live in seconds (default to 1 minute)
671
- */
672
- getIdentityToken(audience, publicKey, tunnelling = false, ttl = 60) {
673
- if (!this._cryptoCache.key) {
674
- throw new Error("crypto cache invalid");
675
- }
676
-
677
- const keyPair = this.getKey();
678
- const publicKeyPem = pki$1.publicKeyToPem(keyPair.publicKey);
679
- const base58Key = createPublicKeyFingerprint(publicKeyPem);
680
-
681
- log$5("base58 key is %s", base58Key);
682
-
683
- // Allow TTL either side of the current time.
684
- // @todo - fix with server time sync on volt connection.
685
- const payload = {
686
- aud: audience,
687
- iat: Math.floor(Date.now() / 1000) - ttl,
688
- exp: Math.floor(Date.now() / 1000) + ttl,
689
- sub: this._cryptoCache.session_id
690
- };
691
-
692
- let sharedKey;
693
- if (tunnelling) {
694
- // Initialise the Relay encryption key.
695
- sharedKey = VoltCredential.aesCreateKey();
696
-
697
- // Encrypt the key details using the target Volt public key and include this in the JWT payload.
698
- payload.sk = VoltCredential.rsaEncrypt(publicKey, sharedKey.key);
699
- payload.iv = VoltCredential.rsaEncrypt(publicKey, sharedKey.iv);
700
- }
701
-
702
- // Sign synchronously.
703
- const token = jwt__default["default"].sign(payload, this._cryptoCache.key, {
704
- algorithm: rs256Algorithm,
705
- keyid: base58Key
706
- });
707
-
708
- // Return the token along with any encryption key details.
709
- return {
710
- token,
711
- sharedKey
712
- };
713
- }
714
-
715
523
  /**
716
524
  * Creates the metadata required for a grpc call, including the Volt authentication token
717
525
  * and optionally tunnel encryption parameters.
@@ -722,9 +530,12 @@ class VoltCredential {
722
530
  * @returns
723
531
  */
724
532
  getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
725
- const identityToken = this.getIdentityToken(
533
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
534
+
535
+ const identityToken = voltClientWeb.getIdentityToken(
536
+ this.sessionId,
537
+ key,
726
538
  audience || this._voltConfig.id,
727
- publicKey,
728
539
  tunnelling,
729
540
  ttl
730
541
  );
@@ -734,48 +545,18 @@ class VoltCredential {
734
545
  }
735
546
 
736
547
  saveCache() {
548
+ const clone = { ...this._config, credential: { ...this._cryptoCache } };
549
+ if (this._config.p) {
550
+ // A passphrase option exists => encrypt the key before writing the cache file.
551
+ const key = voltClientWeb.keyFromPem(this._cryptoCache.key);
552
+ clone.credential.key = voltClientWeb.privateKeyToPem(key.privateKey, this._config.p);
553
+ }
554
+
737
555
  if (this._persistCache) {
738
- const clone = { ...this._config, credential: { ...this._cryptoCache } };
739
- if (this._config.p) {
740
- // A passphrase option exists => encrypt the key before writing the cache file.
741
- const privateKey = pki$1.privateKeyFromPem(this._cryptoCache.key);
742
- clone.credential.key = pki$1.encryptRsaPrivateKey(
743
- privateKey,
744
- this._config.p
745
- );
746
- }
747
556
  fs__default["default"].writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
748
557
  }
749
- }
750
558
 
751
- 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));
559
+ return clone;
779
560
  }
780
561
  }
781
562
 
@@ -918,6 +699,21 @@ function _prepareInvokeRequest(
918
699
  this._service?.service_description.host_type ===
919
700
  "SERVICE_HOST_TYPE_RELAYED";
920
701
 
702
+ const relaying = this._voltClient.isVoltRelay || isServiceRelayed;
703
+
704
+ let targetDID = [];
705
+ if (this._voltClient.isVoltRelay) {
706
+ targetDID.push(this._voltClient.voltConfig.id);
707
+ }
708
+
709
+ if (isServiceRelayed) {
710
+ // Add another relay hop if the target service is relayed.
711
+ log$4("target service is relayed");
712
+ targetDID.push(this._service.service_description.host_client_id);
713
+ }
714
+
715
+ log$4("target is %j", targetDID);
716
+
921
717
  // Use the service's host client id and public key if the service is relayed.
922
718
  const methodToken = this._voltClient.credential.getIdentityMetadata(
923
719
  this._voltClient.grpc,
@@ -925,17 +721,52 @@ function _prepareInvokeRequest(
925
721
  this._voltClient.voltConfig.id,
926
722
  this._service?.service_description.host_public_key ||
927
723
  this._voltClient.credential.voltPublicKey,
928
- true
724
+ relaying
929
725
  );
930
726
 
931
727
  let invokeRequest;
932
728
  let callMethod = method;
933
729
 
934
- if (this._voltClient.isVoltRelay || isServiceRelayed) {
730
+ if (relaying) {
935
731
  //
936
732
  // If the volt connection is via a relay (or the target service is relayed),
937
733
  // we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
938
734
  //
735
+ // Replace the target method with a call to Invoke on the relay Volt.
736
+ //
737
+ callMethod = "Invoke";
738
+
739
+ // But first we need to perform a key exchange with the target service.
740
+ //
741
+ if (!this._encryptionKey) {
742
+ // We haven't exchanged keys yet, so queue the payload.
743
+ this._pendingRequests.push(request);
744
+
745
+ if (this._keyExchangePending) {
746
+ // We've already sent the key exchange request, so do nothing.
747
+ log$4("key exchange pending, not sending payload");
748
+ return;
749
+ }
750
+
751
+ this._keyExchangePending = true;
752
+
753
+ // Send the key exchange request.
754
+ invokeRequest = {
755
+ token: methodToken.token,
756
+ target_did: targetDID,
757
+ target_service_id: isServiceRelayed
758
+ ? this._service.service_description.host_service_id
759
+ : undefined
760
+ };
761
+
762
+ return {
763
+ request: invokeRequest,
764
+ method: callMethod,
765
+ methodName: method,
766
+ meta: methodToken
767
+ };
768
+ }
769
+
939
770
  let requestPayload;
940
771
 
941
772
  // Serialise the rpc request.
@@ -955,45 +786,24 @@ function _prepareInvokeRequest(
955
786
  method_invoke: {
956
787
  method_name: this._grpcClient[method].path,
957
788
  method_type: methodType,
958
- token: methodToken.token,
959
789
  request: requestPayload
960
790
  }
961
791
  };
962
792
 
963
- let targetDID = [];
964
793
  let invokePayload;
965
- 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
- }
794
+ const iv = voltClientWeb.aesCreateKey().iv;
975
795
 
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);
796
+ // When sending via a relay we need to encrypt the payload.
797
+ invokePayload = voltClientWeb.aesEncrypt(
798
+ this._encryptionKey,
799
+ iv,
800
+ tunnelMethod.responseSerialize(tunnelResp)
801
+ );
987
802
 
988
803
  invokeRequest = {
989
- target_did: targetDID,
990
- token: methodToken.token,
991
- payload: invokePayload,
992
- target_service_id: isServiceRelayed ? this._service.id : undefined
804
+ iv,
805
+ payload: invokePayload
993
806
  };
994
-
995
- // Replace the target method with a call to Invoke on the relay Volt.
996
- callMethod = "Invoke";
997
807
  } else {
998
808
  invokeRequest = request;
999
809
  }
@@ -1013,7 +823,10 @@ function _preparePayloadRequest(request, invokeInfo) {
1013
823
  this._service?.service_description.host_type ===
1014
824
  "SERVICE_HOST_TYPE_RELAYED";
1015
825
 
826
+ let iv;
1016
827
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
828
+ iv = voltClientWeb.aesCreateKey().iv;
829
+
1017
830
  const requestPayload =
1018
831
  this._grpcClient[invokeInfo.methodName].requestSerialize(request);
1019
832
 
@@ -1026,13 +839,14 @@ function _preparePayloadRequest(request, invokeInfo) {
1026
839
  }
1027
840
  };
1028
841
 
1029
- const invokePayload = VoltCredential.aesEncrypt(
1030
- tunnelMethod.responseSerialize(tunnelResp),
1031
- invokeInfo.meta.sharedKey.key,
1032
- invokeInfo.meta.sharedKey.iv
842
+ const invokePayload = voltClientWeb.aesEncrypt(
843
+ this._encryptionKey,
844
+ iv,
845
+ tunnelMethod.responseSerialize(tunnelResp)
1033
846
  );
1034
847
 
1035
848
  payloadRequest = {
849
+ iv,
1036
850
  payload: invokePayload
1037
851
  };
1038
852
  } else {
@@ -1051,13 +865,38 @@ function _parseResponse(method, meta, response) {
1051
865
  "SERVICE_HOST_TYPE_RELAYED";
1052
866
 
1053
867
  if (this._voltClient.isVoltRelay || isServiceRelayed) {
1054
- if (response.payload) {
868
+ if (response.key_exchange) {
869
+ const encryptionKey = response.key_exchange.encryption_key;
870
+ const nonce = response.key_exchange.nonce;
871
+ const signature = response.key_exchange.signature;
872
+
873
+ // The signed message is the encryption key and the nonce.
874
+ const message = new Uint8Array(encryptionKey.length + nonce.length);
875
+ message.set(encryptionKey);
876
+ message.set(nonce, encryptionKey.length);
877
+
878
+ // Check the signature using the target service public key.
879
+ const targetKey = voltClientWeb.keyFromPem(
880
+ this._service?.service_description?.host_public_key ||
881
+ this._voltClient.credential.voltPublicKey
882
+ ).publicKey;
883
+
884
+ if (!voltClientWeb.verify(targetKey, message, signature)) {
885
+ throw new Error("signature verification failed");
886
+ }
887
+
888
+ invokeResponse = {
889
+ key_exchange: {
890
+ encryption_key: voltClientWeb.formatPEM(voltClientWeb.toBase64(encryptionKey), "PUBLIC KEY")
891
+ }
892
+ };
893
+ } else if (response.payload) {
1055
894
  let decryptedPayload;
1056
- if (meta.sharedKey.key) {
1057
- decryptedPayload = VoltCredential.aesDecrypt(
1058
- response.payload,
1059
- meta.sharedKey.key,
1060
- meta.sharedKey.iv
895
+ if (this._encryptionKey) {
896
+ decryptedPayload = voltClientWeb.aesDecrypt(
897
+ this._encryptionKey,
898
+ response.iv,
899
+ response.payload
1061
900
  );
1062
901
  } else {
1063
902
  decryptedPayload = response.payload;
@@ -1097,11 +936,11 @@ function _parseResponse(method, meta, response) {
1097
936
  invokeResponse.methodId = response.invoke_id;
1098
937
  log$4("started method %d for %s", invokeResponse.methodId, method);
1099
938
  }
1100
- } else if (this._voltClient.isRemote && meta.sharedKey.key) {
1101
- const decryptedPayload = VoltCredential.aesDecrypt(
1102
- response,
1103
- meta.sharedKey.key,
1104
- meta.sharedKey.iv
939
+ } else if (this._voltClient.isRemote && this._encryptionKey) {
940
+ const decryptedPayload = voltClientWeb.aesDecrypt(
941
+ this._encryptionKey,
942
+ meta.sharedKey.iv,
943
+ response
1105
944
  );
1106
945
  invokeResponse = {
1107
946
  payload:
@@ -1122,6 +961,9 @@ class GRPCCall extends EventEmitter__default["default"] {
1122
961
  this._call = null;
1123
962
  this._voltClient = voltClient;
1124
963
  this._service = service;
964
+ this._keyExchangePending = false;
965
+ this._pendingRequests = [];
966
+ this._encryptionKey = null;
1125
967
  }
1126
968
 
1127
969
  start(grpcClient, request) {
@@ -1150,7 +992,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1150
992
  }
1151
993
 
1152
994
  this._call = callClient[this._initialRequest.method](
1153
- this._initialRequest.meta.metadata
995
+ this._initialRequest?.meta?.metadata
1154
996
  );
1155
997
 
1156
998
  this._call.on("data", (streamResponse) => {
@@ -1161,7 +1003,25 @@ class GRPCCall extends EventEmitter__default["default"] {
1161
1003
  this._initialRequest.meta,
1162
1004
  streamResponse
1163
1005
  );
1164
- if (parsedResponse.payload) {
1006
+ if (parsedResponse.key_exchange) {
1007
+ const peerKey = voltClientWeb.keyFromPem(
1008
+ parsedResponse.key_exchange.encryption_key
1009
+ ).publicKey;
1010
+
1011
+ this._encryptionKey = voltClientWeb.deriveSharedKey(
1012
+ this._initialRequest.meta.sharedKey.key,
1013
+ peerKey,
1014
+ voltClientWeb.x25519
1015
+ );
1016
+
1017
+ // We've exchanged keys, so send any queued payloads on the next tick.
1018
+ process.nextTick(() => {
1019
+ this._pendingRequests.forEach((pendingRequest) => {
1020
+ this.write(pendingRequest);
1021
+ });
1022
+ this._pendingRequests = [];
1023
+ });
1024
+ } else if (parsedResponse.payload) {
1165
1025
  // Interpret a non-empty status message as an error.
1166
1026
  if (parsedResponse.payload?.status?.message) {
1167
1027
  this.emit(
@@ -1236,7 +1096,7 @@ class GRPCCall extends EventEmitter__default["default"] {
1236
1096
  if (!this._call) {
1237
1097
  log$4("ERROR - call not initialised");
1238
1098
  throw new Error("call not initialised");
1239
- } else if (this._initialRequest) {
1099
+ } else if (this._initialRequest && !this._keyExchangePending) {
1240
1100
  const payloadRequest = _preparePayloadRequest.call(
1241
1101
  this,
1242
1102
  request,
@@ -1244,6 +1104,8 @@ class GRPCCall extends EventEmitter__default["default"] {
1244
1104
  );
1245
1105
  return this._call.write(payloadRequest);
1246
1106
  } else {
1107
+ this._keyExchangePending = false;
1108
+
1247
1109
  this._initialRequest = _prepareInvokeRequest.call(
1248
1110
  this,
1249
1111
  request,
@@ -1301,13 +1163,18 @@ function _doConnect(connectHello) {
1301
1163
 
1302
1164
  const grpcClient = this._voltClient.getVoltAPIClient();
1303
1165
 
1304
- let helloPayload = connectHello;
1305
- if (!helloPayload) {
1306
- log$3("defaulting hello payload");
1307
- helloPayload = {
1308
- hello: {}
1309
- };
1310
- }
1166
+ // Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
1167
+ // within this time, it will assume the connection is dead.
1168
+ const defaultHello = {
1169
+ ping_interval: this._pingInterval,
1170
+ timestamp: Date.now()
1171
+ };
1172
+
1173
+ const helloPayload = { ...connectHello } || {};
1174
+
1175
+ helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
1176
+
1177
+ log$3("hello is %j", helloPayload);
1311
1178
 
1312
1179
  this.call.on("error", (err) => {
1313
1180
  log$3("error on connection stream [%s]", err.message);
@@ -1325,17 +1192,22 @@ function _doConnect(connectHello) {
1325
1192
  clearTimeout(this._pingTimeoutTimer);
1326
1193
  }
1327
1194
 
1328
- // Schedule the next ping.
1329
- _startPingTimer.call(this);
1330
-
1331
1195
  this.emit("ping", response.ping.timestamp);
1332
1196
  break;
1333
1197
  }
1334
1198
  case "acknowledge": {
1335
1199
  if (!this._connectionId) {
1336
1200
  this._connectionId = response.acknowledge.connection_id;
1201
+
1202
+ // Set the ping timeout interval to the value returned by the Volt. If we don't
1203
+ // hear from the Volt in this time, we'll assume the connection is dead.
1204
+ this._timeoutInterval = response.acknowledge.ping_interval;
1205
+
1337
1206
  log$3("connection id is %s", this._connectionId);
1338
1207
  this.emit("connected", this._connectionId);
1208
+
1209
+ // Schedule the next ping.
1210
+ _startPingTimer.call(this);
1339
1211
  }
1340
1212
  break;
1341
1213
  }
@@ -1363,8 +1235,6 @@ function _doConnect(connectHello) {
1363
1235
  });
1364
1236
 
1365
1237
  this.call.start(grpcClient, helloPayload);
1366
-
1367
- _startPingTimer.call(this);
1368
1238
  }
1369
1239
 
1370
1240
  function _startPingTimer() {
@@ -1391,6 +1261,8 @@ function _startPingTimer() {
1391
1261
  _connectionTimeoutHandler.bind(this),
1392
1262
  this._timeoutInterval
1393
1263
  );
1264
+
1265
+ _startPingTimer.call(this);
1394
1266
  }, this._pingInterval);
1395
1267
  }
1396
1268
 
@@ -1438,13 +1310,13 @@ class RpcInvocation extends EventEmitter__default["default"] {
1438
1310
  #voltClient = null;
1439
1311
  #voltConnection = null;
1440
1312
  #token = null;
1441
- #encryptKey = null;
1442
- #encryptIV = null;
1443
1313
  #invokeId = null;
1444
1314
  #payload = null;
1445
1315
  #isJSON = false;
1446
1316
  #methodDescriptor = null;
1447
1317
  #methodName = null;
1318
+ #derivedKey = null;
1319
+ #started = false;
1448
1320
 
1449
1321
  constructor(voltClient, voltConnection) {
1450
1322
  super();
@@ -1488,51 +1360,89 @@ class RpcInvocation extends EventEmitter__default["default"] {
1488
1360
  this.#methodDescriptor = methodDescriptor;
1489
1361
  }
1490
1362
 
1491
- #decodeToken(token) {
1363
+ get started() {
1364
+ return this.#started;
1365
+ }
1366
+
1367
+ #keyExchange(token) {
1492
1368
  // We don't verify the token at this point, as we don't know the public key.
1493
1369
  // The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
1494
1370
  this.#token = jwt__default["default"].decode(token);
1495
1371
 
1496
- 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
- }
1372
+ // Store the JWT key id (kid) for use in the response.
1373
+ const callerPublicKeyPem = voltClientWeb.formatPEM(this.#token.k, "PUBLIC KEY");
1374
+ const callerKey = voltClientWeb.keyFromPem(callerPublicKeyPem, "", voltClientWeb.x25519).publicKey;
1375
+
1376
+ const ephemeralKeyPem = voltClientWeb.createEncryptionKey();
1377
+ const ephemeralKey = voltClientWeb.keyFromPem(ephemeralKeyPem, "", voltClientWeb.x25519);
1378
+ const ephemeralPublicKey = voltClientWeb.fromBase64(
1379
+ voltClientWeb.stripPEMHeaders(voltClientWeb.publicKeyToPem(ephemeralKey.publicKey, voltClientWeb.x25519))
1380
+ );
1381
+
1382
+ this.#derivedKey = voltClientWeb.deriveSharedKey(
1383
+ ephemeralKey.privateKey,
1384
+ callerKey,
1385
+ voltClientWeb.x25519
1386
+ );
1387
+
1388
+ const nonce = voltClientWeb.randomBytes(16);
1389
+
1390
+ // The signed message is the encryption key and the nonce.
1391
+ const message = new Uint8Array(ephemeralPublicKey.length + nonce.length);
1392
+ message.set(ephemeralPublicKey);
1393
+ message.set(nonce, ephemeralPublicKey.length);
1507
1394
 
1508
- return Promise.resolve(this.#token);
1395
+ const signature = voltClientWeb.signBase64(
1396
+ this.#voltClient.credential.getKey().privateKey,
1397
+ message
1398
+ );
1399
+
1400
+ const keyExchangeResponse = {
1401
+ invoke_id: this.#invokeId,
1402
+ key_exchange: {
1403
+ nonce,
1404
+ encryption_key: ephemeralPublicKey,
1405
+ signature: voltClientWeb.fromBase64Url(signature)
1406
+ }
1407
+ };
1408
+
1409
+ this.#voltConnection.send({
1410
+ invoke_response: keyExchangeResponse
1411
+ });
1412
+
1413
+ return Promise.resolve(keyExchangeResponse);
1509
1414
  }
1510
1415
 
1511
1416
  initialise(invoke_request) {
1512
1417
  this.#invokeId = invoke_request.invoke_id;
1513
1418
 
1514
- return this.#decodeToken(invoke_request.token)
1515
- .then(() => {
1516
- // Parse the initial payload.
1517
- return this.parsePayload(invoke_request);
1518
- })
1519
- .catch((err) => {
1419
+ if (!this.#derivedKey) {
1420
+ // We don't have the derived key yet, so we need to perform the key exchange.
1421
+ return this.#keyExchange(invoke_request.token).catch((err) => {
1520
1422
  log$2("failure initialising invocation: %s", err.message);
1521
1423
  return Promise.reject(err);
1522
1424
  });
1425
+ } else {
1426
+ // We already have the derived key, so we can just parse the payload.
1427
+ return this.parsePayload(invoke_request);
1428
+ }
1523
1429
  }
1524
1430
 
1525
1431
  parsePayload(invoke_request) {
1432
+ this.#started = true;
1433
+
1526
1434
  return new Promise((resolve, reject) => {
1435
+ const encryptIV = invoke_request.iv;
1436
+
1527
1437
  if (invoke_request.payload) {
1528
1438
  let decryptedPayload;
1529
1439
 
1530
- if (this.#encryptKey) {
1440
+ if (encryptIV) {
1531
1441
  // Decrypt the actual payload using the shared key and iv.
1532
- decryptedPayload = VoltCredential.aesDecrypt(
1533
- invoke_request.payload,
1534
- this.#encryptKey,
1535
- this.#encryptIV
1442
+ decryptedPayload = voltClientWeb.aesDecrypt(
1443
+ this.#derivedKey,
1444
+ encryptIV,
1445
+ invoke_request.payload
1536
1446
  );
1537
1447
  } else {
1538
1448
  // No encryption => just use the payload.
@@ -1568,12 +1478,12 @@ class RpcInvocation extends EventEmitter__default["default"] {
1568
1478
 
1569
1479
  this.#isJSON = true;
1570
1480
 
1571
- if (this.#encryptKey) {
1481
+ if (this.#derivedKey) {
1572
1482
  // 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
1483
+ decryptedPayload = voltClientWeb.aesDecrypt(
1484
+ this.#derivedKey,
1485
+ invoke_request.iv,
1486
+ Buffer.from(invoke_request.json_payload, "base64")
1577
1487
  );
1578
1488
  } else {
1579
1489
  // No encryption => just use the payload.
@@ -1632,17 +1542,15 @@ class RpcInvocation extends EventEmitter__default["default"] {
1632
1542
  });
1633
1543
  }
1634
1544
 
1635
- if (this.#token.sk) {
1545
+ const iv = voltClientWeb.randomBytes(16);
1546
+ if (this.#derivedKey) {
1636
1547
  // Encrypt the response using the shared key and iv.
1637
- responsePayload = VoltCredential.aesEncrypt(
1638
- responsePayload,
1639
- this.#encryptKey,
1640
- this.#encryptIV
1641
- );
1548
+ responsePayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, responsePayload);
1642
1549
  }
1643
1550
 
1644
1551
  const invokeResponse = {
1645
- invoke_id: this.#invokeId
1552
+ invoke_id: this.#invokeId,
1553
+ iv
1646
1554
  };
1647
1555
 
1648
1556
  if (this.#isJSON) {
@@ -1677,18 +1585,16 @@ class RpcInvocation extends EventEmitter__default["default"] {
1677
1585
  endPayload = tunnelMethod.requestSerialize(endPayload);
1678
1586
  }
1679
1587
 
1680
- if (this.#token.sk) {
1588
+ const iv = voltClientWeb.randomBytes(16);
1589
+ if (this.#derivedKey) {
1681
1590
  // Encrypt the response using the shared key and iv.
1682
- endPayload = VoltCredential.aesEncrypt(
1683
- endPayload,
1684
- this.#encryptKey,
1685
- this.#encryptIV
1686
- );
1591
+ endPayload = voltClientWeb.aesEncrypt(this.#derivedKey, iv, endPayload);
1687
1592
  }
1688
1593
 
1689
1594
  const invokeResponse = {
1690
1595
  invoke_id: this.#invokeId,
1691
- server_end: true
1596
+ server_end: true,
1597
+ iv
1692
1598
  };
1693
1599
 
1694
1600
  if (this.#isJSON) {
@@ -1705,7 +1611,6 @@ class RpcInvocation extends EventEmitter__default["default"] {
1705
1611
 
1706
1612
  /* eslint-disable no-underscore-dangle */
1707
1613
 
1708
- const { pki } = forge__default["default"];
1709
1614
  const { filter, pick } = lodash__default["default"];
1710
1615
 
1711
1616
  const log$1 = debug__default["default"]("volt-client-grpc:volt-client-internal");
@@ -1741,9 +1646,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
1741
1646
  // This is the signing CA used by the volt.
1742
1647
  this._credential.cache.ca = authenticateResponse.chain;
1743
1648
 
1744
- // Identity resource id is assigned by the volt.
1649
+ // The session id is assigned by the volt.
1745
1650
  this._credential.cache.session_id = authenticateResponse.session_id;
1746
1651
 
1652
+ if (!this._credential.cache.identity_did) {
1653
+ // Identity DID has been assigned by the volt.
1654
+ this._credential.cache.identity_did =
1655
+ authenticateResponse.identity_did;
1656
+ }
1657
+
1747
1658
  this._credential.saveCache();
1748
1659
  break;
1749
1660
  }
@@ -1754,6 +1665,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
1754
1665
  case "POLICY_DECISION_PROMPT":
1755
1666
  case "POLICY_DECISION_PENDING": {
1756
1667
  log$1("access pending approval - waiting...");
1668
+
1669
+ // This is the signing CA used by the volt.
1670
+ this._credential.cache.ca = authenticateResponse.chain;
1671
+
1672
+ // The session id is assigned by the volt.
1673
+ this._credential.cache.session_id = authenticateResponse.session_id;
1674
+
1675
+ if (!this._credential.cache.identity_did) {
1676
+ // Identity DID has been assigned by the volt.
1677
+ this._credential.cache.identity_did =
1678
+ authenticateResponse.identity_did;
1679
+ }
1680
+
1681
+ this._credential.saveCache();
1757
1682
  break;
1758
1683
  }
1759
1684
  default:
@@ -1778,7 +1703,10 @@ function findDIDDocumentService(document, serviceType) {
1778
1703
  return matches;
1779
1704
  }
1780
1705
 
1781
- async function authenticateInternal(useDID = true) {
1706
+ async function authenticateInternal(
1707
+ sessionOnly = false,
1708
+ ownDID = false
1709
+ ) {
1782
1710
  try {
1783
1711
  // Check if we need to resolve a Relay volt.
1784
1712
  if (
@@ -1790,7 +1718,7 @@ async function authenticateInternal(useDID = true) {
1790
1718
  log$1("attempting to retrieve Relay information from %s", relayURL);
1791
1719
  this._voltConfig.relay = await fetchVoltConfig.call(
1792
1720
  this,
1793
- `${relayURL}/discovery`
1721
+ `${relayURL}/discover`
1794
1722
  );
1795
1723
 
1796
1724
  if (this._voltConfig.relay.ca_pem) {
@@ -1815,8 +1743,8 @@ async function authenticateInternal(useDID = true) {
1815
1743
  }
1816
1744
 
1817
1745
  const keyPair = this._credential.getKey();
1818
- const publicKeyPem = removeCarriageReturn(
1819
- pki.publicKeyToPem(keyPair.publicKey)
1746
+ const publicKeyPem = voltClientWeb.removeCarriageReturn(
1747
+ voltClientWeb.publicKeyToPem(keyPair.publicKey)
1820
1748
  );
1821
1749
 
1822
1750
  if (!this._credential.cache.bindIp) {
@@ -1829,12 +1757,12 @@ async function authenticateInternal(useDID = true) {
1829
1757
 
1830
1758
  // Form the request message.
1831
1759
  const authenticateRequest = {
1832
- binding_name: this._config.client_name,
1760
+ client_name: this._config.client_name,
1833
1761
  host: this._credential.cache.bindIp
1834
1762
  };
1835
1763
 
1836
1764
  if (this._voltConfig.challenge_code) {
1837
- authenticateRequest.challenge = signBase64(
1765
+ authenticateRequest.challenge = voltClientWeb.signBase64(
1838
1766
  keyPair.privateKey,
1839
1767
  this._voltConfig.challenge_code
1840
1768
  );
@@ -1844,68 +1772,58 @@ async function authenticateInternal(useDID = true) {
1844
1772
  );
1845
1773
  }
1846
1774
 
1847
- if (!useDID) {
1775
+ if (sessionOnly) {
1776
+ // We want to create a session-based authentication based simply on our key, rather than a
1777
+ // fully-blown DID-based authentication.
1778
+ log$1("creating session-only request");
1848
1779
  authenticateRequest.public_key = publicKeyPem;
1849
- } else if (!this._credential.cache.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
- };
1780
+ } else if (!this._credential.cache.identity_did) {
1781
+ if (!ownDID) {
1782
+ // We don't want to have to manage our own DID, so we ask the Volt to do it for us.
1783
+ // The Volt will create a new DID and return it to us in the response.
1784
+ log$1("creating DID-based request");
1785
+ authenticateRequest.did_public_key = publicKeyPem;
1786
+ } else {
1787
+ // We want to create and manage our own DID.
1788
+ const did =
1789
+ (this._credential.cache.identity_did = `did:volt:${uuid.v4()}`);
1790
+
1791
+ // Order of properties in the DID document is important,
1792
+ // they must be in alphabetical order throughout the document.
1793
+ // This is because the DID document is signed and the signature
1794
+ // is over the entire document.
1795
+ const didDocument = {
1796
+ "@context": "https://www.w3.org/ns/did/v1",
1797
+ authentication: [`${did}#key-1`],
1798
+ controller: did,
1799
+ description: this._config.client_name,
1800
+ id: did,
1801
+ verificationMethod: [
1802
+ {
1803
+ id: `${did}#key-1`,
1804
+ publicKeyPem: publicKeyPem,
1805
+ type: "Ed25519VerificationKey2018"
1806
+ }
1807
+ ]
1808
+ };
1894
1809
 
1895
- // Set the DID document with proof in the authenticate request.
1896
- authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1810
+ // Set the DID document with proof in the authenticate request.
1811
+ authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
1897
1812
 
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
- );
1813
+ // Now sign the entire DID document, including the proof and set it in the
1814
+ // bind request. This is required by the Volt to verify that we have the private key for the DID.
1815
+ authenticateRequest.did_document_signature = voltClientWeb.signBase64(
1816
+ keyPair.privateKey,
1817
+ authenticateRequest.did_document
1818
+ );
1904
1819
 
1905
- log$1("did document is %s", authenticateRequest.did_document);
1906
- log$1("did signature is %s", authenticateRequest.did_document_signature);
1820
+ log$1("did document is %s", authenticateRequest.did_document);
1821
+ log$1("did signature is %s", authenticateRequest.did_document_signature);
1822
+ }
1907
1823
  } else {
1908
- authenticateRequest.did = this._credential.cache.identity_id;
1824
+ // We already have a DID cached, so re-use it.
1825
+ log$1("using cached DID %s", this._credential.cache.identity_did);
1826
+ authenticateRequest.did = this._credential.cache.identity_did;
1909
1827
  }
1910
1828
 
1911
1829
  log$1("authenticate TTL is ", this._config.bindRequestTTL);
@@ -1999,10 +1917,24 @@ function connectInternal(helloPayload) {
1999
1917
 
2000
1918
  this._voltConnection.on("invoke_request", (invoke_request) => {
2001
1919
  const invokeId = invoke_request.invoke_id;
2002
- if (this._activeRPC[invokeId]) {
2003
- this._activeRPC[invokeId].parsePayload(invoke_request);
1920
+
1921
+ let rpcInvocation = this._activeRPC[invokeId];
1922
+
1923
+ if (rpcInvocation) {
1924
+ if (rpcInvocation.started) {
1925
+ rpcInvocation.parsePayload(invoke_request);
1926
+ } else {
1927
+ rpcInvocation
1928
+ .initialise(invoke_request)
1929
+ .then(() => {
1930
+ this.emit("invoke_request", rpcInvocation);
1931
+ })
1932
+ .catch((err) => {
1933
+ log$1("invoke_request - error [%s]", err.message);
1934
+ });
1935
+ }
2004
1936
  } else {
2005
- const rpcInvocation = new RpcInvocation(this, this._voltConnection);
1937
+ rpcInvocation = new RpcInvocation(this, this._voltConnection);
2006
1938
 
2007
1939
  rpcInvocation.on("end", () => {
2008
1940
  log$1("removing active RPC [%s]", invokeId);
@@ -2011,9 +1943,11 @@ function connectInternal(helloPayload) {
2011
1943
 
2012
1944
  rpcInvocation
2013
1945
  .initialise(invoke_request)
2014
- .then(() => {
1946
+ .then((response) => {
2015
1947
  this._activeRPC[invokeId] = rpcInvocation;
2016
- this.emit("invoke_request", rpcInvocation);
1948
+ if (!response.key_exchange) {
1949
+ this.emit("invoke_request", rpcInvocation);
1950
+ }
2017
1951
  })
2018
1952
  .catch((err) => {
2019
1953
  log$1("invoke_request - error [%s]", err.message);
@@ -2159,6 +2093,7 @@ async function fetchVoltConfig(discovery_url) {
2159
2093
  "public_key",
2160
2094
  "fingerprint",
2161
2095
  "address",
2096
+ "http_address",
2162
2097
  "ca_pem",
2163
2098
  "challenge_code",
2164
2099
  "cloud",
@@ -2246,6 +2181,7 @@ class VoltClient extends EventEmitter__default["default"] {
2246
2181
  /**
2247
2182
  * Initialises a binding and connection to the volt.
2248
2183
  * @param {string} [config] - the location of the Volt configuration file, or a configuration object
2184
+ * @param {object} [extras] - additional configuration properties
2249
2185
  */
2250
2186
  async initialise(config, extras = {}) {
2251
2187
  try {
@@ -2261,10 +2197,15 @@ class VoltClient extends EventEmitter__default["default"] {
2261
2197
  configPath = path__default["default"].resolve(config);
2262
2198
  log("Attempt to load config from file %s", configPath);
2263
2199
  try {
2264
- 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);
2200
+ if (fs__default["default"].existsSync(configPath)) {
2201
+ const configContents = await readFile(
2202
+ new URL(configPath, (typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.cjs', document.baseURI).href)))
2203
+ );
2204
+ configJSON = JSON.parse(configContents);
2205
+ } else {
2206
+ // There is currently no file at the given configuration path.
2207
+ configJSON = {};
2208
+ }
2268
2209
  } catch (err) {
2269
2210
  log("failure loading config file %s [%s]", configPath, err.message);
2270
2211
  throw new Error(
@@ -2281,15 +2222,15 @@ class VoltClient extends EventEmitter__default["default"] {
2281
2222
  throw new Error("config argument is required to be an object");
2282
2223
  }
2283
2224
 
2225
+ this._config = { ...extras, ...configJSON };
2226
+
2284
2227
  if (
2285
- !configJSON.client_name ||
2286
- typeof configJSON.client_name !== "string"
2228
+ !this._config.client_name ||
2229
+ typeof this._config.client_name !== "string"
2287
2230
  ) {
2288
2231
  throw new Error("client_name property required in config");
2289
2232
  }
2290
2233
 
2291
- this._config = { ...configJSON, ...extras };
2292
-
2293
2234
  let voltDID = "";
2294
2235
  let voltHttpAddress = "";
2295
2236
  if (typeof this._config.volt === "string") {
@@ -2298,6 +2239,7 @@ class VoltClient extends EventEmitter__default["default"] {
2298
2239
  } else {
2299
2240
  voltHttpAddress = this._config.volt;
2300
2241
  }
2242
+ delete this._config.volt;
2301
2243
  } else if (typeof this._config.volt !== "object") {
2302
2244
  throw new Error("configuration is missing the 'volt' object");
2303
2245
  } else {
@@ -2312,7 +2254,7 @@ class VoltClient extends EventEmitter__default["default"] {
2312
2254
  } else if (voltHttpAddress && !this._config?.volt?.id) {
2313
2255
  const discoConfig = await fetchVoltConfig.call(
2314
2256
  this,
2315
- `${voltHttpAddress}/discovery`
2257
+ `${voltHttpAddress}/discover`
2316
2258
  );
2317
2259
  this._config = {
2318
2260
  ...this._config,
@@ -2321,8 +2263,12 @@ class VoltClient extends EventEmitter__default["default"] {
2321
2263
  }
2322
2264
 
2323
2265
  this._voltConfig = this._config.volt;
2324
- this._voltConfig.did = voltDID;
2325
- this._voltConfig.http_address = voltHttpAddress;
2266
+ if (voltDID) {
2267
+ this._voltConfig.did = voltDID;
2268
+ }
2269
+ if (voltHttpAddress) {
2270
+ this._voltConfig.http_address = voltHttpAddress;
2271
+ }
2326
2272
 
2327
2273
  if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
2328
2274
  throw new Error("'id' property is missing in Volt configuration");
@@ -2370,9 +2316,12 @@ class VoltClient extends EventEmitter__default["default"] {
2370
2316
  if (!isBound) {
2371
2317
  // This is usually because of request being denied.
2372
2318
  throw new Error("Volt authentication failed");
2319
+ } else {
2320
+ // Perist the configuration.
2321
+ this._config = this._credential.saveCache();
2373
2322
  }
2374
2323
 
2375
- return Promise.resolve();
2324
+ return Promise.resolve(this._config);
2376
2325
  } catch (err) {
2377
2326
  log("Failure starting Volt client [%s]", err.message);
2378
2327
  throw err;
@@ -2839,6 +2788,13 @@ class VoltClient extends EventEmitter__default["default"] {
2839
2788
  });
2840
2789
  }
2841
2790
 
2791
+ SqlTransaction(request) {
2792
+ const grpcClient = this.getVoltAPIClient();
2793
+
2794
+ const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
2795
+ return call.start(grpcClient, request);
2796
+ }
2797
+
2842
2798
  /**
2843
2799
  * WireAPI
2844
2800
  */