@tdxvolt/volt-client-grpc 0.15.0 → 0.16.0-beta.1
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 +114 -95
- package/package.json +3 -3
- package/protobuf/tdx/volt_api/data/v1/sqlite_database_api.proto +2 -2
- package/protobuf/tdx/volt_api/volt/v1/file_api.proto +2 -0
- package/protobuf/tdx/volt_api/volt/v1/ssi.proto +40 -0
- package/protobuf/tdx/volt_api/volt/v1/ssi_api.proto +126 -0
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +117 -60
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +127 -29
- package/src/constants.js +14 -14
- package/src/grpc-call.js +31 -33
- package/src/utils.js +22 -2
- package/src/volt-client-internal.js +205 -18
- package/src/volt-client.js +16 -4
- package/src/volt-credential.js +3 -2
- package/protobuf/tdx/volt_api/ssi/v1/ssi_api.proto +0 -37
package/src/constants.js
CHANGED
|
@@ -4,37 +4,37 @@ export const constants = {
|
|
|
4
4
|
collectionOperation: {
|
|
5
5
|
add: "ADD",
|
|
6
6
|
remove: "REMOVE",
|
|
7
|
-
update: "UPDATE"
|
|
7
|
+
update: "UPDATE"
|
|
8
8
|
},
|
|
9
9
|
crypto: {
|
|
10
10
|
subjectDefaults: [
|
|
11
11
|
{
|
|
12
12
|
name: "countryName",
|
|
13
|
-
value: "GB"
|
|
13
|
+
value: "GB"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
name: "localityName",
|
|
17
|
-
value: "Southampton"
|
|
17
|
+
value: "Southampton"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
name: "organizationName",
|
|
21
|
-
value: "nquiringminds Ltd"
|
|
22
|
-
}
|
|
21
|
+
value: "nquiringminds Ltd"
|
|
22
|
+
}
|
|
23
23
|
],
|
|
24
|
-
userIdOID: "0.9.2342.19200300.100.1.1"
|
|
24
|
+
userIdOID: "0.9.2342.19200300.100.1.1"
|
|
25
25
|
},
|
|
26
26
|
defaultDIDHostName: "cloud.tdxvolt.com",
|
|
27
27
|
didServiceType: {
|
|
28
|
-
voltConfig: "tdx-volt-config"
|
|
28
|
+
voltConfig: "tdx-volt-config"
|
|
29
29
|
},
|
|
30
30
|
identityType: {
|
|
31
31
|
did: "did",
|
|
32
|
-
volt: "volt"
|
|
32
|
+
volt: "volt"
|
|
33
33
|
},
|
|
34
34
|
onlineStatus: {
|
|
35
35
|
offline: "offline",
|
|
36
36
|
online: "online",
|
|
37
|
-
unknown: "unknown"
|
|
37
|
+
unknown: "unknown"
|
|
38
38
|
},
|
|
39
39
|
publicKeyPrefix: "-----BEGIN PUBLIC KEY-----",
|
|
40
40
|
privateKeyPrefix: "-----BEGIN RSA PRIVATE KEY-----",
|
|
@@ -45,14 +45,14 @@ export const constants = {
|
|
|
45
45
|
proxy: "proxy",
|
|
46
46
|
proxyView: "proxy-view",
|
|
47
47
|
vc: "vc",
|
|
48
|
-
vcView: "vc-view"
|
|
48
|
+
vcView: "vc-view"
|
|
49
49
|
},
|
|
50
50
|
voltStatus: {
|
|
51
51
|
pending: "",
|
|
52
52
|
online: "online",
|
|
53
53
|
offline: "offline",
|
|
54
54
|
deleted: "deleted",
|
|
55
|
-
revoked: "revoked"
|
|
55
|
+
revoked: "revoked"
|
|
56
56
|
},
|
|
57
57
|
/**
|
|
58
58
|
* These top-level service types should map to a protobuf definition
|
|
@@ -62,14 +62,14 @@ export const constants = {
|
|
|
62
62
|
fileAPI: "tdx.volt_api.volt.v1.FileAPI",
|
|
63
63
|
sqliteServerAPI: "tdx.volt_api.data.v1.SqliteServerAPI",
|
|
64
64
|
sqliteDatabaseAPI: "tdx.volt_api.data.v1.SqliteDatabaseAPI",
|
|
65
|
-
ssiAPI: "tdx.volt_api.
|
|
65
|
+
ssiAPI: "tdx.volt_api.volt.v1.SsiAPI",
|
|
66
66
|
relayAPI: "tdx.volt_api.relay.v1.RelayAPI",
|
|
67
67
|
voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
|
|
68
|
-
wireAPI: "tdx.volt_api.volt.v1.WireAPI"
|
|
68
|
+
wireAPI: "tdx.volt_api.volt.v1.WireAPI"
|
|
69
69
|
},
|
|
70
70
|
tunnelInvokeMethod: "/tdx.volt_api.volt.v1.VoltAPI/Invoke",
|
|
71
71
|
tunnelPingInterval: 10000,
|
|
72
72
|
tunnelPingTimeout: 2500,
|
|
73
73
|
tunnelReconnectAfter: 30000,
|
|
74
|
-
tunnelTimeoutInterval: 10000 * 2
|
|
74
|
+
tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
|
|
75
75
|
};
|
package/src/grpc-call.js
CHANGED
|
@@ -8,7 +8,7 @@ const log = debug("volt-client-grpc:grpc-call");
|
|
|
8
8
|
function _prepareInvokeRequest(
|
|
9
9
|
request,
|
|
10
10
|
method,
|
|
11
|
-
methodType = "METHOD_TYPE_UNARY"
|
|
11
|
+
methodType = "METHOD_TYPE_UNARY"
|
|
12
12
|
) {
|
|
13
13
|
const isServiceRelayed =
|
|
14
14
|
this._service?.service_description.host_type ===
|
|
@@ -21,7 +21,7 @@ function _prepareInvokeRequest(
|
|
|
21
21
|
this._voltClient.voltConfig.id,
|
|
22
22
|
this._service?.service_description.host_public_key ||
|
|
23
23
|
this._voltClient.credential.voltPublicKey,
|
|
24
|
-
true
|
|
24
|
+
true
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
let invokeRequest;
|
|
@@ -52,42 +52,40 @@ function _prepareInvokeRequest(
|
|
|
52
52
|
method_name: this._grpcClient[method].path,
|
|
53
53
|
method_type: methodType,
|
|
54
54
|
token: methodToken.token,
|
|
55
|
-
request: requestPayload
|
|
56
|
-
}
|
|
55
|
+
request: requestPayload
|
|
56
|
+
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
let
|
|
59
|
+
let targetDID = [];
|
|
60
60
|
let invokePayload;
|
|
61
61
|
if (this._voltClient.isVoltRelay || isServiceRelayed) {
|
|
62
62
|
// When sending via a relay we need to encrypt the payload.
|
|
63
63
|
invokePayload = VoltCredential.aesEncrypt(
|
|
64
64
|
tunnelMethod.responseSerialize(tunnelResp),
|
|
65
65
|
methodToken.sharedKey.key,
|
|
66
|
-
methodToken.sharedKey.iv
|
|
66
|
+
methodToken.sharedKey.iv
|
|
67
67
|
);
|
|
68
68
|
} else {
|
|
69
69
|
invokePayload = tunnelMethod.responseSerialize(tunnelResp);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (this._voltClient.isVoltRelay) {
|
|
73
|
-
|
|
73
|
+
targetDID.push(this._voltClient.voltConfig.id);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (isServiceRelayed) {
|
|
77
77
|
// Add another relay hop if the target service is relayed.
|
|
78
78
|
log("target service is relayed");
|
|
79
|
-
|
|
80
|
-
this._service.service_description.host_fingerprint,
|
|
81
|
-
);
|
|
79
|
+
targetDID.push(this._service.service_description.host_client_id);
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
log("target is %j",
|
|
82
|
+
log("target is %j", targetDID);
|
|
85
83
|
|
|
86
84
|
invokeRequest = {
|
|
87
|
-
|
|
85
|
+
target_did: targetDID,
|
|
88
86
|
token: methodToken.token,
|
|
89
87
|
payload: invokePayload,
|
|
90
|
-
target_service_id: isServiceRelayed ? this._service.id : undefined
|
|
88
|
+
target_service_id: isServiceRelayed ? this._service.id : undefined
|
|
91
89
|
};
|
|
92
90
|
|
|
93
91
|
// Replace the target method with a call to Invoke on the relay Volt.
|
|
@@ -100,7 +98,7 @@ function _prepareInvokeRequest(
|
|
|
100
98
|
meta: methodToken,
|
|
101
99
|
method: callMethod,
|
|
102
100
|
methodName: method,
|
|
103
|
-
request: invokeRequest
|
|
101
|
+
request: invokeRequest
|
|
104
102
|
};
|
|
105
103
|
}
|
|
106
104
|
|
|
@@ -120,18 +118,18 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
120
118
|
|
|
121
119
|
const tunnelResp = {
|
|
122
120
|
method_payload: {
|
|
123
|
-
payload: requestPayload
|
|
124
|
-
}
|
|
121
|
+
payload: requestPayload
|
|
122
|
+
}
|
|
125
123
|
};
|
|
126
124
|
|
|
127
125
|
const invokePayload = VoltCredential.aesEncrypt(
|
|
128
126
|
tunnelMethod.responseSerialize(tunnelResp),
|
|
129
127
|
invokeInfo.meta.sharedKey.key,
|
|
130
|
-
invokeInfo.meta.sharedKey.iv
|
|
128
|
+
invokeInfo.meta.sharedKey.iv
|
|
131
129
|
);
|
|
132
130
|
|
|
133
131
|
payloadRequest = {
|
|
134
|
-
payload: invokePayload
|
|
132
|
+
payload: invokePayload
|
|
135
133
|
};
|
|
136
134
|
} else {
|
|
137
135
|
payloadRequest = request;
|
|
@@ -155,7 +153,7 @@ function _parseResponse(method, meta, response) {
|
|
|
155
153
|
decryptedPayload = VoltCredential.aesDecrypt(
|
|
156
154
|
response.payload,
|
|
157
155
|
meta.sharedKey.key,
|
|
158
|
-
meta.sharedKey.iv
|
|
156
|
+
meta.sharedKey.iv
|
|
159
157
|
);
|
|
160
158
|
} else {
|
|
161
159
|
decryptedPayload = response.payload;
|
|
@@ -166,12 +164,12 @@ function _parseResponse(method, meta, response) {
|
|
|
166
164
|
if (responsePayload.payload === "method_payload") {
|
|
167
165
|
try {
|
|
168
166
|
invokeResponse.payload = this._grpcClient[method].responseDeserialize(
|
|
169
|
-
responsePayload.method_payload.payload
|
|
167
|
+
responsePayload.method_payload.payload
|
|
170
168
|
);
|
|
171
169
|
} catch (err) {
|
|
172
170
|
log("failure deserialising payload for %s", this._methodName);
|
|
173
171
|
throw new Error(
|
|
174
|
-
"failure deserialising payload - check protobuf definition matches with data being sent"
|
|
172
|
+
"failure deserialising payload - check protobuf definition matches with data being sent"
|
|
175
173
|
);
|
|
176
174
|
}
|
|
177
175
|
} else if (responsePayload.payload === "method_end") {
|
|
@@ -185,11 +183,11 @@ function _parseResponse(method, meta, response) {
|
|
|
185
183
|
"Tunnel status received: %s, code %d, description: %s",
|
|
186
184
|
response.status.message,
|
|
187
185
|
response.status.code,
|
|
188
|
-
response.status.description || "n/a"
|
|
186
|
+
response.status.description || "n/a"
|
|
189
187
|
);
|
|
190
188
|
invokeResponse = {
|
|
191
189
|
ended: true,
|
|
192
|
-
error: `Error in tunnel: ${response.status.message}
|
|
190
|
+
error: `Error in tunnel: ${response.status.message}`
|
|
193
191
|
};
|
|
194
192
|
} else {
|
|
195
193
|
invokeResponse.methodId = response.invoke_id;
|
|
@@ -199,11 +197,11 @@ function _parseResponse(method, meta, response) {
|
|
|
199
197
|
const decryptedPayload = VoltCredential.aesDecrypt(
|
|
200
198
|
response,
|
|
201
199
|
meta.sharedKey.key,
|
|
202
|
-
meta.sharedKey.iv
|
|
200
|
+
meta.sharedKey.iv
|
|
203
201
|
);
|
|
204
202
|
invokeResponse = {
|
|
205
203
|
payload:
|
|
206
|
-
this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
|
|
204
|
+
this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
|
|
207
205
|
};
|
|
208
206
|
} else {
|
|
209
207
|
invokeResponse = { payload: response };
|
|
@@ -233,7 +231,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
233
231
|
this,
|
|
234
232
|
request,
|
|
235
233
|
this._methodName,
|
|
236
|
-
this._methodType
|
|
234
|
+
this._methodType
|
|
237
235
|
);
|
|
238
236
|
|
|
239
237
|
const isServiceRelayed =
|
|
@@ -248,7 +246,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
this._call = callClient[this._initialRequest.method](
|
|
251
|
-
this._initialRequest.meta.metadata
|
|
249
|
+
this._initialRequest.meta.metadata
|
|
252
250
|
);
|
|
253
251
|
|
|
254
252
|
this._call.on("data", (streamResponse) => {
|
|
@@ -257,14 +255,14 @@ export default class GRPCCall extends EventEmitter {
|
|
|
257
255
|
this,
|
|
258
256
|
this._methodName,
|
|
259
257
|
this._initialRequest.meta,
|
|
260
|
-
streamResponse
|
|
258
|
+
streamResponse
|
|
261
259
|
);
|
|
262
260
|
if (parsedResponse.payload) {
|
|
263
261
|
// Interpret a non-empty status message as an error.
|
|
264
262
|
if (parsedResponse.payload?.status?.message) {
|
|
265
263
|
this.emit(
|
|
266
264
|
"error",
|
|
267
|
-
new Error(parsedResponse.payload.status.message)
|
|
265
|
+
new Error(parsedResponse.payload.status.message)
|
|
268
266
|
);
|
|
269
267
|
} else {
|
|
270
268
|
this.emit("data", parsedResponse.payload);
|
|
@@ -283,7 +281,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
283
281
|
log(
|
|
284
282
|
"failure processing %s call response: %s",
|
|
285
283
|
this._methodName,
|
|
286
|
-
err.message
|
|
284
|
+
err.message
|
|
287
285
|
);
|
|
288
286
|
this.emit("error", err);
|
|
289
287
|
}
|
|
@@ -305,7 +303,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
305
303
|
log(
|
|
306
304
|
"ended call %s, method id: %s",
|
|
307
305
|
this._methodName,
|
|
308
|
-
this._initialRequest.methodId || "n/a - not tunnelling"
|
|
306
|
+
this._initialRequest.methodId || "n/a - not tunnelling"
|
|
309
307
|
);
|
|
310
308
|
this.emit("end");
|
|
311
309
|
});
|
|
@@ -338,7 +336,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
338
336
|
const payloadRequest = _preparePayloadRequest.call(
|
|
339
337
|
this,
|
|
340
338
|
request,
|
|
341
|
-
this._initialRequest
|
|
339
|
+
this._initialRequest
|
|
342
340
|
);
|
|
343
341
|
return this._call.write(payloadRequest);
|
|
344
342
|
} else {
|
|
@@ -346,7 +344,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
346
344
|
this,
|
|
347
345
|
request,
|
|
348
346
|
this._methodName,
|
|
349
|
-
this._methodType
|
|
347
|
+
this._methodType
|
|
350
348
|
);
|
|
351
349
|
|
|
352
350
|
return this._call.write(this._initialRequest.request);
|
package/src/utils.js
CHANGED
|
@@ -64,11 +64,16 @@ export const createPublicKeyFingerprint = (key) => {
|
|
|
64
64
|
return createBase58Hash(removeCarriageReturn(key));
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
export const base64ToBase64Url = (base64) => {
|
|
68
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
69
|
+
};
|
|
70
|
+
|
|
67
71
|
export const signBase64 = (key, msg) => {
|
|
68
72
|
const md = forge.md.sha256.create();
|
|
69
73
|
md.update(msg);
|
|
70
74
|
const signature = key.sign(md);
|
|
71
|
-
|
|
75
|
+
const base64Standard = forge.util.encode64(signature);
|
|
76
|
+
return base64ToBase64Url(base64Standard);
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
export const verifyBase64 = (key, msg, signature) => {
|
|
@@ -137,10 +142,25 @@ export const createRSAKey = (passphrase = "") => {
|
|
|
137
142
|
export const sha256Base64 = (msg) => {
|
|
138
143
|
const md = forge.md.sha256.create();
|
|
139
144
|
md.update(msg);
|
|
140
|
-
|
|
145
|
+
const base64Standard = forge.util.encode64(md.digest().bytes());
|
|
146
|
+
return base64ToBase64Url(base64Standard);
|
|
141
147
|
};
|
|
142
148
|
|
|
143
149
|
export const decryptPrivateKey = (pem, passphrase) => {
|
|
144
150
|
const decryptedKey = pki.decryptRsaPrivateKey(pem, passphrase);
|
|
145
151
|
return forgePrivateKeyToPem(decryptedKey);
|
|
146
152
|
};
|
|
153
|
+
|
|
154
|
+
export const jwsSignature = (signature) => {
|
|
155
|
+
//
|
|
156
|
+
// Use an unattached payload JWS signature for the DID document proof.
|
|
157
|
+
// This is a JWS with an empty payload and a detached payload digest.
|
|
158
|
+
//
|
|
159
|
+
// (https://tools.ietf.org/html/rfc7797#appendix-F) as used in the VC spec
|
|
160
|
+
// (https://www.w3.org/TR/vc-data-model/).
|
|
161
|
+
//
|
|
162
|
+
const jwsHeader = base64ToBase64Url(
|
|
163
|
+
forge.util.encode64('{"alg":"RS256","b64": false}')
|
|
164
|
+
);
|
|
165
|
+
return jwsHeader + ".." + signature;
|
|
166
|
+
};
|
|
@@ -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,6 +32,55 @@ const voltServices = [
|
|
|
31
32
|
constants.serviceType.wireAPI
|
|
32
33
|
];
|
|
33
34
|
|
|
35
|
+
function issueAuthenticate(authenticateRequest, ttl) {
|
|
36
|
+
// eslint-disable-next-line no-use-before-define
|
|
37
|
+
return unaryCallInternal
|
|
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));
|
|
44
|
+
} else {
|
|
45
|
+
switch (authenticateResponse.decision) {
|
|
46
|
+
case "POLICY_DECISION_PERMIT": {
|
|
47
|
+
//
|
|
48
|
+
// The request status is permit => cache the bind response info.
|
|
49
|
+
//
|
|
50
|
+
|
|
51
|
+
// This is the certificate assigned to us by the volt.
|
|
52
|
+
this._credential.cache.cert = authenticateResponse.cert;
|
|
53
|
+
|
|
54
|
+
// This is the signing CA used by the volt.
|
|
55
|
+
this._credential.cache.ca = authenticateResponse.chain;
|
|
56
|
+
|
|
57
|
+
// Identity resource id is assigned by the volt.
|
|
58
|
+
this._credential.cache.client_id = authenticateResponse.session_id;
|
|
59
|
+
this._credential.saveCache();
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "POLICY_DECISION_DENY": {
|
|
63
|
+
log(">>>>>>>>>>>>>>> access request is DENIED <<<<<<<<<<<<<<<<<<<");
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "POLICY_DECISION_PROMPT":
|
|
67
|
+
case "POLICY_DECISION_PENDING": {
|
|
68
|
+
log("access pending approval - waiting...");
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
log(
|
|
73
|
+
"ignoring unknown bind descision %s",
|
|
74
|
+
authenticateResponse.status
|
|
75
|
+
);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return authenticateResponse.decision;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
function issueBind(bindRequest, ttl) {
|
|
35
85
|
// eslint-disable-next-line no-use-before-define
|
|
36
86
|
return unaryCallInternal
|
|
@@ -86,7 +136,8 @@ function findDIDDocumentService(document, serviceType) {
|
|
|
86
136
|
return matches;
|
|
87
137
|
}
|
|
88
138
|
|
|
89
|
-
|
|
139
|
+
|
|
140
|
+
export async function authenticateInternal() {
|
|
90
141
|
try {
|
|
91
142
|
// Check if we need to resolve a Relay volt.
|
|
92
143
|
if (
|
|
@@ -100,6 +151,7 @@ export async function bindInternal() {
|
|
|
100
151
|
this,
|
|
101
152
|
`${relayURL}/discovery`
|
|
102
153
|
);
|
|
154
|
+
|
|
103
155
|
if (this._voltConfig.relay.ca_pem) {
|
|
104
156
|
log(
|
|
105
157
|
"Auto-fetched Relay CA %s, remote address %s",
|
|
@@ -133,19 +185,107 @@ export async function bindInternal() {
|
|
|
133
185
|
this._credential.saveCache();
|
|
134
186
|
|
|
135
187
|
// Form the request message.
|
|
136
|
-
const
|
|
188
|
+
const authenticateRequest = {
|
|
137
189
|
binding_name: this._config.client_name,
|
|
138
190
|
public_key: publicKeyPem,
|
|
139
191
|
host: this._credential.cache.bindIp,
|
|
140
|
-
x509_credential: []
|
|
141
192
|
};
|
|
142
193
|
|
|
143
|
-
if (this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
194
|
+
if (this._voltConfig.challenge_code) {
|
|
195
|
+
authenticateRequest.challenge = voltUtils.signBase64(
|
|
196
|
+
keyPair.privateKey,
|
|
197
|
+
this._voltConfig.challenge_code
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
log(
|
|
201
|
+
"****no Volt challenge code available**** => not sending challenge signature"
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
log("authenticate TTL is ", this._config.bindRequestTTL);
|
|
206
|
+
|
|
207
|
+
// This will block while the request is pending.
|
|
208
|
+
const pollInterval = 10000;
|
|
209
|
+
let decision = "";
|
|
210
|
+
do {
|
|
211
|
+
if (decision) {
|
|
212
|
+
// Wait a while before retrying.
|
|
213
|
+
// eslint-disable-next-line no-await-in-loop
|
|
214
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
215
|
+
}
|
|
216
|
+
// eslint-disable-next-line no-await-in-loop
|
|
217
|
+
decision = await issueAuthenticate.call(
|
|
218
|
+
this,
|
|
219
|
+
authenticateRequest,
|
|
220
|
+
this._voltConfig.bindRequestTTL
|
|
221
|
+
);
|
|
222
|
+
log("authenticate decision: %s", decision);
|
|
223
|
+
} while (
|
|
224
|
+
decision === "POLICY_DECISION_PROMPT" ||
|
|
225
|
+
decision === "POLICY_DECISION_PENDING"
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return decision === "POLICY_DECISION_PERMIT";
|
|
229
|
+
} catch (err) {
|
|
230
|
+
log("failure binding to Volt [%s]", err.message);
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function bindInternal() {
|
|
236
|
+
try {
|
|
237
|
+
// Check if we need to resolve a Relay volt.
|
|
238
|
+
if (
|
|
239
|
+
typeof this._voltConfig?.relay === "string" ||
|
|
240
|
+
this._voltConfig?.relay?.http_address
|
|
241
|
+
) {
|
|
242
|
+
const relayURL =
|
|
243
|
+
this._voltConfig.relay?.http_address || this._voltConfig.relay;
|
|
244
|
+
log("attempting to retrieve Relay information from %s", relayURL);
|
|
245
|
+
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
246
|
+
this,
|
|
247
|
+
`${relayURL}/discovery`
|
|
248
|
+
);
|
|
249
|
+
if (this._voltConfig.relay.ca_pem) {
|
|
250
|
+
log(
|
|
251
|
+
"Auto-fetched Relay CA %s, remote address %s",
|
|
252
|
+
this._voltConfig.relay.ca_pem,
|
|
253
|
+
this._voltConfig.relay.address
|
|
254
|
+
);
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error("Unable to fetch Relay CA - cannot securely connect.");
|
|
257
|
+
}
|
|
147
258
|
}
|
|
148
259
|
|
|
260
|
+
if (!this._credential.cache.ca) {
|
|
261
|
+
this._credential.cache.ca = this._voltConfig.ca_pem;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!this._credential.cache.ca) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
"No certificate authority found in configuration for target Volt - check configuration"
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const keyPair = this._credential.getKey();
|
|
271
|
+
const publicKeyPem = voltUtils.removeCarriageReturn(
|
|
272
|
+
pki.publicKeyToPem(keyPair.publicKey)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (!this._credential.cache.bindIp) {
|
|
276
|
+
// Default the IP address if none is specified. This is used to set the SAN in the
|
|
277
|
+
// certificate issued by the Volt.
|
|
278
|
+
this._credential.cache.bindIp = ip.address();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this._credential.saveCache();
|
|
282
|
+
|
|
283
|
+
// Form the request message.
|
|
284
|
+
const bindRequest = {
|
|
285
|
+
binding_name: this._config.client_name,
|
|
286
|
+
host: this._credential.cache.bindIp
|
|
287
|
+
};
|
|
288
|
+
|
|
149
289
|
if (this._voltConfig.challenge_code) {
|
|
150
290
|
bindRequest.challenge = voltUtils.signBase64(
|
|
151
291
|
keyPair.privateKey,
|
|
@@ -157,14 +297,65 @@ export async function bindInternal() {
|
|
|
157
297
|
);
|
|
158
298
|
}
|
|
159
299
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this._credential.cache.cloud_cert + this._voltConfig.relay.ca_pem
|
|
164
|
-
);
|
|
300
|
+
if (!this._credential.cache.client_id) {
|
|
301
|
+
// If we don't have a client id, then we need to create a DID.
|
|
302
|
+
this._credential.cache.client_id = `did:volt:${generateId()}`;
|
|
165
303
|
}
|
|
166
304
|
|
|
167
|
-
|
|
305
|
+
const did = this._credential.cache.client_id;
|
|
306
|
+
|
|
307
|
+
// Order of properties in the DID document is important,
|
|
308
|
+
// they must be in alphabetical order throughout the document.
|
|
309
|
+
// This is because the DID document is signed and the signature
|
|
310
|
+
// is over the entire document.
|
|
311
|
+
const didDocument = {
|
|
312
|
+
"@context": "https://www.w3.org/ns/did/v1",
|
|
313
|
+
authentication: [`${did}#key-1`],
|
|
314
|
+
description: this._config.client_name,
|
|
315
|
+
id: did,
|
|
316
|
+
verificationMethod: [
|
|
317
|
+
{
|
|
318
|
+
controller: did,
|
|
319
|
+
id: `${did}#key-1`,
|
|
320
|
+
publicKeyPem: publicKeyPem,
|
|
321
|
+
type: "RsaSignature2018"
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const signDocument = JSON.stringify(didDocument, null, 2);
|
|
327
|
+
log("signing did document");
|
|
328
|
+
log(signDocument);
|
|
329
|
+
|
|
330
|
+
// Sign the DID document.
|
|
331
|
+
const documentDigest = voltUtils.signBase64(
|
|
332
|
+
keyPair.privateKey,
|
|
333
|
+
signDocument
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const proofSignature = voltUtils.jwsSignature(documentDigest);
|
|
337
|
+
|
|
338
|
+
// Create the DID document proof.
|
|
339
|
+
didDocument.proof = {
|
|
340
|
+
created: new Date().toISOString(),
|
|
341
|
+
jws: proofSignature,
|
|
342
|
+
proofPurpose: "assertionMethod",
|
|
343
|
+
type: "RsaSignature2018",
|
|
344
|
+
verificationMethod: `${did}#key-1`
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Set the DID document with proof in the bind request.
|
|
348
|
+
bindRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
349
|
+
|
|
350
|
+
// Now sign the entire DID document, including the proof and set it in the
|
|
351
|
+
// bind request. This is required by the Volt to verify that we have the private key for the DID.
|
|
352
|
+
bindRequest.did_document_signature = voltUtils.signBase64(
|
|
353
|
+
keyPair.privateKey,
|
|
354
|
+
bindRequest.did_document
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
log("did document is %s", bindRequest.did_document);
|
|
358
|
+
log("did signature is %s", bindRequest.did_document_signature);
|
|
168
359
|
log("bind TTL is ", this._config.bindRequestTTL);
|
|
169
360
|
|
|
170
361
|
// This will block while the request is pending.
|
|
@@ -304,9 +495,6 @@ export function getVoltAPIClientInternal() {
|
|
|
304
495
|
|
|
305
496
|
export function getAPIClientInternal(service) {
|
|
306
497
|
if (!this._cachedService[service.id]) {
|
|
307
|
-
const isRelayedService =
|
|
308
|
-
service.service_description.host_type === "SERVICE_HOST_TYPE_RELAYED";
|
|
309
|
-
|
|
310
498
|
const serviceAddress = this.isRemote
|
|
311
499
|
? this._voltConfig.relay.address
|
|
312
500
|
: service.service_description.host_address;
|
|
@@ -354,9 +542,8 @@ export function getServiceClient(service) {
|
|
|
354
542
|
}
|
|
355
543
|
|
|
356
544
|
export function unaryCallInternal(method, request, service) {
|
|
357
|
-
const grpcClient = getServiceClient.call(this, service);
|
|
358
|
-
|
|
359
545
|
return new Promise((resolve, reject) => {
|
|
546
|
+
const grpcClient = getServiceClient.call(this, service);
|
|
360
547
|
const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY", service);
|
|
361
548
|
|
|
362
549
|
let response = null;
|
package/src/volt-client.js
CHANGED
|
@@ -5,6 +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
|
+
authenticateInternal,
|
|
8
9
|
bindInternal,
|
|
9
10
|
connectInternal,
|
|
10
11
|
fetchVoltConfig,
|
|
@@ -166,14 +167,14 @@ export class VoltClient extends EventEmitter {
|
|
|
166
167
|
if (this._credential._cryptoCache.cert) {
|
|
167
168
|
isBound = true;
|
|
168
169
|
} else {
|
|
169
|
-
// No certificate present in the cache yet => we need to
|
|
170
|
+
// No certificate present in the cache yet => we need to authenticate.
|
|
170
171
|
try {
|
|
171
|
-
isBound = await
|
|
172
|
+
isBound = await authenticateInternal.call(this);
|
|
172
173
|
} catch (bindErr) {
|
|
173
174
|
if (bindErr.message === "invalid arguments") {
|
|
174
175
|
// This is usually the result of a missing challenge signature or credential.
|
|
175
176
|
log(
|
|
176
|
-
"failure
|
|
177
|
+
"failure authenticating on Volt - did you supply a challenge code or verified credential?"
|
|
177
178
|
);
|
|
178
179
|
throw bindErr;
|
|
179
180
|
}
|
|
@@ -182,7 +183,7 @@ export class VoltClient extends EventEmitter {
|
|
|
182
183
|
|
|
183
184
|
if (!isBound) {
|
|
184
185
|
// This is usually because of request being denied.
|
|
185
|
-
throw new Error("Volt
|
|
186
|
+
throw new Error("Volt authentication failed");
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
return Promise.resolve();
|
|
@@ -408,6 +409,10 @@ export class VoltClient extends EventEmitter {
|
|
|
408
409
|
* VoltAPI - volt management
|
|
409
410
|
*/
|
|
410
411
|
|
|
412
|
+
Authenticate(request) {
|
|
413
|
+
return unaryCallInternal.call(this, "Authenticate", request);
|
|
414
|
+
}
|
|
415
|
+
|
|
411
416
|
Bind(request) {
|
|
412
417
|
return unaryCallInternal.call(this, "Bind", request);
|
|
413
418
|
}
|
|
@@ -569,6 +574,13 @@ export class VoltClient extends EventEmitter {
|
|
|
569
574
|
});
|
|
570
575
|
}
|
|
571
576
|
|
|
577
|
+
/**
|
|
578
|
+
* SqliteServerAPI
|
|
579
|
+
*/
|
|
580
|
+
CreateDatabase(request) {
|
|
581
|
+
return unaryCallInternal.call(this, "CreateDatabase", request);
|
|
582
|
+
}
|
|
583
|
+
|
|
572
584
|
/**
|
|
573
585
|
* SqliteDatabaseAPI
|
|
574
586
|
*/
|