| #!/bin/sh | |
| # | |
| # Licensed to the Apache Software Foundation (ASF) under one or more | |
| # contributor license agreements. See the NOTICE file distributed with | |
| # this work for additional information regarding copyright ownership. | |
| # The ASF licenses this file to You under the Apache License, Version 2.0 | |
| # (the "License"); you may not use this file except in compliance with | |
| # the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| # This script will populate a directory 'sni' with 3 sites, httpd.conf | |
| # and certificates as to facilitate testing of TLS server name | |
| # indication support (RFC 4366) or SNI. | |
| # | |
| # | |
| OPENSSL=${OPENSSL:-openssl} | |
| DOMAIN=${DOMAIN:-my-sni-test.org} | |
| DIR=${DIR:-$PWD/sni} | |
| # List of hostnames automatically created by default. | |
| NAMES=${NAMES:-ape nut pear apple banana} | |
| # IP address these hostnames are bound to. | |
| IP=${IP:-127.0.0.1} | |
| # A certificate password for the .p12 files of the client | |
| # authentication test. Normally not set. However some browsers | |
| # require a password of at least 4 characters. | |
| # | |
| PASSWD=${PASSWD:-} | |
| args=`getopt a:fd:D:p: $*` | |
| if [ $? != 0 ]; then | |
| echo "Syntax: $0 [-f] [-a IPaddress] [-d outdir] [-D domain ] [two or more vhost names ]" | |
| echo " -f Force overwriting of outdir (default is $DIR)" | |
| echo " -d dir Directory to create the SNI test server in (default is $DIR)" | |
| echo " -D domain Domain name to use for this test (default is $DOMAIN)" | |
| echo " -a IP IP address to use for this virtual host (default is $IP)" | |
| echo " -p str Password for the client certificate test (some browsers require a set password)" | |
| echo " [names] List of optional vhost names (default is $NAMES)" | |
| echo | |
| echo "Example:" | |
| echo " $0 -D SecureBlogsAreUs.com peter fred mary jane ardy" | |
| echo | |
| echo "Which will create peter.SecureBlogsAreUs.com, fred.SecureBlogsAreUs.com and" | |
| echo "so on. Note that the _first_ FQDN is also the default for non SNI hosts. It" | |
| echo "may make sense to give this host a generic name - and allow each of the real" | |
| echo "SNI site as sub directories/URI's of this generic name; thus allowing the " | |
| echo "few non-SNI browsers access." | |
| exit 1 | |
| fi | |
| set -- $args | |
| for i | |
| do | |
| case "$i" | |
| in | |
| -f) | |
| FORCE=1 | |
| shift;; | |
| -a) | |
| IP=$2; shift | |
| shift;; | |
| -d) | |
| DIR=$2; shift | |
| shift;; | |
| -p) | |
| PASSWD=$2; shift | |
| shift;; | |
| -D) | |
| DOMAIN=$2; shift | |
| shift;; | |
| --) | |
| shift; break; | |
| esac | |
| done | |
| if [ $# = 1 ]; then | |
| echo "Aborted - just specifying one vhost makes no sense for SNI testing. Go wild !" | |
| exit 1 | |
| fi | |
| if [ $# -gt 0 ]; then | |
| NAMES=$* | |
| fi | |
| if ! openssl version | grep -Eq 'LibreSSL|OpenSSL'; then | |
| echo Aborted - your openssl is very old or misconfigured. | |
| exit 1 | |
| fi | |
| set `openssl version` | |
| if test "0$2" \< "00.9"; then | |
| echo Aborted - version of openssl too old, 0.9 or up required. | |
| exit 1 | |
| fi | |
| if test -d ${DIR} -a "x$FORCE" != "x1"; then | |
| echo Aborted - already an ${DIR} directory. Use the -f flag to overwrite. | |
| exit 1 | |
| fi | |
| mkdir -p ${DIR} || exit 1 | |
| mkdir -p ${DIR}/ssl ${DIR}/htdocs ${DIR}/logs || exit 1 | |
| # Create a 'CA' - keep using different serial numbers | |
| # as the browsers get upset if they see an identical | |
| # serial with a different pub-key. | |
| # | |
| # Note that we're not relying on the 'v3_ca' section as | |
| # in the default openssl.conf file - so the certificate | |
| # will be without the basicConstraints = CA:true and | |
| # keyUsage = cRLSign, keyCertSign values. This is fine | |
| # for most browsers. | |
| # | |
| serial=$RANDOM$$ | |
| openssl req -new -nodes -batch \ | |
| -x509 \ | |
| -days 10 -subj '/CN=Da Root/O=SNI testing/' -set_serial $serial \ | |
| -keyout ${DIR}/root.key -out ${DIR}/root.pem \ | |
| || exit 2 | |
| CDIR=${DIR}/client-xs-control | |
| mkdir -p ${CDIR} | |
| # Create some certificate authorities for testing client controls | |
| # | |
| openssl req -new -nodes -batch \ | |
| -x509 \ | |
| -days 10 -subj '/CN=Da Second Root/O=SNI user access I/' -set_serial 2$serial$$\ | |
| -keyout ${CDIR}/xs-root-1.key -out ${CDIR}/xs-root-1.pem \ | |
| || exit 2 | |
| openssl req -new -nodes -batch \ | |
| -x509 \ | |
| -days 10 -subj '/CN=Da Second Root/O=SNI user access II/' -set_serial 3$serial$$ \ | |
| -keyout ${CDIR}/xs-root-2.key -out ${CDIR}/xs-root-2.pem \ | |
| || exit 2 | |
| # Create a chain of just the two access authorities: | |
| cat ${CDIR}/xs-root-2.pem ${CDIR}/xs-root-1.pem > ${CDIR}/xs-root-chain.pem | |
| # And likewise a directory with the same information (using the | |
| # required 'hash' naming format | |
| # | |
| mkdir -p ${CDIR}/xs-root-dir || exit 1 | |
| rm -f {$CDIR}/*.0 | |
| ln ${CDIR}/xs-root-1.pem ${CDIR}/xs-root-dir/`openssl x509 -noout -hash -in ${CDIR}/xs-root-1.pem`.0 | |
| ln ${CDIR}/xs-root-2.pem ${CDIR}/xs-root-dir/`openssl x509 -noout -hash -in ${CDIR}/xs-root-2.pem`.0 | |
| # Use the above two client certificate authorities to make a few users | |
| for i in 1 2 | |
| do | |
| # Create a certificate request for a test user. | |
| # | |
| openssl req -new -nodes -batch \ | |
| -days 9 -subj "/CN=User $i/O=SNI Test Crash Dummy Dept/" \ | |
| -keyout ${CDIR}/client-$i.key -out ${CDIR}/client-$i.req -batch \ | |
| || exit 3 | |
| # And get it signed by either our client cert issuing root authority. | |
| # | |
| openssl x509 -text -req \ | |
| -CA ${CDIR}/xs-root-$i.pem -CAkey ${CDIR}/xs-root-$i.key \ | |
| -set_serial 3$serial$$ -in ${CDIR}/client-$i.req -out ${CDIR}/client-$i.pem \ | |
| || exit 4 | |
| # And create a pkcs#12 version for easy browser import. | |
| # | |
| openssl pkcs12 -export \ | |
| -inkey ${CDIR}/client-$i.key -in ${CDIR}/client-$i.pem -name "Client $i" \ | |
| -caname "Issuing client root $i" -certfile ${CDIR}/xs-root-$i.pem \ | |
| -out ${CDIR}/client.p12 -passout pass:"$PASSWD" || exit 5 | |
| rm ${CDIR}/client-$i.req | |
| done | |
| # Create the header for the example '/etc/hosts' file. | |
| # | |
| echo '# To append to your hosts file' > ${DIR}/hosts | |
| # Create a header for the httpd.conf snipped. | |
| # | |
| cat > ${DIR}/httpd-sni.conf << EOM | |
| # To append to your httpd.conf file' | |
| Listen ${IP}:443 | |
| NameVirtualHost ${IP}:443 | |
| LoadModule ssl_module modules/mod_ssl.so | |
| SSLRandomSeed startup builtin | |
| SSLRandomSeed connect builtin | |
| LogLevel debug | |
| TransferLog ${DIR}/logs/access_log | |
| ErrorLog ${DIR}/logs/error_log | |
| # You'll get a warning about this. | |
| # | |
| SSLSessionCache none | |
| # Note that this SSL configuration is far | |
| # from complete - you probably will want | |
| # to configure SSLSession Caches at the | |
| # very least. | |
| <Directory /> | |
| Options None | |
| AllowOverride None | |
| Require all denied | |
| </Directory> | |
| <Directory "${DIR}/htdocs"> | |
| allow from all | |
| Require all granted | |
| </Directory> | |
| # This first entry is also the default for non SNI | |
| # supporting clients. | |
| # | |
| EOM | |
| # Create the header of a sample BIND zone file. | |
| # | |
| ( | |
| echo "; Configuration sample to be added to the $DOMAIN zone file of BIND." | |
| echo "\$ORIGIN $DOMAIN." | |
| ) > ${DIR}/zone-file | |
| ZADD="IN A $IP" | |
| INFO="and also the site you see when the browser does not support SNI." | |
| set -- ${NAMES} | |
| DEFAULT=$1 | |
| for n in ${NAMES} | |
| do | |
| FQDN=$n.$DOMAIN | |
| serial=`expr $serial + 1` | |
| # Create a certificate request for this host. | |
| # | |
| openssl req -new -nodes -batch \ | |
| -days 9 -subj "/CN=$FQDN/O=SNI Testing/" \ | |
| -keyout ${DIR}/$n.key -out ${DIR}/$n.req -batch \ | |
| || exit 3 | |
| # And get it signed by our root authority. | |
| # | |
| openssl x509 -text -req \ | |
| -CA ${DIR}/root.pem -CAkey ${DIR}/root.key \ | |
| -set_serial $serial -in ${DIR}/$n.req -out ${DIR}/$n.pem \ | |
| || exit 4 | |
| # Combine the key and certificate in one file. | |
| # | |
| cat ${DIR}/$n.pem ${DIR}/$n.key > ${DIR}/ssl/$n.crt | |
| rm ${DIR}/$n.req ${DIR}/$n.key ${DIR}/$n.pem | |
| LST="$LST | |
| https://$FQDN/index.html" | |
| # Create a /etc/host and bind-zone file example | |
| # | |
| echo "${IP} $FQDN $n" >> ${DIR}/hosts | |
| echo "$n $ZADD" >> ${DIR}/zone-file | |
| ZADD="IN CNAME $DEFAULT" | |
| # Create and populate a docroot for this host. | |
| # | |
| mkdir -p ${DIR}/htdocs/$n || exit 1 | |
| echo We are $FQDN $INFO > ${DIR}/htdocs/$n/index.html || exit 1 | |
| # And change the info text - so that only the default/fallback site | |
| # gets marked as such. | |
| # | |
| INFO="and you'd normally only see this site when there is proper SNI support." | |
| # And create a configuration snipped. | |
| # | |
| cat >> ${DIR}/httpd-sni.conf << EOM | |
| <VirtualHost ${IP}:443> | |
| SSLEngine On | |
| ServerName $FQDN:443 | |
| DocumentRoot ${DIR}/htdocs/$n | |
| SSLCertificateChainFile ${DIR}/root.pem | |
| SSLCertificateFile ${DIR}/ssl/$n.crt | |
| # Uncomment the following lines if you | |
| # want to only allow access to clients with | |
| # a certificate issued/signed by some | |
| # selection of the issuing authorities | |
| # | |
| # SSLCACertificate ${CDIR}/xs-root-1.pem # just root 1 | |
| # SSLCACertificate ${CDIR}/xs-root-2.pem # just root 2 | |
| # SSLCACertificate ${CDIR}/xs-root-chain.pem # 1 & 2 | |
| # SSLCACertificateDir ${CDIR}/xs-root-dir # 1 & 2 - but as a directory. | |
| # | |
| # SSLVerifyClient require | |
| # SSLVerifyDepth 2 | |
| # | |
| TransferLog ${DIR}/logs/access_$n | |
| </VirtualHost> | |
| EOM | |
| done | |
| cat << EOM | |
| SNI Files generated | |
| =================== | |
| The directory ${DIR}/sni has been populated with the following | |
| - root.key|pem Certificate authority root and key. (You could | |
| import the root.pem key into your browser to | |
| quell warnings about an unknown authority). | |
| - hosts /etc/hosts file with fake entries for the hosts | |
| - htdocs directory with one docroot for each domain, | |
| each with a small sample file. | |
| - ssl directory with an ssl cert (signed by root) | |
| for each of the domains). | |
| - logs logfiles, one for each domain and an | |
| access_log for any misses. | |
| The directory ${CDIR} contains optional test files to allow client | |
| authentication testing: | |
| - client*pem/p12 Files for client authentication testing. These | |
| need to be imported into the browser. | |
| - xs-root-1/2 Certificate authority which has issued above | |
| client authentication certificates. | |
| - xs-root-dir A directory specific for the SSLCACertificateDir | |
| directive. | |
| - xs-root-chain A chain of the two client xs authorities for the | |
| SSLCACertificate directive. | |
| SNI Test | |
| ======== | |
| A directory ${DIR}/sni has been created. Run an apache | |
| server against it with | |
| .../httpd -f ${DIR}/httpd-sni.conf | |
| and keep an eye on ${DIR}/logs/error_log. When everything | |
| is fine you will see entries like: | |
| Feb 11 16:12:26 2008] [debug] Init: | |
| SSL server IP/port overlap: ape.*:443 (httpd-sni.conf:24) vs. jane.*:443 (httpd-sni.conf:42) | |
| for each vhost configured and a concluding warning: | |
| [Mon Feb 11 16:12:26 2008] [warn] Init: | |
| Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366) | |
| HOWEVER - If you see an entry like: | |
| [Mon Feb 11 15:41:41 2008] [warn] Init: | |
| You should not use name-based virtual hosts in conjunction with SSL!! | |
| then you are either using an OpenSSL which is too old and/or you need to ensure that the | |
| TLS Extensions are compiled into openssl with the 'enable-tlsext' flag. Once you have | |
| recompiled or reinstalled OpenSSL with TLS Extensions you will have to recompile mod_ssl | |
| to allow it to recognize SNI support. | |
| Meanwhile add 'hosts' to your c:\windows\system32\drivers\etc\hosts | |
| or /etc/hosts file as to point the various URL's to your server: | |
| $LST | |
| and verify that each returns its own name (and an entry in its | |
| own ${DIR}/logs) file). | |
| NOTE | |
| ==== | |
| Note that in the generated example the 'first' domain is special - and is the | |
| catch all for non-SNI browsers. Depending on your circumstances it may make | |
| sense to use a generic name - and have each of the SNI domains as subdirectories | |
| (and hence URI's under this generic name). Thus allowing non SNI browsers also | |
| access to those sites. | |
| EOM | |
| exit 0 |