/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-keygen

  • Committer: Teddy Hogeborn
  • Date: 2019-02-10 03:50:20 UTC
  • Revision ID: teddy@recompile.se-20190210035020-nttr1tybgwwixueu
Show debconf note about new TLS key IDs

If mandos-client did not see TLS keys and had to create them, or if
mandos sees GnuTLS version 3.6.6 or later, show an important notice on
package installation about the importance of adding the new key_id
options to clients.conf on the Mandos server.

* debian/control (Package: mandos, Package: mandos-client): Depend on
                                                            debconf.
* debian/mandos-client.lintian-overrides: Override warnings.
* debian/mandos-client.postinst (create_keys): Show notice if new TLS
                                               key files were created.
* debian/mandos-client.templates: New.
* debian/mandos.lintian-overrides: Override warnings.
* debian/mandos.postinst (configure): If GnuTLS 3.6.6 or later is
                                      detected, show an important
                                      notice (once) about the new
                                      key_id option required in
                                      clients.conf.
* debian/mandos.templates: New.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/bin/sh -e
 
2
 
3
# Mandos key generator - create new keys for a Mandos client
 
4
 
5
# Copyright © 2008-2018 Teddy Hogeborn
 
6
# Copyright © 2008-2018 Björn Påhlsson
 
7
 
8
# This file is part of Mandos.
 
9
#
 
10
# Mandos is free software: you can redistribute it and/or modify it
 
11
# under the terms of the GNU General Public License as published by
 
12
# the Free Software Foundation, either version 3 of the License, or
 
13
# (at your option) any later version.
 
14
#
 
15
#     Mandos is distributed in the hope that it will be useful, but
 
16
#     WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
#     GNU General Public License for more details.
 
19
 
20
# You should have received a copy of the GNU General Public License
 
21
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
22
 
23
# Contact the authors at <mandos@recompile.se>.
 
24
 
25
 
 
26
VERSION="1.7.20"
 
27
 
 
28
KEYDIR="/etc/keys/mandos"
 
29
KEYTYPE=RSA
 
30
KEYLENGTH=4096
 
31
SUBKEYTYPE=RSA
 
32
SUBKEYLENGTH=4096
 
33
KEYNAME="`hostname --fqdn 2>/dev/null || hostname`"
 
34
KEYEMAIL=""
 
35
KEYCOMMENT=""
 
36
KEYEXPIRE=0
 
37
TLS_KEYTYPE=ed25519
 
38
FORCE=no
 
39
SSH=yes
 
40
KEYCOMMENT_ORIG="$KEYCOMMENT"
 
41
mode=keygen
 
42
 
 
43
if [ ! -d "$KEYDIR" ]; then
 
44
    KEYDIR="/etc/mandos/keys"
 
45
fi
 
46
 
 
47
# Parse options
 
48
TEMP=`getopt --options vhpF:d:t:l:s:L:n:e:c:x:T:fS \
 
49
    --longoptions version,help,password,passfile:,dir:,type:,length:,subtype:,sublength:,name:,email:,comment:,expire:,tls-keytype:,force,no-ssh \
 
50
    --name "$0" -- "$@"`
 
51
 
 
52
help(){
 
53
basename="`basename "$0"`"
 
54
cat <<EOF
 
55
Usage: $basename [ -v | --version ]
 
56
       $basename [ -h | --help ]
 
57
   Key creation:
 
58
       $basename [ OPTIONS ]
 
59
   Encrypted password creation:
 
60
       $basename { -p | --password } [ --name NAME ] [ --dir DIR]
 
61
       $basename { -F | --passfile } FILE [ --name NAME ] [ --dir DIR]
 
62
 
 
63
Key creation options:
 
64
  -v, --version         Show program's version number and exit
 
65
  -h, --help            Show this help message and exit
 
66
  -d DIR, --dir DIR     Target directory for key files
 
67
  -t TYPE, --type TYPE  OpenPGP key type.  Default is RSA.
 
68
  -l BITS, --length BITS
 
69
                        OpenPGP key length in bits.  Default is 4096.
 
70
  -s TYPE, --subtype TYPE
 
71
                        OpenPGP subkey type.  Default is RSA.
 
72
  -L BITS, --sublength BITS
 
73
                        OpenPGP subkey length in bits.  Default 4096.
 
74
  -n NAME, --name NAME  Name of key.  Default is the FQDN.
 
75
  -e ADDRESS, --email ADDRESS
 
76
                        Email address of OpenPGP key.  Default empty.
 
77
  -c TEXT, --comment TEXT
 
78
                        Comment field for OpenPGP key.  Default empty.
 
79
  -x TIME, --expire TIME
 
80
                        OpenPGP key expire time.  Default is none.
 
81
                        See gpg(1) for syntax.
 
82
  -T TYPE, --tls-keytype TYPE
 
83
                        TLS key type.  Default is ed25519.
 
84
  -f, --force           Force overwriting old key files.
 
85
 
 
86
Password creation options:
 
87
  -p, --password        Create an encrypted password using the key in
 
88
                        the key directory.  All options other than
 
89
                        --dir and --name are ignored.
 
90
  -F FILE, --passfile FILE
 
91
                        Encrypt a password from FILE using the key in
 
92
                        the key directory.  All options other than
 
93
                        --dir and --name are ignored.
 
94
  -S, --no-ssh          Don't get SSH key or set "checker" option.
 
95
EOF
 
96
}
 
