The text was created as an example of communication with Tor, because node.js does not have an official library for this, and after reading the protocol it turns out that it is unnecessary and enough net.Socket
Tor is free software that allows you to remain anonymous while using the Internet. Anonymity is achieved by connecting to other relays (computers) on the Tor network.
A very interesting element of this network is hidden services. We can provide local services on the network completely anonymously and we can access them through an address in the .onion domain. For more information about this, please visit the
The Tor network as a daemon in the system has a special port to support it 9051. Thanks to it we can control it from other applications in the system. Ask for a new network connection, read and update configurations, create hidden services and much more. If you are interested, please read the documentation
To access the port control, you need to update your basic torrc configuration.
For your own purposes, i use Tor as a service in the docker
torrc
ControlPort 0.0.0.0:9051
Access to the Tor control port can be tested using telnet.
shell
$ telnet 0.0.0.0 9051
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
In addition, for greater security, access to the port can be secured by password. For this purpose we need to generate a password hash:
shell
tor --hash-password password
16:94D87DAEACD5274060844DAD7AAC00239BBA59C61455407034007C435F
and then add it to the torrc
torrc
ControlPort 0.0.0.0:9051
CookieAuthentication 1
HashedControlPassword 16:94D87DAEACD5274060844DAD7AAC00239BBA59C61455407034007C435F
Now, when connecting as the first message, we must send the set password with the command AUTHENTICATE
shell
$ telnet 0.0.0.0 9051
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
AUTHENTICATE "password"
250 OK
Only after we receive the answer 250 OK
To create a hidden service there is a command ADD_ONION
telnet
ADD_ONION NEW:BEST Port=3000,127.0.0.1:8000
250-ServiceID=ygwtgk26eauhj4xj
250-PrivateKey=RSA1024:MIICXQIBAAKBgQDd/Nery040dYLVmizkzx14egmm2n/rHADDnjWs70SH2LTs7OZp47iRz27N2LXgeBZKJ/BOxJteakZSsNi+7q0GypFxLtn2A5Wt7OkXHEyX1kJVVPb1L2H1NtrmhcIp67PScU+ACur8RVmMFM8gk0kZhUYEzXxtkGEE9SgrZMhBowIDAQABAoGBAK6Sz7z/llH0894tcA7EvoVnW73G6YAotoUgH1APnlJC3w3Pw+gUOKsaeSYSvf14Evdgb0BSp2jZpNIU129X3eMZXuZNXapLnMJtG2isD9MPEleNky6g3+xH1gWU279i1hQ3PrnO0zsAQbpgg4l7tOafTvp7T0Jr71YhU/0W6A7RAkEA8hOIV6/8FKGYDph2jUOzM9n45Jn0FADoCheFAKTfFFhnU46HBubfP7+8t6AI57y8as5LkFx52poexJLECoia2wJBAOrBg3hoEmUivIF5uKCEOdSxVbV6HZVHfCa6BppAuLdAmckdT6JpgL/Hi0YMLbuZn85CXi9pclxLrgDjiMlkWtkCQGZOLPPqEx3hATNB6fBfqS+DsjVifw2pKGeDcxlHlGOzzM86UARxs+3sKWRtRPYzBGobD0JOYs0pD1HCOQm+m7sCQQCBn4aThouZsnt51pcecsRn799OjHYnRkiELbWVmOUlspL33KprGGxR+MGrVPAvpRz7S+Qrs8LEubkDnp+Z7ELJAkAxYE4fpxb1xk+l9fh3g3OqUH3Z+ZU3xLTLg477LiuXIXcW7b/X2CO8s9ZxvvAxSpy+9gF0uV4M4jzEKYMNbYp8
250 OK
As the first parameter we can set NEW:BEST
BEST
telnet
ADD_ONION RSA1024:MIICXQIBAAKBgQDd/Nery040dYLVmizkzx14egmm2n/rHADDnjWs70SH2LTs7OZp47iRz27N2LXgeBZKJ/BOxJteakZSsNi+7q0GypFxLtn2A5Wt7OkXHEyX1kJVVPb1L2H1NtrmhcIp67PScU+ACur8RVmMFM8gk0kZhUYEzXxtkGEE9SgrZMhBowIDAQABAoGBAK6Sz7z/llH0894tcA7EvoVnW73G6YAotoUgH1APnlJC3w3Pw+gUOKsaeSYSvf14Evdgb0BSp2jZpNIU129X3eMZXuZNXapLnMJtG2isD9MPEleNky6g3+xH1gWU279i1hQ3PrnO0zsAQbpgg4l7tOafTvp7T0Jr71YhU/0W6A7RAkEA8hOIV6/8FKGYDph2jUOzM9n45Jn0FADoCheFAKTfFFhnU46HBubfP7+8t6AI57y8as5LkFx52poexJLECoia2wJBAOrBg3hoEmUivIF5uKCEOdSxVbV6HZVHfCa6BppAuLdAmckdT6JpgL/Hi0YMLbuZn85CXi9pclxLrgDjiMlkWtkCQGZOLPPqEx3hATNB6fBfqS+DsjVifw2pKGeDcxlHlGOzzM86UARxs+3sKWRtRPYzBGobD0JOYs0pD1HCOQm+m7sCQQCBn4aThouZsnt51pcecsRn799OjHYnRkiELbWVmOUlspL33KprGGxR+MGrVPAvpRz7S+Qrs8LEubkDnp+Z7ELJAkAxYE4fpxb1xk+l9fh3g3OqUH3Z+ZU3xLTLg477LiuXIXcW7b/X2CO8s9ZxvvAxSpy+9gF0uV4M4jzEKYMNbYp8 Port=3000,127.0.0.1:8000
250-ServiceID=ygwtgk26eauhj4xj
250 OK
Then we only get the service id in response.
The second parameter of the command is the mapping of services on a given port. The first value is the port for hidden service. For 3000 it will be 'ygwtgk26eauhj4xj.onion:3000' and the second value after the comma is the address of the local service which will be visible under port 3000.
Here's an important point. Hidden service is available until the telnet connection is closed. Use the 'Detach' flag to keep the service available even after the call is ended.
telnet
ADD_ONION NEW:BEST Flags=Detach Port=3000,127.0.0.1:8000
250-ServiceID=ygwtgk26eauhj4xj
250-PrivateKey=RSA1024:MIICXQIBAAKBgQDd/Nery040dYLVmizkzx14egmm2n/rHADDnjWs70SH2LTs7OZp47iRz27N2LXgeBZKJ/BOxJteakZSsNi+7q0GypFxLtn2A5Wt7OkXHEyX1kJVVPb1L2H1NtrmhcIp67PScU+ACur8RVmMFM8gk0kZhUYEzXxtkGEE9SgrZMhBowIDAQABAoGBAK6Sz7z/llH0894tcA7EvoVnW73G6YAotoUgH1APnlJC3w3Pw+gUOKsaeSYSvf14Evdgb0BSp2jZpNIU129X3eMZXuZNXapLnMJtG2isD9MPEleNky6g3+xH1gWU279i1hQ3PrnO0zsAQbpgg4l7tOafTvp7T0Jr71YhU/0W6A7RAkEA8hOIV6/8FKGYDph2jUOzM9n45Jn0FADoCheFAKTfFFhnU46HBubfP7+8t6AI57y8as5LkFx52poexJLECoia2wJBAOrBg3hoEmUivIF5uKCEOdSxVbV6HZVHfCa6BppAuLdAmckdT6JpgL/Hi0YMLbuZn85CXi9pclxLrgDjiMlkWtkCQGZOLPPqEx3hATNB6fBfqS+DsjVifw2pKGeDcxlHlGOzzM86UARxs+3sKWRtRPYzBGobD0JOYs0pD1HCOQm+m7sCQQCBn4aThouZsnt51pcecsRn799OjHYnRkiELbWVmOUlspL33KprGGxR+MGrVPAvpRz7S+Qrs8LEubkDnp+Z7ELJAkAxYE4fpxb1xk+l9fh3g3OqUH3Z+ZU3xLTLg477LiuXIXcW7b/X2CO8s9ZxvvAxSpy+9gF0uV4M4jzEKYMNbYp8
250 OK
The available services in detached
GETINFO onions/detached
telnet
GETINFO onions/detached
250+onions/detached=ygwtgk26eauhj4xj
250 OK
Telnet communication is not too complicated, to get the same effect in node.js, we have to use 'net.Socket' and connect to the control port 9051.
import { Socket } from "net";
export const connect = (host: string, port: number) => {
return new Promise<Socket>((resolve) => {
const socket = new Socket();
socket.connect(port, host, () => resolve(socket));
});
}
All operations will be converted to 'Promise' because it will be easier to combine instructions asynchronously.
We need to create a 'sendCommand' function which will return the result of the 'Socket' answer.
export const sendCommand = (socket: Socket, command: string) => {
return new Promise<string>((resolve, reject) => {
socket.once("data", (data: Buffer) => {
const result = data.toString();
if (result.includes("250 OK")) {
resolve(result);
} else {
reject(new Error(result));
}
});
socket.write(`${command}\n`);
});
}
Each correctly executed command returns '250 OK', so we can easily identify the answers and return them as resolve or reject. Also important here, socket returns data only once for each command, so the 'once' method is used, not 'on'.
Once we have 'sendCommand' we can finally program our first command 'AUTHENTICATE'.
export const authenticate = (socket: Socket, password: string) => {
return sendCommand(socket, `AUTHENTICATE "${password}"`);
};
After authorization we can create our first hidden service with the command ADD_ONION
interface AddOnionResponse {
serviceId: string;
keyType?: string;
privateKey?: string;
}
export const addOnion = async (socket: Socket, options: {
keyType?: string;
privateKey?: string;
flags?: string;
port: string;
}): Promise<AddOnionResponse> => {
if (options.privateKey && !options.keyType) {
throw new Error('Set keyType');
}
const keyArgument = options.keyType ? options.privateKey ? `${options.keyType}:${options.privateKey}` : `NEW:${options.keyType}` : 'NEW:BEST';
const flagsArgument = options.flags ? `Flags=${options.flags} ` : '';
const portArgument = `Port=${options.port}`;
const result = await sendCommand(socket, `ADD_ONION ${keyArgument} ${flagsArgument} ${portArgument}`);
const lines = result.split('\r\n');
const serviceId = lines[0].split('250-ServiceID=')[1]; // 250-ServiceID=pismseqxalmmrdgwy6orjwt5xs36bepp4gekudhpgtmclyvc2a6i6sid
if (lines[1].startsWith('250-PrivateKey=')) {
const privateKeyResult = lines[1].split('250-PrivateKey=')[1].split(':'); // 250-PrivateKey=ED25519-V3:wDnhv3eEu5CfBUNZhzOMlEZI9pSZBH9vvvYhJsLMjUcMpzj9HCFmjef52KyfmelhGfN72CaY2RsSqHE60huxtQ==
const keyType = privateKeyResult[0];
const privateKey = privateKeyResult[1];
return {
serviceId,
keyType,
privateKey
}
} else {
return {
serviceId,
keyType: options.keyType,
privateKey: options.privateKey
};
}
}
The function is very powerful. Apart from giving different arguments for the command, we also have to make a basic 'parse' so that we can later use the received answer in the program.
The last command is GETINFO
export const getInfo = (socket: Socket, keyword: string) => {
return sendCommand(socket, `GETINFO ${keyword}`);
}
Using GETINFO
parse
In addition, I leave below an example how to create a detached
const addOnion = async (serviceId: OnionInfo | null) => {
console.log('connect');
const socket = await connect('localhost', 9051);
console.log('authenticate');
await authenticate(socket, 'password');
const onionPort = `80,127.0.0.1:3000`;
console.log(`add_onion: new`);
const result = await addOnion(socket, {
keyType: 'ED25519-V3',
flags: 'Detach',
port: onionPort
});
console.log(`new onion: ${result.serviceId}`);
console.log('getinfo onions/detached');
const onionsDetached = await getInfo(socket, 'onions/detached');
console.log(onionsDetached);
socket.destroy();
}