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