@tdxvolt/volt-client-grpc 0.15.1 → 0.16.0-beta.2

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.
@@ -16,6 +16,7 @@ import * as voltUtils from "./utils.js";
16
16
  import { constants } from "./constants.js";
17
17
  import GRPCCall from "./grpc-call.js";
18
18
  import RpcInvocation from "./rpc-invocation.js";
19
+ import { v4 as generateId } from "uuid";
19
20
 
20
21
  const { pki } = forge;
21
22
  const { filter, pick } = lodash;
@@ -31,30 +32,31 @@ const voltServices = [
31
32
  constants.serviceType.wireAPI
32
33
  ];
33
34
 
34
- function issueBind(bindRequest, ttl) {
35
+ function issueAuthenticate(authenticateRequest, ttl) {
35
36
  // eslint-disable-next-line no-use-before-define
36
37
  return unaryCallInternal
37
- .call(this, "Bind", bindRequest)
38
- .then((bindResponse) => {
39
- log("got binding request response %j", bindResponse);
40
- if (bindResponse.status?.code) {
41
- log("error in bind response: %s", bindResponse.status.message);
42
- return Promise.reject(new Error(bindResponse.status.message));
38
+ .call(this, "Authenticate", authenticateRequest)
39
+ .then((authenticateResponse) => {
40
+ log("got authenticate response %j", authenticateResponse);
41
+ if (authenticateResponse.status?.code) {
42
+ log("error in bind response: %s", authenticateResponse.status.message);
43
+ return Promise.reject(new Error(authenticateResponse.status.message));
43
44
  } else {
44
- switch (bindResponse.decision) {
45
+ switch (authenticateResponse.decision) {
45
46
  case "POLICY_DECISION_PERMIT": {
46
47
  //
47
48
  // The request status is permit => cache the bind response info.
48
49
  //
49
50
 
50
51
  // This is the certificate assigned to us by the volt.
51
- this._credential.cache.cert = bindResponse.cert;
52
+ this._credential.cache.cert = authenticateResponse.cert;
52
53
 
53
54
  // This is the signing CA used by the volt.
54
- this._credential.cache.ca = bindResponse.chain;
55
+ this._credential.cache.ca = authenticateResponse.chain;
55
56
 
56
57
  // Identity resource id is assigned by the volt.
57
- this._credential.cache.client_id = bindResponse.identity_id;
58
+ this._credential.cache.session_id = authenticateResponse.session_id;
59
+
58
60
  this._credential.saveCache();
59
61
  break;
60
62
  }
@@ -68,12 +70,15 @@ function issueBind(bindRequest, ttl) {
68
70
  break;
69
71
  }
70
72
  default:
71
- log("ignoring unknown bind descision %s", bindResponse.status);
73
+ log(
74
+ "ignoring unknown bind descision %s",
75
+ authenticateResponse.status
76
+ );
72
77
  break;
73
78
  }
74
79
  }
75
80
 
76
- return bindResponse.decision;
81
+ return authenticateResponse.decision;
77
82
  });
78
83
  }
79
84
 
@@ -86,7 +91,7 @@ function findDIDDocumentService(document, serviceType) {
86
91
  return matches;
87
92
  }
88
93
 
89
- export async function bindInternal() {
94
+ export async function authenticateInternal(createDID = true) {
90
95
  try {
91
96
  // Check if we need to resolve a Relay volt.
92
97
  if (
@@ -100,6 +105,7 @@ export async function bindInternal() {
100
105
  this,
101
106
  `${relayURL}/discovery`
102
107
  );
108
+
103
109
  if (this._voltConfig.relay.ca_pem) {
104
110
  log(
105
111
  "Auto-fetched Relay CA %s, remote address %s",
@@ -133,21 +139,13 @@ export async function bindInternal() {
133
139
  this._credential.saveCache();
134
140
 
135
141
  // Form the request message.
136
- const bindRequest = {
142
+ const authenticateRequest = {
137
143
  binding_name: this._config.client_name,
138
- public_key: publicKeyPem,
139
- host: this._credential.cache.bindIp,
140
- x509_credential: []
144
+ host: this._credential.cache.bindIp
141
145
  };
142
146
 
143
- if (this._credential.cache.cert) {
144
- bindRequest.x509_credential = [
145
- this._credential.cache.cert + this._credential.cache.ca
146
- ];
147
- }
148
-
149
147
  if (this._voltConfig.challenge_code) {
150
- bindRequest.challenge = voltUtils.signBase64(
148
+ authenticateRequest.challenge = voltUtils.signBase64(
151
149
  keyPair.privateKey,
152
150
  this._voltConfig.challenge_code
153
151
  );
@@ -157,15 +155,71 @@ export async function bindInternal() {
157
155
  );
158
156
  }
159
157
 
160
- // For remote connections, add our cloud-issued certificate as an additional credential.
161
- if (this._voltConfig?.relay?.ca_pem && this._credential.cache.cloud_cert) {
162
- bindRequest.x509_credential = bindRequest.x509_credential.concat(
163
- this._credential.cache.cloud_cert + this._voltConfig.relay.ca_pem
158
+ if (!createDID) {
159
+ authenticateRequest.public_key = publicKeyPem;
160
+ } else {
161
+ if (!this._credential.cache.identity_id) {
162
+ // If we don't have an identity id, then we need to create a DID.
163
+ this._credential.cache.identity_id = `did:volt:${generateId()}`;
164
+ }
165
+
166
+ const did = this._credential.cache.identity_id;
167
+
168
+ // Order of properties in the DID document is important,
169
+ // they must be in alphabetical order throughout the document.
170
+ // This is because the DID document is signed and the signature
171
+ // is over the entire document.
172
+ const didDocument = {
173
+ "@context": "https://www.w3.org/ns/did/v1",
174
+ authentication: [`${did}#key-1`],
175
+ description: this._config.client_name,
176
+ id: did,
177
+ verificationMethod: [
178
+ {
179
+ controller: did,
180
+ id: `${did}#key-1`,
181
+ publicKeyPem: publicKeyPem,
182
+ type: "RsaSignature2018"
183
+ }
184
+ ]
185
+ };
186
+
187
+ const signDocument = JSON.stringify(didDocument, null, 2);
188
+ log("signing did document");
189
+ log(signDocument);
190
+
191
+ // Sign the DID document.
192
+ const documentDigest = voltUtils.signBase64(
193
+ keyPair.privateKey,
194
+ signDocument
164
195
  );
196
+
197
+ const proofSignature = voltUtils.jwsSignature(documentDigest);
198
+
199
+ // Create the DID document proof.
200
+ didDocument.proof = {
201
+ created: new Date().toISOString(),
202
+ jws: proofSignature,
203
+ proofPurpose: "assertionMethod",
204
+ type: "RsaSignature2018",
205
+ verificationMethod: `${did}#key-1`
206
+ };
207
+
208
+ // Set the DID document with proof in the authenticate request.
209
+ authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
210
+
211
+ // Now sign the entire DID document, including the proof and set it in the
212
+ // bind request. This is required by the Volt to verify that we have the private key for the DID.
213
+ authenticateRequest.did_document_signature = voltUtils.signBase64(
214
+ keyPair.privateKey,
215
+ authenticateRequest.did_document
216
+ );
217
+
218
+ log("did document is %s", authenticateRequest.did_document);
219
+ log("did signature is %s", authenticateRequest.did_document_signature);
165
220
  }
166
221
 
167
- log("sending %d x509 credentials", bindRequest.x509_credential.length);
168
- log("bind TTL is ", this._config.bindRequestTTL);
222
+ log("authenticate TTL is ", this._config.bindRequestTTL);
169
223
 
170
224
  // This will block while the request is pending.
171
225
  const pollInterval = 10000;
@@ -177,12 +231,12 @@ export async function bindInternal() {
177
231
  await new Promise((resolve) => setTimeout(resolve, pollInterval));
178
232
  }
179
233
  // eslint-disable-next-line no-await-in-loop
180
- decision = await issueBind.call(
234
+ decision = await issueAuthenticate.call(
181
235
  this,
182
- bindRequest,
236
+ authenticateRequest,
183
237
  this._voltConfig.bindRequestTTL
184
238
  );
185
- log("binding decision: %s", decision);
239
+ log("authenticate decision: %s", decision);
186
240
  } while (
187
241
  decision === "POLICY_DECISION_PROMPT" ||
188
242
  decision === "POLICY_DECISION_PENDING"
@@ -304,9 +358,6 @@ export function getVoltAPIClientInternal() {
304
358
 
305
359
  export function getAPIClientInternal(service) {
306
360
  if (!this._cachedService[service.id]) {
307
- const isRelayedService =
308
- service.service_description.host_type === "SERVICE_HOST_TYPE_RELAYED";
309
-
310
361
  const serviceAddress = this.isRemote
311
362
  ? this._voltConfig.relay.address
312
363
  : service.service_description.host_address;
@@ -354,9 +405,8 @@ export function getServiceClient(service) {
354
405
  }
355
406
 
356
407
  export function unaryCallInternal(method, request, service) {
357
- const grpcClient = getServiceClient.call(this, service);
358
-
359
408
  return new Promise((resolve, reject) => {
409
+ const grpcClient = getServiceClient.call(this, service);
360
410
  const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY", service);
361
411
 
362
412
  let response = null;
@@ -5,7 +5,7 @@ import path from "path";
5
5
  import { getServiceDescriptors } from "./proto-utils.js";
6
6
  import { createClient as createGrpcClient } from "./grpc-utils.js";
7
7
  import {
8
- bindInternal,
8
+ authenticateInternal,
9
9
  connectInternal,
10
10
  fetchVoltConfig,
11
11
  fetchVoltConfigFromDID,
@@ -166,14 +166,14 @@ export class VoltClient extends EventEmitter {
166
166
  if (this._credential._cryptoCache.cert) {
167
167
  isBound = true;
168
168
  } else {
169
- // No certificate present in the cache yet => we need to bind.
169
+ // No certificate present in the cache yet => we need to authenticate.
170
170
  try {
171
- isBound = await bindInternal.call(this);
171
+ isBound = await authenticateInternal.call(this);
172
172
  } catch (bindErr) {
173
173
  if (bindErr.message === "invalid arguments") {
174
174
  // This is usually the result of a missing challenge signature or credential.
175
175
  log(
176
- "failure binding to Volt - did you supply a challenge code or verified credential?"
176
+ "failure authenticating on Volt - did you supply a challenge code or verified credential?"
177
177
  );
178
178
  throw bindErr;
179
179
  }
@@ -182,7 +182,7 @@ export class VoltClient extends EventEmitter {
182
182
 
183
183
  if (!isBound) {
184
184
  // This is usually because of request being denied.
185
- throw new Error("Volt binding failed");
185
+ throw new Error("Volt authentication failed");
186
186
  }
187
187
 
188
188
  return Promise.resolve();
@@ -408,6 +408,10 @@ export class VoltClient extends EventEmitter {
408
408
  * VoltAPI - volt management
409
409
  */
410
410
 
411
+ Authenticate(request) {
412
+ return unaryCallInternal.call(this, "Authenticate", request);
413
+ }
414
+
411
415
  Bind(request) {
412
416
  return unaryCallInternal.call(this, "Bind", request);
413
417
  }
@@ -69,8 +69,12 @@ export class VoltCredential {
69
69
  return this._cryptoCache;
70
70
  }
71
71
 
72
- get clientId() {
73
- return this._cryptoCache.client_id;
72
+ get sessionId() {
73
+ return this._cryptoCache.session_id;
74
+ }
75
+
76
+ get identityId() {
77
+ return this._cryptoCache.identity_id;
74
78
  }
75
79
 
76
80
  get voltPublicKey() {
@@ -159,7 +163,7 @@ export class VoltCredential {
159
163
  aud: audience,
160
164
  iat: Math.floor(Date.now() / 1000) - ttl,
161
165
  exp: Math.floor(Date.now() / 1000) + ttl,
162
- sub: this._cryptoCache.client_id
166
+ sub: this._cryptoCache.session_id
163
167
  };
164
168
 
165
169
  let sharedKey;
@@ -241,12 +245,13 @@ export class VoltCredential {
241
245
  }
242
246
 
243
247
  static rsaEncrypt(key, buffer) {
244
- return crypto.publicEncrypt(key, buffer).toString("base64");
248
+ const cipher = crypto.publicEncrypt(key, buffer);
249
+ return cipher.toString("base64url");
245
250
  }
246
251
 
247
252
  static rsaDecrypt(key, buffer) {
248
253
  const keyObj = crypto.createPrivateKey(key);
249
- const cipher = Buffer.from(buffer, "base64");
254
+ const cipher = Buffer.from(buffer, "base64url");
250
255
  return Buffer.from(crypto.privateDecrypt(keyObj, cipher));
251
256
  }
252
257
  }
@@ -1,37 +0,0 @@
1
- syntax = "proto3";
2
-
3
- package tdx.volt_api.ssi.v1;
4
-
5
- import "tdx/volt_api/volt/v1/status.proto";
6
-
7
- // Volt DIDs are essentially 'peer DIDs', i.e. they are not globally resolvable.
8
- // TDX DIDs are globally resolvable.
9
-
10
- service SsiAPI {
11
- rpc ResolveDID(ResolveDIDRequest) returns (ResolveDIDResponse);
12
- rpc TrustVerifiableCredential(TrustVerifiableCredentialRequest) returns (TrustVerifiableCredentialResponse);
13
- }
14
-
15
- //
16
- // Do we need to support/differentiate between plain DIDs and DID urls here?
17
- // Where a DID url is a DID followed by some additional lookup info,
18
- // e.g. did:tdx:volt:92181ffa-072a-452a-ae79-a243ab2812fc;service=/some/service?query#fragment
19
- //
20
- message ResolveDIDRequest {
21
- string did_url = 1;
22
- }
23
-
24
- message ResolveDIDResponse {
25
- tdx.volt_api.volt.v1.Status status = 1;
26
- string did_document_json = 2;
27
- }
28
-
29
- // Empty ATM - a VC is issued for the authenticated identity.
30
- message TrustVerifiableCredentialRequest {
31
- }
32
-
33
- message TrustVerifiableCredentialResponse {
34
- tdx.volt_api.volt.v1.Status status = 1;
35
- string verifiable_credential_json = 2;
36
- }
37
-