97
 
 
98
eval set -- "$TEMP"
 
99
while :; do
 
100
    case "$1" in
 
101
        -p|--password) mode=password; shift;;
 
102
        -F|--passfile) mode=password; PASSFILE="$2"; shift 2;;
 
103
        -d|--dir) KEYDIR="$2"; shift 2;;
 
104
        -t|--type) KEYTYPE="$2"; shift 2;;
 
105
        -s|--subtype) SUBKEYTYPE="$2"; shift 2;;
 
106
        -l|--length) KEYLENGTH="$2"; shift 2;;
 
107
        -L|--sublength) SUBKEYLENGTH="$2"; shift 2;;
 
108
        -n|--name) KEYNAME="$2"; shift 2;;
 
109
        -e|--email) KEYEMAIL="$2"; shift 2;;
 
110
        -c|--comment) KEYCOMMENT="$2"; shift 2;;
 
111
        -x|--expire) KEYEXPIRE="$2"; shift 2;;
 
112
        -T|--tls-keytype) TLS_KEYTYPE="$2"; shift 2;;
 
113
        -f|--force) FORCE=yes; shift;;
 
114
        -S|--no-ssh) SSH=no; shift;;
 
115
        -v|--version) echo "$0 $VERSION"; exit;;
 
116
        -h|--help) help; exit;;
 
117
        --) shift; break;;
 
118
        *) echo "Internal error" >&2; exit 1;;
 
119
    esac
 
120
done
 
121
if [ "$#" -gt 0 ]; then
 
122
    echo "Unknown arguments: '$*'" >&2
 
123
    exit 1
 
124
fi
 
125
 
 
126
SECKEYFILE="$KEYDIR/seckey.txt"
 
127
PUBKEYFILE="$KEYDIR/pubkey.txt"
 
128
TLS_PRIVKEYFILE="$KEYDIR/tls-privkey.pem"
 
129
TLS_PUBKEYFILE="$KEYDIR/tls-pubkey.pem"
 
130
 
 
131
# Check for some invalid values
 
132
if [ ! -d "$KEYDIR" ]; then
 
133
    echo "$KEYDIR not a directory" >&2
 
134
    exit 1
 
135
fi
 
136
if [ ! -r "$KEYDIR" ]; then
 
137
    echo "Directory $KEYDIR not readable" >&2
 
138
    exit 1
 
139
fi
 
140
 
 
141
if [ "$mode" = keygen ]; then
 
142
    if [ ! -w "$KEYDIR" ]; then
 
143
        echo "Directory $KEYDIR not writeable" >&2
 
144
        exit 1
 
145
    fi
 
146
    if [ -z "$KEYTYPE" ]; then
 
147
        echo "Empty key type" >&2
 
148
        exit 1
 
149
    fi
 
150
    
 
151
    if [ -z "$KEYNAME" ]; then
 
152
        echo "Empty key name" >&2
 
153
        exit 1
 
154
    fi
 
155
    
 
156
    if [ -z "$KEYLENGTH" ] || [ "$KEYLENGTH" -lt 512 ]; then
 
157
        echo "Invalid key length" >&2
 
158
        exit 1
 
159
    fi
 
160
    
 
161
    if [ -z "$KEYEXPIRE" ]; then
 
162
        echo "Empty key expiration" >&2
 
163
        exit 1
 
164
    fi
 
