Skip to content

Instantly share code, notes, and snippets.

@jdeathe
Last active July 29, 2023 21:30
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jdeathe/7f7bb957a4e8e0304f0df070f3cbcbee to your computer and use it in GitHub Desktop.
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.
#!/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; \
}"
@thedude42
Copy link

thedude42 commented Dec 29, 2018

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 message error 18 at 0 depth lookup:self signed certificate for the "intermediate" CA.

The fix is:

  • remove the -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)
  • add the -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:

X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0

and above here is the subject and validity:

 Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=--, ST=STATE, L=LOCALITY, O=ORGANISATION, OU=LOCAL, CN=(LOCAL) ROOT CA
        Validity
            Not Before: Dec 29 16:33:00 2018 GMT
            Not After : Dec 28 16:33:00 2023 GMT
        Subject: C=--, ST=STATE, L=LOCALITY, O=ORGANISATION, OU=LOCAL, CN=(LOCAL) CA

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 separate cfssl sign operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment