From 85007db580ccf662a45cf2aaeb83518ad2ddb85a Mon Sep 17 00:00:00 2001 From: Cullum Smith Date: Thu, 11 Jul 2024 10:55:45 -0400 Subject: initial boxconf scaffolding --- pki | 358 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100755 pki (limited to 'pki') diff --git a/pki b/pki new file mode 100755 index 0000000..9a94121 --- /dev/null +++ b/pki @@ -0,0 +1,358 @@ +#!/bin/sh +# +# Utility to manage an internal certificate authority using OpenSSL. + +set -eu + +PROGNAME=pki +USAGE="" +BOXCONF_ROOT=$(dirname "$(readlink -f "$0")") + +BOXCONF_CA_PASSWORD_FILE="${BOXCONF_ROOT}/.ca_password" +CA_VALID_DAYS=3650 +DEFAULT_VALID_DAYS=3650 +EC_CURVE=prime256v1 +DIGEST=sha256 +CIPHER=aes256 + +usage(){ + printf 'usage: %s %s\n' "$PROGNAME" "$USAGE" 2>&1 + exit 2 +} + +_pki_get_ca_password(){ + # Acquire the CA password. + # If the BOXCONF_CA_PASSWORD environment variable is set, use that. + # Next, try reading the password from the .ca_password file. + # If all else fails, prompt interactively. + if [ -n "${BOXCONF_CA_PASSWORD:-}" ]; then + return + elif [ -f "$BOXCONF_CA_PASSWORD_FILE" ]; then + BOXCONF_CA_PASSWORD=$(cat "$BOXCONF_CA_PASSWORD_FILE") + else + _boxconf_read_password 'Enter CA password: ' BOXCONF_CA_PASSWORD + fi +} + +_pki_dn2cnf() { + # Convert an LDAP DN to its OpenSSL .cnf file representation. + echo "$1" \ + | tr ',' '\n' \ + | awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }' \ + | sed \ + -e 's/^[Cc][Nn]=/commonName=/' \ + -e 's/^[Oo]=/organizationName=/' \ + -e 's/^[Oo][Uu]=/organizationalUnitName=/' \ + -e 's/^[Dd][Cc]=/domainComponent=/' \ + -e 's/^[Cc]=/countryName=/' \ + -e 's/^[Ss][Tt]=/stateOrProvinceName=/' \ + -e 's/^[Ll]=/locality=/' \ + -e 's/^[Ss][Nn]=/surName=/' \ + -e 's/^[Gg][Nn]=/givenName=/' \ + -e 's/^[Uu][Ii][Dd]=/userId=/' \ + | awk '{print NR-1 "." $0}' +} + +_pki_postsign(){ + # Create symlinks and delete temp files after signing a certificate. + # $1 = certificate path + + # Create symlink with human-readable name. + serial=$(awk 'END{print $3}' "${BOXCONF_CA_DIR}/index.txt") + ln -sf "../certs/${serial}.pem" "${BOXCONF_CA_DIR}/${1}.crt" + + # Create fullchain certificate. + cat "${BOXCONF_CA_DIR}/${1}.crt" "${BOXCONF_CA_DIR}/ca.crt" > "${BOXCONF_CA_DIR}/${1}.fullchain.crt" + + # Delete useless files. + rm -f \ + "${BOXCONF_CA_DIR}/index.txt.old" \ + "${BOXCONF_CA_DIR}/index.txt.attr.old" \ + "${BOXCONF_CA_DIR}/serial.old" +} + +_pki_sign(){ + # Given an OpenSSL config file, generate a signed certificate keypair. + # $1 = certificate path + # $2 = validity time (days) + + # Generate encrypted private key for the server certificate. + PASS="$BOXCONF_VAULT_PASSWORD" openssl genpkey \ + -algorithm ec \ + -pkeyopt "ec_paramgen_curve:${EC_CURVE}" \ + "-${CIPHER}" \ + -pass env:PASS \ + -out "${BOXCONF_CA_DIR}/${1}.key" + + # Generate the CSR. + PASS="$BOXCONF_VAULT_PASSWORD" openssl req -new \ + -key "${BOXCONF_CA_DIR}/${1}.key" \ + "-${DIGEST}" \ + -passin env:PASS \ + -config "${BOXCONF_CA_DIR}/${1}.cnf" \ + -out "${BOXCONF_CA_DIR}/${1}.csr" + + # Sign the certificate. + PASS="$BOXCONF_CA_PASSWORD" openssl ca -batch \ + -config "${BOXCONF_CA_DIR}/ca.cnf" \ + -passin env:PASS \ + ${2:+-days $2} \ + -notext \ + -out /dev/null \ + -outdir "${BOXCONF_CA_DIR}/certs" \ + -infiles "${BOXCONF_CA_DIR}/${1}.csr" + + _pki_postsign "$1" +} + +_pki_renew(){ + # Re-sign an existing certificate. + # $1 = certificate path + # $2 = validity time (time) + _pki_get_ca_password + + # Sign the certificate. + PASS="$BOXCONF_CA_PASSWORD" openssl ca -batch \ + -config "${BOXCONF_CA_DIR}/ca.cnf" \ + -passin env:PASS \ + ${2:+-days $2} \ + -notext \ + -out /dev/null \ + -outdir "${BOXCONF_CA_DIR}/certs" \ + -infiles "${BOXCONF_CA_DIR}/${1}.csr" + + _pki_postsign "$1" +} + +pki_init(){ + # Initialize the CA. Create CA cert, private key, and OpenSSL configuration. + USAGE='init [-c CONSTRAINT]... DOMAIN' + + constraints='' + while getopts :c: opt; do + case $opt in + c) constraints="${constraints}, permitted;${OPTARG}" ;; + :) usage ;; + ?) usage ;; + esac + done + shift $((OPTIND - 1 )) + + [ $# -eq 1 ] || usage + domain=$1 + + [ -d "$BOXCONF_CA_DIR" ] && die 'CA already exists' + _pki_get_ca_password + mkdir -p "${BOXCONF_CA_DIR}/certs" + + # Generate encrypted private key for CA. + PASS="$BOXCONF_CA_PASSWORD" openssl genpkey \ + -algorithm ec \ + -pkeyopt "ec_paramgen_curve:${EC_CURVE}" \ + "-${CIPHER}" \ + -pass env:PASS \ + -out "${BOXCONF_CA_DIR}/ca.key" + + # Create a config file for the CA certificate. + cat > "${BOXCONF_CA_DIR}/ca.cnf" < "${BOXCONF_CA_DIR}/${hostname}/${certname}.cnf" < "${BOXCONF_CA_DIR}/${hostname}/${certname}.cnf" <