diodejs 0.1.1 → 0.1.3
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 +74 -19
- package/bindPort.js +108 -32
- package/connection.js +28 -163
- package/examples/RPCTest.js +2 -2
- package/examples/portForwardTest.js +20 -4
- package/examples/publishAndBind.js +3 -3
- package/examples/publishPortTest.js +29 -8
- package/package.json +1 -1
- package/publishPort.js +114 -33
- package/utils.js +74 -15
package/README.md
CHANGED
|
@@ -25,9 +25,9 @@ const { DiodeConnection, DiodeRPC, makeReadable } = require('diodejs');
|
|
|
25
25
|
async function main() {
|
|
26
26
|
const host = 'eu2.prenet.diode.io';
|
|
27
27
|
const port = 41046;
|
|
28
|
-
const
|
|
28
|
+
const keyLocation = './db/keys.json'; // Optional, defaults to './db/keys.json'
|
|
29
29
|
|
|
30
|
-
const connection = new DiodeConnection(host, port,
|
|
30
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
31
31
|
await connection.connect();
|
|
32
32
|
|
|
33
33
|
const rpc = new DiodeRPC(connection);
|
|
@@ -54,24 +54,55 @@ main();
|
|
|
54
54
|
### Bind Port
|
|
55
55
|
Here's a quick example to get you started with port forwarding using the `BindPort` class.
|
|
56
56
|
|
|
57
|
+
#### Port Binding
|
|
57
58
|
```javascript
|
|
58
59
|
const { DiodeConnection, BindPort } = require('diodejs');
|
|
59
60
|
|
|
60
61
|
async function main() {
|
|
61
62
|
const host = 'eu2.prenet.diode.io';
|
|
62
63
|
const port = 41046;
|
|
63
|
-
const
|
|
64
|
+
const keyLocation = './db/keys.json';
|
|
64
65
|
|
|
65
|
-
const connection = new DiodeConnection(host, port,
|
|
66
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
66
67
|
await connection.connect();
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
// Multiple or single port binding with configuration object
|
|
70
|
+
const portsConfig = {
|
|
71
|
+
3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" },
|
|
72
|
+
3003: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const portForward = new BindPort(connection, portsConfig);
|
|
69
76
|
portForward.bind();
|
|
70
77
|
|
|
78
|
+
// You can also dynamically add and remove ports
|
|
79
|
+
portForward.addPort(3004, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
80
|
+
portForward.removePort(3003);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Single Port Binding (Legacy)
|
|
87
|
+
```javascript
|
|
88
|
+
const { DiodeConnection, BindPort } = require('diodejs');
|
|
89
|
+
|
|
90
|
+
async function main() {
|
|
91
|
+
const host = 'eu2.prenet.diode.io';
|
|
92
|
+
const port = 41046;
|
|
93
|
+
const keyLocation = './db/keys.json';
|
|
94
|
+
|
|
95
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
96
|
+
await connection.connect();
|
|
97
|
+
|
|
98
|
+
// Legacy method - single port binding
|
|
99
|
+
const portForward = new BindPort(connection, 3002, 80, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
100
|
+
portForward.bind();
|
|
71
101
|
}
|
|
72
102
|
|
|
73
103
|
main();
|
|
74
104
|
```
|
|
105
|
+
|
|
75
106
|
### Publish Port
|
|
76
107
|
|
|
77
108
|
Here's a quick example to get you started with publishing ports using the `PublishPort` class:
|
|
@@ -82,9 +113,9 @@ const { DiodeConnection, PublishPort } = require('diodejs');
|
|
|
82
113
|
async function main() {
|
|
83
114
|
const host = 'us2.prenet.diode.io';
|
|
84
115
|
const port = 41046;
|
|
85
|
-
const
|
|
116
|
+
const keyLocation = './db/keys.json';
|
|
86
117
|
|
|
87
|
-
const connection = new DiodeConnection(host, port,
|
|
118
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
88
119
|
await connection.connect();
|
|
89
120
|
|
|
90
121
|
// Option 1: Simple array of ports (all public)
|
|
@@ -99,7 +130,8 @@ async function main() {
|
|
|
99
130
|
}
|
|
100
131
|
};
|
|
101
132
|
|
|
102
|
-
|
|
133
|
+
// certPath parameter is maintained for backward compatibility but not required
|
|
134
|
+
const publishPort = new PublishPort(connection, publishedPortsWithConfig);
|
|
103
135
|
}
|
|
104
136
|
|
|
105
137
|
main();
|
|
@@ -111,19 +143,20 @@ main();
|
|
|
111
143
|
|
|
112
144
|
#### `DiodeConnection`
|
|
113
145
|
|
|
114
|
-
- **Constructor**: `new DiodeConnection(host, port,
|
|
146
|
+
- **Constructor**: `new DiodeConnection(host, port, keyLocation)`
|
|
115
147
|
- `host` (string): The host address of the Diode server.
|
|
116
148
|
- `port` (number): The port number of the Diode server.
|
|
117
|
-
- `
|
|
149
|
+
- `keyLocation` (string)(default: './db/keys.json'): The path to the key storage file. If the file doesn't exist, keys are generated automatically.
|
|
118
150
|
|
|
119
151
|
- **Methods**:
|
|
120
152
|
- `connect()`: Connects to the Diode server. Returns a promise.
|
|
121
153
|
- `sendCommand(commandArray)`: Sends a command to the Diode server. Returns a promise.
|
|
122
154
|
- `sendCommandWithSessionId(commandArray, sessionId)`: Sends a command with a session ID. Returns a promise.
|
|
123
|
-
- `getEthereumAddress()`: Returns the Ethereum address derived from the device
|
|
155
|
+
- `getEthereumAddress()`: Returns the Ethereum address derived from the device keys.
|
|
124
156
|
- `getServerEthereumAddress()`: Returns the Ethereum address of the server.
|
|
125
157
|
- `createTicketCommand()`: Creates a ticket command for authentication. Returns a promise.
|
|
126
158
|
- `close()`: Closes the connection to the Diode server.
|
|
159
|
+
- `getDeviceCertificate()`: Returns the generated certificate PEM.
|
|
127
160
|
|
|
128
161
|
#### `DiodeRPC`
|
|
129
162
|
|
|
@@ -145,25 +178,47 @@ main();
|
|
|
145
178
|
|
|
146
179
|
#### `BindPort`
|
|
147
180
|
|
|
148
|
-
- **
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- `
|
|
152
|
-
|
|
181
|
+
- **Constructors**:
|
|
182
|
+
|
|
183
|
+
Legacy Constructor:
|
|
184
|
+
- `new BindPort(connection, localPort, targetPort, deviceIdHex)`
|
|
185
|
+
- `connection` (DiodeConnection): An instance of `DiodeConnection`.
|
|
186
|
+
- `localPort` (number): The local port to bind.
|
|
187
|
+
- `targetPort` (number): The target port on the device.
|
|
188
|
+
- `deviceIdHex` (string): The device ID in hexadecimal format.
|
|
189
|
+
|
|
190
|
+
New Constructor:
|
|
191
|
+
- `new BindPort(connection, portsConfig)`
|
|
192
|
+
- `connection` (DiodeConnection): An instance of `DiodeConnection`.
|
|
193
|
+
- `portsConfig` (object): A configuration object where keys are local ports and values are objects with `targetPort` and `deviceIdHex`.
|
|
194
|
+
Example: `{ 3002: { targetPort: 80, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" } }`
|
|
153
195
|
|
|
154
196
|
- **Methods**:
|
|
155
|
-
- `bind()`: Binds
|
|
197
|
+
- `bind()`: Binds all configured local ports to their target ports on the devices.
|
|
198
|
+
- `addPort(localPort, targetPort, deviceIdHex)`: Adds a new port binding configuration.
|
|
199
|
+
- `removePort(localPort)`: Removes a port binding configuration.
|
|
200
|
+
- `bindSinglePort(localPort)`: Binds a single local port to its target.
|
|
201
|
+
- `closeAllServers()`: Closes all active server instances.
|
|
156
202
|
|
|
157
203
|
#### `PublishPort`
|
|
158
204
|
|
|
159
|
-
- **Constructor**: `new PublishPort(connection, publishedPorts,
|
|
205
|
+
- **Constructor**: `new PublishPort(connection, publishedPorts, _certPath)`
|
|
160
206
|
- `connection` (DiodeConnection): An instance of `DiodeConnection`.
|
|
161
207
|
- `publishedPorts` (array|object): Either:
|
|
162
208
|
- An array of ports to publish (all public mode)
|
|
163
209
|
- An object mapping ports to their configuration: `{ port: { mode: 'public'|'private', whitelist: ['0x123...'] } }`
|
|
164
|
-
- `
|
|
210
|
+
- `_certPath` (string): Has no functionality and maintained for backward compatibility.
|
|
165
211
|
|
|
166
212
|
- **Methods**:
|
|
213
|
+
- `addPort(port, config)`: Adds a new port to publish. Config is optional and defaults to public mode.
|
|
214
|
+
- `port` (number): The port number to publish.
|
|
215
|
+
- `config` (object): Optional configuration with `mode` ('public'|'private') and `whitelist` array.
|
|
216
|
+
- `removePort(port)`: Removes a published port.
|
|
217
|
+
- `port` (number): The port number to remove.
|
|
218
|
+
- `addPorts(ports)`: Adds multiple ports at once (equivalent to the constructor's publishedPorts parameter).
|
|
219
|
+
- `ports` (array|object): Either an array of port numbers or an object mapping ports to their configurations.
|
|
220
|
+
- `getPublishedPorts()`: Returns a plain object with all published ports and their configurations.
|
|
221
|
+
- `clearPorts()`: Removes all published ports. Returns the number of ports that were cleared.
|
|
167
222
|
- `startListening()`: Starts listening for unsolicited messages.
|
|
168
223
|
- `handlePortOpen(sessionIdRaw, messageContent)`: Handles port open requests.
|
|
169
224
|
- `handlePortSend(sessionIdRaw, messageContent)`: Handles port send requests.
|
package/bindPort.js
CHANGED
|
@@ -4,18 +4,27 @@ const DiodeRPC = require('./rpc');
|
|
|
4
4
|
const logger = require('./logger');
|
|
5
5
|
|
|
6
6
|
class BindPort {
|
|
7
|
-
constructor(connection,
|
|
7
|
+
constructor(connection, localPortOrPortsConfig, targetPort, deviceIdHex) {
|
|
8
8
|
this.connection = connection;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
// Handle legacy constructor (connection, localPort, targetPort, deviceIdHex)
|
|
11
|
+
if (typeof localPortOrPortsConfig === 'number' && targetPort !== undefined && deviceIdHex !== undefined) {
|
|
12
|
+
this.portsConfig = {
|
|
13
|
+
[localPortOrPortsConfig]: { targetPort, deviceIdHex }
|
|
14
|
+
};
|
|
15
|
+
} else {
|
|
16
|
+
// New constructor (connection, portsConfig)
|
|
17
|
+
this.portsConfig = localPortOrPortsConfig || {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.servers = new Map(); // Track server instances by localPort
|
|
21
|
+
this.rpc = new DiodeRPC(this.connection);
|
|
22
|
+
|
|
23
|
+
// Set up listener for unsolicited messages once
|
|
24
|
+
this._setupMessageListener();
|
|
12
25
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const deviceId = Buffer.from(this.deviceIdHex, 'hex');
|
|
16
|
-
// Remove local clientSockets map and use the one from connection
|
|
17
|
-
const rpc = new DiodeRPC(this.connection);
|
|
18
|
-
|
|
26
|
+
|
|
27
|
+
_setupMessageListener() {
|
|
19
28
|
// Listen for data events from the device
|
|
20
29
|
this.connection.on('unsolicited', (message) => {
|
|
21
30
|
// message is [messageId, [messageType, ...]]
|
|
@@ -44,7 +53,6 @@ class BindPort {
|
|
|
44
53
|
}
|
|
45
54
|
} else if (messageType === 'portclose') {
|
|
46
55
|
const refRaw = messageContent[1];
|
|
47
|
-
|
|
48
56
|
const dataRef = Buffer.from(refRaw);
|
|
49
57
|
|
|
50
58
|
// Close the associated client socket
|
|
@@ -60,24 +68,91 @@ class BindPort {
|
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
});
|
|
71
|
+
|
|
72
|
+
// Handle device disconnect
|
|
73
|
+
this.connection.on('end', () => {
|
|
74
|
+
logger.info('Disconnected from Diode.io server');
|
|
75
|
+
this.closeAllServers();
|
|
76
|
+
});
|
|
63
77
|
|
|
78
|
+
// Handle connection errors
|
|
79
|
+
this.connection.on('error', (err) => {
|
|
80
|
+
logger.error(`Connection error: ${err}`);
|
|
81
|
+
this.closeAllServers();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
addPort(localPort, targetPort, deviceIdHex) {
|
|
86
|
+
if (this.servers.has(localPort)) {
|
|
87
|
+
logger.warn(`Port ${localPort} is already bound`);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.portsConfig[localPort] = { targetPort, deviceIdHex };
|
|
92
|
+
|
|
93
|
+
// If we're already bound, set up this new port immediately
|
|
94
|
+
if (this.servers.size > 0) {
|
|
95
|
+
this.bindSinglePort(localPort);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
removePort(localPort) {
|
|
102
|
+
if (!this.portsConfig[localPort]) {
|
|
103
|
+
logger.warn(`Port ${localPort} is not configured`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Close the server if it's running
|
|
108
|
+
if (this.servers.has(localPort)) {
|
|
109
|
+
const server = this.servers.get(localPort);
|
|
110
|
+
server.close(() => {
|
|
111
|
+
logger.info(`Server on port ${localPort} closed`);
|
|
112
|
+
});
|
|
113
|
+
this.servers.delete(localPort);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Remove from config
|
|
117
|
+
delete this.portsConfig[localPort];
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
closeAllServers() {
|
|
122
|
+
for (const [localPort, server] of this.servers.entries()) {
|
|
123
|
+
server.close();
|
|
124
|
+
logger.info(`Server on port ${localPort} closed`);
|
|
125
|
+
}
|
|
126
|
+
this.servers.clear();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
bindSinglePort(localPort) {
|
|
130
|
+
const config = this.portsConfig[localPort];
|
|
131
|
+
if (!config) {
|
|
132
|
+
logger.error(`No configuration found for port ${localPort}`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { targetPort, deviceIdHex } = config;
|
|
137
|
+
const deviceId = Buffer.from(deviceIdHex, 'hex');
|
|
138
|
+
|
|
64
139
|
// Set up local server
|
|
65
140
|
const server = net.createServer(async (clientSocket) => {
|
|
66
|
-
logger.info(
|
|
141
|
+
logger.info(`Client connected to local server on port ${localPort}`);
|
|
67
142
|
|
|
68
143
|
// Open a new port on the device for this client
|
|
69
144
|
let ref;
|
|
70
145
|
try {
|
|
71
|
-
ref = await rpc.portOpen(deviceId,
|
|
146
|
+
ref = await this.rpc.portOpen(deviceId, targetPort, 'rw');
|
|
72
147
|
if (!ref) {
|
|
73
|
-
logger.error(
|
|
148
|
+
logger.error(`Error opening port ${targetPort} on deviceId: ${deviceIdHex}`);
|
|
74
149
|
clientSocket.destroy();
|
|
75
|
-
return
|
|
150
|
+
return;
|
|
76
151
|
} else {
|
|
77
|
-
logger.info(`Port opened on device with ref: ${ref.toString('hex')} for client`);
|
|
152
|
+
logger.info(`Port ${targetPort} opened on device with ref: ${ref.toString('hex')} for client`);
|
|
78
153
|
}
|
|
79
154
|
} catch (error) {
|
|
80
|
-
logger.error(`Error opening port on device: ${error}`);
|
|
155
|
+
logger.error(`Error opening port ${targetPort} on device: ${error}`);
|
|
81
156
|
clientSocket.destroy();
|
|
82
157
|
return;
|
|
83
158
|
}
|
|
@@ -88,7 +163,7 @@ class BindPort {
|
|
|
88
163
|
// When data is received from the client, send it to the device
|
|
89
164
|
clientSocket.on('data', async (data) => {
|
|
90
165
|
try {
|
|
91
|
-
await rpc.portSend(ref, data);
|
|
166
|
+
await this.rpc.portSend(ref, data);
|
|
92
167
|
} catch (error) {
|
|
93
168
|
logger.error(`Error sending data to device: ${error}`);
|
|
94
169
|
clientSocket.destroy();
|
|
@@ -100,7 +175,7 @@ class BindPort {
|
|
|
100
175
|
logger.info('Client disconnected');
|
|
101
176
|
if (ref && this.connection.hasClientSocket(ref)) {
|
|
102
177
|
try {
|
|
103
|
-
await rpc.portClose(ref);
|
|
178
|
+
await this.rpc.portClose(ref);
|
|
104
179
|
logger.info(`Port closed on device for ref: ${ref.toString('hex')}`);
|
|
105
180
|
this.connection.deleteClientSocket(ref);
|
|
106
181
|
} catch (error) {
|
|
@@ -117,21 +192,22 @@ class BindPort {
|
|
|
117
192
|
});
|
|
118
193
|
});
|
|
119
194
|
|
|
120
|
-
server.listen(
|
|
121
|
-
logger.info(`Local server listening on port ${
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Handle device disconnect
|
|
125
|
-
this.connection.on('end', () => {
|
|
126
|
-
logger.info('Disconnected from Diode.io server');
|
|
127
|
-
server.close();
|
|
195
|
+
server.listen(localPort, () => {
|
|
196
|
+
logger.info(`Local server listening on port ${localPort} forwarding to device port ${targetPort}`);
|
|
128
197
|
});
|
|
198
|
+
|
|
199
|
+
this.servers.set(parseInt(localPort), server);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
129
202
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
203
|
+
bind() {
|
|
204
|
+
// Close any existing servers first
|
|
205
|
+
this.closeAllServers();
|
|
206
|
+
|
|
207
|
+
// Create servers for each port in the config
|
|
208
|
+
for (const localPort in this.portsConfig) {
|
|
209
|
+
this.bindSinglePort(parseInt(localPort));
|
|
210
|
+
}
|
|
135
211
|
}
|
|
136
212
|
}
|
|
137
213
|
|
package/connection.js
CHANGED
|
@@ -3,7 +3,7 @@ const tls = require('tls');
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { RLP } = require('@ethereumjs/rlp');
|
|
5
5
|
const EventEmitter = require('events');
|
|
6
|
-
const { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert } = require('./utils');
|
|
6
|
+
const { makeReadable, parseRequestId, parseResponseType, parseReason, generateCert, ensureDirectoryExistence, loadOrGenerateKeyPair } = require('./utils');
|
|
7
7
|
const { Buffer } = require('buffer'); // Import Buffer
|
|
8
8
|
const asn1 = require('asn1.js');
|
|
9
9
|
const secp256k1 = require('secp256k1');
|
|
@@ -12,12 +12,14 @@ const crypto = require('crypto');
|
|
|
12
12
|
const DiodeRPC = require('./rpc');
|
|
13
13
|
const abi = require('ethereumjs-abi');
|
|
14
14
|
const logger = require('./logger');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
15
17
|
class DiodeConnection extends EventEmitter {
|
|
16
|
-
constructor(host, port,
|
|
18
|
+
constructor(host, port, keyLocation = './db/keys.json') {
|
|
17
19
|
super();
|
|
18
20
|
this.host = host;
|
|
19
21
|
this.port = port;
|
|
20
|
-
this.
|
|
22
|
+
this.keyLocation = keyLocation;
|
|
21
23
|
this.socket = null;
|
|
22
24
|
this.requestId = 0; // Initialize request ID counter
|
|
23
25
|
this.pendingRequests = new Map(); // Map to store pending requests
|
|
@@ -32,18 +34,19 @@ class DiodeConnection extends EventEmitter {
|
|
|
32
34
|
// Add maps for storing client sockets and connections
|
|
33
35
|
this.clientSockets = new Map(); // For BindPort
|
|
34
36
|
this.connections = new Map(); // For PublishPort
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
generateCert(this.certPath);
|
|
39
|
-
}
|
|
37
|
+
this.certPem = null;
|
|
38
|
+
// Load or generate keypair
|
|
39
|
+
this.keyPair = loadOrGenerateKeyPair(this.keyLocation);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
connect() {
|
|
43
43
|
return new Promise((resolve, reject) => {
|
|
44
|
+
// Generate a temporary certificate valid for 1 month
|
|
45
|
+
this.certPem = generateCert(this.keyPair.prvKeyObj, this.keyPair.pubKeyObj);
|
|
46
|
+
|
|
44
47
|
const options = {
|
|
45
|
-
cert:
|
|
46
|
-
key:
|
|
48
|
+
cert: this.certPem,
|
|
49
|
+
key: this.certPem,
|
|
47
50
|
rejectUnauthorized: false,
|
|
48
51
|
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
49
52
|
ecdhCurve: 'secp256k1',
|
|
@@ -276,63 +279,10 @@ class DiodeConnection extends EventEmitter {
|
|
|
276
279
|
|
|
277
280
|
getEthereumAddress() {
|
|
278
281
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
|
|
285
|
-
// Handle PKCS#8 format
|
|
286
|
-
privateKeyPem = pem
|
|
287
|
-
.replace('-----BEGIN PRIVATE KEY-----', '')
|
|
288
|
-
.replace('-----END PRIVATE KEY-----', '')
|
|
289
|
-
.replace(/\r?\n|\r/g, '');
|
|
290
|
-
|
|
291
|
-
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
292
|
-
|
|
293
|
-
// Define ASN.1 structure for PKCS#8 private key
|
|
294
|
-
const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
|
|
295
|
-
this.seq().obj(
|
|
296
|
-
this.key('version').int(),
|
|
297
|
-
this.key('privateKeyAlgorithm').seq().obj(
|
|
298
|
-
this.key('algorithm').objid(),
|
|
299
|
-
this.key('parameters').optional()
|
|
300
|
-
),
|
|
301
|
-
this.key('privateKey').octstr(),
|
|
302
|
-
this.key('attributes').implicit(0).any().optional(),
|
|
303
|
-
this.key('publicKey').implicit(1).bitstr().optional()
|
|
304
|
-
);
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Decode the DER-encoded private key
|
|
308
|
-
const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
|
|
309
|
-
const privateKeyOctetString = privateKeyInfo.privateKey;
|
|
310
|
-
|
|
311
|
-
// Now parse the ECPrivateKey structure inside the octet string
|
|
312
|
-
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
313
|
-
this.seq().obj(
|
|
314
|
-
this.key('version').int(),
|
|
315
|
-
this.key('privateKey').octstr(),
|
|
316
|
-
this.key('parameters').explicit(0).objid().optional(),
|
|
317
|
-
this.key('publicKey').explicit(1).bitstr().optional()
|
|
318
|
-
);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
|
|
322
|
-
privateKeyBytes = ecPrivateKey.privateKey;
|
|
323
|
-
logger.debug(`Private key bytes: ${privateKeyBytes.toString('hex')}`);
|
|
324
|
-
} else {
|
|
325
|
-
throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Compute the public key
|
|
329
|
-
const publicKeyUint8Array = secp256k1.publicKeyCreate(privateKeyBytes, false); // uncompressed
|
|
330
|
-
|
|
331
|
-
// Convert publicKey to Buffer if necessary
|
|
332
|
-
const publicKeyBuffer = Buffer.isBuffer(publicKeyUint8Array)
|
|
333
|
-
? publicKeyUint8Array
|
|
334
|
-
: Buffer.from(publicKeyUint8Array);
|
|
335
|
-
|
|
282
|
+
// Use the stored keyPair.pubKeyObj to derive Ethereum address
|
|
283
|
+
const publicKeyDer = this.keyPair.prvKeyObj.generatePublicKeyHex();
|
|
284
|
+
const publicKeyBuffer = Buffer.from(publicKeyDer, 'hex');
|
|
285
|
+
|
|
336
286
|
// Derive the Ethereum address
|
|
337
287
|
const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
338
288
|
const address = '0x' + addressBuffer.toString('hex');
|
|
@@ -369,106 +319,15 @@ class DiodeConnection extends EventEmitter {
|
|
|
369
319
|
}
|
|
370
320
|
}
|
|
371
321
|
|
|
372
|
-
//
|
|
373
|
-
// try {
|
|
374
|
-
// const serverCert = this.socket.getPeerCertificate(true);
|
|
375
|
-
// if (!serverCert.raw) {
|
|
376
|
-
// throw new Error('Failed to get server certificate.');
|
|
377
|
-
// }
|
|
378
|
-
|
|
379
|
-
// // Extract public key from the certificate
|
|
380
|
-
// const publicKey = serverCert.pubkey; // May need to parse ASN.1 structure to get the public key
|
|
381
|
-
// // Assume you have a method to extract the public key buffer from the certificate
|
|
382
|
-
|
|
383
|
-
// // Compute Ethereum address from public key
|
|
384
|
-
// const publicKeyBuffer = Buffer.from(publicKey); // Ensure it's a Buffer
|
|
385
|
-
// const addressBuffer = ethUtil.pubToAddress(publicKeyBuffer, true);
|
|
386
|
-
|
|
387
|
-
// return addressBuffer; // Return as Buffer
|
|
388
|
-
// } catch (error) {
|
|
389
|
-
// console.error('Error extracting server Ethereum address:', error);
|
|
390
|
-
// throw error;
|
|
391
|
-
// }
|
|
392
|
-
// }
|
|
393
|
-
|
|
394
|
-
// Method to extract private key bytes from certPath
|
|
322
|
+
// Method to extract private key bytes from keyPair
|
|
395
323
|
getPrivateKey() {
|
|
396
|
-
// Similar to getEthereumAddress(), but return privateKeyBytes
|
|
397
|
-
// Ensure to handle different key formats (EC PRIVATE KEY and PRIVATE KEY)
|
|
398
324
|
try {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
let privateKeyBytes;
|
|
403
|
-
|
|
404
|
-
if (pem.includes('-----BEGIN PRIVATE KEY-----')) {
|
|
405
|
-
// Handle PKCS#8 format
|
|
406
|
-
privateKeyPem = pem
|
|
407
|
-
.replace('-----BEGIN PRIVATE KEY-----', '')
|
|
408
|
-
.replace('-----END PRIVATE KEY-----', '')
|
|
409
|
-
.replace(/\r?\n|\r/g, '');
|
|
410
|
-
|
|
411
|
-
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
412
|
-
|
|
413
|
-
// Define ASN.1 structure for PKCS#8 private key
|
|
414
|
-
const PrivateKeyInfoASN = asn1.define('PrivateKeyInfo', function () {
|
|
415
|
-
this.seq().obj(
|
|
416
|
-
this.key('version').int(),
|
|
417
|
-
this.key('privateKeyAlgorithm').seq().obj(
|
|
418
|
-
this.key('algorithm').objid(),
|
|
419
|
-
this.key('parameters').optional()
|
|
420
|
-
),
|
|
421
|
-
this.key('privateKey').octstr(),
|
|
422
|
-
this.key('attributes').implicit(0).any().optional(),
|
|
423
|
-
this.key('publicKey').implicit(1).bitstr().optional()
|
|
424
|
-
);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// Decode the DER-encoded private key
|
|
428
|
-
const privateKeyInfo = PrivateKeyInfoASN.decode(privateKeyDer, 'der');
|
|
429
|
-
const privateKeyOctetString = privateKeyInfo.privateKey;
|
|
430
|
-
|
|
431
|
-
// Now parse the ECPrivateKey structure inside the octet string
|
|
432
|
-
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
433
|
-
this.seq().obj(
|
|
434
|
-
this.key('version').int(),
|
|
435
|
-
this.key('privateKey').octstr(),
|
|
436
|
-
this.key('parameters').explicit(0).objid().optional(),
|
|
437
|
-
this.key('publicKey').explicit(1).bitstr().optional()
|
|
438
|
-
);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyOctetString, 'der');
|
|
442
|
-
privateKeyBytes = ecPrivateKey.privateKey;
|
|
443
|
-
} else if (pem.includes('-----BEGIN EC PRIVATE KEY-----')) {
|
|
444
|
-
// Handle EC PRIVATE KEY format
|
|
445
|
-
privateKeyPem = pem
|
|
446
|
-
.replace('-----BEGIN EC PRIVATE KEY-----', '')
|
|
447
|
-
.replace('-----END EC PRIVATE KEY-----', '')
|
|
448
|
-
.replace(/\r?\n|\r/g, '');
|
|
449
|
-
|
|
450
|
-
privateKeyDer = Buffer.from(privateKeyPem, 'base64');
|
|
451
|
-
|
|
452
|
-
// Define ASN.1 structure for EC private key
|
|
453
|
-
const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () {
|
|
454
|
-
this.seq().obj(
|
|
455
|
-
this.key('version').int(),
|
|
456
|
-
this.key('privateKey').octstr(),
|
|
457
|
-
this.key('parameters').explicit(0).objid().optional(),
|
|
458
|
-
this.key('publicKey').explicit(1).bitstr().optional()
|
|
459
|
-
);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Decode the DER-encoded private key
|
|
463
|
-
const ecPrivateKey = ECPrivateKeyASN.decode(privateKeyDer, 'der');
|
|
464
|
-
privateKeyBytes = ecPrivateKey.privateKey;
|
|
465
|
-
} else {
|
|
466
|
-
throw new Error('Unsupported key format. Expected EC PRIVATE KEY or PRIVATE KEY in PEM format.');
|
|
467
|
-
}
|
|
468
|
-
|
|
325
|
+
// Extract private key bytes from the keyPair.prvKeyObj
|
|
326
|
+
const privateKeyHex = this.keyPair.prvKeyObj.prvKeyHex;
|
|
327
|
+
const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');
|
|
469
328
|
return privateKeyBytes;
|
|
470
329
|
} catch (error) {
|
|
471
|
-
logger.error(`Error extracting
|
|
330
|
+
logger.error(`Error extracting private key: ${error}`);
|
|
472
331
|
throw error;
|
|
473
332
|
}
|
|
474
333
|
}
|
|
@@ -557,6 +416,12 @@ class DiodeConnection extends EventEmitter {
|
|
|
557
416
|
return ticketCommand;
|
|
558
417
|
}
|
|
559
418
|
|
|
419
|
+
getDeviceCertificate() {
|
|
420
|
+
return this.certPem;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
560
425
|
_getNextRequestId() {
|
|
561
426
|
// Increment the request ID counter, wrap around if necessary
|
|
562
427
|
this.requestId = (this.requestId + 1) % Number.MAX_SAFE_INTEGER;
|
package/examples/RPCTest.js
CHANGED
|
@@ -4,9 +4,9 @@ const { makeReadable } = require('../utils');
|
|
|
4
4
|
async function main() {
|
|
5
5
|
const host = 'us2.prenet.diode.io';
|
|
6
6
|
const port = 41046;
|
|
7
|
-
const
|
|
7
|
+
const keyLocation = './db/keys.json';
|
|
8
8
|
|
|
9
|
-
const connection = new DiodeConnection(host, port,
|
|
9
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
10
10
|
await connection.connect();
|
|
11
11
|
const rpc = connection.RPC;
|
|
12
12
|
|
|
@@ -3,14 +3,30 @@ const { DiodeConnection, BindPort } = require('../index');
|
|
|
3
3
|
async function main() {
|
|
4
4
|
const host = 'us2.prenet.diode.io';
|
|
5
5
|
const port = 41046;
|
|
6
|
-
const
|
|
6
|
+
const keyLocation = './db/keys.json';
|
|
7
7
|
|
|
8
|
-
const connection = new DiodeConnection(host, port,
|
|
8
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
9
9
|
await connection.connect();
|
|
10
10
|
|
|
11
|
-
const portForward = new BindPort(connection,
|
|
11
|
+
const portForward = new BindPort(connection, {
|
|
12
|
+
3003: { targetPort: 8080, deviceIdHex: "ca1e71d8105a598810578fb6042fa8cbc1e7f039" },
|
|
13
|
+
3004: { targetPort: 443, deviceIdHex: "5365baf29cb7ab58de588dfc448913cb609283e2" }
|
|
14
|
+
});
|
|
12
15
|
portForward.bind();
|
|
16
|
+
|
|
17
|
+
// after 5 seconds, remove port 3003
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
console.log("Removing port 3003");
|
|
20
|
+
portForward.removePort(3003);
|
|
21
|
+
}, 5000);
|
|
22
|
+
|
|
23
|
+
// after 10 seconds, add port 3003 back
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
console.log("Adding port 3003 back");
|
|
26
|
+
portForward.addPort(3003, 8080, "ca1e71d8105a598810578fb6042fa8cbc1e7f039");
|
|
27
|
+
}, 10000);
|
|
28
|
+
|
|
13
29
|
|
|
14
30
|
}
|
|
15
31
|
|
|
16
|
-
main();
|
|
32
|
+
main().catch(console.error);
|
|
@@ -4,14 +4,14 @@ const BindPort = require('../bindPort')
|
|
|
4
4
|
|
|
5
5
|
const host = 'us2.prenet.diode.io';
|
|
6
6
|
const port = 41046;
|
|
7
|
-
const
|
|
7
|
+
const keyLocation = './db/keys.json';
|
|
8
8
|
|
|
9
|
-
const connection = new DiodeConnection(host, port,
|
|
9
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
10
10
|
|
|
11
11
|
async function main() {
|
|
12
12
|
await connection.connect();
|
|
13
13
|
const publishedPorts = [8080]; // Ports you want to publish
|
|
14
|
-
const publishPort = new PublishPort(connection, publishedPorts
|
|
14
|
+
const publishPort = new PublishPort(connection, publishedPorts);
|
|
15
15
|
|
|
16
16
|
const portForward = new BindPort(connection, 3002, 8080, "5365baf29cb7ab58de588dfc448913cb609283e2");
|
|
17
17
|
portForward.bind();
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
// example.js
|
|
2
2
|
|
|
3
|
-
const DiodeConnection = require('../
|
|
4
|
-
const PublishPort = require('../publishPort')
|
|
3
|
+
const { DiodeConnection, PublishPort } = require('../index');
|
|
5
4
|
|
|
6
5
|
async function startPublishing() {
|
|
7
6
|
const host = 'us2.prenet.diode.io';
|
|
8
7
|
const port = 41046;
|
|
9
|
-
const
|
|
8
|
+
const keyLocation = './db/keys.json';
|
|
10
9
|
|
|
11
|
-
const connection = new DiodeConnection(host, port,
|
|
10
|
+
const connection = new DiodeConnection(host, port, keyLocation);
|
|
12
11
|
await connection.connect();
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
const publishPort = new PublishPort(connection,
|
|
16
|
-
|
|
13
|
+
// Create a PublishPort instance with initial ports
|
|
14
|
+
const publishPort = new PublishPort(connection, {
|
|
15
|
+
3000: { mode: 'public' },
|
|
16
|
+
8080: {
|
|
17
|
+
mode: 'private',
|
|
18
|
+
whitelist: ['0xca1e71d8105a598810578fb6042fa8cbc1e7f039'] // Replace with actual addresses
|
|
19
|
+
}
|
|
20
|
+
}, keyLocation);
|
|
21
|
+
|
|
22
|
+
console.log('Initial published ports:', publishPort.getPublishedPorts());
|
|
23
|
+
|
|
24
|
+
// After 10 seconds, remove a port
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
console.log("Removing port 8080");
|
|
27
|
+
publishPort.removePort(3000);
|
|
28
|
+
console.log('Updated published ports:', publishPort.getPublishedPorts());
|
|
29
|
+
}, 10000);
|
|
30
|
+
|
|
31
|
+
// After 15 seconds, add multiple ports
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
console.log("Adding multiple ports");
|
|
34
|
+
publishPort.addPort(3000,{ mode: 'public' });
|
|
35
|
+
console.log('Updated published ports:', publishPort.getPublishedPorts());
|
|
36
|
+
}, 15000);
|
|
37
|
+
|
|
17
38
|
}
|
|
18
39
|
|
|
19
|
-
startPublishing();
|
|
40
|
+
startPublishing().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diodejs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A JavaScript client for interacting with the Diode network. It provides functionalities to bind and publish ports, send RPC commands, and handle responses.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/publishPort.js
CHANGED
|
@@ -36,39 +36,98 @@ class DiodeSocket extends Duplex {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
class PublishPort extends EventEmitter {
|
|
39
|
-
constructor(connection, publishedPorts,
|
|
39
|
+
constructor(connection, publishedPorts, _certPath = null) {
|
|
40
40
|
super();
|
|
41
41
|
this.connection = connection;
|
|
42
|
+
this.rpc = new DiodeRPC(connection);
|
|
42
43
|
|
|
43
44
|
// Convert publishedPorts to a Map with configurations
|
|
44
45
|
this.publishedPorts = new Map();
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
if (
|
|
47
|
+
// Initialize with the provided ports
|
|
48
|
+
if (publishedPorts) {
|
|
49
|
+
this.addPorts(publishedPorts);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.startListening();
|
|
53
|
+
if (this.publishedPorts.size > 0) {
|
|
54
|
+
logger.info(`Publishing ports: ${Array.from(this.publishedPorts.keys())}`);
|
|
55
|
+
} else {
|
|
56
|
+
logger.info("No ports published initially");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add a single port with configuration
|
|
61
|
+
addPort(port, config = { mode: 'public', whitelist: [] }) {
|
|
62
|
+
const portNum = parseInt(port, 10);
|
|
63
|
+
|
|
64
|
+
// Normalize the configuration
|
|
65
|
+
const portConfig = {
|
|
66
|
+
mode: config.mode || 'public',
|
|
67
|
+
whitelist: Array.isArray(config.whitelist) ? config.whitelist : []
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Add to map
|
|
71
|
+
this.publishedPorts.set(portNum, portConfig);
|
|
72
|
+
logger.info(`Added published port ${portNum} with mode: ${portConfig.mode}`);
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Remove a published port
|
|
78
|
+
removePort(port) {
|
|
79
|
+
const portNum = parseInt(port, 10);
|
|
80
|
+
|
|
81
|
+
if (!this.publishedPorts.has(portNum)) {
|
|
82
|
+
logger.warn(`Port ${portNum} is not published`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Close any active connections for this port
|
|
87
|
+
// This could require tracking active connections by port
|
|
88
|
+
// For now, let's log about active connections
|
|
89
|
+
const activeConnections = Array.from(this.connection.connections.values())
|
|
90
|
+
.filter(conn => conn.port === portNum);
|
|
91
|
+
|
|
92
|
+
if (activeConnections.length > 0) {
|
|
93
|
+
logger.warn(`Removing port ${portNum} with ${activeConnections.length} active connections`);
|
|
94
|
+
// We could close these connections, but they'll be rejected naturally on next data transfer
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.publishedPorts.delete(portNum);
|
|
98
|
+
logger.info(`Removed published port ${portNum}`);
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add multiple ports at once (from array or object)
|
|
104
|
+
addPorts(ports) {
|
|
105
|
+
if (Array.isArray(ports)) {
|
|
48
106
|
// Legacy array format - treat all ports as public
|
|
49
|
-
|
|
50
|
-
this.
|
|
107
|
+
ports.forEach(port => {
|
|
108
|
+
this.addPort(port);
|
|
51
109
|
});
|
|
52
|
-
} else if (typeof
|
|
110
|
+
} else if (typeof ports === 'object' && ports !== null) {
|
|
53
111
|
// New object format with configurations
|
|
54
|
-
Object.entries(
|
|
55
|
-
|
|
56
|
-
// Ensure config is properly structured
|
|
57
|
-
const portConfig = typeof config === 'object' && config !== null
|
|
58
|
-
? {
|
|
59
|
-
mode: config.mode || 'public',
|
|
60
|
-
whitelist: Array.isArray(config.whitelist) ? config.whitelist : []
|
|
61
|
-
}
|
|
62
|
-
: { mode: 'public', whitelist: [] };
|
|
63
|
-
|
|
64
|
-
this.publishedPorts.set(portNum, portConfig);
|
|
112
|
+
Object.entries(ports).forEach(([port, config]) => {
|
|
113
|
+
this.addPort(port, config);
|
|
65
114
|
});
|
|
66
115
|
}
|
|
67
116
|
|
|
68
|
-
this
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Get all published ports with their configurations
|
|
121
|
+
getPublishedPorts() {
|
|
122
|
+
return Object.fromEntries(this.publishedPorts.entries());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Clear all published ports
|
|
126
|
+
clearPorts() {
|
|
127
|
+
const portCount = this.publishedPorts.size;
|
|
128
|
+
this.publishedPorts.clear();
|
|
129
|
+
logger.info(`Cleared ${portCount} published ports`);
|
|
130
|
+
return portCount;
|
|
72
131
|
}
|
|
73
132
|
|
|
74
133
|
startListening() {
|
|
@@ -112,7 +171,6 @@ class PublishPort extends EventEmitter {
|
|
|
112
171
|
port = portString;
|
|
113
172
|
} else {
|
|
114
173
|
var [protocol, portStr] = portString.split(':');
|
|
115
|
-
console.log(`Protocol: ${protocol}, Port: ${portStr}`);
|
|
116
174
|
if (!portStr) {
|
|
117
175
|
portStr = protocol;
|
|
118
176
|
protocol = 'tcp';
|
|
@@ -141,11 +199,11 @@ class PublishPort extends EventEmitter {
|
|
|
141
199
|
|
|
142
200
|
// Handle based on protocol
|
|
143
201
|
if (protocol === 'tcp') {
|
|
144
|
-
this.handleTCPConnection(sessionId, ref, port);
|
|
202
|
+
this.handleTCPConnection(sessionId, ref, port, deviceId);
|
|
145
203
|
} else if (protocol === 'tls') {
|
|
146
|
-
this.handleTLSConnection(sessionId, ref, port);
|
|
204
|
+
this.handleTLSConnection(sessionId, ref, port, deviceId);
|
|
147
205
|
} else if (protocol === 'udp') {
|
|
148
|
-
this.handleUDPConnection(sessionId, ref, port);
|
|
206
|
+
this.handleUDPConnection(sessionId, ref, port, deviceId);
|
|
149
207
|
} else {
|
|
150
208
|
logger.warn(`Unsupported protocol: ${protocol}`);
|
|
151
209
|
this.rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
|
|
@@ -177,7 +235,7 @@ class PublishPort extends EventEmitter {
|
|
|
177
235
|
}
|
|
178
236
|
}
|
|
179
237
|
|
|
180
|
-
handleTCPConnection(sessionId, ref, port) {
|
|
238
|
+
handleTCPConnection(sessionId, ref, port, deviceId) {
|
|
181
239
|
// Create a TCP connection to the local service on the specified port
|
|
182
240
|
const localSocket = net.connect({ port: port }, () => {
|
|
183
241
|
logger.info(`Connected to local TCP service on port ${port}`);
|
|
@@ -189,17 +247,19 @@ class PublishPort extends EventEmitter {
|
|
|
189
247
|
this.setupLocalSocketHandlers(localSocket, ref, 'tcp');
|
|
190
248
|
|
|
191
249
|
// Store the local socket with the ref using connection's method
|
|
192
|
-
this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp' });
|
|
250
|
+
this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, deviceId });
|
|
193
251
|
}
|
|
194
252
|
|
|
195
|
-
handleTLSConnection(sessionId, ref, port) {
|
|
253
|
+
handleTLSConnection(sessionId, ref, port, deviceId) {
|
|
196
254
|
// Create a DiodeSocket instance
|
|
197
255
|
const diodeSocket = new DiodeSocket(ref, this.rpc);
|
|
198
256
|
|
|
257
|
+
certPem = this.connection.getDeviceCertificate();
|
|
258
|
+
|
|
199
259
|
// TLS options with your server's certificate and key
|
|
200
260
|
const tlsOptions = {
|
|
201
|
-
cert:
|
|
202
|
-
key:
|
|
261
|
+
cert: certPem,
|
|
262
|
+
key: certPem,
|
|
203
263
|
rejectUnauthorized: false,
|
|
204
264
|
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
205
265
|
ecdhCurve: 'secp256k1',
|
|
@@ -231,7 +291,6 @@ class PublishPort extends EventEmitter {
|
|
|
231
291
|
});
|
|
232
292
|
|
|
233
293
|
tlsSocket.on('close', () => {
|
|
234
|
-
console.log('TLS Socket closed');
|
|
235
294
|
this.connection.deleteConnection(ref);
|
|
236
295
|
});
|
|
237
296
|
|
|
@@ -241,10 +300,12 @@ class PublishPort extends EventEmitter {
|
|
|
241
300
|
tlsSocket,
|
|
242
301
|
localSocket,
|
|
243
302
|
protocol: 'tls',
|
|
303
|
+
port,
|
|
304
|
+
deviceId,
|
|
244
305
|
});
|
|
245
306
|
}
|
|
246
307
|
|
|
247
|
-
handleUDPConnection(sessionId, ref, port) {
|
|
308
|
+
handleUDPConnection(sessionId, ref, port, deviceId) {
|
|
248
309
|
// Create a UDP socket
|
|
249
310
|
const localSocket = dgram.createSocket('udp4');
|
|
250
311
|
|
|
@@ -259,6 +320,8 @@ class PublishPort extends EventEmitter {
|
|
|
259
320
|
socket: localSocket,
|
|
260
321
|
protocol: 'udp',
|
|
261
322
|
remoteInfo,
|
|
323
|
+
port,
|
|
324
|
+
deviceId
|
|
262
325
|
});
|
|
263
326
|
|
|
264
327
|
logger.info(`UDP connection set up on port ${port}`);
|
|
@@ -289,8 +352,26 @@ class PublishPort extends EventEmitter {
|
|
|
289
352
|
const data = Buffer.from(dataRaw)//.slice(4);
|
|
290
353
|
|
|
291
354
|
const connectionInfo = this.connection.getConnection(ref);
|
|
355
|
+
// Check if the port is still open and address is still in whitelist
|
|
292
356
|
if (connectionInfo) {
|
|
293
|
-
const { socket: localSocket, protocol, remoteInfo } = connectionInfo;
|
|
357
|
+
const { socket: localSocket, protocol, remoteInfo, port, deviceId } = connectionInfo;
|
|
358
|
+
|
|
359
|
+
if (!this.publishedPorts.has(port)) {
|
|
360
|
+
logger.warn(`Port ${port} is not published. Sending portclose.`);
|
|
361
|
+
this.rpc.portClose(ref);
|
|
362
|
+
this.connection.deleteConnection(ref);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const portConfig = this.publishedPorts.get(port);
|
|
367
|
+
if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
|
|
368
|
+
if (!portConfig.whitelist.includes(deviceId)) {
|
|
369
|
+
logger.warn(`Device ${deviceId} is not whitelisted for port ${port}. Sending portclose.`);
|
|
370
|
+
this.rpc.portClose(ref);
|
|
371
|
+
this.connection.deleteConnection(ref);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
294
375
|
|
|
295
376
|
if (protocol === 'udp') {
|
|
296
377
|
// Send data to the local UDP service
|
package/utils.js
CHANGED
|
@@ -80,19 +80,32 @@ function parseReason(reasonRaw) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function generateCert(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
function generateCert(privateKeyObj, publicKeyObj) {
|
|
84
|
+
// Generate a certificate valid for 1 month
|
|
85
|
+
function formatDate(date) {
|
|
86
|
+
const pad = n => n < 10 ? '0' + n : n;
|
|
87
|
+
return String(date.getUTCFullYear()).slice(2) +
|
|
88
|
+
pad(date.getUTCMonth() + 1) +
|
|
89
|
+
pad(date.getUTCDate()) +
|
|
90
|
+
pad(date.getUTCHours()) +
|
|
91
|
+
pad(date.getUTCMinutes()) +
|
|
92
|
+
pad(date.getUTCSeconds()) +
|
|
93
|
+
'Z';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const now = new Date();
|
|
97
|
+
const notBefore = formatDate(new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)); // 30 days before now
|
|
98
|
+
const notAfter = formatDate(new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)); // 30 days after now
|
|
99
|
+
|
|
100
|
+
|
|
90
101
|
var x = new KJUR.asn1.x509.Certificate({
|
|
91
102
|
version: 3,
|
|
92
|
-
serial: { int:
|
|
103
|
+
serial: { int: Math.floor(Math.random() * 1000000) },
|
|
93
104
|
issuer: { str: "/CN=device" },
|
|
105
|
+
notbefore: notBefore,
|
|
106
|
+
notafter: notAfter,
|
|
94
107
|
subject: { str: "/CN=device" },
|
|
95
|
-
sbjpubkey:
|
|
108
|
+
sbjpubkey: publicKeyObj,
|
|
96
109
|
ext: [
|
|
97
110
|
{ extname: "basicConstraints", cA: false },
|
|
98
111
|
{ extname: "keyUsage", critical: true, names: ["digitalSignature"] },
|
|
@@ -102,14 +115,52 @@ function generateCert(path) {
|
|
|
102
115
|
}
|
|
103
116
|
],
|
|
104
117
|
sigalg: "SHA256withECDSA",
|
|
105
|
-
cakey:
|
|
118
|
+
cakey: privateKeyObj
|
|
106
119
|
});
|
|
107
120
|
|
|
121
|
+
// Get PEM representations
|
|
122
|
+
const priv = KEYUTIL.getPEM(privateKeyObj, "PKCS8PRV");
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
// Return the certificate with private key
|
|
126
|
+
return priv + x.getPEM();
|
|
127
|
+
}
|
|
108
128
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
129
|
+
function loadOrGenerateKeyPair(keyLocation) {
|
|
130
|
+
try {
|
|
131
|
+
ensureDirectoryExistence(keyLocation);
|
|
132
|
+
|
|
133
|
+
// Try to load existing keys
|
|
134
|
+
if (fs.existsSync(keyLocation)) {
|
|
135
|
+
logger.info(`Loading keys from ${keyLocation}`);
|
|
136
|
+
const keyData = JSON.parse(fs.readFileSync(keyLocation, 'utf8'));
|
|
137
|
+
|
|
138
|
+
// Convert the stored JSON back to keypair objects
|
|
139
|
+
const prvKeyObj = KEYUTIL.getKeyFromPlainPrivatePKCS8PEM(keyData.privateKey);
|
|
140
|
+
const pubKeyObj = KEYUTIL.getKey(keyData.publicKey);
|
|
141
|
+
|
|
142
|
+
return { prvKeyObj, pubKeyObj };
|
|
143
|
+
} else {
|
|
144
|
+
// Generate new keypair
|
|
145
|
+
logger.info(`Generating new key pair at ${keyLocation}`);
|
|
146
|
+
const kp = KEYUTIL.generateKeypair("EC", "secp256k1");
|
|
147
|
+
|
|
148
|
+
// Store the keys in a serializable format
|
|
149
|
+
const keyData = {
|
|
150
|
+
privateKey: KEYUTIL.getPEM(kp.prvKeyObj, "PKCS8PRV"),
|
|
151
|
+
publicKey: KEYUTIL.getPEM(kp.pubKeyObj, "PKCS8PUB"),
|
|
152
|
+
check: kp.prvKeyObj.prvKeyHex
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Save to file
|
|
156
|
+
fs.writeFileSync(keyLocation, JSON.stringify(keyData, null, 2), 'utf8');
|
|
157
|
+
|
|
158
|
+
return kp;
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error(`Error loading or generating key pair: ${error}`);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
113
164
|
}
|
|
114
165
|
|
|
115
166
|
function ensureDirectoryExistence(filePath) {
|
|
@@ -121,4 +172,12 @@ function ensureDirectoryExistence(filePath) {
|
|
|
121
172
|
fs.mkdirSync(dirname);
|
|
122
173
|
}
|
|
123
174
|
|
|
124
|
-
module.exports = {
|
|
175
|
+
module.exports = {
|
|
176
|
+
makeReadable,
|
|
177
|
+
parseRequestId,
|
|
178
|
+
parseResponseType,
|
|
179
|
+
parseReason,
|
|
180
|
+
generateCert,
|
|
181
|
+
loadOrGenerateKeyPair,
|
|
182
|
+
ensureDirectoryExistence
|
|
183
|
+
};
|