provide access to the apt-file functionality of the PTS
[shellsnippets/shellsnippets.git] / bash-ksh / generate-pgpkey-for-at-home
1 #!/bin/sh
2 # $Id: genkey-privatgebrauch.sh 2710 2012-02-07 09:27:07Z tglase $
3 #-
4 # Copyright © 2010, 2011
5 #       Thorsten Glaser <t.glaser@tarent.de>
6 # All rights reserved.
7 #
8 # Licenced under the GNU AGPLv3.
9 #-
10 # Generate a new GnuPG (PGP) key, for private use.
11
12 # check if we're called with mksh or bash; fix if not
13 if test -z "$shell_tried"; then
14         if test -z "$KSH_VERSION"; then
15                 if mksh -c true >/dev/null 2>&1; then
16                         shell_tried=1
17                         export shell_tried
18                         exec mksh "$0" "$@"
19                 fi
20                 if test -z "$BASH_VERSION"; then
21                         if bash -c true >/dev/null 2>&1; then
22                                 shell_tried=1
23                                 export shell_tried
24                                 exec bash "$0" "$@"
25                         fi
26                 fi
27         fi
28 fi
29 if test -z "$KSH_VERSION$BASH_VERSION"; then
30         echo >&2 "I've tried but couldn't find mksh or GNU bash."
31         echo >&2 "Please call me with one of these shells."
32         exit 1
33 fi
34 unset shell_tried
35
36 # set up some basic environment
37 export LC_ALL=C
38 unset LANG LANGUAGE
39 test -z "$BASH_VERSION" || shopt -s extglob
40 # we can now use Korn Shell extensions common to mksh and GNU bash
41
42 # initiate logging
43 cd "$(dirname "$0")"
44 log="$(basename "$0").log"
45 cat >>"$log" <<-EOF
46
47         New key generation started (Privatgebrauch)
48         $(date)
49         ===========================================
50 EOF
51 test -z "$KSH_VERSION" || echo ksh >>"$log"
52 test -z "$BASH_VERSION" || echo bash >>"$log"
53
54 # check for existence of prerequisite tools
55 for tool in gpg wget; do
56         $tool --version >/dev/null 2>&1 && continue
57         echo >&2 You must install $tool to continue.
58         exit 1
59 done
60
61 # subroutine for converting a string into an array
62 # taking into account Korn Shell vs GNU bash syntax
63 str2arr() {
64         local _a _b _s _vn=$1
65
66         eval _s=\$$_vn
67         if [[ -n $KSH_VERSION ]]; then
68                 _a="set -A $_vn -- "
69                 _b=
70         else
71                 _a="${_vn}=("
72                 _b=")"
73         fi
74         eval $_a$_s$_b
75 }
76
77 # subroutines for converting array elements into hex,
78 # printing with escapes honoured/ignored
79 # taking into account Korn Shell vs GNU bash syntax
80 if [[ -n $KSH_VERSION ]]; then
81         alias arr2hex='typeset -i16 '
82         alias eprint='print -n'
83         alias nprint='print -nr -- '
84 else
85         arr2hex() {
86                 local _vn=$1 _i _n _v
87
88                 _i=0
89                 eval _n='${#'$_vn'[*]}'
90                 while (( _i < _n )); do
91                         eval _v='${'$_vn'[_i]}'
92                         _v=$(printf '16#%x' $_v)
93                         eval $_vn'[_i++]=$_v'
94                 done
95         }
96         eprint() {
97                 printf "$@"
98         }
99         nprint() {
100                 printf '%s' "$*"
101         }
102 fi
103
104 ### BEGIN imported code {{{
105 # Copyright (c) 2008
106 #       Thorsten Glaser <tg@mirbsd.org>
107 #
108 # Provided that these terms and disclaimer and all copyright notices
109 # are retained or reproduced in an accompanying document, permission
110 # is granted to deal in this work without restriction, including un-
111 # limited rights to use, publicly perform, distribute, sell, modify,
112 # merge, give away, or sublicence.
113 #
114 # This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
115 # the utmost extent permitted by applicable law, neither express nor
116 # implied; without malicious intent or gross negligence. In no event
117 # may a licensor, author or contributor be held liable for indirect,
118 # direct, other damage, loss, or other issues arising in any way out
119 # of dealing in the work, even if advised of the possibility of such
120 # damage or existence of a defect, except proven that it results out
121 # of said person's immediate fault when using the work as intended.
122
123 # read a password without echoing
124 askpass() {
125         set -o noglob
126         stty -echo
127         echo -n "$1 "
128         read resp
129         stty echo
130         set +o noglob
131         echo
132 }
133
134 # convert a string from UTF-8 or ISO-8859-1 to UTF-8
135 str2utf8() {
136         local _s="$*" _z _c _i _hv _wc _n
137
138         _c=$(nprint "$_s" | hexdump -ve '1/1 "16#%x "')
139         _c="$_c 0"
140         str2arr _c
141         _s=
142         _z=0
143         _i=0
144         while (( _c[_i] )); do
145                 (( _hv = _c[_i] ))
146                 if (( (_hv < 16#C2) || (_hv >= 16#F0) )); then
147                         _n=1
148                 elif (( _hv < 16#E0 )); then
149                         _n=2
150                 else
151                         _n=3
152                 fi
153                 if (( _n > 1 )); then
154                         (( (_c[_i + 1] & 16#C0) == 16#80 )) || _n=1
155                         (( _hv == 16#E0 )) && \
156                             (( _c[_i + 1] < 16#A0 )) && _n=1
157                 fi
158                 if (( _n > 2 )); then
159                         (( (_c[_i + 2] & 16#C0) == 16#80 )) || _n=1
160                         (( _hv == 16#EF && _c[_i + 1] == 16#EF && \
161                             _c[_i + 2] > 16#BD )) && _n=1
162                 fi
163                 case $_n in
164                 (1)
165                         if (( (_wc = _c[_i]) < 16#80 )); then
166                                 (( _s[_z++] = _wc ))
167                         else
168                                 (( _s[_z++] = 16#C0 | (_wc >> 6) ))
169                                 (( _s[_z++] = 16#80 | (_wc & 16#3F) ))
170                         fi
171                         ;;
172                 (2)
173                         (( _s[_z++] = _c[_i] ))
174                         (( _s[_z++] = _c[_i + 1] ))
175                         ;;
176                 (3)
177                         (( _s[_z++] = _c[_i] ))
178                         (( _s[_z++] = _c[_i + 1] ))
179                         (( _s[_z++] = _c[_i + 2] ))
180                         ;;
181                 esac
182                 (( _i += _n ))
183         done
184         arr2hex _s
185         eprint "$(echo ${_s[*]} | sed -e 's/16#/\\x/g' -e 's/ //g')"
186 }
187
188 ### END imported code }}}
189
190 # create a temporary directory in /dev/shm (Linux tmpfs) or /tmp (otherwise)
191 if [[ ! -d /dev/shm/. ]] || ! T=$(mktemp -d /dev/shm/genkey.XXXXXXXXXX); then
192         if ! T=$(mktemp -d /tmp/genkey.XXXXXXXXXX); then
193                 echo >&2 Cannot create temporary directory.
194                 exit 1
195         fi
196 fi
197
198 cleanup() {
199         trap - 0 1 2 3 13 15
200         # files to overwrite before removing
201         for wipefiles in resp wgetrc; do
202                 for x in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
203                         echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
204                 done >"$T/$wipefiles"
205         done
206         sync
207         rm -rf "$T"
208         exit $1
209 }
210
211 # make sure the temporary files are removed if we are interrupted
212 trap "cleanup 1" 1 2 3 13 15
213 trap "cleanup 0" 0
214
215
216 # request and record user/pass
217 echo -n "Vor- und Zuname: "
218 read i_name
219 [[ -n $i_name ]] || cleanup 0
220 echo -n "eMail-Adresse: "
221 read i_mail
222 echo -n "Kommentarfeld: "
223 read i_comm
224 echo "Generiere für: '$i_name${i_comm:+ "($i_comm)"}${i_mail:+ "<$i_mail>"}' (^C wenn falsch)"
225 askpass "Password:"
226 [[ -n $resp ]] || cleanup 0
227 pw1=$resp
228 askpass "Password (nochmal):"
229 [[ -n $resp ]] || cleanup 0
230 if [[ $pw1 != $resp ]]; then
231         echo Sind nicht gleich.
232         cleanup 1
233 fi
234
235 echo "userdata: $i_name ($i_comm) <$i_mail>" >>"$log"
236
237 # add entropy from CGIs to that pool (magic code ;) {{{
238 if [[ ! -s ~/.gnupg/random_seed ]]; then
239         # create and fill if it didn't exist
240         :>~/.gnupg/random_seed
241         chmod 0600 ~/.gnupg/random_seed
242         dd if=/dev/urandom of=~/.gnupg/random_seed bs=600 count=1
243 fi
244 entropy=$x\ $(wget -O - --no-check-certificate -T 10 2>/dev/null https://call.mirbsd.org/lb.cgi?genkey.sh,1=$(hostname -f),seed=$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM | hexdump -ve '1/1 "16#%x "')
245 poolfile=$(hexdump -ve '1/1 "16#%x "' <~/.gnupg/random_seed)
246 str2arr entropy
247 str2arr poolfile
248 (( n = ${#poolfile[*]} < ${#entropy[*]} ? ${#entropy[*]} : ${#poolfile[*]} ))
249 i=0
250 # XOR poolfile with new entropy (from CGIs)
251 while (( i < n )); do
252         (( poolfile[i % ${#poolfile[*]}] ^= entropy[i % ${#entropy[*]}] ))
253         let i++
254 done
255 # write back into the pool file
256 arr2hex poolfile
257 eprint "$(echo ${poolfile[*]} | sed -e 's/16#/\\x/g' -e 's/ //g')" | \
258     dd of=~/.gnupg/random_seed conv=notrunc 2>/dev/null
259 # }}} end of magic code block ;)
260
261 # create response file for gpg
262 # NOTE: Key-Length can go up to 8192 (not more!) but please no lower
263 #       than 2048 (although more than 4096 may be incompatible *AND*
264 #       TERRIBLY SLOW)
265 cat >"$T/resp" <<-EOF
266         %echo Generating the key for $i_name ($i_mail) $i_comm
267         Key-Type: RSA
268         Key-Length: 3072
269         Key-Usage: auth,encrypt,sign
270         Passphrase: $(str2utf8 "$resp")
271         Name-Real: $(str2utf8 "$i_name")
272         ${i_comm:+Name-Comment: $(str2utf8 "$i_comm")}
273         ${i_mail:+Name-Email: $(str2utf8 "$i_mail")}
274         Expire-Date: 3y
275         Preferences: H8 H3 S8 S4 Z2 Z0 H9 H10 S9 S7
276         Keyserver: hkp://pgp.uni-mainz.de
277         %commit
278         %echo done
279 EOF
280
281 # really generate the key
282 echo
283 (gpg --no-use-agent --batch --gen-key "$T/resp"; echo $? >"$T/rc") 2>&1 | \
284     tee "$T/gen.out"
285 echo
286 (echo "create key {"; sed 's/^/ /' <"$T/gen.out"; echo "}") >>"$log"
287 # check for error exit
288 if (( $(<"$T/rc") > 0 )); then
289         echo >&2 Key generation failed.
290         cleanup 1
291 fi
292 # scan the gpg log for keyid of keypair just created
293 pkid=$(sed -n \
294     's/^gpg: key \([0-9A-F]*\) marked as ultimately trusted.*$/\1/p' \
295     "$T/gen.out")
296 if [[ $pkid != +([0-9A-F]) ]] || ! gpg -K $pkid; then
297         echo >&2 '┌─────────────────────────────────────────────────────────┐'
298         echo >&2 '│ Finding the key failed. YOU CAN USE THE KEY, BUT YOU    │'
299         echo >&2 '│ *MUST* CONTACT THE ADMINS with this error message.      │'
300         echo >&2 '│ Kann den neuen Schlüssel nicht finden. DU KANNST DIESES │'
301         echo >&2 '│ SCHLÜSSELPAAR BENUTZEN, ABER DU *MUẞT* DIE ADMINS mit   │'
302         echo >&2 '│ dieser Fehlernachricht KONTAKTIEREN.                    │'
303         echo >&2 '└─────────────────────────────────────────────────────────┘'
304         echo
305         echo >&2 Cannot find the key just generated.
306         cleanup 1
307 fi
308
309 # apply preference settings to our newly generated key
310 gpg --no-use-agent -q -u $pkid --command-fd 4 --edit-key $pkid \
311     >>"$T/edit.log" 2>&1 4<<-EOF
312         notation preferred-email-encoding@pgp.com=partitioned,pgpmime
313         $(str2utf8 "$resp")
314         trust
315         5
316         y
317         save
318 EOF
319 echo "=> $?" >>"$T/edit.log"
320
321         cat >&2 <<'EOF'
322 ┌─────────────────────────────────────────┐
323 │ Key generation finished with no errors. │
324 │ Schlüsselerzeugung fehlerfrei erledigt. │
325 └─────────────────────────────────────────┘
326
327 ╔═════════════════════════════════════════════════════════════════════╗
328 ║ You are responsible for backing up yout PGP secret key BY YOURSELF! ║
329 ║ Du mußt SELBER für Sicherungskopiën des privaten Schlüssels sorgen! ║
330 ╚═════════════════════════════════════════════════════════════════════╝
331 EOF
332 (echo "finished:"; gpg -k $pkid | sed 's/^/|    /'; echo) >>"$log"
333 echo >&2
334 gpg -k $pkid || echo gpg ERROR -k: $?
335 cleanup 0