diodejs 0.1.0 → 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 +89 -23
- 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 +136 -19
- 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,18 +113,28 @@ 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
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
121
|
+
// Option 1: Simple array of ports (all public)
|
|
122
|
+
const publishedPorts = [8080, 3000];
|
|
123
|
+
|
|
124
|
+
// Option 2: Object with port configurations for public/private access control
|
|
125
|
+
const publishedPortsWithConfig = {
|
|
126
|
+
8080: { mode: 'public' }, // Public port, accessible by any device
|
|
127
|
+
3000: {
|
|
128
|
+
mode: 'private',
|
|
129
|
+
whitelist: ['0x1234abcd5678...', '0x9876fedc5432...'] // Only these devices can connect
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// certPath parameter is maintained for backward compatibility but not required
|
|
134
|
+
const publishPort = new PublishPort(connection, publishedPortsWithConfig);
|
|
93
135
|
}
|
|
94
136
|
|
|
95
137
|
main();
|
|
96
|
-
|
|
97
138
|
```
|
|
98
139
|
|
|
99
140
|
## Reference
|
|
@@ -102,19 +143,20 @@ main();
|
|
|
102
143
|
|
|
103
144
|
#### `DiodeConnection`
|
|
104
145
|
|
|
105
|
-
- **Constructor**: `new DiodeConnection(host, port,
|
|
146
|
+
- **Constructor**: `new DiodeConnection(host, port, keyLocation)`
|
|
106
147
|
- `host` (string): The host address of the Diode server.
|
|
107
148
|
- `port` (number): The port number of the Diode server.
|
|
108
|
-
- `
|
|
149
|
+
- `keyLocation` (string)(default: './db/keys.json'): The path to the key storage file. If the file doesn't exist, keys are generated automatically.
|
|
109
150
|
|
|
110
151
|
- **Methods**:
|
|
111
152
|
- `connect()`: Connects to the Diode server. Returns a promise.
|
|
112
153
|
- `sendCommand(commandArray)`: Sends a command to the Diode server. Returns a promise.
|
|
113
154
|
- `sendCommandWithSessionId(commandArray, sessionId)`: Sends a command with a session ID. Returns a promise.
|
|
114
|
-
- `getEthereumAddress()`: Returns the Ethereum address derived from the device
|
|
155
|
+
- `getEthereumAddress()`: Returns the Ethereum address derived from the device keys.
|
|
115
156
|
- `getServerEthereumAddress()`: Returns the Ethereum address of the server.
|
|
116
157
|
- `createTicketCommand()`: Creates a ticket command for authentication. Returns a promise.
|
|
117
158
|
- `close()`: Closes the connection to the Diode server.
|
|
159
|
+
- `getDeviceCertificate()`: Returns the generated certificate PEM.
|
|
118
160
|
|
|
119
161
|
#### `DiodeRPC`
|
|
120
162
|
|
|
@@ -136,23 +178,47 @@ main();
|
|
|
136
178
|
|
|
137
179
|
#### `BindPort`
|
|
138
180
|
|
|
139
|
-
- **
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- `
|
|
143
|
-
|
|
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" } }`
|
|
144
195
|
|
|
145
196
|
- **Methods**:
|
|
146
|
-
- `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.
|
|
147
202
|
|
|
148
203
|
#### `PublishPort`
|
|
149
204
|
|
|
150
|
-
- **Constructor**: `new PublishPort(connection, publishedPorts,
|
|
205
|
+
- **Constructor**: `new PublishPort(connection, publishedPorts, _certPath)`
|
|
151
206
|
- `connection` (DiodeConnection): An instance of `DiodeConnection`.
|
|
152
|
-
- `publishedPorts` (array):
|
|
153
|
-
|
|
207
|
+
- `publishedPorts` (array|object): Either:
|
|
208
|
+
- An array of ports to publish (all public mode)
|
|
209
|
+
- An object mapping ports to their configuration: `{ port: { mode: 'public'|'private', whitelist: ['0x123...'] } }`
|
|
210
|
+
- `_certPath` (string): Has no functionality and maintained for backward compatibility.
|
|
154
211
|
|
|
155
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.
|
|
156
222
|
- `startListening()`: Starts listening for unsolicited messages.
|
|
157
223
|
- `handlePortOpen(sessionIdRaw, messageContent)`: Handles port open requests.
|
|
158
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,14 +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.publishedPorts = new Set(publishedPorts); // Convert array to a Set
|
|
43
|
-
// Remove local connections map and use the one from connection
|
|
44
|
-
this.startListening();
|
|
45
42
|
this.rpc = new DiodeRPC(connection);
|
|
46
|
-
|
|
43
|
+
|
|
44
|
+
// Convert publishedPorts to a Map with configurations
|
|
45
|
+
this.publishedPorts = new Map();
|
|
46
|
+
|
|
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)) {
|
|
106
|
+
// Legacy array format - treat all ports as public
|
|
107
|
+
ports.forEach(port => {
|
|
108
|
+
this.addPort(port);
|
|
109
|
+
});
|
|
110
|
+
} else if (typeof ports === 'object' && ports !== null) {
|
|
111
|
+
// New object format with configurations
|
|
112
|
+
Object.entries(ports).forEach(([port, config]) => {
|
|
113
|
+
this.addPort(port, config);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
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;
|
|
47
131
|
}
|
|
48
132
|
|
|
49
133
|
startListening() {
|
|
@@ -76,7 +160,7 @@ class PublishPort extends EventEmitter {
|
|
|
76
160
|
const sessionId = Buffer.from(sessionIdRaw);
|
|
77
161
|
const portString = makeReadable(portStringRaw);
|
|
78
162
|
const ref = Buffer.from(refRaw);
|
|
79
|
-
const deviceId = Buffer.from(deviceIdRaw).toString('hex')
|
|
163
|
+
const deviceId = `0x${Buffer.from(deviceIdRaw).toString('hex')}`;
|
|
80
164
|
|
|
81
165
|
logger.info(`Received portopen request for portString ${portString} with ref ${ref.toString('hex')} from device ${deviceId}`);
|
|
82
166
|
|
|
@@ -87,7 +171,6 @@ class PublishPort extends EventEmitter {
|
|
|
87
171
|
port = portString;
|
|
88
172
|
} else {
|
|
89
173
|
var [protocol, portStr] = portString.split(':');
|
|
90
|
-
console.log(`Protocol: ${protocol}, Port: ${portStr}`);
|
|
91
174
|
if (!portStr) {
|
|
92
175
|
portStr = protocol;
|
|
93
176
|
protocol = 'tcp';
|
|
@@ -96,20 +179,31 @@ class PublishPort extends EventEmitter {
|
|
|
96
179
|
}
|
|
97
180
|
|
|
98
181
|
// Check if the port is published
|
|
99
|
-
if (!this.publishedPorts.has(port)) {
|
|
182
|
+
if (!this.publishedPorts.has(port)) {
|
|
100
183
|
logger.warn(`Port ${port} is not published. Rejecting request.`);
|
|
101
184
|
// Send error response
|
|
102
185
|
this.rpc.sendError(sessionId, ref, 'Port is not published');
|
|
103
186
|
return;
|
|
104
187
|
}
|
|
105
188
|
|
|
189
|
+
// Get port configuration and check whitelist if in private mode
|
|
190
|
+
const portConfig = this.publishedPorts.get(port);
|
|
191
|
+
if (portConfig.mode === 'private' && Array.isArray(portConfig.whitelist)) {
|
|
192
|
+
if (!portConfig.whitelist.includes(deviceId)) {
|
|
193
|
+
logger.warn(`Device ${deviceId} is not whitelisted for port ${port}. Rejecting request.`);
|
|
194
|
+
this.rpc.sendError(sessionId, ref, 'Device not whitelisted');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
logger.info(`Device ${deviceId} is whitelisted for port ${port}. Accepting request.`);
|
|
198
|
+
}
|
|
199
|
+
|
|
106
200
|
// Handle based on protocol
|
|
107
201
|
if (protocol === 'tcp') {
|
|
108
|
-
this.handleTCPConnection(sessionId, ref, port);
|
|
202
|
+
this.handleTCPConnection(sessionId, ref, port, deviceId);
|
|
109
203
|
} else if (protocol === 'tls') {
|
|
110
|
-
this.handleTLSConnection(sessionId, ref, port);
|
|
204
|
+
this.handleTLSConnection(sessionId, ref, port, deviceId);
|
|
111
205
|
} else if (protocol === 'udp') {
|
|
112
|
-
this.handleUDPConnection(sessionId, ref, port);
|
|
206
|
+
this.handleUDPConnection(sessionId, ref, port, deviceId);
|
|
113
207
|
} else {
|
|
114
208
|
logger.warn(`Unsupported protocol: ${protocol}`);
|
|
115
209
|
this.rpc.sendError(sessionId, ref, `Unsupported protocol: ${protocol}`);
|
|
@@ -141,7 +235,7 @@ class PublishPort extends EventEmitter {
|
|
|
141
235
|
}
|
|
142
236
|
}
|
|
143
237
|
|
|
144
|
-
handleTCPConnection(sessionId, ref, port) {
|
|
238
|
+
handleTCPConnection(sessionId, ref, port, deviceId) {
|
|
145
239
|
// Create a TCP connection to the local service on the specified port
|
|
146
240
|
const localSocket = net.connect({ port: port }, () => {
|
|
147
241
|
logger.info(`Connected to local TCP service on port ${port}`);
|
|
@@ -153,17 +247,19 @@ class PublishPort extends EventEmitter {
|
|
|
153
247
|
this.setupLocalSocketHandlers(localSocket, ref, 'tcp');
|
|
154
248
|
|
|
155
249
|
// Store the local socket with the ref using connection's method
|
|
156
|
-
this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp' });
|
|
250
|
+
this.connection.addConnection(ref, { socket: localSocket, protocol: 'tcp', port, deviceId });
|
|
157
251
|
}
|
|
158
252
|
|
|
159
|
-
handleTLSConnection(sessionId, ref, port) {
|
|
253
|
+
handleTLSConnection(sessionId, ref, port, deviceId) {
|
|
160
254
|
// Create a DiodeSocket instance
|
|
161
255
|
const diodeSocket = new DiodeSocket(ref, this.rpc);
|
|
162
256
|
|
|
257
|
+
certPem = this.connection.getDeviceCertificate();
|
|
258
|
+
|
|
163
259
|
// TLS options with your server's certificate and key
|
|
164
260
|
const tlsOptions = {
|
|
165
|
-
cert:
|
|
166
|
-
key:
|
|
261
|
+
cert: certPem,
|
|
262
|
+
key: certPem,
|
|
167
263
|
rejectUnauthorized: false,
|
|
168
264
|
ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384',
|
|
169
265
|
ecdhCurve: 'secp256k1',
|
|
@@ -195,7 +291,6 @@ class PublishPort extends EventEmitter {
|
|
|
195
291
|
});
|
|
196
292
|
|
|
197
293
|
tlsSocket.on('close', () => {
|
|
198
|
-
console.log('TLS Socket closed');
|
|
199
294
|
this.connection.deleteConnection(ref);
|
|
200
295
|
});
|
|
201
296
|
|
|
@@ -205,10 +300,12 @@ class PublishPort extends EventEmitter {
|
|
|
205
300
|
tlsSocket,
|
|
206
301
|
localSocket,
|
|
207
302
|
protocol: 'tls',
|
|
303
|
+
port,
|
|
304
|
+
deviceId,
|
|
208
305
|
});
|
|
209
306
|
}
|
|
210
307
|
|
|
211
|
-
handleUDPConnection(sessionId, ref, port) {
|
|
308
|
+
handleUDPConnection(sessionId, ref, port, deviceId) {
|
|
212
309
|
// Create a UDP socket
|
|
213
310
|
const localSocket = dgram.createSocket('udp4');
|
|
214
311
|
|
|
@@ -223,6 +320,8 @@ class PublishPort extends EventEmitter {
|
|
|
223
320
|
socket: localSocket,
|
|
224
321
|
protocol: 'udp',
|
|
225
322
|
remoteInfo,
|
|
323
|
+
port,
|
|
324
|
+
deviceId
|
|
226
325
|
});
|
|
227
326
|
|
|
228
327
|
logger.info(`UDP connection set up on port ${port}`);
|
|
@@ -253,8 +352,26 @@ class PublishPort extends EventEmitter {
|
|
|
253
352
|
const data = Buffer.from(dataRaw)//.slice(4);
|
|
254
353
|
|
|
255
354
|
const connectionInfo = this.connection.getConnection(ref);
|
|
355
|
+
// Check if the port is still open and address is still in whitelist
|
|
256
356
|
if (connectionInfo) {
|
|
257
|
-
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
|
+
}
|
|
258
375
|
|
|
259
376
|
if (protocol === 'udp') {
|
|
260
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
|
+
};
|