Sunday, June 10, 2012

Securing Node.js and Express with SSL Client-Authentication

In the course of my work using Node.js, I did some research on implementing securing Node.js.  There are a couple of decent articles on the subject, and of course a number of frameworks that performed this work, but nothing I read or evaluated really quite fit my needs:
  • Use strong authentication.
    In my case, I preferred certificate-based (SSL).
  • Remain as unobtrusive as possible.  
    I didn't want security code in my routers or generally entangled in the rest of my application.
  • Be Flexible.
    Let me determine how and when users could see content or perform actions.
I know it's sacrilege to say it, but I really was looking for a capability similar to Spring Security or J2EE container security.  I wanted to constrain access by route, apply arbitrary access rules, and utilize ACL's which could be used to restrict content, or constrain access to data when queries are performed against external data sources.

In this post, I will demonstrate how to setup SSL authentication in Node.js.  

In following posts, I will show you how to write Connect "middleware" components to constrain access by route and enhance user identity with roles (enabling ACLs). 

Enabling SSL Authentication in Node.js

The first step to enabling SSL Authentication is to have the necessary Public-Key Infrastructure (PKI) to generate both server and client certificates, as well as, establishing trust between those certificates by deriving the certificates from a Certificate Authority.  I discuss this topic in detail in the previous post, and will even references the certificate paths used in that discussion.

Once you have the certificates in place, the code to actually enable SSL Authentication in Node.js is actually pretty easy:
var express = require('express')
  , routes = require('./routes')
  , fs = require('fs')

// MAGIC HAPPENS HERE!
var opts = {
  
  // Specify the key file for the server
  key: fs.readFileSync('ssl/server/keys/server1.key'),
  
  // Specify the certificate file
  cert: fs.readFileSync('ssl/server/certificates/server1.crt'),
  
  // Specify the Certificate Authority certificate
  ca: fs.readFileSync('ssl/ca/ca.crt'),
  
  // This is where the magic happens in Node.  All previous
  // steps simply setup SSL (except the CA).  By requesting
  // the client provide a certificate, we are essentially
  // authenticating the user.
  requestCert: true,
  
  // If specified as "true", no unauthenticated traffic
  // will make it to the route specified.
  rejectUnauthorized: true
};

var app = module.exports = express.createServer(opts);

// Configuration 

app.configure(function(){
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
  app.use(express.errorHandler(
    { dumpExceptions: true, showStack: true })); 
});

app.configure('production', function(){
  app.use(express.errorHandler()); 
});

// Routes
app.get('/', routes.index);

app.listen(8443);

console.log(
   "Express server listening on port %d in %s mode", 
    8443, 
    app.settings.env);
With the exception of the "options" object containing the SSL configuration, the application is just an Express.js application.

Let's start up the application...

When you run the application you will get the "Enter PEM pass phrase:" request before the web server will start.  The PEM is the password you used when creating the Server's key.  This is actually a pretty nice feature when you think about it, since you don't have to submit it when launching the application (making it visible when you use the ps command in POSIX systems).

If you are really not that concerned about security, you can specify the passphrase in the Server options:

var opts = {
  key: fs.readFileSync('ssl/server/keys/server1.key'),
  cert: fs.readFileSync('ssl/server/certificates/server1.crt'),
  ca: fs.readFileSync('ssl/ca/ca.crt'),
  requestCert: true,
  rejectUnauthorized: true,
  
  // This is the password used when generating the server's key
  // that was used to create the server's certificate.
  // And no, I do not use "password" as a password.
  passphrase: "password"
};
No when you start the server, it will not harass you for a password.

Now when a user comes to the website without a certificate, the will get the following "nasty gram":



As opposed to the original route they intended to hit (in this case the "index" route).

Pretty awesome, huh?

If you don't need to deal with users of varying levels of access (administrators, moderaters, etc.), and don't have any anonymous users, you're done.  However, if you want to show a nice "Unauthorized" web page, we will need to change our configuration a little bit.


Allowing Anonymous Access with Custom Unauthorized Pages


One of the settings in our configuration is preventing the unauthenticated browser (Firefox) from reaching our web server.  This may be okay in certain circumstances, but in general, it may be smarter to let the user know the web server is functioning correctly, it's just that they aren't privileged enough to view your content.

