summaryrefslogtreecommitdiffstats
path: root/debian/initramfs/hooks/cryptroot
blob: c16f7c2c94f38210691e1aa678e4db1bf59815e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#!/bin/sh

PREREQ=""

prereqs()
{
    echo "$PREREQ"
}

case "$1" in
    prereqs)
        prereqs
        exit 0
        ;;
esac

. /usr/share/initramfs-tools/hook-functions
. /lib/cryptsetup/functions
TABFILE="/etc/crypttab"


# crypttab_find_and_print_entry($target)
#   Find the crypttab(5) entry for the given (unmangled) $target and
#   print it - preserving the mangling - to FD nr. 3; but only if the
#   target has not already been processed during an earlier function
#   call.  (Processed target names are stored in
#   $DESTDIR/cryptroot/targets.)
#   Return 0 on success, 1 on error.
crypttab_find_and_print_entry() {
    local target="$1"
    local _CRYPTTAB_NAME _CRYPTTAB_SOURCE _CRYPTTAB_KEY _CRYPTTAB_OPTIONS
    if ! grep -Fxqz -e "$target" -- "$DESTDIR/cryptroot/targets"; then
        printf '%s\0' "$target" >>"$DESTDIR/cryptroot/targets"
        crypttab_find_entry "$target" || return 1
        crypttab_parse_options --missing-path=warn || return 1
        crypttab_print_entry
    fi
}

# crypttab_print_entry()
#   Print an unmangled crypttab(5) entry to FD nr. 3, using CRYPTTAB_*
#   and _CRYPTTAB_* values.
#   _CRYPTTAB_SOURCE is replaced with UUID=<uuid> if possible (eg, for
#   LUKS), unless the value starts with /dev/disk/by- or /dev/mapper/,
#   or is already a device specification (such as LABEL= or PARTUUID=).
#   If the entry uses the 'decrypt_derived' keyscript, the other
#   crypttab(5) entries it depends on are (recursively) printed before
#   hand.
#   Various checks are performed on the key and crypttab options, but no
#   parsing is done so it's the responsibility of the caller to call
#   crypttab_parse_options().
#   Return 0 on success, 1 on error.
crypttab_print_entry() {
    local DEV MAJ MIN uuid keyfile
    if _resolve_device "$CRYPTTAB_SOURCE"; then
        if [ "$(dmsetup info -c --noheadings -o devnos_used -- "$CRYPTTAB_NAME" 2>/dev/null)" != "$MAJ:$MIN" ]; then
            cryptsetup_message "ERROR: $CRYPTTAB_NAME: Source mismatch"
        elif [ "${_CRYPTTAB_SOURCE#[A-Za-z]*=}" = "$_CRYPTTAB_SOURCE" ] && \
                [ "${CRYPTTAB_SOURCE#/dev/disk/by-}" = "$CRYPTTAB_SOURCE" ] && \
                [ "${CRYPTTAB_SOURCE#/dev/mapper/}" = "$CRYPTTAB_SOURCE" ] && \
                uuid="$(_device_uuid "$DEV")"; then
            _CRYPTTAB_SOURCE="UUID=$uuid"
        fi
        # on failure _resolve_device() prints a warning and we try our
        # luck with the unchanged _CRYPTTAB_SOURCE value
    fi

    # if keyscript is set, the "key" is just an argument to the script
    if [ -z "${CRYPTTAB_OPTION_keyscript+x}" ] && [ "$CRYPTTAB_KEY" != "none" ]; then
        crypttab_key_check || return 1
        case "$CRYPTTAB_KEY" in
            $KEYFILE_PATTERN)
                mkdir -pm0700 -- "$DESTDIR/cryptroot/keyfiles"
                # $CRYPTTAB_NAME can't contain '/' (even after unmangling)
                keyfile="/cryptroot/keyfiles/$CRYPTTAB_NAME.key"
                if [ ! -f "$DESTDIR$keyfile" ] && ! copy_file keyfile "$CRYPTTAB_KEY" "$keyfile"; then
                    cryptsetup_message "WARNING: couldn't copy keyfile $CRYPTTAB_KEY"
                fi
                _CRYPTTAB_KEY="/cryptroot/keyfiles/$_CRYPTTAB_NAME.key" # preserve mangled name
                ;;
            *)
                if [ "$usage" = rootfs ]; then
                    cryptsetup_message "WARNING: Skipping root target $CRYPTTAB_NAME: uses a key file"
                    return 1
                elif [ "$usage" = resume ]; then
                    cryptsetup_message "WARNING: Resume target $CRYPTTAB_NAME uses a key file"
                fi
                if [ -L "$CRYPTTAB_KEY" ] && keyfile="$(readlink -- "$CRYPTTAB_KEY")" &&
                        [ "${keyfile#/}" != "$keyfile" ]; then
                    cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is a symlink with absolute target"
                    return 1
                elif [ -f "$CRYPTTAB_KEY" ] && [ "$(stat -L -c"%m" -- "$CRYPTTAB_KEY" 2>/dev/null)" != "/" ]; then
                    cryptsetup_message "WARNING: Skipping target $CRYPTTAB_NAME: key file is not on the root FS"
                    return 1
                fi
                if [ ! -e "$CRYPTTAB_KEY" ]; then
                    cryptsetup_message "WARNING: Target $CRYPTTAB_NAME has a non-existing key file $CRYPTTAB_KEY"
                else
                    _CRYPTTAB_KEY="/FIXME-initramfs-rootmnt$_CRYPTTAB_KEY" # preserve mangled name
                fi
        esac
    fi

    if [ -n "${CRYPTTAB_OPTION_keyscript+x}" ]; then
        copy_exec "$CRYPTTAB_OPTION_keyscript"
    elif [ "$CRYPTTAB_KEY" = "none" ]; then
        ASKPASS="y"
    fi
    if [ "${CRYPTTAB_OPTION_keyscript-}" = "/lib/cryptsetup/scripts/decrypt_derived" ]; then
        # (recursively) list first the device to derive the key from (so
        # the boot scripts unlock it first); since _CRYPTTAB_* are local
        # to crypttab_find_and_print_entry() the new value won't
        # override the new ones
        crypttab_find_and_print_entry "$CRYPTTAB_KEY"
    fi
    printf '%s %s %s %s\n' \
        "$_CRYPTTAB_NAME" "$_CRYPTTAB_SOURCE" "$_CRYPTTAB_KEY" "$_CRYPTTAB_OPTIONS" >&3
}

