Skip to content

Instantly share code, notes, and snippets.

@mattwillsher
Last active August 31, 2025 16:42
Show Gist options
  • Save mattwillsher/d5bb138d26f67eb54e1cf1b7613f696f to your computer and use it in GitHub Desktop.
Save mattwillsher/d5bb138d26f67eb54e1cf1b7613f696f to your computer and use it in GitHub Desktop.
Generate CA cert/key and server cert/key for localhost use.
#!/usr/bin/env bash
set -e
OUTPUT_DIR=${OUTPUT_DIR:-"out"}
SUBDOMAIN=${1:-"my"}
CA_CERT="certs/ca.cert.pem"
CA_KEY="private/ca.key.pem"
LOCALHOST_CERT="certs/$SUBDOMAIN.localhost.cert.pem"
LOCALHOST_KEY="private/$SUBDOMAIN.localhost.key.pem"
OPENSSL_CNF="openssl.cnf"
mkdir -p "$OUTPUT_DIR" || true
cd "$OUTPUT_DIR"
# === Setup directories ===
mkdir -p certs private newcerts
chmod 700 private
touch index.txt
echo 1000 >serial
# === Write OpenSSL config ===
cat >"$OPENSSL_CNF" <<EOF
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = .
certs = certs
crl_dir = crl
database = index.txt
new_certs_dir = newcerts
certificate = "$CA_CERT"
private_key = "$CA_KEY"
serial = serial
default_md = sha256
# policy = policy_strict
x509_extensions = v3_ca
# [ policy_strict ]
# countryName = match
# stateOrProvinceName = match
# organizationName = match
# commonName = supplied
[ req ]
default_bits = 4096
default_md = sha256
prompt = no
# distinguished_name = req_distinguished_name
x509_extensions = v3_ca
# These don't seem to be needed. Remove in future
# [ req_distinguished_name ]
# C = Country
# ST = State
# O = Organization
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
nameConstraints = critical, permitted;DNS:localhost, permitted;DNS:.localhost, permitted;IP:127.0.0.1/255.255.255.255, permitted;IP:0:0:0:0:0:0:0:1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "Localhost Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = $SUBDOMAIN.localhost
DNS.2 = *.$SUBDOMAIN.localhost
IP.1 = 127.0.0.1
IP.2 = ::1
EOF
function generate_root_ca {
if [ -f "$CA_CERT" ] && [ -f "$CA_KEY" ]; then
echo "Root CA already exists, skipping generation."
return
fi
openssl req -config "$OPENSSL_CNF" \
-new -x509 -days 366 -sha256 \
-extensions v3_ca \
-subj "/CN=My localhost CA" \
-nodes \
-keyout "$CA_KEY" \
-out "$CA_CERT"
chmod 400 "$CA_KEY"
chmod 444 "$CA_CERT"
}
function generate_server_cert {
if [ -f "$LOCALHOST_CERT" ] && [ -f "$LOCALHOST_KEY" ]; then
echo "Localhost cert and key already exist, skipping generation."
return
fi
echo "Generating localhost cert and key for $SUBDOMAIN.localhost."
openssl genrsa -out "$LOCALHOST_KEY" 2048
chmod 400 "$LOCALHOST_KEY"
openssl req -new -sha256 \
-key "$LOCALHOST_KEY" \
-subj "/CN=$SUBDOMAIN.localhost" \
-nodes \
-out localhost.csr.pem
openssl x509 -req -days 365 -sha256 \
-in localhost.csr.pem \
-CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial \
-extfile "$OPENSSL_CNF" -extensions server_cert \
-out "$LOCALHOST_CERT"
chmod 444 "$LOCALHOST_CERT"
rm localhost.csr.pem
}
generate_root_ca
generate_server_cert
cat <<'EOF'
# PowerShell commands to manage the certificates, needs administrative privileges
# To add the cert
Import-Certificate -FilePath \"$OUTPUT_DIR/$CA_CERT\" -CertStoreLocation \"Cert:\LocalMachine\My\"
# To remove the certificate
$cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -like "*CN=My localhost CA*" }
if ($cert) {
Remove-Item -Path $cert.PSPath
}
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment