From 3c5f739b6d6d86ecb86cf88834b95a8929771c8f Mon Sep 17 00:00:00 2001 From: Erich Eckner Date: Thu, 29 Aug 2019 11:13:36 +0200 Subject: ca stuff done --- .gitignore | 5 +- Makefile | 15 +-- ca.conf | 9 ++ generate-and-upload-self-signed-keys.conf | 8 -- generate-and-upload-self-signed-keys.in | 125 ------------------------ generate-and-upload-self-signed-keys.service.in | 6 -- generate-and-upload-self-signed-keys.timer | 11 --- sign-ca | 12 --- sign-ca.in | 63 ++++++++++++ sign-ca.service.in | 6 ++ sign-ca.timer | 11 +++ sign-request | 90 ----------------- sign-request.in | 103 +++++++++++++++++++ 13 files changed, 203 insertions(+), 261 deletions(-) create mode 100644 ca.conf delete mode 100644 generate-and-upload-self-signed-keys.conf delete mode 100755 generate-and-upload-self-signed-keys.in delete mode 100644 generate-and-upload-self-signed-keys.service.in delete mode 100644 generate-and-upload-self-signed-keys.timer delete mode 100755 sign-ca create mode 100755 sign-ca.in create mode 100644 sign-ca.service.in create mode 100644 sign-ca.timer delete mode 100755 sign-request create mode 100755 sign-request.in diff --git a/.gitignore b/.gitignore index 801b64b..628da3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -generate-and-upload-self-signed-keys -generate-and-upload-self-signed-keys.service +sign-ca +sign-ca.service +sign-request diff --git a/Makefile b/Makefile index 85391b3..0aa1847 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # -# generate-and-upload-self-signed-keys - script to handle self signed keys +# simple-pki - scripts to handle a simple pki # # Copyright (c) 2019 Erich Eckner # @@ -27,7 +27,7 @@ MANDIR = /usr/share/man VERSION = 0.2 -all: generate-and-upload-self-signed-keys generate-and-upload-self-signed-keys.service +all: sign-ca sign-request sign-ca.service %: %.in sed " \ @@ -36,14 +36,15 @@ all: generate-and-upload-self-signed-keys generate-and-upload-self-signed-keys.s s@#ETCDIR#@$(ETCDIR)@; \ s@#HELPTEXT#\(\s\+\)#@ --help \1display this help and exit\n --version\1display version and exit@; \ " $< > $@ - [ "$@" = "generate-and-upload-self-signed-keys" ] && chmod +x "$@" || true + [ "$@" = "sign-ca" ] && chmod +x "$@" || true .PHONY: install dist clean -install: - install -D -m0755 generate-and-upload-self-signed-keys $(DESTDIR)$(BINDIR)/generate-and-upload-self-signed-keys - install -D -m0644 generate-and-upload-self-signed-keys.conf $(DESTDIR)$(ETCDIR)/generate-and-upload-self-signed-keys.conf - install -D -m0644 -t $(DESTDIR)$(LIBDIR)/systemd/system generate-and-upload-self-signed-keys.service generate-and-upload-self-signed-keys.timer +install-ca: + install -D -m0755 -t $(DESTDIR)$(BINDIR) sign-ca sign-request + install -D -m0644 -t $(DESTDIR)$(ETCDIR)/simple-pki ca.conf + install -d -m0700 $(DESTDIR)$(ETCDIR)/simple-pki/keys + install -D -m0644 -t $(DESTDIR)$(LIBDIR)/systemd/system sign-ca.service sign-ca.timer clean: ls -A | \ diff --git a/ca.conf b/ca.conf new file mode 100644 index 0000000..ab2eac4 --- /dev/null +++ b/ca.conf @@ -0,0 +1,9 @@ +#!/bin/bash + +# details of the ca +ca_name='eckner-ca' +ca_subject_prefix='/C=DE/ST=Thuringia/L=Jena/O=Eckner/OU=Net' + +# where should the ca certificates be published? +remote_host='user@example.com' +remote_dir='httpdocs/certs' diff --git a/generate-and-upload-self-signed-keys.conf b/generate-and-upload-self-signed-keys.conf deleted file mode 100644 index a83ab66..0000000 --- a/generate-and-upload-self-signed-keys.conf +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# ignore these hosts -ignore_hosts=('localhost') - -# where should the certificates be published? -remote_host='user@example.com' -remote_dir='httpdocs/certs' diff --git a/generate-and-upload-self-signed-keys.in b/generate-and-upload-self-signed-keys.in deleted file mode 100755 index 9215d22..0000000 --- a/generate-and-upload-self-signed-keys.in +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/bash - -if [ -r '#ETCDIR#/generate-and-upload-self-signed-keys.conf' ]; then - . '#ETCDIR#/generate-and-upload-self-signed-keys.conf' -fi - -key_dir='#ETCDIR#/nginx/keys' - -cd / - -hosts=$( - find '#ETCDIR#/nginx/' \ - -name keys -prune , \ - -name sites-available -prune , \ - \( -type f -o -type l \) \ - -exec sed -n ' - s/^\s*// - /^server_name\s.*;/ p - /^server_name[^;]*$/,/;/ p - ' {} \; 2>/dev/null \ - | tr '\n' ' ' \ - | sed ' - s/\s\+/ /g - s/;\s*/;\n/g - '"$( - printf 's/\\s%s\\(;\\|\\s\)//\n' "${ignore_hosts[@]}" - )"' - ' \ - | sed -n ' - s/^server_name // - T - s/;$// - T - p - ' \ - | sort -u -) - -host_key_files=$( - printf '%s\n' "${hosts}" \ - | cut -d' ' -f1 -) - -if [ "$(whoami)" = 'root' ]; then - updated_something=false - for host_key_file in ${host_key_files}; do - if [ -f "${key_dir}/${host_key_file}.key.pem.new" ] \ - && [ -f "${key_dir}/${host_key_file}.cert.pem.new" ]; then - if [ "$(stat -c%Y "${key_dir}/${host_key_file}.key.pem.new")" -ge "$(($(date +%s)-60*60*24*30))" ] \ - && [ -f "${key_dir}/${host_key_file}.key.pem" ] \ - && [ "$(stat -c%Y "${key_dir}/${host_key_file}.cert.pem.new")" -ge "$(($(date +%s)-60*60*24*30))" ] \ - && [ -f "${key_dir}/${host_key_file}.cert.pem" ]; then - continue - fi - mv "${key_dir}/${host_key_file}.key.pem"{.new,} - mv "${key_dir}/${host_key_file}.cert.pem"{.new,} - updated_something=true - fi - done - if ${updated_something}; then - systemctl try-restart nginx - fi - - su http -s /bin/bash -c "$0" -fi - -if [ "$(whoami)" != 'http' ]; then - exit -fi - -if [ -n "$(trap)" ]; then - >&2 echo 'outer traps set - those will be forgotten!' - exit 1 -fi - -tmp_dir=$(mktemp -d '/srv/http/httpdocs/.csr/tmp.XXXXXXXXXX') -trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT - -printf '%s\n' "${hosts}" \ -| while read -r host other_hosts; do - if [ -f "${key_dir}/${host}.key.pem.new" ] \ - && [ -f "${key_dir}/${host}.cert.pem.new" ]; then - continue - fi - if [ -n "${other_hosts}" ]; then - extensions="-addext subjectAltName=$( - printf ',DNS:%s' \ - "${host}" \ - ${other_hosts} \ - | sed 's/^,//' - )" - else - extensions='' - fi - openssl req -newkey rsa:4096 \ - -keyout "${key_dir}/${host}.key.pem.new" \ - -out "${tmp_dir}/${host}.csr" \ - -nodes -subj '/C=DE/ST=Thuringia/L=Jena/O=Eckner/OU=Net/CN='"${host}" -sha256 \ - ${extensions} - printf 'https://%s/.csr/%s/%s.csr %s %s\n' \ - "${host}" \ - "${tmp_dir##*/}" \ - "${host}" \ - '/C=DE/ST=Thuringia/L=Jena/O=Eckner/OU=Net/CN='"${host}" \ - "${extensions}" \ - >> "${tmp_dir}/commands" -done - -if [ ! -s "${tmp_dir}/commands" ]; then - >&2 echo 'nothing to do.' - exit -fi - -cd "${tmp_dir}" - -cut -d' ' -f1 \ -< 'commands' \ -| ssh -T erich@192.168.1.3 \ -| tar -xzf - - -for host_key_file in ${host_key_files}; do - [ -f "${tmp_dir}/${host_key_file}.crt" ] || continue - cat "${tmp_dir}/${host_key_file}.crt" \ - > "${key_dir}/${host_key_file}.cert.pem.new" -done diff --git a/generate-and-upload-self-signed-keys.service.in b/generate-and-upload-self-signed-keys.service.in deleted file mode 100644 index 2f851c4..0000000 --- a/generate-and-upload-self-signed-keys.service.in +++ /dev/null @@ -1,6 +0,0 @@ -[Unit] -Description=generate and manage self-signed keys - -[Service] -Type=oneshot -ExecStart=#BINDIR#/generate-and-manage-self-signed-keys diff --git a/generate-and-upload-self-signed-keys.timer b/generate-and-upload-self-signed-keys.timer deleted file mode 100644 index 308a8e6..0000000 --- a/generate-and-upload-self-signed-keys.timer +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=generate and upload self-signed keys twice a year - -[Timer] -OnCalendar=*-01,05,09-01 00:00:00 -AccuracySec=1us -RandomizeDelaySec=10000000 -Persistent=true - -[Install] -WantedBy=timers.target diff --git a/sign-ca b/sign-ca deleted file mode 100755 index f2e8200..0000000 --- a/sign-ca +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -openssl req -x509 \ - -new -nodes \ - -key eckner-ca.key \ - -sha256 -days 3654 \ - -out eckner-ca.crt \ - -subj '/C=DE/ST=Thuringia/L=Jena/O=Eckner/OU=Net/CN=Certification Authority' \ - -addext 'subjectKeyIdentifier = hash' \ - -addext 'authorityKeyIdentifier = keyid:always, issuer' \ - -addext 'basicConstraints = critical, CA:true' \ - -addext 'keyUsage = keyCertSign, cRLSign' diff --git a/sign-ca.in b/sign-ca.in new file mode 100755 index 0000000..ff0e0c5 --- /dev/null +++ b/sign-ca.in @@ -0,0 +1,63 @@ +#!/bin/bash + +# generate new ca certificate, roll over the old one(s) + +set -e + +key_dir='#ETCDIR#/simple-pki/keys' + +if [ -r '#ETCDIR#/simple-pki/ca.conf' ]; then + . '#ETCDIR#/simple-pki/ca.conf' +fi + +if [ -f "${key_dir}/${ca_name}.key.new" ] \ +&& [ -f "${key_dir}/${ca_name}.crt.new" ]; then + if [ "$(stat -c%Y "${key_dir}/${ca_name}.key.new")" -lt "$(($(date +%s)-60*60*24*30))" ] \ + || [ ! -f "${key_dir}/${ca_name}.key" ] \ + || [ "$(stat -c%Y "${key_dir}/${ca_name}.crt.new")" -lt "$(($(date +%s)-60*60*24*30))" ] \ + || [ ! -f "${key_dir}/${ca_name}.crt" ]; then + mv "${key_dir}/${host_key_file}.key"{.new,} + mv "${key_dir}/${host_key_file}.crt"{.new,} + fi +fi + +if [ ! -f "${key_dir}/${ca_name}.key.new" ] \ +|| [ ! -f "${key_dir}/${ca_name}.crt.new" ]; then + openssl req -x509 -new \ + -newkey rsa:4096 -sha256 \ + -keyout "${key_dir}/${ca_name}.key.new" \ + -out "${key_dir}/${ca_name}.crt.new" \ + -days 365 -nodes \ + -subj "${ca_subject}"'/CN=Certification Authority' \ + -addext 'subjectKeyIdentifier = hash' \ + -addext 'authorityKeyIdentifier = keyid:always, issuer' \ + -addext 'basicConstraints = critical, CA:true' \ + -addext 'keyUsage = keyCertSign, cRLSign' +done + +rsync --ignore-missing-args \ + "${key_dir}/${ca_name}.crt"{.new,} \ + "${remote_host}:${remote_dir}/" + +( + cd "${key_dir}" + printf '%s %s\n' "$( + date -u --iso-8601=seconds -d@$(stat -c%Y "${ca_name}.key") \ + | cut -d+ -f1 + )" "$( + sha512sum "${ca_name}.key" \ + | sed 's/\s\+/ /' + )" +) \ +| ssh "${remote_host}" ' + cd "'"${remote_dir}"'" + while read -r time sum file; do + rm -f ????-??-??T??\:??\:??".${file}" + mv "${file}" "${time}.${file}" + sed -i '"'"' + / [^.]\+\.'"'"'"${file//./\.}"'"'"'$/d + '"'"' sha512sums + printf '"'"'%s %s\n'"'"' "${sum}" "${time}.${file}" \ + >> sha512sums + done +' diff --git a/sign-ca.service.in b/sign-ca.service.in new file mode 100644 index 0000000..b87ad36 --- /dev/null +++ b/sign-ca.service.in @@ -0,0 +1,6 @@ +[Unit] +Description=generate new ca key and certificate + +[Service] +Type=oneshot +ExecStart=#BINDIR#/sign-ca diff --git a/sign-ca.timer b/sign-ca.timer new file mode 100644 index 0000000..308a8e6 --- /dev/null +++ b/sign-ca.timer @@ -0,0 +1,11 @@ +[Unit] +Description=generate and upload self-signed keys twice a year + +[Timer] +OnCalendar=*-01,05,09-01 00:00:00 +AccuracySec=1us +RandomizeDelaySec=10000000 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/sign-request b/sign-request deleted file mode 100755 index cf3442e..0000000 --- a/sign-request +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/bash - -cd "${0%/*}" - -remove_leading_spaces() { - sed ' - s/^ \{'"$1"'\}// - t - d - ' -} - -tmp_dir=$(mktemp -d) -trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT - -while read -r csr; do - csr_local="${tmp_dir}/${csr##*/}" - curl -Ss "${csr}" -o "${csr_local}" - if ! content=$( - openssl req -text -noout -verify -in "${csr_local}" 2>/dev/null - ); then - rm "${csr_local}" - continue - fi - content=$( - printf '%s\n' "${content}" \ - | sed -n ' - /^Certificate Request:$/,/^\S/p - ' \ - | remove_leading_spaces 4 \ - | sed -n ' - /^Data:$/,/^\S/p - ' \ - | remove_leading_spaces 4 - ) - cn=$( - printf '%s\n' "${content}" \ - | sed ' - s/^Subject: .*, CN = \(\S\+\)$/\1/ - t - d - ' - ) - sans=$( - printf '%s\n' "${content}" \ - | sed -n ' - /^Requested Extensions:$/,/^\S/ p - ' \ - | remove_leading_spaces 4 \ - | sed -n ' - /^X\S\+ Subject Alternative Name:\s*$/,/^\S/ p - ' \ - | remove_leading_spaces 4 \ - | sed ' - s/, /\n/g - ' - ) - if printf '%s\n' "${sans}" | grep -vq '^\(DNS\|IP\):'; then - continue - fi - sans=$( - printf '%s\n' "${sans}" \ - | sed ' - s/^\(DNS\|IP\):// - ' - ) - ok_sans=$( - printf '%s\n' "${cn}" "${sans}" \ - | while read -r san; do - if ! curl -Ss --insecure 'https://'"${san}/${csr#*//*/}" \ - | diff -q - "${csr_local}"; then - rm "${csr_local}" - break - fi - printf '%s\n' "${san}" - done - ) - if [ ! -f "${csr_local}" ]; then - continue - fi - if [ "$(printf '%s\n' "${cn}" "${sans}")" != "${ok_sans}" ]; then - rm "${csr_local}" - continue - fi - openssl req -x509 -key eckner-ca.key -in "${csr_local}" -out "${csr_local%.csr}.crt" -addext 'basicConstraints = critical, CA:false' - rm "${csr_local}" -done - -cd "${tmp_dir}" -tar -czf - * diff --git a/sign-request.in b/sign-request.in new file mode 100755 index 0000000..f70c99a --- /dev/null +++ b/sign-request.in @@ -0,0 +1,103 @@ +#!/bin/bash + +key_dir='#ETCDIR#/simple-pki/keys' + +if [ -r '#ETCDIR#/simple-pki/ca.conf' ]; then + . '#ETCDIR#/simple-pki/ca.conf' +fi + +cd "${0%/*}" + +remove_leading_spaces() { + sed ' + s/^ \{'"$1"'\}// + t + d + ' +} + +tmp_dir=$(mktemp -d) +trap 'rm -rf --one-file-system "${tmp_dir}"' EXIT + +while read -r csr; do + csr_local="${tmp_dir}/${csr##*/}" + curl -Ss "${csr}" -o "${csr_local}" + if ! content=$( + openssl req -text -noout -verify -in "${csr_local}" 2>/dev/null + ); then + rm "${csr_local}" + continue + fi + content=$( + printf '%s\n' "${content}" \ + | sed -n ' + /^Certificate Request:$/,/^\S/p + ' \ + | remove_leading_spaces 4 \ + | sed -n ' + /^Data:$/,/^\S/p + ' \ + | remove_leading_spaces 4 + ) + cn=$( + printf '%s\n' "${content}" \ + | sed ' + s/^Subject: // + t + d + ' \ + | tr -d ' ' \ + | tr ',' '/' + ) + if [ -n "${cn#${ca_subject_prefix}/CN=*/}" ]; then + continue + fi + cn="${cn#${ca_subject_prefix}/CN=}" + cn="${cn%/}" + sans=$( + printf '%s\n' "${content}" \ + | sed -n ' + /^Requested Extensions:$/,/^\S/ p + ' \ + | remove_leading_spaces 4 \ + | sed -n ' + /^X\S\+ Subject Alternative Name:\s*$/,/^\S/ p + ' \ + | remove_leading_spaces 4 \ + | sed ' + s/, /\n/g + ' + ) + if printf '%s\n' "${sans}" | grep -vq '^\(DNS\|IP\):'; then + continue + fi + sans=$( + printf '%s\n' "${sans}" \ + | sed ' + s/^\(DNS\|IP\):// + ' + ) + ok_sans=$( + printf '%s\n' "${cn}" "${sans}" \ + | while read -r san; do + if ! curl -Ss --insecure 'https://'"${san}/${csr#*//*/}" \ + | diff -q - "${csr_local}"; then + rm "${csr_local}" + break + fi + printf '%s\n' "${san}" + done + ) + if [ ! -f "${csr_local}" ]; then + continue + fi + if [ "$(printf '%s\n' "${cn}" "${sans}")" != "${ok_sans}" ]; then + rm "${csr_local}" + continue + fi + openssl req -x509 -key "${key_dir}/${ca_name}.key" -in "${csr_local}" -out "${csr_local%.csr}.crt" -addext 'basicConstraints = critical, CA:false' + rm "${csr_local}" +done + +cd "${tmp_dir}" +tar -czf - * -- cgit v1.2.3-54-g00ecf