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