# get_resume_devno()
#   Return the device ID(s) used for system suspend/hibernate.
get_resume_devno() {
    local dev filename

    # uswsusp
    for filename in /etc/uswsusp.conf /etc/suspend.conf; do
        [ -e "$filename" ] || continue
        dev="$(sed -nr '/^resume device\s*[:=]\s*/ {s///p;q}' "$filename")"
        if [ -n "$dev" ] && [ "$dev" != "<path_to_resume_device_file>" ]; then
            # trim quotes
            dev="$(printf '%s' "$dev" | sed -re 's/^"(.*)"\s*$/\1/' -e "s/^'(.*)'\\s*$/\\1/")"
            _print_devno "$(printf '%b' "$dev")" # unmangle
        fi
    done

    # regular swsusp
    dev="$(sed -nr 's,^(.*\s)?resume=(\S+)(\s.*)?$,\2,p' /proc/cmdline)"
    _print_devno "$(printf '%b' "$dev")" # unmangle

    # initramfs-tools >=0.129
    dev="${RESUME:-auto}"
    if [ "$dev" != none ]; then
        if [ "$dev" = auto ]; then
            # next line from /usr/share/initramfs-tools/hooks/resume
            dev="$(grep ^/dev/ /proc/swaps | sort -rnk3 | head -n 1 | cut -d " " -f 1)"
        fi
        _print_devno "$(printf '%b' "$dev")" # unmangle
    fi
}
_print_devno() {
    local DEV MAJ MIN # locally scope the 3 variables _resolve_device() sets
    if [ -n "$1" ] && _resolve_device "$1"; then
        printf '%d:%d\n' "$MAJ" "$MIN"
    fi
}

# crypttab_print_initramfs_entry()
#   Print a crypttab(5) entry - unless it was already processed - if it
#   has the 'initramfs' option set.
crypttab_print_initramfs_entry() {
    local usage=
    if ! grep -Fxqz -e "$CRYPTTAB_NAME" -- "$DESTDIR/cryptroot/targets" &&
            crypttab_parse_options --quiet &&
            [ "${CRYPTTAB_OPTION_initramfs-no}" = "yes" ]; then
        printf '%s\0' "$CRYPTTAB_NAME" >>"$DESTDIR/cryptroot/targets"
        crypttab_print_entry
    fi
}

# generate_initrd_crypttab()
#   Generate the crypttab(5) snippet that is relevant at initramfs
#   stage.  (Devices that aren't required at initramfs stage are
#   ignored.)
generate_initrd_crypttab() {
    local devnos usage IFS="$(printf '\t\n ')"
    mkdir -- "$DESTDIR/cryptroot"
    true >"$DESTDIR/cryptroot/targets"

    {
        if devnos="$(get_mnt_devno /)"; then
            usage=rootfs foreach_cryptdev crypttab_find_and_print_entry $devnos
        else
            cryptsetup_message "WARNING: Couldn't determine root device"
        fi

        if devnos="$(get_resume_devno)"; then
            usage=resume foreach_cryptdev crypttab_find_and_print_entry $devnos
        fi

        if devnos="$(get_mnt_devno /usr)"; then
            usage="" foreach_cryptdev crypttab_find_and_print_entry $devnos
        fi

        # add crypttab entries with the 'initramfs' option set
        crypttab_foreach_entry crypttab_print_initramfs_entry
    } 3>"$DESTDIR/cryptroot/crypttab"
    rm -f "$DESTDIR/cryptroot/targets"
}

