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