51b3a9adf851a355aa6e40940bc5c471008f28bd
[shellsnippets/shellsnippets.git] / bash-ksh / generate-pgpkey-for-at-work
1 #!/bin/sh
2 # $Id: genkey-firmengebrauch.sh 1641 2011-01-24 10:55:40Z tglase $
3 #-
4 # Copyright © 2010, 2011
5 #       Thorsten Glaser <t.glaser@tarent.de>
6
7 # check if we're called with mksh or bash; fix if not
8 if test -z "$shell_tried"; then
9         if test -z "$KSH_VERSION"; then
10                 if mksh -c true >/dev/null 2>&1; then
11                         shell_tried=1
12                         export shell_tried
13                         exec mksh "$0" "$@"
14                 fi
15                 if test -z "$BASH_VERSION"; then
16                         if bash -c true >/dev/null 2>&1; then
17                                 shell_tried=1
18                                 export shell_tried
19                                 exec bash "$0" "$@"
20                         fi
21                 fi
22         fi
23 fi
24 if test -z "$KSH_VERSION$BASH_VERSION"; then
25         echo >&2 "I've tried but couldn't find mksh or GNU bash."
26         echo >&2 "Please call me with one of these shells."
27         exit 1
28 fi
29 unset shell_tried
30
31 # set up some basic environment
32 export LC_ALL=C
33 unset LANG LANGUAGE
34 test -z "$BASH_VERSION" || shopt -s extglob
35 # we can now use Korn Shell extensions common to mksh and GNU bash
36
37 # initiate logging
38 cd "$(dirname "$0")"
39 log="$(basename "$0").log"
40 cat >>"$log" <<-EOF
41
42         New key generation started (Firmengebrauch)
43         $(date)
44         ===========================================
45 EOF
46 test -z "$KSH_VERSION" || echo ksh >>"$log"
47 test -z "$BASH_VERSION" || echo bash >>"$log"
48
49 # check for existence of prerequisite tools
50 for tool in gpg wget; do
51         $tool --version >/dev/null 2>&1 && continue
52         echo >&2 You must install $tool to continue.
53         exit 1
54 done
55
56 # subroutine for converting a string into an array
57 # taking into account Korn Shell vs GNU bash syntax
58 str2arr() {
59         local _a _b _s _vn=$1
60
61         eval _s=\$$_vn
62         if [[ -n $KSH_VERSION ]]; then
63                 _a="set -A $_vn -- "
64                 _b=
65         else
66                 _a="${_vn}=("
67                 _b=")"
68         fi
69         eval $_a$_s$_b
70 }
71
72 # subroutines for converting array elements into hex,
73 # printing with escapes honoured/ignored
74 # taking into account Korn Shell vs GNU bash syntax
75 if [[ -n $KSH_VERSION ]]; then
76         alias arr2hex='typeset -i16 '
77         alias eprint='print -n'
78         alias nprint='print -nr -- '
79 else
80         arr2hex() {
81                 local _vn=$1 _i _n _v
82
83                 _i=0
84                 eval _n='${#'$_vn'[*]}'
85                 while (( _i < _n )); do
86                         eval _v='${'$_vn'[_i]}'
87                         _v=$(printf '16#%x' $_v)
88                         eval $_vn'[_i++]=$_v'
89                 done
90         }
91         eprint() {
92                 printf "$@"
93         }
94         nprint() {
95                 printf '%s' "$*"
96         }
97 fi
98
99 ### BEGIN imported code {{{
100 # Copyright (c) 2008
101 #       Thorsten Glaser <tg@mirbsd.org>
102 #
103 # Provided that these terms and disclaimer and all copyright notices
104 # are retained or reproduced in an accompanying document, permission
105 # is granted to deal in this work without restriction, including un-
106 # limited rights to use, publicly perform, distribute, sell, modify,
107 # merge, give away, or sublicence.
108 #
109 # This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
110 # the utmost extent permitted by applicable law, neither express nor
111 # implied; without malicious intent or gross negligence. In no event
112 # may a licensor, author or contributor be held liable for indirect,
113 # direct, other damage, loss, or other issues arising in any way out
114 # of dealing in the work, even if advised of the possibility of such
115 # damage or existence of a defect, except proven that it results out
116 # of said person's immediate fault when using the work as intended.
117
118 # read a password without echoing
119 askpass() {
120         set -o noglob
121         stty -echo
122         echo -n "$1 "
123         read resp
124         stty echo
125         set +o noglob
126         echo
127 }
128
129 # convert a string from UTF-8 or ISO-8859-1 to UTF-8
130 str2utf8() {
131         local _s="$*" _z _c _i _hv _wc _n
132
133         _c=$(nprint "$_s" | hexdump -ve '1/1 "16#%x "')
134         _c="$_c 0"
135         str2arr _c
136         _s=
137         _z=0
138         _i=0
139         while (( _c[_i] )); do
140                 (( _hv = _c[_i] ))
141                 if (( (_hv < 16#C2) || (_hv >= 16#F0) )); then
142                         _n=1
143                 elif (( _hv < 16#E0 )); then
144                         _n=2
145                 else
146                         _n=3
147                 fi
148                 if (( _n > 1 )); then
149                         (( (_c[_i + 1] & 16#C0) == 16#80 )) || _n=1
150                         (( _hv == 16#E0 )) && \
151                             (( _c[_i + 1] < 16#A0 )) && _n=1
152                 fi
153                 if (( _n > 2 )); then
154                         (( (_c[_i + 2] & 16#C0) == 16#80 )) || _n=1
155                         (( _hv == 16#EF && _c[_i + 1] == 16#EF && \
156                             _c[_i + 2] > 16#BD )) && _n=1
157                 fi
158                 case $_n in
159                 (1)
160                         if (( (_wc = _c[_i]) < 16#80 )); then
161                                 (( _s[_z++] = _wc ))
162                         else
163                                 (( _s[_z++] = 16#C0 | (_wc >> 6) ))
164                                 (( _s[_z++] = 16#80 | (_wc & 16#3F) ))
165                         fi
166                         ;;
167                 (2)
168                         (( _s[_z++] = _c[_i] ))
169                         (( _s[_z++] = _c[_i + 1] ))
170                         ;;
171                 (3)
172                         (( _s[_z++] = _c[_i] ))
173                         (( _s[_z++] = _c[_i + 1] ))
174                         (( _s[_z++] = _c[_i + 2] ))
175                         ;;
176                 esac
177                 (( _i += _n ))
178         done
179         arr2hex _s
180         eprint "$(echo ${_s[*]} | sed -e 's/16#/\\x/g' -e 's/ //g')"
181 }
182
183 ### END imported code }}}
184
185 # create a temporary directory in /dev/shm (Linux tmpfs) or /tmp (otherwise)
186 if [[ ! -d /dev/shm/. ]] || ! T=$(mktemp -d /dev/shm/genkey.XXXXXXXXXX); then
187         if ! T=$(mktemp -d /tmp/genkey.XXXXXXXXXX); then
188                 echo >&2 Cannot create temporary directory.
189                 exit 1
190         fi
191 fi
192
193 cleanup() {
194         trap - 0 1 2 3 13 15
195         # files to overwrite before removing
196         for wipefiles in resp wgetrc; do
197                 for x in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
198                         echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
199                 done >"$T/$wipefiles"
200         done
201         sync
202         rm -rf "$T"
203         exit $1
204 }
205
206 # make sure the temporary files are removed if we are interrupted
207 trap "cleanup 1" 1 2 3 13 15
208 trap "cleanup 0" 0
209
210
211 # import company key (delete first, just in case)
212 ckid=3166EA4AF24EFF313803A739EEABC048D0620C0F
213 gpg --batch --delete-key $ckid
214 gpg --import --batch <<'EOF'
215 -----BEGIN PGP PUBLIC KEY BLOCK-----
216 Version: GnuPG v1.4.9 (MirBSD)
217
218 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
252 4wBQF+0/W2exE5FX3pCwrYUmYe/ItsnAZKY5RA==
253 =tDFz
254 -----END PGP PUBLIC KEY BLOCK-----
255 EOF
256
257
258 # show introduction
259 echo "
260 tarent GmbH - PGP Key Generation
261 ================================
262
263 [de] Ein PGP-Schlüsselpaar wird jetzt erstellt und der öffentliche Teil
264      an den tarent-Server übertragen, sodaß die Admins diesen signieren
265      und veröffentlichen können. Außerdem wird mit diesem Schlüssel der
266      Firmenschlüssel unterschrieben; diese Signatur wird ebenfalls ver-
267      öffentlicht. Dein Name und eMail-Adresse wird aus dem LDAP bezogen
268      und das LDAP-Paßwort auch für den privaten Schlüssel verwendet.
269
270 [en] We will now generate a PGP keypair; the public key will be submit-
271      ted to the tarent server for being signed and published by the ad-
272      mins. The freshly generated key will also be used to sign and pub-
273      lish the company key. Your name and eMail address information will
274      be taken from the LDAP; the LDAP password will be used as password
275      for the secret key.
276 "
277
278 # request and record user/pass
279 echo -n "LDAP login Username: "
280 read un
281 [[ -n $un ]] || cleanup 0
282 askpass "LDAP login Password:"
283 [[ -n $resp ]] || cleanup 0
284
285 echo "login: $un" >>"$log"
286
287 # create wgetrc(5) to use for HTTP Basic Authentication
288 export WGETRC="$T/wgetrc"
289 cat >"$WGETRC" <<EOF
290 password = $resp
291 user = $un
292 EOF
293
294 # get user information from LDAP
295 x=$(wget -O "$T/ui" -S https://tarentpgp.tarent.de/ui.cgi 2>&1 | \
296     if md5sum --version >/dev/null 2>&1; then
297         md5sum | sed 's/ .*$//'
298 else
299         md5
300 fi | sed 's/../16#& /g')
301 (echo "got user info {"; sed 's/^/      /' <"$T/ui"; echo "}") >>"$log"
302
303 # add entropy from CGIs to that pool (magic code ;) {{{
304 if [[ ! -s ~/.gnupg/random_seed ]]; then
305         # create and fill if it didn't exist
306         :>~/.gnupg/random_seed
307         chmod 0600 ~/.gnupg/random_seed
308         dd if=/dev/urandom of=~/.gnupg/random_seed bs=600 count=1
309 fi
310 entropy=$x\ $( (
311         wget -O - --no-check-certificate -T 10 \
312             https://spamfilter2.tarent.de/lb.cgi?genkey.sh,1=$(hostname -f),seed=$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM
313         wget -O - -T 10 http://mirror.bonn.tarent.de/cgi-bin/rb.cgi
314         wget -O - --no-check-certificate -T 10 \
315             https://spamfilter2.tarent.de/lb.cgi?genkey.sh,2=$(hostname),seed=$RANDOM$RANDOM$RANDOM$RANDOM$RANDOM
316     ) 2>/dev/null | hexdump -ve '1/1 "16#%x "')
317 poolfile=$(hexdump -ve '1/1 "16#%x "' <~/.gnupg/random_seed)
318 str2arr entropy
319 str2arr poolfile
320 (( n = ${#poolfile[*]} < ${#entropy[*]} ? ${#entropy[*]} : ${#poolfile[*]} ))
321 i=0
322 # XOR poolfile with new entropy (from CGIs)
323 while (( i < n )); do
324         (( poolfile[i % ${#poolfile[*]}] ^= entropy[i % ${#entropy[*]}] ))
325         let i++
326 done
327 # write back into the pool file
328 arr2hex poolfile
329 eprint "$(echo ${poolfile[*]} | sed -e 's/16#/\\x/g' -e 's/ //g')" | \
330     dd of=~/.gnupg/random_seed conv=notrunc 2>/dev/null
331 # }}} end of magic code block ;)
332
333 # check the user information
334 status=bad
335 i_name=
336 i_mail=
337 i_comm=
338 while read key value; do
339         case x$key in
340         (xstatus)
341                 [[ $value = ok ]] || break
342                 [[ $status = bad ]] && status=good
343                 ;;
344         (xname|xmail|xcomm)
345                 eval i_$key=\$value
346                 ;;
347         (x)
348                 ;;
349         (*)
350                 echo >&2 "Invalid server response '$key $value'"
351                 status=invalid
352                 ;;
353         esac
354 done <"$T/ui"
355 if [[ $status != good || -z $i_name || -z $i_mail ]]; then
356         echo >&2 "Cannot process further (status: $status)"
357         [[ $status = bad ]] && echo >&2 Maybe wrong password.
358         cleanup 1
359 fi
360
361 # create response file for gpg
362 # NOTE: Key-Length can go up to 8192 (not more!) but please no lower
363 #       than 2048 (although more than 4096 may be incompatible *AND*
364 #       TERRIBLY SLOW)
365 cat >"$T/resp" <<-EOF
366         %echo Generating the key for $i_name ($i_mail) $i_comm
367         Key-Type: RSA
368         Key-Length: 3072
369         Key-Usage: auth,encrypt,sign
370         Passphrase: $(str2utf8 "$resp")
371         Name-Real: $(str2utf8 "$i_name")
372         ${i_comm:+Name-Comment: $(str2utf8 "$i_comm")}
373         Name-Email: $(str2utf8 "$i_mail")
374         Expire-Date: 3y
375         Preferences: H8 H3 S8 S4 Z2 Z0 H9 H10 S9 S7
376         Revoker: 1:$ckid
377         Keyserver: hkp://tarentpgp.tarent.de
378         %commit
379         %echo done
380 EOF
381
382 # really generate the key
383 echo
384 (gpg --no-use-agent --batch --gen-key "$T/resp"; echo $? >"$T/rc") 2>&1 | \
385     tee "$T/gen.out"
386 echo
387 (echo "create key {"; sed 's/^/ /' <"$T/gen.out"; echo "}") >>"$log"
388 # check for error exit
389 if (( $(<"$T/rc") > 0 )); then
390         echo >&2 Key generation failed.
391         cleanup 1
392 fi
393 # scan the gpg log for keyid of keypair just created
394 pkid=$(sed -n \
395     's/^gpg: key \([0-9A-F]*\) marked as ultimately trusted.*$/\1/p' \
396     "$T/gen.out")
397 if [[ $pkid != +([0-9A-F]) ]] || ! gpg -K $pkid; then
398         echo >&2 '┌─────────────────────────────────────────────────────────┐'
399         echo >&2 '│ Finding the key failed. YOU CAN USE THE KEY, BUT YOU    │'
400         echo >&2 '│ *MUST* CONTACT THE ADMINS with this error message.      │'
401         echo >&2 '│ Kann den neuen Schlüssel nicht finden. DU KANNST DIESES │'
402         echo >&2 '│ SCHLÜSSELPAAR BENUTZEN, ABER DU *MUẞT* DIE ADMINS mit   │'
403         echo >&2 '│ dieser Fehlernachricht KONTAKTIEREN.                    │'
404         echo >&2 '└─────────────────────────────────────────────────────────┘'
405         echo
406         echo >&2 Cannot find the key just generated.
407         cleanup 1
408 fi
409
410 # apply preference settings to our newly generated key
411 gpg --no-use-agent -q -u $pkid --command-fd 4 --edit-key $pkid \
412     >>"$T/edit.log" 2>&1 4<<-EOF
413         notation preferred-email-encoding@pgp.com=partitioned,pgpmime
414         $(str2utf8 "$resp")
415         trust
416         5
417         y
418         uid 1
419         primary
420         $(str2utf8 "$resp")
421         adduid
422         $(str2utf8 "$i_name")
423         $(str2utf8 "$un")@info.tarent.de
424         Jabber/XMPP
425         $(str2utf8 "$resp")
426         uid 1
427         uid 2
428         setpref H8 H3 S8 S4 Z2 Z0 Z1 H9 H10 S9 S7
429         y
430         $(str2utf8 "$resp")
431         keyserver hkp://tarentpgp.tarent.de
432         $(str2utf8 "$resp")
433         save
434 EOF
435 echo "=> $?" >>"$T/edit.log"
436
437 # sign the company key with that key
438 # XXX if the key has >1 UID, there must be an extra line
439 #       y
440 # after the line saying "tsign"!
441 gpg --no-use-agent -q -u $pkid --command-fd 4 --edit-key $ckid \
442     >>"$T/edit.log" 2>&1 4<<-EOF
443         tsign
444         2
445         2
446
447         y
448         $(str2utf8 "$resp")
449         trust
450         4
451         save
452 EOF
453 echo "=> $?" >>"$T/edit.log"
454 sed 's/^/│/' "$T/edit.log" >>"$log"
455
456 # export our own public key and the signed company key into a keyring
457 rc=0
458 gpg --export-options no-export-attributes,export-clean \
459     --export $ckid >"$T/exp.c" 2>>"$log" || rc=$?
460 gpg --export-options no-export-attributes,export-clean,export-minimal \
461     --export $pkid >"$T/exp.p" 2>>"$log" || rc=$?
462 rm -f "$T/exp.kr"
463 GNUPGHOME="$T/.gnupg" gpg --no-default-keyring --primary-keyring "$T/exp.kr" \
464     --import "$T/exp.c" 2>>"$log" || rc=$?
465 GNUPGHOME="$T/.gnupg" gpg --no-default-keyring --primary-keyring "$T/exp.kr" \
466     --import "$T/exp.p" 2>>"$log" || rc=$?
467 echo >>"$log"
468 if (( rc )); then
469         (echo "export error $rc"; gpg -k $pkid; echo "=> $?") >>"$log"
470         echo >&2 '┌────────────────────────────────────────────────────────┐'
471         echo >&2 '│ Exporting the key failed. YOU CAN USE THE KEY, BUT YOU │'
472         echo >&2 '│ *MUST* CONTACT THE ADMINS with this error message.     │'
473         echo >&2 '│ Export des Schlüssels fehlgeschlagen. DU KANNST DIESES │'
474         echo >&2 '│ SCHLÜSSELPAAR BENUTZEN, ABER DU *MUẞT* DIE ADMINS mit  │'
475         echo >&2 '│ dieser Fehlernachricht KONTAKTIEREN.                   │'
476         echo >&2 '└────────────────────────────────────────────────────────┘'
477         echo
478         gpg -k $pkid || echo gpg ERROR -k: $?
479         cleanup 0
480 fi
481 echo "export ok" >>"$log"
482 GNUPGHOME="$T/.gnupg" gpg --no-default-keyring --primary-keyring "$T/exp.kr" \
483     --list-sigs >>"$log" 2>&1
484
485 # upload the exported keyring
486 wget -O "$T/upload" \
487     --header="Content-type: application/octet-stream" \
488     --post-file="$T/exp.kr" \
489     https://tarentpgp.tarent.de/fu.cgi >"$T/upload.log" 2>&1
490 (echo "upload keyring {"; sed 's/^/[    /' <"$T/upload.log"; \
491     sed 's/^/]  /' <"$T/upload"; echo "}") >>"$log"
492 echo
493 if [[ $(head -1 "$T/upload") != "upload ok" ]]; then
494         echo >&2 '┌────────────────────────────────────────────────────────┐'
495         echo >&2 '│ Uploading the key failed. YOU CAN USE THE KEY, BUT YOU │'
496         echo >&2 '│ *MUST* CONTACT THE ADMINS with this error message.     │'
497         echo >&2 '│ Upload des Schlüssels fehlgeschlagen. DU KANNST DIESES │'
498         echo >&2 '│ SCHLÜSSELPAAR BENUTZEN, ABER DU *MUẞT* DIE ADMINS mit  │'
499         echo >&2 '│ dieser Fehlernachricht KONTAKTIEREN.                   │'
500         echo >&2 '└────────────────────────────────────────────────────────┘'
501 else
502         echo >&2 '┌─────────────────────────────────────────┐'
503         echo >&2 '│ Key generation finished with no errors. │'
504         echo >&2 '│ Schlüsselerzeugung fehlerfrei erledigt. │'
505         echo >&2 '└─────────────────────────────────────────┘'
506 fi
507 (echo "finished:"; gpg -k $pkid | sed 's/^/|    /'; echo) >>"$log"
508 echo >&2
509 gpg -k $pkid || echo gpg ERROR -k: $?
510 cleanup 0