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