From c422d72098b31a8013b443663c4498afd80fd418 Mon Sep 17 00:00:00 2001 From: Mark Huang Date: Mon, 10 Jul 2006 21:06:16 +0000 Subject: [PATCH] - think i finally understand ssl now - 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 | 31 +++++---- plc.d/ssl | 196 ++++++++++++++++++++++------------------------------- 2 files changed, 98 insertions(+), 129 deletions(-) diff --git a/api-config b/api-config index 765da82..b7b4eee 100755 --- a/api-config +++ b/api-config @@ -6,7 +6,7 @@ # Mark Huang # 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']} diff --git a/plc.d/ssl b/plc.d/ssl index d8da402..a4afb7f 100755 --- a/plc.d/ssl +++ b/plc.d/ssl @@ -7,7 +7,7 @@ # Mark Huang # 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 @@ -17,112 +17,96 @@ # 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 -- 2.45.2