Adding your own EV-SSL Root CA to Firefox
By Sid Stamm (sid at mozilla dot com)

Extended Validation SSL certificates (EV certificates) have been proposed by the Certification Authority/Browser Forum and widely implemented with the cooperation of many SSL certificate authorities. These new certificates are more difficult for an entity to obtain, requiring more proof of identity on the requester's part in the hopes of minimizing the risk that a phisher or other type of attacker could obtain a certificate for a domain they do not legitimately control.

This document will explain how to create an EV-SSL certificate authority and install the appropriate root CA certificate into Firefox so that you can issue EV certificates. NOTE: this only works in debug builds, and you must compile your own copy of Firefox to modify the root CAs accepted for EV. Basically, you can't screw with existing binaries' lists of EV certificates.

Creating an EV-SSL CA

Extended Validation requirements make it a bit more difficult to create an EV certificate authority and issue EV certificates--especially with the requirement that there be "fresh" revocation information available for EV certs, all the time. Usually this means that you have to set up and maintain an OCSP responder (RFC 2560), but depending on the implementation of EV, this might not be the case. It may be possible for you to set up an OCSP responder using OpenSSL, but that is beyond the scope of this document. You may also get by with a CRL that is updated every 7 days.

Requirements for an EV CA

While the precise requirements for a CA certificate are not easy to discern, they are documented fairly well by the CABForum in their v1.1 guidelines document, particularly in Appendix B, "EV Certificates Required Certificate Extensions". I was able to create a Root CA that issued EV certs with the following extensions:

In addition, the subject information was filled out as completely as possible.

Creating the CA Certificate and keys

OpenSSL comes with a CA.pl script that can be used to create a certificate authority. Once you've created and configured an openssl.cnf file (or you can use mine), modify CA.pl to point to your config file (insert config directive into line 50: $REQ="$openssl req -config ./openssl.cnf $SSLEAY_CONFIG") and follow the instructions:

[prompt]% ./CA.pl -newca<ret>
CA certificate filename (or enter to create)
<ret>
Making CA certificate ...
Generating a 2048 bit RSA private key
..............+++
........................................................................+++
writing new private key to './demoCA/private/cakey.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [US]: <ret> 
State or Province Name (full name) [California]: <ret> 
Locality Name (eg, city) [Mountain View]: <ret>
Organization Name (eg, company) []: Mozilla Corporation Demo Cert<ret>
Organizational Unit Name (eg, section) []: Demo EV CA<ret>
Business Category (For ev, V1.0, Clause 5.(d))[]: V1.0, Clause 5.(d)<ret>
Common Name (eg, YOUR name) [mytestcadomain.local]: <ret>
Email Address []: admin@mytestcadomain.local<ret>
[prompt]% 
  
This will create a demoCA directory, and the CA cert is inside there: demoCA/cacert.pem. The RSA private key is in demoCA/private/cakey.pem.

Choosing a Policy OID

A CA is not able to issue EV certificates unless they can attach a policy OID to each EV cert issued stating that the new cert is EV. In order to make sure the CA can issue EV certs, pick an OID (See some existing ones) and you might want to clear it with the owner of that OID tree (unless of course you just want to use it for testing). ALL EV CERTIFICATES issued must carry the Policy OID that you choose, and the browsers must expect your CA to embed it on EV certs.

The Policy OID can be either added to new certificate requests, or it can be attached by the CA when the EV certificat is issued. Regardless, the Policy OID must end up in the final EV certificate. I attached it to the certificate request, and copied all extensions (since the request was also going to contain an arbitrary trustList extension). The OpenSSL Config file section looks like this:

[ new_oids ]
...
trustList   = 2.16.840.1.113730.1.900
...

[ cert_request ]

trustList = ASN1:UTF8String:https://mytestdomain.local/EVTrustList.etl
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
crlDistributionPoints=URI:http://mytestdomain.local/my.crl
certificatePolicies=@v3_req_ev_cp

[ v3_req_ev_cp ]
policyIdentifier = 1.2.1.2.1.2.1.3.3.7
CPS.1="http://mytestdomain.local/cps";
  
All EV certs using EVSSL Trust must define the trustList OID to be the same; this will be recognized across implementations. The policyIdentifier is an arbitrary policy OID that simply means "my CA's EV policy". That varies across EV authorities.

Jurisdiction Data

To make life even more complex, EV certificates must state their jurisdiction to satisfy Clause 5 of the CABForum document (business category). For that, there must be the following definitions in the Subject field (and respectively, the OIDs):

[ new_oids ] 
...
# for EV distinguished name --- SHOULD be present in EV certs
businessCategory = 2.5.4.15
jurisdictionOfIncorporationLocalityName = 1.3.6.1.4.1.311.60.2.1.1
jurisdictionOfIncorporationStateOrProvinceName = 1.3.6.1.4.1.311.60.2.1.2
jurisdictionOfIncorporationCountryName = 1.3.6.1.4.1.311.60.2.1.3
...

[ the_ca_policy ]
...
businessCategory = supplied
jurisdictionOfIncorporationCountryName = supplied
jurisdictionOfIncorporationStateOrProvinceName = supplied
jurisdictionOfIncorporationLocalityName = supplied
...

[ request_distinguished_name ]
...

