]> Sergey Matveev's repositories - zeasypki.git/blob - zeasypki
Raise copyright years
[zeasypki.git] / zeasypki
1 #!/usr/bin/env zsh
2 # zeasypki -- easy PKI
3 # Copyright (C) 2022-2023 Sergey Matveev <stargrave@stargrave.org>
4
5 set -e
6
7 KEY_ENCRYPT_RECIPIENT=${KEY_ENCRYPT_RECIPIENT:-12AD32689C660D426967FD75CB8205632107AD8A}
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     cat >&2 <<EOF
27 Usage:
28   \$ $ZSH_ARGZERO:t ca [ecdsa|gost|eddsa] 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 ${=1} --no-text
52 }
53
54 ca_new_xdsa() {
55     local keytype=$1
56     local domain=$2
57     local key=`mktemp`
58     local tmpl=`mktemp`
59     local cert=`mktemp`
60     trap "rm -f $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT
61     cat > $tmpl <<EOF
62 dn = "cn=$domain,c=$COUNTRY"
63 serial = 1
64 expiration_days = 3650
65 ca
66 cert_signing_key
67 EOF
68     certtool_genkey "$keytype" > $key
69     certtool \
70         --generate-self-signed \
71         --load-privkey $key \
72         --template $tmpl \
73         --outfile $cert
74     reply=(${mapfile[$key]} ${mapfile[$cert]})
75 }
76
77 ca_new_ecdsa() {
78     ca_new_xdsa "--key-type=ecdsa --bits 512" $1
79 }
80
81 ee_key_new_ecdsa() {
82     certtool_genkey "--key-type=ecdsa --bits 256"
83 }
84
85 ca_new_eddsa() {
86     ca_new_xdsa "--key-type=ed25519" $1
87 }
88
89 ee_key_new_eddsa() {
90     certtool_genkey "--key-type=ed25519"
91 }
92
93 ee_key_new_gost() {
94     cert-selfsigned-example.py --cn does-not-matter --ai 256A --only-key
95 }
96
97 ee_renew_xdsa() {
98     local algo=$1
99     local ca=$2
100     local domain=$3
101     local cakey=`mktemp`
102     local key=`mktemp`
103     local tmpl=`mktemp`
104     local cert=`mktemp`
105     trap "rm -f $cakey $key $tmpl $cert" HUP PIPE INT QUIT TERM EXIT
106     key_get ca/$algo/$ca
107     mapfile[$cakey]=$REPLY
108     key_get ee/$algo/$ca/$domain
109     mapfile[$key]=$REPLY
110     cat > $tmpl <<EOF
111 dn = "cn=$domain,c=RU"
112 expiration_days = 365
113 signing_key
114 dns_name = "$domain"
115 EOF
116     certtool \
117         --load-ca-certificate ca/$algo/$ca/cer.pem \
118         --load-ca-privkey $cakey \
119         --generate-certificate \
120         --load-privkey $key \
121         --template $tmpl
122 }
123
124 ee_renew_ecdsa() {
125     ee_renew_xdsa ecdsa "$1" "$2"
126 }
127
128 ee_renew_eddsa() {
129     ee_renew_xdsa eddsa "$1" "$2"
130 }
131
132 ee_renew_gost() {
133     local ca=$1
134     local domain=$2
135     local cakey=`mktemp`
136     local key=`mktemp`
137     local cert=`mktemp`
138     trap "rm -f $cakey $key $cert" HUP PIPE INT QUIT TERM EXIT
139     key_get ca/gost/$ca
140     mapfile[$cakey]=$REPLY
141     cat >> $cakey < ca/gost/$ca/cer.pem
142     key_get ee/gost/$ca/$domain
143     mapfile[$key]=$REPLY
144     cert-selfsigned-example.py \
145         --issue-with $cakey \
146         --reuse-key $key \
147         --cn $domain --country $COUNTRY --ai 256A
148 }
149
150 ca_new_gost() {
151     local domain=$1
152     local key=`mktemp`
153     local cert=`mktemp`
154     trap "rm -f $key $cert" HUP PIPE INT QUIT TERM EXIT
155     cert-selfsigned-example.py \
156         --ca \
157         --cn $domain \
158         --country $COUNTRY \
159         --serial 1 \
160         --ai 512C \
161         --out-key $key \
162         --out-cert $cert
163     reply=(${mapfile[$key]} ${mapfile[$cert]})
164 }
165
166 dane_ecdsa() {
167     certtool --key-id --hash=sha256
168 }
169
170 dane_eddsa() {
171     dane_ecdsa
172 }
173
174 dane_gost() {
175     cert-dane-hash.py
176 }
177
178 case $1 in
179 (ca)
180     [[ $# -eq 3 ]] || usage
181     algo=$2
182     domain=$3
183     dst=ca/$algo/$domain
184     zf_mkdir -p $dst
185     [[ -s $dst/key.pem ]] && {
186         print $dst/key.pem already exists >&2
187         exit 1
188     }
189     ca_new_${algo} $domain
190     _umask=`umask`
191     umask 077
192     mapfile[${dst}/key.pem]=${reply[1]}
193     umask $_umask
194     mapfile[${dst}/cer.pem]=${reply[2]}
195     print $dst
196     ;;
197 (encrypt)
198     [[ $# -eq 2 ]] || usage
199     key=$2/key.pem
200     [[ -s $key ]] || {
201         print no $key found >&2
202         exit 1
203     }
204     umask 077
205     key_encrypt < $key > $key.enc
206     rm $key
207     ;;
208 (new)
209     [[ $# -eq 2 ]] || usage
210     cols=(${(s:/:)2})
211     algo=${cols[2]}
212     ca=${cols[3]}
213     domain=${cols[4]}
214     dst=ee/$algo/$ca/$domain
215     [[ $dst = $2 ]]
216     zf_mkdir -p $dst
217     [[ -s $dst/key.pem ]] && {
218         print $dst/key.pem already exists >&2
219         exit 1
220     }
221     _umask=`umask`
222     umask 077
223     ee_key_new_${algo} > $dst/key.pem
224     umask $_umask
225     ee_renew_${algo} $ca $domain > $dst/cer.pem
226     ;;
227 (renew)
228     [[ $# -eq 2 ]] || usage
229     cols=(${(s:/:)2})
230     algo=${cols[2]}
231     ca=${cols[3]}
232     domain=${cols[4]}
233     ee_renew_${algo} $ca $domain > ee/$algo/$ca/$domain/cer.pem
234     ;;
235 (dane)
236     [[ $# -eq 2 ]] || usage
237     dane_${${(s:/:)2}[2]} < $2/cer.pem
238     ;;
239 (keypair)
240     [[ $# -eq 2 ]] || usage
241     key_get $2
242     print -- "$REPLY"
243     cat $2/cer.pem
244     ;;
245 (rem)
246     zmodload -F zsh/datetime b:strftime
247     export LC_ALL=C
248     for cer (**/cer.pem) {
249         certtool --certificate-info < $cer | while read line ; do
250             [[ $line =~ "^\s*Not After: .*" ]] && break
251         done
252         [[ $MATCH ]]
253         # Not After: Sat Jul 02 10:02:29 UTC 2022
254         cols=(${=MATCH})
255         strftime -s ts_ugly -r "%b %d %H:%M:%S UTC %Y" ${(j: :)cols[4,-1]}
256         strftime -s ts_good %F $ts_ugly
257         print REM $ts_good +30 MSG $cer
258     }
259     ;;
260 (list) print -C1 ee/*/*/*(/on) ;;
261 (list-ca) print -C1 ca/*/*(/on) ;;
262 (*) usage ;;
263 esac