- think i finally understand ssl now
authorMark Huang <mlhuang@cs.princeton.edu>
Mon, 10 Jul 2006 21:06:16 +0000 (21:06 +0000)
committerMark Huang <mlhuang@cs.princeton.edu>
Mon, 10 Jul 2006 21:06:16 +0000 (21:06 +0000)
- allow CA to be configured for each ssl certificate set
- never do any root CA stuff. this is outside the scope of myplc. myplc
  now only generates self-signed certs (but supports replacement of the
  self-signed certs with real certs signed by another CA, as long as the
  CA is specified)
- self-sign the MA/SA SSL certificate (and by extension, the MA/SA API
  certificate)

api-config
plc.d/ssl

index 765da82..b7b4eee 100755 (executable)
@@ -6,7 +6,7 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: api-config,v 1.12 2006/05/30 15:06:20 mlhuang Exp $
+# $Id: api-config,v 1.13 2006/06/23 20:33:28 mlhuang Exp $
 #
 
 from plc_config import PLCConfiguration
@@ -28,9 +28,11 @@ def main():
         globals()[category_id] = dict(zip(variablelist.keys(),
                                        [variable['value'] for variable in variablelist.values()]))
 
-    # Get the issuer e-mail address of the root CA certificate
+    # Get the issuer e-mail address and public key from the root CA certificate
     root_ca_email = commands.getoutput("openssl x509 -in %s -noout -email" % \
-                                       plc['root_ca_ssl_crt'])
+                                       plc_ma_sa['ca_ssl_crt'])
+    root_ca_key_pub = commands.getoutput("openssl x509 -in %s -noout -pubkey" % \
+                                         plc_ma_sa['ca_ssl_crt'])
 
     # Verify API certificate
     if os.path.exists(plc_ma_sa['api_crt']):
@@ -38,36 +40,35 @@ def main():
         try:
             cert_xml = file(plc_ma_sa['api_crt']).read().strip()
             # Verify root CA signature
-            CertOps.authenticate_cert(cert_xml,
-                                      {root_ca_email:
-                                       file(plc['root_ca_ssl_key_pub']).read().strip()})
+            CertOps.authenticate_cert(cert_xml, {root_ca_email: root_ca_key_pub})
             # Check if MA/SA e-mail address has changed
             dom = xml.dom.minidom.parseString(cert_xml)
-            for issuer in dom.getElementsByTagName('issuer'):
-                if issuer.getAttribute('email') != plc_mail['support_address']:
+            for subject in dom.getElementsByTagName('subject'):
+                if subject.getAttribute('email') != plc_mail['support_address']:
                     raise Exception, "E-mail address '%s' in certificate '%s' does not match support address '%s'" % \
-                          (issuer.getAttribute('email'), plc_ma_sa['api_crt'], plc_mail['support_address'])
+                          (subject.getAttribute('email'), plc_ma_sa['api_crt'], plc_mail['support_address'])
         except Exception, e:
             # Delete invalid API certificate
             print "Warning: ", e
             os.unlink(plc_ma_sa['api_crt'])
 
-    # Generate API certificate
+    # Generate self-signed API certificate
     if not os.path.exists(plc_ma_sa['api_crt']):
         print "Generating new API certificate"
         try:
             cert = Certificate.Certificate('ticket-cert-0')
-            ma_sa_ssl_key_pub = file(plc_ma_sa['ssl_key_pub']).read().strip()
+            ma_sa_ssl_key_pub = commands.getoutput("openssl x509 -in %s -noout -pubkey" % \
+                                                   plc_ma_sa['ssl_crt'])
             cert.add_subject_pubkey(pubkey = ma_sa_ssl_key_pub, email = plc_mail['support_address'])
             root_ca_subject = commands.getoutput("openssl x509 -in %s -noout -subject" % \
-                                                 plc['root_ca_ssl_crt'])
+                                                 plc_ma_sa['ssl_crt'])
             m = re.search('/CN=([^/]*).*', root_ca_subject)
             if m is None:
