summaryrefslogtreecommitdiff
path: root/utilities/ovs-pki.in
diff options
context:
space:
mode:
Diffstat (limited to 'utilities/ovs-pki.in')
-rwxr-xr-xutilities/ovs-pki.in582
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