From 348a0df6387337445c187be3f601f3171c8916be Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Mon, 12 Oct 2009 22:46:50 +0100 Subject: [PATCH] initial commit of ca-scripts devel work --- bin/ca-create-cert | 169 +++++++++++++++++++++++++++++++++++++ bin/ca-init | 72 ++++++++++++++++ bin/ca-renew-cert | 80 ++++++++++++++++++ bin/ca-revoke-cert | 51 ++++++++++++ ca-scripts.conf | 45 ++++++++++ doc/README | 61 ++++++++++++++ doc/ca-cert.txt | 17 ++++ doc/create-cert.txt | 97 ++++++++++++++++++++++ doc/create-p12.txt | 17 ++++ doc/renew-cert.txt | 20 +++++ doc/revoke-cert.txt | 18 ++++ lib/ca-functions | 198 ++++++++++++++++++++++++++++++++++++++++++++ tpl/ca-config.tpl | 160 +++++++++++++++++++++++++++++++++++ tpl/client-ext.tpl | 17 ++++ tpl/index-html.tpl | 27 ++++++ tpl/req-config.tpl | 32 +++++++ tpl/server-ext.tpl | 19 +++++ tpl/user-ext.tpl | 18 ++++ vims | 3 + 19 files changed, 1121 insertions(+) create mode 100755 bin/ca-create-cert create mode 100755 bin/ca-init create mode 100755 bin/ca-renew-cert create mode 100755 bin/ca-revoke-cert create mode 100644 ca-scripts.conf create mode 100644 doc/README create mode 100644 doc/ca-cert.txt create mode 100644 doc/create-cert.txt create mode 100644 doc/create-p12.txt create mode 100644 doc/renew-cert.txt create mode 100644 doc/revoke-cert.txt create mode 100644 lib/ca-functions create mode 100644 tpl/ca-config.tpl create mode 100644 tpl/client-ext.tpl create mode 100644 tpl/index-html.tpl create mode 100644 tpl/req-config.tpl create mode 100644 tpl/server-ext.tpl create mode 100644 tpl/user-ext.tpl create mode 100644 vims diff --git a/bin/ca-create-cert b/bin/ca-create-cert new file mode 100755 index 0000000..8bb814e --- /dev/null +++ b/bin/ca-create-cert @@ -0,0 +1,169 @@ +#! /bin/bash + +. "/home/alex/code/ca-scripts/lib/ca-functions" + +ALT_NAMES=() +TPL_ONLY=0 +CSR_ONLY=0 +CRT_ONLY=0 +MAKE_P12=0 + +# XXX: in the ca_extension_policy section of ca-config.tpl it states that the +# C= and O= DN values in a CSR have to match those of the CA +# should we have options here to change them when it will cause breakage? +usage() { + cat <<__EOT__ +Usage: + $PROGNAME -t server [options] + $PROGNAME -t client [options] + $PROGNAME -t user [options] + +Options: + -h, --help Print this helpful message! + -c, --encrypt Encrypt certificate private key with Triple-DES + -f, --config FILE Use config file instead of $CONFFILE + -t, --type Certificate type: "server", "client" or "user" + -n, --alt-name Alternative host name (can be provided multiple times) + -p, --pkcs12 Create PKCS#12 certificate archive from generated cert + -r, --csr-only Only generate CSR, don't sign it + -s, --crt-only Only sign certificate, requires CSR in place + -x, --tpl-only Only generate templates, do not create CSR or sign CRT + --country Certificate DN -- C + --state Certificate DN -- ST + --loc Certificate DN -- L + --org Certificate DN -- O + --org-unit Certificate DN -- OU + --email Certificate DN -- E + --comment Certificate nsComment field + +__EOT__ +} + +short='hcf:t:n:prsx' +long='help,encrypt.config:,type:,alt-name:,csr-only,crt-only,tpl-only,pkcs12' +long="$long,country:,state:,loc:,org:,org-unit:,email:,comment:" +opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" ) +if [ 0 -ne $? ]; then echo; usage; exit 1; fi +eval set -- "$opts"; + +while :; do + case "$1" in + -h|--help) usage; exit 0;; + -c|--encrypt) CRYPTKEY=""; shift;; + -f|--config) shift; CONFFILE="$1"; shift;; + -t|--type) shift; CA_CRT_TYPE="$1"; shift;; + -n|--alt-name) shift; ALT_NAMES+=("$1"); shift;; + -p|--pkcs12) MAKE_P12=1; shift;; + -r|--csr-only) CSR_ONLY=1; shift;; + -s|--crt-only) CRT_ONLY=1; shift;; + -x|--tpl-only) TPL_ONLY=1; shift;; + --country) shift; CA_CRT_C="$1"; shift;; + --state) shift; CA_CRT_ST="$1"; shift;; + --location) shift; CA_CRT_L="$1"; shift;; + --org) shift; CA_CRT_O="$1"; shift;; + --org-unit) shift; CA_CRT_OU="$1"; shift;; + --email) shift; CA_CRT_E="$1"; shift;; + --comment) shift; CA_CRT_COMMENT="$1"; shift;; + --) shift; break;; + *) echo "Unknown value '$1'"; exit 1;; + esac +done +CA_CRT_CN="$1"; + +# load up the configuration file +ca_load_conf + +# parameter checking fun -- we need a type and a cn (either user or host name) +if [ -z "$CA_CRT_CN" ]; then + error "The host or username parameter is mandatory!" +fi + +if [ 1 -eq "$CSR_ONLY" -a 1 -eq "$CRT_ONLY" ]; then + error "Options --csr-only and --crt-only are mutually exclusive." +fi + +if [ "$CA_CRT_TYPE" = "user" ]; then + # append @$CA_DOMAIN to user CN if it's not already there + if [ "${CA_CRT_CN%%@*}" = "$CA_CRT_CN" ]; then + CA_CRT_CN="$CA_CRT_CN@$CA_DOMAIN"; + fi +else + # fully qualify server or client CN with $CA_DOMAIN if it's not already + if [ "${CA_CRT_CN%%.*}" = "$CA_CRT_CN" ]; then + # however we may also want the unqualified one as an alt-name + ALT_NAMES+=("$CA_CRT_CN") + CA_CRT_CN="$CA_CRT_CN.$CA_DOMAIN" + fi +fi +CNF_NAME=$( echo -n "$CA_CRT_CN" | tr -c '[:alnum:]@-' _ )".$CA_CRT_TYPE"; + +# if they've provided a comment, reformat it correctly +if [ -n "$CA_CRT_COMMENT" ]; then + CA_CRT_COMMENT="$( tr -d\" <<< $CA_CRT_COMMENT )" + CA_CRT_COMMENT="nsComment = \"$CA_CRT_COMMENT\"\n" +else + CA_CRT_COMMENT="" +fi + +CA_CRT_ALT_NAMES="" +# generate a list of alternative DNS names for server certificates +if [ "$CA_CRT_TYPE" = "server" ]; then + i=1 + for ALT_NAME in "$CA_CRT_CN" "${ALT_NAMES[@]}"; do + # also fully-qualify unqualified alt-names too (see below) + if [ "${ALT_NAME%%.*}" = "$ALT_NAME" ]; then + CA_CRT_ALT_NAMES="${CA_CRT_ALT_NAMES}DNS.$i=$ALT_NAME.$CA_DOMAIN\n" + i=$(( $i+1 )) + fi + CA_CRT_ALT_NAMES="${CA_CRT_ALT_NAMES}DNS.$i=$ALT_NAME\n" + i=$(( $i+1 )) + done +fi + +if [ 1 -ne "$CRT_ONLY" ]; then + if [ 1 -eq "$TPL_ONLY" -o "$CSR_ONLY" -eq "$TPL_ONLY" ]; then + # dirty logic here that probably needs commenting! + # generate a *new* certificate request configuration if... + # a) --tpl-only is set, i.e. we only want to generate a config + # b) both --tpl-only and --csr-only are unset, i.e. + # we're just generating a csr/crt as per usual + # c) both --tpl-only and --csr-only are set, i.e. + # we're just generating the config and not the csr itself + ca_template "req-config" "$CA_HOME/cnf/$CNF_NAME.req.cnf" + fi + if [ 1 -ne "$TPL_ONLY" ]; then + if [ ! -f "$CA_HOME/cnf/$CNF_NAME.req.cnf" ]; then + error "Couldn't find CSR config $CA_HOME/cnf/$CNF_NAME.req.cnf!" + fi + # the above logic means that if you pass --csr-only but not + # --tpl-only, you can re-use a pre-existing config to generate + # a new csr, should you wish to do so... + openssl req -new $CRYPTKEY -config "$CA_HOME/cnf/$CNF_NAME.req.cnf" \ + -keyout "$CA_HOME/key/$CNF_NAME.key" \ + -out "$CA_HOME/csr/$CNF_NAME.csr" + fi +fi +if [ 1 -ne "$CSR_ONLY" ]; then + if [ 1 -eq "$TPL_ONLY" -o "$CRT_ONLY" -eq "$TPL_ONLY" ]; then + # same logic above applies here, but for generating the extensions + # configuration file and signed certificate instead + ca_template "$CA_CRT_TYPE-ext" "$CA_HOME/cnf/$CNF_NAME.ext.cnf" + fi + if [ 1 -ne "$TPL_ONLY" ]; then + # ensure relevant files are in place before continuing... + if [ ! -f "$CA_HOME/csr/$CNF_NAME.csr" ]; then + error "CSR not present in $CA_HOME/csr/$CNF_NAME.csr" + fi + if [ ! -f "$CA_HOME/cnf/$CNF_NAME.ext.cnf" ]; then + error "Couldn't find extensions in $CA_HOME/cnf/$CNF_NAME.ext.cnf" + fi + openssl ca -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" \ + -extfile "$CA_HOME/cnf/$CNF_NAME.ext.cnf" -batch \ + -out "$CA_HOME/crt/$CNF_NAME.crt" \ + -in "$CA_HOME/csr/$CNF_NAME.csr" + fi +fi + +if [ 1 -eq "$MAKE_P12" ]; then + ca_gen_p12 "$CNF_NAME" +fi diff --git a/bin/ca-init b/bin/ca-init new file mode 100755 index 0000000..beab62d --- /dev/null +++ b/bin/ca-init @@ -0,0 +1,72 @@ +#! /bin/bash + +. "/home/alex/code/ca-scripts/lib/ca-functions" + +usage() { + cat <<__EOT__ +Usage: $PROGNAME [options] + +Options: + -h, --help Print this helpful message! + -c, --encrypt Encrypt CA private key with Triple-DES + -f, --config FILE Use config file instead of $CONFFILE + -i, --template FILE Use alternative index.html template + -o, --output FILE Generate CA index.html in FILE + +__EOT__ +} + +short='hcf:i:o:' +long='help,encrypt,config:,template:,output:' +opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" ) +if [ 0 -ne $? ]; then echo; usage; exit 1; fi +eval set -- "$opts"; + +while :; do + case "$1" in + -h|--help) usage; exit 0;; + -c|--encrypt) CRYPTKEY=""; shift;; + -f|--config) shift; CONFFILE="$1"; shift;; + -i|--template) shift; INDEXTPL="$1"; shift;; + -o|--output) shift; INDEXOUT="$1"; shift;; + --) shift; break;; + *) echo "Unknown value '$1'"; exit 1;; + esac +done + +# load up the configuration file +CA_CRT_TYPE="ca" +ca_load_conf + +# create the directory structure that'll be populated by the scripts +mkdir -p $CA_HOME/{cnf,crl,crt,csr,db,idx,key,p12} +echo "01" > $CA_HOME/db/crlnumber +touch $CA_HOME/db/index.txt +touch $CA_HOME/db/.rand +chmod 600 $CA_HOME/db/.rand +chmod 700 $CA_HOME/key + +# generate an openssl configuration for this CA +ca_template ca-config "$CA_HOME/cnf/$CA_NAME.ca.cnf" + +# generate a self-signed cert that is valid for 10 years, with +# ... the private key in $CA_HOME/key/$CA_NAME.ca.key +# ... the certificate in $CA_HOME/crt/$CA_NAME.ca.crt +# ... using the config in $CA_HOME/cnf/$CA_NAME.ca.cnf +openssl req -new $CRYPTKEY -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" \ + -keyout "$CA_HOME/key/$CA_NAME.ca.key" \ + -out "$CA_HOME/csr/$CA_NAME.ca.csr" + +openssl ca -create_serial -selfsign -days 3652 -batch \ + -name ca_scripts -extensions ca_x509_extensions \ + -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" \ + -in "$CA_HOME/csr/$CA_NAME.ca.csr" \ + -keyfile "$CA_HOME/key/$CA_NAME.ca.key" \ + -out "$CA_HOME/crt/$CA_NAME.ca.crt" + +# generate an initial CRL too (yes it will be empty, but we should serve it) +ca_gen_crl +if [ -n "$INDEXOUT" ]; then + ca_checksum + ca_template $INDEXTPL $INDEXOUT +fi diff --git a/bin/ca-renew-cert b/bin/ca-renew-cert new file mode 100755 index 0000000..ce30d75 --- /dev/null +++ b/bin/ca-renew-cert @@ -0,0 +1,80 @@ +#! /bin/bash + +. "/home/alex/code/ca-scripts/lib/ca-functions" + +usage() { + cat <<__EOT__ +Usage: $PROGNAME -t [options] + +Options: + -h, --help Print this helpful message! + -f, --config FILE Use config file instead of $CONFFILE + -t, --type Certificate type: "server", "client" or "user" + +__EOT__ +} + +short='hf:t:' +long='help,config:,type:' +opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" ) +if [ 0 -ne $? ]; then echo; usage; exit 1; fi +eval set -- "$opts"; + +while :; do + case "$1" in + -h|--help) usage; exit 0;; + -f|--config) shift; CONFFILE="$1"; shift;; + -t|--type) shift; CA_CRT_TYPE="$1"; shift;; + --) shift; break;; + *) echo "Unknown value '$1'"; exit 1;; + esac +done + +CNF_NAME="$1" + +ca_load_conf + +CNF_NAME=$( ca_find_cnf "$CNF_NAME" ) +CRT="$CA_HOME/crt/$CNF_NAME.crt" + +# make sure that configuration files are present as expected +if [ ! -f "$CA_HOME/cnf/$CNF_NAME.ext.cnf" ]; then + error "Couldn't find extensions in $CA_HOME/cnf/$CNF_NAME-ext.cnf" +fi + +# according to the below URL we should create the new CRT using the old CSR +# and with the same serial as the previous certificate. +# http://blog.fupps.com/2007/11/30/x509ssl-certificate-prolongation/ +# After some fun googling, I found the following URL which tells us how... +# http://ca.dutchgrid.nl/info/CA_gymnastics.html +# XXX: this is only *really* relevant for certs that have been used for code +# or e-mail encryption. should we regenerate client/server certs entirely? +# ... for the moment there's always the revoke/recreate route for people. + +# acquire required info from old certificate +ENDDATE=$( openssl x509 -in "$CRT" -noout -enddate | cut -d= -f2 ) +SERIAL=$( openssl x509 -in "$CRT" -noout -serial | cut -d= -f2 ) +# work out new expiry date based on expiry date of current cert + 1 year +# these dates are " " +export TZ=UTC +NOWYEAR=$( date +%Y ) +NOWDAYS=$( date +%j ) +ENDYEAR=$( date +%Y -d "$ENDDATE + 1 year" ) +ENDDAYS=$( date +%j -d "$ENDDATE + 1 year" ) +CERTDATE=$( date +%Y-%m-%d -d "$ENDDATE" ) + +# and this does the maths to work out how many days there are from now +# (when we're creating the new cert) to the new expiry date +DAYS=$(( ($ENDYEAR-$NOWYEAR)*365 + ($ENDDAYS-$NOWDAYS) )) + +# Now perform required CA gymnastics ;p +openssl x509 -req -set_serial "0x$SERIAL" -days "$DAYS" \ + -CA "$CA_HOME/crt/$CA_NAME.ca.crt" \ + -CAkey "$CA_HOME/key/$CA_NAME.ca.key" \ + -extfile "$CA_HOME/cfg/$CNF_NAME.ext.cnf" \ + -out "$CA_HOME/crt/$CNF_NAME.crt" \ + -in "$CA_HOME/csr/$CNF_NAME.csr" + +# This doesn't update the original certificate in the index, so let's do that +mv "$CA_HOME/idx/$SERIAL.pem" "$CA_HOME/idx/$SERIAL.$CERTDATE.pem" +cp "$CA_HOME/crt/$CNF_NAME.crt" "$CA_HOME/idx/$SERIAL.pem" diff --git a/bin/ca-revoke-cert b/bin/ca-revoke-cert new file mode 100755 index 0000000..92b29ad --- /dev/null +++ b/bin/ca-revoke-cert @@ -0,0 +1,51 @@ +#! /bin/sh + +. "/home/alex/code/ca-scripts/lib/ca-functions" + +usage() { + cat <<__EOT__ +Usage: $PROGNAME -t [options] + +Options: + -h, --help Print this helpful message! + -f, --config FILE Use config file instead of $CONFFILE + -t, --type Certificate type: "server", "client" or "user" + -i, --template FILE Use alternative index.html template + -o, --output FILE Generate CA index.html in FILE + +__EOT__ +} + +short='hf:t:i:o:' +long='help,config:,type:,template:,output:' +opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" ) +if [ 0 -ne $? ]; then echo; usage; exit 1; fi +eval set -- "$opts"; + +while :; do + case "$1" in + -h|--help) usage; exit 0;; + -f|--config) shift; CONFFILE="$1"; shift;; + -t|--type) shift; CA_CRT_TYPE="$1"; shift;; + -i|--template) shift; INDEXTPL="$1"; shift;; + -o|--output) shift; INDEXOUT="$1"; shift;; + --) shift; break;; + *) echo "Unknown value '$1'"; exit 1;; + esac +done +CNF_NAME="$1" + +ca_load_conf + +CNF_NAME=$( ca_find_cnf "$CNF_NAME" "$TYPE" ) +CRT="$CA_HOME/crt/$CNF_NAME.crt" + +openssl ca -config $CA_HOME/cnf/$CA_NAME.ca.cnf \ + -revoke $CRT -crl_reason superseded + +ca_gen_crl +if [ -n "$INDEXOUT" ]; then + ca_checksum + ca_template $INDEXTPL $INDEXOUT +fi + diff --git a/ca-scripts.conf b/ca-scripts.conf new file mode 100644 index 0000000..220d03b --- /dev/null +++ b/ca-scripts.conf @@ -0,0 +1,45 @@ +# example ca-scripts configuration file + +# REQUIRED: CA_HOME provides the path to the root of the CA directory tree +# this directory must exist and be writeable +#CA_HOME="/etc/ssl/ca" +CA_HOME="/tmp/ca" + +# REQUIRED: CA_DOMAIN provides a template for other optional variables and +# the filenames that are generated within the directory tree +CA_DOMAIN="example.com" + +# OPTIONAL: CA_NAME is the internal templating variable for filenames etc +# Defaults to: +# CA_NAME="$( echo $CA_DOMAIN | tr 'A-Z' 'a-z' | tr -c '-a-z0-9' '_' )" + +# REQUIRED: CA_DN_* configures the Distinguished Name fields present in the +# CA certificate generated by ca-init +CA_DN_C="GB" +CA_DN_ST="London" +CA_DN_L="Example House, Mayfair" +CA_DN_O="Example Security Services Ltd." +CA_DN_OU="Example Internet Encryption Division" +CA_DN_CN="Example Security Services Root Certificate Authority" + +# OPTIONAL: CA_DESC configures a single-line description for your CA +# using the CN= or O= line from your DN is recommended +# Default value: +# CA_DESC="$CA_DN_CN" + +# OPTIONAL: CA_EMAIL provides an e-mail address that is embedded into all +# generated certificates as a point-of-contact +# Default value: +# CA_EMAIL="ca@$CA_DOMAIN" + +# OPTIONAL: CA_CRT_URI and CA_CRL_URI provide locations where the CA +# certificate and revocation lists can be found +# Default value: +# CA_CRT_URI="http://$CA_DOMAIN/ca/$CA_NAME.ca.crt" +# CA_CRL_URI="http://$CA_DOMAIN/ca/$CA_NAME.ca.crl" + +# OPTIONAL: CA_PATHLEN sets the maximum number of intermediate CA certificates +# that can be in the chain of authority between the root CA and the +# final certificate. +# Default value: +# CA_PATHLEN=0 diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..835c5f4 --- /dev/null +++ b/doc/README @@ -0,0 +1,61 @@ +1. Creating a Certificate Authority. + + + + +To fully understand it's contents you're unfortunately going to need to read ca(1ssl), +req(1ssl), x509(1ssl), config(5ssl), and x509v3_config(5ssl). Particularly +important are the x509v3 extensions present in the certificate, which are +defined in the "stglab_x509_ca_extensions" section of the config file. + + The ca-cert script configures some important files in db/, then creates a +certificate request and signs it. It also generates an initial (empty) +revocation list, then substitutes the correct fingerprints into the html +template for serving the CA certificate and CRL to the intranet. + +2. Creating a certificate. + + The create-cert script can generate three "types" of certificate -- server +certificates for securing a service with SSL/TLS, client certificates for +authenticating a client to these services, and user certificates for +authentication, S/MIME e-mail signing or encryption, and code signing. There +are minor but important differences in the extensions present in these +different certificate types, but these are set in the *-ext.tpl files in tpl/ +and thus you shouldn't need to worry about them. + + The create-cert script takes a number of arguments, of which the hostname or +username and the type are mandatory. It is also a very good idea to supply a +number of alternative DNS names when generating a server certificate, because +while the script will happily append "stglab.manchester.uk.ibm.com" to an +un-qualified host name, it won't append "transitives.com" and for the moment we +probably need that. + + You should also provide a team name for the organisational unit, e.g. +"Manchester STG Lab Test", an e-mail address (preferably for the team rather +than an individual for server/client certificates), and a comment that reflects +the usage of the certificate, e.g. "Soak Infrastructure Live Server". Reasonable +defaults are provided for all of these for our team's use. + +3. Renewing a certificate. + + The renew-cert script does some painful certificate manipulation that is not +strictly necessary in most cases, and may in fact decrease SSL security +slightly. This is done because the normal renewal process re-generates the +certificate signing request and thus creates a new public/private keypair. +If the certificates are used for S/MIME encryption or code signing, this +renders all the encrypted e-mail unreadable and requires you to re-sign the +code with your new private key. The code in renew-cert re-signs the old +certificate request with a new expiry date and the extensions generated when +the original certificate was created, and avoids this problem. + + Renewing a certificate is done by giving the hostname, username or path to +the certificate to renew-cert.sh. + +4. Revoking a certificate. + + Revoking a certificate is done by giving the hostname, username or path to +the certificat to revoke-cert.sh. This script also regenerates a new CRL in +both PEM and DER encodings (firefox prefers the latter while IE and other +browsers work better with the former), and re-generates the html file with the +new fingerprints. + diff --git a/doc/ca-cert.txt b/doc/ca-cert.txt new file mode 100644 index 0000000..7dce05b --- /dev/null +++ b/doc/ca-cert.txt @@ -0,0 +1,17 @@ +# a brief man-page for ca-cert.sh +# $Id: ca-cert.txt 2660 2009-07-24 18:49:52Z alexeb $ + +NAME + ca-cert.sh - generate a CA cert and perform initial db setup + +SYNOPSIS + ca-cert.sh + +DESCRIPTION + This script generates a CSR and signs it to turn it into a root + certificate authority. It also sets up some important files in the CA + database directory, generates an initial empty revocation list, and + creates index.html from the template. + +OPTIONS + This script takes no options. diff --git a/doc/create-cert.txt b/doc/create-cert.txt new file mode 100644 index 0000000..ca0a907 --- /dev/null +++ b/doc/create-cert.txt @@ -0,0 +1,97 @@ +# a brief man-page for create-cert.sh +# $Id: create-cert.txt 2660 2009-07-24 18:49:52Z alexeb $ + +NAME + create-cert.sh - generate a signed X.509 certificate + +SYNOPSIS + create-cert.sh -t server [options] + create-cert.sh -t client [options] + create-cert.sh -t user [options] + +DESCRIPTION + The create-cert.sh script creates the configuration files necessary + for generating a signed X.509 certificate, creates a certificate + signing request using these configuration files, and signs that request + using the root CA key so that it is trusted by anything that has + imported the CA certificate. + +OPTIONS + -h, --help + Prints out a short synopsis of the arguments that this script takes. + + -t, --type {server|client|user} + This argument is mandatory. create-cert.sh can create three types of + X.509 certificate: server, client, and user. These differ in the + X.509v3 extensions present, and in the uses the certificate is trusted + for. + + Server certificates are used for securing SSL/TLS services, such as + TLS-encrypted LDAP connections or SSL HTTP. In this case the + argument is used for the Common Name in the certificate, and any + additional alternative names supplied by -n are added to the X.509v3 + "SubjectAltName" extension. + + Client certificates are used for authenticating to SSL/TLS services. + For the most part they will be used by automated systems to identify + and authenticate to services they interact with. + + User certificates are for individuals to authenticate themselves to + SSL/TLS services in the same manner as client certificates, but they + may also be used for S/MIME e-mail encryption and code signing. + + -c, --comment "COMMENT" + This argument sets the "Netscape Comment" X.509 extension. + + -n, --alt-name HOSTNAME + This argument adds an alternative hostname to the "SubjectAltName" + X.509v3 extension. It may be supplied multiple times to add more than + one additional hostname. + + -l, --location LOCATION + This argument sets the "Location" field of the certificate's + distinguished name. Syggested values are "Maybrook House" and + "Jackson House", but the field is freeform text. + + -o, --org-unit TEAMNAME + This argument sets the "Organisational Unit" field of the certificate's + distinguished name. Ideally this should begin with "Manchester STG Lab" + for consistency's sake, for example: + + Manchester STG Lab Systems and Network Infrastructure + Manchester STG Lab Testing + Manchester STG Lab Starlight Development + + -e, --email EMAIL + This argument sets the "E-Mail Address" field of the certificate's + distinguished name. As per current X.509 standards this is actually + removed from the DN of the CSR and placed into the "SubjectAltName" + extension in the signed certificate. In general it should be a team + alias rather than an individual's address for server and client certs. + + -r, --csr-only + This argument causes create-cert.sh to only generate a new CSR. It will + not generate the request configuration files in cfg/ unless --tpl-only + is also passed; in this case it will just create the configuration + files instead. This allows you to re-generate a CSR after manually + tweaking the configuration files. + + -s, --crt-only + This argument causes create-cert.sh to only sign an existing CSR. As + with --csr-only, it will not generate extension configuration files + unless --tpl-only is also passed; again in this case it will just + create the configuration files so that you can re-sign the same CSR + with new extensions. + + -t, --tpl-only + This argument modifies the behaviour of the previous two options when + passed with them, as described above. On it's own it causes + create-cert.sh to generate both sets of configuration files, but + not generate either the signing request or the signed certificate. + +DEFAULTS + * The LOCATION defaults to "Maybrook House" + * The TEAM defaults to "Manchester STG Lab Systems and Network Infrastructure" + * The EMAIL defaults to "mcr_lab_lsni@wwpdl.vnet.ibm.com" + * There is no COMMENT set by default + diff --git a/doc/create-p12.txt b/doc/create-p12.txt new file mode 100644 index 0000000..4fee2ed --- /dev/null +++ b/doc/create-p12.txt @@ -0,0 +1,17 @@ +# a brief man-page for create-p12.sh +# $Id: create-p12.txt 2660 2009-07-24 18:49:52Z alexeb $ + +NAME + create-p12.sh - create a PKCS#12 archive of a certificate and key + +SYNOPSIS + create-p12.sh /path/to/certificate + create-p12.sh + +DESCRIPTION + This script exports a PKCS#12 archive containing a user's certificate, + private key, and the CA certificate. It will prompt for a password to + lock the archive with, and then place it in p12/. + +OPTIONS + This script takes no options. diff --git a/doc/renew-cert.txt b/doc/renew-cert.txt new file mode 100644 index 0000000..f6dd50a --- /dev/null +++ b/doc/renew-cert.txt @@ -0,0 +1,20 @@ +# a brief man-page for renew-cert.sh +# $Id: renew-cert.txt 2660 2009-07-24 18:49:52Z alexeb $ + +NAME + renew-cert.sh - renew a previously generated cert for another year + +SYNOPSIS + renew-cert.sh /path/to/certificate + renew-cert.sh + renew-cert.sh + +DESCRIPTION + This script renews a certificate for another 365 days from it's current + end-date. It does some interesting hackery to re-sign the certificate + request generated when the certificate was initially signed, using the + same key-pair and the same serial, so that S/MIME encrypted e-mail and + previously signed code does not become unusable. + +OPTIONS + This script takes no options. diff --git a/doc/revoke-cert.txt b/doc/revoke-cert.txt new file mode 100644 index 0000000..1b66adb --- /dev/null +++ b/doc/revoke-cert.txt @@ -0,0 +1,18 @@ +# a brief man-page for revoke-cert.sh +# $Id: revoke-cert.txt 2660 2009-07-24 18:49:52Z alexeb $ + +NAME + revoke-cert.sh - revoke a certificate and generate revocation list + +SYNOPSIS + revoke-cert.sh /path/to/certificate + revoke-cert.sh + revoke-cert.sh + +DESCRIPTION + This script revokes the provided certificate and updates the revocation + list. It generates both a PEM and a DER encoded version of the CRL for + different browsers, and updates the html page with the new fingerprints. + +OPTIONS + This script takes no options. diff --git a/lib/ca-functions b/lib/ca-functions new file mode 100644 index 0000000..84a2519 --- /dev/null +++ b/lib/ca-functions @@ -0,0 +1,198 @@ +#! /bin/bash +# common functions for ca-scripts + +PROGNAME=$( basename $0 ) +CONFFILE="/etc/ca-scripts.conf" +SHAREDIR="/home/alex/code/ca-scripts/tpl" +CRYPTKEY="-nodes" + +INDEXTPL="index-html" +INDEXOUT="" + +error() { + usage >&2 + echo -e "ERROR: $1\n" >&2 + exit 1 +} + +ca_check_var() { + local varname vartest + + varname="$1" + vartest="$2" + eval "if [ ! $vartest \"\$$varname\" ]; then + echo '$varname value \"\$$varname\" failed \"$vartest\" test' + fi" +} + +ca_set_default() { + local varname vardef + + varname="$1" + vardef="$2" + eval "if [ -z \"\$$varname\" ]; then + $varname=\"$vardef\"; + fi" +} + +ca_load_conf() { + local varname vartest varerr vardef error ca_name + if [ ! -r "$CONFFILE" ]; then + error "Unable to find $CONFFILE." + fi + # XXX: seems like . doesn't work if it's not relative to a directory + # look this up on the internet sometime to work out why... + if [ "$CONFFILE" = "$( basename $CONFFILE )" ]; then + CONFFILE="./$CONFFILE" + fi + . "$CONFFILE" + + error="" + while read vartest varname; do + varerr=$( ca_check_var "$varname" "$vartest" ) + if [ -n "$varerr" ]; then + error="$error\n $varerr" + fi + done <<__TESTS__ +-d CA_HOME +-n CA_DOMAIN +-n CA_DN_C +-n CA_DN_ST +-n CA_DN_L +-n CA_DN_O +-n CA_DN_OU +-n CA_DN_CN +__TESTS__ + if [ -n "$error" ]; then + error "Parsing config file $CONFFILE failed:\n$error" + fi + + case "$CA_CRT_TYPE" in + server|client|user|ca) :;; + '') error "The type option is mandatory!";; + *) error "Unrecognised type '$CA_CRT_TYPE'!";; + esac + + # we need to do these first to use them in other default defs + # NOTE: bash's here-string syntax appends \n which tr turns to _ :( + ca_set_default CA_NAME "$( echo -n "$CA_DOMAIN" | tr -c '[:alnum:]@-' _ )" + ca_set_default CA_EMAIL "ca@$CA_DOMAIN" + + while read varname vardef; do + ca_set_default "$varname" "$vardef" + done <<__DEFAULTS__ +CA_DESC $CA_DN_CN +CA_CRT_URI http://$CA_DOMAIN/ca/$CA_NAME.ca.crt +CA_CRL_URI http://$CA_DOMAIN/ca/$CA_NAME.ca.crl +CA_PATHLEN 0 +CA_CRT_C $CA_DN_C +CA_CRT_ST $CA_DN_ST +CA_CRT_L $CA_DN_L +CA_CRT_O $CA_DN_O +CA_CRT_OU $CA_DN_OU +CA_CRT_E $CA_EMAIL +__DEFAULTS__ +} + +ca_sed_cmd() { + # MD5 in CA_CR[TL]_MD5_FP has a non alphabetic character :( + # XXX: pretty sure this is a dirty and wrong way of templating vars + set | awk -F\= '/^CA_[A-Z5_]*=/{print $1}' | while read ca_var; do + echo "s#%$ca_var%#${!ca_var}#;" + done +} + +ca_template() { + local template dest + + if [ -r "$1" ]; then + template="$1" + elif [ -r "$SHAREDIR/$1.tpl" ]; then + template="$SHAREDIR/$1.tpl" + else + error "Could not read from template $1" + fi + dest="$2" + + sed -e "$(ca_sed_cmd)" <"$template" >"$dest" +} + +ca_gen_crl() { + openssl ca -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" \ + -gencrl -out "$CA_HOME/crl/$CA_NAME.ca.crl" -md sha1 + openssl crl -in "$CA_HOME/crl/$CA_NAME.ca.crl" \ + -out "$CA_HOME/crl/$CA_NAME.ca.crl.der" -outform DER +} + +ca_gen_p12() { + local cnf_name + cnf_name="$1" + openssl pkcs12 -export -descert -out $CA_HOME/p12/$cnf_name.p12 \ + -in $CA_HOME/crt/$cnf_name.crt \ + -inkey $CA_HOME/key/$cnf_name.key \ + -certfile $CA_HOME/crt/$CA_NAME.ca.crt +} + +ca_checksum() { + CA_CRT_MD5_FP="$( openssl x509 -in $CA_HOME/crt/$CA_NAME.ca.crt \ + -noout -md5 -fingerprint | cut -d= -f2 )" + CA_CRT_SHA_FP="$( openssl x509 -in $CA_HOME/crt/$CA_NAME.ca.crt \ + -noout -sha1 -fingerprint | cut -d= -f2 )" + CA_CRL_MD5_FP="$( openssl crl -in $CA_HOME/crl/$CA_NAME.ca.crl \ + -noout -md5 -fingerprint | cut -d= -f2 )" + CA_CRL_SHA_FP="$( openssl crl -in $CA_HOME/crl/$CA_NAME.ca.crl \ + -noout -sha1 -fingerprint | cut -d= -f2 )" +} + +ca_cnf_name() { + local crt + crt="$1" + # work out what configuration files we should be using from the cert's CN + echo $( openssl x509 -in "$crt" -noout -nameopt sep_multiline,use_quote \ + -subject | grep "CN=" | cut -d= -f2 | tr -c '[:alnum:]@-' _ ) +} + +ca_find_cnf() { + local name _name + name="$1" + + if [ -f "$name" ]; then + if ! grep -q "$CA_CRT_TYPE" <<<"$name"; then + error "Certificate '$name' does not appear to be of type '$CA_CRT_TYPE'" + else + echo "$(ca_cnf_name $name).$CA_CRT_TYPE" + fi + return + fi + + _name=$( echo -n "$name" | tr -c '[:alnum:]@-' _ ) + if [ "$CA_CRT_TYPE" = "user" ]; then + # user names may have dots etc. in, so use munged version in match + # check if name is "user@domain", append $CA_DOMAIN if not + if [ "${_name%%@*}" = "$_name" \ + -a -f "$CA_HOME/crt/${_name}@$CA_NAME.$CA_CRT_TYPE.crt" ]; + then + # name is not fully-qualified, but a cert exists for it + echo "${_name}@$CA_NAME.$CA_CRT_TYPE" + elif [ -f "$CA_HOME/crt/$_name.$CA_CRT_TYPE.crt" ]; then + # name was fully-qualified and a cert exists + echo "$_name.$CA_CRT_TYPE" + else + error "Could not find $CA_CRT_TYPE certificate configuration matching '$name'" + fi + else + # check if name is fully-qualified -- contains more than 1 dot + # NOTE: we have to do this test with the unmunged name ... + if [ "${name%%.*}" = "$name" \ + -a -f "$CA_HOME/crt/${_name}_$CA_NAME.$CA_CRT_TYPE.crt" ]; + then + # name is not fully-qualified, but a cert exists for it + echo "${_name}_$CA_NAME.$CA_CRT_TYPE" + elif [ -f "$CA_HOME/crt/$_name.$CA_CRT_TYPE.crt" ]; then + # name was fully-qualified and a cert exists + echo "$_name.$CA_CRT_TYPE" + else + error "Could not find $CA_CRT_TYPE certificate configuration matching '$name'" + fi + fi +} diff --git a/tpl/ca-config.tpl b/tpl/ca-config.tpl new file mode 100644 index 0000000..de3a29b --- /dev/null +++ b/tpl/ca-config.tpl @@ -0,0 +1,160 @@ +# CA configuration file template + +# ---------------------------------------------------------------------------- # +# This defines the CA configuration to use +[ ca ] +default_ca = ca_scripts + +# ---------------------------------------------------------------------------- # +# This defines our CA configuration +[ ca_scripts ] +# interpolation variables defining the directories to use +dir = %CA_HOME% # root data directory of CA +db_dir = $dir/db # database files are kept here +csr_dir = $dir/csr # generated CSRs are kept here +crt_dir = $dir/crt # signed CRTs are kept here +key_dir = $dir/key # generated KEYs are kept here +crl_dir = $dir/crl # generated CRL is kept here +new_certs_dir = $dir/idx # default place for new CRTs + +# required settings +database = $db_dir/index.txt # database index file +serial = $db_dir/serial # serial number index file +certificate = $crt_dir/%CA_NAME%.ca.crt # CA certificate +private_key = $key_dir/%CA_NAME%.ca.key # CA private key +crl = $crl_dir/%CA_NAME%.ca.crl # current CRL +RANDFILE = $db_dir/.rand # private random number file + +# these two CA directives can be commented out so that v1 CRLs are created +crlnumber = $db_dir/crlnumber # crlnumber index file +crl_extensions = ca_crl_extensions # extensions in v2 CRL + +# x509v3 certificate extensions and certificate signing policy +x509_extensions = ca_x509_default_extensions +copy_extensions = copy # copy extensions from CSR to CRT +policy = ca_extension_policy # policy on required CSR attributes + +# leave these defaults +name_opt = oneline # Subject Name options - x509(1) +cert_opt = ca_default # Certificate field options - x509(1) +default_days = 365 # how long to certify for +default_crl_days= 365 # how long before next CRL +default_md = sha1 # which md to use. +preserve = no # keep passed DN ordering +unique_subject = no # recommended +email_in_dn = no # remove email from CSR DN when signing + +# ---------------------------------------------------------------------------- # +# This defines the CA's policy on required CSR attributes. +# It requires: +# the country [C] to be supplied in the CSR and match the CA +# the state or province [ST] to be supplied in the CSR +# the locality [L] to be supplied in the CSR +# the organisation name [O] to be supplied in the CSR and match the CA +# the organisational unit [OU] to be supplied in the CSR +# the server common name [CN] to be supplied in the CSR +# ... and an [emailAddress] may optionally be supplied in the CSR +# XXX: is this too restrictive or not restrictive enough? +# should options for ca-create-cert to change "match" values even exist? +[ ca_extension_policy ] +countryName = match +stateOrProvinceName = supplied +localityName = supplied +organizationName = match +organizationalUnitName = supplied +commonName = supplied +emailAddress = optional + +# ---------------------------------------------------------------------------- # +# This defines the default x509 extensions present in a cert signed by the CA. +# These should be replaced by a specific set of extensions per certificate. +[ ca_x509_default_extensions ] + +# certificates signed by this CA by default are not CA certificates themselves +basicConstraints = CA:FALSE + +# old netscape certificate attributes +nsCertType = server +nsComment = "%CA_DESC% Certificate" +nsRevocationUrl = %CA_CRL_URI% + +# key usage restrictions +keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyAgreement +extendedKeyUsage = serverAuth + +issuerAltName = issuer:copy +subjectAltName = URI:%CA_CRT_URI% +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +authorityInfoAccess = caIssuers;URI:%CA_CRT_URI% +crlDistributionPoints = URI:%CA_CRL_URI% + +# ---------------------------------------------------------------------------- # +# This defines the x509 extensions present in the generated CA certificate. +[ ca_x509_extensions ] + +# this certificate is authoritative and allowed to sign other certificates +# pathlen=1 implies there may be up to one intermediate CA in the chain +# that leads to this root CA certificate. +basicConstraints = critical,CA:TRUE,pathlen:%CA_PATHLEN% + +# old netscape certificate attributes +nsCertType = objsign, sslCA, emailCA, objCA +nsComment = "%CA_DESC%" +nsRevocationUrl = %CA_CRL_URI% +nsCaRevocationUrl = %CA_CRL_URI% + +# key usage restrictions +keyUsage = critical, cRLSign, keyCertSign +extendedKeyUsage = serverAuth, clientAuth, codeSigning, emailProtection, timeStamping + +issuerAltName = @ca_altname +subjectAltName = @ca_altname +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +authorityInfoAccess = caIssuers;URI:%CA_CRT_URI% +crlDistributionPoints = URI:%CA_CRL_URI% + +# ---------------------------------------------------------------------------- # +# This is a separate section defining the attributes in the CA's subjectAltName. +[ ca_altname ] +URI=%CA_CRT_URI% +DNS.1=%CA_DOMAIN% +DNS.2=*.%CA_DOMAIN% +email=%CA_EMAIL% + +# ---------------------------------------------------------------------------- # +# This defines the extensions present in the CRLs generated by this CA. +[ ca_crl_extensions ] +issuerAltName = issuer:copy +authorityKeyIdentifier = keyid:always, issuer:always +# the below is only supported in the very latest releases of openssl +# issuingDistributionPoint= URI:%CA_CRL_URI% + +# ---------------------------------------------------------------------------- # +# This defines the extensions present in the CSRs created by this CA. +[ ca_req_extensions ] +basicConstraints = critical, CA:FALSE +keyUsage = critical, nonRepudiation, keyEncipherment, keyAgreement +extendedKeyUsage = serverAuth + +# ---------------------------------------------------------------------------- # +# This defines default settings for certificate requests and CA cert creation. +[ req ] +default_bits = 2048 +default_md = sha1 +distinguished_name = ca_req_dn +x509_extensions = ca_x509_extensions +req_extensions = ca_req_extensions +string_mask = nombstr +prompt = no + +# ---------------------------------------------------------------------------- # +# This defines the DN of the CA certificate. +[ ca_req_dn ] +C = %CA_DN_C% +ST = %CA_DN_ST% +L = %CA_DN_L% +O = %CA_DN_O% +OU = %CA_DN_OU% +CN = %CA_DN_CN% diff --git a/tpl/client-ext.tpl b/tpl/client-ext.tpl new file mode 100644 index 0000000..95096dc --- /dev/null +++ b/tpl/client-ext.tpl @@ -0,0 +1,17 @@ +basicConstraints = critical, CA:FALSE +nsCertType = client +nsRevocationUrl = %CA_CRL_URI% +%CA_CRT_COMMENT% +keyUsage = critical, keyEncipherment, keyAgreement, digitalSignature +extendedKeyUsage = clientAuth, timeStamping + +issuerAltName = issuer:copy +subjectAltName = @client_altname +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +authorityInfoAccess = caIssuers;URI:%CA_CRT_URI% +crlDistributionPoints = URI:%CA_CRL_URI% + +[ client_altname ] +URI=%CA_CRT_URI% +email=move diff --git a/tpl/index-html.tpl b/tpl/index-html.tpl new file mode 100644 index 0000000..c003503 --- /dev/null +++ b/tpl/index-html.tpl @@ -0,0 +1,27 @@ + + +%CA_DESC% + + + + +

