@tdxvolt/volt-client-grpc 0.17.0 → 0.18.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/README.md +39 -3
- package/lib/index.cjs +334 -216
- package/package.json +2 -2
- package/protobuf/tdx/volt_api/volt/v1/ssi_api.proto +5 -5
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +6 -1
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +3 -0
- package/protobuf/tdx/volt_api/volt/v1/wire_api.proto +4 -0
- package/src/constants.js +2 -54
- package/src/grpc-call.js +109 -56
- package/src/grpc-utils.js +3 -3
- package/src/rpc-invocation.js +51 -13
- package/src/utils.js +1 -1
- package/src/volt-client-internal.js +39 -26
- package/src/volt-client.js +42 -12
- package/src/volt-connection.js +16 -2
- package/src/volt-credential.js +13 -7
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.18.1",
|
|
7
7
|
"description": "tdx Volt library for nodejs clients",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"license": "ISC",
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@grpc/proto-loader": "^0.6.4",
|
|
40
|
-
"@tdxvolt/volt-client-web": "^0.
|
|
40
|
+
"@tdxvolt/volt-client-web": "^0.18.1",
|
|
41
41
|
"bent": "^7.3.12",
|
|
42
42
|
"builtin-modules": "^3.3.0",
|
|
43
43
|
"debug": "^4.1.1",
|
|
@@ -2,18 +2,19 @@ syntax = "proto3";
|
|
|
2
2
|
|
|
3
3
|
package tdx.volt_api.volt.v1;
|
|
4
4
|
|
|
5
|
-
import "tdx/volt_api/volt/v1/ssi.proto";
|
|
6
5
|
import "tdx/volt_api/volt/v1/status.proto";
|
|
6
|
+
import "tdx/volt_api/volt/v1/ssi.proto";
|
|
7
7
|
|
|
8
8
|
service SsiAPI {
|
|
9
9
|
// Delete a DID document or all DID documents originating from a given Volt.
|
|
10
|
-
// Requires
|
|
10
|
+
// Requires `volt:delete-did` API privilege.
|
|
11
11
|
rpc DeleteDID(DeleteDIDRequest) returns (DeleteDIDResponse);
|
|
12
12
|
|
|
13
13
|
// Get all updates to the DID registry since the specified timestamp.
|
|
14
14
|
// Internal use only.
|
|
15
15
|
rpc GetDIDRegistryUpdates(GetDIDRegistryUpdatesRequest) returns (GetDIDRegistryUpdatesResponse);
|
|
16
16
|
|
|
17
|
+
// Import a verifiable credential.
|
|
17
18
|
rpc ImportCredential(ImportCredentialRequest) returns (ImportCredentialResponse);
|
|
18
19
|
|
|
19
20
|
// Parse a verifiable credential from a URL or a verifiable presentation.
|
|
@@ -23,8 +24,8 @@ service SsiAPI {
|
|
|
23
24
|
rpc ResolveDID(ResolveDIDRequest) returns (ResolveDIDResponse);
|
|
24
25
|
|
|
25
26
|
// Register a DID document.
|
|
26
|
-
// This is
|
|
27
|
-
// To register a new DID document, use the VoltAPI Authenticate method.
|
|
27
|
+
// This is intended for use by the DID registry when synchronising with other registries.
|
|
28
|
+
// To register a new DID document, it is recommended to use the VoltAPI Authenticate method.
|
|
28
29
|
rpc RegisterDIDDocument(RegisterDIDDocumentRequest) returns (RegisterDIDDocumentResponse);
|
|
29
30
|
|
|
30
31
|
// Save a verifiable credential.
|
|
@@ -153,7 +154,6 @@ message RegisterDIDDocumentRequest {
|
|
|
153
154
|
DIDRegistryUpdate did_update = 2;
|
|
154
155
|
|
|
155
156
|
// When updating an existing DID document, it is necessary to include a signature of the document JSON, signed by the DID document's current owner.
|
|
156
|
-
// This is different from the proof contained in the DID document itself.
|
|
157
157
|
string update_signature = 3;
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -309,12 +309,17 @@ message VoltEndpoint {
|
|
|
309
309
|
// The online status of the Volt.
|
|
310
310
|
OnlineStatus online_status = 11;
|
|
311
311
|
|
|
312
|
-
//
|
|
312
|
+
// Indicates that this Volt acts as a Relay.
|
|
313
313
|
bool has_relay = 12;
|
|
314
314
|
|
|
315
|
+
// The API version supported by the endpoint.
|
|
315
316
|
Version api_version = 13;
|
|
316
317
|
|
|
318
|
+
// Optional description of the endpoint.
|
|
317
319
|
string description = 14;
|
|
320
|
+
|
|
321
|
+
// The list of DID registries that this Volt trusts.
|
|
322
|
+
repeated string did_registry = 15;
|
|
318
323
|
}
|
|
319
324
|
|
|
320
325
|
// Internal use only.
|
|
@@ -22,6 +22,10 @@ message PublishWireRequest {
|
|
|
22
22
|
|
|
23
23
|
// The chunk of data to publish.
|
|
24
24
|
bytes chunk = 2;
|
|
25
|
+
|
|
26
|
+
// Whether to persist the chunk.
|
|
27
|
+
// This is only valid if the wire is configured to persist messages, i.e. the `volt:wire-persist` attribute is true.
|
|
28
|
+
bool do_not_persist = 3;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
message PublishWireResponse {
|
package/src/constants.js
CHANGED
|
@@ -1,57 +1,10 @@
|
|
|
1
|
-
// eslint-disable-next-line import/prefer-default-export
|
|
2
1
|
export const constants = {
|
|
3
2
|
authTokenName: "volt-token",
|
|
4
|
-
|
|
5
|
-
add: "ADD",
|
|
6
|
-
remove: "REMOVE",
|
|
7
|
-
update: "UPDATE"
|
|
8
|
-
},
|
|
9
|
-
crypto: {
|
|
10
|
-
subjectDefaults: [
|
|
11
|
-
{
|
|
12
|
-
name: "countryName",
|
|
13
|
-
value: "GB"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
name: "localityName",
|
|
17
|
-
value: "Southampton"
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: "organizationName",
|
|
21
|
-
value: "nquiringminds Ltd"
|
|
22
|
-
}
|
|
23
|
-
],
|
|
24
|
-
userIdOID: "0.9.2342.19200300.100.1.1"
|
|
25
|
-
},
|
|
26
|
-
defaultDIDHostName: "tdxvolt.com",
|
|
3
|
+
defaultDIDHostName: "coreid.com",
|
|
27
4
|
didServiceType: {
|
|
28
5
|
voltConfig: "volt-discovery"
|
|
29
6
|
},
|
|
30
|
-
identityType: {
|
|
31
|
-
did: "did",
|
|
32
|
-
volt: "volt"
|
|
33
|
-
},
|
|
34
|
-
onlineStatus: {
|
|
35
|
-
offline: "offline",
|
|
36
|
-
online: "online",
|
|
37
|
-
unknown: "unknown"
|
|
38
|
-
},
|
|
39
7
|
privateEncryptedKeyPrefix: "-----BEGIN ENCRYPTED PRIVATE KEY-----",
|
|
40
|
-
resource: {
|
|
41
|
-
identity: "ident",
|
|
42
|
-
identityView: "ident-view",
|
|
43
|
-
proxy: "proxy",
|
|
44
|
-
proxyView: "proxy-view",
|
|
45
|
-
vc: "vc",
|
|
46
|
-
vcView: "vc-view"
|
|
47
|
-
},
|
|
48
|
-
voltStatus: {
|
|
49
|
-
pending: "",
|
|
50
|
-
online: "online",
|
|
51
|
-
offline: "offline",
|
|
52
|
-
deleted: "deleted",
|
|
53
|
-
revoked: "revoked"
|
|
54
|
-
},
|
|
55
8
|
/**
|
|
56
9
|
* These top-level service types should map to a protobuf definition
|
|
57
10
|
* in @tdxvolt/volt-proto.
|
|
@@ -64,10 +17,5 @@ export const constants = {
|
|
|
64
17
|
relayAPI: "tdx.volt_api.relay.v1.RelayAPI",
|
|
65
18
|
voltAPI: "tdx.volt_api.volt.v1.VoltAPI",
|
|
66
19
|
wireAPI: "tdx.volt_api.volt.v1.WireAPI"
|
|
67
|
-
}
|
|
68
|
-
tunnelInvokeMethod: "/tdx.volt_api.volt.v1.VoltAPI/Invoke",
|
|
69
|
-
tunnelPingInterval: 10000,
|
|
70
|
-
tunnelPingTimeout: 2500,
|
|
71
|
-
tunnelReconnectAfter: 30000,
|
|
72
|
-
tunnelTimeoutInterval: 10000 * 2 // This must always be greater than `tunnelPingInterval`.
|
|
20
|
+
}
|
|
73
21
|
};
|
package/src/grpc-call.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import EventEmitter from "events";
|
|
4
|
-
import {
|
|
4
|
+
import { voltUtils as voltClientUtils } from "@tdxvolt/volt-client-web/js";
|
|
5
|
+
|
|
6
|
+
const {
|
|
5
7
|
aesCreateKey,
|
|
6
8
|
aesEncrypt,
|
|
7
9
|
aesDecrypt,
|
|
@@ -11,7 +13,9 @@ import {
|
|
|
11
13
|
verify,
|
|
12
14
|
deriveSharedKey,
|
|
13
15
|
x25519
|
|
14
|
-
}
|
|
16
|
+
} = voltClientUtils;
|
|
17
|
+
|
|
18
|
+
const invokeMethod = "Invoke";
|
|
15
19
|
|
|
16
20
|
const log = debug("volt-client-grpc:grpc-call");
|
|
17
21
|
|
|
@@ -24,10 +28,10 @@ function _prepareInvokeRequest(
|
|
|
24
28
|
this._service?.service_description.host_type ===
|
|
25
29
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
26
30
|
|
|
27
|
-
const relaying = this._voltClient.
|
|
31
|
+
const relaying = this._voltClient.isRelayed || isServiceRelayed;
|
|
28
32
|
|
|
29
33
|
let targetDID = [];
|
|
30
|
-
if (this._voltClient.
|
|
34
|
+
if (this._voltClient.isRelayed) {
|
|
31
35
|
targetDID.push(this._voltClient.voltConfig.id);
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -52,20 +56,23 @@ function _prepareInvokeRequest(
|
|
|
52
56
|
let invokeRequest;
|
|
53
57
|
let callMethod = method;
|
|
54
58
|
|
|
59
|
+
const voltGrpcClient = this._voltClient.getVoltAPIClient();
|
|
60
|
+
|
|
55
61
|
if (relaying) {
|
|
56
62
|
//
|
|
57
63
|
// If the volt connection is via a relay (or the target service is relayed),
|
|
58
64
|
// we wrap the rpc in a RemoteRequest message and send it via a call to Invoke().
|
|
59
65
|
//
|
|
66
|
+
|
|
60
67
|
// Replace the target method with a call to Invoke on the relay Volt.
|
|
61
|
-
|
|
62
|
-
callMethod = "Invoke";
|
|
68
|
+
callMethod = invokeMethod;
|
|
63
69
|
|
|
64
70
|
// But first we need to perform a key exchange with the target service.
|
|
65
|
-
//
|
|
66
71
|
if (!this._encryptionKey) {
|
|
67
72
|
// We haven't exchanged keys yet, so queue the payload.
|
|
68
|
-
|
|
73
|
+
if (request) {
|
|
74
|
+
this._pendingRequests.push(request);
|
|
75
|
+
}
|
|
69
76
|
|
|
70
77
|
if (this._keyExchangePending) {
|
|
71
78
|
// We've already sent the key exchange request, so do nothing.
|
|
@@ -84,6 +91,13 @@ function _prepareInvokeRequest(
|
|
|
84
91
|
: undefined
|
|
85
92
|
};
|
|
86
93
|
|
|
94
|
+
if (this._voltClient.isRelayed) {
|
|
95
|
+
// When tunnelling, we serialise the request using the original serialisation
|
|
96
|
+
// method for Invoke.
|
|
97
|
+
invokeRequest =
|
|
98
|
+
voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
return {
|
|
88
102
|
request: invokeRequest,
|
|
89
103
|
method: callMethod,
|
|
@@ -94,41 +108,45 @@ function _prepareInvokeRequest(
|
|
|
94
108
|
|
|
95
109
|
let requestPayload;
|
|
96
110
|
|
|
97
|
-
// Serialise the rpc request.
|
|
111
|
+
// Serialise the rpc request using the original serialisation method.
|
|
98
112
|
if (request) {
|
|
99
|
-
|
|
113
|
+
if (this._voltClient.isRelayed) {
|
|
114
|
+
requestPayload =
|
|
115
|
+
this._grpcClient[method].requestSerializeOriginal(request);
|
|
116
|
+
} else {
|
|
117
|
+
requestPayload = this._grpcClient[method].requestSerialize(request);
|
|
118
|
+
}
|
|
100
119
|
} else if (request !== null) {
|
|
101
120
|
log("****************LOOKOUT****************** - empty request");
|
|
102
121
|
} else {
|
|
103
122
|
log("explicit empty request payload");
|
|
104
123
|
}
|
|
105
124
|
|
|
106
|
-
//
|
|
107
|
-
const
|
|
108
|
-
const tunnelMethod = voltGrpcClient.Tunnel;
|
|
109
|
-
|
|
110
|
-
const tunnelResp = {
|
|
125
|
+
// Create a tunnel message containing the method call metadata and request payload.
|
|
126
|
+
const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
|
|
111
127
|
method_invoke: {
|
|
112
128
|
method_name: this._grpcClient[method].path,
|
|
113
129
|
method_type: methodType,
|
|
114
130
|
request: requestPayload
|
|
115
131
|
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
let invokePayload;
|
|
119
|
-
const iv = aesCreateKey().iv;
|
|
132
|
+
});
|
|
120
133
|
|
|
121
134
|
// When sending via a relay we need to encrypt the payload.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
iv,
|
|
125
|
-
tunnelMethod.responseSerialize(tunnelResp)
|
|
126
|
-
);
|
|
135
|
+
const iv = aesCreateKey().iv;
|
|
136
|
+
const invokePayload = aesEncrypt(this._encryptionKey, iv, tunnelResp);
|
|
127
137
|
|
|
138
|
+
// Construct the Invoke request.
|
|
128
139
|
invokeRequest = {
|
|
129
140
|
iv,
|
|
130
141
|
payload: invokePayload
|
|
131
142
|
};
|
|
143
|
+
|
|
144
|
+
if (this._voltClient.isRelayed) {
|
|
145
|
+
// When tunnelling, we serialise the request using the original serialisation
|
|
146
|
+
// method for Invoke.
|
|
147
|
+
invokeRequest =
|
|
148
|
+
voltGrpcClient.Invoke.requestSerializeOriginal(invokeRequest);
|
|
149
|
+
}
|
|
132
150
|
} else {
|
|
133
151
|
invokeRequest = request;
|
|
134
152
|
}
|
|
@@ -148,32 +166,45 @@ function _preparePayloadRequest(request, invokeInfo) {
|
|
|
148
166
|
this._service?.service_description.host_type ===
|
|
149
167
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
if (this._voltClient.isRelayed || isServiceRelayed) {
|
|
170
|
+
let requestPayload;
|
|
171
|
+
if (this._voltClient.isRelayed) {
|
|
172
|
+
// Serialise the rpc request using the original serialisation method.
|
|
173
|
+
requestPayload =
|
|
174
|
+
this._grpcClient[invokeInfo.methodName].requestSerializeOriginal(
|
|
175
|
+
request
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
requestPayload =
|
|
179
|
+
this._grpcClient[invokeInfo.methodName].requestSerialize(request);
|
|
180
|
+
}
|
|
157
181
|
|
|
182
|
+
// Use the Tunnel method to serialise the relay request wrapper, this
|
|
158
183
|
const voltGrpcClient = this._voltClient.getVoltAPIClient();
|
|
159
|
-
const tunnelMethod = voltGrpcClient.Tunnel;
|
|
160
184
|
|
|
161
|
-
|
|
185
|
+
// Create a tunnel message containing the method call metadata and request payload.
|
|
186
|
+
const tunnelResp = voltGrpcClient.Tunnel.responseSerialize({
|
|
162
187
|
method_payload: {
|
|
163
188
|
payload: requestPayload
|
|
164
189
|
}
|
|
165
|
-
};
|
|
190
|
+
});
|
|
166
191
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
tunnelMethod.responseSerialize(tunnelResp)
|
|
171
|
-
);
|
|
192
|
+
// Encrypt the payload using the shared key.
|
|
193
|
+
const iv = aesCreateKey().iv;
|
|
194
|
+
const invokePayload = aesEncrypt(this._encryptionKey, iv, tunnelResp);
|
|
172
195
|
|
|
196
|
+
// We then serialise the entire request using the original serialisation method for Invoke.
|
|
173
197
|
payloadRequest = {
|
|
174
198
|
iv,
|
|
175
199
|
payload: invokePayload
|
|
176
200
|
};
|
|
201
|
+
|
|
202
|
+
if (this._voltClient.isRelayed) {
|
|
203
|
+
// When tunnelling, we serialise the request using the original serialisation
|
|
204
|
+
// method for Invoke.
|
|
205
|
+
payloadRequest =
|
|
206
|
+
voltGrpcClient.Invoke.requestSerializeOriginal(payloadRequest);
|
|
207
|
+
}
|
|
177
208
|
} else {
|
|
178
209
|
payloadRequest = request;
|
|
179
210
|
}
|
|
@@ -189,7 +220,14 @@ function _parseResponse(method, meta, response) {
|
|
|
189
220
|
this._service?.service_description.host_type ===
|
|
190
221
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
191
222
|
|
|
192
|
-
|
|
223
|
+
const voltGrpcClient = this._voltClient.getVoltAPIClient();
|
|
224
|
+
|
|
225
|
+
if (this._voltClient.isRelayed || isServiceRelayed) {
|
|
226
|
+
if (this._voltClient.isRelayed) {
|
|
227
|
+
// Deserialise the incoming response using the original deserialisation method for Invoke.
|
|
228
|
+
response = voltGrpcClient.Invoke.responseDeserializeOriginal(response);
|
|
229
|
+
}
|
|
230
|
+
|
|
193
231
|
if (response.key_exchange) {
|
|
194
232
|
const encryptionKey = response.key_exchange.encryption_key;
|
|
195
233
|
const nonce = response.key_exchange.nonce;
|
|
@@ -227,13 +265,24 @@ function _parseResponse(method, meta, response) {
|
|
|
227
265
|
decryptedPayload = response.payload;
|
|
228
266
|
}
|
|
229
267
|
|
|
230
|
-
|
|
231
|
-
const responsePayload =
|
|
268
|
+
// Deserialise the decrypted payload using the Tunnel deserialisation method.
|
|
269
|
+
const responsePayload =
|
|
270
|
+
voltGrpcClient.Tunnel.requestDeserialize(decryptedPayload);
|
|
232
271
|
if (responsePayload.payload === "method_payload") {
|
|
233
272
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
273
|
+
// Deserialise the method payload.
|
|
274
|
+
if (this._voltClient.isRelayed) {
|
|
275
|
+
// Use the original deserialisation method for the target method.
|
|
276
|
+
invokeResponse.payload = this._grpcClient[
|
|
277
|
+
method
|
|
278
|
+
].responseDeserializeOriginal(
|
|
279
|
+
responsePayload.method_payload.payload
|
|
280
|
+
);
|
|
281
|
+
} else {
|
|
282
|
+
invokeResponse.payload = this._grpcClient[
|
|
283
|
+
method
|
|
284
|
+
].responseDeserialize(responsePayload.method_payload.payload);
|
|
285
|
+
}
|
|
237
286
|
} catch (err) {
|
|
238
287
|
log("failure deserialising payload for %s", this._methodName);
|
|
239
288
|
throw new Error(
|
|
@@ -261,16 +310,6 @@ function _parseResponse(method, meta, response) {
|
|
|
261
310
|
invokeResponse.methodId = response.invoke_id;
|
|
262
311
|
log("started method %d for %s", invokeResponse.methodId, method);
|
|
263
312
|
}
|
|
264
|
-
} else if (this._voltClient.isRemote && this._encryptionKey) {
|
|
265
|
-
const decryptedPayload = aesDecrypt(
|
|
266
|
-
this._encryptionKey,
|
|
267
|
-
meta.sharedKey.iv,
|
|
268
|
-
response
|
|
269
|
-
);
|
|
270
|
-
invokeResponse = {
|
|
271
|
-
payload:
|
|
272
|
-
this._grpcClient[method].responseDeserializeOriginal(decryptedPayload)
|
|
273
|
-
};
|
|
274
313
|
} else {
|
|
275
314
|
invokeResponse = { payload: response };
|
|
276
315
|
}
|
|
@@ -310,7 +349,7 @@ export default class GRPCCall extends EventEmitter {
|
|
|
310
349
|
"SERVICE_HOST_TYPE_RELAYED";
|
|
311
350
|
|
|
312
351
|
let callClient;
|
|
313
|
-
if (this._voltClient.
|
|
352
|
+
if (this._voltClient.isRelayed || isServiceRelayed) {
|
|
314
353
|
callClient = this._voltClient.getVoltAPIClient();
|
|
315
354
|
} else {
|
|
316
355
|
callClient = this._grpcClient;
|
|
@@ -377,7 +416,11 @@ export default class GRPCCall extends EventEmitter {
|
|
|
377
416
|
});
|
|
378
417
|
|
|
379
418
|
this._call.on("error", (err) => {
|
|
380
|
-
log(
|
|
419
|
+
log(
|
|
420
|
+
"ERROR - intercepted stream error on %s: %s",
|
|
421
|
+
this._methodName,
|
|
422
|
+
err.message
|
|
423
|
+
);
|
|
381
424
|
this.emit("error", err);
|
|
382
425
|
});
|
|
383
426
|
|
|
@@ -413,6 +456,16 @@ export default class GRPCCall extends EventEmitter {
|
|
|
413
456
|
}
|
|
414
457
|
}
|
|
415
458
|
|
|
459
|
+
pause() {
|
|
460
|
+
log("pausing call %s", this._methodName);
|
|
461
|
+
return this._call?.pause();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
resume() {
|
|
465
|
+
log("resuming call %s", this._methodName);
|
|
466
|
+
return this._call?.resume();
|
|
467
|
+
}
|
|
468
|
+
|
|
416
469
|
get writable() {
|
|
417
470
|
return this._call?.writable;
|
|
418
471
|
}
|
package/src/grpc-utils.js
CHANGED
|
@@ -20,7 +20,7 @@ export function createClient(
|
|
|
20
20
|
) {
|
|
21
21
|
let grpcCredentials;
|
|
22
22
|
if (credentials) {
|
|
23
|
-
const relaying = !!credentials.config?.volt?.relay;
|
|
23
|
+
const relaying = !!credentials.config?.volt?.relay && !Array.isArray(credentials.config?.volt?.relay);
|
|
24
24
|
const ca = relaying
|
|
25
25
|
? credentials.config?.volt?.relay?.ca_pem
|
|
26
26
|
: credentials.cache.ca;
|
|
@@ -28,8 +28,8 @@ export function createClient(
|
|
|
28
28
|
const { key } = credentials.cache;
|
|
29
29
|
|
|
30
30
|
if (key && cert) {
|
|
31
|
-
const
|
|
32
|
-
const tlsOptions = createSecureContextOptions(
|
|
31
|
+
const cryptoOptions = { ca, cert, key };
|
|
32
|
+
const tlsOptions = createSecureContextOptions(cryptoOptions);
|
|
33
33
|
grpcCredentials = grpc.credentials.createSsl(
|
|
34
34
|
tlsOptions.ca,
|
|
35
35
|
tlsOptions.key,
|
package/src/rpc-invocation.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import jwt from "jsonwebtoken";
|
|
3
3
|
import EventEmitter from "events";
|
|
4
|
-
import {
|
|
4
|
+
import { voltUtils as voltClientUtils } from "@tdxvolt/volt-client-web/js";
|
|
5
|
+
|
|
6
|
+
const {
|
|
5
7
|
aesDecrypt,
|
|
6
8
|
aesEncrypt,
|
|
7
9
|
createEncryptionKey,
|
|
@@ -13,23 +15,26 @@ import {
|
|
|
13
15
|
publicKeyToPem,
|
|
14
16
|
randomBytes,
|
|
15
17
|
signBase64,
|
|
16
|
-
|
|
18
|
+
stripPemHeaders,
|
|
17
19
|
x25519
|
|
18
|
-
}
|
|
20
|
+
} = voltClientUtils;
|
|
19
21
|
|
|
20
|
-
const log = debug("rpc-invocation");
|
|
22
|
+
const log = debug("volt-client-grpc:rpc-invocation");
|
|
21
23
|
|
|
22
24
|
class RpcInvocation extends EventEmitter {
|
|
23
25
|
#voltClient = null;
|
|
24
26
|
#voltConnection = null;
|
|
25
27
|
#token = null;
|
|
28
|
+
#callerPublicKeyPem = null;
|
|
26
29
|
#invokeId = null;
|
|
30
|
+
#serviceId = null;
|
|
27
31
|
#payload = null;
|
|
28
32
|
#isJSON = false;
|
|
29
33
|
#methodDescriptor = null;
|
|
30
34
|
#methodName = null;
|
|
31
35
|
#derivedKey = null;
|
|
32
36
|
#started = false;
|
|
37
|
+
#endEmitted = false;
|
|
33
38
|
|
|
34
39
|
constructor(voltClient, voltConnection) {
|
|
35
40
|
super();
|
|
@@ -41,6 +46,18 @@ class RpcInvocation extends EventEmitter {
|
|
|
41
46
|
return this.#invokeId;
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
get serviceId() {
|
|
50
|
+
return this.#serviceId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get token() {
|
|
54
|
+
return this.#token;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get callerPublicKeyPem() {
|
|
58
|
+
return this.#callerPublicKeyPem;
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
get payload() {
|
|
45
62
|
if (!this.#payload) {
|
|
46
63
|
return null;
|
|
@@ -82,14 +99,23 @@ class RpcInvocation extends EventEmitter {
|
|
|
82
99
|
// The rpc implementation should do the verification using the Volt API (e.g. CanAccessResource).
|
|
83
100
|
this.#token = jwt.decode(token);
|
|
84
101
|
|
|
102
|
+
if (!this.#token) {
|
|
103
|
+
log("failed to decode token %s", token || "null");
|
|
104
|
+
return Promise.reject(new Error("failed to decode token"));
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
// Store the JWT key id (kid) for use in the response.
|
|
86
|
-
|
|
87
|
-
const callerKey = keyFromPem(
|
|
108
|
+
this.#callerPublicKeyPem = formatPEM(this.#token.k, "PUBLIC KEY");
|
|
109
|
+
const callerKey = keyFromPem(
|
|
110
|
+
this.#callerPublicKeyPem,
|
|
111
|
+
"",
|
|
112
|
+
x25519
|
|
113
|
+
).publicKey;
|
|
88
114
|
|
|
89
115
|
const ephemeralKeyPem = createEncryptionKey();
|
|
90
116
|
const ephemeralKey = keyFromPem(ephemeralKeyPem, "", x25519);
|
|
91
117
|
const ephemeralPublicKey = fromBase64(
|
|
92
|
-
|
|
118
|
+
stripPemHeaders(publicKeyToPem(ephemeralKey.publicKey, x25519))
|
|
93
119
|
);
|
|
94
120
|
|
|
95
121
|
this.#derivedKey = deriveSharedKey(
|
|
@@ -106,7 +132,7 @@ class RpcInvocation extends EventEmitter {
|
|
|
106
132
|
message.set(nonce, ephemeralPublicKey.length);
|
|
107
133
|
|
|
108
134
|
const signature = signBase64(
|
|
109
|
-
this.#voltClient.credential.
|
|
135
|
+
this.#voltClient.credential.cache.key,
|
|
110
136
|
message
|
|
111
137
|
);
|
|
112
138
|
|
|
@@ -127,7 +153,10 @@ class RpcInvocation extends EventEmitter {
|
|
|
127
153
|
}
|
|
128
154
|
|
|
129
155
|
initialise(invoke_request) {
|
|
130
|
-
this.#invokeId
|
|
156
|
+
if (!this.#invokeId) {
|
|
157
|
+
this.#invokeId = invoke_request.invoke_id;
|
|
158
|
+
this.#serviceId = invoke_request.target_service_id;
|
|
159
|
+
}
|
|
131
160
|
|
|
132
161
|
if (!this.#derivedKey) {
|
|
133
162
|
// We don't have the derived key yet, so we need to perform the key exchange.
|
|
@@ -149,8 +178,7 @@ class RpcInvocation extends EventEmitter {
|
|
|
149
178
|
|
|
150
179
|
if (invoke_request.payload) {
|
|
151
180
|
let decryptedPayload;
|
|
152
|
-
|
|
153
|
-
if (encryptIV) {
|
|
181
|
+
if (encryptIV?.length) {
|
|
154
182
|
// Decrypt the actual payload using the shared key and iv.
|
|
155
183
|
decryptedPayload = aesDecrypt(
|
|
156
184
|
this.#derivedKey,
|
|
@@ -218,8 +246,13 @@ class RpcInvocation extends EventEmitter {
|
|
|
218
246
|
);
|
|
219
247
|
this.emit("payload", this);
|
|
220
248
|
} else if (wrappedPayload.method_end) {
|
|
221
|
-
// We don't need to do anything here, since we notify the client when we receive the client_end.
|
|
222
249
|
log("method_end received");
|
|
250
|
+
if (!this.#endEmitted) {
|
|
251
|
+
this.#endEmitted = true;
|
|
252
|
+
this.emit("end", this);
|
|
253
|
+
} else {
|
|
254
|
+
log("method_end: end already emitted");
|
|
255
|
+
}
|
|
223
256
|
}
|
|
224
257
|
} catch (err) {
|
|
225
258
|
log("JSON.parse failure parsing json_payload: %s", err.message);
|
|
@@ -228,7 +261,12 @@ class RpcInvocation extends EventEmitter {
|
|
|
228
261
|
|
|
229
262
|
resolve();
|
|
230
263
|
} else if (invoke_request.client_end) {
|
|
231
|
-
|
|
264
|
+
if (!this.#endEmitted) {
|
|
265
|
+
this.#endEmitted = true;
|
|
266
|
+
this.emit("end", this);
|
|
267
|
+
} else {
|
|
268
|
+
log("client_end: end already emitted");
|
|
269
|
+
}
|
|
232
270
|
} else {
|
|
233
271
|
// Do we need to support json_payload here?
|
|
234
272
|
reject(new Error("No payload in invoke request"));
|