1#!/bin/sh
2#
3# Licensed to the Apache Software Foundation (ASF) under one or more
4# contributor license agreements.  See the NOTICE file distributed with
5# this work for additional information regarding copyright ownership.
6# The ASF licenses this file to You under the Apache License, Version 2.0
7# (the "License"); you may not use this file except in compliance with
8# the License.  You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18# This script will populate a directory 'sni' with 3 sites, httpd.conf
19# and certificates as to facilitate testing of TLS server name
20# indication support (RFC 4366) or SNI.
21#
22#
23OPENSSL=${OPENSSL:-openssl}
24DOMAIN=${DOMAIN:-my-sni-test.org}
25DIR=${DIR:-$PWD/sni}
26
27# List of hostnames automatically created by default.
28NAMES=${NAMES:-ape nut pear apple banana}
29
30# IP address these hostnames are bound to.
31IP=${IP:-127.0.0.1}
32
33# A certificate password for the .p12 files of the client
34# authentication test. Normally not set. However some browsers
35# require a password of at least 4 characters.
36#
37PASSWD=${PASSWD:-}
38
39args=`getopt a:fd:D:p: $*`
40if [ $? != 0 ]; then
41    echo "Syntax: $0 [-f] [-a IPaddress] [-d outdir] [-D domain ] [two or more vhost names ]"
42    echo "    -f        Force overwriting of outdir (default is $DIR)"
43    echo "    -d dir    Directory to create the SNI test server in (default is $DIR)"
44    echo "    -D domain Domain name to use for this test (default is $DOMAIN)"
45    echo "    -a IP     IP address to use for this virtual host (default is $IP)"
46    echo "    -p str    Password for the client certificate test (some browsers require a set password)"
47    echo "    [names]   List of optional vhost names (default is $NAMES)"
48    echo
49    echo "Example:"
50    echo "    $0 -D SecureBlogsAreUs.com peter fred mary jane ardy"
51    echo
52    echo "Which will create peter.SecureBlogsAreUs.com, fred.SecureBlogsAreUs.com and"
53    echo "so on. Note that the _first_ FQDN is also the default for non SNI hosts. It"
54    echo "may make sense to give this host a generic name - and allow each of the real"
55    echo "SNI site as sub directories/URI's of this generic name; thus allowing the "
56    echo "few non-SNI browsers access."
57    exit 1
58fi
59set -- $args
60for i
61do
62    case "$i"
63    in
64        -f)
65            FORCE=1
66            shift;;
67        -a)
68            IP=$2; shift
69            shift;;
70        -d)
71            DIR=$2; shift
72            shift;;
73        -p)
74            PASSWD=$2; shift
75            shift;;
76        -D)
77            DOMAIN=$2; shift
78            shift;;
79        --)
80            shift; break;
81    esac
82done
83
84if [ $# = 1 ]; then
85    echo "Aborted - just specifying one vhost makes no sense for SNI testing. Go wild !"
86    exit 1
87fi
88
89if [ $# -gt 0 ]; then
90    NAMES=$*
91fi
92
93if ! openssl version | grep -q OpenSSL; then
94    echo Aborted - your openssl is very old or misconfigured.
95    exit 1
96fi
97
98set `openssl version`
99if test "0$2" \< "00.9"; then
100    echo Aborted - version of openssl too old, 0.9 or up required.
101    exit 1
102fi
103
104if test -d ${DIR} -a "x$FORCE" != "x1"; then
105    echo Aborted - already an ${DIR} directory. Use the -f flag to overwrite.
106    exit 1
107fi
108
109mkdir -p ${DIR} || exit 1
110mkdir -p ${DIR}/ssl ${DIR}/htdocs ${DIR}/logs || exit 1
111
112# Create a 'CA' - keep using different serial numbers
113# as the browsers get upset if they see an identical
114# serial with a different pub-key.
115#
116# Note that we're not relying on the 'v3_ca' section as
117# in the default openssl.conf file - so the certificate
118# will be without the basicConstraints = CA:true and
119# keyUsage = cRLSign, keyCertSign values. This is fine
120# for most browsers.
121#
122serial=$RANDOM$$
123
124openssl req -new -nodes -batch \
125    -x509  \
126    -days 10 -subj '/CN=Da Root/O=SNI testing/' -set_serial $serial \
127    -keyout ${DIR}/root.key -out ${DIR}/root.pem  \
128    || exit 2
129
130CDIR=${DIR}/client-xs-control
131mkdir -p ${CDIR}
132# Create some certificate authorities for testing client controls
133#
134openssl req -new -nodes -batch \
135    -x509  \
136    -days 10 -subj '/CN=Da Second Root/O=SNI user access I/' -set_serial 2$serial$$\
137    -keyout ${CDIR}/xs-root-1.key -out ${CDIR}/xs-root-1.pem  \
138    || exit 2
139
140openssl req -new -nodes -batch \
141    -x509  \
142    -days 10 -subj '/CN=Da Second Root/O=SNI user access II/' -set_serial 3$serial$$ \
143    -keyout ${CDIR}/xs-root-2.key -out ${CDIR}/xs-root-2.pem  \
144    || exit 2
145
146# Create a chain of just the two access authorities:
147cat ${CDIR}/xs-root-2.pem ${CDIR}/xs-root-1.pem > ${CDIR}/xs-root-chain.pem
148
149# And likewise a directory with the same information (using the
150# required 'hash' naming format
151#
152mkdir -p ${CDIR}/xs-root-dir || exit 1
153rm -f {$CDIR}/*.0
154ln ${CDIR}/xs-root-1.pem ${CDIR}/xs-root-dir/`openssl x509 -noout -hash -in ${CDIR}/xs-root-1.pem`.0
155ln ${CDIR}/xs-root-2.pem ${CDIR}/xs-root-dir/`openssl x509 -noout -hash -in ${CDIR}/xs-root-2.pem`.0
156
157# Use the above two client certificate authorities to make a few users
158for i in 1 2
159do
160    # Create a certificate request for a test user.
161    #
162    openssl req -new -nodes -batch \
163        -days 9 -subj "/CN=User $i/O=SNI Test Crash Dummy Dept/" \
164        -keyout ${CDIR}/client-$i.key -out ${CDIR}/client-$i.req -batch  \
165                || exit 3
166
167    # And get it signed by either our client cert issuing root authority.
168    #
169    openssl x509 -text -req \
170        -CA ${CDIR}/xs-root-$i.pem -CAkey ${CDIR}/xs-root-$i.key \
171        -set_serial 3$serial$$ -in ${CDIR}/client-$i.req -out ${CDIR}/client-$i.pem \
172                || exit 4
173
174    # And create a pkcs#12 version for easy browser import.
175    #
176    openssl pkcs12 -export \
177        -inkey ${CDIR}/client-$i.key -in ${CDIR}/client-$i.pem -name "Client $i" \
178        -caname "Issuing client root $i" -certfile ${CDIR}/xs-root-$i.pem  \
179        -out ${CDIR}/client.p12 -passout pass:"$PASSWD" || exit 5
180
181    rm ${CDIR}/client-$i.req
182done
183
184# Create the header for the example '/etc/hosts' file.
185#
186echo '# To append to your hosts file' > ${DIR}/hosts
187
188# Create a header for the httpd.conf snipped.
189#
190cat > ${DIR}/httpd-sni.conf << EOM
191# To append to your httpd.conf file'
192Listen ${IP}:443
193NameVirtualHost ${IP}:443
194
195LoadModule ssl_module modules/mod_ssl.so
196
197SSLRandomSeed startup builtin
198SSLRandomSeed connect builtin
199
200LogLevel debug
201TransferLog ${DIR}/logs/access_log
202ErrorLog ${DIR}/logs/error_log
203
204# You'll get a warning about this.
205#
206SSLSessionCache none
207
208# Note that this SSL configuration is far
209# from complete - you probably will want
210# to configure SSLSession Caches at the 
211# very least.
212
213<Directory />
214    Options None
215    AllowOverride None
216    Require all denied
217</Directory>
218
219<Directory "${DIR}/htdocs">
220    allow from all
221    Require all granted
222</Directory>
223
224# This first entry is also the default for non SNI
225# supporting clients.
226#
227EOM
228
229# Create the header of a sample BIND zone file.
230#
231(
232        echo "; Configuration sample to be added to the $DOMAIN zone file of BIND."
233        echo "\$ORIGIN $DOMAIN."
234) > ${DIR}/zone-file
235
236ZADD="IN A $IP"
237INFO="and also the site you see when the browser does not support SNI."
238
239set -- ${NAMES}
240DEFAULT=$1
241
242for n in ${NAMES}
243do
244    FQDN=$n.$DOMAIN
245    serial=`expr $serial + 1`
246
247    # Create a certificate request for this host.
248    #
249    openssl req -new -nodes -batch \
250        -days 9 -subj "/CN=$FQDN/O=SNI Testing/" \
251        -keyout ${DIR}/$n.key -out ${DIR}/$n.req -batch  \
252                || exit 3
253
254    # And get it signed by our root authority.
255    #
256    openssl x509 -text -req \
257        -CA ${DIR}/root.pem -CAkey ${DIR}/root.key \
258        -set_serial $serial -in ${DIR}/$n.req -out ${DIR}/$n.pem \
259                || exit 4
260
261    # Combine the key and certificate in one file.
262    #
263    cat ${DIR}/$n.pem ${DIR}/$n.key > ${DIR}/ssl/$n.crt
264    rm ${DIR}/$n.req ${DIR}/$n.key ${DIR}/$n.pem
265
266    LST="$LST
267    https://$FQDN/index.html"
268
269    # Create a /etc/host and bind-zone file example
270    #
271    echo "${IP}         $FQDN $n" >> ${DIR}/hosts
272    echo "$n    $ZADD" >> ${DIR}/zone-file
273    ZADD="IN CNAME $DEFAULT"
274
275    # Create and populate a docroot for this host.
276    #
277    mkdir -p ${DIR}/htdocs/$n || exit 1
278    echo We are $FQDN $INFO > ${DIR}/htdocs/$n/index.html || exit 1
279
280    # And change the info text - so that only the default/fallback site
281    # gets marked as such.
282    #
283    INFO="and you'd normally only see this site when there is proper SNI support."
284
285    # And create a configuration snipped.
286    #
287    cat >> ${DIR}/httpd-sni.conf << EOM
288<VirtualHost ${IP}:443>
289    SSLEngine On
290    ServerName $FQDN:443
291    DocumentRoot ${DIR}/htdocs/$n
292    SSLCertificateChainFile ${DIR}/root.pem
293    SSLCertificateFile ${DIR}/ssl/$n.crt
294
295    # Uncomment the following lines if you
296    # want to only allow access to clients with
297    # a certificate issued/signed by some 
298    # selection of the issuing authorities
299    #
300    # SSLCACertificate ${CDIR}/xs-root-1.pem # just root 1
301    # SSLCACertificate ${CDIR}/xs-root-2.pem # just root 2
302    # SSLCACertificate ${CDIR}/xs-root-chain.pem # 1 & 2 
303    # SSLCACertificateDir ${CDIR}/xs-root-dir # 1 & 2 - but as a directory.
304    #
305    # SSLVerifyClient require
306    # SSLVerifyDepth 2
307    # 
308    TransferLog ${DIR}/logs/access_$n
309</VirtualHost>
310
311EOM
312
313done
314
315cat << EOM
316SNI Files generated
317===================
318
319The directory ${DIR}/sni has been populated with the following
320
321-       root.key|pem    Certificate authority root and key. (You could
322                        import the root.pem key into your browser to
323                        quell warnings about an unknown authority).
324
325-       hosts           /etc/hosts file with fake entries for the hosts
326
327-       htdocs          directory with one docroot for each domain,
328                        each with a small sample file.
329
330-       ssl             directory with an ssl cert (signed by root)
331                        for each of the domains).
332
333-       logs            logfiles, one for each domain and an
334                        access_log for any misses.
335
336The directory ${CDIR} contains optional test files to allow client
337authentication testing:
338
339-       client*pem/p12  Files for client authentication testing. These
340                        need to be imported into the browser.
341
342-       xs-root-1/2     Certificate authority which has issued above
343                        client authentication certificates.
344
345-       xs-root-dir     A directory specific for the SSLCACertificateDir
346                        directive.
347
348-       xs-root-chain   A chain of the two client xs authorities for the
349                        SSLCACertificate directive.
350
351SNI Test
352========
353
354A directory ${DIR}/sni has been created. Run an apache
355server against it with
356
357    .../httpd -f ${DIR}/httpd-sni.conf
358
359and keep an eye on ${DIR}/logs/error_log. When everything 
360is fine you will see entries like:
361
362    Feb 11 16:12:26 2008] [debug] Init: 
363        SSL server IP/port overlap: ape.*:443 (httpd-sni.conf:24) vs. jane.*:443 (httpd-sni.conf:42)
364
365for each vhost configured and a concluding warning:
366
367    [Mon Feb 11 16:12:26 2008] [warn] Init: 
368        Name-based SSL virtual hosts only work for clients with TLS server name indication support (RFC 4366)
369
370HOWEVER - If you see an entry like:
371
372    [Mon Feb 11 15:41:41 2008] [warn] Init: 
373        You should not use name-based virtual hosts in conjunction with SSL!!
374
375then you are either using an OpenSSL which is too old and/or you need to ensure that the
376TLS Extensions are compiled into openssl with the 'enable-tlsext' flag. Once you have
377recompiled or reinstalled OpenSSL with TLS Extensions you will have to recompile mod_ssl
378to allow it to recognize SNI support.
379
380Meanwhile add 'hosts' to your c:\windows\system32\drivers\etc\hosts
381or /etc/hosts file as to point the various URL's to your server:
382$LST
383
384and verify that each returns its own name (and an entry in its
385own ${DIR}/logs) file).
386
387NOTE
388====
389
390Note that in the generated example the 'first' domain is special - and is the
391catch all for non-SNI browsers. Depending on your circumstances it may make
392sense to use a generic name - and have each of the SNI domains as subdirectories
393(and hence URI's under this generic name). Thus allowing non SNI browsers also
394access to those sites.
395EOM
396exit 0
397