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