#! /bin/sh # Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. 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 <&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" >&2 exit 1 fi if test "$bits" -lt 1024; then echo "$0: argument to -B or --bits must be at least 1024" >&2 exit 1 fi if test -z "$dsaparam"; then dsaparam=$pkidir/dsaparam.pem fi case $log in /*) ;; *) log="$PWD/$log" ;; esac logdir=$(dirname "$log") if test ! -d "$logdir"; then mkdir -p -m755 "$logdir" 2>/dev/null || true if test ! -d "$logdir"; then echo "$0: log directory $logdir does not exist and cannot be created" >&2 exit 1 fi fi 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 # Get the current date to add some uniqueness to this certificate curr_date=`date +"%Y %b %d %T"` # 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;s/@curr_date@/$curr_date/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 = OVS @ca@ CA Certificate (@curr_date@) [ 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 unique_subject = no # Allow certs with duplicate subjects # 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 2191 -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)" >&2 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" >&2 exit 1 ;; *" "*) echo "$1 matches more than one certificate request:" >&2 echo $fingerprint | sed 's/ /\ /g' >&2 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() { file=$1 name=${1-$2} date=$(date -r $file) if grep -e '-BEGIN CERTIFICATE-' "$file" > /dev/null; 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" <&3 2>&3 \ || exit $? else must_exist "$dsaparam" (umask 077 && openssl gendsa -out "$1-privkey.pem" "$dsaparam") \ 1>&3 2>&3 || exit $? fi openssl req -config "$TMP/req.cnf" -new -text \ -key "$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() { 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" # Create both the private key and certificate with restricted permissions. (umask 077 && \ openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem.tmp" \ -signkey "$arg1-privkey.pem" -req -text) 2>&3 || exit $? # Reset the permissions on the certificate to the user's default. cat "$arg1-cert.pem.tmp" > "$arg1-cert.pem" rm -f "$arg1-cert.pem.tmp" 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