From 07f8c26d5dce2364b105a02e793d995826cfe920 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 16 Oct 2009 18:37:11 +0100 Subject: [PATCH] updates on train! --- bin/ca-create-cert | 25 ++++++----- bin/ca-init | 78 ++++++++++++++++++-------------- doc/README | 108 +++++++++++++++++++++++++++++---------------- lib/ca-functions | 6 ++- 4 files changed, 136 insertions(+), 81 deletions(-) diff --git a/bin/ca-create-cert b/bin/ca-create-cert index 8bb814e..b9997b2 100755 --- a/bin/ca-create-cert +++ b/bin/ca-create-cert @@ -3,7 +3,7 @@ . "/home/alex/code/ca-scripts/lib/ca-functions" ALT_NAMES=() -TPL_ONLY=0 +CNF_ONLY=0 CSR_ONLY=0 CRT_ONLY=0 MAKE_P12=0 @@ -23,11 +23,12 @@ Options: -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" + -d, --days DAYS Certificate is valid for DAYS days instead of 365 -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 + -x, --cnf-only Only generate templates, do not create CSR or sign CRT --country Certificate DN -- C --state Certificate DN -- ST --loc Certificate DN -- L @@ -40,7 +41,7 @@ __EOT__ } short='hcf:t:n:prsx' -long='help,encrypt.config:,type:,alt-name:,csr-only,crt-only,tpl-only,pkcs12' +long='help,encrypt,config:,type:,alt-name:,pkcs12,csr-only,crt-only,cnf-only' 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 @@ -52,11 +53,12 @@ while :; do -c|--encrypt) CRYPTKEY=""; shift;; -f|--config) shift; CONFFILE="$1"; shift;; -t|--type) shift; CA_CRT_TYPE="$1"; shift;; + -d|--days) shift; CA_CRT_DAYS="-days $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;; + -x|--cnf-only) CNF_ONLY=1; shift;; --country) shift; CA_CRT_C="$1"; shift;; --state) shift; CA_CRT_ST="$1"; shift;; --location) shift; CA_CRT_L="$1"; shift;; @@ -111,7 +113,10 @@ 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 + # NOTE: except when it's the previously-fully-qualified CN... + # XXX: maybe we should uniq the alt-names? hmm. + if [ "${ALT_NAME%%.*}" = "$ALT_NAME" \ + -a "${CA_CRT_CN%%.*}" != "$ALT_NAME" ]; then CA_CRT_ALT_NAMES="${CA_CRT_ALT_NAMES}DNS.$i=$ALT_NAME.$CA_DOMAIN\n" i=$(( $i+1 )) fi @@ -121,7 +126,7 @@ if [ "$CA_CRT_TYPE" = "server" ]; then fi if [ 1 -ne "$CRT_ONLY" ]; then - if [ 1 -eq "$TPL_ONLY" -o "$CSR_ONLY" -eq "$TPL_ONLY" ]; then + if [ 1 -eq "$CNF_ONLY" -o "$CSR_ONLY" -eq "$CNF_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 @@ -131,7 +136,7 @@ if [ 1 -ne "$CRT_ONLY" ]; then # 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 [ 1 -ne "$CNF_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 @@ -144,12 +149,12 @@ if [ 1 -ne "$CRT_ONLY" ]; then fi fi if [ 1 -ne "$CSR_ONLY" ]; then - if [ 1 -eq "$TPL_ONLY" -o "$CRT_ONLY" -eq "$TPL_ONLY" ]; then + if [ 1 -eq "$CNF_ONLY" -o "$CRT_ONLY" -eq "$CNF_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 + if [ 1 -ne "$CNF_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" @@ -157,7 +162,7 @@ if [ 1 -ne "$CSR_ONLY" ]; then 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" \ + openssl ca -config "$CA_HOME/cnf/$CA_NAME.ca.cnf" $CA_CRT_DAYS \ -extfile "$CA_HOME/cnf/$CNF_NAME.ext.cnf" -batch \ -out "$CA_HOME/crt/$CNF_NAME.crt" \ -in "$CA_HOME/csr/$CNF_NAME.csr" diff --git a/bin/ca-init b/bin/ca-init index beab62d..910b55a 100755 --- a/bin/ca-init +++ b/bin/ca-init @@ -12,12 +12,14 @@ Options: -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 + -s, --crt-only Only generate CA cert/key, use pre-created config + -x, --cnf-only Only generate CA config file, don't create CA cert/key __EOT__ } -short='hcf:i:o:' -long='help,encrypt,config:,template:,output:' +short='hcf:i:o:sx' +long='help,encrypt,config:,template:,output:,crt-only,tpl-only' opts=$( getopt -o "$short" -l "$long" -n "$PROGNAME" -- "$@" ) if [ 0 -ne $? ]; then echo; usage; exit 1; fi eval set -- "$opts"; @@ -29,6 +31,8 @@ while :; do -f|--config) shift; CONFFILE="$1"; shift;; -i|--template) shift; INDEXTPL="$1"; shift;; -o|--output) shift; INDEXOUT="$1"; shift;; + -s|--crt-only) CRT_ONLY=1; shift;; + -x|--cnf-only) CNF_ONLY=1; shift;; --) shift; break;; *) echo "Unknown value '$1'"; exit 1;; esac @@ -38,35 +42,43 @@ done 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 +if [ 1 -eq "$CRT_ONLY" -a 1 -eq "$CNF_ONLY" ]; then + error "The --crt-only and --cnf-only options are mutually exclusive." +fi + +if [ 1 -ne "$CRT_ONLY" ]; then + # 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 -R 640 $CA_HOME + 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" +fi +if [ 1 -ne "$CNF_ONLY" ]; then + # 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 fi diff --git a/doc/README b/doc/README index 835c5f4..6b24f47 100644 --- a/doc/README +++ b/doc/README @@ -1,55 +1,89 @@ 1. Creating a Certificate Authority. + Before running ca-init(1), a configuration file for the CA scripts must be +created. This configuration file sets up some templating variables that will +be present in certificates created for this CA, such as the domain, CA name, +and the root directory which will be populated with the generated certificates. +An example configuration file is provided with the scripts, and the comments +should be self-explanatory. + By default the CA scripts will read /etc/ca-scripts.conf. This is fine for +creating a single CA serving a single domain with no intermediary certificates, +but for a more complex setup a directory of configuration files will probably +be needed. Some settings are required, namely the CA_HOME, CA_DOMAIN, and +CA_DN_* variables, while others can be inferred from these or have sensible +defaults set. See ca-scripts.conf(5) for more detail on these. + Once the configuration has been created the initial CA setup can be performed +with ca-init(1), but please note that the path set in CA_HOME must exist and be +writeable before it will run correctly. It is recommended (but not in any way +required) to create an unprivileged "ssl" user to run all the scripts as, so +the permissions are correctly set. A number of subdirectories will be set +up underneath this root, and an openssl configuration file, certificate and +private key will be generated. This key can be 3DES encrypted for security. -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. + Optionally, it is possible to split the initial setup process so that the +directory structure and openssl configuration generation can be done in a +seperate step to the generation of the CA certificates, so that the config can +be manually edited. 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 "ca_x509_extensions" section of +the config file. 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 + The ca-create-cert(1) 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. +are minor but important differences in the key usage extensions present in +these different certificate types, details can be found in the extension +templates provided with the scripts. - 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. + ca-create-cert(1) takes a number of options to customise the generated +certificate. The --type option is mandatory, and for server certs it is very +likely that the --alt-name option will be useful to set x509v3 SubjectAltName +DNS records for other hostnames for the server. Both the server hostname and +any alternative names will be fully-qualified to CA_DOMAIN if they do not +contain any dots, but if unqualified names are passed in they are also +preserved as alternative DNS names in the certificate. The private key may be +encrypted with 3DES, and optionally the certificate, key, and CA certificate +can be bundled together into a PKCS#12 format certificate archive. By default +certificates are valid for 365 days from signing, but this may be changed with +the --days option. - 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. + The certificate's DN can be completely changed from the defaults provided by +ca-scripts.conf(5), but be wary as by default the generated openssl config file +requires that the country (C) and organisation (O) fields match those of the CA +certificate. A comment may also be set that will show up in user browsers when +they click on their padlock icons to examine the certificate's properties. As +with the CA setup, the steps to generate the certificate can be split up so +that configurations that are created from templates can be edited beforehand. 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. + Certificates are renewed using ca-renew-cert(1). This script currently 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. - Renewing a certificate is done by giving the hostname, username or path to -the certificate to renew-cert.sh. + To avoid this, ca-renew-cert(1) re-signs the old certificate request with a +a new expiry date using the extensions generated when the old certificate was +signed. In the future it is possible (even likely) that this renewal method +will only be used on "user" type certificates, and the "server" and "client" +types will be renewed normally. If the current renewal method doesn't provide +sufficient security, the current certificate should be revoked and a new one +generated that is valid for the correct period of time using the --days option +to ca-create-cert(1). + + As with the certificate creation script the --type option is mandatory for +ca-renew-cert(1), but the argument may be either a hostname, a username or a +path to a certificate. Internally this will be resolved to the correct +information required for certificate renewal. 4. Revoking a certificate. diff --git a/lib/ca-functions b/lib/ca-functions index 84a2519..12f6bc1 100644 --- a/lib/ca-functions +++ b/lib/ca-functions @@ -149,7 +149,11 @@ ca_cnf_name() { 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:]@-' _ ) + -subject | awk -F= '/CN=/{ gsub("[^A-Za-z0-9@-]", "_", $2); print $2}' ) + # grep "CN=" | cut -d= -f2 | tr -c '[:alnum:]@-' _ ) + # originally did this like the above but tried awk instead. awk(1) seems + # to lie about it's egrep(1) support though as no matter what I tried the + # tr(1) regex didn't work in the gsub() call above. } ca_find_cnf() {