@tdxvolt/volt-client-grpc 0.16.0-beta.10 → 0.16.0-beta.12
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 +1 -1
- package/lib/index.cjs +412 -456
- package/package.json +2 -4
- package/protobuf/tdx/volt_api/data/v1/sqlite_database_api.proto +49 -13
- package/protobuf/tdx/volt_api/data/v1/sqlite_server_api.proto +3 -0
- package/protobuf/tdx/volt_api/volt/v1/discovery_api.proto +0 -4
- package/protobuf/tdx/volt_api/volt/v1/ssi.proto +31 -5
- package/protobuf/tdx/volt_api/volt/v1/ssi_api.proto +119 -31
- package/protobuf/tdx/volt_api/volt/v1/terminal_api.proto +42 -0
- package/protobuf/tdx/volt_api/volt/v1/volt.proto +141 -134
- package/protobuf/tdx/volt_api/volt/v1/volt_api.proto +138 -234
- package/src/constants.js +3 -5
- package/src/grpc-call.js +142 -51
- package/src/grpc-utils.js +8 -4
- package/src/rpc-invocation.js +94 -46
- package/src/utils.js +4 -119
- package/src/volt-client-internal.js +106 -73
- package/src/volt-client.js +33 -12
- package/src/volt-connection.js +22 -12
- package/src/volt-credential.js +37 -124
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import ip from "ip";
|
|
4
4
|
import bent from "bent";
|
|
5
|
-
import forge from "node-forge";
|
|
6
5
|
import lodash from "lodash";
|
|
7
6
|
import VoltConnection from "./volt-connection.js";
|
|
8
7
|
import { join } from "path";
|
|
@@ -17,8 +16,12 @@ import { constants } from "./constants.js";
|
|
|
17
16
|
import GRPCCall from "./grpc-call.js";
|
|
18
17
|
import RpcInvocation from "./rpc-invocation.js";
|
|
19
18
|
import { v4 as generateId } from "uuid";
|
|
19
|
+
import {
|
|
20
|
+
publicKeyToPem,
|
|
21
|
+
removeCarriageReturn,
|
|
22
|
+
signBase64
|
|
23
|
+
} from "@tdxvolt/volt-client-web";
|
|
20
24
|
|
|
21
|
-
const { pki } = forge;
|
|
22
25
|
const { filter, pick } = lodash;
|
|
23
26
|
|
|
24
27
|
const log = debug("volt-client-grpc:volt-client-internal");
|
|
@@ -54,9 +57,15 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
54
57
|
// This is the signing CA used by the volt.
|
|
55
58
|
this._credential.cache.ca = authenticateResponse.chain;
|
|
56
59
|
|
|
57
|
-
//
|
|
60
|
+
// The session id is assigned by the volt.
|
|
58
61
|
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
59
62
|
|
|
63
|
+
if (!this._credential.cache.identity_did) {
|
|
64
|
+
// Identity DID has been assigned by the volt.
|
|
65
|
+
this._credential.cache.identity_did =
|
|
66
|
+
authenticateResponse.identity_did;
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
this._credential.saveCache();
|
|
61
70
|
break;
|
|
62
71
|
}
|
|
@@ -67,6 +76,20 @@ function issueAuthenticate(authenticateRequest, ttl) {
|
|
|
67
76
|
case "POLICY_DECISION_PROMPT":
|
|
68
77
|
case "POLICY_DECISION_PENDING": {
|
|
69
78
|
log("access pending approval - waiting...");
|
|
79
|
+
|
|
80
|
+
// This is the signing CA used by the volt.
|
|
81
|
+
this._credential.cache.ca = authenticateResponse.chain;
|
|
82
|
+
|
|
83
|
+
// The session id is assigned by the volt.
|
|
84
|
+
this._credential.cache.session_id = authenticateResponse.session_id;
|
|
85
|
+
|
|
86
|
+
if (!this._credential.cache.identity_did) {
|
|
87
|
+
// Identity DID has been assigned by the volt.
|
|
88
|
+
this._credential.cache.identity_did =
|
|
89
|
+
authenticateResponse.identity_did;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this._credential.saveCache();
|
|
70
93
|
break;
|
|
71
94
|
}
|
|
72
95
|
default:
|
|
@@ -91,7 +114,10 @@ function findDIDDocumentService(document, serviceType) {
|
|
|
91
114
|
return matches;
|
|
92
115
|
}
|
|
93
116
|
|
|
94
|
-
export async function authenticateInternal(
|
|
117
|
+
export async function authenticateInternal(
|
|
118
|
+
sessionOnly = false,
|
|
119
|
+
ownDID = false
|
|
120
|
+
) {
|
|
95
121
|
try {
|
|
96
122
|
// Check if we need to resolve a Relay volt.
|
|
97
123
|
if (
|
|
@@ -103,7 +129,7 @@ export async function authenticateInternal(useDID = true) {
|
|
|
103
129
|
log("attempting to retrieve Relay information from %s", relayURL);
|
|
104
130
|
this._voltConfig.relay = await fetchVoltConfig.call(
|
|
105
131
|
this,
|
|
106
|
-
`${relayURL}/
|
|
132
|
+
`${relayURL}/discover`
|
|
107
133
|
);
|
|
108
134
|
|
|
109
135
|
if (this._voltConfig.relay.ca_pem) {
|
|
@@ -128,8 +154,8 @@ export async function authenticateInternal(useDID = true) {
|
|
|
128
154
|
}
|
|
129
155
|
|
|
130
156
|
const keyPair = this._credential.getKey();
|
|
131
|
-
const publicKeyPem =
|
|
132
|
-
|
|
157
|
+
const publicKeyPem = removeCarriageReturn(
|
|
158
|
+
publicKeyToPem(keyPair.publicKey)
|
|
133
159
|
);
|
|
134
160
|
|
|
135
161
|
if (!this._credential.cache.bindIp) {
|
|
@@ -142,12 +168,12 @@ export async function authenticateInternal(useDID = true) {
|
|
|
142
168
|
|
|
143
169
|
// Form the request message.
|
|
144
170
|
const authenticateRequest = {
|
|
145
|
-
|
|
171
|
+
client_name: this._config.client_name,
|
|
146
172
|
host: this._credential.cache.bindIp
|
|
147
173
|
};
|
|
148
174
|
|
|
149
175
|
if (this._voltConfig.challenge_code) {
|
|
150
|
-
authenticateRequest.challenge =
|
|
176
|
+
authenticateRequest.challenge = signBase64(
|
|
151
177
|
keyPair.privateKey,
|
|
152
178
|
this._voltConfig.challenge_code
|
|
153
179
|
);
|
|
@@ -157,68 +183,58 @@ export async function authenticateInternal(useDID = true) {
|
|
|
157
183
|
);
|
|
158
184
|
}
|
|
159
185
|
|
|
160
|
-
if (
|
|
186
|
+
if (sessionOnly) {
|
|
187
|
+
// We want to create a session-based authentication based simply on our key, rather than a
|
|
188
|
+
// fully-blown DID-based authentication.
|
|
189
|
+
log("creating session-only request");
|
|
161
190
|
authenticateRequest.public_key = publicKeyPem;
|
|
162
|
-
} else if (!this._credential.cache.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
);
|
|
191
|
+
} else if (!this._credential.cache.identity_did) {
|
|
192
|
+
if (!ownDID) {
|
|
193
|
+
// We don't want to have to manage our own DID, so we ask the Volt to do it for us.
|
|
194
|
+
// The Volt will create a new DID and return it to us in the response.
|
|
195
|
+
log("creating DID-based request");
|
|
196
|
+
authenticateRequest.did_public_key = publicKeyPem;
|
|
197
|
+
} else {
|
|
198
|
+
// We want to create and manage our own DID.
|
|
199
|
+
const did =
|
|
200
|
+
(this._credential.cache.identity_did = `did:volt:${generateId()}`);
|
|
201
|
+
|
|
202
|
+
// Order of properties in the DID document is important,
|
|
203
|
+
// they must be in alphabetical order throughout the document.
|
|
204
|
+
// This is because the DID document is signed and the signature
|
|
205
|
+
// is over the entire document.
|
|
206
|
+
const didDocument = {
|
|
207
|
+
"@context": "https://www.w3.org/ns/did/v1",
|
|
208
|
+
authentication: [`${did}#key-1`],
|
|
209
|
+
controller: did,
|
|
210
|
+
description: this._config.client_name,
|
|
211
|
+
id: did,
|
|
212
|
+
verificationMethod: [
|
|
213
|
+
{
|
|
214
|
+
id: `${did}#key-1`,
|
|
215
|
+
publicKeyPem: publicKeyPem,
|
|
216
|
+
type: "Ed25519VerificationKey2018"
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Set the DID document with proof in the authenticate request.
|
|
222
|
+
authenticateRequest.did_document = JSON.stringify(didDocument, null, 2);
|
|
223
|
+
|
|
224
|
+
// Now sign the entire DID document, including the proof and set it in the
|
|
225
|
+
// bind request. This is required by the Volt to verify that we have the private key for the DID.
|
|
226
|
+
authenticateRequest.did_document_signature = signBase64(
|
|
227
|
+
keyPair.privateKey,
|
|
228
|
+
authenticateRequest.did_document
|
|
229
|
+
);
|
|
217
230
|
|
|
218
|
-
|
|
219
|
-
|
|
231
|
+
log("did document is %s", authenticateRequest.did_document);
|
|
232
|
+
log("did signature is %s", authenticateRequest.did_document_signature);
|
|
233
|
+
}
|
|
220
234
|
} else {
|
|
221
|
-
|
|
235
|
+
// We already have a DID cached, so re-use it.
|
|
236
|
+
log("using cached DID %s", this._credential.cache.identity_did);
|
|
237
|
+
authenticateRequest.did = this._credential.cache.identity_did;
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
log("authenticate TTL is ", this._config.bindRequestTTL);
|
|
@@ -312,10 +328,24 @@ export function connectInternal(helloPayload) {
|
|
|
312
328
|
|
|
313
329
|
this._voltConnection.on("invoke_request", (invoke_request) => {
|
|
314
330
|
const invokeId = invoke_request.invoke_id;
|
|
315
|
-
|
|
316
|
-
|
|
331
|
+
|
|
332
|
+
let rpcInvocation = this._activeRPC[invokeId];
|
|
333
|
+
|
|
334
|
+
if (rpcInvocation) {
|
|
335
|
+
if (rpcInvocation.started) {
|
|
336
|
+
rpcInvocation.parsePayload(invoke_request);
|
|
337
|
+
} else {
|
|
338
|
+
rpcInvocation
|
|
339
|
+
.initialise(invoke_request)
|
|
340
|
+
.then(() => {
|
|
341
|
+
this.emit("invoke_request", rpcInvocation);
|
|
342
|
+
})
|
|
343
|
+
.catch((err) => {
|
|
344
|
+
log("invoke_request - error [%s]", err.message);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
317
347
|
} else {
|
|
318
|
-
|
|
348
|
+
rpcInvocation = new RpcInvocation(this, this._voltConnection);
|
|
319
349
|
|
|
320
350
|
rpcInvocation.on("end", () => {
|
|
321
351
|
log("removing active RPC [%s]", invokeId);
|
|
@@ -324,9 +354,11 @@ export function connectInternal(helloPayload) {
|
|
|
324
354
|
|
|
325
355
|
rpcInvocation
|
|
326
356
|
.initialise(invoke_request)
|
|
327
|
-
.then(() => {
|
|
357
|
+
.then((response) => {
|
|
328
358
|
this._activeRPC[invokeId] = rpcInvocation;
|
|
329
|
-
|
|
359
|
+
if (!response.key_exchange) {
|
|
360
|
+
this.emit("invoke_request", rpcInvocation);
|
|
361
|
+
}
|
|
330
362
|
})
|
|
331
363
|
.catch((err) => {
|
|
332
364
|
log("invoke_request - error [%s]", err.message);
|
|
@@ -472,6 +504,7 @@ export async function fetchVoltConfig(discovery_url) {
|
|
|
472
504
|
"public_key",
|
|
473
505
|
"fingerprint",
|
|
474
506
|
"address",
|
|
507
|
+
"http_address",
|
|
475
508
|
"ca_pem",
|
|
476
509
|
"challenge_code",
|
|
477
510
|
"cloud",
|
package/src/volt-client.js
CHANGED
|
@@ -59,6 +59,7 @@ export class VoltClient extends EventEmitter {
|
|
|
59
59
|
/**
|
|
60
60
|
* Initialises a binding and connection to the volt.
|
|
61
61
|
* @param {string} [config] - the location of the Volt configuration file, or a configuration object
|
|
62
|
+
* @param {object} [extras] - additional configuration properties
|
|
62
63
|
*/
|
|
63
64
|
async initialise(config, extras = {}) {
|
|
64
65
|
try {
|
|
@@ -74,10 +75,15 @@ export class VoltClient extends EventEmitter {
|
|
|
74
75
|
configPath = path.resolve(config);
|
|
75
76
|
log("Attempt to load config from file %s", configPath);
|
|
76
77
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (fs.existsSync(configPath)) {
|
|
79
|
+
const configContents = await readFile(
|
|
80
|
+
new URL(configPath, import.meta.url)
|
|
81
|
+
);
|
|
82
|
+
configJSON = JSON.parse(configContents);
|
|
83
|
+
} else {
|
|
84
|
+
// There is currently no file at the given configuration path.
|
|
85
|
+
configJSON = {};
|
|
86
|
+
}
|
|
81
87
|
} catch (err) {
|
|
82
88
|
log("failure loading config file %s [%s]", configPath, err.message);
|
|
83
89
|
throw new Error(
|
|
@@ -94,15 +100,15 @@ export class VoltClient extends EventEmitter {
|
|
|
94
100
|
throw new Error("config argument is required to be an object");
|
|
95
101
|
}
|
|
96
102
|
|
|
103
|
+
this._config = { ...extras, ...configJSON };
|
|
104
|
+
|
|
97
105
|
if (
|
|
98
|
-
!
|
|
99
|
-
typeof
|
|
106
|
+
!this._config.client_name ||
|
|
107
|
+
typeof this._config.client_name !== "string"
|
|
100
108
|
) {
|
|
101
109
|
throw new Error("client_name property required in config");
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
this._config = { ...configJSON, ...extras };
|
|
105
|
-
|
|
106
112
|
let voltDID = "";
|
|
107
113
|
let voltHttpAddress = "";
|
|
108
114
|
if (typeof this._config.volt === "string") {
|
|
@@ -111,6 +117,7 @@ export class VoltClient extends EventEmitter {
|
|
|
111
117
|
} else {
|
|
112
118
|
voltHttpAddress = this._config.volt;
|
|
113
119
|
}
|
|
120
|
+
delete this._config.volt;
|
|
114
121
|
} else if (typeof this._config.volt !== "object") {
|
|
115
122
|
throw new Error("configuration is missing the 'volt' object");
|
|
116
123
|
} else {
|
|
@@ -125,7 +132,7 @@ export class VoltClient extends EventEmitter {
|
|
|
125
132
|
} else if (voltHttpAddress && !this._config?.volt?.id) {
|
|
126
133
|
const discoConfig = await fetchVoltConfig.call(
|
|
127
134
|
this,
|
|
128
|
-
`${voltHttpAddress}/
|
|
135
|
+
`${voltHttpAddress}/discover`
|
|
129
136
|
);
|
|
130
137
|
this._config = {
|
|
131
138
|
...this._config,
|
|
@@ -134,8 +141,12 @@ export class VoltClient extends EventEmitter {
|
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
this._voltConfig = this._config.volt;
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
if (voltDID) {
|
|
145
|
+
this._voltConfig.did = voltDID;
|
|
146
|
+
}
|
|
147
|
+
if (voltHttpAddress) {
|
|
148
|
+
this._voltConfig.http_address = voltHttpAddress;
|
|
149
|
+
}
|
|
139
150
|
|
|
140
151
|
if (!this._voltConfig.id || typeof this._voltConfig.id !== "string") {
|
|
141
152
|
throw new Error("'id' property is missing in Volt configuration");
|
|
@@ -183,9 +194,12 @@ export class VoltClient extends EventEmitter {
|
|
|
183
194
|
if (!isBound) {
|
|
184
195
|
// This is usually because of request being denied.
|
|
185
196
|
throw new Error("Volt authentication failed");
|
|
197
|
+
} else {
|
|
198
|
+
// Perist the configuration.
|
|
199
|
+
this._config = this._credential.saveCache();
|
|
186
200
|
}
|
|
187
201
|
|
|
188
|
-
return Promise.resolve();
|
|
202
|
+
return Promise.resolve(this._config);
|
|
189
203
|
} catch (err) {
|
|
190
204
|
log("Failure starting Volt client [%s]", err.message);
|
|
191
205
|
throw err;
|
|
@@ -654,6 +668,13 @@ export class VoltClient extends EventEmitter {
|
|
|
654
668
|
});
|
|
655
669
|
}
|
|
656
670
|
|
|
671
|
+
SqlTransaction(request) {
|
|
672
|
+
const grpcClient = this.getVoltAPIClient();
|
|
673
|
+
|
|
674
|
+
const call = new GRPCCall(this, "Transaction", "METHOD_TYPE_BIDI");
|
|
675
|
+
return call.start(grpcClient, request);
|
|
676
|
+
}
|
|
677
|
+
|
|
657
678
|
/**
|
|
658
679
|
* WireAPI
|
|
659
680
|
*/
|
package/src/volt-connection.js
CHANGED
|
@@ -47,13 +47,18 @@ function _doConnect(connectHello) {
|
|
|
47
47
|
|
|
48
48
|
const grpcClient = this._voltClient.getVoltAPIClient();
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
// Notify the Volt of our ping interval. If the Volt doesn't receive a ping from us
|
|
51
|
+
// within this time, it will assume the connection is dead.
|
|
52
|
+
const defaultHello = {
|
|
53
|
+
ping_interval: this._pingInterval,
|
|
54
|
+
timestamp: Date.now()
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const helloPayload = { ...connectHello } || {};
|
|
58
|
+
|
|
59
|
+
helloPayload.hello = { ...defaultHello, ...connectHello?.hello };
|
|
60
|
+
|
|
61
|
+
log("hello is %j", helloPayload);
|
|
57
62
|
|
|
58
63
|
this.call.on("error", (err) => {
|
|
59
64
|
log("error on connection stream [%s]", err.message);
|
|
@@ -71,17 +76,22 @@ function _doConnect(connectHello) {
|
|
|
71
76
|
clearTimeout(this._pingTimeoutTimer);
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
// Schedule the next ping.
|
|
75
|
-
_startPingTimer.call(this);
|
|
76
|
-
|
|
77
79
|
this.emit("ping", response.ping.timestamp);
|
|
78
80
|
break;
|
|
79
81
|
}
|
|
80
82
|
case "acknowledge": {
|
|
81
83
|
if (!this._connectionId) {
|
|
82
84
|
this._connectionId = response.acknowledge.connection_id;
|
|
85
|
+
|
|
86
|
+
// Set the ping timeout interval to the value returned by the Volt. If we don't
|
|
87
|
+
// hear from the Volt in this time, we'll assume the connection is dead.
|
|
88
|
+
this._timeoutInterval = response.acknowledge.ping_interval;
|
|
89
|
+
|
|
83
90
|
log("connection id is %s", this._connectionId);
|
|
84
91
|
this.emit("connected", this._connectionId);
|
|
92
|
+
|
|
93
|
+
// Schedule the next ping.
|
|
94
|
+
_startPingTimer.call(this);
|
|
85
95
|
}
|
|
86
96
|
break;
|
|
87
97
|
}
|
|
@@ -109,8 +119,6 @@ function _doConnect(connectHello) {
|
|
|
109
119
|
});
|
|
110
120
|
|
|
111
121
|
this.call.start(grpcClient, helloPayload);
|
|
112
|
-
|
|
113
|
-
_startPingTimer.call(this);
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
function _startPingTimer() {
|
|
@@ -137,6 +145,8 @@ function _startPingTimer() {
|
|
|
137
145
|
_connectionTimeoutHandler.bind(this),
|
|
138
146
|
this._timeoutInterval
|
|
139
147
|
);
|
|
148
|
+
|
|
149
|
+
_startPingTimer.call(this);
|
|
140
150
|
}, this._pingInterval);
|
|
141
151
|
}
|
|
142
152
|
|
package/src/volt-credential.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/* eslint-disable no-underscore-dangle */
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
import forge from "node-forge";
|
|
5
|
-
import jwt from "jsonwebtoken";
|
|
6
|
-
import crypto from "crypto";
|
|
7
|
-
import * as voltUtils from "./utils.js";
|
|
8
4
|
import { constants } from "./constants.js";
|
|
5
|
+
import {
|
|
6
|
+
createKey,
|
|
7
|
+
fingerprintFromPublicKey,
|
|
8
|
+
getIdentityToken,
|
|
9
|
+
keyFromPem,
|
|
10
|
+
privateKeyToPem,
|
|
11
|
+
publicKeyToPem
|
|
12
|
+
} from "@tdxvolt/volt-client-web";
|
|
9
13
|
|
|
10
|
-
const { pki } = forge;
|
|
11
14
|
const log = debug("volt-client-grpc:volt-credential");
|
|
12
|
-
const aesAlgorithm = "aes-256-cbc";
|
|
13
|
-
const rs256Algorithm = "RS256";
|
|
14
15
|
|
|
15
16
|
export class VoltCredential {
|
|
16
17
|
constructor(config, configPath) {
|
|
@@ -34,14 +35,13 @@ export class VoltCredential {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
// Need to unencrypt the private key.
|
|
37
|
-
const decrypted =
|
|
38
|
-
|
|
39
|
-
config.p
|
|
40
|
-
);
|
|
38
|
+
const decrypted = keyFromPem(this._cryptoCache.key, config.p);
|
|
39
|
+
|
|
41
40
|
if (!decrypted) {
|
|
42
41
|
throw new Error("failed to decrypt key - check passphrase");
|
|
43
42
|
}
|
|
44
|
-
|
|
43
|
+
|
|
44
|
+
this._cryptoCache.key = privateKeyToPem(decrypted);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (this._voltConfig.ca_pem) {
|
|
@@ -50,14 +50,11 @@ export class VoltCredential {
|
|
|
50
50
|
this._cryptoCache.ca = this._voltConfig.ca_pem;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Pre-cache the target Volt public key by extracting it from the signing certificate.
|
|
54
|
-
const voltCert = forge.pki.certificateFromPem(this._voltConfig.ca_pem);
|
|
55
|
-
|
|
56
53
|
// Extract Volt public key (used for encrypting Relay payloads).
|
|
57
|
-
this._voltPublicKey =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
54
|
+
this._voltPublicKey = this._voltConfig.public_key;
|
|
55
|
+
|
|
56
|
+
const voltKey = keyFromPem(this._voltPublicKey).publicKey;
|
|
57
|
+
this._voltFingerprint = fingerprintFromPublicKey(voltKey);
|
|
61
58
|
}
|
|
62
59
|
}
|
|
63
60
|
|
|
@@ -78,7 +75,7 @@ export class VoltCredential {
|
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
get identityId() {
|
|
81
|
-
return this._cryptoCache.
|
|
78
|
+
return this._cryptoCache.identity_did;
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
get voltPublicKey() {
|
|
@@ -86,7 +83,7 @@ export class VoltCredential {
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
get publicKey() {
|
|
89
|
-
return
|
|
86
|
+
return publicKeyToPem(this.getKey().publicKey);
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
get voltFingerprint() {
|
|
@@ -94,9 +91,8 @@ export class VoltCredential {
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
getKey() {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
return { privateKey, publicKey };
|
|
94
|
+
const key = keyFromPem(this._cryptoCache.key);
|
|
95
|
+
return key;
|
|
100
96
|
}
|
|
101
97
|
|
|
102
98
|
/**
|
|
@@ -109,17 +105,10 @@ export class VoltCredential {
|
|
|
109
105
|
return Promise.resolve(this.getKey());
|
|
110
106
|
} else {
|
|
111
107
|
log("creating key");
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this._cryptoCache.key = keyInfo.pem;
|
|
117
|
-
return keyInfo.keypair;
|
|
118
|
-
})
|
|
119
|
-
.catch((err) => {
|
|
120
|
-
log("failure creating key [%s]", err.message);
|
|
121
|
-
return Promise.reject(err);
|
|
122
|
-
});
|
|
108
|
+
this._cryptoCache.key = createKey();
|
|
109
|
+
const key = keyFromPem(this._cryptoCache.key);
|
|
110
|
+
log("successfully created key");
|
|
111
|
+
return Promise.resolve(key);
|
|
123
112
|
}
|
|
124
113
|
}
|
|
125
114
|
|
|
@@ -144,55 +133,6 @@ export class VoltCredential {
|
|
|
144
133
|
return !!this._cryptoCache.cert;
|
|
145
134
|
}
|
|
146
135
|
|
|
147
|
-
/**
|
|
148
|
-
* Signs the identity resource id using the private key.
|
|
149
|
-
* @param {*} audience the token audience (usually the target volt)
|
|
150
|
-
* @param {*} tunnelling flag indicating if the token is required for a tunnelled connection
|
|
151
|
-
* @param {*} ttl time to live in seconds (default to 1 minute)
|
|
152
|
-
*/
|
|
153
|
-
getIdentityToken(audience, publicKey, tunnelling = false, ttl = 60) {
|
|
154
|
-
if (!this._cryptoCache.key) {
|
|
155
|
-
throw new Error("crypto cache invalid");
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const keyPair = this.getKey();
|
|
159
|
-
const publicKeyPem = pki.publicKeyToPem(keyPair.publicKey);
|
|
160
|
-
const base58Key = voltUtils.createPublicKeyFingerprint(publicKeyPem);
|
|
161
|
-
|
|
162
|
-
log("base58 key is %s", base58Key);
|
|
163
|
-
|
|
164
|
-
// Allow TTL either side of the current time.
|
|
165
|
-
// @todo - fix with server time sync on volt connection.
|
|
166
|
-
const payload = {
|
|
167
|
-
aud: audience,
|
|
168
|
-
iat: Math.floor(Date.now() / 1000) - ttl,
|
|
169
|
-
exp: Math.floor(Date.now() / 1000) + ttl,
|
|
170
|
-
sub: this._cryptoCache.session_id
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
let sharedKey;
|
|
174
|
-
if (tunnelling) {
|
|
175
|
-
// Initialise the Relay encryption key.
|
|
176
|
-
sharedKey = VoltCredential.aesCreateKey();
|
|
177
|
-
|
|
178
|
-
// Encrypt the key details using the target Volt public key and include this in the JWT payload.
|
|
179
|
-
payload.sk = VoltCredential.rsaEncrypt(publicKey, sharedKey.key);
|
|
180
|
-
payload.iv = VoltCredential.rsaEncrypt(publicKey, sharedKey.iv);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Sign synchronously.
|
|
184
|
-
const token = jwt.sign(payload, this._cryptoCache.key, {
|
|
185
|
-
algorithm: rs256Algorithm,
|
|
186
|
-
keyid: base58Key
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// Return the token along with any encryption key details.
|
|
190
|
-
return {
|
|
191
|
-
token,
|
|
192
|
-
sharedKey
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
136
|
/**
|
|
197
137
|
* Creates the metadata required for a grpc call, including the Volt authentication token
|
|
198
138
|
* and optionally tunnel encryption parameters.
|
|
@@ -203,9 +143,12 @@ export class VoltCredential {
|
|
|
203
143
|
* @returns
|
|
204
144
|
*/
|
|
205
145
|
getIdentityMetadata(grpc, audience, publicKey, tunnelling = false, ttl = 60) {
|
|
206
|
-
const
|
|
146
|
+
const key = keyFromPem(this._cryptoCache.key);
|
|
147
|
+
|
|
148
|
+
const identityToken = getIdentityToken(
|
|
149
|
+
this.sessionId,
|
|
150
|
+
key,
|
|
207
151
|
audience || this._voltConfig.id,
|
|
208
|
-
publicKey,
|
|
209
152
|
tunnelling,
|
|
210
153
|
ttl
|
|
211
154
|
);
|
|
@@ -215,47 +158,17 @@ export class VoltCredential {
|
|
|
215
158
|
}
|
|
216
159
|
|
|
217
160
|
saveCache() {
|
|
161
|
+
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
162
|
+
if (this._config.p) {
|
|
163
|
+
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
164
|
+
const key = keyFromPem(this._cryptoCache.key);
|
|
165
|
+
clone.credential.key = privateKeyToPem(key.privateKey, this._config.p);
|
|
166
|
+
}
|
|
167
|
+
|
|
218
168
|
if (this._persistCache) {
|
|
219
|
-
const clone = { ...this._config, credential: { ...this._cryptoCache } };
|
|
220
|
-
if (this._config.p) {
|
|
221
|
-
// A passphrase option exists => encrypt the key before writing the cache file.
|
|
222
|
-
const privateKey = pki.privateKeyFromPem(this._cryptoCache.key);
|
|
223
|
-
clone.credential.key = pki.encryptRsaPrivateKey(
|
|
224
|
-
privateKey,
|
|
225
|
-
this._config.p
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
169
|
fs.writeFileSync(this._configPath, JSON.stringify(clone, null, 2));
|
|
229
170
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
static aesCreateKey() {
|
|
233
|
-
// Generate random key for the AES-256 algorithm (32 bytes = 256 bits).
|
|
234
|
-
const key = crypto.randomBytes(32);
|
|
235
|
-
const iv = crypto.randomBytes(16);
|
|
236
|
-
return { key, iv };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
static aesEncrypt(buffer, key, iv) {
|
|
240
|
-
const cipher = crypto.createCipheriv(aesAlgorithm, key, iv);
|
|
241
|
-
const crypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
242
|
-
return crypted;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
static aesDecrypt(buffer, key, iv) {
|
|
246
|
-
const cipher = crypto.createDecipheriv(aesAlgorithm, key, iv);
|
|
247
|
-
const decrypted = Buffer.concat([cipher.update(buffer), cipher.final()]);
|
|
248
|
-
return decrypted;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
static rsaEncrypt(key, buffer) {
|
|
252
|
-
const cipher = crypto.publicEncrypt(key, buffer);
|
|
253
|
-
return cipher.toString("base64url");
|
|
254
|
-
}
|
|
255
171
|
|
|
256
|
-
|
|
257
|
-
const keyObj = crypto.createPrivateKey(key);
|
|
258
|
-
const cipher = Buffer.from(buffer, "base64url");
|
|
259
|
-
return Buffer.from(crypto.privateDecrypt(keyObj, cipher));
|
|
172
|
+
return clone;
|
|
260
173
|
}
|
|
261
174
|
}
|