From 0480d1bfd59a6ea4bc2f1f9441d4cb3be0c60b20 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 17 Mar 2022 13:50:24 +0300 Subject: [PATCH] Initial commit --- README | 50 ++++++++++++ zeasypki | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 README create mode 100755 zeasypki diff --git a/README b/README new file mode 100644 index 0000000..344b3d5 --- /dev/null +++ b/README @@ -0,0 +1,50 @@ +zeasypki -- easy PKI +This is helper script for managing X.509 TLS PKI. + +ECDSA keypairs are handled with GnuTLS'es certtool. +GOST keypairs are handled with PyGOST'es utilities +(http://www.pygost.cypherpunks.ru). + +CA certificates have 10 years validity lifetime. +EE certificates have 365 days one. +EE certificates contain only domain name and a country. + +Edit zeasypki to suit your needs and working environment. Probably you +want to change goston(), that activates PyGOST venv and key encryption +procedures. + +* Create CA keypairs: + $ mkdir mypki && cd mypki + $ zeasypki ca ecdsa ecdsa-root.com + $ zeasypki ca gost gost-root.ru + + $ zeasypki list-ca + ca/ecdsa/ecdsa-root.com + ca/gost/gost-root.ru + + $ print ca/ecdsa/ecdsa-root.com/* + cer.pem + key.pem + +* Optionally encrypt them (that also can be done with EE keypairs too): + $ zeasypki encrypt ca/ecdsa/ecdsa-root.com + [GnuPG is invoked here] + $ print ca/ecdsa/ecdsa-root.com/* + cer.pem + key.pem.enc + +* Create EE keypairs: + $ zeasypki new ee/ecdsa/ecdsa-root.com/some.domain.com + +* Renew then EE keypairs: + $ zeasypki renew ee/ecdsa/ecdsa-root.com/some.domain.com + +* To get DANE SHA256 fingerprint: + $ zeasypki dane KEY + +* To get full PEM-encoded keypair: + $ zeasypki keypair KEY + +* To get remind (https://dianne.skoll.ca/projects/remind/) compatible + calendar of certificates expiration times: + $ zeasypki rem diff --git a/zeasypki b/zeasypki new file mode 100755 index 0000000..ba52090 --- /dev/null +++ b/zeasypki @@ -0,0 +1,240 @@ +#!/usr/bin/env zsh +# zeasypki -- easy PKI +# Copyright (C) 2022 Sergey Matveev + +set -e + +CERTTOOL=${CERTTOOL:-certtool} +GPG=${GPG:-gpg} +KEY_ENCRYPT_RECIPIENT=${KEY_ENCRYPT_RECIPIENT:-CF60E89A59231E76E2636422AE1A8109E49857EF} +COUNTRY=${COUNTRY:-RU} + +goston() { + path=(~/local/stow/py310/bin ~/work/pygost/pygost/asn1schemas $path) + export -TU PYTHONPATH pythonpath + pythonpath=(~/work/pygost ~/work/pyderasn) +} + +key_encrypt() { + ${=GPG} --encrypt --recipient $KEY_ENCRYPT_RECIPIENT +} + +key_decrypt() { + ${=GPG} --decrypt +} + +# ------------------------ >8 ------------------------ + +usage() { + >&2 < $tmpl < $key + ${=CERTTOOL} \ + --generate-self-signed \ + --load-privkey $key \ + --template $tmpl \ + --outfile $cert + reply=(${mapfile[$key]} ${mapfile[$cert]}) +} + +ee_key_new_ecdsa() { + certtool_genkey 256 +} + +ee_key_new_gost() { + goston + cert-selfsigned-example.py --cn does-not-matter --ai 256A --only-key +} + +ee_renew_ecdsa() { + local ca=$1 + local domain=$2 + local cakey=`mktemp` + local key=`mktemp` + local tmpl=`mktemp` + local cert=`mktemp` + trap "rm -f $cakey $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT + key_get ca/ecdsa/$ca + mapfile[$cakey]=$REPLY + key_get ee/ecdsa/$ca/$domain + mapfile[$key]=$REPLY + > $tmpl <> $cakey < ca/gost/$ca/cer.pem + key_get ee/gost/$ca/$domain + mapfile[$key]=$REPLY + cert-selfsigned-example.py \ + --issue-with $cakey \ + --reuse-key $key \ + --cn $domain --country $COUNTRY --ai 256A +} + +ca_new_gost() { + local domain=$1 + goston + local key=`mktemp` + local cert=`mktemp` + trap "rm -f $key $cert" HUP PIPE INT QUIT TERM EXIT + cert-selfsigned-example.py \ + --ca \ + --cn $domain \ + --country $COUNTRY \ + --serial 1 \ + --ai 512C \ + --out-key $key \ + --out-cert $cert + reply=(${mapfile[$key]} ${mapfile[$cert]}) +} + +dane_ecdsa() { + ${=CERTTOOL} --key-id --hash=sha256 +} + +dane_gost() { + goston + cert-dane-hash.py +} + +case $1 in +(ca) + [[ $# -eq 3 ]] || usage + local algo=$2 + local domain=$3 + local dst=ca/$algo/$domain + zf_mkdir -p $dst + [[ -s $dst/key.pem ]] && { + print $dst/key.pem already exists >&2 + exit 1 + } + ca_new_${algo} $domain + local _umask=`umask` + umask 077 + mapfile[${dst}/key.pem]=${reply[1]} + umask $_umask + mapfile[${dst}/cer.pem]=${reply[2]} + print $dst + ;; +(encrypt) + [[ $# -eq 2 ]] || usage + local key=$2/key.pem + [[ -s $key ]] || { + print no $key found >&2 + exit 1 + } + umask 077 + key_encrypt < $key > $key.enc + rm $key + ;; +(new) + [[ $# -eq 2 ]] || usage + local cols=(${(s:/:)2}) + local algo=${cols[2]} + local ca=${cols[3]} + local domain=${cols[4]} + local dst=ee/$algo/$ca/$domain + [[ $dst = $2 ]] + zf_mkdir -p $dst + [[ -s $dst/key.pem ]] && { + print $dst/key.pem already exists >&2 + exit 1 + } + local _umask=`umask` + umask 077 + ee_key_new_${algo} > $dst/key.pem + umask $_umask + ee_renew_${algo} $ca $domain > $dst/cer.pem + ;; +(renew) + [[ $# -eq 2 ]] || usage + local cols=(${(s:/:)2}) + local algo=${cols[2]} + local ca=${cols[3]} + local domain=${cols[4]} + ee_renew_${algo} $ca $domain > ee/$algo/$ca/$domain/cer.pem + ;; +(dane) + [[ $# -eq 2 ]] || usage + dane_${${(s:/:)2}[2]} < $2/cer.pem + ;; +(keypair) + [[ $# -eq 2 ]] || usage + key_get $2 + print -- "$REPLY" + cat $2/cer.pem + ;; +(rem) + setopt GLOB_STAR_SHORT + export LC_ALL=C + for cer (**/cer.pem) { + date_bad_format=`certtool -i < $cer | + perl -ne '/Not After: \w+ (\w+ \d+ \d+:\d+):\d+ UTC (\d+)/ && print "$1 $2"'` + date_good_format=`date -j -f "%b %d %H:%M %Y" "$date_bad_format" +"%Y-%m-%d"` + print REM $date_good_format +30 MSG $cer + } + ;; +(list) print -C1 ee/*/*/*(/on) ;; +(list-ca) print -C1 ca/*/*(/on) ;; +(*) usage ;; +esac -- 2.44.0