3 # Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
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:
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
24 log='@LOGDIR@/ovs-pki.log'
28 # OS-specific compatibility routines
33 stat -r "$1" | awk '{print $10}'
60 # This option-parsing mechanism borrowed from a Autoconf-generated
61 # configure script under the following license:
63 # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
64 # 2002, 2003, 2004, 2005, 2006, 2009 Free Software Foundation, Inc.
65 # This configure script is free software; the Free Software Foundation
66 # gives unlimited permission to copy, distribute and modify it.
68 # If the previous option needs an argument, assign it.
69 if test -n "$prev"; then
75 *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;;
79 case $dashdash$option in
84 ovs-pki, for managing a simple OpenFlow public key infrastructure
85 usage: $0 [OPTION...] COMMAND [ARG...]
87 The valid stand-alone commands and their arguments are:
88 init Initialize the PKI
89 req NAME Create new private key and certificate request
90 named NAME-privkey.pem and NAME-req.pem, resp.
91 sign NAME [TYPE] Sign switch certificate request NAME-req.pem,
92 producing certificate NAME-cert.pem
93 req+sign NAME [TYPE] Combine the above two steps, producing all three files.
94 verify NAME [TYPE] Checks that NAME-cert.pem is a valid TYPE certificate
95 fingerprint FILE Prints the fingerprint for FILE
96 self-sign NAME Sign NAME-req.pem with NAME-privkey.pem,
97 producing self-signed certificate NAME-cert.pem
99 The following additional commands manage an online PKI:
100 ls [PREFIX] [TYPE] Lists incoming requests of the given TYPE, optionally
101 limited to those whose fingerprint begins with PREFIX
102 flush [TYPE] Rejects all incoming requests of the given TYPE
103 reject PREFIX [TYPE] Rejects the incoming request(s) whose fingerprint begins
104 with PREFIX and has the given TYPE
105 approve PREFIX [TYPE] Approves the incoming request whose fingerprint begins
106 with PREFIX and has the given TYPE
107 expire [AGE] Rejects all incoming requests older than AGE, in
108 one of the forms Ns, Nmin, Nh, Nday (default: 1day)
109 prompt [TYPE] Interactively prompts to accept or reject each incoming
110 request of the given TYPE
112 Each TYPE above is a certificate type: 'switch' (default) or 'controller'.
114 Options for 'init', 'req', and 'req+sign' only:
115 -k, --key=rsa|dsa Type of keys to use (default: rsa)
116 -B, --bits=NBITS Number of bits in keys (default: 2048). For DSA keys,
117 this has an effect only on 'init'.
118 -D, --dsaparam=FILE File with DSA parameters (DSA only)
119 (default: dsaparam.pem within PKI directory)
120 Options for use with the 'sign' and 'approve' commands:
121 -b, --batch Skip fingerprint verification
122 Options that apply to any command:
123 -d, --dir=DIR Directory where the PKI is located
125 -f, --force Continue even if file or directory already exists
126 -l, --log=FILE Log openssl output to FILE (default: ovs-log.log)
127 -h, --help Print this usage message.
128 -V, --version Display version information.
133 echo "ovs-pki (Open vSwitch) @VERSION@"
173 echo "unrecognized option $option" >&2
177 if test -z "$command"; then
179 elif test -z "${arg1+set}"; then
181 elif test -z "${arg2+set}"; then
184 echo "$option: only two arguments may be specified" >&2
191 if test -n "$prev"; then
192 option=--`echo $prev | sed 's/_/-/g'`
193 { echo "$as_me: error: missing argument to $option" >&2
194 { (exit 1); exit 1; }; }
196 if test -z "$command"; then
197 echo "$0: missing command name; use --help for help" >&2
200 if test "$keytype" != rsa && test "$keytype" != dsa; then
201 echo "$0: argument to -k or --key must be rsa or dsa" >&2
204 if test "$bits" -lt 1024; then
205 echo "$0: argument to -B or --bits must be at least 1024" >&2
208 if test -z "$dsaparam"; then
209 dsaparam=$pkidir/dsaparam.pem
216 logdir=$(dirname "$log")
217 if test ! -d "$logdir"; then
218 mkdir -p -m755 "$logdir" 2>/dev/null || true
219 if test ! -d "$logdir"; then
220 echo "$0: log directory $logdir does not exist and cannot be created" >&2
225 if test "$command" = "init"; then
226 if test -e "$pkidir" && test "$force" != "yes"; then
227 echo "$0: $pkidir already exists and --force not specified" >&2
231 if test ! -d "$pkidir"; then
237 if test $keytype = dsa && test ! -e dsaparam.pem; then
238 echo "Generating DSA parameters, please wait..." >&2
239 openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3
242 # Get the current date to add some uniqueness to this certificate
243 curr_date=`date +"%Y %b %d %T"`
246 for ca in controllerca switchca; do
247 echo "Creating $ca..." >&2
252 mkdir -p certs crl newcerts
253 mkdir -p -m 0700 private
254 mkdir -p -m 0733 incoming
256 test -e crlnumber || echo 01 > crlnumber
257 test -e serial || echo 01 > serial
259 # Put DSA parameters in directory.
260 if test $keytype = dsa && test ! -e dsaparam.pem; then
264 # Write CA configuration file.
265 if test ! -e ca.cnf; then
266 sed "s/@ca@/$ca/g;s/@curr_date@/$curr_date/g" > ca.cnf <<'EOF'
269 distinguished_name = req_distinguished_name
271 [ req_distinguished_name ]
277 CN = OVS @ca@ CA Certificate (@curr_date@)
284 database = $dir/index.txt # index file.
285 new_certs_dir = $dir/newcerts # new certs dir
286 certificate = $dir/cacert.pem # The CA cert
287 serial = $dir/serial # serial no file
288 private_key = $dir/private/cakey.pem# CA private key
289 RANDFILE = $dir/private/.rand # random number file
290 default_days = 365 # how long to certify for
291 default_crl_days= 30 # how long before next CRL
292 default_md = md5 # md to use
293 policy = policy # default policy
294 email_in_dn = no # Don't add the email into cert DN
295 name_opt = ca_default # Subject name display option
296 cert_opt = ca_default # Certificate display option
297 copy_extensions = none # Don't copy extensions from request
298 unique_subject = no # Allow certs with duplicate subjects
302 countryName = optional
303 stateOrProvinceName = optional
304 organizationName = match
305 organizationalUnitName = optional
306 commonName = supplied
307 emailAddress = optional
311 # Create certificate authority.
312 if test $keytype = dsa; then
313 newkey=dsa:dsaparam.pem
317 openssl req -config ca.cnf -nodes \
318 -newkey $newkey -keyout private/cakey.pem -out careq.pem \
320 openssl ca -config ca.cnf -create_serial -out cacert.pem \
321 -days 2191 -batch -keyfile private/cakey.pem -selfsign \
322 -infiles careq.pem 1>&3 2>&3
323 chmod 0700 private/cakey.pem
331 if test -z "$arg1" || test -n "$arg2"; then
332 echo "$0: $command must have exactly one argument; use --help for help" >&2
338 if test -n "$arg2"; then
339 echo "$0: $command must have zero or one arguments; use --help for help" >&2
345 if test -z "$arg1"; then
346 echo "$0: $command must have one or two arguments; use --help for help" >&2
352 if test -e "$1" && test "$force" != "yes"; then
353 echo "$0: $1 already exists and --force not supplied" >&2
359 test -n "$type" || exit 123 # Forgot to call check_type?
365 echo "Prefix $arg1 is too short (less than 4 hex digits)" >&2
370 fingerprint=$(cd "$pkidir/${type}ca/incoming" && echo "$1"*-req.pem | sed 's/-req\.pem$//')
373 echo "No certificate requests matching $1" >&2
377 echo "$1 matches more than one certificate request:" >&2
378 echo $fingerprint | sed 's/ /\
386 req="$pkidir/${type}ca/incoming/$fingerprint-req.pem"
387 cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem"
391 TMP=/tmp/ovs-pki.tmp$$
400 date=$(file_mod_date "$file")
401 if grep -e '-BEGIN CERTIFICATE-' "$file" > /dev/null; then
402 fingerprint=$(openssl x509 -noout -in "$file" -fingerprint |
403 sed 's/SHA1 Fingerprint=//' | tr -d ':')
405 fingerprint=$(sha1sum "$file" | awk '{print $1}')
407 printf "$name\\t$date\\n"
410 printf "\\t(correct fingerprint in filename)\\n"
413 printf "\\tfingerprint $fingerprint\\n"
418 verify_fingerprint() {
420 if test $batch != yes; then
421 echo "Does fingerprint match? (yes/no)"
423 if test "$answer" != yes; then
424 echo "Match failure, aborting" >&2
431 if test x = x"$1"; then
433 elif test "$1" = switch || test "$1" = controller; then
436 echo "$0: type argument must be 'switch' or 'controller'" >&2
442 number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/')
443 unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/')
458 echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2
462 echo $(($number * $factor))
466 if test ! -e "$1"; then
467 echo "$0: $1 does not exist" >&2
472 pkidir_must_exist() {
473 if test ! -e "$pkidir"; then
474 echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2
476 elif test ! -d "$pkidir"; then
477 echo "$0: $pkidir is not a directory" >&2
483 must_not_exist "$arg1-privkey.pem"
484 must_not_exist "$arg1-req.pem"
486 cat > "$TMP/req.cnf" <<EOF
489 distinguished_name = req_distinguished_name
491 [ req_distinguished_name ]
496 OU = Open vSwitch certifier
497 CN = Open vSwitch certificate for $arg1
499 if test $keytype = rsa; then
500 (umask 077 && openssl genrsa -out "$1-privkey.pem" $bits) 1>&3 2>&3 \
503 must_exist "$dsaparam"
504 (umask 077 && openssl gendsa -out "$1-privkey.pem" "$dsaparam") \
507 openssl req -config "$TMP/req.cnf" -new -text \
508 -key "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3
516 (cd "$pkidir/${type}ca" &&
517 openssl ca -config ca.cnf -batch -in /dev/stdin) \
518 < "$1" > "$2.tmp$$" 2>&3
524 if test "$files" != "$1"; then
530 if test "$command" = req; then
534 fingerprint "$arg1-req.pem"
535 elif test "$command" = sign; then
538 verify_fingerprint "$arg1-req.pem"
540 sign_request "$arg1-req.pem" "$arg2-cert.pem"
541 elif test "$command" = req+sign; then
547 sign_request "$arg1-req.pem" "$arg1-cert.pem"
548 fingerprint "$arg1-req.pem"
549 elif test "$command" = verify; then
551 must_exist "$arg1-cert.pem"
555 openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem"
556 elif test "$command" = fingerprint; then
560 elif test "$command" = self-sign; then
562 must_exist "$arg1-req.pem"
563 must_exist "$arg1-privkey.pem"
564 must_not_exist "$arg1-cert.pem"
566 # Create both the private key and certificate with restricted permissions.
568 openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem.tmp" \
569 -signkey "$arg1-privkey.pem" -req -text) 2>&3 || exit $?
571 # Reset the permissions on the certificate to the user's default.
572 cat "$arg1-cert.pem.tmp" > "$arg1-cert.pem"
573 rm -f "$arg1-cert.pem.tmp"
574 elif test "$command" = ls; then
577 cd "$pkidir/${type}ca/incoming"
578 for file in $(glob "$arg1*-req.pem"); do
581 elif test "$command" = flush; then
584 rm -f "$pkidir/${type}ca/incoming/"*
585 elif test "$command" = reject; then
588 resolve_prefix "$arg1"
591 elif test "$command" = approve; then
594 resolve_prefix "$arg1"
597 cp "$req" "$TMP/$req"
598 verify_fingerprint "$TMP/$req"
599 sign_request "$TMP/$req"
600 rm -f "$req" "$TMP/$req"
601 elif test "$command" = prompt; then
606 cd "$pkidir/${type}ca/incoming"
607 for req in $(glob "*-req.pem"); do
608 cp "$req" "$TMP/$req"
610 cert=$(echo "$pkidir/${type}ca/certs/$req" |
611 sed 's/-req.pem/-cert.pem/')
612 if test -f $cert; then
613 echo "Request $req already approved--dropping duplicate request"
614 rm -f "$req" "$TMP/$req"
620 fingerprint "$TMP/$req" "$req"
621 printf "Disposition for this request (skip/approve/reject)? "
625 echo "Approving $req"
626 sign_request "$TMP/$req" "$cert"
627 rm -f "$req" "$TMP/$req"
630 echo "Rejecting $req"
631 rm -f "$req" "$TMP/$req"
638 elif test "$command" = expire; then
640 cutoff=$(($(date +%s) - $(parse_age ${arg1-1day})))
641 for type in switch controller; do
642 cd "$pkidir/${type}ca/incoming" || exit 1
643 for file in $(glob "*"); do
644 time=$(file_mod_epoch "$file")
645 if test "$time" -lt "$cutoff"; then
651 echo "$0: $command command unknown; use --help for help" >&2