]> Sergey Matveev's repositories - zeasypki.git/blob - zeasypki
Correct name of the script
[zeasypki.git] / zeasypki
1 #!/usr/bin/env zsh
2 # zeasypki -- easy PKI
3 # Copyright (C) 2022 Sergey Matveev <stargrave@stargrave.org>
4
5 set -e
6
7 KEY_ENCRYPT_RECIPIENT=${KEY_ENCRYPT_RECIPIENT:-CF60E89A59231E76E2636422AE1A8109E49857EF}
8 COUNTRY=${COUNTRY:-RU}
9
10 # Turn on PyGOST utilities
11 path=(~/local/stow/py310/bin ~/work/pygost/pygost/asn1schemas $path)
12 export -TU PYTHONPATH pythonpath
13 pythonpath=(~/work/pygost ~/work/pyderasn)
14
15 key_encrypt() {
16     gpg --encrypt --recipient $KEY_ENCRYPT_RECIPIENT
17 }
18
19 key_decrypt() {
20     gpg --decrypt
21 }
22
23 # ------------------------ >8 ------------------------
24
25 usage() {
26     >&2 <<EOF
27 Usage:
28   \$ $ZSH_ARGZERO:t ca [ecdsa|gost] NAME -- new CA keypair
29   \$ $ZSH_ARGZERO:t list-ca              -- list CA keypairs
30   \$ $ZSH_ARGZERO:t list                 -- list EE ones
31   \$ $ZSH_ARGZERO:t rem                  -- list certificate expirations
32   \$ $ZSH_ARGZERO:t new KEY              -- new EE
33   \$ $ZSH_ARGZERO:t renew KEY            -- renew EE
34   \$ $ZSH_ARGZERO:t dane KEY             -- show DANE SHA256 hash
35   \$ $ZSH_ARGZERO:t encrypt KEY          -- encrypt private key
36   \$ $ZSH_ARGZERO:t keypair KEY          -- PEM-encoded full keypair
37 EOF
38     exit 1
39 }
40
41 zmodload -F zsh/files b:zf_mkdir
42 zmodload zsh/mapfile
43
44 key_get() {
45     [[ -s $1/key.pem ]] &&
46         REPLY=`< ${1}/key.pem` ||
47         REPLY=`key_decrypt < ${1}/key.pem.enc`
48 }
49
50 certtool_genkey() {
51     certtool --generate-privkey --ecc --bits $1 --no-text
52 }
53
54 ca_new_ecdsa() {
55     local domain=$1
56     local key=`mktemp`
57     local tmpl=`mktemp`
58     local cert=`mktemp`
59     trap "rm -f $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT
60     > $tmpl <<EOF
61 dn = "cn=$domain,c=$COUNTRY"
62 serial = 1
63 expiration_days = 3650
64 ca
65 cert_signing_key
66 EOF
67     certtool_genkey 512 > $key
68     certtool \
69         --generate-self-signed \
70         --load-privkey $key \
71         --template $tmpl \
72         --outfile $cert
73     reply=(${mapfile[$key]} ${mapfile[$cert]})
74 }
75
76 ee_key_new_ecdsa() {
77     certtool_genkey 256
78 }
79
80 ee_key_new_gost() {
81     cert-selfsigned-example.py --cn does-not-matter --ai 256A --only-key
82 }
83
84 ee_renew_ecdsa() {
85     local ca=$1
86     local domain=$2
87     local cakey=`mktemp`
88     local key=`mktemp`
89     local tmpl=`mktemp`
90     local cert=`mktemp`
91     trap "rm -f $cakey $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT
92     key_get ca/ecdsa/$ca
93     mapfile[$cakey]=$REPLY
94     key_get ee/ecdsa/$ca/$domain
95     mapfile[$key]=$REPLY
96     > $tmpl <<EOF
97 dn = "cn=$domain,c=RU"
98 expiration_days = 365
99 signing_key
100 dns_name = "$domain"
101 EOF
102     certtool \
103         --load-ca-certificate ca/ecdsa/$ca/cer.pem \
104         --load-ca-privkey $cakey \
105         --generate-certificate \
106         --load-privkey $key \
107         --template $tmpl
108 }
109
110 ee_renew_gost() {
111     local ca=$1
112     local domain=$2
113     local cakey=`mktemp`
114     local key=`mktemp`
115     local cert=`mktemp`
116     trap "rm -f $cakey $key $cert" HUP PIPE INT QUIT TERM EXIT
117     key_get ca/gost/$ca
118     mapfile[$cakey]=$REPLY
119     >> $cakey < ca/gost/$ca/cer.pem
120     key_get ee/gost/$ca/$domain
121     mapfile[$key]=$REPLY
122     cert-selfsigned-example.py \
123         --issue-with $cakey \
124         --reuse-key $key \
125         --cn $domain --country $COUNTRY --ai 256A
126 }
127
128 ca_new_gost() {
129     local domain=$1
130     local key=`mktemp`
131     local cert=`mktemp`
132     trap "rm -f $key $cert" HUP PIPE INT QUIT TERM EXIT
133     cert-selfsigned-example.py \
134         --ca \
135         --cn $domain \
136         --country $COUNTRY \
137         --serial 1 \
138         --ai 512C \
139         --out-key $key \
140         --out-cert $cert
141     reply=(${mapfile[$key]} ${mapfile[$cert]})
142 }
143
144 dane_ecdsa() {
145     certtool --key-id --hash=sha256
146 }
147
148 dane_gost() {
149     cert-dane-hash.py
150 }
151
152 case $1 in
153 (ca)
154     [[ $# -eq 3 ]] || usage
155     algo=$2
156     domain=$3
157     dst=ca/$algo/$domain
158     zf_mkdir -p $dst
159     [[ -s $dst/key.pem ]] && {
160         print $dst/key.pem already exists >&2
161         exit 1
162     }
163     ca_new_${algo} $domain
164     _umask=`umask`
165     umask 077
166     mapfile[${dst}/key.pem]=${reply[1]}
167     umask $_umask
168     mapfile[${dst}/cer.pem]=${reply[2]}
169     print $dst
170     ;;
171 (encrypt)
172     [[ $# -eq 2 ]] || usage
173     key=$2/key.pem
174     [[ -s $key ]] || {
175         print no $key found >&2
176         exit 1
177     }
178     umask 077
179     key_encrypt < $key > $key.enc
180     rm $key
181     ;;
182 (new)
183     [[ $# -eq 2 ]] || usage
184     cols=(${(s:/:)2})
185     algo=${cols[2]}
186     ca=${cols[3]}
187     domain=${cols[4]}
188     dst=ee/$algo/$ca/$domain
189     [[ $dst = $2 ]]
190     zf_mkdir -p $dst
191     [[ -s $dst/key.pem ]] && {
192         print $dst/key.pem already exists >&2
193         exit 1
194     }
195     _umask=`umask`
196     umask 077
197     ee_key_new_${algo} > $dst/key.pem
198     umask $_umask
199     ee_renew_${algo} $ca $domain > $dst/cer.pem
200     ;;
201 (renew)
202     [[ $# -eq 2 ]] || usage
203     cols=(${(s:/:)2})
204     algo=${cols[2]}
205     ca=${cols[3]}
206     domain=${cols[4]}
207     ee_renew_${algo} $ca $domain > ee/$algo/$ca/$domain/cer.pem
208     ;;
209 (dane)
210     [[ $# -eq 2 ]] || usage
211     dane_${${(s:/:)2}[2]} < $2/cer.pem
212     ;;
213 (keypair)
214     [[ $# -eq 2 ]] || usage
215     key_get $2
216     print -- "$REPLY"
217     cat $2/cer.pem
218     ;;
219 (rem)
220     zmodload -F zsh/datetime b:strftime
221     export LC_ALL=C
222     for cer (**/cer.pem) {
223         certtool --certificate-info < $cer | while read line ; do
224             [[ $line =~ "^\s*Not After: .*" ]] && break
225         done
226         [[ $MATCH ]]
227         # Not After: Sat Jul 02 10:02:29 UTC 2022
228         cols=(${=MATCH})
229         strftime -s ts_ugly -r "%b %d %H:%M:%S UTC %Y" ${(j: :)cols[4,-1]}
230         strftime -s ts_good %F $ts_ugly
231         print REM $ts_good +30 MSG $cer
232     }
233     ;;
234 (list) print -C1 ee/*/*/*(/on) ;;
235 (list-ca) print -C1 ca/*/*(/on) ;;
236 (*) usage ;;
237 esac