Deploying a VPN with PKI

This article is the second of two focusing on tunneling VPNs
and Public Key Infrastructure (PKI). The first article provided a simple overview of
the technology for people without a deep computer security background. This article
is a walk-through tutorial that implements the concepts covered previously. We
assume that the reader has some knowledge of VPN and digital certificate basics
and wants to see exactly what it takes in practice to extend a private
network by deploying a secure VPN. This tutorial presents code examples that
will require novice- to intermediate-level system administration skills
to reproduce, but anyone should be able to follow along with the discussion.

The tutorial implements a certificate-based security infrastructure using
OpenSSL and uses this to secure both OpenVPN client and server endpoints. We
will highlight two great new features to appear in OpenVPN-2.0 (now in beta)
that will make it a good choice for any VPN--single-instance server mode
and certificate revocation list support.

Deploying a VPN and Public Key Infrastructure

In part one of this series, we covered VPN
and PKI at a conceptual level. We discussed the
benefits that VPN technology offers to a business network and touched on three
different techniques or protocols that can actually implement a VPN tunnel.
These were PPTP, L2TP/IPSec, and the relative newcomer, OpenVPN. An
advantage of OpenVPN is that it encapsulates network traffic inside of UDP or TCP
packets, which improves the odds of it working when deploying a VPN across
unknown third-party network equipment and the Internet as a whole.

The previous article also talked about PKI and loosely defined the term as a system for issuing, publishing,
and revoking digital certificates. These digital certificates are simple data
structures containing, at the minimum, a subject's name, his public key, and the
name of an issuer who has verified the subject's identity. When the issuer
digitally signs the certificate and attaches the signature, the data structure
becomes a digital certificate. The public key inside of the certificate can
encrypt any message, producing a message decipherable only by the named subject
in the certificate using the closely guarded private key.

For our step-by-step description of establishing connections across a VPN
tunnel, secured with PKI, we will focus on two separate software packages,
OpenVPN-2.0 (beta 8) and OpenSSL 0.9.7d. This version of OpenSSL was the latest
stable version of the package at the time of writing. The OpenVPN beta version
includes a couple of new features that really make it quite acceptable for a
business VPN, namely, Certificate Revocation List (CRL) support and
single-instance server mode. We'll use a generic GNU/Linux system to host both
the PKI and VPN server endpoints. Our sample client configuration will be
platform-neutral, meaning that the same client configuration should work on any
supported OS, including Linux and the BSDs, Solaris, Mac, and Windows. Setup
and operation for both OpenSSL and OpenVPN is similar on any supported
platform.

First, we will configure OpenSSL to act as our root Certificate
Authority (CA) by creating a self-signed certificate that will sit at the top of
our trust hierarchy. Then, we will create certificate requests for our clients
and actually issue certificates. Next, we will use OpenSSL's built-in test
framework to make sure everything works smoothly before we complicate things by
throwing a VPN into the mix. After running the OpenSSL test framework, we will
configure both the OpenVPN server and client endpoints. Finally, we will
demonstrate a CRL by revoking a user certificate, generating a CRL containing
the revoked certificate, and effectively terminating the user's access to our
VPN.

OpenSSL

OpenSSL is primarily a library of cryptographic functions that provides an
extensive crypto API to programmers. However, it also includes a shell tool
that exposes that API to users and batch scripts. Start the shell by typing
openssl at the command line. From there, you can type commands
at the OpenSSL> prompt.

Of the many commands provided with the OpenSSL shell, this tutorial will
only cover five:

ca: Certificate Authority management

req: Certificate request management

verify: Certificate verification

s_server: Secure server test mode

s_client: Secure client test mode

The OpenSSL Configuration File

When installing an OpenSSL Certificate Authority, we need to provide a
master configuration file. This file contains default parameters for all
OpenSSL commands. Compiling the library hard-codes its default location; its
name is openssl.cnf. To find the default location of this file, use
the version command with the -d option:

Once you know where to look, edit this file with a text editor. The OpenSSL
distribution includes a heavily commented example configuration file, but it's
more complex than the simplified version we will work with in this tutorial.
Hopefully, by paring the configuration down to a minimum, we will simplify our
deployment without sacrificing security. Specifically, our configuration file
removes many of the certificate extension definitions that appear in the
prepackaged OpenSSL configuration.

It's probably a good time to mention these extensions. Part one of this
series listed the minimum amount of information that must appear in a
digital certificate: a subject name, the subject's public key, and the name of
an issuer. Certificates that limit themselves to this minimum amount of
information and conform to the X.509 encoding format are X.509v1 (version 1)
certificates. Presently, the current standard specifies additional information
that can appear in the certificate to increase security and aid applications
that work with them. Most certificates used with Internet-enabled applications
currently conform to the X.509v3
(version 3) specification and include some extensions. OpenSSL can produce
either v1 or v3 certificates depending on the configuration file settings.

In this tutorial, we will configure OpenSSL to produce v3 certificates and
include the basicConstraints extension to limit how
people can use the certificates that we issue, as this is the recommended
configuration. Here is the configuration file that we will use:

