While working on my company’s implemenation of STIR/SHAKEN, I ran into a very basic issue – how do I easily create test self-signed SHAKEN certificates?
While I did find a couple of posts on how to do this, I wanted a nice script that would accomplish the following:
- Generate either a self-signed certificate or a key/CSR pair for use with an official certificate authority
- Allow easy passing of the certificate subject information (Country, State, Locality, etc.)
- Create certificate filenames with a unique identifier, to prevent name collisions (by default, the identifier is a UNIX timestamp)
Below is the script I came up with:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -e | |
# Set default values for variables | |
BUILD_DIR="/tmp/stir-shaken" | |
CA_DIR="${BUILD_DIR}/ca" | |
SP_DIR="${BUILD_DIR}/sp" | |
SP_CREATED_FILES_LIST="${SP_DIR}/stir_shaken_files.txt" | |
IDENTIFIER=$(date +%s) | |
DEBUG_MODE=0 | |
SELF_SIGNED=0 | |
CERT_COUNTRY="" | |
CERT_STATE="" | |
CERT_LOCALITY="" | |
CERT_ORG="" | |
CERT_OU="" | |
CERT_CN="SHAKEN" | |
# Default, 5 years. | |
SP_PRIVKEY_EXPIRY_DAYS=${SP_PRIVKEY_EXPIRY_DAYS:-1825} | |
# Default, 1 year. | |
SP_PUBKEY_EXPIRY_DAYS=${SP_PUBKEY_EXPIRY_DAYS:-366} | |
usage() { | |
echo | |
cat << EOF | |
Usage: | |
${0} [-b BUILD_DIR] [-i IDENTIFIER] [-d] [-h] [-s] <CERT_COUNTRY> <CERT_STATE> <CERT_LOCALITY> <CERT_ORG> <CERT_OU> [CERT_CN] | |
Arguments: | |
-h : Show this help message | |
-s : Generate self-signed certificate | |
-i IDENTIFIER : Unique Key/Cert/CSR identifier (default: current timestamp) | |
-d : Enable debug mode | |
-b BUILD_DIR : Build directory (default: ${BUILD_DIR}) | |
CERT_COUNTRY : Required. Certificate subject country | |
CERT_STATE : Required. Certificate subject state | |
CERT_LOCALITY : Required. Certificate subject locality | |
CERT_ORG : Required. Certificate subject organization | |
CERT_OU : Required. Certificate subject organizational unit | |
CERT_CN : Certificate subject common name (default: SHAKEN) | |
Environment overrides: | |
SP_PRIVKEY_EXPIRY_DAYS: Default: ${SP_PRIVKEY_EXPIRY_DAYS} | |
SP_PUBKEY_EXPIRY_DAYS: Default: ${SP_PUBKEY_EXPIRY_DAYS} | |
EOF | |
exit 1 | |
} | |
log() { | |
echo "$1" | |
} | |
log_debug() { | |
if [[ ${DEBUG_MODE} -eq 1 ]]; then | |
echo "$1" | |
fi | |
} | |
log_error() { | |
echo "$1" >&2 | |
} | |
validate_cert_info() { | |
if [[ -z "${CERT_COUNTRY}" || -z "${CERT_STATE}" || -z "${CERT_LOCALITY}" || -z "${CERT_ORG}" || -z "${CERT_OU}" || -z "${CERT_CN}" ]]; then | |
echo | |
log_error "ERROR: Incomplete certificate subject information." | |
log_error "Please provide all of the following: CERT_COUNTRY, CERT_STATE, CERT_LOCALITY, CERT_ORG, CERT_OU" | |
echo | |
usage | |
fi | |
} | |
make_build_dir() { | |
log "Cleaning old certs" | |
rm -rf "${BUILD_DIR}" | |
mkdir -p "${BUILD_DIR}" | |
} | |
create_sp_key() { | |
log "Creating SP key" | |
local sp_key="stir_shaken_sp_key_${IDENTIFIER}.pem" | |
mkdir -p "${SP_DIR}" | |
cd "${SP_DIR}" | |
openssl ecparam -noout -name prime256v1 -genkey -out ${sp_key} | |
} | |
create_self_signed_certs() { | |
log "Creating CA certificate and key" | |
local sp_key="stir_shaken_sp_key_${IDENTIFIER}.pem" | |
local sp_csr="stir_shaken_sp_csr_${IDENTIFIER}.pem" | |
local sp_cert="stir_shaken_sp_cert_${IDENTIFIER}.pem" | |
local ca_key="stir_shaken_ca_key.pem" | |
local ca_cert="stir_shaken_ca_cert.pem" | |
mkdir -p "${CA_DIR}" | |
cd "${CA_DIR}" | |
openssl ecparam -noout -name prime256v1 -genkey -out ${ca_key} | |
openssl req -x509 -new -nodes -key ${ca_key} -sha256 -days ${SP_PRIVKEY_EXPIRY_DAYS} -subj "/O=${CERT_ORG}/CN=${CERT_CN}" -out ${ca_cert} | |
cd "${SP_DIR}" | |
log "Configuring TNAuthList" | |
cat >TNAuthList.conf << EOF | |
asn1=SEQUENCE:tn_auth_list | |
[tn_auth_list] | |
field1=EXP:0,IA5:1001 | |
EOF | |
openssl asn1parse -genconf TNAuthList.conf -out TNAuthList.der | |
cat >openssl.conf << EOF | |
[ req ] | |
distinguished_name = req_distinguished_name | |
req_extensions = v3_req | |
[ req_distinguished_name ] | |
commonName = "${CERT_CN}" | |
[ v3_req ] | |
EOF | |
od -An -t x1 -w TNAuthList.der | sed -e 's/ /:/g' -e 's/^/1.3.6.1.5.5.7.1.26=DER/' >> openssl.conf | |
log "Creating SP certificate" | |
openssl req -new -nodes -key ${sp_key} -keyform PEM -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_LOCALITY}/O=${CERT_ORG}/OU=${CERT_OU}/CN=${CERT_CN}" -sha256 -config openssl.conf -out ${sp_csr} | |
log "Signing SP certificate" | |
openssl x509 -req -in ${sp_csr} -CA "${CA_DIR}/${ca_cert}" -CAkey "${CA_DIR}/${ca_key}" -CAcreateserial -days ${SP_PUBKEY_EXPIRY_DAYS} -sha256 -extfile openssl.conf -extensions v3_req -out ${sp_cert} | |
log "Verifying CA certificate" | |
if [[ ${DEBUG_MODE} -eq 1 ]]; then | |
openssl x509 -in "${CA_DIR}/${ca_cert}" -text -noout | |
else | |
openssl x509 -in "${CA_DIR}/${ca_cert}" -noout > /dev/null 2>&1 | |
fi | |
if [[ $? -ne 0 ]]; then | |
echo | |
log_error "ERROR: CA certificate verification failed." | |
echo | |
exit 1 | |
fi | |
log "Verifying SP certificate" | |
if [[ ${DEBUG_MODE} -eq 1 ]]; then | |
openssl x509 -in "${SP_DIR}/${sp_cert}" -text -noout | |
else | |
openssl x509 -in "${SP_DIR}/${sp_cert}" -noout > /dev/null 2>&1 | |
fi | |
if [[ $? -ne 0 ]]; then | |
echo | |
log_error "ERROR: SP certificate verification failed." | |
echo | |
exit 1 | |
fi | |
log "Setting proper permissions on generated files" | |
chmod 600 "${CA_DIR}/${ca_key}" "${SP_DIR}/${sp_key}" | |
chmod 644 "${CA_DIR}/${ca_cert}" "${SP_DIR}/${sp_csr}" "${SP_DIR}/${sp_cert}" | |
echo "Key: ${SP_DIR}/${sp_key}" > "${SP_CREATED_FILES_LIST}" | |
echo "Certificate: ${SP_DIR}/${sp_cert}" >> "${SP_CREATED_FILES_LIST}" | |
log "Self-signed STIR/SHAKEN certificate generated successfully!" | |
log "Key: ${SP_DIR}/${sp_key}" | |
log "Certificate: ${SP_DIR}/${sp_cert}" | |
log "Created files list written to: ${SP_CREATED_FILES_LIST}" | |
} | |
create_csr() { | |
log "Creating CSR for SP certificate" | |
local sp_key="stir_shaken_sp_key_${IDENTIFIER}.pem" | |
local sp_csr="stir_shaken_sp_csr_${IDENTIFIER}.pem" | |
openssl req -new -nodes -key ${sp_key} -keyform PEM -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_LOCALITY}/O=${CERT_ORG}/OU=${CERT_OU}/CN=${CERT_CN}" -sha256 -out ${sp_csr} | |
log "Verifying CSR" | |
if [[ ${DEBUG_MODE} -eq 1 ]]; then | |
openssl req -in "${SP_DIR}/${sp_csr}" -text -noout | |
else | |
openssl req -in "${SP_DIR}/${sp_csr}" -noout > /dev/null 2>&1 | |
fi | |
if [[ $? -ne 0 ]]; then | |
echo | |
log_error "ERROR: CSR verification failed." | |
echo | |
exit 1 | |
fi | |
log "Setting proper permissions on generated files" | |
chmod 600 "${SP_DIR}/${sp_key}" | |
chmod 644 "${SP_DIR}/${sp_csr}" | |
echo "Key: ${SP_DIR}/${sp_key}" > "${SP_CREATED_FILES_LIST}" | |
echo "CSR: ${SP_DIR}/${sp_csr}" >> "${SP_CREATED_FILES_LIST}" | |
log "STIR/SHAKEN CSR generated successfully!" | |
log "Key: ${SP_DIR}/${sp_key}" | |
log "CSR: ${SP_DIR}/${sp_csr}" | |
log "Created files list written to: ${SP_CREATED_FILES_LIST}" | |
} | |
# Process script arguments | |
while getopts ":b:i:dhs" opt; do | |
case ${opt} in | |
h) usage;; | |
s) SELF_SIGNED=1;; | |
i) IDENTIFIER="${OPTARG}";; | |
d) DEBUG_MODE=1;; | |
b) BUILD_DIR="${OPTARG}";; | |
\?) log_error "Invalid option -${OPTARG}"; usage;; | |
esac | |
done | |
shift $((OPTIND -1)) | |
# Set certificate subject information from positional arguments | |
CERT_COUNTRY="${1:-$CERT_COUNTRY}" | |
CERT_STATE="${2:-$CERT_STATE}" | |
CERT_LOCALITY="${3:-$CERT_LOCALITY}" | |
CERT_ORG="${4:-$CERT_ORG}" | |
CERT_OU="${5:-$CERT_OU}" | |
CERT_CN="${6:-$CERT_CN}" | |
validate_cert_info | |
make_build_dir | |
create_sp_key | |
if [[ ${SELF_SIGNED} -eq 1 ]]; then | |
create_self_signed_certs | |
else | |
create_csr | |
fi |