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
|
#
# Shared Functions for nannycam and the associated install hook
#
ensure_initramfs_environment() {
# Check if running outside the initramfs environment
if ! type resolve_device &>/dev/null; then
# Setup enough of the early userspace environment that resolve_device works
source /usr/lib/initcpio/init_functions
udevd_running=$(ps aux | grep udevd | grep -v grep | wc -l)
rootdelay=1
fi
}
ensure_mkcpinitio_environment() {
# This script is expected to be called from mkinitcpio, if not...
if [ -z ${BUILDROOT:-} ]; then
# ...then mock out enough of the environment to enable testing
saveOpts=$(set +o | egrep 'xtrace|errexit|nounset')
saveGlob=$(shopt -p | grep extglob)
shopt -s extglob
set +e
set +u
set +x
. "/usr/lib/initcpio/functions"
BUILDROOT=$(initialize_buildroot $(uname -r) $(mktemp -d --tmpdir mkinitcpio.XXXXXX))
_optgenimg=$(find /boot -name '*.img' 2>/dev/null | head -n 1)
_optquiet=1
eval "$saveOpts"
eval "$saveGlob"
fi
}
nannycam_usage () {
cat <<HELPEOF
nannycam -k keyfile -m hash -p hash [-e hash]
-k Signing Key
-m Expected MBR hash
-p Expected Post-MBR Gap hash
-e Expected EFI Stub hash
nannycam helps protect against Evil Maid attacks against encrypted boot partitions.
First, nannycam hashes the MBR, Post-MBR gap, and (optionally) the named EFI stub
and compares them against expected values. Next, nannycam uses a private key to
sign the current date and time. It is expected that this key be stored inside the
encrypted boot partition. The signature is displayed as a QR code so that another
device can verify that the signature is valid and the date/time signed is recent.
If any of the hashes do not match, the boot process is paused. An error message is
displayed advising the user NOT to enter their root device encryption passphrase.
If the user feels there has been a misconfiguration, the user can continue the boot
sequence (potentially exposing their root encryption passphrase in the process).
The user must type (in uppercase) YES to continue the boot sequence after verifying
the date/time signature. If the signature does not validate, it is possible that
the entire encrypted boot partition has been replaced with one that was crafted to
appear similar but might record and/or transmit the root encryption passphrase.
Presumably, if the encryption of the boot partition is secure, then the key stored
inside the encrypted boot partition cannot be known by an attacker. This prevents
wholesale replacement of the entire boot partition. Hashes taken of the MBR, Post-
MBR Gap, and EFI Stub assist in protecting against attackers replacing them in an
attempt to record the boot partition's encryption passphrase and subsequently
extracting the private key material used to authenticate the boot partition.
nannycam does not prevent all Evil Maid attacks. It is of course possible for state
actors to launch hardware-level attacks, but even less powerful adversaries may be
clever enough to thwart these protections.
HELPEOF
}
err_required_arg () {
echo "-$1 is a required option" >&2
exit 1
}
hash_mismatch () {
(cat <<WARNEOF
*****************************************************************
* WARNING: Unexpected hash. Do NOT enter root device passphrase *
*****************************************************************
There was a mismatch in the expected and actual hash values for
critical boot programs. Either a misconfiguration has occurred or
a malicious actor has modified one or more of these programs. It
is advised that you restore the MBR, Post MBR Gap, and (if using
EFI) the EFI Stub from secure backups. Do NOT enter your root
device passphrase unless you are certain this is a
misconfiguration.
Hashing algorithm: $HASH_ALG
MBR (expected) $EXPECTED_MBR_HASH
MBR (actual) $ACTUAL_MBR_HASH
MBR Gap (expected) $EXPECTED_MBR_GAP_HASH
MBR Gap (actual) $ACTUAL_MBR_GAP_HASH
EFI Stub (expected) $EXPECTED_EFI_STUB_HASH
EFI Stub (actual) $ACTUAL_EFI_STUB_HASH
WARNEOF
) >&2
local response=""
while [[ "$response" != "YES" ]]; do
read -p "Enter YES to continue booting (not recommended): " response
done
}
determine_mbr_boot_device () {
local possibleDevices=$(parted -s -m -l \
| sed -e 's/^$/\x00/' \
| tr -d '\n' \
| tr '\0' '\n' \
| egrep ';([^:]*:){6}boot' \
| cut -f 2 -d ';' \
| cut -f 1 -d ':')
[ $(echo "$possibleDevices" | wc -l) -eq 1 ] \
|| ( echo "Expected exactly one partition with boot flag set, aborting." >&2; exit 5 )
echo "$possibleDevices"
}
hash_mbr () {
local mbrDevice=$(determine_mbr_boot_device)
dd if="$mbrDevice" of=/tmp/mbr.bin bs=512 count=1 &>/dev/null
ACTUAL_MBR_HASH="$(openssl dgst -$HASH_ALG /tmp/mbr.bin | cut -f 2 -d ' ')"
rm /tmp/mbr.bin
}
check_mbr () {
hash_mbr
[[ "$EXPECTED_MBR_HASH" == "$ACTUAL_MBR_HASH" ]]
}
hash_mbr_gap () {
local mbrDevice=$(determine_mbr_boot_device)
local part_start=$(parted -s -m "$mbrDevice" unit b print | egrep '^1:' | cut -f 2 -d ':' | tr -d 'Bb')
local blocks=$(( part_start / 512 ))
local check=$(( $blocks * 512 ))
[ $part_start -eq $check ] || ( echo "Partition doesn't start at 512 byte boundary! Aborting." >&2; exit 3 )
dd if="$mbrDevice" of=/tmp/gap.bin bs=512 skip=1 count=$blocks &> /dev/null
ACTUAL_MBR_GAP_HASH="$(openssl dgst -$HASH_ALG /tmp/gap.bin | cut -f 2 -d ' ')"
rm /tmp/gap.bin
}
check_mbr_gap () {
hash_mbr_gap
[[ "$EXPECTED_MBR_GAP_HASH" == "$ACTUAL_MBR_GAP_HASH" ]]
}
check_already_mounted () {
local device="$1"
echo $(mount | grep "^$device on" | cut -f 3 -d ' ')
}
hash_efi_stub () {
# Don't bother checking efi stub if booting in MBR mode
if ! mount | grep efivarfs &>/dev/null; then
return 0
fi
# Determine which EFI stub was used
local bootinfo="$(efibootmgr -v)"
local currentNum=$(echo "$bootinfo" | egrep '^BootCurrent:' | cut -f 2 -d ' ')
local current=$(echo "$bootinfo" | grep "^Boot$currentNum\*")
# TODO: Support other boot devices other than ESP System Partitions
local partitionAndPath=$( echo "$current" | \
sed -nre 's_^.*HD\([0-9]+,GPT,([^,]{36}),[^)]+\)/File\(([^)]+)\)_\1\2_p')
local partitionUUID=$(echo "$partitionAndPath" | head -c 36 )
# TODO: Determine how escaped paths work with efibootmgr
local path=$(echo "$partitionAndPath" | tail -c +37 | tr '\\' '/' )
local mountDevice=$(resolve_device PARTUUID=$partitionUUID)
local mountPoint=$(check_already_mounted "$mountDevice")
if [ -z "$mountPoint" ]; then
mountPath="/tmp/efi"
mount $mountDevice $mountPoint
fi
ACTUAL_EFI_STUB_HASH=$(openssl dgst -$HASH_ALG "$mountPoint$path" | cut -f 2 -d ' ')
}
check_efi_stub () {
hash_efi_stub
[[ "$EXPECTED_EFI_STUB_HASH" == "$ACTUAL_EFI_STUB_HASH" ]]
}
assert_root() {
if [[ "0" != "$(id -u)" ]]; then
echo "Must be run as root." >&2
exit 4
fi
}
assert_ephemeral() {
fsType=$(df "$1" | tail -n 1 | cut -f 1 -d ' ')
if [[ "tmpfs" != "$fsType" ]]; then
(cat <<TMPWARN
"$1" is not on an ephemeral file system. Cowardly aborting in order to avoid
leaking the private key that will authenticate the encrypted boot device.
TMPWARN
) >&2
exit 1
fi
}
assert_encrypted() {
fsMnt=$(df "$1" | tail -n 1 | egrep -o ' [^ ]+$' | tail -c +2)
isCrypt=$(lsblk -ro TYPE,MOUNTPOINT | egrep "$fsMnt$" | egrep '^crypt' | wc -l)
if [ ! $isCrypt -eq 1 ]; then
(cat <<DESTWARN
Destination location for the initramfs image is not on an encrypted device.
The nannycam software can only protect against Evil Maid style attacks if
the initramfs (and therefore the authentication key) is stored inside an
encrypted boot partition. Cowardly aborting in order to avoid leaking the
private key.
Image location: $_optgenimg
DESTWARN
) >&2
exit 2
fi
}
|