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