Site icon Shine Technologies

TLS/SSL tunneling with Node.js

I was recently faced with a bit of a coding challenge whereby I needed to get LDAP authentication working via SSL/TLS using Node. Unfortunately for me Node.js is a relatively new language and a secure LDAP library is still on the wish list. When I was first given this task, I actually didn’t know where to start. I looked into creating a Node wrapper for some of the OpenLDAP libraries written in C. My project team was already using node-ldapauth, which utilizes OpenLDAP behind the scenes, so extending that was a possibility. I felt though that there must be an easier alternative, especially given how powerful node is with I/O. So I decided to implement a kind of TLS/SSL tunnel/port forward solution and use it in conjunction with node-ldapauth. Node v0.4.7 already has a built-in TLS connection library, so it was just a matter of constructing a ‘tunnel’ with a non-secure socket on one end, and a secure socket on the other.

Before trying anything too fancy, I thought I would prove the concept worked with a basic test. To do this I created three small apps:

In order to make this a true SSL connection, I first needed to generate a certificate and a private server key. Thankfully OpenSSL makes this task a breeze:

openssl genrsa -out server.key 1024
openssl req -new -key server.key -out csr.pem
openssl x509 -req -in csr.pem -signkey server.key -out cert.pem

Here is the code for each of the apps:

server.js:
[code lang=”javascript”]
var tls = require(‘tls’), fs = require(‘fs’), sys = require(‘sys’);

var options = {
key: fs.readFileSync(‘server.key’),
cert: fs.readFileSync(‘cert.pem’)
};

sys.puts(“TLS server started.”);

tls.createServer(options, function (socket) {
sys.puts(“TLS connection established”);
socket.addListener(“data”, function (data) {
sys.puts(“Data received: ” + data);
});

socket.pipe(socket);
}).listen(8000);
[/code]

tunnel.js:
[code lang=”javascript”]
var tls = require(‘tls’), fs = require(‘fs’), sys = require(‘sys’), net = require(‘net’);

var options = {
cert: fs.readFileSync(‘cert.pem’),
ca: fs.readFileSync(‘cert.pem’)
};

sys.puts(“Tunnel started.”);
var client = this;

// try to connect to the server
client.socket = tls.connect(8000, options, function() {
if (client.socket.authorized) {
sys.puts(“Auth success, connected to TLS server”);
} else {
//Something may be wrong with your certificates
sys.puts(“Failed to auth TLS connection: “);
sys.puts(client.socket.authorizationError);
}
});

client.socket.addListener(“data”, function (data) {
sys.puts(“Data received from server: ” + data);
});

var server = net.createServer(function (socket) {
socket.addListener(“connect”, function () {
sys.puts(“Connection from ” + socket.remoteAddress);
//sync the file descriptors, so that the socket data structures are the same
client.socket.fd = socket.fd;
//pipe the incoming data from the client directly onto the server
client.socket.pipe(socket);
//and the response from the server back to the client
socket.pipe(client.socket);
});

socket.addListener(“data”, function (data) {
sys.puts(“Data received from client: ” + data);
});

socket.addListener(“close”, function () {
//close the tunnel when the client finishes the connection.
server.close();
});
});

server.listen(7000);
[/code]

client.js:
[code lang=”javascript”]
var net = require(‘net’), sys = require(‘sys’);

var msg = “Hello from net client!”;

client = net.createConnection(7000, function() {
sys.puts(“Sending data: ” + msg);
client.write(msg);
});

client.addListener(“data”, function (data) {
sys.puts(“Received: ” + data);
});[/code]

Make sure the certificate and key files are in the same directory as the server/tunnel/client and then start them up in different terminals in the following order:

  1. node server.js
  2. node tunnel.js
  3. node client.js

If all goes well you should see the text: “Hello from client!” across all terminals.

To get this working with our existing LDAP library, I wrapped the tunnel code in a method ‘connect’ with a callback function, which then invokes the LDAP authenticate code:

[code lang=”javascript”]
tunnel.connect(ldap_port, ldap_host, ssloptions, function() {
ldap.authenticate(tunnel.port, etc…)
}
[/code]

The LDAP authenticate call is configured to point to the tunnel, and the tunnel points to the actual LDAP server on the TLS port (636).

I love how easy this task was made, simply by using Node. It’s a powerful language and I look forward to using it in the future.

Exit mobile version