165
    
 
166
    # Make FORCE be 0 or 1
 
167
    case "$FORCE" in
 
168
        [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) FORCE=1;;
 
169
        [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) FORCE=0;;
 
170
    esac
 
171
    
 
172
    if { [ -e "$SECKEYFILE" ] || [ -e "$PUBKEYFILE" ] \
 
173
             || [ -e "$TLS_PRIVKEYFILE" ] \
 
174
             || [ -e "$TLS_PUBKEYFILE" ]; } \
 
175
        && [ "$FORCE" -eq 0 ]; then
 
176
        echo "Refusing to overwrite old key files; use --force" >&2
 
177
        exit 1
 
178
    fi
 
179
    
 
180
    # Set lines for GnuPG batch file
 
181
    if [ -n "$KEYCOMMENT" ]; then
 
182
        KEYCOMMENTLINE="Name-Comment: $KEYCOMMENT"
 
183
    fi
 
184
    if [ -n "$KEYEMAIL" ]; then
 
185
        KEYEMAILLINE="Name-Email: $KEYEMAIL"
 
186
    fi
 
187
    
 
188
    # Create temporary gpg batch file
 
189
    BATCHFILE="`mktemp -t mandos-keygen-batch.XXXXXXXXXX`"
 
190
fi
 
191
 
 
192
if [ "$mode" = password ]; then
 
193
    # Create temporary encrypted password file
 
194
    SECFILE="`mktemp -t mandos-keygen-secfile.XXXXXXXXXX`"
 
195
fi
 
196
 
 
197
# Create temporary key ring directory
 
198
RINGDIR="`mktemp -d -t mandos-keygen-keyrings.XXXXXXXXXX`"
 
199
 
 
200
# Remove temporary files on exit
 
201
trap "
 
202
set +e; \
 
203
test -n \"$SECFILE\" && shred --remove \"$SECFILE\"; \
 
204
shred --remove \"$RINGDIR\"/sec* 2>/dev/null;
 
205
test -n \"$BATCHFILE\" && rm --force \"$BATCHFILE\"; \
 
206
rm --recursive --force \"$RINGDIR\";
 
207
tty --quiet && stty echo; \
 
208
" EXIT
 
209
 
 
210
set -e
 
211
 
 
212
umask 077
 
213
 
 
214
if [ "$mode" = keygen ]; then
 
215
    # Create batch file for GnuPG
 
216
    cat >"$BATCHFILE" <<-EOF
 
217
        Key-Type: $KEYTYPE
 
218
        Key-Length: $KEYLENGTH
 
219
        Key-Usage: sign,auth
 
220
        Subkey-Type: $SUBKEYTYPE
 
221
        Subkey-Length: $SUBKEYLENGTH
 
222
        Subkey-Usage: encrypt
 
223
        Name-Real: $KEYNAME
 
224
        $KEYCOMMENTLINE
 
225
        $KEYEMAILLINE
 
226
        Expire-Date: $KEYEXPIRE
 
227
        #Preferences: <string>
 
228
        #Handle: <no-spaces>
 
229
        #%pubring pubring.gpg
 
230
        #%secring secring.gpg
 
231
        %no-protection
 
232
        %commit
 
233
        EOF
 
234
    
 
235
    if tty --quiet; then
 
236
        cat <<-EOF
 
237
        Note: Due to entropy requirements, key generation could take
 
238
        anything from a few minutes to SEVERAL HOURS.  Please be
 
239
        patient and/or supply the system with more entropy if needed.
 
240
        EOF
 
241
        echo -n "Started: "
 
242
        date
 
243
    fi
 
244
    
 
245
    # Backup any old key files
 
246
    if cp --backup=numbered --force "$TLS_PRIVKEYFILE" "$TLS_PRIVKEYFILE" \
 
247
        2>/dev/null; then
 
248
        shred --remove "$TLS_PRIVKEYFILE"
 
249
    fi
 
250
    if cp --backup=numbered --force "$TLS_PUBKEYFILE" "$TLS_PUBKEYFILE" \
 
251
        2>/dev/null; then
 
252
        rm --force "$TLS_PUBKEYFILE"
 
253
    fi
 
254
 
 
255
    ## Generate TLS private key
 
256
 
 
257
    # First try certtool from GnuTLS
 
258
    if ! certtool --generate-privkey --password='' \
 
259
         --outfile "$TLS_PRIVKEYFILE" --sec-param ultra \
 
