diff options
Diffstat (limited to 'utilities/ovs-pki.in')
-rwxr-xr-x | utilities/ovs-pki.in | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/utilities/ovs-pki.in b/utilities/ovs-pki.in new file mode 100755 index 000000000..15ac17b92 --- /dev/null +++ b/utilities/ovs-pki.in @@ -0,0 +1,582 @@ +#! /bin/sh + +set -e + +pkidir='@PKIDIR@' +command= +prev= +force=no +batch=no +log='@LOGDIR@/ovs-pki.log' +keytype=rsa +bits=2048 +for option; do + # This option-parsing mechanism borrowed from a Autoconf-generated + # configure script under the following license: + + # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, + # 2002, 2003, 2004, 2005, 2006, 2009 Free Software Foundation, Inc. + # This configure script is free software; the Free Software Foundation + # gives unlimited permission to copy, distribute and modify it. + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + eval $prev=\$option + prev= + continue + fi + case $option in + *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;; + *) optarg=yes ;; + esac + + case $dashdash$option in + --) + dashdash=yes ;; + -h|--help) + cat <<EOF +ovs-pki, for managing a simple OpenFlow public key infrastructure +usage: $0 [OPTION...] COMMAND [ARG...] + +The valid stand-alone commands and their arguments are: + init Initialize the PKI + req NAME Create new private key and certificate request + named NAME-privkey.pem and NAME-req.pem, resp. + sign NAME [TYPE] Sign switch certificate request NAME-req.pem, + producing certificate NAME-cert.pem + req+sign NAME [TYPE] Combine the above two steps, producing all three files. + verify NAME [TYPE] Checks that NAME-cert.pem is a valid TYPE certificate + fingerprint FILE Prints the fingerprint for FILE + self-sign NAME Sign NAME-req.pem with NAME-privkey.pem, + producing self-signed certificate NAME-cert.pem + +The following additional commands manage an online PKI: + ls [PREFIX] [TYPE] Lists incoming requests of the given TYPE, optionally + limited to those whose fingerprint begins with PREFIX + flush [TYPE] Rejects all incoming requests of the given TYPE + reject PREFIX [TYPE] Rejects the incoming request(s) whose fingerprint begins + with PREFIX and has the given TYPE + approve PREFIX [TYPE] Approves the incoming request whose fingerprint begins + with PREFIX and has the given TYPE + expire [AGE] Rejects all incoming requests older than AGE, in + one of the forms Ns, Nmin, Nh, Nday (default: 1day) + prompt [TYPE] Interactively prompts to accept or reject each incoming + request of the given TYPE + +Each TYPE above is a certificate type: 'switch' (default) or 'controller'. + +Options for 'init', 'req', and 'req+sign' only: + -k, --key=rsa|dsa Type of keys to use (default: rsa) + -B, --bits=NBITS Number of bits in keys (default: 2048). For DSA keys, + this has an effect only on 'init'. + -D, --dsaparam=FILE File with DSA parameters (DSA only) + (default: dsaparam.pem within PKI directory) +Options for use with the 'sign' and 'approve' commands: + -b, --batch Skip fingerprint verification +Options that apply to any command: + -d, --dir=DIR Directory where the PKI is located + (default: $pkidir) + -f, --force Continue even if file or directory already exists + -l, --log=FILE Log openssl output to FILE (default: ovs-log.log) + -h, --help Print this usage message. +EOF + exit 0 + ;; + --di*=*) + pkidir=$optarg + ;; + --di*|-d) + prev=pkidir + ;; + --k*=*) + keytype=$optarg + ;; + --k*|-k) + prev=keytype + ;; + --bi*=*) + bits=$optarg + ;; + --bi*|-B) + prev=bits + ;; + --ds*=*) + dsaparam=$optarg + ;; + --ds*|-D) + prev=dsaparam + ;; + --l*=*) + log=$optarg + ;; + --l*|-l) + prev=log + ;; + --force|-f) + force=yes + ;; + --ba*|-b) + batch=yes + ;; + -*) + echo "unrecognized option $option" >&2 + exit 1 + ;; + *) + if test -z "$command"; then + command=$option + elif test -z "${arg1+set}"; then + arg1=$option + elif test -z "${arg2+set}"; then + arg2=$option + else + echo "$option: only two arguments may be specified" >&2 + exit 1 + fi + ;; + esac + shift +done +if test -n "$prev"; then + option=--`echo $prev | sed 's/_/-/g'` + { echo "$as_me: error: missing argument to $option" >&2 + { (exit 1); exit 1; }; } +fi +if test -z "$command"; then + echo "$0: missing command name; use --help for help" >&2 + exit 1 +fi +if test "$keytype" != rsa && test "$keytype" != dsa; then + echo "$0: argument to -k or --key must be rsa or dsa" + exit 1 +fi +if test "$bits" -lt 1024; then + echo "$0: argument to -B or --bits must be at least 1024" + exit 1 +fi +if test -z "$dsaparam"; then + dsaparam=$pkidir/dsaparam.pem +fi +case $log in + /*) ;; + *) $log="$PWD/$log" ;; +esac + +if test "$command" = "init"; then + if test -e "$pkidir" && test "$force" != "yes"; then + echo "$0: $pkidir already exists and --force not specified" >&2 + exit 1 + fi + + if test ! -d "$pkidir"; then + mkdir -p "$pkidir" + fi + cd "$pkidir" + exec 3>>$log + + if test $keytype = dsa && test ! -e dsaparam.pem; then + echo "Generating DSA parameters, please wait..." >&2 + openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3 + fi + + # Create the CAs. + for ca in controllerca switchca; do + echo "Creating $ca..." >&2 + oldpwd=$PWD + mkdir -p $ca + cd $ca + + mkdir -p certs crl newcerts + mkdir -p -m 0700 private + mkdir -p -m 0733 incoming + touch index.txt + test -e crlnumber || echo 01 > crlnumber + test -e serial || echo 01 > serial + + # Put DSA parameters in directory. + if test $keytype = dsa && test ! -e dsaparam.pem; then + cp ../dsaparam.pem . + fi + + # Write CA configuration file. + if test ! -e ca.cnf; then + sed "s/@ca@/$ca/g" > ca.cnf <<'EOF' +[ req ] +prompt = no +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +C = US +ST = CA +L = Palo Alto +O = Open vSwitch +OU = @ca@ +CN = Open vSwitch @ca@ CA Certificate + +[ ca ] +default_ca = the_ca + +[ the_ca ] +dir = . # top dir +database = $dir/index.txt # index file. +new_certs_dir = $dir/newcerts # new certs dir +certificate = $dir/cacert.pem # The CA cert +serial = $dir/serial # serial no file +private_key = $dir/private/cakey.pem# CA private key +RANDFILE = $dir/private/.rand # random number file +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = md5 # md to use +policy = policy # default policy +email_in_dn = no # Don't add the email into cert DN +name_opt = ca_default # Subject name display option +cert_opt = ca_default # Certificate display option +copy_extensions = none # Don't copy extensions from request + +# For the CA policy +[ policy ] +countryName = optional +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional +EOF + fi + + # Create certificate authority. + if test $keytype = dsa; then + newkey=dsa:dsaparam.pem + else + newkey=rsa:$bits + fi + openssl req -config ca.cnf -nodes \ + -newkey $newkey -keyout private/cakey.pem -out careq.pem \ + 1>&3 2>&3 + openssl ca -config ca.cnf -create_serial -out cacert.pem \ + -days 1095 -batch -keyfile private/cakey.pem -selfsign \ + -infiles careq.pem 1>&3 2>&3 + chmod 0700 private/cakey.pem + + cd "$oldpwd" + done + exit 0 +fi + +one_arg() { + if test -z "$arg1" || test -n "$arg2"; then + echo "$0: $command must have exactly one argument; use --help for help" >&2 + exit 1 + fi +} + +zero_or_one_args() { + if test -n "$arg2"; then + echo "$0: $command must have zero or one arguments; use --help for help" >&2 + exit 1 + fi +} + +one_or_two_args() { + if test -z "$arg1"; then + echo "$0: $command must have one or two arguments; use --help for help" >&2 + exit 1 + fi +} + +must_not_exist() { + if test -e "$1" && test "$force" != "yes"; then + echo "$0: $1 already exists and --force not supplied" >&2 + exit 1 + fi +} + +resolve_prefix() { + test -n "$type" || exit 123 # Forgot to call check_type? + + case $1 in + ????*) + ;; + *) + echo "Prefix $arg1 is too short (less than 4 hex digits)" + exit 0 + ;; + esac + + fingerprint=$(cd "$pkidir/${type}ca/incoming" && echo "$1"*-req.pem | sed 's/-req\.pem$//') + case $fingerprint in + "${1}*") + echo "No certificate requests matching $1" + exit 1 + ;; + *" "*) + echo "$1 matches more than one certificate request:" + echo $fingerprint | sed 's/ /\ +/g' + exit 1 + ;; + *) + # Nothing to do. + ;; + esac + req="$pkidir/${type}ca/incoming/$fingerprint-req.pem" + cert="$pkidir/${type}ca/certs/$fingerprint-cert.pem" +} + +make_tmpdir() { + TMP=/tmp/ovs-pki.tmp$$ + rm -rf $TMP + trap "rm -rf $TMP" 0 + mkdir -m 0700 $TMP +} + +fingerprint() { + local file=$1 + local name=${1-$2} + local date=$(date -r $file) + local fingerprint + if grep -q -e '-BEGIN CERTIFICATE-' "$file"; then + fingerprint=$(openssl x509 -noout -in "$file" -fingerprint | + sed 's/SHA1 Fingerprint=//' | tr -d ':') + else + fingerprint=$(sha1sum "$file" | awk '{print $1}') + fi + printf "$name\\t$date\\n" + case $file in + $fingerprint*) + printf "\\t(correct fingerprint in filename)\\n" + ;; + *) + printf "\\tfingerprint $fingerprint\\n" + ;; + esac +} + +verify_fingerprint() { + fingerprint "$@" + if test $batch != yes; then + echo "Does fingerprint match? (yes/no)" + read answer + if test "$answer" != yes; then + echo "Match failure, aborting" >&2 + exit 1 + fi + fi +} + +check_type() { + if test x = x"$1"; then + type=switch + elif test "$1" = switch || test "$1" = controller; then + type=$1 + else + echo "$0: type argument must be 'switch' or 'controller'" >&2 + exit 1 + fi +} + +parse_age() { + number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/') + unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/') + case $unit in + s) + factor=1 + ;; + min) + factor=60 + ;; + h) + factor=3600 + ;; + day) + factor=86400 + ;; + *) + echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2 + exit 1 + ;; + esac + echo $(($number * $factor)) +} + +must_exist() { + if test ! -e "$1"; then + echo "$0: $1 does not exist" >&2 + exit 1 + fi +} + +pkidir_must_exist() { + if test ! -e "$pkidir"; then + echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2 + exit 1 + elif test ! -d "$pkidir"; then + echo "$0: $pkidir is not a directory" >&2 + exit 1 + fi +} + +make_request() { + must_not_exist "$arg1-privkey.pem" + must_not_exist "$arg1-req.pem" + make_tmpdir + cat > "$TMP/req.cnf" <<EOF +[ req ] +prompt = no +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +C = US +ST = CA +L = Palo Alto +O = Open vSwitch +OU = Open vSwitch certifier +CN = Open vSwitch certificate for $arg1 +EOF + if test $keytype = rsa; then + newkey=rsa:$bits + else + must_exist "$dsaparam" + newkey=dsa:$dsaparam + fi + openssl req -config "$TMP/req.cnf" -text -nodes \ + -newkey $newkey -keyout "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3 +} + +sign_request() { + must_exist "$1" + must_not_exist "$2" + pkidir_must_exist + + (cd "$pkidir/${type}ca" && + openssl ca -config ca.cnf -batch -in /dev/stdin) \ + < "$1" > "$2.tmp$$" 2>&3 + mv "$2.tmp$$" "$2" +} + +glob() { + local files=$(echo $1) + if test "$files" != "$1"; then + echo "$files" + fi +} + +exec 3>>$log || true +if test "$command" = req; then + one_arg + + make_request "$arg1" + fingerprint "$arg1-req.pem" +elif test "$command" = sign; then + one_or_two_args + check_type "$arg2" + verify_fingerprint "$arg1-req.pem" + + sign_request "$arg1-req.pem" "$arg2-cert.pem" +elif test "$command" = req+sign; then + one_or_two_args + check_type "$arg2" + + pkidir_must_exist + make_request "$arg1" + sign_request "$arg1-req.pem" "$arg1-cert.pem" + fingerprint "$arg1-req.pem" +elif test "$command" = verify; then + one_or_two_args + must_exist "$arg1-cert.pem" + check_type "$arg2" + + pkidir_must_exist + openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem" +elif test "$command" = fingerprint; then + one_arg + + fingerprint "$arg1" +elif test "$command" = self-sign; then + one_arg + must_exist "$arg1-req.pem" + must_exist "$arg1-privkey.pem" + must_not_exist "$arg1-cert.pem" + + openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem" \ + -signkey "$arg1-privkey.pem" -req -text 2>&3 +elif test "$command" = ls; then + check_type "$arg2" + + cd "$pkidir/${type}ca/incoming" + for file in $(glob "$arg1*-req.pem"); do + fingerprint $file + done +elif test "$command" = flush; then + check_type "$arg1" + + rm -f "$pkidir/${type}ca/incoming/"* +elif test "$command" = reject; then + one_or_two_args + check_type "$arg2" + resolve_prefix "$arg1" + + rm -f "$req" +elif test "$command" = approve; then + one_or_two_args + check_type "$arg2" + resolve_prefix "$arg1" + + make_tmpdir + cp "$req" "$TMP/$req" + verify_fingerprint "$TMP/$req" + sign_request "$TMP/$req" + rm -f "$req" "$TMP/$req" +elif test "$command" = prompt; then + zero_or_one_args + check_type "$arg1" + + make_tmpdir + cd "$pkidir/${type}ca/incoming" + for req in $(glob "*-req.pem"); do + cp "$req" "$TMP/$req" + + cert=$(echo "$pkidir/${type}ca/certs/$req" | + sed 's/-req.pem/-cert.pem/') + if test -f $cert; then + echo "Request $req already approved--dropping duplicate request" + rm -f "$req" "$TMP/$req" + continue + fi + + echo + echo + fingerprint "$TMP/$req" "$req" + printf "Disposition for this request (skip/approve/reject)? " + read answer + case $answer in + approve) + echo "Approving $req" + sign_request "$TMP/$req" "$cert" + rm -f "$req" "$TMP/$req" + ;; + r*) + echo "Rejecting $req" + rm -f "$req" "$TMP/$req" + ;; + *) + echo "Skipping $req" + ;; + esac + done +elif test "$command" = expire; then + zero_or_one_args + cutoff=$(($(date +%s) - $(parse_age ${arg1-1day}))) + for type in switch controller; do + cd "$pkidir/${type}ca/incoming" || exit 1 + for file in $(glob "*"); do + time=$(date -r "$file" +%s) + if test "$time" -lt "$cutoff"; then + rm -f "$file" + fi + done + done +else + echo "$0: $command command unknown; use --help for help" >&2 + exit 1 +fi |