@tdxvolt/volt-client-grpc 0.15.1 → 0.16.0-beta.10
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 +195 -102
- 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 +101 -40
- package/src/volt-client.js +9 -5
- package/src/volt-connection.js +3 -4
- package/src/volt-credential.js +14 -5
- package/protobuf/tdx/volt_api/ssi/v1/ssi_api.proto +0 -37
|
@@ -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
|
|
35
|
+
function issueAuthenticate(authenticateRequest, ttl) {
|
|
35
36
|
// eslint-disable-next-line no-use-before-define
|
|
36
37
|
return unaryCallInternal
|
|
37
|
-
.call(this, "
|
|
38
|
-
.then((
|
|
39
|
-
log("got
|
|
40
|
-
if (
|
|
41
|
-
log("error in bind response: %s",
|
|
42
|
-
return Promise.reject(new Error(
|
|
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 (
|
|
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 =
|
|
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 =
|
|
55
|
+
this._credential.cache.ca = authenticateResponse.chain;
|
|
55
56
|
|
|
56
57
|
// Identity resource id is assigned by the volt.
|
|
57
|
-
this._credential.cache.
|
|
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(
|
|
73
|
+
log(
|
|
74
|
+
"ignoring unknown bind descision %s",
|
|
75
|
+
authenticateResponse.status
|
|
76
|
+
);
|
|
72
77
|
break;
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
76
|
-
return
|
|
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
|
|
94
|
+
export async function authenticateInternal(useDID = 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",
|
|
@@ -122,7 +128,9 @@ export async function bindInternal() {
|
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
const keyPair = this._credential.getKey();
|
|
125
|
-
const publicKeyPem =
|
|
131
|
+
const publicKeyPem = voltUtils.removeCarriageReturn(
|
|
132
|
+
pki.publicKeyToPem(keyPair.publicKey)
|
|
133
|
+
);
|
|
126
134
|
|
|
127
135
|
if (!this._credential.cache.bindIp) {
|
|
128
136
|
// Default the IP address if none is specified. This is used to set the SAN in the
|
|
@@ -133,21 +141,13 @@ export async function bindInternal() {
|
|
|
133
141
|
this._credential.saveCache();
|
|
134
142
|
|
|
135
143
|
// Form the request message.
|
|
136
|
-
const
|
|
144
|
+
const authenticateRequest = {
|
|
137
145
|
binding_name: this._config.client_name,
|
|
138
|
-
|
|
139
|
-
host: this._credential.cache.bindIp,
|
|
140
|
-
x509_credential: []
|
|
146
|
+
host: this._credential.cache.bindIp
|
|
141
147
|
};
|
|
142
148
|
|
|
143
|
-
if (this._credential.cache.cert) {
|
|
144
|
-
bindRequest.x509_credential = [
|
|
145
|
-
this._credential.cache.cert + this._credential.cache.ca
|
|
146
|
-
];
|
|
147
|
-
}
|
|
148
|
-
|
|
149
149
|
if (this._voltConfig.challenge_code) {
|
|
150
|
-
|
|
150
|
+
authenticateRequest.challenge = voltUtils.signBase64(
|
|
151
151
|
keyPair.privateKey,
|
|
152
152
|
this._voltConfig.challenge_code
|
|
153
153
|
);
|
|
@@ -157,15 +157,71 @@ export async function bindInternal() {
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
if (!useDID) {
|
|
161
|
+
authenticateRequest.public_key = publicKeyPem;
|
|
162
|
+
} else if (!this._credential.cache.identity_id) {
|
|
163
|
+
// If we don't have an identity id, then we need to create a DID.
|
|
164
|
+
this._credential.cache.identity_id = `did:volt:${generateId()}`;
|
|
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);
|
|
220
|
+
} else {
|
|
221
|
+
authenticateRequest.did = this._credential.cache.identity_id;
|
|
165
222
|
}
|
|
166
223
|
|
|
167
|
-
log("
|
|
168
|
-
log("bind TTL is ", this._config.bindRequestTTL);
|
|
224
|
+
log("authenticate TTL is ", this._config.bindRequestTTL);
|
|
169
225
|
|
|
170
226
|
// This will block while the request is pending.
|
|
171
227
|
const pollInterval = 10000;
|
|
@@ -177,12 +233,12 @@ export async function bindInternal() {
|
|
|
177
233
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
178
234
|
}
|
|
179
235
|
// eslint-disable-next-line no-await-in-loop
|
|
180
|
-
decision = await
|
|
236
|
+
decision = await issueAuthenticate.call(
|
|
181
237
|
this,
|
|
182
|
-
|
|
238
|
+
authenticateRequest,
|
|
183
239
|
this._voltConfig.bindRequestTTL
|
|
184
240
|
);
|
|
185
|
-
log("
|
|
241
|
+
log("authenticate decision: %s", decision);
|
|
186
242
|
} while (
|
|
187
243
|
decision === "POLICY_DECISION_PROMPT" ||
|
|
188
244
|
decision === "POLICY_DECISION_PENDING"
|
|
@@ -235,6 +291,15 @@ export function connectInternal(helloPayload) {
|
|
|
235
291
|
onDisconnected();
|
|
236
292
|
this.emit("connected", false);
|
|
237
293
|
}
|
|
294
|
+
|
|
295
|
+
if (err.message.endsWith("session invalid")) {
|
|
296
|
+
log("Volt session invalid - clearing session");
|
|
297
|
+
delete this._credential.cache.session_id;
|
|
298
|
+
delete this._credential.cache.cert;
|
|
299
|
+
this._credential.saveCache();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.emit("connectionError", err);
|
|
238
303
|
});
|
|
239
304
|
|
|
240
305
|
this._voltConnection.on("evt", (evt) => {
|
|
@@ -304,9 +369,6 @@ export function getVoltAPIClientInternal() {
|
|
|
304
369
|
|
|
305
370
|
export function getAPIClientInternal(service) {
|
|
306
371
|
if (!this._cachedService[service.id]) {
|
|
307
|
-
const isRelayedService =
|
|
308
|
-
service.service_description.host_type === "SERVICE_HOST_TYPE_RELAYED";
|
|
309
|
-
|
|
310
372
|
const serviceAddress = this.isRemote
|
|
311
373
|
? this._voltConfig.relay.address
|
|
312
374
|
: service.service_description.host_address;
|
|
@@ -354,9 +416,8 @@ export function getServiceClient(service) {
|
|
|
354
416
|
}
|
|
355
417
|
|
|
356
418
|
export function unaryCallInternal(method, request, service) {
|
|
357
|
-
const grpcClient = getServiceClient.call(this, service);
|
|
358
|
-
|
|
359
419
|
return new Promise((resolve, reject) => {
|
|
420
|
+
const grpcClient = getServiceClient.call(this, service);
|
|
360
421
|
const call = new GRPCCall(this, method, "METHOD_TYPE_UNARY", service);
|
|
361
422
|
|
|
362
423
|
let response = null;
|
package/src/volt-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
169
|
+
// No certificate present in the cache yet => we need to authenticate.
|
|
170
170
|
try {
|
|
171
|
-
isBound = await
|
|
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
|
|
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
|
|
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
|
}
|
package/src/volt-connection.js
CHANGED
|
@@ -19,10 +19,9 @@ function _cleanUp(immediateReconnect) {
|
|
|
19
19
|
this._pingTimeoutTimer = 0;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) {
|
|
22
|
+
if (this._dying) {
|
|
23
|
+
clearTimeout(this._reconnectTimer);
|
|
24
|
+
} else if (immediateReconnect || (this._autoRetry && !this._reconnectTimer)) {
|
|
26
25
|
this._reconnectTimer = setTimeout(
|
|
27
26
|
() => {
|
|
28
27
|
this._reconnectTimer = 0;
|
package/src/volt-credential.js
CHANGED
|
@@ -69,8 +69,16 @@ export class VoltCredential {
|
|
|
69
69
|
return this._cryptoCache;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
get
|
|
73
|
-
return this._cryptoCache.
|
|
72
|
+
get certificate() {
|
|
73
|
+
return this._cryptoCache.cert;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get sessionId() {
|
|
77
|
+
return this._cryptoCache.session_id;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get identityId() {
|
|
81
|
+
return this._cryptoCache.identity_id;
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
get voltPublicKey() {
|
|
@@ -159,7 +167,7 @@ export class VoltCredential {
|
|
|
159
167
|
aud: audience,
|
|
160
168
|
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
161
169
|
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
162
|
-
sub: this._cryptoCache.
|
|
170
|
+
sub: this._cryptoCache.session_id
|
|
163
171
|
};
|
|
164
172
|
|
|
165
173
|
let sharedKey;
|
|
@@ -241,12 +249,13 @@ export class VoltCredential {
|
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
static rsaEncrypt(key, buffer) {
|
|
244
|
-
|
|
252
|
+
const cipher = crypto.publicEncrypt(key, buffer);
|
|
253
|
+
return cipher.toString("base64url");
|
|
245
254
|
}
|
|
246
255
|
|
|
247
256
|
static rsaDecrypt(key, buffer) {
|
|
248
257
|
const keyObj = crypto.createPrivateKey(key);
|
|
249
|
-
const cipher = Buffer.from(buffer, "
|
|
258
|
+
const cipher = Buffer.from(buffer, "base64url");
|
|
250
259
|
return Buffer.from(crypto.privateDecrypt(keyObj, cipher));
|
|
251
260
|
}
|
|
252
261
|
}
|
|
@@ -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
|
-
|