--- /dev/null
+ca.cert
+certs
+index
+private
+state
+tmp
--- /dev/null
+#! /bin/sh
+
+set -e
+certroot=$(cd ${0%/*}/..; pwd)
+cd "$certroot"
+. lib/func.sh
+runas_ca
+
+now=$(date +%Y-%m-%d)
+n=0
+while t=$now#$n.crl; [ -f crls/$t ]; do
+ n=$(expr $n + 1)
+done
+openssl ca -config openssl.conf -gencrl -out crls/$t
+rm -f crls/new
+ln -s $t crls/new
+mv crls/new crls/current
--- /dev/null
+#! /bin/sh
+
+set -e
+certroot=$(cd ${0%/*}/..; pwd)
+cd "$certroot"
+umask 022
+
+## Archive any existing CA.
+if [ -f ca.cert ]; then
+ mkdir -p archive
+ if [ -f archive/state/serial ]; then
+ next=$(cat archive/state/serial)
+ else
+ mkdir -p archive/state
+ next=1
+ fi
+ mkdir archive/"$next"
+ mv ca.cert certs crls index private state archive/"$next"/
+ expr "$next" + 1 >archive/state/serial.new
+ mv archive/state/serial.new archive/state/serial
+fi
+
+## Clear out the old CA completely.
+rm -rf certs index private tmp state
+rm -f ca.cert distorted.crl
+
+## Build a new one.
+mkdir -m750 private
+mkdir -m775 certs crls index index/byhash index/byserial state tmp
+chown root:ca certs crls index index/byhash index/byserial private state tmp
+touch state/db
+echo 01 >state/serial
+echo 01 >state/crlnumber
+
+## Set the CA subject name. It won't fit on one line, and there's no
+## good way of continuing it. Have fun parsing the sed.
+subject=$(sed -n 's:^:/:;1h;2,$H;${x;s/\n//g;p;}' <<EOF
+C=GB
+ST=Cambridgeshire
+O=distorted.org.uk
+OU=Certificate Authority
+CN=distorted.org.uk top-level CA
+emailAddress=ca@distorted.org.uk
+EOF
+)
+
+## Build the new CA key and certificate.
+umask 027
+openssl req -new -config openssl.conf -x509 -days 3650 \
+ -out ca.cert -keyout private/ca.key \
+ -subj "$subject"
+chown root:ca private/ca.key
+chmod 644 ca.cert
--- /dev/null
+#! /bin/sh
+
+set -e
+certroot=$(cd ${0%/*}/..; pwd)
+. "$certroot"/lib/func.sh
+runas_ca "$@"
+
+## Parse the command line.
+case "$#" in
+ 3) ;;
+ *) echo >&2 "Usage: $0 TAG PROFILE FILE"; exit 1 ;;
+esac
+tag=$1 profile=$2 file=$3
+
+## Make sure we're not overwriting anything. Put sequence numbers
+## into labels to prevent bad things from happening.
+if [ -f "$certroot"/certs/"$tag".cert ]; then
+ echo >&2 "$0: certificate $tag already exists"
+ exit 1
+fi
+
+## Make a temporary copy of the certificate. This prevents a race, and
+## more importantly lets us change directory.
+cp "$file" "$certroot"/tmp/"$tag".req
+cd "$certroot"
+
+## Make the certificate.
+openssl ca -config openssl.conf -extensions $profile-extensions \
+ -in tmp/"$tag".req -out tmp/"$tag".cert
+
+## Install a hash link the benefit of OpenSSL's `verify' command and
+## similar, and install the completed request and certificate in the
+## archive.
+mv tmp/"$tag".req tmp/"$tag".cert certs/
+linkserial certs/"$tag".cert
+linkhash certs/"$tag".cert
+rm tmp/*.pem
+
+## Output the certificate.
+openssl x509 -in certs/"$tag".cert
--- /dev/null
+#! /bin/sh
+
+set -e
+certroot=$(cd ${0%/*}/..; pwd)
+cd "$certroot"
+. lib/func.sh
+runas_ca
+
+badness=0
+indices="byhash byserial"
+for i in $indices; do rm -rf index/$i; done
+for i in $indices; do mkdir index/$i.new; done
+
+for i in certs/*.cert; do
+ linkserial "$i" .new
+ linkhash "$i" .new
+done
+
+for i in $indices; do
+ if [ -d index/$i ]; then mv index/$i index/$i.old; fi;
+done
+for i in $indices; do mv index/$i.new index/$i; done
+for i in $indices; do rm -rf index/$i.old; done
--- /dev/null
+### -*-sh-*-
+
+runas_ca () {
+ ## runas_ca
+ ##
+ ## Make sure we're running as the CA user. I don't trust ASN.1 parsers
+ ## to run as root against untrusted input -- especially OpenSSL's one.
+
+ case $(id -un) in
+ ca) ;;
+ *) exec sudo -u ca "$0" "$@" ;;
+ esac
+}
+
+linkserial () {
+ ## linkserial CERT [SERIAL]
+ ##
+ ## Make a link for the certificate according to its serial number.
+
+ cert=$1 suffix=$2
+ serial=$(openssl x509 -serial -noout -in "$cert")
+ serial=${serial##*=}
+ t=index/byserial$suffix/$serial.pem
+ if [ -L "$t" ]; then
+ other=$(readlink "$t")
+ echo "Duplicate serial numbers: ${other##*/}, ${cert##*/}"
+ badness=1
+ return
+ fi
+ lns "$cert" "$t"
+}
+
+linkhash () {
+ ## linkhash CERT [SUFFIX]
+ ##
+ ## Make links for the certificate according to its hash.
+
+ cert=$1 suffix=$2
+ fpr=$(openssl x509 -fingerprint -noout -in "$cert")
+ for opt in subject_hash subject_hash_old; do
+ n=0
+ hash=$(openssl x509 -$opt -noout -in "$cert")
+ while t=index/byhash$suffix/$hash.$n; [ -L "$t" ]; do
+ ofpr=$(openssl x509 -fingerprint -noout -in "$t")
+ other=$(readlink "$t")
+ case "${cert##*/}" in "${other##*/}") continue ;; esac
+ case "$ofpr" in
+ "$fpr")
+ echo "Duplicate certificates: ${other##*/}, ${cert##*/}"
+ badness=1
+ return
+ ;;
+ esac
+ n=$(expr $n + 1)
+ done
+ lns "$cert" "$t"
+ done
+}
--- /dev/null
+### -*-conf-*-
+###
+### OpenSSL configuration for distorted.org.uk CA.
+
+###--------------------------------------------------------------------------
+### Defaults.
+
+RANDFILE = /dev/urandom
+
+###--------------------------------------------------------------------------
+### Certificate request configuration.
+
+[req]
+default_bits = 3072
+encrypt_key = no
+default_md = sha1
+utf8 = yes
+x509_extensions = ca-extensions
+distinguished_name = req-dn
+prompt = yes
+
+[req-dn]
+
+countryName = "Country name"
+countryName_default = "GB"
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = "State, province, or county"
+stateOrProvinceName_default = "Cambridgeshire"
+stateOrProvinceName_max = 64
+
+localityName = "Locality (e.g., city)"
+localityName_default = "Cambridge"
+localityName_max = 64
+
+organizationName = "Organization"
+organizationName_default = "distorted.org.uk"
+organizationName_max = 64
+organizationalUnitName = "Organizational unit"
+organizationalUnitName_max = 64
+
+commonName = "Common name"
+commonName_max = 64
+
+emailAddress = "Email address"
+emailAddress_max = 64
+
+###--------------------------------------------------------------------------
+### CA configuration.
+
+[ca]
+default_ca = distorted-ca
+preserve = yes
+
+[distorted-ca]
+default_days = 1825
+default_md = sha1
+unique_subject = no
+email_in_dn = no
+private_key = private/ca.key
+certificate = ca.cert
+database = state/db
+serial = state/serial
+crlnumber = state/crlnumber
+default_crl_days = 7
+new_certs_dir = tmp
+x509_extensions = tls-server-extensions
+crl_extensions = crl-extensions
+policy = distorted-policy
+name_opt = sep_multiline, esc_ctrl, utf8, dump_nostr, dump_unknown, space_eq, lname, align
+cert_opt = no_header, ext_parse, no_pubkey
+copy_extensions = copy
+
+[distorted-policy]
+countryName = supplied
+stateOrProvinceName = optional
+localityName = optional
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+[crl-extensions]
+issuerAltName = email:ca@distorted.org.uk
+crlDistributionPoints=URI:http://www.distorted.org.uk/ca/distorted.crl
+
+[ca-extensions]
+basicConstraints = critical, CA:TRUE
+keyUsage = critical, keyCertSign
+subjectKeyIdentifier = hash
+subjectAltName = email:ca@distorted.org.uk
+crlDistributionPoints=URI:http://www.distorted.org.uk/ca/distorted.crl
+
+[tls-server-extensions]
+basicConstraints = critical, CA:FALSE
+keyUsage = critical, digitalSignature, keyEncipherment
+extendedKeyUsage = serverAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always, issuer:always
+issuerAltName = issuer:copy
+crlDistributionPoints=URI:http://www.distorted.org.uk/ca/distorted.crl
+
+[tls-client-extensions]
+basicConstraints = critical, CA:FALSE
+keyUsage = critical, digitalSignature
+extendedKeyUsage = clientAuth
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+issuerAltName = issuer:copy
+subjectAltName = email:copy
+crlDistributionPoints=URI:http://www.distorted.org.uk/ca/distorted.crl
+
+###----- That's all, folks --------------------------------------------------