-                root_ca_cn = plc['name'] + " Root CA"
+                root_ca_cn = plc['name'] + " Management and Slice Authority"
             else:
                 root_ca_cn = m.group(1)
             cert.set_issuer(email = root_ca_email, cn = root_ca_cn)
-            cert_xml = cert.sign(plc['root_ca_ssl_key'])
+            cert_xml = cert.sign(plc_ma_sa['ssl_key'])
             ma_sa_api_crt = file(plc_ma_sa['api_crt'], "w")
             ma_sa_api_crt.write(cert_xml)
             ma_sa_api_crt.close()
@@ -96,7 +97,7 @@ def main():
                      'MA_SA_NAMESPACE': plc_ma_sa['namespace'],
                      'SESSION_LENGTH_HOURS': "24",
                      'ROOT_CA_EMAIL': root_ca_email,
-                     'ROOT_CA_PUB_KEY': plc['root_ca_ssl_key_pub'],
+                     'ROOT_CA_PUB_KEY': plc_ma_sa['ca_ssl_key_pub'],
                      'API_CERT_PATH': plc_ma_sa['api_crt'],
                      'MA_SA_PRIVATE_KEY': plc_ma_sa['ssl_key'],
                      'PL_API_TICKET_KEY_FILE': plc_ma_sa['ssl_key']}
index d8da402..a4afb7f 100755 (executable)
--- a/plc.d/ssl
+++ b/plc.d/ssl
@@ -7,7 +7,7 @@
 # Mark Huang <mlhuang@cs.princeton.edu>
 # Copyright (C) 2006 The Trustees of Princeton University
 #
-# $Id: ssl,v 1.6 2006/06/28 20:44:17 alk Exp $
+# $Id: ssl,v 1.7 2006/06/28 21:34:18 mlhuang Exp $
 #
 
 # Source function library and configuration
 # Be verbose
 set -x
 
-mkcert ()
+# Print the CNAME of an SSL certificate
+ssl_cname ()
 {
-    CN=$1
-    KEY=$2
-    CRT=$3
-
-    # Generate a temporary CSR. We could save the CSR, but it's not
-    # worth the trouble.
-    csr=$(mktemp /tmp/csr.XXXXXX)
-
-    mkdir -p $(dirname $KEY)
-    openssl req -config /etc/planetlab/ssl/openssl.cnf \
-       -new -extensions v3_req -days 3650 -set_serial $RANDOM \
-       -batch -subj "/CN=$CN" \
-       -nodes -keyout $KEY -out $csr
-    check
-    chmod 600 $KEY
-
-    # Generate and sign certificate from CSR
-    serial=$(cat /etc/planetlab/ssl/serial)
-
-    openssl ca -config /etc/planetlab/ssl/openssl.cnf \
-       -keyfile $PLC_ROOT_CA_SSL_KEY \
-       -cert $PLC_ROOT_CA_SSL_CRT \
-       -batch -infiles $csr
-    check
-
-    mv /etc/planetlab/ssl/$serial.pem $CRT
-    chmod 644 $CRT
-
-    # Delete CSR
-    rm -f $csr
+    openssl x509 -noout -in $1 -subject | \
+       sed -n -e 's@.*/CN=\([^/]*\).*@\1@p'
 }
 
-case "$1" in
-    start)
-       MESSAGE=$"Generating SSL certificates"
-       dialog "$MESSAGE"
+# Print the emailAddress of an SSL certificate
+ssl_email ()
+{
+    openssl x509 -noout -in $1 -subject | \
+       sed -n -e 's@.*/emailAddress=\([^/]*\).*@\1@p'
+}
 
