For SNAT, don't store the pre-fragment L2 header before actions are applied.
[sliver-openvswitch.git] / utilities / ofp-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@/ofp-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 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 ofp-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: ofp-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 = OpenFlow
213 OU = @ca@
214 CN = OpenFlow @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
307                   | sed 's/-req\.pem$//')
308     case $fingerprint in
309         "${1}*")
310             echo "No certificate requests matching $1"
311             exit 1
312             ;;
313         *" "*)
314             echo "$1 matches more than one certificate request:"
315             echo $fingerprint | sed 's/ /\
316 /g'
317             exit 1
318             ;;
319         *)
320             # Nothing to do.
321             ;;
322     esac
323     req="$pkidir/${type}ca/incoming/$fingerprint-req.pem"
324     cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem"
325 }
326
327 make_tmpdir() {
328     TMP=/tmp/ofp-pki.tmp$$
329     rm -rf $TMP
330     trap "rm -rf $TMP" 0
331     mkdir -m 0700 $TMP
332 }
333
334 fingerprint() {
335     local file=$1
336     local name=${1-$2}
337     local date=$(date -r $file)
338     local fingerprint
339     if grep -q -e '-BEGIN CERTIFICATE-' "$file"; then
340         fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
341                       sed 's/SHA1 Fingerprint=//' | tr -d ':')
342     else
343         fingerprint=$(sha1sum "$file" | awk '{print $1}')
344     fi
345     printf "$name\\t$date\\n"
346     case $file in
347         $fingerprint*)
348             printf "\\t(correct fingerprint in filename)\\n"
349             ;;
350         *)
351             printf "\\tfingerprint $fingerprint\\n"
352             ;;
353     esac
354 }
355
356 verify_fingerprint() {
357     fingerprint "$@"
358     if test $batch != yes; then
359         echo "Does fingerprint match? (yes/no)"
360         read answer
361         if test "$answer" != yes; then 
362             echo "Match failure, aborting" >&2
363             exit 1
364         fi
365     fi
366 }
367
368 check_type() {
369     if test x = x"$1"; then
370         type=switch
371     elif test "$1" = switch || test "$1" = controller; then 
372         type=$1
373     else
374         echo "$0: type argument must be 'switch' or 'controller'" >&2
375         exit 1
376     fi
377 }
378
379 parse_age() {
380     number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/')
381     unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/')
382     case $unit in
383         s)
384             factor=1
385             ;;
386         min)
387             factor=60
388             ;;
389         h)
390             factor=3600
391             ;;
392         day)
393             factor=86400
394             ;;
395         *)
396             echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2
397             exit 1
398             ;;
399     esac
400     echo $(($number * $factor))
401 }
402
403 must_exist() {
404     if test ! -e "$1"; then
405         echo "$0: $1 does not exist" >&2
406         exit 1
407     fi
408 }
409
410 pkidir_must_exist() {
411     if test ! -e "$pkidir"; then
412         echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2
413         exit 1
414     elif test ! -d "$pkidir"; then
415         echo "$0: $pkidir is not a directory" >&2
416         exit 1
417     fi
418 }
419
420 make_request() {
421     must_not_exist "$arg1-privkey.pem"
422     must_not_exist "$arg1-req.pem"
423     make_tmpdir
424     cat > "$TMP/req.cnf" <<EOF
425 [ req ]
426 prompt = no
427 distinguished_name = req_distinguished_name
428
429 [ req_distinguished_name ]
430 C = US
431 ST = CA
432 L = Palo Alto
433 O = OpenFlow
434 OU = OpenFlow certifier
435 CN = OpenFlow certificate for $arg1
436 EOF
437     if test $keytype = rsa; then
438         newkey=rsa:$bits
439     else
440         must_exist "$dsaparam"
441         newkey=dsa:$dsaparam
442     fi
443     openssl req -config "$TMP/req.cnf" -text -nodes \
444         -newkey $newkey -keyout "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
445 }
446
447 sign_request() {
448     must_exist "$1"
449     must_not_exist "$2"
450     pkidir_must_exist
451
452     (cd "$pkidir/${type}ca" && 
453      openssl ca -config ca.cnf -batch -in /dev/stdin) \
454         < "$1" > "$2.tmp$$" 2>&3
455     mv "$2.tmp$$" "$2"
456 }
457
458 glob() {
459     local files=$(echo $1)
460     if test "$files" != "$1"; then
461         echo "$files"
462     fi
463 }
464
465 exec 3>>$log || true
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" = self-sign; then
497     one_arg
498     must_exist "$arg1-req.pem"
499     must_exist "$arg1-privkey.pem"
500     must_not_exist "$arg1-cert.pem"
501
502     openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem" \
503         -signkey "$arg1-privkey.pem" -req -text 2>&3
504 elif test "$command" = ls; then
505     check_type "$arg2"
506
507     cd "$pkidir/${type}ca/incoming"
508     for file in $(glob "$arg1*-req.pem"); do
509         fingerprint $file
510     done
511 elif test "$command" = flush; then
512     check_type "$arg1"
513
514     rm -f "$pkidir/${type}ca/incoming/"*
515 elif test "$command" = reject; then
516     one_or_two_args
517     check_type "$arg2"
518     resolve_prefix "$arg1"
519
520     rm -f "$req"
521 elif test "$command" = approve; then
522     one_or_two_args
523     check_type "$arg2"
524     resolve_prefix "$arg1"
525
526     make_tmpdir
527     cp "$req" "$TMP/$req"
528     verify_fingerprint "$TMP/$req"
529     sign_request "$TMP/$req"
530     rm -f "$req" "$TMP/$req"
531 elif test "$command" = prompt; then
532     zero_or_one_args
533     check_type "$arg1"
534
535     make_tmpdir
536     cd "$pkidir/${type}ca/incoming"
537     for req in $(glob "*-req.pem"); do
538         cp "$req" "$TMP/$req"
539
540         cert=$(echo "$pkidir/${type}ca/certs/$req" |
541                sed 's/-req.pem/-cert.pem/')
542         if test -f $cert; then
543             echo "Request $req already approved--dropping duplicate request"
544             rm -f "$req" "$TMP/$req"
545             continue
546         fi
547
548         echo
549         echo
550         fingerprint "$TMP/$req" "$req"
551         printf "Disposition for this request (skip/approve/reject)? "
552         read answer
553         case $answer in
554             approve)
555                 echo "Approving $req"
556                 sign_request "$TMP/$req" "$cert"
557                 rm -f "$req" "$TMP/$req"
558                 ;;
559             r*)
560                 echo "Rejecting $req"
561                 rm -f "$req" "$TMP/$req"
562                 ;;
563             *)
564                 echo "Skipping $req"
565                 ;;
566         esac
567     done
568 elif test "$command" = expire; then
569     zero_or_one_args
570     cutoff=$(($(date +%s) - $(parse_age ${arg1-1day})))
571     for type in switch controller; do
572         cd "$pkidir/${type}ca/incoming" || exit 1
573         for file in $(glob "*"); do
574             time=$(date -r "$file" +%s)
575             if test "$time" -lt "$cutoff"; then
576                 rm -f "$file"
577             fi
578         done
579     done
580 else
581     echo "$0: $command command unknown; use --help for help" >&2
582     exit 1
583 fi