#!/usr/bin/env zsh
# zeasypki -- easy PKI
-# Copyright (C) 2022 Sergey Matveev <stargrave@stargrave.org>
+# Copyright (C) 2022-2024 Sergey Matveev <stargrave@stargrave.org>
-set -e
+setopt ERR_EXIT PIPE_FAIL
-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)
-}
+path=(
+ ~/work/gogost/cmd/cer-selfsigned-example
+ ~/work/gogost/cmd/cer-dane-hash
+ $path
+)
key_encrypt() {
- ${=GPG} --encrypt --recipient $KEY_ENCRYPT_RECIPIENT
+ age -R ~/.age/general.pub
}
key_decrypt() {
- ${=GPG} --decrypt
+ age -d -i ~/.age/general.age
}
# ------------------------ >8 ------------------------
usage() {
- >&2 <<EOF
+ cat >&2 <<EOF
Usage:
- \$ $0:t ca [ecdsa|gost] NAME -- new CA keypair
- \$ $0:t list-ca -- list CA keypairs
- \$ $0:t list -- list EE ones
- \$ $0:t rem -- list certificate expirations
- \$ $0:t new KEY -- new EE
- \$ $0:t renew KEY -- renew EE
- \$ $0:t dane KEY -- show DANE SHA256 hash
- \$ $0:t encrypt KEY -- encrypt private key
- \$ $0:t keypair KEY -- PEM-encoded full keypair
+ \$ $ZSH_ARGZERO:t ca [ecdsa|gost|eddsa] NAME -- new CA keypair
+ \$ $ZSH_ARGZERO:t list-ca -- list CA keypairs
+ \$ $ZSH_ARGZERO:t list -- list EE ones
+ \$ $ZSH_ARGZERO:t rem -- list certificate expirations
+ \$ $ZSH_ARGZERO:t new KEY -- new EE
+ \$ $ZSH_ARGZERO:t renew KEY -- renew EE
+ \$ $ZSH_ARGZERO:t dane KEY -- show DANE SHA256 hash
+ \$ $ZSH_ARGZERO:t encrypt KEY -- encrypt private key
+ \$ $ZSH_ARGZERO:t keypair KEY -- PEM-encoded full keypair
EOF
exit 1
}
zmodload zsh/mapfile
key_get() {
- [[ -s $1/key.pem ]] &&
- REPLY=`< ${1}/key.pem` ||
+ if [[ -s $1/key.pem ]] ; then
+ REPLY=`< ${1}/key.pem`
+ else
REPLY=`key_decrypt < ${1}/key.pem.enc`
+ fi
}
certtool_genkey() {
- local bits=$1
- ${=CERTTOOL} --generate-privkey --ecc --bits $bits --no-text
+ certtool --generate-privkey ${=1} --no-text
}
-ca_new_ecdsa() {
- local domain=$1
+ca_new_xdsa() {
+ local keytype=$1
+ local domain=$2
local key=`mktemp`
local tmpl=`mktemp`
local cert=`mktemp`
trap "rm -f $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT
- > $tmpl <<EOF
+ cat > $tmpl <<EOF
dn = "cn=$domain,c=$COUNTRY"
-serial = 1
expiration_days = 3650
ca
cert_signing_key
EOF
- certtool_genkey 512 > $key
- ${=CERTTOOL} \
+ certtool_genkey "$keytype" > $key
+ certtool \
--generate-self-signed \
--load-privkey $key \
--template $tmpl \
reply=(${mapfile[$key]} ${mapfile[$cert]})
}
+ca_new_ecdsa() {
+ ca_new_xdsa "--key-type=ecdsa --bits 512" $1
+}
+
ee_key_new_ecdsa() {
- certtool_genkey 256
+ certtool_genkey "--key-type=ecdsa --bits 256"
+}
+
+ca_new_eddsa() {
+ ca_new_xdsa "--key-type=ed25519" $1
+}
+
+ee_key_new_eddsa() {
+ certtool_genkey "--key-type=ed25519"
}
ee_key_new_gost() {
- goston
- cert-selfsigned-example.py --cn does-not-matter --ai 256A --only-key
+ cer-selfsigned-example -cn does-not-matter -ai 256A -only-key
}
-ee_renew_ecdsa() {
- local ca=$1
- local domain=$2
+ee_renew_xdsa() {
+ local algo=$1
+ local ca=$2
+ local domain=$3
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
+ key_get ca/$algo/$ca
mapfile[$cakey]=$REPLY
- key_get ee/ecdsa/$ca/$domain
+ key_get ee/$algo/$ca/$domain
mapfile[$key]=$REPLY
- > $tmpl <<EOF
+ cat > $tmpl <<EOF
dn = "cn=$domain,c=RU"
expiration_days = 365
signing_key
dns_name = "$domain"
EOF
- ${=CERTTOOL} \
- --load-ca-certificate ca/ecdsa/$ca/cer.pem \
+ certtool \
+ --load-ca-certificate ca/$algo/$ca/cer.pem \
--load-ca-privkey $cakey \
--generate-certificate \
--load-privkey $key \
--template $tmpl
}
+ee_renew_ecdsa() {
+ ee_renew_xdsa ecdsa "$1" "$2"
+}
+
+ee_renew_eddsa() {
+ ee_renew_xdsa eddsa "$1" "$2"
+}
+
ee_renew_gost() {
local ca=$1
local domain=$2
- goston
local cakey=`mktemp`
local key=`mktemp`
local cert=`mktemp`
trap "rm -f $cakey $key $cert" HUP PIPE INT QUIT TERM EXIT
key_get ca/gost/$ca
mapfile[$cakey]=$REPLY
- >> $cakey < ca/gost/$ca/cer.pem
+ print >> $cakey
+ cat >> $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
+ cer-selfsigned-example \
+ -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
+ cer-selfsigned-example \
+ -ca \
+ -cn $domain \
+ -country $COUNTRY \
+ -ai 512C \
+ -out-key $key \
+ -out-cert $cert
reply=(${mapfile[$key]} ${mapfile[$cert]})
}
dane_ecdsa() {
- ${=CERTTOOL} --key-id --hash=sha256
+ certtool --key-id --hash=sha256
+}
+
+dane_eddsa() {
+ dane_ecdsa
}
dane_gost() {
- goston
- cert-dane-hash.py
+ cer-dane-hash
}
case $1 in
(ca)
[[ $# -eq 3 ]] || usage
- local algo=$2
- local domain=$3
- local dst=ca/$algo/$domain
+ algo=$2
+ domain=$3
+ dst=ca/$algo/$domain
zf_mkdir -p $dst
- [[ -s $dst/key.pem ]] && {
+ [[ ! -s $dst/key.pem ]] || {
print $dst/key.pem already exists >&2
exit 1
}
ca_new_${algo} $domain
- local _umask=`umask`
+ _umask=`umask`
umask 077
mapfile[${dst}/key.pem]=${reply[1]}
umask $_umask
;;
(encrypt)
[[ $# -eq 2 ]] || usage
- local key=$2/key.pem
+ key=$2/key.pem
[[ -s $key ]] || {
print no $key found >&2
exit 1
;;
(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
+ cols=(${(s:/:)2})
+ algo=${cols[2]}
+ ca=${cols[3]}
+ domain=${cols[4]}
+ dst=ee/$algo/$ca/$domain
[[ $dst = $2 ]]
zf_mkdir -p $dst
- [[ -s $dst/key.pem ]] && {
+ [[ ! -s $dst/key.pem ]] || {
print $dst/key.pem already exists >&2
exit 1
}
- local _umask=`umask`
+ _umask=`umask`
umask 077
ee_key_new_${algo} > $dst/key.pem
umask $_umask
;;
(renew)
[[ $# -eq 2 ]] || usage
- local cols=(${(s:/:)2})
- local algo=${cols[2]}
- local ca=${cols[3]}
- local domain=${cols[4]}
+ cols=(${(s:/:)2})
+ algo=${cols[2]}
+ ca=${cols[3]}
+ domain=${cols[4]}
ee_renew_${algo} $ca $domain > ee/$algo/$ca/$domain/cer.pem
;;
(dane)
cat $2/cer.pem
;;
(rem)
- setopt GLOB_STAR_SHORT
+ zmodload -F zsh/datetime b:strftime
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
+ certtool --certificate-info < $cer | while read line ; do
+ [[ ! $line =~ "^Not After: .*" ]] || break
+ done
+ [[ $MATCH ]]
+ # Not After: Sat Jul 02 10:02:29 UTC 2022
+ cols=(${=MATCH})
+ strftime -s ts_ugly -r "%b %d %H:%M:%S UTC %Y" ${(j: :)cols[4,-1]}
+ strftime -s ts_good %F $ts_ugly
+ print REM $ts_good +30 MSG $cer
}
;;
(list) print -C1 ee/*/*/*(/on) ;;