From: Mark Wooding Date: Sun, 10 Jul 2011 22:11:40 +0000 (+0100) Subject: Minimal X.509 certificate authority. X-Git-Url: https://git.distorted.org.uk/~mdw/ca/commitdiff_plain/b294f6b5e028de310c672c45b565075a1d0b6ca9 Minimal X.509 certificate authority. --- b294f6b5e028de310c672c45b565075a1d0b6ca9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7e6d72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +ca.cert +certs +index +private +state +tmp diff --git a/bin/issue-crl b/bin/issue-crl new file mode 100755 index 0000000..600e22a --- /dev/null +++ b/bin/issue-crl @@ -0,0 +1,17 @@ +#! /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 diff --git a/bin/make-ca-key b/bin/make-ca-key new file mode 100755 index 0000000..f563bd9 --- /dev/null +++ b/bin/make-ca-key @@ -0,0 +1,53 @@ +#! /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;}' <&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 diff --git a/bin/refresh b/bin/refresh new file mode 100755 index 0000000..22c444d --- /dev/null +++ b/bin/refresh @@ -0,0 +1,23 @@ +#! /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 diff --git a/lib/func.sh b/lib/func.sh new file mode 100644 index 0000000..3cfd55e --- /dev/null +++ b/lib/func.sh @@ -0,0 +1,58 @@ +### -*-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 +} diff --git a/openssl.conf b/openssl.conf new file mode 100644 index 0000000..4ff681e --- /dev/null +++ b/openssl.conf @@ -0,0 +1,114 @@ +### -*-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 --------------------------------------------------