Skip to content

Instantly share code, notes, and snippets.

@pcan
Last active July 7, 2024 01:28
Show Gist options
  • Save pcan/e384fcad2a83e3ce20f9a4c33f4a13ae to your computer and use it in GitHub Desktop.
Save pcan/e384fcad2a83e3ce20f9a4c33f4a13ae to your computer and use it in GitHub Desktop.
Node.js plain TLS Client & Server, 2-way Cert Auth

Node.js TLS plain TLS sockets

This guide shows how to set up a bidirectional client/server authentication for plain TLS sockets.

Newer versions of openssl are stricter about certificate purposes. Use extensions accordingly.

Prepare certificates

Generate a Certificate Authority:

openssl req -new -x509 -days 9999 -keyout ca-key.pem -out ca-crt.pem
  • Insert a CA Password
  • Specify a CA Common Name, like 'root.localhost' or 'ca.localhost'. This MUST be different from both server and client CN.

Server certificate

Generate Server Key:

openssl genrsa -out server-key.pem 4096

Generate Server certificate signing request:

openssl req -new -key server-key.pem -out server-csr.pem
  • Specify server Common Name, like 'localhost' or 'server.localhost'. The client will verify this, so make sure you have a vaild DNS name for this.
  • For this example, do not insert the challenge password.

Sign certificate using the CA:

openssl x509 -req -days 9999 -in server-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out server-crt.pem
  • insert CA Password

Verify server certificate:

openssl verify -CAfile ca-crt.pem server-crt.pem

Client certificate

Generate Client Key:

openssl genrsa -out client1-key.pem 4096

Generate Client certificate signing request:

openssl req -new -key client1-key.pem -out client1-csr.pem
  • Specify client Common Name, like 'client.localhost'. Server should not verify this, since it should not do reverse-dns lookup.
  • For this example, do not insert the challenge password.

Sign certificate using the CA:

openssl x509 -req -days 9999 -in client1-csr.pem -CA ca-crt.pem -CAkey ca-key.pem -CAcreateserial -out client1-crt.pem
  • insert CA Password

Verify client certificate:

openssl verify -CAfile ca-crt.pem client1-crt.pem

Server code

const tls = require('tls');
const fs = require('fs');

const options = { 
    key: fs.readFileSync('server-key.pem'), 
    cert: fs.readFileSync('server-crt.pem'), 
    ca: fs.readFileSync('ca-crt.pem'), 
    requestCert: true, 
    rejectUnauthorized: true
}; 

const server = tls.createServer(options, (socket) => {
    console.log('server connected', 
        socket.authorized ? 'authorized' : 'unauthorized');
    
    socket.on('error', (error) => {
        console.log(error);
    });
    
    socket.write('welcome!\n');
    socket.setEncoding('utf8');
    socket.pipe(process.stdout);
    socket.pipe(socket);
});

server.listen(8000, () => {
    console.log('server bound');
});

Client code

const tls = require('tls');
const fs = require('fs');

const options = {
    ca: fs.readFileSync('ca-crt.pem'),
    key: fs.readFileSync('client1-key.pem'),
    cert: fs.readFileSync('client1-crt.pem'),
    host: 'server.localhost',
    port: 8000,
    rejectUnauthorized:true,
    requestCert:true
};

const socket = tls.connect(options, () => {
    console.log('client connected', 
        socket.authorized ? 'authorized' : 'unauthorized');
    process.stdin.pipe(socket);
    process.stdin.resume();
});

socket.setEncoding('utf8');

socket.on('data', (data) => {
    console.log(data);
});

socket.on('error', (error) => {
    console.log(error);
});

socket.on('end', (data) => {
    console.log('Socket end event');
});

Credits

See the original post by Anders Brownworth.

Thanks to this StackOverflow answer, too (I was using same CN for CA, Server and Client and I got the DEPTH_ZERO_SELF_SIGNED_CERT error).

@pcan
Copy link
Author

pcan commented Oct 7, 2020

Actually I don't think you can use TLS socket at all in the browser, while the fs thing is the minor issue (I was just using it to read the PEM files content, that are just plaintext files). Just curious, what is your use case here?

@zhamidi
Copy link

zhamidi commented Oct 12, 2020

I wanted to use it for a secure key exchange, to mostly authenticate the client. Hence why wanted to use that on the browser side.

@orefalo
Copy link

orefalo commented May 16, 2022

Lost, your description says "This MUST be different from both server and client CN." therefore two ca(s), one for each - the client and the service.

Then in your scripts, you only use one

@pcan
Copy link
Author

pcan commented May 16, 2022

@orefalo where exactly in the scripts? I was referring to generation not usage (you never need to reference the CA name anywhere in the code). Please advise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment