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