Last active
December 17, 2024 19:48
-
-
Save jdeathe/7f7bb957a4e8e0304f0df070f3cbcbee to your computer and use it in GitHub Desktop.
Generate a Root CA + Intermediate CA for local (internal) use on Mac OSX using cfssl and add the intermediate certificate to your keychain so it can be trusted by your local browser.
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 | |
# REF: https://github.com/cloudflare/cfssl | |
# Change working directory | |
cd -- "$( | |
dirname "${0}" | |
)" || exit 1 | |
readonly CA_ROOT_CERT_KEY="ca-root" | |
readonly CA_INTERMEDIATE_CERT_KEY="ca-intermediate" | |
readonly CF_COMMANDS=" | |
cfssl | |
cfssljson | |
" | |
readonly SERVER_CERT_KEY="localhost" | |
HOSTNAMES | |
REGENERATE="${1:-false}" | |
SERVER_HOST_NAME="${2:-localhost.localdomain}" | |
SERVER_LOCAL_HOST_NAME="${3:-localhost}" | |
SERVER_PUBLIC_HOST_NAMES="${4:-}" | |
for COMMAND in ${CF_COMMANDS}; do | |
if ! command -v ${COMMAND} &> /dev/null; then | |
echo "ERROR: Missing command ${COMMAND}" >&2 | |
echo "Install the package from: https://pkg.cfssl.org/" >&2 | |
exit 1 | |
fi | |
done | |
tee ca-config.json 1> /dev/null <<-CONFIG | |
{ | |
"signing": { | |
"default": { | |
"expiry": "8760h" | |
}, | |
"profiles": { | |
"server": { | |
"expiry": "43800h", | |
"usages": [ | |
"signing", | |
"key encipherment", | |
"server auth" | |
] | |
}, | |
"client": { | |
"expiry": "43800h", | |
"usages": [ | |
"signing", | |
"key encipherment", | |
"client auth" | |
] | |
}, | |
"client-server": { | |
"expiry": "43800h", | |
"usages": [ | |
"signing", | |
"key encipherment", | |
"server auth", | |
"client auth" | |
] | |
} | |
} | |
} | |
} | |
CONFIG | |
tee ca-root-to-intermediate-config.json 1> /dev/null <<-CONFIG | |
{ | |
"signing": { | |
"default": { | |
"expiry": "43800h", | |
"ca_constraint": { | |
"is_ca": true, | |
"max_path_len": 0, | |
"max_path_len_zero": true | |
}, | |
"usages": [ | |
"digital signature", | |
"cert sign", | |
"crl sign", | |
"signing" | |
] | |
} | |
} | |
} | |
CONFIG | |
if [[ ! -f ${CA_ROOT_CERT_KEY}-key.pem ]]; then | |
cfssl genkey \ | |
-initca \ | |
- \ | |
<<-CONFIG | cfssljson -bare ${CA_ROOT_CERT_KEY} | |
{ | |
"CN": "(LOCAL) ROOT CA", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "--", | |
"ST": "STATE", | |
"L": "LOCALITY", | |
"O": "ORGANISATION", | |
"OU": "LOCAL" | |
} | |
], | |
"ca": { | |
"expiry": "131400h" | |
} | |
} | |
CONFIG | |
else | |
echo "${CA_ROOT_CERT_KEY}-key.pem already generated." | |
fi | |
if [[ ! -f ${CA_INTERMEDIATE_CERT_KEY}-key.pem ]]; then | |
cfssl gencert \ | |
-initca \ | |
- \ | |
<<-CONFIG | cfssljson -bare ${CA_INTERMEDIATE_CERT_KEY} | |
{ | |
"CN": "(LOCAL) CA", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
}, | |
"names": [ | |
{ | |
"C": "--", | |
"ST": "STATE", | |
"L": "LOCALITY", | |
"O": "ORGANISATION", | |
"OU": "LOCAL" | |
} | |
], | |
"ca": { | |
"expiry": "43800h" | |
} | |
} | |
CONFIG | |
else | |
echo "${CA_INTERMEDIATE_CERT_KEY}-key.pem already generated." | |
fi | |
if [[ ! -f ${CA_INTERMEDIATE_CERT_KEY}.pem ]] \ | |
|| [[ ${REGENERATE} == true ]]; then | |
set -x | |
# Sign intermediate certificate with root certificate | |
cfssl sign \ | |
-ca ${CA_ROOT_CERT_KEY}.pem \ | |
-ca-key ${CA_ROOT_CERT_KEY}-key.pem \ | |
-config ca-root-to-intermediate-config.json \ | |
${CA_INTERMEDIATE_CERT_KEY}.csr \ | |
| cfssljson -bare ${CA_INTERMEDIATE_CERT_KEY} | |
if [[ -f ${HOME}/Library/Keychains/login.keychain ]]; then | |
echo "Adding intermediate certificate to keychain" | |
echo "You will need to manually set to 'Always Trust' using Keychain Access." | |
sudo security \ | |
add-trusted-cert \ | |
-r trustRoot \ | |
-k "${HOME}/Library/Keychains/login.keychain" \ | |
"${CA_INTERMEDIATE_CERT_KEY}.pem" | |
fi | |
set +x | |
fi | |
echo -e "\nDistribute the intermediate certificate: ${CA_INTERMEDIATE_CERT_KEY}.pem" | |
cat ${CA_INTERMEDIATE_CERT_KEY}.pem | |
## Server certificate | |
cfssl gencert \ | |
-ca ${CA_INTERMEDIATE_CERT_KEY}.pem \ | |
-ca-key ${CA_INTERMEDIATE_CERT_KEY}-key.pem \ | |
-config ca-config.json \ | |
-profile server \ | |
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \ | |
- \ | |
<<-CONFIG | cfssljson -bare ${SERVER_CERT_KEY} | |
{ | |
"CN": "${SERVER_HOST_NAME}", | |
"key": { | |
"algo": "rsa", | |
"size": 2048 | |
} | |
} | |
CONFIG | |
echo -e "\nserver private key:" | |
cat ${SERVER_CERT_KEY}-key.pem | |
echo -e "\nserver certificate:" | |
cat ${SERVER_CERT_KEY}.pem | |
rm ca-config.json | |
rm ca-root-to-intermediate-config.json | |
echo -e "\nUpdating Apache Configuration: /etc/pki/tls/certs/${SERVER_CERT_KEY}.crt" | |
sudo bash -c "{ \ | |
mkdir -p /etc/pki/tls/certs; \ | |
cat ${SERVER_CERT_KEY}-key.pem \ | |
${SERVER_CERT_KEY}.pem \ | |
${CA_INTERMEDIATE_CERT_KEY}.pem \ | |
> /etc/pki/tls/certs/${SERVER_CERT_KEY}.crt; \ | |
}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If you stumble upon this gist while searching for resources on cfssl, there are some issues with the above, namely that the script does not actually produce a cert chain but rather two independently self-signed "root" CAs. If you use
openssl verify ...
to validate your end-host cert against the intermediate CA you will find it validates fine without the need to include the-untrusted
flag for including the bundle. Also you should notice the intermediate cert does not verify against the root, and you receive a messageerror 18 at 0 depth lookup:self signed certificate
for the "intermediate" CA.The fix is:
-initca
in line 121 and also remove the JSON property "ca" from lines 139-141 (don't forget to remove the trailing comma in line 138)-ca
and-ca-key
options around line 121 along with-config ca-root-to-intermediate-config.json
The result of making these changes will be to produce a signed intermediate CA signed by the root. Using my above fix here's what the cert looks like for key usage and constraints:
and above here is the subject and validity:
Lastly, the conditional block at line 148 may be a no-op with the above changes unless you manually delete your .pem produced from an earlier run, since the changes I prescribe will result in an intermediate CA certificate signed by the explicitly expressed CA (from the
-ca
option) without the need to perform a separatecfssl sign
operation.