%CA_DESC%

+

CA Certificate

+

The CA certificate can be found +here

+

MD5 Fingerprint: %CA_CRT_MD5_FP%

+

SHA1 Fingerprint: %CA_CRT_SHA_FP%

+

Certificate Revocation List

+

The certificate revocation list can be found +here (DER encoded) +or here (PEM encoded)

+

MD5 Fingerprint: %CA_CRL_MD5_FP%

+

SHA1 Fingerprint: %CA_CRL_SHA_FP%

+ + diff --git a/tpl/req-config.tpl b/tpl/req-config.tpl new file mode 100644 index 0000000..f25b457 --- /dev/null +++ b/tpl/req-config.tpl @@ -0,0 +1,32 @@ +[ req ] +default_bits = 2048 +default_md = sha1 +distinguished_name = req_dn +req_extensions = req_%CA_CRT_TYPE%_extensions +string_mask = nombstr +prompt = no + +[ req_dn ] +C = %CA_CRT_C% +ST = %CA_CRT_ST% +L = %CA_CRT_L% +O = %CA_CRT_O% +OU = %CA_CRT_OU% +CN = %CA_CRT_CN% +emailAddress = %CA_CRT_E% + +[ req_server_extensions ] +basicConstraints = critical, CA:FALSE +keyUsage = critical, keyEncipherment, keyAgreement +extendedKeyUsage = serverAuth + +[ req_client_extensions ] +basicConstraints = critical, CA:FALSE +keyUsage = critical, keyEncipherment, keyAgreement, digitalSignature +extendedKeyUsage = clientAuth, timeStamping + +[ req_user_extensions ] +basicConstraints = critical, CA:FALSE +keyUsage = critical, keyEncipherment, keyAgreement, digitalSignature, nonRepudiation, dataEncipherment +extendedKeyUsage = clientAuth, codeSigning, emailProtection + diff --git a/tpl/server-ext.tpl b/tpl/server-ext.tpl new file mode 100644 index 0000000..f1d3597 --- /dev/null +++ b/tpl/server-ext.tpl @@ -0,0 +1,19 @@ +basicConstraints = critical, CA:FALSE +nsCertType = server +nsRevocationUrl = %CA_CRL_URI% +%CA_CRT_COMMENT% +keyUsage = critical, keyEncipherment, keyAgreement +extendedKeyUsage = serverAuth + +issuerAltName = issuer:copy +subjectAltName = @server_altname +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +authorityInfoAccess = caIssuers;URI:%CA_CRT_URI% +crlDistributionPoints = URI:%CA_CRL_URI% + +[ server_altname ] +URI=%CA_CRT_URI% +email=move +%CA_CRT_ALT_NAMES% + diff --git a/tpl/user-ext.tpl b/tpl/user-ext.tpl new file mode 100644 index 0000000..2af730f --- /dev/null +++ b/tpl/user-ext.tpl @@ -0,0 +1,18 @@ +basicConstraints = critical, CA:FALSE +nsCertType = client, objsign, email +nsRevocationUrl = %CA_CRL_URI% +%CA_CRT_COMMENT% +keyUsage = critical, keyEncipherment, keyAgreement, digitalSignature, nonRepudiation, dataEncipherment +extendedKeyUsage = clientAuth, codeSigning, emailProtection + +issuerAltName = issuer:copy +subjectAltName = @user_altname +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +authorityInfoAccess = caIssuers;URI:%CA_CRT_URI% +crlDistributionPoints = URI:%CA_CRL_URI% + +[ user_altname ] +URI=%CA_CRT_URI% +email=move + diff --git a/vims b/vims new file mode 100644 index 0000000..a8baeac --- /dev/null +++ b/vims @@ -0,0 +1,3 @@ +gvim -p bin/* lib/* +gvim -p ca-scripts.conf tpl/* +gvim -p doc/*