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