260
         --key-type="$TLS_KEYTYPE" --pkcs8 --no-text 2>/dev/null; then
 
261
        # Otherwise try OpenSSL
 
262
        if ! openssl genpkey -algorithm X25519 -out \
 
263
             /etc/keys/mandos/tls-privkey.pem; then
 
264
            rm --force /etc/keys/mandos/tls-privkey.pem
 
265
            # None of the commands succeded; give up
 
266
            return 1
 
267
        fi
 
268
    fi
 
269
 
 
270
    ## TLS public key
 
271
 
 
272
    # First try certtool from GnuTLS
 
273
    if ! certtool --password='' --load-privkey="$TLS_PRIVKEYFILE" \
 
274
         --outfile="$TLS_PUBKEYFILE" --pubkey-info --no-text \
 
275
         2>/dev/null; then
 
276
        # Otherwise try OpenSSL
 
277
        if ! openssl pkey -in "$TLS_PRIVKEYFILE" \
 
278
             -out "$TLS_PUBKEYFILE" -pubout; then
 
279
            rm --force "$TLS_PUBKEYFILE"
 
280
            # None of the commands succeded; give up
 
281
            return 1
 
282
        fi
 
283
    fi
 
284
    
 
285
    # Make sure trustdb.gpg exists;
 
286
    # this is a workaround for Debian bug #737128
 
287
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
288
        --homedir "$RINGDIR" \
 
289
        --import-ownertrust < /dev/null
 
290
    # Generate a new key in the key rings
 
291
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
292
        --homedir "$RINGDIR" --trust-model always \
 
293
        --gen-key "$BATCHFILE"
 
294
    rm --force "$BATCHFILE"
 
295
    
 
296
    if tty --quiet; then
 
297
        echo -n "Finished: "
 
298
        date
 
299
    fi
 
300
    
 
301
    # Backup any old key files
 
302
    if cp --backup=numbered --force "$SECKEYFILE" "$SECKEYFILE" \
 
303
        2>/dev/null; then
 
304
        shred --remove "$SECKEYFILE"
 
305
    fi
 
306
    if cp --backup=numbered --force "$PUBKEYFILE" "$PUBKEYFILE" \
 
307
        2>/dev/null; then
 
308
        rm --force "$PUBKEYFILE"
 
309
    fi
 
310
    
 
311
    FILECOMMENT="Mandos client key for $KEYNAME"
 
312
    if [ "$KEYCOMMENT" != "$KEYCOMMENT_ORIG" ]; then
 
313
        FILECOMMENT="$FILECOMMENT ($KEYCOMMENT)"
 
314
    fi
 
315
    
 
316
    if [ -n "$KEYEMAIL" ]; then
 
317
        FILECOMMENT="$FILECOMMENT <$KEYEMAIL>"
 
318
    fi
 
319
    
 
320
    # Export key from key rings to key files
 
321
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
322
        --homedir "$RINGDIR" --armor --export-options export-minimal \
 
323
        --comment "$FILECOMMENT" --output "$SECKEYFILE" \
 
324
        --export-secret-keys
 
325
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
326
        --homedir "$RINGDIR" --armor --export-options export-minimal \
 
327
        --comment "$FILECOMMENT" --output "$PUBKEYFILE" --export
 
328
fi
 
329
 
 
330
if [ "$mode" = password ]; then
 
331
    
 
332
    # Make SSH be 0 or 1
 
333
    case "$SSH" in
 
334
        [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) SSH=1;;
 
335
        [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|*) SSH=0;;
 
336
    esac
 
337
    
 
338
    if [ $SSH -eq 1 ]; then
 
339
        for ssh_keytype in ecdsa-sha2-nistp256 ed25519 rsa; do
 
340
            set +e
 
341
            ssh_fingerprint="`ssh-keyscan -t $ssh_keytype localhost 2>/dev/null`"
 
342
            err=$?
 
343
            set -e
 
344
            if [ $err -ne 0 ]; then
 
345
                ssh_fingerprint=""
 
346
                continue
 
347
            fi
 
348
            if [ -n "$ssh_fingerprint" ]; then
 
349
                ssh_fingerprint="${ssh_fingerprint#localhost }"
 
350
                break
 
351
            fi
 
352
        done
 
353
    fi
 
354
    
 
355
    # Import key into temporary key rings
 
356
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
357
        --homedir "$RINGDIR" --trust-model always --armor \
 
