Add build number to datapath version and --version output from programs.
[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='@LOGDIR@/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 case $log in
159     /*) ;;
160     *) $log="$PWD/$log" ;;
161 esac
162
163 if test "$command" = "init"; then
164     if test -e "$pkidir" && test "$force" != "yes"; then
165         echo "$0: $pkidir already exists and --force not specified" >&2
166         exit 1
167     fi
168
169     if test ! -d "$pkidir"; then
170         mkdir -p "$pkidir"
171     fi
172     cd "$pkidir"
173     exec 3>>$log
174
175     if test $keytype = dsa && test ! -e dsaparam.pem; then
176         echo "Generating DSA parameters, please wait..." >&2
177         openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3
178     fi
179
180     # Create the CAs.
181     for ca in controllerca switchca; do
182         echo "Creating $ca..." >&2
183         oldpwd=$PWD
184         mkdir -p $ca
185         cd $ca
186
187         mkdir -p certs crl newcerts
188         mkdir -p -m 0700 private
189         mkdir -p -m 0733 incoming
190         touch index.txt
191         test -e crlnumber || echo 01 > crlnumber
192         test -e serial || echo 01 > serial
193
194         # Put DSA parameters in directory.
195         if test $keytype = dsa && test ! -e dsaparam.pem; then
196             cp ../dsaparam.pem .
197         fi
198
199     # Write CA configuration file.
200         if test ! -e ca.cnf; then
201             sed "s/@ca@/$ca/g" > ca.cnf <<'EOF'
202 [ req ]
203 prompt = no
204 distinguished_name = req_distinguished_name
205
206 [ req_distinguished_name ]
207 C = US
208 ST = CA
209 L = Palo Alto
210 O = OpenFlow
211 OU = @ca@
212 CN = OpenFlow @ca@ CA Certificate
213
214 [ ca ]
215 default_ca = the_ca
216
217 [ the_ca ]
218 dir            = .                     # top dir
219 database       = $dir/index.txt        # index file.
220 new_certs_dir  = $dir/newcerts         # new certs dir
221 certificate    = $dir/cacert.pem       # The CA cert
222 serial         = $dir/serial           # serial no file
223 private_key    = $dir/private/cakey.pem# CA private key
224 RANDFILE       = $dir/private/.rand    # random number file
225 default_days   = 365                   # how long to certify for
226 default_crl_days= 30                   # how long before next CRL
227 default_md     = md5                   # md to use
228 policy         = policy                # default policy
229 email_in_dn    = no                    # Don't add the email into cert DN
230 name_opt       = ca_default            # Subject name display option
231 cert_opt       = ca_default            # Certificate display option
232 copy_extensions = none                 # Don't copy extensions from request
233
234 # For the CA policy
235 [ policy ]
236 countryName             = optional
237 stateOrProvinceName     = optional
238 organizationName        = match
239 organizationalUnitName  = optional
240 commonName              = supplied
241 emailAddress            = optional
242 EOF
243         fi
244
245         # Create certificate authority.
246         if test $keytype = dsa; then
247             newkey=dsa:dsaparam.pem
248         else
249             newkey=rsa:$bits
250         fi
251         openssl req -config ca.cnf -nodes \
252             -newkey $newkey -keyout private/cakey.pem -out careq.pem \
253             1>&3 2>&3
254         openssl ca -config ca.cnf -create_serial -out cacert.pem \
255             -days 1095 -batch -keyfile private/cakey.pem -selfsign \
256             -infiles careq.pem 1>&3 2>&3
257         chmod 0700 private/cakey.pem
258
259         cd "$oldpwd"
260     done
261     exit 0
262 fi
263
264 one_arg() {
265     if test -z "$arg1" || test -n "$arg2"; then
266         echo "$0: $command must have exactly one argument; use --help for help" >&2
267         exit 1
268     fi
269 }
270
271 zero_or_one_args() {
272     if test -n "$arg2"; then
273         echo "$0: $command must have zero or one arguments; use --help for help" >&2
274         exit 1
275     fi
276 }
277
278 one_or_two_args() {
279     if test -z "$arg1"; then
280         echo "$0: $command must have one or two arguments; use --help for help" >&2
281         exit 1
282     fi
283 }
284
285 must_not_exist() {
286     if test -e "$1" && test "$force" != "yes"; then
287         echo "$0: $1 already exists and --force not supplied" >&2
288         exit 1
289     fi
290 }
291
292 resolve_prefix() {
293     test -n "$type" || exit 123 # Forgot to call check_type?
294
295     case $1 in
296         ????*)
297             ;;
298         *)
299             echo "Prefix $arg1 is too short (less than 4 hex digits)"
300             exit 0
301             ;;
302     esac
303     
304     fingerprint=$(cd "$pkidir/${type}ca/incoming" && echo "$1"*-req.pem
305                   | sed 's/-req\.pem$//')
306     case $fingerprint in
307         "${1}*")
308             echo "No certificate requests matching $1"
309             exit 1
310             ;;
311         *" "*)
312             echo "$1 matches more than one certificate request:"
313             echo $fingerprint | sed 's/ /\
314 /g'
315             exit 1
316             ;;
317         *)
318             # Nothing to do.
319             ;;
320     esac
321     req="$pkidir/${type}ca/incoming/$fingerprint-req.pem"
322     cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem"
323 }
324
325 make_tmpdir() {
326     TMP=/tmp/ofp-pki.tmp$$
327     rm -rf $TMP
328     trap "rm -rf $TMP" 0
329     mkdir -m 0700 $TMP
330 }
331
332 fingerprint() {
333     local file=$1
334     local name=${1-$2}
335     local date=$(date -r $file)
336     local fingerprint
337     if grep -q -e '-BEGIN CERTIFICATE-' "$file"; then
338         fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
339                       sed 's/SHA1 Fingerprint=//' | tr -d ':')
340     else
341         fingerprint=$(sha1sum "$file" | awk '{print $1}')
342     fi
343     printf "$name\\t$date\\n"
344     case $file in
345         $fingerprint*)
346             printf "\\t(correct fingerprint in filename)\\n"
347             ;;
348         *)
349             printf "\\tfingerprint $fingerprint\\n"
350             ;;
351     esac
352 }
353
354 verify_fingerprint() {
355     fingerprint "$@"
356     if test $batch != yes; then
357         echo "Does fingerprint match? (yes/no)"
358         read answer
359         if test "$answer" != yes; then 
360             echo "Match failure, aborting" >&2
361             exit 1
362         fi
363     fi
364 }
365
366 check_type() {
367     if test x = x"$1"; then
368         type=switch
369     elif test "$1" = switch || test "$1" = controller; then 
370         type=$1
371     else
372         echo "$0: type argument must be 'switch' or 'controller'" >&2
373         exit 1
374     fi
375 }
376
377 parse_age() {
378     number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/')
379     unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/')
380     case $unit in
381         s)
382             factor=1
383             ;;
384         min)
385             factor=60
386             ;;
387         h)
388             factor=3600
389             ;;
390         day)
391             factor=86400
392             ;;
393         *)
394             echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2
395             exit 1
396             ;;
397     esac
398     echo $(($number * $factor))
399 }
400
401 must_exist() {
402     if test ! -e "$1"; then
403         echo "$0: $1 does not exist" >&2
404         exit 1
405     fi
406 }
407
408 pkidir_must_exist() {
409     if test ! -e "$pkidir"; then
410         echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2
411         exit 1
412     elif test ! -d "$pkidir"; then
413         echo "$0: $pkidir is not a directory" >&2
414         exit 1
415     fi
416 }
417
418 make_request() {
419     must_not_exist "$arg1-privkey.pem"
420     must_not_exist "$arg1-req.pem"
421     make_tmpdir
422     cat > "$TMP/req.cnf" <<EOF
423 [ req ]
424 prompt = no
425 distinguished_name = req_distinguished_name
426
427 [ req_distinguished_name ]
428 C = US
429 ST = CA
430 L = Palo Alto
431 O = OpenFlow
432 OU = OpenFlow certifier
433 CN = OpenFlow certificate for $arg1
434 EOF
435     if test $keytype = rsa; then
436         newkey=rsa:$bits
437     else
438         must_exist "$dsaparam"
439         newkey=dsa:$dsaparam
440     fi
441     openssl req -config "$TMP/req.cnf" -text -nodes \
442         -newkey $newkey -keyout "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
443 }
444
445 sign_request() {
446     must_exist "$1"
447     must_not_exist "$2"
448     pkidir_must_exist
449
450     (cd "$pkidir/${type}ca" && 
451      openssl ca -config ca.cnf -batch -in /dev/stdin) \
452         < "$1" > "$2.tmp$$" 2>&3
453     mv "$2.tmp$$" "$2"
454 }
455
456 glob() {
457     local files=$(echo $1)
458     if test "$files" != "$1"; then
459         echo "$files"
460     fi
461 }
462
463 exec 3>>$log || true
464 if test "$command" = req; then
465     one_arg
466
467     make_request "$arg1"
468     fingerprint "$arg1-req.pem"
469 elif test "$command" = sign; then
470     one_or_two_args
471     check_type "$arg2"
472     verify_fingerprint "$arg1-req.pem"
473
474     sign_request "$arg1-req.pem" "$arg2-cert.pem"
475 elif test "$command" = req+sign; then
476     one_or_two_args
477     check_type "$arg2"
478
479     pkidir_must_exist
480     make_request "$arg1"
481     sign_request "$arg1-req.pem" "$arg1-cert.pem"
482     fingerprint "$arg1-req.pem"
483 elif test "$command" = verify; then
484     one_or_two_args
485     must_exist "$arg1-cert.pem"
486     check_type "$arg2"
487
488     pkidir_must_exist
489     openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem"
490 elif test "$command" = fingerprint; then
491     one_arg
492
493     fingerprint "$arg1"
494 elif test "$command" = self-sign; then
495     one_arg
496     must_exist "$arg1-req.pem"
497     must_exist "$arg1-privkey.pem"
498     must_not_exist "$arg1-cert.pem"
499
500     openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem" \
501         -signkey "$arg1-privkey.pem" -req -text 2>&3
502 elif test "$command" = ls; then
503     check_type "$arg2"
504
505     cd "$pkidir/${type}ca/incoming"
506     for file in $(glob "$arg1*-req.pem"); do
507         fingerprint $file
508     done
509 elif test "$command" = flush; then
510     check_type "$arg1"
511
512     rm -f "$pkidir/${type}ca/incoming/"*
513 elif test "$command" = reject; then
514     one_or_two_args
515     check_type "$arg2"
516     resolve_prefix "$arg1"
517
518     rm -f "$req"
519 elif test "$command" = approve; then
520     one_or_two_args
521     check_type "$arg2"
522     resolve_prefix "$arg1"
523
524     make_tmpdir
525     cp "$req" "$TMP/$req"
526     verify_fingerprint "$TMP/$req"
527     sign_request "$TMP/$req"
528     rm -f "$req" "$TMP/$req"
529 elif test "$command" = prompt; then
530     zero_or_one_args
531     check_type "$arg1"
532
533     make_tmpdir
534     cd "$pkidir/${type}ca/incoming"
535     for req in $(glob "*-req.pem"); do
536         cp "$req" "$TMP/$req"
537
538         cert=$(echo "$pkidir/${type}ca/certs/$req" |
539                sed 's/-req.pem/-cert.pem/')
540         if test -f $cert; then
541             echo "Request $req already approved--dropping duplicate request"
542             rm -f "$req" "$TMP/$req"
543             continue
544         fi
545
546         echo
547         echo
548         fingerprint "$TMP/$req" "$req"
549         printf "Disposition for this request (skip/approve/reject)? "
550         read answer
551         case $answer in
552             approve)
553                 echo "Approving $req"
554                 sign_request "$TMP/$req" "$cert"
555                 rm -f "$req" "$TMP/$req"
556                 ;;
557             r*)
558                 echo "Rejecting $req"
559                 rm -f "$req" "$TMP/$req"
560                 ;;
561             *)
562                 echo "Skipping $req"
563                 ;;
564         esac
565     done
566 elif test "$command" = expire; then
567     zero_or_one_args
568     cutoff=$(($(date +%s) - $(parse_age ${arg1-1day})))
569     for type in switch controller; do
570         cd "$pkidir/${type}ca/incoming" || exit 1
571         for file in $(glob "*"); do
572             time=$(date -r "$file" +%s)
573             if test "$time" -lt "$cutoff"; then
574                 rm -f "$file"
575             fi
576         done
577     done
578 else
579     echo "$0: $command command unknown; use --help for help" >&2
580     exit 1
581 fi