Restrict Certificate Authority to a Domain

Sun Sep 4, 2016

Many intranets setup a certificate authority (CA) in order to have a good internal SSL/TLS infrastructure. As computers inside the network are managed, to automatically install the CA certificate on all computers is not a problem. Things are different when you have people bringing external computers to work on the internal network, network administrators can request that people install the CA certificate, if they do, they are trusting a lot more than access to encrypted sites inside the internal network, by default a trusted CA can be used to generate certificates for any site, and browsers will accept them. I wanted a way to be able to trust the CA certificate without exposing my encrypted sessions to, for example, a rogue intranet administrator generating certificates for *.google.com.

There is a certificate extension (options that can be embedded on a certificate) that tell encryption libraries for which domains is the CA restricted, it is called Name Constraints.

If you control the CA, you can think about adding the name constraints, but what to do when you are given a CA certificate and you want to restrict it? Create a new CA that cross-certify the original one.

Let’s do one example using OpenSSL tools. First create a new directory to work on, we will do it on /dev/shm/crossca in order to be sure all files are generated on RAM and not stored on disk.

TOPDIR=/dev/shm/crossca
mkdir $TOPDIR
chmod 700 $TOPDIR
cd $TOPDIR

We will need the source certificate in PEM format, let call it ca.crt. Copy it to the work directory

cp /path_to_ca.crt $TOPDIR/ca.crt

Now create a new CA

mkdir -p $TOPDIR/CA/private $TOPDIR/CA/newcerts
touch $TOPDIR/CA/index.txt

openssl req \
  -new -x509 \
  -days 730 \
  -newkey rsa:2048 \
  -sha256 \
  -out ./CA/cacert.pem \
  -keyout CA/private/cakey.pem \
  -subj "/O=Example Organization/CN=Example Root CA"

OpenSSL will ask you for a password, set anything you want and take note of it. You must set a good Organization name (O) and Common name (CN). Play with the amount of days your CA will be valid. I try to set it to a value that generate the same expiration day of our source ca.crt. You can get information about ca.crt with the command: openssl x509 -in ca.crt -text -noout | less

...
        Validity
            Not Before: Jul 22 16:09:17 2016 GMT
            Not After : Jul 22 16:09:17 2036 GMT
...
X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid: xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx

            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Certificate Sign, CRL Sign
            X509v3 Subject Key Identifier:
                yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy:yy
            Authority Information Access:
                OCSP - URI:http://ca.example.com:80/ca/ocsp

It is important to see which extensions are used on the source ca.crt because OpenSSL do not automate copying these extensions to the new certificate.

Now, copy your OpenSSL configuration file, we are going to make changes to it in order to not touch the original file and use directories a non root user can write. openssl version -d can help you locate it:

cp $OPENSSLDIR/openssl.cnf $TOPDIR

Edit the file and replace the lines:

[ CA_default ]
dir = /etc/pki/CA    # Where everything is kept

with

[ CA_default ]
dir = ./CA           # Where everything is kept

Create a file named $TOPDIR/ossl_domain_com.cfg with the following content:

[ req ]
prompt=no
distinguished_name=req_distinguished_name
req_extensions=domain_ca

[ req_distinguished_name ]
O=Example Organization
CN=Original Certificate CN

[ domain_ca ]
authorityKeyIdentifier=keyid
basicConstraints=critical,CA:true
keyUsage=critical,digitalSignature,nonRepudiation,keyCertSign,cRLSign
subjectKeyIdentifier=hash
authorityInfoAccess=OCSP;URI:http://ca.example.com:80/ca/ocsp
nameConstraints=critical,permitted;DNS:.example.com

Adapt the file to your needs, try to copy the information of the source ca.crt on [ req_distinguished_name ] and set all the extensions found of ca.crt on [ domain_ca ]. Read OpenSSL documentation if you need help. The important new extension is nameConstraints=critical,permitted;DNS:.example.com that restrict the CA to the domain name example.com

Now create the cross-certification:

openssl ca \
  -config openssl.cnf \
  -create_serial \
  -preserveDN \
  -extensions domain_ca \
  -extfile ossl_domain_com.cfg \
  -ss_cert ca.crt \
  -startdate 160722160917Z \
  -enddate 360722160917Z \
  -out crosscertified_ca.crt

Again, play with the start and end date to your liking.

The new cross-certified CA is now in the file crosscertified_ca.crt. Import it to your browser and it should be able to be used to trust servers only from the example.com domain.

Note: Firefox support name constraints on all platforms, Chrome support it on Linux and Windows but not on macOS because it uses the platform SSL libraries and as always macOS is behind the times.


Next: »