├── .gitignore ├── common ├── global.sh ├── output.sh ├── gen-selfsigned.sh ├── gen-ca.sh ├── gen-password.sh └── gen-keypair.sh ├── Dockerfile ├── usage ├── ca ├── selfsigned-keypair ├── README.md └── signed-keypair /.gitignore: -------------------------------------------------------------------------------- 1 | certificates/ 2 | test.sh 3 | -------------------------------------------------------------------------------- /common/global.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Variables shared by common/ scripts. 4 | 5 | set -o errexit 6 | 7 | export ROOTDIR=$(cd $(dirname $0); pwd) 8 | export CONFDIR=${ROOTDIR}/conf 9 | export CERTDIR=/certificates 10 | 11 | export PASSFILE="${HOME}/password" 12 | export CAFILE="${CERTDIR}/ca.pem" 13 | 14 | export PASSOPT="file:${PASSFILE}" 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:wheezy 2 | MAINTAINER smashwilson@gmail.com 3 | 4 | RUN useradd hagrid && \ 5 | apt-get update && \ 6 | DEBIAN_FRONTEND=noninteractive apt-get install -q -y openssl 7 | 8 | ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/hagrid/ 9 | 10 | ADD . /home/hagrid/ 11 | RUN chown -R hagrid:hagrid /home/hagrid 12 | 13 | USER hagrid 14 | WORKDIR /home/hagrid/ 15 | 16 | CMD ["usage"] 17 | -------------------------------------------------------------------------------- /common/output.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Output coloring to make keymaster output show up more prominently among the OpenSSL output. 4 | 5 | _color() 6 | { 7 | local COLOR_CODE=$1 8 | local SIGIL=$2 9 | local MESSAGE=$3 10 | 11 | echo -e "\033[${COLOR_CODE}m[${SIGIL}]\033[0m ${MESSAGE}" 12 | } 13 | 14 | info() 15 | { 16 | _color "1;34" ">>" "${1}" 17 | } 18 | 19 | success() 20 | { 21 | _color "1;32" "<<" "${1}" 22 | } 23 | 24 | warning() 25 | { 26 | _color "1;33" "!!" "${1}" 27 | } 28 | 29 | error() 30 | { 31 | _color "1;31" "!!" "${1}" 32 | } 33 | -------------------------------------------------------------------------------- /common/gen-selfsigned.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate an independent, self-signed keypair that isn't related to a certificate authority. 4 | 5 | # Arguments: 6 | # - NAME 7 | # - LIFETIME in days 8 | generate_selfsigned() 9 | { 10 | local NAME=$1 11 | local LIFETIME=$2 12 | 13 | info "Generating a self-signed keypair for: <${NAME}>" 14 | 15 | openssl req -x509 -newkey rsa:2048 -days ${LIFETIME} -nodes -batch \ 16 | -keyout /certificates/${NAME}-key.pem \ 17 | -out /certificates/${NAME}-cert.pem 18 | 19 | success "Self-signed keypair generated for: <${NAME}>" 20 | } 21 | -------------------------------------------------------------------------------- /usage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Show a usage statement for the container image. 4 | 5 | cat < certificates/password 18 | 19 | EOM 20 | exit 1 21 | fi 22 | 23 | touch ${PASSFILE} 24 | chmod 600 ${PASSFILE} 25 | 26 | # "If the same pathname argument is supplied to -passin and -passout arguments then the first 27 | # line will be used for the input password and the next line for the output password." 28 | cat ${CERTDIR}/password > ${PASSFILE} 29 | cat ${CERTDIR}/password >> ${PASSFILE} 30 | } 31 | 32 | require_password() 33 | { 34 | [ -f ${PASSFILE} ] || generate_password 35 | } 36 | -------------------------------------------------------------------------------- /ca: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate a certificate authority. 4 | 5 | set -o errexit 6 | 7 | for SCRIPT in common/*.sh; do 8 | source ${SCRIPT} 9 | done 10 | 11 | LIFETIME="" 12 | 13 | FAIL="false" 14 | 15 | while getopts ":l:" VARNAME; do 16 | case ${VARNAME} in 17 | l) 18 | LIFETIME="${OPTARG}" 19 | ;; 20 | \?) 21 | error "Unrecognized option: -${OPTARG}" 22 | FAIL="true" 23 | ;; 24 | :) 25 | error "Option -${OPTARG} requires an argument." 26 | FAIL="true" 27 | ;; 28 | esac 29 | done 30 | 31 | # Default to 365 days. 32 | [ -z "${LIFETIME}" ] && LIFETIME="365" 33 | 34 | # Default to 365 days. 35 | [ -z "${LIFETIME}" ] && LIFETIME="365" 36 | 37 | if [ "${FAIL}" = "true" ]; then 38 | cat < certificates/password 21 | ``` 22 | 23 | 1. Run the container with different commands to create a certificate authority, signed keypairs or self-signed keypairs. You'll need to mount your input/output directory to the path `/certificates` within the container. 24 | 25 | ```bash 26 | KEYMASTER="docker run --rm -v $(pwd)/certificates/:/certificates/ cloudpipe/keymaster" 27 | 28 | # Certificate authority 29 | # certificates/ca.pem and certificates/ca-key.pem 30 | ${KEYMASTER} ca 31 | 32 | # Signed client keypair for "service1.host.com" 33 | # certificates/service1-cert.pem and certificates/service1-key.pem 34 | ${KEYMASTER} signed-keypair -n service1 -h service1.host.com 35 | 36 | # Signed server keypair for "service2.host.com" 37 | # certificates/service2-cert.pem and certificates/service2-key.pem 38 | ${KEYMASTER} signed-keypair -n service2 -h service2.host.com -p server 39 | 40 | # Self-signed credentials for development of externally-facing parts 41 | # certificates/external-cert.pem and certificates/external-key.pem 42 | ${KEYMASTER} selfsigned-keypair -n external 43 | ``` 44 | -------------------------------------------------------------------------------- /common/gen-keypair.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate a keypair that's signed by the certificate authority. These should be used for internal 4 | # communications. 5 | 6 | # Arguments: 7 | # - NAME 8 | # - PURPOSE, either "server", "client", or "both" 9 | # - LIFETIME in days 10 | # - HOSTNAME 11 | generate_keypair() 12 | { 13 | require_password 14 | require_ca_certificate 15 | 16 | local NAME=$1 17 | local PURPOSE=$2 18 | local LIFETIME=$3 19 | local HOSTNAME=$4 20 | local ALTNAME=$5 21 | 22 | local SERIALOPT="-CAcreateserial" 23 | [ -f ${CERTDIR}/ca.srl ] && SERIALOPT="-CAserial ${CERTDIR}/ca.srl" 24 | 25 | local EXTFILE="/tmp/ext.cnf" 26 | local EXTOPT="" 27 | if [ "${PURPOSE}" = "client" ]; then 28 | echo "extendedKeyUsage = clientAuth" >> "${EXTFILE}" 29 | EXTOPT="-extfile ${EXTFILE}" 30 | elif [ "${PURPOSE}" = "both" ]; then 31 | echo "extendedKeyUsage = clientAuth,serverAuth" >> "${EXTFILE}" 32 | EXTOPT="-extfile ${EXTFILE}" 33 | fi 34 | 35 | if [ ! -z "${ALTNAME}" ]; then 36 | echo "subjectAltName=${ALTNAME}" >> "${EXTFILE}" 37 | EXTOPT="-extfile ${EXTFILE}" 38 | fi 39 | 40 | info "Generating a CA-signed keypair for: <${NAME}>" 41 | 42 | info ".. key" 43 | openssl genrsa -des3 \ 44 | -passout ${PASSOPT} \ 45 | -out ${CERTDIR}/${NAME}-key.pem 2048 46 | 47 | info ".. request" 48 | openssl req -subj "/CN=${HOSTNAME}" -new \ 49 | -batch \ 50 | -passin ${PASSOPT} \ 51 | -key ${CERTDIR}/${NAME}-key.pem \ 52 | -passout ${PASSOPT} \ 53 | -out ${CERTDIR}/${NAME}-req.csr 54 | 55 | info ".. certificate" 56 | openssl x509 -req -days ${LIFETIME} \ 57 | -passin ${PASSOPT} \ 58 | -in ${CERTDIR}/${NAME}-req.csr \ 59 | -CA ${CERTDIR}/ca.pem \ 60 | -CAkey ${CERTDIR}/ca-key.pem \ 61 | ${SERIALOPT} \ 62 | ${EXTOPT} \ 63 | -out ${CERTDIR}/${NAME}-cert.pem 64 | 65 | info ".. removing key password" 66 | openssl rsa \ 67 | -passin ${PASSOPT} \ 68 | -in ${CERTDIR}/${NAME}-key.pem \ 69 | -out ${CERTDIR}/${NAME}-key.pem 70 | 71 | success "CA-signed keypair generated for: <${NAME}>" 72 | } 73 | -------------------------------------------------------------------------------- /signed-keypair: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Generate a keypair, signed by a certificate authority. 4 | 5 | for SCRIPT in common/*.sh; do 6 | source ${SCRIPT} 7 | done 8 | 9 | NAME="" 10 | PURPOSE="" 11 | LIFETIME="" 12 | HOSTNAME="" 13 | 14 | FAIL="false" 15 | 16 | while getopts ":n:p:l:h:s:" VARNAME; do 17 | case ${VARNAME} in 18 | n) 19 | NAME="${OPTARG}" 20 | ;; 21 | p) 22 | case ${OPTARG} in 23 | client) 24 | PURPOSE="client" 25 | ;; 26 | server) 27 | PURPOSE="server" 28 | ;; 29 | both) 30 | PURPOSE="both" 31 | ;; 32 | *) 33 | error 'Invalid purpose: must be "client", "server", or "both".' 34 | FAIL="true" 35 | ;; 36 | esac 37 | ;; 38 | l) 39 | LIFETIME="${OPTARG}" 40 | ;; 41 | h) 42 | HOSTNAME="${OPTARG}" 43 | ;; 44 | s) 45 | ALTNAME="${OPTARG}" 46 | ;; 47 | \?) 48 | error "Unrecognized option: -${OPTARG}" 49 | FAIL="true" 50 | ;; 51 | :) 52 | error "Option -${OPTARG} requires an argument." 53 | FAIL="true" 54 | ;; 55 | esac 56 | done 57 | 58 | [ -z "${NAME}" ] && { 59 | error "Missing required parameter: -n" 60 | FAIL="true" 61 | } 62 | 63 | [ -z "${HOSTNAME}" ] && { 64 | error "Missing required parameter: -h" 65 | FAIL="true" 66 | } 67 | 68 | # Default to client. 69 | [ -z "${PURPOSE}" ] && PURPOSE="client" 70 | 71 | # Default to 365 days. 72 | [ -z "${LIFETIME}" ] && LIFETIME="365" 73 | 74 | if [ "${FAIL}" = "true" ]; then 75 | cat <