ovs-bugtool: Add "ovs-appctl coverage/show" output to bugtool.
[openvswitch] / utilities / ovs-pki.in
1 #! /bin/sh
2
3 # Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira Networks, Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 set -e
18
19 pkidir='@PKIDIR@'
20 command=
21 prev=
22 force=no
23 batch=no
24 log='@LOGDIR@/ovs-pki.log'
25 keytype=rsa
26 bits=2048
27 for option; do
28     # This option-parsing mechanism borrowed from a Autoconf-generated
29     # configure script under the following license:
30
31     # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
32     # 2002, 2003, 2004, 2005, 2006, 2009 Free Software Foundation, Inc.
33     # This configure script is free software; the Free Software Foundation
34     # gives unlimited permission to copy, distribute and modify it.
35
36     # If the previous option needs an argument, assign it.
37     if test -n "$prev"; then
38         eval $prev=\$option
39         prev=
40         continue
41     fi
42     case $option in
43         *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
44         *) optarg=yes ;;
45     esac
46
47     case $dashdash$option in
48         --)
49             dashdash=yes ;;
50         -h|--help)
51             cat <<EOF
52 ovs-pki, for managing a simple OpenFlow public key infrastructure 
53 usage: $0 [OPTION...] COMMAND [ARG...]
54
55 The valid stand-alone commands and their arguments are:
56   init                 Initialize the PKI
57   req NAME             Create new private key and certificate request
58                        named NAME-privkey.pem and NAME-req.pem, resp.
59   sign NAME [TYPE]     Sign switch certificate request NAME-req.pem,
60                        producing certificate NAME-cert.pem
61   req+sign NAME [TYPE] Combine the above two steps, producing all three files.
62   verify NAME [TYPE]   Checks that NAME-cert.pem is a valid TYPE certificate
63   fingerprint FILE     Prints the fingerprint for FILE
64   self-sign NAME       Sign NAME-req.pem with NAME-privkey.pem,
65                        producing self-signed certificate NAME-cert.pem
66
67 The following additional commands manage an online PKI:
68   ls [PREFIX] [TYPE]   Lists incoming requests of the given TYPE, optionally 
69                        limited to those whose fingerprint begins with PREFIX
70   flush [TYPE]         Rejects all incoming requests of the given TYPE
71   reject PREFIX [TYPE] Rejects the incoming request(s) whose fingerprint begins
72                        with PREFIX and has the given TYPE
73   approve PREFIX [TYPE] Approves the incoming request whose fingerprint begins
74                        with PREFIX and has the given TYPE
75   expire [AGE]         Rejects all incoming requests older than AGE, in
76                        one of the forms Ns, Nmin, Nh, Nday (default: 1day)
77   prompt [TYPE]        Interactively prompts to accept or reject each incoming
78                        request of the given TYPE
79
80 Each TYPE above is a certificate type: 'switch' (default) or 'controller'.
81
82 Options for 'init', 'req', and 'req+sign' only:
83   -k, --key=rsa|dsa    Type of keys to use (default: rsa)
84   -B, --bits=NBITS     Number of bits in keys (default: 2048).  For DSA keys,
85                          this has an effect only on 'init'.
86   -D, --dsaparam=FILE  File with DSA parameters (DSA only)
87                          (default: dsaparam.pem within PKI directory)
88 Options for use with the 'sign' and 'approve' commands:
89   -b, --batch          Skip fingerprint verification
90 Options that apply to any command:
91   -d, --dir=DIR        Directory where the PKI is located
92                          (default: $pkidir)
93   -f, --force          Continue even if file or directory already exists
94   -l, --log=FILE       Log openssl output to FILE (default: ovs-log.log)
95   -h, --help           Print this usage message.
96   -V, --version        Display version information.
97 EOF
98             exit 0
99             ;;
100         -V|--version)
101             echo "ovs-pki (Open vSwitch) @VERSION@"
102             exit 0
103             ;;
104         --di*=*)
105             pkidir=$optarg
106             ;;
107         --di*|-d)
108             prev=pkidir
109             ;;
110         --k*=*)
111             keytype=$optarg
112             ;;
113         --k*|-k)
114             prev=keytype
115             ;;
116         --bi*=*)
117             bits=$optarg
118             ;;
119         --bi*|-B)
120             prev=bits
121             ;;
122         --ds*=*)
123             dsaparam=$optarg
124             ;;
125         --ds*|-D)
126             prev=dsaparam
127             ;;
128         --l*=*)
129             log=$optarg
130             ;;
131         --l*|-l)
132             prev=log
133             ;;
134         --force|-f)
135             force=yes
136             ;;
137         --ba*|-b)
138             batch=yes
139             ;;
140         -*)
141             echo "unrecognized option $option" >&2
142             exit 1
143             ;;
144         *)
145             if test -z "$command"; then
146                 command=$option
147             elif test -z "${arg1+set}"; then
148                 arg1=$option
149             elif test -z "${arg2+set}"; then
150                 arg2=$option
151             else
152                 echo "$option: only two arguments may be specified" >&2
153                 exit 1
154             fi
155             ;;
156     esac
157     shift
158 done
159 if test -n "$prev"; then
160     option=--`echo $prev | sed 's/_/-/g'`
161     { echo "$as_me: error: missing argument to $option" >&2
162         { (exit 1); exit 1; }; }
163 fi
164 if test -z "$command"; then
165     echo "$0: missing command name; use --help for help" >&2
166     exit 1
167 fi
168 if test "$keytype" != rsa && test "$keytype" != dsa; then
169     echo "$0: argument to -k or --key must be rsa or dsa" >&2
170     exit 1
171 fi
172 if test "$bits" -lt 1024; then
173     echo "$0: argument to -B or --bits must be at least 1024" >&2
174     exit 1
175 fi
176 if test -z "$dsaparam"; then
177     dsaparam=$pkidir/dsaparam.pem
178 fi
179 case $log in
180     /*) ;;
181     *) log=`pwd`/$log ;;
182 esac
183
184 logdir=$(dirname "$log")
185 if test ! -d "$logdir"; then
186     mkdir -p -m755 "$logdir" 2>/dev/null || true
187     if test ! -d "$logdir"; then
188         echo "$0: log directory $logdir does not exist and cannot be created" >&2
189         exit 1
190     fi
191 fi
192
193 if test "$command" = "init"; then
194     if test -e "$pkidir" && test "$force" != "yes"; then
195         echo "$0: $pkidir already exists and --force not specified" >&2
196         exit 1
197     fi
198
199     if test ! -d "$pkidir"; then
200         mkdir -p "$pkidir"
201     fi
202     cd "$pkidir"
203     exec 3>>$log
204
205     if test $keytype = dsa && test ! -e dsaparam.pem; then
206         echo "Generating DSA parameters, please wait..." >&2
207         openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3
208     fi
209
210     # Get the current date to add some uniqueness to this certificate
211     curr_date=`date +"%Y %b %d %T"`
212
213     # Create the CAs.
214     for ca in controllerca switchca; do
215         echo "Creating $ca..." >&2
216         oldpwd=`pwd`
217         mkdir -p $ca
218         cd $ca
219
220         mkdir -p certs crl newcerts
221         mkdir -p -m 0700 private
222         mkdir -p -m 0733 incoming
223         touch index.txt
224         test -e crlnumber || echo 01 > crlnumber
225         test -e serial || echo 01 > serial
226
227         # Put DSA parameters in directory.
228         if test $keytype = dsa && test ! -e dsaparam.pem; then
229             cp ../dsaparam.pem .
230         fi
231
232         # Write CA configuration file.
233         if test ! -e ca.cnf; then
234             sed "s/@ca@/$ca/g;s/@curr_date@/$curr_date/g" > ca.cnf <<'EOF'
235 [ req ]
236 prompt = no
237 distinguished_name = req_distinguished_name
238
239 [ req_distinguished_name ]
240 C = US
241 ST = CA
242 L = Palo Alto
243 O = Open vSwitch
244 OU = @ca@
245 CN = OVS @ca@ CA Certificate (@curr_date@)
246
247 [ ca ]
248 default_ca = the_ca
249
250 [ the_ca ]
251 dir            = .                     # top dir
252 database       = $dir/index.txt        # index file.
253 new_certs_dir  = $dir/newcerts         # new certs dir
254 certificate    = $dir/cacert.pem       # The CA cert
255 serial         = $dir/serial           # serial no file
256 private_key    = $dir/private/cakey.pem# CA private key
257 RANDFILE       = $dir/private/.rand    # random number file
258 default_days   = 365                   # how long to certify for
259 default_crl_days= 30                   # how long before next CRL
260 default_md     = md5                   # md to use
261 policy         = policy                # default policy
262 email_in_dn    = no                    # Don't add the email into cert DN
263 name_opt       = ca_default            # Subject name display option
264 cert_opt       = ca_default            # Certificate display option
265 copy_extensions = none                 # Don't copy extensions from request
266 unique_subject = no                    # Allow certs with duplicate subjects
267
268 # For the CA policy
269 [ policy ]
270 countryName             = optional
271 stateOrProvinceName     = optional
272 organizationName        = match
273 organizationalUnitName  = optional
274 commonName              = supplied
275 emailAddress            = optional
276 EOF
277         fi
278
279         # Create certificate authority.
280         if test $keytype = dsa; then
281             newkey=dsa:dsaparam.pem
282         else
283             newkey=rsa:$bits
284         fi
285         openssl req -config ca.cnf -nodes \
286             -newkey $newkey -keyout private/cakey.pem -out careq.pem \
287             1>&3 2>&3
288         openssl ca -config ca.cnf -create_serial -out cacert.pem \
289             -days 2191 -batch -keyfile private/cakey.pem -selfsign \
290             -infiles careq.pem 1>&3 2>&3
291         chmod 0700 private/cakey.pem
292
293         cd "$oldpwd"
294     done
295     exit 0
296 fi
297
298 one_arg() {
299     if test -z "$arg1" || test -n "$arg2"; then
300         echo "$0: $command must have exactly one argument; use --help for help" >&2
301         exit 1
302     fi
303 }
304
305 zero_or_one_args() {
306     if test -n "$arg2"; then
307         echo "$0: $command must have zero or one arguments; use --help for help" >&2
308         exit 1
309     fi
310 }
311
312 one_or_two_args() {
313     if test -z "$arg1"; then
314         echo "$0: $command must have one or two arguments; use --help for help" >&2
315         exit 1
316     fi
317 }
318
319 must_not_exist() {
320     if test -e "$1" && test "$force" != "yes"; then
321         echo "$0: $1 already exists and --force not supplied" >&2
322         exit 1
323     fi
324 }
325
326 resolve_prefix() {
327     test -n "$type" || exit 123 # Forgot to call check_type?
328
329     case $1 in
330         ????*)
331             ;;
332         *)
333             echo "Prefix $arg1 is too short (less than 4 hex digits)" >&2
334             exit 0
335             ;;
336     esac
337     
338     fingerprint=$(cd "$pkidir/${type}ca/incoming" && echo "$1"*-req.pem | sed 's/-req\.pem$//')
339     case $fingerprint in
340         "${1}*")
341             echo "No certificate requests matching $1" >&2
342             exit 1
343             ;;
344         *" "*)
345             echo "$1 matches more than one certificate request:" >&2
346             echo $fingerprint | sed 's/ /\
347 /g' >&2
348             exit 1
349             ;;
350         *)
351             # Nothing to do.
352             ;;
353     esac
354     req="$pkidir/${type}ca/incoming/$fingerprint-req.pem"
355     cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem"
356 }
357
358 make_tmpdir() {
359     TMP=/tmp/ovs-pki.tmp$$
360     rm -rf $TMP
361     trap "rm -rf $TMP" 0
362     mkdir -m 0700 $TMP
363 }
364
365 fingerprint() {
366     file=$1
367     name=${1-$2}
368     date=$(date -r $file)
369     if grep -e '-BEGIN CERTIFICATE-' "$file" > /dev/null; then
370         fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
371                       sed 's/SHA1 Fingerprint=//' | tr -d ':')
372     else
373         fingerprint=$(sha1sum "$file" | awk '{print $1}')
374     fi
375     printf "$name\\t$date\\n"
376     case $file in
377         $fingerprint*)
378             printf "\\t(correct fingerprint in filename)\\n"
379             ;;
380         *)
381             printf "\\tfingerprint $fingerprint\\n"
382             ;;
383     esac
384 }
385
386 verify_fingerprint() {
387     fingerprint "$@"
388     if test $batch != yes; then
389         echo "Does fingerprint match? (yes/no)"
390         read answer
391         if test "$answer" != yes; then 
392             echo "Match failure, aborting" >&2
393             exit 1
394         fi
395     fi
396 }
397
398 check_type() {
399     if test x = x"$1"; then
400         type=switch
401     elif test "$1" = switch || test "$1" = controller; then 
402         type=$1
403     else
404         echo "$0: type argument must be 'switch' or 'controller'" >&2
405         exit 1
406     fi
407 }
408
409 parse_age() {
410     number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/')
411     unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/')
412     case $unit in
413         s)
414             factor=1
415             ;;
416         min)
417             factor=60
418             ;;
419         h)
420             factor=3600
421             ;;
422         day)
423             factor=86400
424             ;;
425         *)
426             echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2
427             exit 1
428             ;;
429     esac
430     echo $(($number * $factor))
431 }
432
433 must_exist() {
434     if test ! -e "$1"; then
435         echo "$0: $1 does not exist" >&2
436         exit 1
437     fi
438 }
439
440 pkidir_must_exist() {
441     if test ! -e "$pkidir"; then
442         echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2
443         exit 1
444     elif test ! -d "$pkidir"; then
445         echo "$0: $pkidir is not a directory" >&2
446         exit 1
447     fi
448 }
449
450 make_request() {
451     must_not_exist "$arg1-privkey.pem"
452     must_not_exist "$arg1-req.pem"
453     make_tmpdir
454     cat > "$TMP/req.cnf" <<EOF
455 [ req ]
456 prompt = no
457 distinguished_name = req_distinguished_name
458
459 [ req_distinguished_name ]
460 C = US
461 ST = CA
462 L = Palo Alto
463 O = Open vSwitch
464 OU = Open vSwitch certifier
465 CN = Open vSwitch certificate for $arg1
466 EOF
467     if test $keytype = rsa; then
468         (umask 077 && openssl genrsa -out "$1-privkey.pem" $bits) 1>&3 2>&3 \
469             || exit $?
470     else
471         must_exist "$dsaparam"
472         (umask 077 && openssl gendsa -out "$1-privkey.pem" "$dsaparam") \
473             1>&3 2>&3 || exit $?
474     fi
475     openssl req -config "$TMP/req.cnf" -new -text \
476         -key "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
477 }
478
479 sign_request() {
480     must_exist "$1"
481     must_not_exist "$2"
482     pkidir_must_exist
483
484     (cd "$pkidir/${type}ca" && 
485      openssl ca -config ca.cnf -batch -in /dev/stdin) \
486         < "$1" > "$2.tmp$$" 2>&3
487     mv "$2.tmp$$" "$2"
488 }
489
490 glob() {
491     files=$(echo $1)
492     if test "$files" != "$1"; then
493         echo "$files"
494     fi
495 }
496
497 exec 3>>$log || true
498 if test "$command" = req; then
499     one_arg
500
501     make_request "$arg1"
502     fingerprint "$arg1-req.pem"
503 elif test "$command" = sign; then
504     one_or_two_args
505     check_type "$arg2"
506     verify_fingerprint "$arg1-req.pem"
507
508     sign_request "$arg1-req.pem" "$arg2-cert.pem"
509 elif test "$command" = req+sign; then
510     one_or_two_args
511     check_type "$arg2"
512
513     pkidir_must_exist
514     make_request "$arg1"
515     sign_request "$arg1-req.pem" "$arg1-cert.pem"
516     fingerprint "$arg1-req.pem"
517 elif test "$command" = verify; then
518     one_or_two_args
519     must_exist "$arg1-cert.pem"
520     check_type "$arg2"
521
522     pkidir_must_exist
523     openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem"
524 elif test "$command" = fingerprint; then
525     one_arg
526
527     fingerprint "$arg1"
528 elif test "$command" = self-sign; then
529     one_arg
530     must_exist "$arg1-req.pem"
531     must_exist "$arg1-privkey.pem"
532     must_not_exist "$arg1-cert.pem"
533
534     # Create both the private key and certificate with restricted permissions.
535     (umask 077 && \
536      openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem.tmp" \
537         -signkey "$arg1-privkey.pem" -req -text) 2>&3 || exit $?
538
539     # Reset the permissions on the certificate to the user's default.
540     cat "$arg1-cert.pem.tmp" > "$arg1-cert.pem"
541     rm -f "$arg1-cert.pem.tmp"
542 elif test "$command" = ls; then
543     check_type "$arg2"
544
545     cd "$pkidir/${type}ca/incoming"
546     for file in $(glob "$arg1*-req.pem"); do
547         fingerprint $file
548     done
549 elif test "$command" = flush; then
550     check_type "$arg1"
551
552     rm -f "$pkidir/${type}ca/incoming/"*
553 elif test "$command" = reject; then
554     one_or_two_args
555     check_type "$arg2"
556     resolve_prefix "$arg1"
557
558     rm -f "$req"
559 elif test "$command" = approve; then
560     one_or_two_args
561     check_type "$arg2"
562     resolve_prefix "$arg1"
563
564     make_tmpdir
565     cp "$req" "$TMP/$req"
566     verify_fingerprint "$TMP/$req"
567     sign_request "$TMP/$req"
568     rm -f "$req" "$TMP/$req"
569 elif test "$command" = prompt; then
570     zero_or_one_args
571     check_type "$arg1"
572
573     make_tmpdir
574     cd "$pkidir/${type}ca/incoming"
575     for req in $(glob "*-req.pem"); do
576         cp "$req" "$TMP/$req"
577
578         cert=$(echo "$pkidir/${type}ca/certs/$req" |
579                sed 's/-req.pem/-cert.pem/')
580         if test -f $cert; then
581             echo "Request $req already approved--dropping duplicate request"
582             rm -f "$req" "$TMP/$req"
583             continue
584         fi
585
586         echo
587         echo
588         fingerprint "$TMP/$req" "$req"
589         printf "Disposition for this request (skip/approve/reject)? "
590         read answer
591         case $answer in
592             approve)
593                 echo "Approving $req"
594                 sign_request "$TMP/$req" "$cert"
595                 rm -f "$req" "$TMP/$req"
596                 ;;
597             r*)
598                 echo "Rejecting $req"
599                 rm -f "$req" "$TMP/$req"
600                 ;;
601             *)
602                 echo "Skipping $req"
603                 ;;
604         esac
605     done
606 elif test "$command" = expire; then
607     zero_or_one_args
608     cutoff=$(($(date +%s) - $(parse_age ${arg1-1day})))
609     for type in switch controller; do
610         cd "$pkidir/${type}ca/incoming" || exit 1
611         for file in $(glob "*"); do
612             time=$(date -r "$file" +%s)
613             if test "$time" -lt "$cutoff"; then
614                 rm -f "$file"
615             fi
616         done
617     done
618 else
619     echo "$0: $command command unknown; use --help for help" >&2
620     exit 1
621 fi