To allow unauthenticated browsers through, simply turn the "rejectUnauthorized" property in the options object to "false":
var opts = {
  key: fs.readFileSync('ssl/server/keys/server1.key'),
  cert: fs.readFileSync('ssl/server/certificates/server1.crt'),
  ca: fs.readFileSync('ssl/ca/ca.crt'),
  requestCert: true,

  // Now every one can get through to the web server.
  rejectUnauthorized: false,
};
Now all users can hit the website. Authentication still occurs, but unauthenticated users are given access to everything that an authenticated user gets.  This is obviously not what we want, but it's necessary to at least provide an "Unauthorized Access" webpage to that unauthenticated user.  Since we turned off Node's native way of restricting access, it's our responsibility to control that access in our application.

The next snippet of code demonstrates how to determine whether a user was authenticated and what you can potentially do with this knowledge.  In this example, I will render different content based on this condition.  If the user is unauthenticated, I will render the "Unauthorized" template.  If the user is authorized, I will render an "Authorized" template displaying the user's certificate information.
app.get('/', function(req, res){

  // AUTHORIZED 
  if(req.client.authorized){

    var subject = req.connection
      .getPeerCertificate().subject;
    
    // Render the authorized template, providing
    // the user information found on the certificate
    res.render('authorized', 
      { title:        'Authorized!',
     user:         subject.CN,
     email:        subject.emailAddress,
     organization: subject.O,
     unit:         subject.OU,
     location:     subject.L,
     state:        subject.ST,
     country:      subject.C
   }); 
 
  // NOT AUTHORIZED
  } else {
 
 // Render the unauthorized template.
    res.render('unauthorized', 
  { title: 'Unauthorized!' }); 
  }
});
OK, I've written a little security logic in a route (which I already mentioned was evil) to demonstrate how you can gain access to the authenticated user, as well as, what you can do when based on the authorization status.

I won't show you the Jade templates I used (they really simple; if you want a copy, leave me a comment), but I will show you the results:

Unauthorized Template Rendered
Authorized Template Rendered

That's it for Part 1.  In the next post, I will show you how to create Connect middleware to better separate this security logic from your routes.

Until next time, good luck.

Saturday, June 9, 2012

Automating the Creation of a Certificate Authority and Issuing Certificates with OpenSSL

I spent a little time over the weekend working on securing an Node.js server with SSL client-authentication.  I needed to create a certificate authority from which I could create and sign both server and client certificates for use by my application.

While the process is not terribly difficult, there is a lot to know (and remember), I decided to automate the it a little bit and thought others might be interested.  I should mention up front that this website (full link below) was especially helpful in understanding the process.

I will start by explaining the process of creating a CA and issuing certificates.  Once you understand the process, I will describe how you can customize some of that process using OpenSSL config files.  I will conclude with describing some automation steps that can be performed to make this process easier.

I've created a public Github repo with the scripts mentioned in this post:  https://github.com/berico-rclayton/certificate-automation/.

Creating a Certificate Authority


Each step will require some input (the commands are interactive).  You will set the CA's password in the first step, and this password will be use in many of the other steps (including signing certificates for servers and clients).  Needless to say, remember this password (but keep it safe -- this is quite literally the keys to the castle).

1.  Create a key for the certificate authority.
openssl genrsa -des3 -out ca.key 1024
2.  Create a certificate request for the authority:
openssl req -new -key ca.key -out ca.csr
3.  Create a certificate for the CA, by signing (self-sign) the certificate with the CA's own key:
openssl x509 -req -days 365 -in ca.csr \
    -out ca.crt -signkey ca.key

Create a Server Certificate


1.  Create key for the server.
openssl genrsa -des3 -out server.key 1024
2.  Create a request for certificate.
openssl req -new -key server.key -out server.csr
3.  Sign (CA) and issue the certificate.
openssl x509 -req -in server.csr -out server.crt \
    -CA ca.crt -CAkey ca.key -CAcreateserial -days 365

Create a Client Certificate