358
        --import "$SECKEYFILE"
 
359
    gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
360
        --homedir "$RINGDIR" --trust-model always --armor \
 
361
        --import "$PUBKEYFILE"
 
362
    
 
363
    # Get fingerprint of key
 
364
    FINGERPRINT="`gpg --quiet --batch --no-tty --no-options \
 
365
        --enable-dsa2 --homedir "$RINGDIR" --trust-model always \
 
366
        --fingerprint --with-colons \
 
367
        | sed --quiet \
 
368
        --expression='/^fpr:/{s/^fpr:.*:\\([0-9A-Z]*\\):\$/\\1/p;q}'`"
 
369
    
 
370
    test -n "$FINGERPRINT"
 
371
    
 
372
    KEY_ID="$(certtool --key-id --hash=sha256 \
 
373
                       --infile="$TLS_PUBKEYFILE" 2>/dev/null || :)"
 
374
 
 
375
    if [ -z "$KEY_ID" ]; then
 
376
        KEY_ID=$(openssl pkey -pubin -in /tmp/tls-pubkey.pem \
 
377
                         -outform der \
 
378
                     | openssl sha256 \
 
379
                     | sed --expression='s/^.*[^[:xdigit:]]//')
 
380
    fi
 
381
    test -n "$KEY_ID"
 
382
    
 
383
    FILECOMMENT="Encrypted password for a Mandos client"
 
384
    
 
385
    while [ ! -s "$SECFILE" ]; do
 
386
        if [ -n "$PASSFILE" ]; then
 
387
            cat "$PASSFILE"
 
388
        else
 
389
            tty --quiet && stty -echo
 
390
            echo -n "Enter passphrase: " >/dev/tty
 
391
            read -r first
 
392
            tty --quiet && echo >&2
 
393
            echo -n "Repeat passphrase: " >/dev/tty
 
394
            read -r second
 
395
            if tty --quiet; then
 
396
                echo >&2
 
397
                stty echo
 
398
            fi
 
399
            if [ "$first" != "$second" ]; then
 
400
                echo "Passphrase mismatch" >&2
 
401
                touch "$RINGDIR"/mismatch
 
402
            else
 
403
                echo -n "$first"
 
404
            fi
 
405
        fi | gpg --quiet --batch --no-tty --no-options --enable-dsa2 \
 
406
            --homedir "$RINGDIR" --trust-model always --armor \
 
407
            --encrypt --sign --recipient "$FINGERPRINT" --comment \
 
408
            "$FILECOMMENT" > "$SECFILE"
 
409
        if [ -e "$RINGDIR"/mismatch ]; then
 
410
            rm --force "$RINGDIR"/mismatch
 
411
            if tty --quiet; then
 
412
                > "$SECFILE"
 
413
            else
 
414
                exit 1
 
415
            fi
 
416
        fi
 
417
    done
 
418
    
 
419
    cat <<-EOF
 
420
        [$KEYNAME]
 
421
        host = $KEYNAME
 
422
        key_id = $KEY_ID
 
423
        fingerprint = $FINGERPRINT
 
424
        secret =
 
425
        EOF
 
426
    sed --quiet --expression='
 
427
        /^-----BEGIN PGP MESSAGE-----$/,/^-----END PGP MESSAGE-----$/{
 
428
            /^$/,${
 
429
                # Remove 24-bit Radix-64 checksum
 
430
                s/=....$//
 
431
                # Indent four spaces
 
432
                /^[^-]/s/^/    /p
 
433
            }
 
434
        }' < "$SECFILE"
 
435
    if [ -n "$ssh_fingerprint" ]; then
 
436
        echo 'checker = ssh-keyscan -t '"$ssh_keytype"' %%(host)s 2>/dev/null | grep --fixed-strings --line-regexp --quiet --regexp=%%(host)s" %(ssh_fingerprint)s"'
 
437
        echo "ssh_fingerprint = ${ssh_fingerprint}"
 
438
    fi
 
439
fi
 
440
 
 
441
trap - EXIT
 
442
 
 
443
set +e
 
444
# Remove the password file, if any
 
445
if [ -n "$SECFILE" ]; then
 
446
    shred --remove "$SECFILE"
 
447
fi
 
448
# Remove the key rings
 
449
shred --remove "$RINGDIR"/sec* 2>/dev/null
 
450
rm --recursive --force "$RINGDIR"