summarylogtreecommitdiffstats
path: root/nannycam.functions
blob: e8e65ea78ac8b2892ae3d6c6e56d32f390ae9c04 (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
#
# 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 
}