@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.
- package/lib/index.cjs +176 -97
- 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 +89 -39
- package/src/volt-client.js +9 -5
- package/src/volt-credential.js +10 -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(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
|
|
142
|
+
const authenticateRequest = {
|
|
137
143
|
binding_name: this._config.client_name,
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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("
|
|
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
|
|
234
|
+
decision = await issueAuthenticate.call(
|
|
181
235
|
this,
|
|
182
|
-
|
|
236
|
+
authenticateRequest,
|
|
183
237
|
this._voltConfig.bindRequestTTL
|
|
184
238
|
);
|
|
185
|
-
log("
|
|
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;
|
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-credential.js
CHANGED
|
@@ -69,8 +69,12 @@ export class VoltCredential {
|
|
|
69
69
|
return this._cryptoCache;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
get
|
|
73
|
-
return this._cryptoCache.
|
|
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.
|
|
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
|
-
|
|
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, "
|
|
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
|
-
|