# OID 2.5.4.15
businessCategory = Business Category (For example, 'V1.0, Clause 5.(c)')

# OID 1.3.6.1.4.1.311.60.2.1.1
jurisdictionOfIncorporationLocalityName = Inc. Locality

# OID 1.3.6.1.4.1.311.60.2.1.2
jurisdictionOfIncorporationStateOrProvinceName = Inc. State/Province

# OID 1.3.6.1.4.1.311.60.2.1.3
jurisdictionOfIncorporationCountryName = Inc. Country
  

The Whole CA/Cert generation process

Here's everything needed to generate the CA and an EV certificate:

#!/bin/bash

# 1. create CA
CA.pl -newca

# 2. create request
openssl req -config ./openssl.cnf -new -keyout newkey.pem \
            -out newreq.pem -days 30

# 3. sign certificate 
openssl ca -config ./openssl.cnf -policy policy_anything \
           -out newcert.pem -infiles newreq.pem

# 4. strip passphrase from private key (for apache, very insecure) 
openssl rsa -in newkey.pem -out newkey-nopassphrase.pem

# 5. install into webserver
sudo cp newkey-nopassphrase.pem /etc/apache2/ssl.key/mytestdomain.key
sudo cp newcert.pem /etc/apache2/ssl.key/mytestdomain.crt
sudo restartApache2
  
My sample openssl.cnf file is a good place to start. It's not clean, but it works.

Adding the EV CA to Firefox

In order to test out your very own EV certificates and CA, the CA must first be recognized by a browser as an EV CA (and the browser must be made aware of the policy OID). For safety, this is not easily done in Firefox, and can only be done on a custom build or a debugging build of the browser. I'll describe how to custom-build Firefox to listen for your EV CA's policy OID, and additionally how to patch it so you don't need a fresh CRL or OCSP responder to get EV status.

  1. Obtain the Firefox Source
  2. Enable Debug Building
  3. Patch the source
  4. Build NSS tools
  5. Install your own CA and EV data
  6. profit!

Get the Firefox Source

Detailed information on obtaining Firefox source is here. Be sure you have the build tools all set up and ready to go (fairly standard stuff) on Mac, Linux, or Windows. Assume the source is checked out to the directory $SOURCE.

Enable Debugging Build

Set up your ~/.mozconfig like this:

mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff
ac_add_options --disable-libxul
ac_add_options --enable-debug --disable-optimize
ac_add_options --enable-shared --disable-static
mk_add_options MOZ_MAKE_FLAGS="-s -j4"
mk_add_options AUTOCONF=autoconf213
  
And if you're on a mac, add this line too:
ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.4u.sdk
  

Patch the Source

Next, Firefox must be told to ignore the lack of up-to-date CRL or OCSP responder. Apply this patch to the code. (hg patch fresheness.patch)

Also, it is necessary to modify a tool (in NSS) to give us some encoded information about our CA. To do that, apply this patch to the code.

Once you've got the patches installed, build Firefox with make -f client.mk build. It will take a while.

Build the NSS tools

When Firefox has been built, you want to build the NSS tools. Decend into $SOURCE/security/nss and issue make nss_build_all.

Once built, test out the pp command by going into $SOURCE/dist/[target]/bin and running pp. If you get an error about library issues, add DLYD_LIBRARY_PATH=$SOURCE/dist/[target]/lib to your environment.

Install your own CA and EV data

Next, install your own CA. To do this, run Firefox (I suggest using a separate profile for testing), and locate the certificate information (Preferences - Advanced - Encryption - View Certificates). Click the Authorites tab and import the cacert.pem file. Trust it for everyting.

Once the certificate is installed, quit Firefox. The base64-encoded DER values for the root CA's serial and subject are needed. To get those values, go back to the $SOURCE/dist/[target]/bin directory and run pp on your cert:

./pp -a -x -t certificate < $PATH_TO_CERT/demoCA/cacert.pem 
  
It will spit out two things in base64, Issuer and Serial Number. Copy those, along with the subject and SHA1 fingerprint and the EV policy OID into a new file called "test_ev_roots.txt" in this format:
1_fingerprint 76:2F:[...]
2_readable_oid 1.2.1.2.1.2.1.3.3.7
3_issuer [base64 issuer]
4_serial [base64 serial] 
  
Obviously, use the whole SHA1 fingerprint and your own OID.

Once you've created that file, copy it into your firefox profile directory. On Mac, mine is in:

~/Library/Application\ Support/Firefox/Profiles/xx4971c93.dev/test_ev_roots.txt
  
The location of your profile directory may vary.

Profit!

Now test out your new CA. Set up a website that serves SSL content encrypted with an EV cert you issued, and launch Firefox. important: To enable the test EV roots file, you must first add ENABLE_TEST_EV_ROOTS_FILE=1 to your environment, then launch Firefox. I have a script that launches a test firefox for me, and doesn't let it fork:

#!/bin/bash

pushd $PROFILE_DIR/xx59pdce.dev
BIN=$FIREFOX_SRC/obj-ff/dist/MinefieldDebug.app/Contents/MacOS/firefox

#clean plugin cache
rm compreg.dat
rm xpti.dat
popd

# enables support for an external file with root CA certs and OIDs that will be
# accepted to trigger EV
export ENABLE_TEST_EV_ROOTS_FILE=1

$BIN -no-remote -P dev $@