# populate_CRYPTO_HASHES()
#   Find out which crypto hashes are required for a crypttab(5) entry,
#   and append them to the CRYPTO_HASHES variable.
populate_CRYPTO_HASHES() {
    local hash source newline="
"

    if crypttab_parse_options --quiet && [ -n "${CRYPTTAB_OPTION_header+x}" ]; then
        source="$CRYPTTAB_OPTION_header"
    else
        source="$(_resolve_device_spec "$CRYPTTAB_SOURCE")" || source=""
    fi

    if [ ! -e "$source" ]; then
        # missing source device or detached header, can't determine hashing function(s)
        hash="@@UNKNOWN@@"
    elif [ "$CRYPTTAB_TYPE" = "luks" ]; then
        # using --dump-json-metadata would be more robust for LUKS2 but
        # we also have to support LUKS1 hence have to parse luksDump output
        hash="$(/sbin/cryptsetup luksDump -- "$source" | sed -nr 's/^\s*(AF hash|Hash|Hash spec)\s*:\s*//Ip')"
    elif [ "$CRYPTTAB_TYPE" = "plain" ]; then
        # --hash is being ignored when opening via key file
        if [ "$CRYPTTAB_KEY" = "none" ] && [ -z "${CRYPTTAB_OPTION_keyscript+x}" ]; then
            hash="${CRYPTTAB_OPTION_hash-ripemd160}" # default password hashing as of cryptsetup 2.5
        fi
    else
        hash="" # or hash="@@UNKNOWN@@"?
    fi

    if [ -n "$hash" ]; then
        CRYPTO_HASHES="${CRYPTO_HASHES:+$CRYPTO_HASHES$newline}$hash"
    fi
}

# populate_CRYPTO_MODULES()
#   Find out which crypto modules are required for a crypttab(5) entry,
#   and append them to the CRYPTO_MODULES variable.
populate_CRYPTO_MODULES() {
    local cipher iv

    # cf. dmsetup(8) and https://gitlab.com/cryptsetup/cryptsetup/wikis/DMCrypt
    cipher="$(dmsetup table --target crypt -- "$CRYPTTAB_NAME" | cut -d' ' -f4)"
    if [ -z "$cipher" ]; then
        cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME"
    elif [ "${cipher#capi:}" = "$cipher" ]; then
        # direct specification "cipher[:keycount]-chainmode-ivmode[:ivopts]"
        CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%%[-:]*}" # cipher
        cipher="${cipher#"${cipher%%-*}-"}" # chainmode-ivmode[:ivopts]"
        CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${cipher%-*}" # chainmode
        iv="${cipher##*-}" # ivmode[:ivopts]"
        CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv%%:*}" # ivmode
        if [ "${iv#*:}" != "$iv" ]; then
            CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }${iv#*:}" # ivopts
        fi
    else
        # kernel crypto API format "capi:cipher_api_spec-ivmode[:ivopts]", since linux 4.12
        cipher="${cipher#capi:}"
        cryptsetup_message "WARNING: Couldn't determine cipher modules to load for $CRYPTTAB_NAME" \
            "(kernel crypto API format isn't supported yet)"
    fi
}

# add_modules($glob, $moduledir, [$moduledir ..])
#   Add modules matching under the given $moduledir(s), the name of
#   which matching $glob.
#   Return 0 if any module was found found, 1 if not.
add_modules() {
    local glob="$1" found=n
    shift
    for mod in $(find -H "$@" -name "$glob.ko*" -type f -printf '%f\n'); do
        manual_add_modules "${mod%%.*}"
        found=y
    done
    [ "$found" = y ] && return 0 || return 1
}