1.  Create key for the client.
openssl genrsa -des3 -out user.key 1024
2.  Create a request for certificate.
openssl req -new -key user.key -out user.csr
3.  Sign (CA) and issue the certificate.
openssl x509 -req -in user.csr -out user.crt \
     -CA ca.crt -CAkey ca.key -CAcreateserial -days 365
4.  Create a PKCS#12 formatted certificate for use in browsers like Firefox.
openssl pkcs12 -export -clcerts -in user.crt \
    -inkey user.key -out user.p12

Customizing OpenSSL Configuration


If you execute these commands, you will soon discover how annoyingly interactive the process is (lots of stuff to input just to create a certificate).  Fortunately, the CA process is a one time deal (well as long as you issued your own CA certificate).  Generating a server certificate will also probably be a relatively infrequent process.  It's the issuing of client certificates that becomes a real pain in the ass, which is why I automated this process.

There is an out-of-the-box way to automate a little bit of this work using custom OpenSSL configuration files.

1.  In the directory of your choice, create a text file called "openssl.cnf".
2.  Add configuration values to the newly created file.

Copy and paste from here: https://github.com/berico-rclayton/certificate-automation/blob/master/conf/server_openssl.cnf
 
# ... truncated ...

[ req_distinguished_name ]
0.organizationName = Organization Name (company)
organizationalUnitName  = Organizational Unit Name (department, division)
emailAddress  = Email Address
emailAddress_max = 40
localityName  = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName  = Country Name (2 letter code)
countryName_min  = 2
countryName_max  = 2
commonName  = Common Name (Computer Name, Server, or Username)
commonName_max  = 64
 
0.organizationName_default = Berico Technologies
organizationalUnitName_default  = Engineering BU
localityName_default  = Reston
stateOrProvinceName_default = Virginia
countryName_default  = US
emailAddress_default  = server@bericotechnologies.com
commonName_default  = server
 
# ... truncated ...
3.  Edit the values (right-hand side of "=" sign) of the variables suffixed with "_default" to change the default values displayed in the OpenSSL commands.  There are also a number of other important variables like default "key length" (we've been using 1024 bits in this example) that you can also set.

4.  Ensure OpenSSL uses your new config file.
export OPENSSL_CONF=openssl.cnf

Automating the Process


We've gotten as far as we're going to get with configuration, so lets use a little BASH magic to automate some of this process.  The first thing we will do is organize our directory structure instead of writing all files to the current working directory as I have done in the previous examples.  I'm going to use a directory structure almost identical to the one suggested by the website listed above (and below):
  • ca/ - holds certificate authority's certificate, certificate request, and key.
  • server/keys/ - holds all keys generated for servers.
  • server/requests/ - holds all certificate creation requests to the CA for servers.
  • server/certificates/ - holds all certificates for servers.
  • user/keys/ - holds all keys generated for users.
  • user/requests/ - holds all certificate creation requests to the CA for users.
  • user/certificates/ - holds all certificates for users.
  • user/p12/ - holds all PKCS#12 formatted certificates for users.
By the way, make sure you protect these folders judiciously (owned and accessed by root only).

I've created three BASH scripts for automating this process:

1.  create_certauth.sh - creates the directory structure and Certificate Authority's key and self-signed certificate.  This will probably only be executed once for an organization.
sh setup_certauth.sh
2.  make_server_cert.sh - creates a certificate for a server.  You will need to specify the server name, which will be used as the file name for all artifacts.
sh make_server_cert.sh {server-name}
3.  new_client_cert.sh - creates a certificate for a user, along with the PKCS#12 certificate, which is used by some browsers.  You will need to specify the client's name, which is used as the file name for all artifacts.
sh new_client_cert.sh {client-name}

Screenshot of the client certificate creation process.


Viola.  I should mention that I am not a BASH Ninja, so if you have some suggestions, I would love to hear them.  The scripts in the Github repo will use the directory structure to gather the CA certificate, as well as, store the keys, requests, and certs.

Hope this was useful; if not, let me know.

References:


Special Thanks to the Author of this Guide:  http://www.cafesoft.com/products/cams/ps/docs30/admin/ConfiguringApache2ForSSLTLSMutualAuthentication.html#Creating_a_Certificate_Authority_using_OpenSSL