# openssl.cnf
#
# OpenSSL example configuration file.
#
# This configuration file has been derived from the original
# example file included with the OpenSSL distribution. It
# has been edited mostly to eliminate extensions in order to
# simplify it for the purpose of an online tutorial. It has
# also been reformatted to limit the maximum line length to
# 60 characters for online publication.
############################################################
# This section will configure the ca (Certificate Authority)
# command. We will use the ca command to sign user
# certificates and periodically generate CRLs.
############################################################
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
dir = /home/admin/CA-DB # Top
crl_dir = $dir/crl # The crl location
database = $dir/index.txt # Database index file
new_certs_dir = $dir/newcerts # Location for new certs
certificate = $dir/cacert.pem # The CA certificate
serial = $dir/serial # The next serial number
crl = $dir/crl.pem # The current CRL
# All DNs need to be unique. This is the default behavior
# but due to an OpenSSL library bug in the 0.9.7d release,
# if we don't supply this redundant definition here we will
# see a cryptic message when signing certificates.
unique_subject = yes
# The CA private key
private_key = $dir/private/cakey.pem
# Private random number file
RANDFILE = $dir/private/.rand
# Issued certificates will be valid for 1 year
default_days = 365
default_crl_days= 30
# Hashing function
default_md = md5
# This assignment causes the extensions defined in the
# 'user_extensions' section to be included in any
# certificates that are signed using the ca command.
x509_extensions = user_extensions
# This sections describes the policy that will be enforced
# on a request to be signed, the subject organization name
# must match that in the CA certificate. The request may
# contain an optional organizational unit name. The common
# name is assigned the policy format 'supplied' which means
# it must be present in the certificate request.
policy = policy_any
[ policy_any ]
organizationName = match
organizationalUnitName = optional
commonName = supplied
############################################################
# This section configures the req (certificate request)
# command.
############################################################
[ req ]
# The default key length and the filename that will contain
# a private key. The public key will be contained in the
# certificate request.
default_bits = 1024
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
# The makeup of our subject name
[ req_distinguished_name ]
organizationName = Organization Name (eg, company)
organizationName_default = Inyo Technical Services
organizationalUnitName = Organizational Unit (eg, west)
commonName = Common Name (eg, YOUR name)
commonName_max = 64
# This assignment mimics that in the ca section but causes
# the following extensions to be included in a certificate
# request. In our case these extensions will only be
# included when we self-sign a certificate using 'req
# -new -x509'.
x509_extensions = CA_extensions
############################################################
# Extensions that will be added to certificates that are
# issued. The 'user_extensions' sections contains
# definitions that will be included in certificates that are
# signed by this CA. The CA_extensions section contains
# extensions that will be included when we create a
# self-signed certificate using the req command.
############################################################
[ user_extensions ]
# CA:FALSE will not permit this certificate to sign other
# certificates.
basicConstraints = CA:FALSE
[ CA_extensions ]
# CA:TRUE will allow this certificate to sign others.
basicConstraints = CA:TRUE

There are quite a few things to note about this file. First, note that
comment blocks divide it into logical sections. In this sample, those sections
are [ ca ], the configuration for the ca (Certificate Authority) command; [ req ], the section containing
items to include in a certificate request with the req command;
and finally, a section defining some extensions. We only include one extension
and assign it two different values depending on the certificate that we are
creating.

Our self-signed CA certificate will include the extension
basicConstraints = CA:TRUE. This allows a Certificate Authority to
use the certificate to sign other certificates. When we issue certificates to
users, we will use the extension basicConstraints = CA:FALSE,
denying users the ability to create subordinate certificates and thereby extend the
chain of trust beyond our control.

The start of the [ ca ] section defines the directory
hierarchy. This lets the OpenSSL library know where to find important files,
and the definitions here should be clear. The assignment x509_extensions
= user_extensions adds the extensions defined in the [
user_extensions ] section to any certificates signed by this CA. As
explained before, the only extension we will include is
basicConstraints, which was explained previously. Next come the
definitions of the certificate request subject's name-matching policy and
Distinguished Name structure.

The [ policy ] section defines the Distinguished Name
components to allow in certificate requests. To understand this, we need to
describe the encoding of subject names in X.509 certificates.

X.509 certificates encode subject names in a structure called a
Distinguished Name (DN)--a sequence of name components called
Relative Distinguished Names (RDNs). This naming structure comes from
a complex specification for methods and syntaxes defining communication between
computer programs as seen in RFC
1274, which gives the attribute definitions for the DN components that we
will use in our certificates. The [ policy ] section specifies
which RDNs or DN components must appear in a certificate
request and which of these components must match the subject DN
in the CA certificate prior to signing. Here, we have configured our CA to
encode a subject's DN as follows:

The subject name must contain an organizationName
part that matches our CA certificate exactly--match.

The subject name may contain an optional and arbitrary
organizationalUnitName--optional.

The subject name must contain an arbitrary commonName
component--supplied.

In our application, we will try to be consistent and issue user certificates
with a commonName component that matches the user's login ID, but
OpenSSL will not enforce this.

The next section, labeled [ req ], contains default configuration
values for the req command. We will use this command to create
certificate requests. The default_bits and
default_keyfile definitions set the length of the private key and
its location in the file system. More interesting here is the [
req_distinguished_name ] section, which defines default values for the
RDN components that will make up our subject's Distinguished Name. Here we
define the prompts that will appear at the terminal when we execute the
req command. We assign a default value to our
organizationName: Inyo Technical Services.
Finally, the assignment x509_extensions = CA_extensions includes
the extension defined in the [ CA_extensions ] section in a
certificate request.

This extension assignment is similar to that in the [ ca ]
section. The difference here is that these extensions will appear only in
certificate requests, while extensions in the [ user_extensions ]
section will appear in a certificate signed by our CA. By default, if an
extension is present in a certificate request but does not appear in the
[ user_extensions ] section, then the extension will not
be present in the issued certificate. The extension basicConstraints =
CA:TRUE will only appear in the certificate when we use the
req command with the -x509 option to create a
self-signed root certificate.