# add_crypto_modules($name, [$name ..])
#   Determine kernel module name and add to initramfs.
add_crypto_modules() {
    local mod
    for mod in "$@"; do
        # We have several potential sources of modules (in order of preference):
        #
        #   a) /lib/modules/$VERSION/kernel/arch/$ARCH/crypto/$mod-$specific.ko
        #   b) /lib/modules/$VERSION/kernel/crypto/$mod_generic.ko
        #   c) /lib/modules/$VERSION/kernel/crypto/$mod.ko
        #
        # and (currently ignored):
        #
        #   d) /lib/modules/$VERSION/kernel/drivers/crypto/$specific-$mod.ko
        add_modules "$mod-*" "$MODULESDIR"/kernel/arch/*/crypto || true
        add_modules "${mod}_generic" "$MODULESDIR/kernel/crypto" \
            || add_modules "$mod" "$MODULESDIR/kernel/crypto" \
            || true
    done
}

# copy_libssl_legacy_library()
#   Copy ossl-modules/legacy.so (from libssl library) to initramfs if needed.
#   OpenSSL 3.0 moved support for some crypto hashes into legacy.so.
#   See https://launchpad.net/bugs/1979159
copy_libssl_legacy_library() {
    local libcryptodir CRYPTO_HASHES=""

    libcryptodir="$(env --unset=LD_PRELOAD ldd /sbin/cryptsetup | sed -nr '/.*=>\s*(\S+)\/libcrypto\.so\..*/ {s//\1/p;q}')"
    [ -d "$libcryptodir" ] || return

    crypttab_foreach_entry populate_CRYPTO_HASHES
    # See https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html#Hashing-Algorithms-Message-Digests
    if printf '%s\n' "$CRYPTO_HASHES" | grep -Fxq -e @@UNKNOWN@@ -e ripemd160 -e whirlpool; then
        # legacy hashes are used so legacy.so needs to be copied to the initramfs
        # (assume ossl-modules/legacy.so is relative to the linked libcrypto.so)
        copy_exec "$libcryptodir/ossl-modules/legacy.so" || true
    fi
}

# See #1032221: newer libargon2 are built with glibc ≥2.34 hence no
# longer links libpthread.  This in turns means that initramfs-tool's
# copy_exec() is no longer able to detect pthread_*() need and thus
# doesn't copy libgcc_s.so anymore.  So we need to do it manually
# instead.
copy_libgcc_argon2() {
    local libdir rv=0
    libdir="$(env --unset=LD_PRELOAD ldd /sbin/cryptsetup | sed -nr '/.*=>\s*(\S+)\/libargon2\.so\..*/ {s//\1/p;q}')"
    copy_libgcc "$libdir" || rv=$?
    if [ $rv -ne 0 ]; then
        # merged-/usr mismatch, see #1032518
        if [ "${libdir#/usr/}" != "$libdir" ]; then
            libdir="${libdir#/usr}"
        else
            libdir="/usr/${libdir#/}"
        fi
        copy_libgcc "$libdir" && rv=0 || rv=$?
    fi
    return $rv
}


#######################################################################
# Begin real processing

unset -v ASKPASS KEYFILE_PATTERN
ASKPASS="y"
KEYFILE_PATTERN=

# Load the hook config
if [ -f "/etc/cryptsetup-initramfs/conf-hook" ]; then
    . /etc/cryptsetup-initramfs/conf-hook
fi

if [ -n "$KEYFILE_PATTERN" ]; then
    case "${UMASK:-$(umask)}" in
        0[0-7]77) ;;
        *) cryptsetup_message "WARNING: Permissive UMASK (${UMASK:-$(umask)})." \
                "Private key material within the initrd might be left unprotected."
        ;;
    esac
fi

CRYPTO_MODULES=
if [ -r "$TABFILE" ]; then
    generate_initrd_crypttab
    TABFILE="$DESTDIR/cryptroot/crypttab"
    crypttab_foreach_entry populate_CRYPTO_MODULES
    copy_libssl_legacy_library
fi

# add required components
manual_add_modules dm_mod
manual_add_modules dm_crypt

copy_exec /sbin/cryptsetup
copy_exec /sbin/dmsetup
copy_libgcc_argon2

[ "$ASKPASS" = n ] || copy_exec /lib/cryptsetup/askpass

# We need sed. Either via busybox or as standalone binary.
if [ "$BUSYBOX" = n ] || [ -z "$BUSYBOXDIR" ]; then
    copy_exec /bin/sed
fi

# detect whether the host CPU has AES-NI support
if grep -Eq '^flags\s*:(.*\s)?aes(\s.*)?$' /proc/cpuinfo; then
    CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }aesni"
else
    # workaround for #883595/#901884 (xts depends on ecb)
    CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }ecb"
fi

# add userspace crypto module (only required for opening LUKS2 devices
# we add the module unconditionally as it's the default format)
CRYPTO_MODULES="${CRYPTO_MODULES:+$CRYPTO_MODULES }algif_skcipher"

if [ "$MODULES" = most ]; then
    for d in "$MODULESDIR"/kernel/arch/*/crypto; do
        copy_modules_dir "${d#"$MODULESDIR/"}"
    done
    copy_modules_dir "kernel/crypto"
else
    if [ "$MODULES" != "dep" ]; then
        # with large initramfs, we always add a basic subset of modules
        add_crypto_modules aes cbc chainiv cryptomgr krng sha256 xts
    fi
    add_crypto_modules $(printf '%s' "${CRYPTO_MODULES-}" | tr ' ' '\n' | sort -u)
fi
copy_file library /lib/cryptsetup/functions /lib/cryptsetup/functions