commit 12df907bebff4029abf2bacd32ac0cb6924f44d1
Author: MikoĊaj Lenczewski <mikolaj@lenczewski.org>
Date: Tue, 5 May 2026 01:06:56 +0100
Initial commit
Diffstat:
14 files changed, 513 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+ca/
+certs/
+
+**/.*.swp
diff --git a/README b/README
@@ -0,0 +1,7 @@
+ca
+==============================================================================
+A set of shell scripts for generating a small self-signed certificate authority,
+and generating signed certificates for use with email and other services.
+
+Taken from the excellent website:
+ https://jamielinux.com/docs/openssl-certificate-authority/index.html
diff --git a/ca.country.txt b/ca.country.txt
@@ -0,0 +1 @@
+PLACEHOLDER
diff --git a/ca.organization.txt b/ca.organization.txt
@@ -0,0 +1 @@
+PLACEHOLDER
diff --git a/ca.state.txt b/ca.state.txt
@@ -0,0 +1 @@
+PLACEHOLDER
diff --git a/crl.uri.txt b/crl.uri.txt
@@ -0,0 +1 @@
+PLACEHOLDER
diff --git a/frag.cnf b/frag.cnf
@@ -0,0 +1,66 @@
+# strict policy for signing intermediate certificates
+# see policy format for `man ca`
+[ CA_policy_strict ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# loose policy for signing generic certificates (signed by intermediate certs)
+# see policy format for `man ca`
+[ CA_policy_loose ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# extensions for the root ca certificate (`man x509v3_config`)
+[ v3_ca ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+# extensions for intermediate ca certificates (`man x509v3_config`)
+[ v3_intermediate_ca ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer
+basicConstraints = critical, CA:true, pathlen:0
+keyUsage = critical, digitalSignature, cRLSign, keyCertSign
+
+# extensions for client certificates (`man x509v3_config`)
+[ client_cert ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+basicConstraints = CA:false
+keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = clientAuth, emailProtection
+nsCertType = client, email
+nsComment = "Generated Client Certificate"
+
+# extensions for server certificates (`man x509v3_config`)
+[ server_cert ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer:always
+basicConstraints = CA:false
+keyUsage = critical, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+nsCertType = server
+nsComment = "Generated Server Certificate"
+
+# extensions for the revocation list (`man x509v3_config`)
+[ crl_ext ]
+authorityKeyIdentifier = keyid:always
+
+# extensions for OSCP signing certificates (`man ocsp`)
+[ ocsp ]
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+basicConstraints = CA:false
+keyUsage = critical, digitalSignature
+extendedKeyUsage = critical, OCSPSigning
diff --git a/init-ca.sh b/init-ca.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+set -ex
+
+# initial directory structure
+
+mkdir -p ca \
+ ca/cert \
+ ca/crl \
+ ca/new \
+ ca/priv
+
+chmod 700 ca/priv
+
+touch ca/index.txt
+touch ca/serial
+
+SERIAL=$(date '+%Y%m%d')
+echo $SERIAL > ca/serial
+
+echo 1000 > ca/crlnumber
+
+# create openssl ca config
+
+C=$(cat ca.country.txt)
+ST=$(cat ca.state.txt)
+O=$(cat ca.organization.txt)
+
+[ "${C:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.country.txt!" && exit 1
+
+[ "${ST:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.state.txt!" && exit 1
+
+[ "${O:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.organization.txt!" && exit 1
+
+CRL_URI="$(cat crl.uri.txt)"
+
+[ "${CRL_URI:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in crl.uri.txt!" && exit 1
+
+cat >ca/openssl.cnf <<EOF
+# root ca configuration
+
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+dir = $(dirname $0)/ca
+certs = \$dir/cert
+crl_dir = \$dir/crl
+new_certs_dir = \$dir/new
+database = \$dir/index.txt
+serial = \$dir/serial
+RANDFILE = \$dir/priv/.rand
+
+# root key and certificate
+private_key = \$dir/priv/ca.key.pem
+certificate = \$dir/cert/ca.crt.pem
+
+# certificate revocation list config
+crlnumber = \$dir/crlnumber
+crl = \$dir/crl/ca.crl.pem
+crl_extensions = crl_ext
+default_crl_days = 31
+
+# use a strong digest
+default_md = sha512
+
+# misc config
+name_opt = ca_default
+cert_opt = ca_default
+default_days = 365
+preserve = no
+policy = CA_policy_strict
+
+# default fields when making certificate requests
+[ req ]
+default_bits = 4096
+
+string_mask = utf8only
+
+default_md = sha512
+
+x509_extensions = v3_ca
+
+[ v3_intermediate_ca ]
+crlDistributionPoints = URI:http://${CRL_URI}
+
+EOF
+
+cat >>ca/openssl.cnf < frag.cnf
+
+# create the root certificate
+
+# generate the key
+openssl genrsa -aes256 -out ca/priv/ca.key.pem 4096
+chmod 400 ca/priv/ca.key.pem
+
+# generate the certificate
+openssl req -config ca/openssl.cnf \
+ -new -x509 -days 36500 -extensions v3_ca \
+ -subj "/C=$C/ST=$ST/O=$O/CN=$O Root CA" \
+ -key ca/priv/ca.key.pem \
+ -out ca/cert/ca.crt.pem
+
+chmod 444 ca/cert/ca.crt.pem
+
+# verify the certificate
+openssl x509 -noout -text -in ca/cert/ca.crt.pem
+
+echo "NOTE: Install the root certificate on all devices"
+echo "NOTE: Root certificate is found in 'ca/cert/ca.crt.pem'"
+echo "NOTE: Please run 'make-intermediate-ca.sh' at least once"
diff --git a/make-ca-crl.sh b/make-ca-crl.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -ex
+
+# generate the crl
+
+openssl ca -config ca/openssl.cnf \
+ -gencrl -out ca/intermediate/crl/intermediate.crl.pem
diff --git a/make-cert.sh b/make-cert.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+TYPE="$1"
+DOMAIN="$2"
+shift 2
+
+EXTRA="$(echo $@ | xargs printf '%s\n' | paste -s -d '/' -)"
+
+PCKSPASS="${PCKSPASS:-password}"
+DAYS="${DAYS:-730}"
+
+case "$TYPE" in
+ client|server)
+ ;;
+
+ *)
+ echo "Usage: $0 <server|client> <fqdn> <x509-subj-extra>..."
+ exit 1
+ ;;
+esac
+
+if [ -z "$DOMAIN" ]; then
+ echo "Usage: $0 <server|client> <fqdn> <x509-subj-extra>..."
+ exit 1
+fi
+
+set -ex
+
+mkdir -p certs certs/$TYPE "certs/$TYPE/$DOMAIN"
+
+# generate the key
+openssl genrsa \
+ -out "certs/$TYPE/$DOMAIN/$DOMAIN.key.pem" 4096
+
+chmod 600 "certs/$TYPE/$DOMAIN/$DOMAIN.key.pem"
+
+# generate the certificate
+
+C=$(cat ca.country.txt)
+ST=$(cat ca.state.txt)
+O=$(cat ca.organization.txt)
+
+[ "${C:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.country.txt!" && exit 1
+
+[ "${ST:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.state.txt!" && exit 1
+
+[ "${O:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.organization.txt!" && exit 1
+
+openssl req -config ca/intermediate/current/openssl_usr.cnf \
+ -key "certs/$TYPE/$DOMAIN/$DOMAIN.key.pem" \
+ -new -subj "/C=$C/ST=$ST/O=$O/CN=${DOMAIN}/${EXTRA}" \
+ -out "certs/$TYPE/$DOMAIN/$DOMAIN.csr.pem"
+
+openssl ca -config ca/intermediate/current/openssl_usr.cnf \
+ -days $DAYS -notext -extensions ${TYPE}_cert \
+ -in "certs/$TYPE/$DOMAIN/$DOMAIN.csr.pem" \
+ -out "certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem"
+
+chmod 644 "certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem"
+
+# verify the certificate
+openssl x509 -noout -text -in "certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem"
+openssl verify -CAfile ca/intermediate/current/cert/ca-chain.pem \
+ "certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem"
+
+# publish certificate
+echo "NOTE: Certificate key is found in 'certs/$TYPE/$DOMAIN/$DOMAIN.key.pem'"
+echo "NOTE: Certificate is found in 'certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem'"
diff --git a/make-intermediate-ca.sh b/make-intermediate-ca.sh
@@ -0,0 +1,208 @@
+#!/bin/sh
+
+DATE="$(date '+%Y-%m-%d')"
+SERIAL=$(date '+%Y%m%d')
+
+[ -d ca/intermediate/$DATE ] && \
+ echo "Warning: Intermediate CA for serial $SERIAL already exists: ca/intermediate/$DATE" && exit 1
+
+INTERMEDIATE_CA_ROOT=ca/intermediate/$DATE
+
+set -ex
+
+# initial directory structure
+
+mkdir -p $INTERMEDIATE_CA_ROOT \
+ $INTERMEDIATE_CA_ROOT/cert \
+ $INTERMEDIATE_CA_ROOT/csr \
+ $INTERMEDIATE_CA_ROOT/new \
+ $INTERMEDIATE_CA_ROOT/priv \
+ ca/intermediate/crl
+
+chmod 700 $INTERMEDIATE_CA_ROOT/priv
+
+touch $INTERMEDIATE_CA_ROOT/index.txt $INTERMEDIATE_CA_ROOT/serial
+
+echo $SERIAL > $INTERMEDIATE_CA_ROOT/serial
+echo 1000 > $INTERMEDIATE_CA_ROOT/crlnumber
+
+# create openssl ca config
+
+C=$(cat ca.country.txt)
+ST=$(cat ca.state.txt)
+O=$(cat ca.organization.txt)
+
+[ "${C:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.country.txt!" && exit 1
+
+[ "${ST:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.state.txt!" && exit 1
+
+[ "${O:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ca.organization.txt!" && exit 1
+
+OCSP_URI="$(cat ocsp.uri.txt)"
+
+[ "${OCSP_URI:-PLACEHOLDER}" = "PLACEHOLDER" ] && \
+ echo "Please fill in ocsp.uri.txt!" && exit 1
+
+cat >$INTERMEDIATE_CA_ROOT/openssl.cnf <<EOF
+# intermediate ca configuration
+
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+dir = $(dirname $0)/ca/intermediate/$DATE
+certs = \$dir/cert
+crl_dir = \$dir/crl
+new_certs_dir = \$dir/new
+database = \$dir/index.txt
+serial = \$dir/serial
+RANDFILE = \$dir/priv/.rand
+
+# root key and certificate
+private_key = \$dir/priv/intermediate.key.pem
+certificate = \$dir/cert/intermediate.crt.pem
+
+# certificate revocation list config
+crlnumber = \$dir/crlnumber
+crl = \$dir/crl/intermediate.crl.pem
+crl_extensions = crl_ext
+default_crl_days = 30
+
+# use a strong digest
+default_md = sha512
+
+# misc config
+name_opt = ca_default
+cert_opt = ca_default
+default_days = 365
+preserve = no
+policy = CA_policy_loose
+
+# default fields when making certificate requests
+[ req ]
+default_bits = 4096
+
+string_mask = utf8only
+
+default_md = sha512
+
+x509_extensions = v3_ca
+
+EOF
+
+cat >>$INTERMEDIATE_CA_ROOT/openssl.cnf < frag.cnf
+
+cat >$INTERMEDIATE_CA_ROOT/openssl_usr.cnf <<EOF
+# server and client certificate configuration
+
+[ ca ]
+default_ca = CA_default
+
+[ CA_default ]
+dir = $(dirname $0)/ca/intermediate/$DATE
+certs = \$dir/cert
+crl_dir = \$dir/crl
+new_certs_dir = \$dir/new
+database = \$dir/index.txt
+serial = \$dir/serial
+RANDFILE = \$dir/priv/.rand
+
+# root key and certificate
+private_key = \$dir/priv/intermediate.key.pem
+certificate = \$dir/cert/intermediate.crt.pem
+
+# certificate revocation list config
+crlnumber = \$dir/crlnumber
+crl = \$dir/crl/intermediate.crl.pem
+crl_extensions = crl_ext
+default_crl_days = 30
+
+# use a strong digest
+default_md = sha512
+
+# misc config
+name_opt = ca_default
+cert_opt = ca_default
+default_days = 365
+preserve = no
+policy = CA_policy_loose
+
+# default fields when making certificate requests
+[ req ]
+default_bits = 4096
+
+string_mask = utf8only
+
+default_md = sha512
+
+x509_extensions = v3_ca
+
+[ client_cert ]
+authorityInfoAccess = OCSP;URI:http://${OCSP_URI}
+
+[ server_cert ]
+authorityInfoAccess = OCSP;URI:http://${OCSP_URI}
+
+EOF
+
+cat >>$INTERMEDIATE_CA_ROOT/openssl_usr.cnf < frag.cnf
+
+# create the intermediate pair
+
+# generate the key
+openssl genrsa -aes256 -out $INTERMEDIATE_CA_ROOT/priv/intermediate.key.pem 4096
+chmod 400 $INTERMEDIATE_CA_ROOT/priv/intermediate.key.pem
+
+# generate the certificate
+openssl req -config $INTERMEDIATE_CA_ROOT/openssl.cnf \
+ -new -subj "/C=$C/ST=$ST/O=$O/CN=$O Intermediate CA" \
+ -key $INTERMEDIATE_CA_ROOT/priv/intermediate.key.pem \
+ -out $INTERMEDIATE_CA_ROOT/csr/intermediate.csr.pem
+
+openssl ca -config ca/openssl.cnf \
+ -days 3650 -notext -extensions v3_intermediate_ca \
+ -in $INTERMEDIATE_CA_ROOT/csr/intermediate.csr.pem \
+ -out $INTERMEDIATE_CA_ROOT/cert/intermediate.crt.pem
+
+chmod 444 $INTERMEDIATE_CA_ROOT/cert/intermediate.crt.pem
+
+# verify the certificate
+openssl x509 -noout -text -in $INTERMEDIATE_CA_ROOT/cert/intermediate.crt.pem
+openssl verify -CAfile ca/cert/ca.crt.pem $INTERMEDIATE_CA_ROOT/cert/intermediate.crt.pem
+
+# create the ocsp pair
+
+openssl genrsa -out $INTERMEDIATE_CA_ROOT/priv/$OCSP_URI.ocsp.key.pem 4096
+chmod 400 $INTERMEDIATE_CA_ROOT/priv/$OCSP_URI.ocsp.key.pem
+
+openssl req -config $INTERMEDIATE_CA_ROOT/openssl.cnf \
+ -new -subj="/C=$C/ST=$ST/O=$O/CN=$OCSP_URI" \
+ -key "$INTERMEDIATE_CA_ROOT/priv/$OCSP_URI.ocsp.key.pem" \
+ -out "$INTERMEDIATE_CA_ROOT/csr/$OCSP_URI.ocsp.csr.pem"
+
+openssl ca -config $INTERMEDIATE_CA_ROOT/openssl.cnf \
+ -days 3650 -notext -extensions ocsp \
+ -in "$INTERMEDIATE_CA_ROOT/csr/$OCSP_URI.ocsp.csr.pem" \
+ -out "$INTERMEDIATE_CA_ROOT/cert/$OCSP_URI.ocsp.crt.pem"
+
+openssl x509 -noout -text -in "$INTERMEDIATE_CA_ROOT/cert/$OCSP_URI.ocsp.crt.pem"
+
+# create root ca crl
+
+$(dirname $0)/make-ca-crl.sh
+
+# create chain of trust
+
+cat $INTERMEDIATE_CA_ROOT/cert/intermediate.crt.pem > $INTERMEDIATE_CA_ROOT/cert/ca-chain.pem
+
+# create symlink
+ln -sf $DATE ca/intermediate/current
+
+echo "NOTE: Generated CRL is found in '$INTERMEDIATE_CA_ROOT/crl/intermediate.crl.pem'"
+echo "NOTE: Please run 'make-ca-crl.sh' on a regular basis (once every 30 days) to update it"
+echo "NOTE: OCSP key is found in '$INTERMEDIATE_CA_ROOT/priv/*.ocsp.key.pem'"
+echo "NOTE: OCSP cert is found in '$INTERMEDIATE_CA_ROOT/cert/*.ocsp.crt.pem'"
+echo "NOTE: Chain of Trust is found in '$INTERMEDIATE_CA_ROOT/cert/ca-chain.pem'"
diff --git a/ocsp.uri.txt b/ocsp.uri.txt
@@ -0,0 +1 @@
+PLACEHOLDER
diff --git a/revoke-cert.sh b/revoke-cert.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+TYPE="$1"
+DOMAIN="$2"
+
+case "$TYPE" in
+ client|server)
+ ;;
+
+ *)
+ echo "Usage: $0 <server|client> <fqdn>"
+ ;;
+esac
+
+if [ -z "$DOMAIN" ]; then
+ echo "Usage: $0 <server|client> <fqdn>"
+ exit 1
+fi
+
+openssl ca -config ca/intermediate/current/openssl_usr.cnf \
+ -revoke "certs/$TYPE/$DOMAIN/$DOMAIN.crt.pem"
diff --git a/revoke-intermediate-ca.sh b/revoke-intermediate-ca.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+
+openssl ca -config ca/openssl.cnf \
+ -revoke $1
+
+$(dirname $0)/make-ca-crl.sh