-       # Check if root CA certificate is valid
-       if [ -f $PLC_ROOT_CA_SSL_CRT ] ; then
-           verify=$(openssl verify $PLC_ROOT_CA_SSL_CRT)
-           # If self signed, assume that we generated it
-           if grep -q "self signed certificate" <<<$verify ; then
-               # Delete if expired or PLC name or e-mail address has changed
-               if grep -q "expired" <<<$verify || \
-                   [ "$(ssl_cname $PLC_ROOT_CA_SSL_CRT)" != "$PLC_NAME Root CA" ] || \
-                   [ "$(ssl_email $PLC_ROOT_CA_SSL_CRT)" != "$PLC_MAIL_SUPPORT_ADDRESS" ] ; then
-                   rm -f $PLC_ROOT_CA_SSL_CRT
-               fi
-           fi
+# Verify a certificate. If invalid, generate a new self-signed
+# certificate.
+verify_or_generate_certificate() {
+    crt=$1
+    key=$2
+    ca=$3
+    cname=$4
+    email=$5
+
+    if [ -f $crt ] ; then
+       # Check if certificate is valid
+       verify=$(openssl verify -CAfile $ca $crt)
+       # Delete if invalid or if the subject has changed
+       if grep -q "error" <<<$verify || \
+           [ "$(ssl_cname $crt)" != "$cname" ] || \
+           [ "$(ssl_email $crt)" != "$email" ] ; then
+           rm -f $crt $ca
        fi
+    fi
 
-       # Generate root CA key pair and certificate
-       if [ ! -f $PLC_ROOT_CA_SSL_CRT ] ; then
-           mkdir -p $(dirname $PLC_ROOT_CA_SSL_CRT)
-           openssl req -config /etc/planetlab/ssl/openssl.cnf \
-               -new -x509 -extensions v3_ca -days 3650 -set_serial $RANDOM \
-               -batch -subj "/CN=$PLC_NAME Root CA/emailAddress=$PLC_MAIL_SUPPORT_ADDRESS" \
-               -nodes -keyout $PLC_ROOT_CA_SSL_KEY -out $PLC_ROOT_CA_SSL_CRT
-           check
-           chmod 600 $PLC_ROOT_CA_SSL_KEY
-           chmod 644 $PLC_ROOT_CA_SSL_CRT
-
-           # API certificate verification requires a public key
-           openssl rsa -pubout <$PLC_ROOT_CA_SSL_KEY >$PLC_ROOT_CA_SSL_KEY_PUB
-           check
-           chmod 644 $PLC_ROOT_CA_SSL_KEY_PUB
-
-           # Reset DB
-           >/etc/planetlab/ssl/index.txt
-           echo "01" >/etc/planetlab/ssl/serial
+    if [ ! -f $crt ] ; then
+        # Set subject
+       subj=
+       if [ -n "$cname" ] ; then
+           subj="$subj/CN=$cname"
        fi
-
-       # Check if MA/SA certificate is valid
-       if [ -f $PLC_MA_SA_SSL_CRT ] ; then
-           verify=$(openssl verify -CAfile $PLC_ROOT_CA_SSL_CRT $PLC_MA_SA_SSL_CRT)
-           # Delete if expired or not signed correctly
-           if grep -q "error" <<<$verify ; then
-               rm -f $PLC_MA_SA_SSL_CRT
-           fi
+       if [ -n "$email" ] ; then
+           subj="$subj/emailAddress=$email"
        fi
 
-       # Generate MA/SA key pair and certificate
-       if [ ! -f $PLC_MA_SA_SSL_CRT ] ; then
-           mkcert "$PLC_NAME Management and Slice Authority" \
-               $PLC_MA_SA_SSL_KEY $PLC_MA_SA_SSL_CRT
-
-           # Make readable by apache so that the API can sign certificates
-           chown apache $PLC_MA_SA_SSL_KEY
-           chmod 600 $PLC_MA_SA_SSL_KEY
+       # Generate new self-signed certificate
+       mkdir -p $(dirname $crt)
+       openssl req -new -x509 -days 3650 -set_serial $RANDOM \
+           -batch -subj "$subj" \
+           -nodes -keyout $key -out $crt
+       check
+       chmod 644 $crt
+    fi
+
+    if [ ! -f $ca ] ; then
+        # The certificate it self-signed, so it is its own CA
+       cp -a $crt $ca
+    fi
+}
 
-           # API requires a public key for slice ticket verification
-           openssl rsa -pubout <$PLC_MA_SA_SSL_KEY >$PLC_MA_SA_SSL_KEY_PUB
-           check
-           chmod 644 $PLC_MA_SA_SSL_KEY_PUB
-       fi
+case "$1" in
+    start)
+       MESSAGE=$"Generating SSL certificates"
+       dialog "$MESSAGE"
 
-       # Generate self-signed HTTPS certificate(s). These nice
-       # commands come from the mod_ssl spec file for Fedora Core
-       # 2. We generate a certificate for each enabled server
-       # with a different hostname. These self-signed
-       # certificates may be overridden later.
+       # Verify or generate MA/SA certificate if necessary. This
+       # self-signed certificate may be overridden later.
+       verify_or_generate_certificate \
+           $PLC_MA_SA_SSL_CRT $PLC_MA_SA_SSL_KEY $PLC_MA_SA_CA_SSL_CRT \
+           "$PLC_NAME Management and Slice Authority" \
+           $PLC_MAIL_SUPPORT_ADDRESS
+
+       # Make MA/SA key readable by apache so that the API can sign
+       # certificates
+       chown apache $PLC_MA_SA_SSL_KEY
+       chmod 600 $PLC_MA_SA_SSL_KEY
+
+       # Extract the public key of the root CA (if any) that signed
+       # the MA/SA certificate.
+       openssl x509 -in $PLC_MA_SA_CA_SSL_CRT -noout -pubkey >$PLC_MA_SA_CA_SSL_KEY_PUB
+       check
+       chmod 644 $PLC_MA_SA_CA_SSL_KEY_PUB
+
+       # Generate HTTPS certificates if necessary. We generate a
+       # certificate for each enabled server with a different
+       # hostname. These self-signed certificates may be overridden
+       # later.
        for server in WWW API BOOT ; do
            ssl_key=PLC_${server}_SSL_KEY
            ssl_crt=PLC_${server}_SSL_CRT
+           ca_ssl_crt=PLC_${server}_CA_SSL_CRT
            hostname=PLC_${server}_HOST
 
            # Check if we have already generated a certificate for
@@ -133,38 +117,22 @@ case "$1" in
                fi
                previous_ssl_key=PLC_${previous_server}_SSL_KEY
                previous_ssl_crt=PLC_${previous_server}_SSL_CRT
+               previous_ca_ssl_crt=PLC_${previous_server}_CA_SSL_CRT
                previous_hostname=PLC_${previous_server}_HOST
 
                if [ -f ${!previous_ssl_crt} ] && \
                    [ "$(ssl_cname ${!previous_ssl_crt})" = "${!hostname}" ] ; then
                    cp -a ${!previous_ssl_key} ${!ssl_key}
                    cp -a ${!previous_ssl_crt} ${!ssl_crt}
+                   cp -a ${!previous_ca_ssl_crt} ${!ca_ssl_crt}
                    break
                fi
            done
 
-           # Check if self-signed certificate is valid
-           if [ -f ${!ssl_crt} ] ; then
-               verify=$(openssl verify ${!ssl_crt})
-               # If self-signed
-               if grep -q "self signed certificate" <<<$verify ; then
-                   # Delete if expired or hostname changed
-                   if grep -q "expired" <<<$verify || \
-                       [ "$(ssl_cname ${!ssl_crt})" != "${!hostname}" ] ; then
-                       rm -f ${!ssl_crt}
-                   fi
-               fi
-           fi
+           verify_or_generate_certificate \
+               ${!ssl_crt} ${!ssl_key} ${!ca_ssl_crt} \
+               ${!hostname} $PLC_MAIL_SUPPORT_ADDRESS
 
-           # Generate new self-signed certificate
-           if [ ! -f ${!ssl_crt} ] ; then
-               mkdir -p $(dirname ${!ssl_crt})
-               openssl req -new -x509 -days 3650 -set_serial $RANDOM \
-                   -batch -subj "/CN=${!hostname}" \
-                   -nodes -keyout ${!ssl_key} -out ${!ssl_crt}
-               check
-               chmod 644 ${!ssl_crt}
-           fi
        done
 
        # Install HTTPS certificates into both /etc/pki (Fedora Core