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