Repository: QubesOS/qubes-antievilmaid
Branch: main
Commit: c085d776c499
Files: 24
Total size: 81.9 KB
Directory structure:
gitextract_b_nyytam/
├── .gitignore
├── .gitlab-ci.yml
├── .qubesbuilder
├── 90anti-evil-maid/
│ ├── anti-evil-maid-check-mount-devs
│ ├── anti-evil-maid-unseal
│ ├── hosts
│ └── module-setup.sh
├── Makefile.builder
├── README
├── anti-evil-maid.spec.in
├── etc/
│ ├── anti-evil-maid.conf
│ ├── dracut.conf.d/
│ │ └── anti-evil-maid.conf
│ └── grub.d/
│ └── 19_linux_xen_tboot
├── sbin/
│ ├── anti-evil-maid-install
│ ├── anti-evil-maid-lib
│ ├── anti-evil-maid-lib-tpm1
│ ├── anti-evil-maid-lib-tpm2
│ ├── anti-evil-maid-seal
│ └── anti-evil-maid-tpm-setup
├── systemd/
│ └── system/
│ ├── anti-evil-maid-check-mount-devs.service
│ ├── anti-evil-maid-seal.service
│ ├── anti-evil-maid-unseal.service
│ └── tcsd.service.d/
│ └── anti-evil-maid-seal.conf
└── version
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
pkgs/
================================================
FILE: .gitlab-ci.yml
================================================
checks:shellcheck:
stage: checks
tags:
- docker
script:
- cd sbin/
- shellcheck -s bash * ../etc/anti-evil-maid.conf
- cd ../90anti-evil-maid/
- shellcheck -s bash anti-evil-maid-unseal ../sbin/anti-evil-maid-lib*
include:
- file: /r4.2/gitlab-base.yml
project: QubesOS/qubes-continuous-integration
- file: /r4.2/gitlab-host.yml
project: QubesOS/qubes-continuous-integration
- file: /r4.3/gitlab-base.yml
project: QubesOS/qubes-continuous-integration
- file: /r4.3/gitlab-host.yml
project: QubesOS/qubes-continuous-integration
================================================
FILE: .qubesbuilder
================================================
host:
rpm:
build:
- anti-evil-maid.spec
================================================
FILE: 90anti-evil-maid/anti-evil-maid-check-mount-devs
================================================
#!/bin/bash
# shellcheck disable=SC1091
. /lib/dracut-lib.sh
# this cannot contain -u option because it causes an error inside
# /lib/dracut-lib.sh
set -eo pipefail
shopt -s expand_aliases
function check_device() {
local sysfs_path recursion_limit dm_name dm_target slave
sysfs_path="$1"
recursion_limit="${2:-10}"
if [ -r "$sysfs_path/dm/name" ]; then
dm_name=$(cat "$sysfs_path"/dm/name)
dm_target=$(dmsetup table "$dm_name" | cut -d ' ' -f 3)
# This also ensures that the dm table have only single entry
if [ "$dm_target" = "crypt" ]; then
return 0
elif [ -n "$(ls -A "$sysfs_path"/slaves)" ] && [ "$recursion_limit" -gt 0 ]; then
for slave in "$sysfs_path"/slaves/*; do
if ! check_device "$slave" "$(( recursion_limit - 1 ))"; then
return 1
fi
done
return 0
else
return 1
fi
else
return 1
fi
}
root_name="$(getarg root)"
if echo "$root_name" | grep -q = ; then
root_matches=$(blkid -t "$root_name" | wc -l)
if [ "$root_matches" -gt 1 ]; then
die "AEM: multiple devices matching $root_name found, aborting!"
fi
root_dev=$(blkid -o device -t "$root_name")
else
root_dev=$root_name
fi
root_devid=$(lsblk -dnr -o MAJ:MIN "$root_dev")
if ! check_device /sys/dev/block/"$root_devid"; then
die "AEM: (bogus?) root device found not encrypted!"
fi
for lv in $(getarg rd.lvm.lv); do
if [ -e /dev/"$lv" ]; then
devid=$(lsblk -dnr -o MAJ:MIN /dev/"$lv")
if ! check_device /sys/dev/block/"$devid"; then
die "AEM: (bogus?) device /dev/$lv found not encrypted!"
fi
fi
done
exit 0
================================================
FILE: 90anti-evil-maid/anti-evil-maid-unseal
================================================
#!/bin/bash
set -euo pipefail
shopt -s expand_aliases
# Anti Evil Maid for dracut by Invisible Things Lab
# Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
#
# Mount our device, read the sealed secret blobs, initialize TPM
# and finally try to unseal the secrets and display them to the user
MNT=/anti-evil-maid
UNSEALED_SECRET=/tmp/unsealed-secret
LUKS_HEADER_DUMP=/tmp/luks-header-dump
LUKS_PCR=13
PLYMOUTH_MESSAGES=()
plymouth_message() {
if [ "${#PLYMOUTH_MESSAGES[@]}" -eq 0 ]; then
# add vertical "padding" to avoid printing messages over plymouth's
# prompt help
plymouth message --text=""
plymouth message --text=""
plymouth message --text=""
fi
plymouth message --text="$*"
PLYMOUTH_MESSAGES+=("$*")
}
plymouth_messages_hide() {
for m in "${PLYMOUTH_MESSAGES[@]}"; do
plymouth hide-message --text="$m"
done
}
# shellcheck source=../sbin/anti-evil-maid-lib
. anti-evil-maid-lib
# find AEM device
UUID=$(getparams aem.uuid)
DEV=/dev/disk/by-uuid/$UUID
udevadm trigger
udevadm settle
waitfor -b "$DEV"
n=$(lsblk -nr -o UUID | grep -Fixc "$UUID") || true
if [ "$n" != 1 ]; then
message "Error: found ${n:-?} devices with UUID $UUID"
exit 1
fi
LABEL=$(lsblk -dnr -o LABEL "$DEV")
if [[ "$LABEL" != "$LABEL_PREFIX"* ]]; then
message "AEM boot device $DEV has wrong label: $LABEL"
exit 1
fi
# mount AEM device
log "Mounting $DEV (\"$LABEL\")..."
mkdir -p "$MNT"
mount -t ext4 -o ro "$DEV" "$MNT"
# this way an error prior to unmounting will keep the device usable after initrd
trap 'umount "$MNT"' EXIT
# setup TPM & copy secrets to initrd tmpfs
log "Initializing TPM..."
modprobe tpm_tis
validatetpm || exit 1
ip link set dev lo up
mkdir -p "${TPMS_DIR%/*}"
log "Copying sealed AEM secrets..."
cp -Tr "$MNT/aem/${TPMS_DIR##*/}" "${TPMS_DIR}"
tpmstartinitrdservices
SEALED_SECRET_TXT=$TPM_DIR/$LABEL/secret.txt.sealed2
SEALED_SECRET_KEY=$TPM_DIR/$LABEL/secret.key.sealed2
SEALED_SECRET_OTP=$TPM_DIR/$LABEL/secret.otp.sealed2
SEALED_SECRET_FRE=$TPM_DIR/$LABEL/secret.fre.sealed2
# unmount AEM device
log "Unmounting $DEV (\"$LABEL\")..."
umount "$MNT"
# remove umount trap set after mount
trap - EXIT
if [ "$(blockdev --getro "$DEV")" = 1 ]; then
message "You should now unplug the AEM device if it is intentionally read-only."
fi
# Extend PCR with LUKS header(s)
getluksuuids |
sort -u |
while read -r luksid; do
waitfor -b "/dev/disk/by-uuid/$luksid"
cryptsetup luksHeaderBackup "/dev/disk/by-uuid/$luksid" \
--header-backup-file "$LUKS_HEADER_DUMP"
luks_header_hash=$(hashfile "$LUKS_HEADER_DUMP")
log "Extending PCR $LUKS_PCR, value $luks_header_hash, device $luksid..."
tpmpcrextend "$LUKS_PCR" "$luks_header_hash"
done
# cache suffix and SRK password, if applicable
mkdir -p "$CACHE_DIR"
echo "${LABEL##"$LABEL_PREFIX"}" >"$SUFFIX_CACHE"
Z=$(tpmzsrk)
if [ -n "$Z" ]; then
true >"$SRK_PASSWORD_CACHE"
else
for _ in 1 2 3; do
log "Prompting for SRK password..."
if systemd-ask-password --timeout=0 \
"TPM SRK password to unseal the secret(s)" \
> "$SRK_PASSWORD_CACHE" && checksrkpass; then
log "Correct SRK password"
break
fi
log "Wrong SRK password"
done
fi
# check freshness token
log "Unsealing freshness token..."
if tpmunsealdata "$Z" "$SEALED_SECRET_FRE" "$UNSEALED_SECRET" \
"$TPM_DIR/$LABEL"; then
log "Freshness token unsealed."
true >"$CACHE_DIR/unseal-success"
else
log "Freshness token unsealing failed!"
log "This is expected during the first boot from a particular"
log "AEM media or after updating any of the boot components or"
log "changing their configuration."
exit 1
fi
if checkfreshness "$UNSEALED_SECRET"; then
log "Freshness token valid, continuing."
else
log "Freshness token invalid!"
exit 1
fi
# unseal & show OTP if provisioned
# unseal & decrypt key file unless the user switches to text secret mode
if [ -e "$SEALED_SECRET_OTP" ]; then
alias otp=true
else
alias otp=false
fi
if otp; then
log "Unsealing TOTP shared secret seed..."
if tpmunsealdata "$Z" "$SEALED_SECRET_OTP" "$UNSEALED_SECRET" \
"$TPM_DIR/$LABEL"; then
log "TOTP secret unsealed."
message ""
message "Never type in your key file password unless the code below is correct!"
message ""
seed=$(cat "$UNSEALED_SECRET")
last=
{
trap 'plymouth_messages_hide; exit' TERM
while :; do
now=$(date +%s)
if [ -z "$last" ] ||
{ [ "$((now % 30))" = 0 ] && [ "$last" != "$now" ]; }; then
code=$(oathtool --totp -b "$seed")
message "[ $(date) ] TOTP code: $code"
last=$now
fi
sleep 0.1
done
} &
totp_loop_pid=$!
if tpmunsealdata "$Z" "$SEALED_SECRET_KEY" "$UNSEALED_SECRET" \
"$TPM_DIR/$LABEL"; then
for _ in 1 2 3; do
pass=$(systemd-ask-password --timeout=0 \
'LUKS key file password (or "t" to show text secret)')
if [ "$pass" = "t" ]; then
alias otp=false
break
fi
if scrypt dec -P "$UNSEALED_SECRET" /tmp/aem-keyfile \
<<<"$pass"; then
log "Correct LUKS key file password"
# dracut "90crypt" module will parse the
# rd.luks.key=/tmp/aem-keyfile
# kernel cmdline arg and attempt to use it;
# this file is deleted on root switch
# along with everything in /tmp
break
else
log "Wrong LUKS key file password"
fi
done
fi
kill "$totp_loop_pid"
fi
fi
# unseal text secret
if ! otp; then
log "Unsealing text secret..."
if tpmunsealdata "$Z" "$SEALED_SECRET_TXT" "$UNSEALED_SECRET" \
"$TPM_DIR/$LABEL"; then
{
message ""
message "$(cat "$UNSEALED_SECRET" 2>/dev/null)"
message ""
} 2>&1 # don't put the secret into the journal
message "Never type in your disk password unless the secret above is correct!"
waitforenter
fi
plymouth_messages_hide
clear
fi
# prevent sealing service from starting if user unplugged
# the (supposedly read-only) AEM device
if [ ! -b "$DEV" ]; then
rm -rf "$CACHE_DIR"
fi
================================================
FILE: 90anti-evil-maid/hosts
================================================
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
================================================
FILE: 90anti-evil-maid/module-setup.sh
================================================
#!/bin/bash
check() {
which tpm_unsealdata tpm2_unseal >/dev/null 2>&1 || return 1
}
#depends() {
#}
installkernel() {
instmods tpm_tis tpm_crb
}
install() {
inst_script "$moddir"/anti-evil-maid-unseal /sbin/anti-evil-maid-unseal
inst_script "$moddir"/anti-evil-maid-check-mount-devs /sbin/anti-evil-maid-check-mount-devs
inst $systemdsystemunitdir/cryptsetup-pre.target
dracut_install \
/usr/sbin/anti-evil-maid-lib* \
base32 \
blockdev \
clear \
cryptsetup \
cut \
date \
file \
/usr/share/misc/magic \
grep \
head \
install \
killall \
lsblk \
oathtool \
printf \
scrypt \
sed \
seq \
sha1sum \
sort \
tail \
tcsd \
trousers_changer_identify \
tee \
tpm_id \
tpm2_id \
tpm_nvinfo \
tpm_nvread \
tpm_nvread_stdout \
tpm_pcr_extend \
tpm_sealdata \
tpm_unsealdata \
tpm_z_srk \
tpm2_z_srk \
tr \
uniq \
wc \
xargs \
xxd
# TPM2-related:
# tpm2-tools
dracut_install \
tpm2_changeauth \
tpm2_create \
tpm2_createprimary \
tpm2_evictcontrol \
tpm2_flushcontext \
tpm2_load \
tpm2_nvdefine \
tpm2_nvread \
tpm2_nvreadpublic \
tpm2_nvundefine \
tpm2_nvwrite \
tpm2_nvwritelock \
tpm2_pcrextend \
tpm2_pcrread \
tpm2_policycommandcode \
tpm2_startauthsession \
tpm2_unseal
# other utilities
dracut_install \
mktemp \
openssl \
sha256sum
# such tpm2-tss libraries must be listed explicitly because they are
# discovered at runtime instead of being linked to during build
dracut_install \
/usr/lib64/libtss2-tcti-device.so.0*
dracut_install \
$systemdsystemunitdir/anti-evil-maid-unseal.service \
$systemdsystemunitdir/anti-evil-maid-check-mount-devs.service \
$systemdsystemunitdir/initrd.target.wants/anti-evil-maid-unseal.service \
$systemdsystemunitdir/initrd.target.requires/anti-evil-maid-check-mount-devs.service
# all this crap below is needed for tcsd to start properly...
dracut_install ip
inst_simple "$moddir"/hosts /etc/hosts
touch "$initdir/etc/"{passwd,shadow,group}
chmod 0644 "$initdir/etc/"{passwd,group}
chmod 0640 "$initdir/etc/shadow"
for name in root tss; do
for file in /etc/{passwd,group}; do
if ! grep -q "^$name:" "$initdir/$file"; then
grep "^$name:" "$file" >> "$initdir/$file"
fi
done
if ! grep -q "^$name:" "$initdir/etc/shadow"; then
echo "$name:*:::::::" >> "$initdir/etc/shadow"
fi
done
}
================================================
FILE: Makefile.builder
================================================
ifeq ($(PACKAGE_SET),dom0)
RPM_SPEC_FILES := anti-evil-maid.spec
endif
================================================
FILE: README
================================================
Intro
======
Anti Evil Maid is an implementation of a TPM-based dynamic (Intel TXT) trusted
boot for dracut/initramfs-based OSes (Fedora, Qubes, etc.) with a primary goal
to prevent Evil Maid attacks.
In short, AEM relies on TPM and a feature found in Intel's vPro CPUs (TXT) to
detect tampering of various boot components.
For more information and discussion about potential attacks see:
http://blog.invisiblethings.org/2011/09/07/anti-evil-maid.html
(Note that this article is somewhat outdated, e.g. AEM uses Intel TXT now.)
Requirements and security notes ("before you start")
====================================================
* Only TPM version 1.2 is currently supported. It may be possible to
configure your 2.0 TPM to emulate the 1.2 interface.
* AEM is not compatible with (U)EFI boot. Legacy boot is required.
* If you are using LUKS with LVM, you must encrypt the whole volume group
instead of each volume, or else AEM will fail to boot.
* You MUST set a TPM owner password
* Unless you're installing AEM to internal disk, TPM SRK password SHOULD
NOT be set (otherwise tboot will not be able to check whether critical
parts of RAM were not altered during S3 sleep). Please be aware that by
installing to internal disk and setting a TPM SRK password, your RAM
WILL NOT be protected against tampering when your laptop is suspended,
so make sure it is completely shut down any time an attacker might gain
physical access.
* When RAM tampering is detected on wake-up from S3 sleep, the default
tboot policy only prints a warning into its log (`sudo txt-stat`) so
even if you checked it immediately after each wake-up, the attacker
might already have exfiltrated your login passphrase. Fix this by
either using two-factor for desktop login (weak) or create stronger
Launch Control Policy (LCP) and Verified Launch Policy (LCP) and write
them into TPM NVRAM -- especially make sure that tboot will forcefully
halt/reboot the platform if RAM tampering is detected. See tboot docs
for more information (`/usr/share/doc/tboot`). Creating a small NVRAM
area for tboot to write last error code might be a good idea for
debugging crashes on S3 suspend/resume:
`sudo tpmnv_defindex -i 0x20000002 -s 8 -pv 0 -rl 0x07 -wl 0x07 -p <ownerpw>`
* Be aware that Intel TXT is vulnerable to System Management Mode (SMM)
exploits like overwriting System Management Interrupt (SMI) handlers.
Since SMM code is stored in platform firmware (BIOS) which usually is
updatable (and thus can be overwritten by an attacker), it is quite
an attractive target (and not just for the NSA). This can be fixed by
integrating an SMI Transfer Monitor (STM) into the platform (but this,
again, relies on the the same BIOS vendor who wrote a buggy SMM code
to safely implement STM). Additionally, STM does not appear to be
widely available yet (STM specification released mid-2015 by Intel).
You can check whether your platform includes STM:
`sudo txt-stat | grep -iA 1 stm`
Seeing "stm: 0" and "stm_hash" being all zeros means you DO NOT have
STM. Either way, BIOS is now part of your Trusted Computing Base (TCB)
and you need to prevent attackers with physical access from modifying
it. Good luck.
Some hints: connect the write protect pin on BIOS flash chip to ground
(prevents attacker from booting their own software which would bypass
BIOS protections and overwrite it) and make sure physically accessing
the chip will be tamper-evident by eg. covering the screws holding
laptop body together in glitter and taking high-res photos, then
examining before each use.
* You might want to consider assessing the firmware security of your
platform using an automated tool such as CHIPSEC:
https://github.com/chipsec/chipsec
* To recap -- you need to fully trust:
* CPU (Intel, since we're depending on TXT)
* sometimes over-optimizes for performance at the cost of security,
see eg. Meltdown/Spectre, cache attacks against SGX enclaves, ...
* TPM (various vendors)
* few known attacks sniffing and injecting commands on the LPC bus;
differential power analysis; buggy RSA key generation code
* note that any potential TPM exploits (should) have no means of
compromising your system directly -- a TPM under attacker's control
can only be used to hide the fact that a compromise has occurred
(ie. defeating the whole AEM feature)
* BIOS (a few vendors)
* it's full of holes!
* that the attacker cannot get physically inside your laptop without
you noticing (see the glitter hint above)
Upgrading to AEM v4
===================
If you have an existing AEM installation, there are a few steps required
after updating AEM packages to version >= 4.0 (available since Qubes R4).
The easiest way to upgrade is to completely reset the TPM and start from
scratch and re-create all existing AEM devices.
Should you want to migrate without resetting the TPM (in case you're using
it for something else besides Qubes AEM), you can manually replicate the
steps taken in the TPM setup script (/usr/sbin/anti-evil-maid-tpm-setup).
Note that you still need to re-create all provisioned AEM media afterwards.
Otherwise, perform a TPM reset (via BIOS) and skip to the "Installation"
section below.
Installation
=============
The instructions below assume Qubes OS.
1) Enable TPM in BIOS. Also enable TXT if there is an option for it.
2) Install and Verify TPM support under your OS/Dom0.
a) Install anti-evil-maid packages (in Dom0 on Qubes). It will install all the
required dependencies and tools.
# qubes-dom0-update anti-evil-maid
b) Verify kernel support for TPM:
# cat /sys/class/tpm/tpm0/pcrs
If you see something like this:
PCR-00: 67 DC B4 8C AB 8D C7 9B 28 84 D9 15 69 DE 82 F2 F0 E1 2A D8
PCR-01: 11 75 9A 19 E5 BD E8 4E DA 1C 01 EC 53 87 FD 50 18 E1 94 1E
PCR-02: 4B 43 98 82 65 04 E9 F4 14 78 26 F9 ED EA 92 91 6D FD AF D5
PCR-03: B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36
PCR-04: 93 33 4E 81 A6 9C 80 54 D6 87 C7 FD 76 7C 6F 4C 70 FC C6 73
(...)
... then your TPM is supported by your kernel.
If your tpm has already been owned in the past, you can reset it by running
tpm_clear -z, powering your computer off, and then resetting TPM in the BIOS
(e.g.: TPM Authentication Reset).
c) Initialize the TPM for use with AEM
# anti-evil-maid-tpm-setup -z
In case you want to install AEM to an internal disk, an SRK password must
be set up in order for AEM to be secure. The SRK password can be set up
by NOT passing the "-z" option to the above command. Should you not
anticipate future need for internal AEM boot device and want to use
external media only, use the "-z" option. If you later decide to provision
AEM on the internal drive, create an SRK password first:
# tpm_changeownerauth -s
You will need to copy & paste the randomly-generated TPM owner password
from the /var/lib/anti-evil-maid/tpm-owner-pw file. Existing AEM media
will _not_ need to be re-sealed.
3) Setup Anti Evil Maid
a) SINIT module
You should download the SINIT module required for your system.
Intel documented the required SINIT module depending on your CPU platform in:
http://software.intel.com/en-us/articles/intel-trusted-execution-technology
But DO NOT download the modules besides the SINIT/RACM from here. The download links provide old versions and broken binaries.
You can then download the module and unzip it. All the modules could be
downloaded from:
https://cdrdv2.intel.com/v1/dl/getContent/630744?wapkw=intel%20txt%20sinit%20acm%20revocation%20
Find the module fitting to your platform and rename it to the names mentioned on the intel website link.
Also, make sure you have the latest RACM update, if available (2nd & 3rd gen):
https://software.intel.com/system/files/article/183305/intel-txt-sinit-acm-revocation-tools-guide-rev1-0_2.pdf
It's possible to use 3rd gen SINIT/RACM on 2nd gen platforms. In fact, the
only RACM available at the time of writing is for the 3rd gen, while the 2nd
gen platforms were also affected by the buffer overflow bug in old SINIT
version.
Finally, you should retrieve the BIN file inside /boot in dom0. E.g., run from
dom0:
$ sudo -s
# qvm-run --pass-io vm_name_containing_bin_file 'cat /home/user/path_to_sinit/name_of_sinit_file.BIN' > /boot/name_of_sinit_file.BIN
NOTE: The SINIT files are digitally signed by Intel. While there is no easy
way to verify their integrity after downloading (and after copying to Dom0),
still, the operation of placing such a file into Dom0's /boot filesystem
should be reasonably safe to do -- after all the file should not be processed
by any software in Dom0, and only by the SENTER instruction of the processes,
which, we hope, correctly verifies the signature before executing it...
b) Create an Anti Evil Maid device:
# anti-evil-maid-install -h
Please note that each AEM device you provision should have a unique
filesystem label suffix (use the '-s' option). You may safely re-use
suffixes for destroyed devices.
Installation directly onto a (truly) read-only media (such as a CD-R) is not
supported. You can, however, copy the contents of an existing RW media onto
RO media after the initial sealing takes place. Physical write-protect
switches on USB sticks are fine (install AEM in RW mode, then
flip the switch and proceed to use as RO media). Remember to always pull
out the RO media when your text secret or TOTP code is displayed! Failing
to do that will result in invalidation of freshness token in the TPM memory
and the AEM media will fail to perform verified boot next time, falling
back to non-AEM-protected mode.
For example, to install on the internal boot partition (assuming that it
is /dev/sda1):
# anti-evil-maid-install /dev/sda1
Or better, create an external AEM boot device (in this case an SD card):
# anti-evil-maid-install /dev/mmcblk0p1
Alternatively, a multi-factor authentication AEM boot device can be created,
which provides additional protection against shoulder surfing and video
surveillance -- with the above setups, if an attacker sees your disk
encryption password as you're typing it then they can simply steal and decrypt
your computer. Long story short, if you ever need to boot your AEM-protected
computer in public or anywhere a camera may be hidden, this is the thing you
want to use. However, this setup requires an external boot media (eg. USB
stick or memory card) for maximum protection and owning a suitable two-factor
authentication device supporting time-based one-time passwords (TOTP). There
are apps available for Android/Apple smartphones (Google Authenticator,
FreeOTP Authenticator) and Windows Phone (Microsoft Authenticator). You can
also use a dedicated hardware token, either a reseedable one (letting the AEM
installer generate a seed for you), or a manufacturer-seeded token (you will
need to store the seed in /var/lib/anti-evil-maid/aem<suffix>/secret.otp in
base32 format, then run the AEM installer). The command to set this all up is
simple:
# anti-evil-maid-install -m /dev/sdb1
This will automatically generate a TOTP seed and display it as a QR code for
you to enroll on your 2FA device (if it doesn't have a camera, there's also
text version for manual entry). Once a successful TOTP seed enrollment is
verified (you need to enter the 6-digit code displayed on your 2FA device),
a LUKS key file will be randomly generated and encrypted by a password of
your choice (make sure you're not using this password for anything else).
This key file will then get added to your encrypted drive's LUKS key slot.
For more details, see the associated qubes-devel mailing list thread:
https://groups.google.com/d/topic/qubes-devel/8cAjSyg1msM/discussion
In case you would like to install multi-factor AEM on internal disk, beware
that keyboard observation attacks cannot be prevented! Plus, you still need
to have TPM SRK password set. The only advantage over plain static text secret
is, of course, that there's not static secret **shown on the screen** to
observe (ie. cover the keyboard while typing AEM passwords).
If you've chosen to install AEM on an external device (and not the internal
drive), you should then remove the internal boot partition from dom0's
/etc/fstab, never mount it again in dom0, and never boot from it again,
because an attacker might modify it to exploit GRUB or dom0 filesystem
drivers.
Note: If you choose to use a USB device (e.g., a flash drive) as your AEM
device and you previously created a USB qube, then you may have to unhide
your USB controller from dom0:
1. Open the file `/etc/default/grub` in dom0.
2. Find the line that begins with `GRUB_CMDLINE_LINUX`.
3. If present, remove `rd.qubes.hide_all_usb` from that line.
4. Save and close the file.
5. Run the command `grub2-mkconfig -o /boot/grub2/grub.cfg` in dom0.
6. Reboot.
c) Create a secret text (note: it cannot be larger than 255 bytes):
Note: This step is unnecessary if using the multi-factor auth setup, but
can serve as a fallback option in case you ever find yourself temporarily
not having access to your 2FA device (eg. smartphone or hardware TOTP token).
# cat >/var/lib/anti-evil-maid/aem/secret.txt <<END
My secret text
has two lines
END
Note: You are saving this not-yet-sealed secret to your root
filesystem without further encryption (besides that provided by LUKS).
If an attacker somehow gains access to your decrypted filesystem data,
e.g. by compelling you to reveal the LUKS passphrase, they can of
course see these secrets. So they are probably not the right place to
store your most intimate confessions. ;)
4) Reboot the system, choose one of the entries called "AEM Qubes". This will
attempt to perform a "measured launch" using tboot and the SINIT module you
downloaded, which records the Xen, kernel, and initrd versions used in PCRs
17-19 of the TPM for use in sealing and unsealing your secret. If the measured
launch fails for any reason, tboot will fall back to a normal boot and AEM
will not function.
a) Enter your SRK password if prompted. You won't see your secret afterwards,
because it hasn't been sealed yet (seeing a `Freshness token unsealing
failed!` message here is expected). Enter your disk decryption passphrase
anyway, right now you still trust your system.
As the system continues booting, AEM will automatically seal your
secret(s). You should see a line, or multiple lines, like this one:
Sealed /var/lib/anti-evil-maid/aem/secret.txt using
--pcr 13 --pcr 17 --pcr 18 --pcr 19
Debug output can be read using:
$ journalctl -u anti-evil-maid-unseal -u anti-evil-maid-seal
Note: The PCRs used to seal to can be changed in /etc/anti-evil-maid.conf
-- though the defaults should work just fine. If you decide to change
them and you want to reseal immediately, run anti-evil-maid-seal manually
once.
If you get a message that the "PCR sanity check failed" and you are sure you
have saved the right SINIT blob in step 3.a, then check the tboot log for
details. The easiest way to view it is to set "logging=vga vga_delay=10" on
the "multiboot /tboot.gz" line in grub.cfg and reboot. Alternatively, run
`sudo txt-stat` from dom0. For more information, see the tboot readme
(/usr/share/doc/tboot/README on an installed system).
b) If a chunk of your installed RAM seems to be missing after the reboot
(which can be checked by running "xl info" in dom0), do the following:
# echo 'export GRUB_CMDLINE_TBOOT=min_ram=0x2000000' >>/etc/default/grub
# grub2-mkconfig -o /boot/grub2/grub.cfg
Then go to step 4.a again. A discussion of this problem can be found at
http://thread.gmane.org/gmane.comp.boot-loaders.tboot.devel/610/focus=611
and by searching for "min_ram" in the qubes mailing lists.
c) Now, every time you boot your system (from your Anti Evil Maid stick)
you should see your secret text or TOTP code displayed *before* you
enter your LUKS disk encryption or key file passphrase.
Xen/kernel/BIOS/firmware upgrades
==================================
After Xen, kernel, BIOS, or firmware upgrades, you will need to reboot
and enter your disk decryption passphrase even though you can't see your
secret. Please note that you will see a `Freshness toekn unsealing failed!`
error. It (along with your AEM secrets) will be resealed again automatically
later in the boot process (see step 4.a).
Some additional things that can cause AEM secrets and freshness token to
fail to unseal (non-exhaustive list):
* changing the LUKS header of the encrypted root partition
* modifying the initrd (adding/removing files or just re-generating it)
* changing kernel commandline parameters in GRUB
What to do in case of compromise
================================
For a discussion of potential attacks against Anti Evil Maid, see the article
referenced at the beginning.
AEM media copied/stolen by an attacker
--------------------------------------
If you have your system up and running or have an extra AEM media using which
you can boot the system before the attacker can get to it:
* `sudo -s` in dom0
* `. /usr/sbin/anti-evil-maid-lib` (note the dot at the beginning)
* `revokefreshness <suffix>` (where `<suffix>` is the missing label suffix)
In case you do not remember the used media label suffix, take a look at
`/var/lib/anti-evil-maid/`. If the particular media had no special suffix set
(i.e. if the subdirectory is just named `aem`), use `revokefreshness ""`.
Alternatively, `resetfreshness` will wipe all freshness tokens from the TPM
(thus invalidating all enrolled AEM media and forcing you to boot an
unverified system).
As a last resort, you can attempt to reset the TPM from BIOS (with similar
effect to `resetfreshness`). Otherwise, it's game over.
Someone saw my LUKS passphrase
------------------------------
You should've installed AEM in multi-factor mode.
If you're fairly confident the attacker does not possess a bitwise copy
of your encrypted Qubes OS drive, change the LUKS passphrase:
* determine the path to LUKS-encrypted disk (usually /dev/sda2)
* add a new password with `sudo cryptsetup luksAddKey <disk>`
* remove old one with `sudo cryptsetup luksRemoveKey <disk>`
As these actions will change the LUKS header, which is fed into one
of the TPM PCRs upon each AEM boot, all the existing AEM media will
get invalidated (ie. fall back to unverified boot).
Beware that solid-state devices will most likely NOT overwrite the
LUKS header, but rather write the new one into another memory cell
(due to wear leveling algorithms designed to prolong SSD life).
If you're worried about this and have recent-enough backups (as
you always should), perform an ATA secure erase of the whole SSD
using a live CD and then reinstall Qubes OS.
https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase
Someone saw my text secret
--------------------------
Again, if you're going to boot your Qubes OS in public places,
using multi-factor AEM is strongly recommended.
Assuming the attacker haven't yet had access to your computer
(in order to install a compromised bootloader which will show
the correct secret but record your LUKS passphrase), simply
changing the text secret and re-creating affected AEM media
(if you used same secret for multiple ones) will do.
The text secrets are stored in
`/var/lib/anti-evil-maid/aem<suffix>/secret.txt`
Make sure to never trust the old text secret ever again!
TODO: write up more scenarios and how to recover, best practices
================================================
FILE: anti-evil-maid.spec.in
================================================
Name: anti-evil-maid
Version: @VERSION@
Release: 1%{?dist}
Summary: Anti Evil Maid for initramfs-based systems.
Requires: dracut grub2-tools parted tboot tpm-tools
Requires: tpm-extra >= 4.0.0
Requires: trousers-changer >= 4.0.0
Requires: systemd >= 227
Requires: coreutils >= 8.25-2
Requires: scrypt qrencode oathtool
Requires: tpm2-tools openssl
Requires(post): dracut grub2-tools tboot systemd
Obsoletes: anti-evil-maid-dracut
Vendor: Invisible Things Lab
License: GPL
URL: http://www.qubes-os.org
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
%description
Anti Evil Maid for initramfs-based systems.
%prep
%setup -q
%install
mkdir -p $RPM_BUILD_ROOT/usr
cp -r sbin $RPM_BUILD_ROOT/usr
mkdir -p $RPM_BUILD_ROOT/usr/share/doc/anti-evil-maid
cp README $RPM_BUILD_ROOT/usr/share/doc/anti-evil-maid
cp -r etc $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/mnt/anti-evil-maid
mkdir -p $RPM_BUILD_ROOT/var/lib/anti-evil-maid
mkdir -p $RPM_BUILD_ROOT/usr/lib/dracut/modules.d
cp -r 90anti-evil-maid $RPM_BUILD_ROOT/usr/lib/dracut/modules.d/
mkdir -p $RPM_BUILD_ROOT/usr/lib
cp -r systemd $RPM_BUILD_ROOT/usr/lib
%files
/usr/sbin/anti-evil-maid-install
/usr/sbin/anti-evil-maid-lib
/usr/sbin/anti-evil-maid-lib-tpm1
/usr/sbin/anti-evil-maid-lib-tpm2
/usr/sbin/anti-evil-maid-seal
/usr/sbin/anti-evil-maid-tpm-setup
/usr/share/doc/anti-evil-maid/README
/usr/lib/systemd/system/anti-evil-maid-seal.service
/usr/lib/systemd/system/tcsd.service.d/anti-evil-maid-seal.conf
/usr/lib/systemd/system/basic.target.wants/anti-evil-maid-seal.service
/etc/anti-evil-maid.conf
/etc/grub.d/19_linux_xen_tboot
%dir /mnt/anti-evil-maid
%dir /var/lib/anti-evil-maid
/etc/dracut.conf.d/anti-evil-maid.conf
/usr/lib/dracut/modules.d/90anti-evil-maid
/usr/lib/systemd/system/anti-evil-maid-unseal.service
/usr/lib/systemd/system/anti-evil-maid-check-mount-devs.service
/usr/lib/systemd/system/initrd.target.wants/anti-evil-maid-unseal.service
/usr/lib/systemd/system/initrd.target.requires/anti-evil-maid-check-mount-devs.service
%define tboot_grub /etc/grub.d/20_linux_tboot /etc/grub.d/20_linux_xen_tboot
%define refresh \
dracut --regenerate-all --force \
grub2-mkconfig -o /boot/grub2/grub.cfg \
systemctl daemon-reload
%post
chmod -x %tboot_grub
%refresh
%postun
if [ "$1" = 0 ]; then
%refresh
chmod -f +x %tboot_grub || true
fi
%triggerin -- tboot
chmod -x %tboot_grub
%changelog
@CHANGELOG@
================================================
FILE: etc/anti-evil-maid.conf
================================================
# List of PCRs -- but note that Qubes DOESN'T USE TrustedGRUB:
#
# 0-3: (SRTM) BIOS, option ROMs, platform config
# 4: (SRTM) MBR
# 5-7: (SRTM) OEM specific, probably safe to skip
# 8,9: (SRTM) TrustedGRUB1 stage2
# 12: (SRTM) Xen/kernel params passed by TrustedGRUB1
# 13: LUKS header(s)
# 14: (SRTM) Xen/kernel/initrd loaded by TrustedGRUB1
# 17-19: (DRTM) TBoot
#
# SRTM = Static Root of Trust Measurement
# DRTM = Dynamic Root of Trust Measurement (Intel TXT)
# shellcheck disable=SC2034
SEAL="--pcr 13 --pcr 17 --pcr 18 --pcr 19"
================================================
FILE: etc/dracut.conf.d/anti-evil-maid.conf
================================================
add_dracutmodules+=" anti-evil-maid "
================================================
FILE: etc/grub.d/19_linux_xen_tboot
================================================
#! /bin/sh
set -e
# grub-mkconfig helper script.
# Copyright (C) 2006,2007,2008,2009,2010 Free Software Foundation, Inc.
#
# GRUB is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# GRUB is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GRUB. If not, see <http://www.gnu.org/licenses/>.
prefix="/usr"
exec_prefix="/usr"
datarootdir="${prefix}/share"
. "/usr/share/grub/grub-mkconfig_lib"
export TEXTDOMAIN=grub
export TEXTDOMAINDIR="${datarootdir}/locale"
CLASS="--class gnu-linux --class gnu --class os --class xen"
if [ "x${GRUB_DISTRIBUTOR}" = "x" ] ; then
OS="$(sed 's, release .*$,,g' /etc/system-release)"
else
OS="${GRUB_DISTRIBUTOR}"
CLASS="--class $(echo ${GRUB_DISTRIBUTOR} | tr 'A-Z' 'a-z' | cut -d' ' -f1) ${CLASS}"
fi
# loop-AES arranges things so that /dev/loop/X can be our root device, but
# the initrds that Linux uses don't like that.
case ${GRUB_DEVICE} in
/dev/loop/*|/dev/loop[0-9])
GRUB_DEVICE=`losetup ${GRUB_DEVICE} | sed -e "s/^[^(]*(\([^)]\+\)).*/\1/"`
;;
esac
if [ "x${GRUB_DEVICE_UUID}" = "x" ] || [ "x${GRUB_DISABLE_LINUX_UUID}" = "xtrue" ] \
|| ! test -e "/dev/disk/by-uuid/${GRUB_DEVICE_UUID}" \
|| uses_abstraction "${GRUB_DEVICE}" lvm; then
LINUX_ROOT_DEVICE=${GRUB_DEVICE}
else
LINUX_ROOT_DEVICE=UUID=${GRUB_DEVICE_UUID}
fi
# Allow overriding GRUB_CMDLINE_LINUX and GRUB_CMDLINE_LINUX_DEFAULT.
if [ "${GRUB_CMDLINE_LINUX_XEN_REPLACE}" ]; then
GRUB_CMDLINE_LINUX="${GRUB_CMDLINE_LINUX_XEN_REPLACE}"
fi
if [ "${GRUB_CMDLINE_LINUX_XEN_REPLACE_DEFAULT}" ]; then
GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_XEN_REPLACE_DEFAULT}"
fi
GRUBFS="`${grub_probe} --device ${GRUB_DEVICE} --target=fs 2>/dev/null || true`"
if [ x"$GRUBFS" = x ]; then
GRUBFS="$(stat -f --printf=%T /)"
fi
case x"$GRUBFS" in
xbtrfs)
rootsubvol="`make_system_path_relative_to_its_root /`"
rootsubvol="${rootsubvol#/}"
if [ "x${rootsubvol}" != x ]; then
GRUB_CMDLINE_LINUX="rootflags=subvol=${rootsubvol} ${GRUB_CMDLINE_LINUX}"
fi;;
xzfs)
rpool=`${grub_probe} --device ${GRUB_DEVICE} --target=fs_label 2>/dev/null || true`
bootfs="`make_system_path_relative_to_its_root / | sed -e "s,@$,,"`"
LINUX_ROOT_DEVICE="ZFS=${rpool}${bootfs}"
;;
esac
title_correction_code=
linux_entry ()
{
os="$1"
version="$2"
xen_version="$3"
type="$4"
args="$5"
xen_args="$6"
if [ -z "$boot_device_id" ]; then
boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
fi
if [ x$type != xsimple ] ; then
if [ x$type = xrecovery ] ; then
title="$(gettext_printf "AEM %s boot, with Xen %s and Linux %s (recovery mode)" "${os}" "${xen_version}" "${version}")"
else
title="$(gettext_printf "AEM %s boot, with Xen %s and Linux %s" "${os}" "${xen_version}" "${version}")"
fi
replacement_title="$(echo "Advanced options with AEM boot for ${OS}" | sed 's,>,>>,g')>$(echo "$title" | sed 's,>,>>,g')"
if [ x"Xen ${xen_version}>$title" = x"$GRUB_ACTUAL_DEFAULT" ]; then
quoted="$(echo "$GRUB_ACTUAL_DEFAULT" | grub_quote)"
title_correction_code="${title_correction_code}if [ \"x\$default\" = '$quoted' ]; then default='$(echo "$replacement_title" | grub_quote)'; fi;"
grub_warn "$(gettext_printf "Please don't use old title \`%s' for GRUB_DEFAULT, use \`%s' (for versions before 2.00) or \`%s' (for 2.00 or later)" "$GRUB_ACTUAL_DEFAULT" "$replacement_title" "gnulinux-advanced-$boot_device_id>gnulinux-$version-$type-$boot_device_id")"
fi
echo "menuentry '$(echo "$title" | grub_quote)' ${CLASS} \$menuentry_id_option 'xen-gnulinux-$version-$type-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
else
title="$(gettext_printf "AEM %s, with Xen hypervisor" "${os}")"
echo "menuentry '$(echo "$title" | grub_quote)' ${CLASS} \$menuentry_id_option 'xen-gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
fi
if [ x$type != xrecovery ] ; then
save_default_entry | grub_add_tab | sed "s/^/$submenu_indentation/"
fi
if [ -z "${prepare_boot_cache}" ]; then
prepare_boot_cache="$(prepare_grub_to_access_device ${GRUB_DEVICE_BOOT} | grub_add_tab)"
fi
printf '%s\n' "${prepare_boot_cache}" | sed "s/^/$submenu_indentation/"
tmessage="$(gettext_printf "Loading tboot ...")"
xmessage="$(gettext_printf "Loading Xen %s ..." ${xen_version})"
lmessage="$(gettext_printf "Loading Linux %s ..." ${version})"
sed "s/^/$submenu_indentation/" << EOF
echo '$(echo "$tmessage" | grub_quote)'
multiboot /tboot.gz placeholder logging=memory,serial ${GRUB_CMDLINE_TBOOT}
echo '$(echo "$xmessage" | grub_quote)'
if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
xen_rm_opts=
else
xen_rm_opts="no-real-mode edd=off"
fi
module ${rel_xen_dirname}/${xen_basename} placeholder ${xen_args} \${xen_rm_opts}
echo '$(echo "$lmessage" | grub_quote)'
module ${rel_dirname}/${basename} placeholder root=${linux_root_device_thisversion} ro ${args} aem.uuid=${GRUB_DEVICE_BOOT_UUID} rd.luks.key=/tmp/aem-keyfile rd.luks.crypttab=no
EOF
if test -n "${initrd}" ; then
# TRANSLATORS: ramdisk isn't identifier. Should be translated.
message="$(gettext_printf "Loading initial ramdisk ...")"
sed "s/^/$submenu_indentation/" << EOF
echo '$(echo "$message" | grub_quote)'
module ${rel_dirname}/${initrd}
EOF
fi
if test -n "${sinit_module_list}" ; then
for i in ${sinit_module_list} ; do
sinit_module=`basename $i`
message="$(gettext_printf "Loading SINIT module %s ..." ${sinit_module})"
sed "s/^/$submenu_indentation/" << EOF
echo '$message'
module /${sinit_module}
EOF
done
fi
sed "s/^/$submenu_indentation/" << EOF
}
EOF
}
linux_list=`for i in /boot/vmlinu[xz]-* /vmlinu[xz]-* /boot/kernel-*; do
if grub_file_is_not_garbage "$i"; then
basename=$(basename $i)
version=$(echo $basename | sed -e "s,^[^0-9]*-,,g")
dirname=$(dirname $i)
config=
for j in "${dirname}/config-${version}" "${dirname}/config-${alt_version}" "/etc/kernels/kernel-config-${version}" ; do
if test -e "${j}" ; then
config="${j}"
break
fi
done
if (grep -qx "CONFIG_XEN_DOM0=y" "${config}" 2> /dev/null || grep -qx "CONFIG_XEN_PRIVILEGED_GUEST=y" "${config}" 2> /dev/null); then echo -n "$i " ; fi
fi
done`
if [ "x${linux_list}" = "x" ] ; then
exit 0
fi
file_is_not_sym () {
case "$1" in
*/xen-syms-*)
return 1;;
*)
return 0;;
esac
}
xen_list=`for i in /boot/xen*; do
if grub_file_is_not_garbage "$i" && file_is_not_sym "$i" ; then echo -n "$i " ; fi
done`
sinit_module_list=`for i in /boot/*SINIT*.BIN; do
if grub_file_is_not_garbage "$i"; then
echo "$i"
fi
done`
prepare_boot_cache=
boot_device_id=
title_correction_code=
machine=`uname -m`
case "$machine" in
i?86) GENKERNEL_ARCH="x86" ;;
mips|mips64) GENKERNEL_ARCH="mips" ;;
mipsel|mips64el) GENKERNEL_ARCH="mipsel" ;;
arm*) GENKERNEL_ARCH="arm" ;;
*) GENKERNEL_ARCH="$machine" ;;
esac
echo "if [ -d /aem/ ]; then"
# Extra indentation to add to menu entries in a submenu. We're not in a submenu
# yet, so it's empty. In a submenu it will be equal to '\t' (one tab).
submenu_indentation=""
is_first_entry=true
while [ "x${xen_list}" != "x" ] ; do
list="${linux_list}"
current_xen=`version_find_latest $xen_list`
xen_basename=`basename ${current_xen}`
xen_dirname=`dirname ${current_xen}`
rel_xen_dirname=`make_system_path_relative_to_its_root $xen_dirname`
xen_version=`echo $xen_basename | sed -e "s,.gz$,,g;s,^xen-,,g"`
if [ -z "$boot_device_id" ]; then
boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
fi
if [ "x$is_first_entry" != xtrue ]; then
echo " submenu '$(gettext_printf "Xen hypervisor, version %s with AEM boot" "${xen_version}" | grub_quote)' \$menuentry_id_option 'xen-hypervisor-$xen_version-$boot_device_id' {"
fi
while [ "x$list" != "x" ] ; do
linux=`version_find_latest $list`
gettext_printf "Found linux image: %s\n" "$linux" >&2
basename=`basename $linux`
dirname=`dirname $linux`
rel_dirname=`make_system_path_relative_to_its_root $dirname`
version=`echo $basename | sed -e "s,^[^0-9]*-,,g"`
alt_version=`echo $version | sed -e "s,\.old$,,g"`
linux_root_device_thisversion="${LINUX_ROOT_DEVICE}"
initrd=
for i in "initrd.img-${version}" "initrd-${version}.img" "initrd-${version}.gz" \
"initrd-${version}" "initramfs-${version}.img" \
"initrd.img-${alt_version}" "initrd-${alt_version}.img" \
"initrd-${alt_version}" "initramfs-${alt_version}.img" \
"initramfs-genkernel-${version}" \
"initramfs-genkernel-${alt_version}" \
"initramfs-genkernel-${GENKERNEL_ARCH}-${version}" \
"initramfs-genkernel-${GENKERNEL_ARCH}-${alt_version}" ; do
if test -e "${dirname}/${i}" ; then
initrd="$i"
break
fi
done
if test -n "${initrd}" ; then
gettext_printf "Found initrd image: %s\n" "${dirname}/${initrd}" >&2
else
# "UUID=" magic is parsed by initrds. Since there's no initrd, it can't work here.
linux_root_device_thisversion=${GRUB_DEVICE}
fi
if [ "x$is_first_entry" = xtrue ]; then
linux_entry "${OS}" "${version}" "${xen_version}" simple \
"${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}" "${GRUB_CMDLINE_XEN} ${GRUB_CMDLINE_XEN_DEFAULT}"
submenu_indentation="$grub_tab$grub_tab"
if [ -z "$boot_device_id" ]; then
boot_device_id="$(grub_get_device_id "${GRUB_DEVICE}")"
fi
# TRANSLATORS: %s is replaced with an OS name
echo "submenu '$(gettext_printf "Advanced options with AEM boot for %s (with Xen hypervisor)" "${OS}" | grub_quote)' \$menuentry_id_option 'gnulinux-advanced-$boot_device_id' {"
echo " submenu '$(gettext_printf "Xen hypervisor, version %s" "${xen_version}" | grub_quote)' \$menuentry_id_option 'xen-hypervisor-$xen_version-$boot_device_id' {"
fi
is_first_entry=false
linux_entry "${OS}" "${version}" "${xen_version}" advanced \
"${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT}" "${GRUB_CMDLINE_XEN} ${GRUB_CMDLINE_XEN_DEFAULT}"
if [ "x${GRUB_DISABLE_RECOVERY}" != "xtrue" ]; then
linux_entry "${OS}" "${version}" "${xen_version}" recovery \
"single ${GRUB_CMDLINE_LINUX}" "${GRUB_CMDLINE_XEN}"
fi
list=`echo $list | tr ' ' '\n' | grep -vx $linux | tr '\n' ' '`
done
if [ x"$is_first_entry" != xtrue ]; then
echo ' }'
fi
xen_list=`echo $xen_list | tr ' ' '\n' | grep -vx $current_xen | tr '\n' ' '`
done
# If at least one kernel was found, then we need to
# add a closing '}' for the submenu command.
if [ x"$is_first_entry" != xtrue ]; then
echo '}'
fi
echo "$title_correction_code"
echo fi
================================================
FILE: sbin/anti-evil-maid-install
================================================
#!/bin/bash
set -euo pipefail
shopt -s expand_aliases
. anti-evil-maid-lib
LABEL_SUFFIX_CHARS=0-9a-zA-Z=.-
BOOT_DIR=/boot
GRUB_DIR=$BOOT_DIR/grub2
GRUB_CFG=$GRUB_DIR/grub.cfg
validatetpm || exit 1
usage() {
cat <<END
Usage:
anti-evil-maid-install [-s <suffix>] [-F] [-m] <device>
Installs Anti Evil Maid to your system's boot partition, or to a different
storage device (e.g. an SD card or a USB stick).
Arguments:
-s: <device> gets labeled "$LABEL_PREFIX<suffix>"
<suffix> can be composed of 0-13 characters from the alphabet
$LABEL_SUFFIX_CHARS
It defaults to <device>'s current suffix, if any, or the empty string
otherwise. Each of your AEM installations must have a unique suffix.
This suffix has no particular meaning, except that you can let it end
in .rm=1 or .rm=0 to hint that <device> is removable or fixed,
respectively, no matter what the Linux kernel detects.
-F: passed on to mkfs.ext4 (don't ask for confirmation, etc.)
-m: set up a multi-factor auth AEM media
Using time-based one time password and a LUKS key file, provides
resistance to shoulder surfing and video surveillance based passphrase
snooping.
Examples:
Install on the system's boot partition (assuming that it is /dev/sda1), and
label its current filesystem "$LABEL_PREFIX":
anti-evil-maid-install /dev/sda1
Install on an SD card's first partition, replacing its data with a new ext4
filesystem labeled "$LABEL_PREFIX.sd", and make it bootable:
anti-evil-maid-install -s .sd /dev/mmcblk0p1
Install MFA-enabled AEM on USB stick's first partition, overwriting it with
a new ext4 filesystem and marking it bootable:
anti-evil-maid-install -m /dev/sdb1
END
exit 1
}
# check invocation
alias mfa=false
LABEL_SUFFIX=
F=()
while getopts s:Fhm opt; do
case "$opt" in
s) LABEL_SUFFIX=$OPTARG ;;
F) F=( -F ) ;;
m) alias mfa=true ;;
*) usage ;;
esac
done
# shellcheck disable=SC2102
case "$LABEL_SUFFIX" in *[!$LABEL_SUFFIX_CHARS]*|??????????????*) usage; esac
LABEL=$LABEL_PREFIX$LABEL_SUFFIX
shift $((OPTIND - 1))
case $# in
1) PART_DEV=$1 ;;
*) usage ;;
esac
if [ "$(id -ur)" != 0 ]; then
log "This command must be run as root!"
exit 1
fi
if [ -z "$(getluksuuids)" ]; then
log "Anti Evil Maid requires encrypted disk!"
exit 1
fi
tpmstartservices
# examine device
BOOT_MAJMIN=$(mountpoint -d "$BOOT_DIR") || BOOT_MAJMIN=
PART_DEV_MAJMIN=$(lsblk -dnr -o MAJ:MIN "$PART_DEV")
if external "$PART_DEV" && [ "$BOOT_MAJMIN" != "$PART_DEV_MAJMIN" ]; then
alias replace=true
else
alias replace=false
fi
WHOLE_DEV=$(lsblk -dnp -o PKNAME "$PART_DEV")
if [ ! -b "$WHOLE_DEV" ] || [ "$WHOLE_DEV" == "$PART_DEV" ]; then
log "Couldn't find parent device: $WHOLE_DEV"
exit 1
fi
PART_DEV_REAL=$(readlink -f "$PART_DEV")
PART_NUM=${PART_DEV_REAL##*[!0-9]}
if ! [ "$PART_NUM" -gt 0 ]; then
log "Couldn't extract partition number: $PART_NUM"
exit 1
fi
# MFA-specific checks
if mfa && ! external "$PART_DEV"; then
log "WARNING: Installing MFA AEM on the same disk"
log "as Qubes OS will NOT provide any resistance"
log "against keyboard observation during boot!"
log "Additionally, compromise recovery using"
log "freshness token revocation will be a lot"
log "less feasible."
waitforenter
elif mfa && ! removable "$PART_DEV" "$LABEL" ; then
log "WARNING: Installing MFA AEM on an internal"
log "disk will NOT provide any resistance"
log "against keyboard observation during boot!"
log "Additionally, compromise recovery using"
log "freshness token revocation will be a lot"
log "less feasible."
log "You can safely ignore this warning if the"
log "device in question is, in fact, removable."
waitforenter
fi
# This check (instead of a more obvious 'mountpoint $BOOT_DIR') should work
# even in unusual setups without any internal boot partition at all:
if [ ! -e "$GRUB_CFG" ]; then
log "Couldn't find boot files at $BOOT_DIR"
exit 1
fi
# keep old label unless overridden explicitly
OLD_LABEL=$(lsblk -dnr -o LABEL "$PART_DEV") ||
OLD_LABEL=
case "$OLD_LABEL" in "$LABEL_PREFIX"*)
if [ -z "${LABEL_SUFFIX+set}" ]; then
LABEL=$OLD_LABEL
fi
esac
# create and/or label fs
if replace; then
log "Creating new ext4 filesystem labeled $LABEL"
mkfs.ext4 "${F[@]}" -L "$LABEL" "$PART_DEV"
else
log "Labeling filesystem $LABEL"
e2label "$PART_DEV" "$LABEL"
fi
# move secrets if label changed
if [ -n "$OLD_LABEL" ] &&
[ -e "$AEM_DIR/$OLD_LABEL" ] &&
[ ! -e "$AEM_DIR/$LABEL" ]; then
mv -v "$AEM_DIR/$OLD_LABEL" "$AEM_DIR/$LABEL"
fi
# add the AEM media being created to the freshness database
if suffixtoslot "$LABEL_SUFFIX" >/dev/null; then
log "WARNING: (possibly another) AEM media with the same"
log "label suffix is already enrolled in the freshness token"
log "database! Overwriting will result in the old AEM media"
log "failing to perform a successful AEM boot. If you're"
log "simply reinstalling on the same device or intentionally"
log "replacing an old AEM media that was lost/destroyed/etc.,"
log "it is safe to continue."
read -r -p "Proceed? [y/N] " response
case "$response" in
y|Y) echo "continuing..." ;;
*) exit ;;
esac
else
assignslottosuffix "$LABEL_SUFFIX"
slot=$(suffixtoslot "$LABEL_SUFFIX")
log "Assigned slot $slot to this AEM media"
fi
# MFA: generate a TOTP seed
if mfa && [ ! -e "$AEM_DIR/$LABEL/secret.otp" ]; then
log "Generating new 160-bit TOTP seed"
mkdir -p "$AEM_DIR/$LABEL"
otp_secret=$(head -c 20 /dev/random | base32 -w 0 | tr -d =)
echo "$otp_secret" > "$AEM_DIR/$LABEL/secret.otp"
# create an ANSI text QR code and show it in the terminal
otp_uri="otpauth://totp/${LABEL}?secret=${otp_secret}"
echo -n "$otp_uri" | qrencode -t ansiutf8
log "Please scan the above QR code with your OTP device."
# display the text form of secret to user, too
# shellcheck disable=SC2001
human_readable_secret="$(echo "$otp_secret" | sed 's/\(....\)/\1\ /g')"
log "Alternatively, you may manually enter the following"
log "secret into your OTP device:"
log " $human_readable_secret"
if timedatectl status | grep -q 'RTC in local TZ: yes'; then
log ""
log "WARNING: Your computer's RTC (real-time clock) is set"
log "to store time in local timezone. This will cause wrong"
log "TOTP codes to be generated during AEM boot. Please fix"
log "this by running (as root):"
log " timedatectl set-local-rtc 0"
waitforenter
fi
# check whether secret was provisioned correctly
log ""
log "After you have set up your OTP device, please enter"
log "the code displayed on your device and press <ENTER>"
log "to continue."
log ""
totp_tries=3
for try in $(seq $totp_tries); do
read -r -p "Code: "
if ! oathtool --totp -b "$otp_secret" "$REPLY" >/dev/null; then
log "Entered TOTP code is invalid!"
if [ "$try" -lt $totp_tries ]; then
log "Please check clock synchronization."
log "If you made mistake while manually entering the secret,"
log "remove the added token, repeat the process & try again."
log ""
else
log "Aborting AEM setup..."
exit 1
fi
else
break
fi
done
log "TOTP code matches, continuing AEM setup."
fi
# MFA: generate and enroll a LUKS key file if not already present
if mfa && [ ! -e "$AEM_DIR/$LABEL/secret.key" ]; then
log "Generating new LUKS key file"
rawkey=$(mktemp)
head -c 64 /dev/random > "$rawkey"
log "Encrypting key file"
mkdir -p "$AEM_DIR/$LABEL"
scrypt enc "$rawkey" "$AEM_DIR/$LABEL/secret.key"
for uuid in $(getluksuuids); do
dev=/dev/disk/by-uuid/$uuid
devname=$(readlink -f "$dev")
log "Adding key file to new key slot for $devname (UUID $uuid)"
cryptsetup luksAddKey "$dev" "$rawkey"
done
log "Shredding the unencrypted key file"
shred -zu "$rawkey"
fi
# mount
if CUR_MNT=$(devtomnt "$PART_DEV") && [ -n "$CUR_MNT" ]; then
PART_MNT=$CUR_MNT
else
CUR_MNT=
PART_MNT=/mnt/anti-evil-maid/$LABEL
log "Mounting at $PART_MNT"
mkdir -p "$PART_MNT"
mount "$PART_DEV" "$PART_MNT"
fi
# sync
mkdir -p "$PART_MNT/aem"
synctpms "$LABEL" "$PART_MNT"
mkdir -p "$AEM_DIR/$LABEL"
# make device bootable
if replace; then
log "Setting bootable flag"
parted -s "$WHOLE_DEV" set "$PART_NUM" boot on
log "Copying boot files"
find "$BOOT_DIR" -maxdepth 1 -type f ! -name 'initramfs-*.img' \
-exec cp {} "$PART_MNT" \;
# TODO: If dracut is configured for no-hostonly mode (so we don't have to
# worry about picking up loaded kernel modules), just copy each initramfs
# instead of regenerating it
for img in "$BOOT_DIR"/initramfs-*.img; do
ver=${img%.img}
ver=${ver##*initramfs-}
log "Generating initramfs for kernel $ver"
dracut --force "$PART_MNT/${img##*/}" "$ver"
done
log "Copying GRUB themes"
dst=$PART_MNT/${GRUB_DIR#"$BOOT_DIR"/}
mkdir "$dst"
cp -r "$GRUB_DIR/themes" "$dst"
log "Installing GRUB"
grub2-install --boot-directory="$PART_MNT" "$WHOLE_DEV"
log "Bind mounting $PART_MNT at $BOOT_DIR"
mount --bind "$PART_MNT" "$BOOT_DIR"
fi
log "Generating GRUB configuration"
grub2-mkconfig -o "$GRUB_CFG"
if replace; then
log "Unmounting bind mounted $BOOT_DIR"
umount "$BOOT_DIR"
fi
if [ -z "$CUR_MNT" ]; then
log "Unmounting $PART_MNT"
umount "$PART_MNT"
fi
================================================
FILE: sbin/anti-evil-maid-lib
================================================
LABEL_PREFIX=aem
SYSFS_TPM_DIR=/sys/class/tpm/tpm0
AEM_DIR=/var/lib/anti-evil-maid
TPM_DIR=/var/lib/tpm
TPMS_DIR=${TPM_DIR}s
CACHE_DIR=/run/anti-evil-maid
SRK_PASSWORD_CACHE=$CACHE_DIR/srk-password
# shellcheck disable=SC2034
SUFFIX_CACHE=$CACHE_DIR/suffix
TPM_OWNER_PASSWORD_FILE=$AEM_DIR/tpm-owner-pw
TPM_FRESHNESS_PASSWORD_FILE=$AEM_DIR/tpm-freshness-pw
TPM_FRESHNESS_INDEX="0x454d"
TPM_FRESHNESS_SLOTS=8
# work with or without plymouth
if command plymouth --ping 2>/dev/null; then
alias plymouth_active=true
alias message=plymouth_message
else
alias plymouth=:
alias plymouth_active=false
alias message=log
fi
getparams() {
_CMDLINE=${_CMDLINE-$(cat /proc/cmdline)}
for _param in $_CMDLINE; do
for _key; do
case "$_param" in "$_key"=*)
printf '%s\n' "${_param#*=}"
break
esac
done
done
}
getluksuuids() {
getparams rd.luks.uuid rd_LUKS_UUID | sed s/^luks-//
}
log() {
echo "${0##*/}: $1" >&2
}
hex() {
xxd -ps | tr -dc 0-9a-f
}
unhex() {
tr -dc 0-9a-f | xxd -ps -r
}
waitfor() {
case $# in
2) _file=$2; _what=connected ;;
3) _file=$3; _what=removed ;;
*) return 1 ;;
esac
if [ "$@" ]; then
return
fi
message "Waiting for $_file to be $_what..."
plymouth pause-progress
until [ "$@" ]; do
sleep 0.1
done
plymouth unpause-progress
message "$_file $_what"
}
waitforenter() {
msg='Press <ENTER> to continue...'
if plymouth_active; then
message "$msg"
plymouth watch-keystroke --keys=$'\n'
else
systemd-ask-password --timeout=0 --echo=no "$msg" >/dev/null
fi
}
suffixtoslotfile() {
echo "$AEM_DIR/$LABEL_PREFIX$1/tpm-freshness-slot"
}
suffixtoslot() {
# returns the slot number assigned to the AEM media given its label suffix
# as the first argument
_slotfile=$(suffixtoslotfile "$1")
cat "$_slotfile" 2>/dev/null
}
assignslottosuffix() {
# assigns an unused freshness slot number (if available) to an AEM
# media identified by its label suffix (passed as the first argument)
_slotfile=$(suffixtoslotfile "$1")
rm -f "$_slotfile"
_slotfilesglob=$(suffixtoslotfile '*')
_lastslot=$((TPM_FRESHNESS_SLOTS - 1))
_freeslot=$(
{
cat "$_slotfilesglob" 2>/dev/null || true
seq 0 $_lastslot
} | sort -n | uniq -u | head -n 1
)
if [ -z "$_freeslot" ]; then
message "No more freshness token slots available!"
return 1
fi
mkdir -p "${_slotfile%/*}"
echo "$_freeslot" >> "$_slotfile"
}
synctpms() {
_label=${1:?}
_mnt=${2:?}
message "Syncing to $_mnt"
_mnt_tpms_dir=$_mnt/aem/${TPMS_DIR##*/}
rm -rf "$_mnt_tpms_dir"
_ids=$(ls "$TPMS_DIR")
for _id in $_ids; do
mkdir -p "$_mnt_tpms_dir/$_id"
# this file is used only with TPM1
if [ -f "$TPMS_DIR/$_id/system.data" ]; then
cp "$TPMS_DIR/$_id/system.data" "$_mnt_tpms_dir/$_id"
fi
if [ -d "$TPMS_DIR/$_id/$_label" ]; then
cp -r "$TPMS_DIR/$_id/$_label" "$_mnt_tpms_dir/$_id"
fi
done
}
devtomnt() {
lsblk -dnr -o MOUNTPOINT "$1" 2>/dev/null |
sed 's/%/\\x25/g' |
xargs -0 printf
}
topdev() {
lsblk -snrp -o KNAME "$1" | tail -n 1
}
external() {
_aem_whole=$(topdev "$1")
for _luks_uuid in $(getluksuuids); do
_luks_whole=$(topdev "/dev/disk/by-uuid/$_luks_uuid")
if [ "$_aem_whole" = "$_luks_whole" ]; then
return 1
fi
done
return 0
}
removable() {
_rm="$(lsblk -dnr -o RM "$1") ${2-$(lsblk -dnr -o LABEL "$1")}"
case "$_rm" in
*.rm=[01]) _rm=${_rm##*=} ;;
*) _rm=${_rm%% *} ;;
esac
[ "$_rm" = 1 ]
}
validatetpm() {
# makes sure TPM is there and can be used, determines TPM version
if [ ! -d "$SYSFS_TPM_DIR" ]; then
message "$SYSFS_TPM_DIR isn't present"
return 1
fi
_tpm_version=$(cat "$SYSFS_TPM_DIR/tpm_version_major")
if [ -z "$_tpm_version" ]; then
message "Failed to determine the version of the TPM"
return 1
fi
if [ "$_tpm_version" -eq 1 ]; then
# shellcheck source=../sbin/anti-evil-maid-lib-tpm1
source /sbin/anti-evil-maid-lib-tpm1
return 0
fi
if [ "$_tpm_version" -eq 2 ]; then
# shellcheck source=../sbin/anti-evil-maid-lib-tpm2
source /sbin/anti-evil-maid-lib-tpm2
return 0
fi
message "Unexpected TPM version: $_tpm_version"
return 1
}
================================================
FILE: sbin/anti-evil-maid-lib-tpm1
================================================
tpmid() {
tpm_id
}
tpmzsrk() {
tpm_z_srk
}
checktpmnvram() {
# checks whether the TPM NVRAM area is defined
# NOTE: tpm_nvinfo does not return non-zero if requested index
# is not a defined NVRAM area so we need to parse
if ! tpm_nvinfo -i "$TPM_FRESHNESS_INDEX" | grep -q 'AUTHWRITE'; then
return 1
fi
}
createtpmnvram() {
# create the world-readable/AUTHWRITE TPM NVRAM area to hold up to
# TPM_FRESHNESS_SLOTS anti-replay freshness token hashes;
# takes TPM owner password as an agument
if [ ! -e "$TPM_FRESHNESS_PASSWORD_FILE" ]; then
message "Generating TPM NVRAM area AUTHWRITE password"
head -c 16 /dev/random | hex > "$TPM_FRESHNESS_PASSWORD_FILE"
fi
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
if ! tpm_nvdefine -i "$TPM_FRESHNESS_INDEX" \
-s $((TPM_FRESHNESS_SLOTS * 20)) \
-p AUTHWRITE --pwda="$_pw" --pwdo="$1"; then
return 1
fi
}
hashfile() {
# computes hash of a file passed as the only argument
_path=$1
sha1sum "$_path" | cut -d ' ' -f 1
}
checkfreshness() {
# check whether hash of an usealed freshness token (file path
# given as an argument) is contained in TPM NVRAM area
_hash=$(hashfile "$1")
_lastslot=$((TPM_FRESHNESS_SLOTS - 1))
for _i in $(seq 0 $_lastslot); do
_slot=$(tpm_nvread_stdout -i "$TPM_FRESHNESS_INDEX" \
-n "$((_i * 20))" -s 20 | hex)
if [ "$_hash" == "$_slot" ]; then
return 0
fi
done
message "Freshness token does not match any slot in TPM NVRAM!"
return 1
}
updatefreshness() {
# takes a path to the new freshness token as an argument and
# stores its sha1 hash in the appropriate freshness token slot
# of the TPM NVRAM area; second argument is the AEM boot device
# label suffix
if [ ! -e "$TPM_FRESHNESS_PASSWORD_FILE" ]; then
message "TPM NVRAM area AUTHWRITE password file does not exist!"
return 1
fi
if ! _slot=$(suffixtoslot "$2"); then
message "Suffix '$2' not in DB, attempting to create..."
if ! _slot=$(assignslottosuffix "$2"); then
message "Failed to add suffix '$2' into DB!"
return 1
fi
fi
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
hashfile "$1" | unhex \
| tpm_nvwrite_stdin -i "$TPM_FRESHNESS_INDEX" \
-n "$((_slot * 20))" -s 20 --password="$_pw"
}
revokefreshness() {
# invalidates the freshness token of a specified AEM media (by its
# label suffix
_suff=$1
if _slot=$(suffixtoslot "$_suff"); then
message "Revoking freshness token for AEM media w/ suffix '$_suff'..."
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
if tpm_nvwrite -i "$TPM_FRESHNESS_INDEX" \
-n "$((_slot * 20))" -s 20 \
--password="$_pw" -m "0xff"; then
message "Done."
else
message "Failed!"
fi
else
message "AEM device with label suffix '$_suff' not found in DB!"
fi
}
resetfreshness() {
# invalidates ALL freshness tokens
message "Invalidating **ALL** freshness tokens..."
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
if tpm_nvwrite -i "$TPM_FRESHNESS_INDEX" \
-s "$((TPM_FRESHNESS_SLOTS * 20))" \
--password="$_pw" -m "0xff"; then
message "Done."
else
message "Failed!"
fi
}
destroytpmnvram() {
# releases the TPM NVRAM area; TPM owner pw as first argument
tpm_nvrelease -i "$TPM_FRESHNESS_INDEX" --pwdo="$1"
}
listbadpcrs() {
# prints those standard PCRs configured to be used via $SEAL which haven't
# been extended, output is empty there are no such PCRs
_pcrs=$(printf %s "$SEAL" | grep -Eo '\b1[3789]\b') || true
grep -E "^PCR-(${_pcrs//$'\n'/|}):( 00| FF){20}" "$SYSFS_TPM_DIR"/pcrs
}
tpmowned() {
# checks whether TPM is already owned, signals results with exit code
[ "$(cat "$SYSFS_TPM_DIR"/owned)" -ne 0 ]
}
provisiontpmid() {
# stores TPM ID into an NVRAM entry
_tpm_id_index=$(tpm_id -i)
_opw=$(cat "$TPM_OWNER_PASSWORD_FILE")
# create a write-once NVRAM area
tpm_nvdefine -i "$_tpm_id_index" -s 20 -p "WRITEDEFINE|WRITEALL" \
--pwdo="$_opw"
# generate a random ID and write it into NVRAM
head -c 20 /dev/random | tpm_nvwrite_stdin -i "$_tpm_id_index" -s 20
# lock the area to prevent non-owners from changing ID
tpm_nvwrite -i "$_tpm_id_index" -s 0
}
postprovisioning() {
# takes care of updating /var/lib/tpms after a successful provisioning by
# provisiontpmid
_tpmid=$(tpm_id)
mkdir -p "/var/lib/tpms/$_tpmid"
systemctl stop tcsd
mv "$TPMS_DIR"/unknown/* "$TPMS_DIR/$_tpmid/"
rm -rf "$TPMS_DIR/unknown"
systemctl start tcsd
}
checksrkpass() {
# checks whether contents of $SRK_PASSWORD_CACHE file is a valid SRK
# password, signals result with exit code
tpm_sealdata -i /dev/null -o /dev/null < "$SRK_PASSWORD_CACHE"
}
tpmpcrextend() {
# extends a PCR with a hash value of a suitable type
_pcr=$1
_hash=$2
tpm_pcr_extend "$_pcr" "$_hash"
}
tpmsealprepare() {
# does necessary preparations before the use of tpmsealdata, accepts path
# to media-specific storage of sealed data
true # nothing to do for TPM1
}
tpmsealdata() {
# seals source specified by second argument into destination specified by
# the third one, non-empty first argument signifies empty SRK password, the
# forth argument specifies path to AEM media-specific storage
_nosrkpass=()
if [ -n "$1" ]; then
_nosrkpass=( -z )
fi
_input=$2
_output=$3
# shellcheck disable=SC2086
if [ ! -t 0 ]; then cat "$SRK_PASSWORD_CACHE"; fi |
tpm_sealdata "${_nosrkpass[@]}" $SEAL -i "$_input" -o "$_output"
}
tpmunsealdata() {
# unseals source specified by second argument into destination specified by
# the third one, non-empty first argument signifies empty SRK password, the
# forth argument specifies path to AEM media-specific storage
_nosrkpass=()
if [ -n "$1" ]; then
_nosrkpass=( -z )
fi
_infile=$2
_outfile=$3
tpm_unsealdata "${_nosrkpass[@]}" -i "$_infile" -o "$_outfile" \
< "$SRK_PASSWORD_CACHE"
}
tpmtakeownership() {
# takes ownership of the TPM, accepts owner and SRK passwords in this order
_opw=$1
_srkpw=$2
_lines=( "$_opw" "$_opw" )
_nosrkpass=()
if [ -n "$_srkpw" ]; then
_lines+=( "$_srkpw" "$_srkpw" )
else
_nosrkpass=( -z )
fi
printf '%s\n' "${_lines[@]}" |
notty env LC_ALL=C tpm_takeownership "${_nosrkpass[@]}" \
2> >(grep -vF "Confirm password:" >&2)
}
tpmresetdalock() {
notty tpm_resetdalock <"$TPM_OWNER_PASSWORD_FILE"
}
tpmstartservices() {
systemctl start tcsd
}
tpmstartinitrdservices() {
trousers_changer_identify
# it forks
tcsd
}
tpmrestartservices() {
systemctl restart tcsd
}
================================================
FILE: sbin/anti-evil-maid-lib-tpm2
================================================
# Value recommended by TCG TPM v2.0 Provisioning Guidance
# https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
# in Table 2
TPM2_SRK_HANDLE=0x81000001
# this is necessary for anti-evil-maid-seal to not try to use tarbmd TCTI which
# has large timeouts for trying to connect with tpm2-abrmd which isn't running
export TPM2TOOLS_TCTI="device:/dev/tpm0"
# make sure we're not leaving any temporary state in the TPM (some very
# unobvious commands create sessions/objects), this way if tpm2-abrmd will be
# used later, its idea about the initial contents of the TPM being empty will
# be correct (otherwise you can get "out of memory", but tpm2_getcap won't show
# anything and tpm2_flushcontext won't clean anything unless $TPM2TOOLS_TCTI
# is set as above)
trap 'tpm2_flushcontext -tls' EXIT
tpmid() {
tpm2_id
}
tpmzsrk() {
tpm2_z_srk
}
checktpmnvram() {
# checks whether the TPM NVRAM area is defined
# NOTE: tpm2_nvreadpublic returns all defined NV indices so we need to parse
tpm2_nvreadpublic | grep "$TPM_FRESHNESS_INDEX" -A 7 | grep -q 'authwrite'
}
createtpmnvram() {
# create the world-readable/AUTHWRITE TPM NVRAM area to hold up to
# TPM_FRESHNESS_SLOTS anti-replay freshness token hashes;
# takes TPM owner password as an agument
if [ ! -e "$TPM_FRESHNESS_PASSWORD_FILE" ]; then
message "Generating TPM NVRAM area AUTHWRITE password"
head -c 16 /dev/random | hex > "$TPM_FRESHNESS_PASSWORD_FILE"
fi
_opw=$1
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
_session="$(mktemp)"
_policy="$(mktemp)"
tpm2_startauthsession -S "$_session"
tpm2_policycommandcode -Q -S "$_session" -L "$_policy" \
TPM2_CC_NV_Read
tpm2_flushcontext "$_session"
rm "$_session"
tpm2_nvdefine -Q "$TPM_FRESHNESS_INDEX" \
-L "$_policy" \
-a "policyread|authread|authwrite" \
-s $((TPM_FRESHNESS_SLOTS * 32)) \
-P "$_opw" -p "$_pw"
rm "$_policy"
}
hashfile() {
# computes hash of a file passed as the only argument
_path=$1
sha256sum "$_path" | cut -d ' ' -f 1
}
checkfreshness() {
# check whether hash of an usealed freshness token (file path
# given as an argument) is contained in TPM NVRAM area
_hash=$(hashfile "$1")
_lastslot=$((TPM_FRESHNESS_SLOTS - 1))
tpm2_startauthsession -S "$CACHE_DIR/session" --policy-session
tpm2_policycommandcode -Q -S "$CACHE_DIR/session" TPM2_CC_NV_Read
for _i in $(seq 0 $_lastslot); do
_slot=$(tpm2_nvread "$TPM_FRESHNESS_INDEX" \
-P "session:$CACHE_DIR/session" \
--offset="$((_i * 32))" -s 32 | hex)
if [ "$_hash" == "$_slot" ]; then
tpm2_flushcontext "$CACHE_DIR/session"
return 0
fi
done
tpm2_flushcontext "$CACHE_DIR/session"
message "Freshness token does not match any slot in TPM NVRAM!"
return 1
}
updatefreshness() {
# takes a path to the new freshness token as an argument and
# stores its hash in the appropriate freshness token slot
# of the TPM NVRAM area; second argument is the AEM boot device
# label suffix
_file=$1
if [ ! -e "$TPM_FRESHNESS_PASSWORD_FILE" ]; then
message "TPM NVRAM area AUTHWRITE password file does not exist!"
return 1
fi
if ! _slot=$(suffixtoslot "$2"); then
message "Suffix '$2' not in DB, attempting to create..."
if ! _slot=$(assignslottosuffix "$2"); then
message "Failed to add suffix '$2' into DB!"
return 1
fi
fi
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
hashfile "$_file" | unhex |
tpm2_nvwrite "$TPM_FRESHNESS_INDEX" -i - \
--offset "$((_slot * 32))" -P "$_pw"
}
ffbytestream() {
_count=$1
tr '\0' '\377' < /dev/zero | dd bs="$_count" count=1
}
revokefreshness() {
# invalidates the freshness token of a specified AEM media (by its
# label suffix
_suff=$1
if _slot=$(suffixtoslot "$_suff"); then
message "Revoking freshness token for AEM media w/ suffix '$_suff'..."
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
if ffbytestream 32 |
tpm2_nvwrite "$TPM_FRESHNESS_INDEX" \
--offset "$((_slot * 32))" \
-P "$_pw" -i - ; then
message "Done."
else
message "Failed!"
fi
else
message "AEM device with label suffix '$_suff' not found in DB!"
fi
}
resetfreshness() {
# invalidates ALL freshness tokens
message "Invalidating **ALL** freshness tokens..."
_pw=$(cat "$TPM_FRESHNESS_PASSWORD_FILE")
if ffbytestream "$((TPM_FRESHNESS_SLOTS * 32))" |
tpm2_nvwrite "$TPM_FRESHNESS_INDEX" \
-P "$_pw" -i - ; then
message "Done."
else
message "Failed!"
fi
}
destroytpmnvram() {
# releases the TPM NVRAM area; TPM owner pw as first argument
_pw=$1
tpm2_nvundefine "$TPM_FRESHNESS_INDEX" -C owner -P "$_pw"
}
listbadpcrs() {
# prints those standard PCRs configured to be used via $SEAL which haven't
# been extended, output is empty there are no such PCRs
_pcrs=$(printf %s "$SEAL" | grep -Eo '\b1[3789]\b') || true
tpm2_pcrread "sha256:${_pcrs//$'\n'/,}" | grep -E ": 0x((00){32}|(FF){32})"
}
tpmowned() {
# checks whether TPM is already owned, signals results with exit code
! tpm2_changeauth --quiet -c owner 2>/dev/null
}
provisiontpmid() {
# stores TPM ID into an NVRAM entry
_tpm_id_index=$(tpm2_id -i)
_opw=$(cat "$TPM_OWNER_PASSWORD_FILE")
# create a write-once and read-by-anyone NVRAM area
_session="$(mktemp)"
_policy="$(mktemp)"
tpm2_startauthsession -S "$_session"
tpm2_policycommandcode -Q -S "$_session" -L "$_policy" TPM2_CC_NV_Read
tpm2_flushcontext "$_session"
rm "$_session"
tpm2_nvdefine -Q -s 20 -P "$_opw" -L "$_policy" "$_tpm_id_index" \
-a "policyread|writedefine|writeall|ownerwrite|ownerread"
rm "$_policy"
# generate a random ID and write it into NVRAM
head -c 20 /dev/random |
tpm2_nvwrite -Q -C o -P "$_opw" -i - "$_tpm_id_index"
# lock the area to prevent changing the ID even by the owner
tpm2_nvwritelock -C o -P "$_opw" "$_tpm_id_index"
}
postprovisioning() {
# takes care of updating /var/lib/tpms after a successful provisioning by
# provisiontpmid
_tpmid=$(tpm2_id)
mkdir -p "/var/lib/tpms/$_tpmid"
}
checksrkpass() {
# checks whether contents of $SRK_PASSWORD_CACHE file is a valid SRK
# password, signals result with exit code
# `echo` is needed because empty input doesn't work and `echo` it provides
# '\n'
echo | tpm2_create -Q -C "$TPM2_SRK_HANDLE" -i - \
-P "str:$(cat "$SRK_PASSWORD_CACHE")"
}
tpmpcrextend() {
# extends a PCR with a hash value of a suitable type
_pcr=$1
_hash=$2
tpm2_pcrextend "$_pcr:sha256=$_hash"
}
tpmsealprepare() {
# does necessary preparations before the use of tpmsealdata, accepts path
# to media-specific storage of sealed data
_dir=$1
# this recreates a sealing key on every run to pick up configuration/PCR
# changes if there were any
_pcrs=$(printf %s "$SEAL" | grep -Eo '\b[0-9]+\b')
_pcrs=sha256:${_pcrs//$'\n'/,}
_policy="$(mktemp)"
_session="$(mktemp)"
# make a suitable PCR policy
tpm2_startauthsession -S "$_session"
tpm2_policypcr -Q -S "$_session" -l "$_pcrs" -L "$_policy"
tpm2_flushcontext "$_session"
rm "$_session"
tpm2_flushcontext -t || return 1
# make a key for sealing
head -c 16 /dev/random |
tpm2_create -Q -C "$TPM2_SRK_HANDLE" \
-P "str:$(cat "$SRK_PASSWORD_CACHE")" \
-L "$_policy" -i - -u "$_dir/key.pub" -r "$_dir/key.priv"
echo "$_pcrs" > "$_dir/key.pcrs"
rm "$_policy"
}
tpmsealdata() {
# seals source specified by second argument into destination specified by
# the third one, non-empty first argument signifies empty SRK password, the
# forth argument specifies path to AEM media-specific storage
_infile=$2
_outfile=$3
_dir=$4
_ctx="$(mktemp)"
tpm2_load -Q -C "$TPM2_SRK_HANDLE" -P "str:$(cat "$SRK_PASSWORD_CACHE")" \
--private "$_dir/key.priv" --public "$_dir/key.pub" \
-c "$_ctx" || return 1
tpm2_unseal -Q -c "$_ctx" -p "pcr:$_pcrs" | hex |
openssl enc -aes-256-ctr -pbkdf2 -e \
-kfile - -in "$_infile" -out "$_outfile" || return 1
tpm2_flushcontext -t || return 1
rm "$_ctx"
}
tpmunsealdata() {
# unseals source specified by second argument into destination specified by
# the third one, non-empty first argument signifies empty SRK password, the
# forth argument specifies path to AEM media-specific storage
#
# there is not need to handle the first argument, empty $SRK_PASSWORD_CACHE
# file will do
_infile=$2
_outfile=$3
_dir=$4
_pcrs=$(cat "$_dir/key.pcrs")
_ctx="$(mktemp)"
tpm2_load -Q -C "$TPM2_SRK_HANDLE" -P "str:$(cat "$SRK_PASSWORD_CACHE")" \
--private "$_dir/key.priv" --public "$_dir/key.pub" \
-c "$_ctx" 2>/dev/null || return 1
tpm2_unseal -Q -c "$_ctx" -p "pcr:$_pcrs" | hex |
openssl enc -aes-256-ctr -pbkdf2 -d \
-kfile - -in "$_infile" -out "$_outfile" || return 1
tpm2_flushcontext -t || return 1
rm "$_ctx"
}
tpmtakeownership() {
# takes ownership of the TPM, accepts owner and SRK passwords in this order
_opw=$1
_srkpw=$2
tpm2_changeauth --quiet -c owner "$_opw"
# use the same password for lockout handle
tpm2_changeauth --quiet -c lockout "$_opw"
_srkctx="$(mktemp)"
tpm2_createprimary -Q --hierarchy=o \
--key-context="$_srkctx" \
--key-auth="$_srkpw" \
-P "$_opw"
# make SRK key persistent
tpm2_evictcontrol -Q -C o -P "$_opw" -c "$_srkctx" "$TPM2_SRK_HANDLE"
rm "$_srkctx"
}
tpmresetdalock() {
tpm2_dictionarylockout -p "$(cat "$TPM_OWNER_PASSWORD_FILE")" \
--clear-lockout
}
tpmstartservices() {
trousers_changer_migrate || true
trousers_changer_identify
}
tpmstartinitrdservices() {
trousers_changer_identify
}
tpmrestartservices() {
trousers_changer_migrate 2>/dev/null || true
trousers_changer_identify 2>/dev/null
}
================================================
FILE: sbin/anti-evil-maid-seal
================================================
#!/bin/bash
set -euo pipefail -o errtrace
shopt -s expand_aliases
alias plymouth_message="plymouth message --text"
source anti-evil-maid-lib
trap 'rm -rf "$CACHE_DIR"' EXIT
validatetpm || exit 1
# Listing foo.service in anti-evil-maid-seal.service's Requires= and After=
# would cause it to always be started (even when not booting in AEM mode or
# when sealing is unnecessary) due to the way systemd evaluates conditions.
# Putting the systemctl command in ExecStartPre= is also insufficient: The
# user might want to run this script manually after changing the secret(s).
tpmstartservices
if [ ! -e "$SUFFIX_CACHE" ] && [ $# -ne 1 ]; then
message "AEM media suffix cache file does not exist"
message "and you didn't specify a suffix as the"
message "first positional argument to this script."
exit 1
fi
# scream loudly if sealing fails for some reason
# (eg. AEM media read-only)
_failure() {
message "Failed to seal secrets (error @ line $1)!"
waitforenter
exit 1
}
trap '_failure $LINENO' ERR
# define sealing and device variables
# shellcheck source=../etc/anti-evil-maid.conf
source /etc/anti-evil-maid.conf
tpmresetdalock || true
Z=$(tpmzsrk)
LABEL_SUFFIX=${1-$(cat "$SUFFIX_CACHE")}
LABEL=$LABEL_PREFIX$LABEL_SUFFIX
case $# in
0) DEV=/dev/disk/by-uuid/$(getparams aem.uuid) ;;
1) DEV=/dev/disk/by-label/$LABEL ;;
*) exit 1 ;;
esac
# ensure that all standard PCRs configured to be used have been extended
bad_pcrs=$(listbadpcrs) || true
if [ -n "$bad_pcrs" ]; then
message "PCR sanity check failed!"
message "Bad PCRs:"$'\n'"$bad_pcrs"
message "See /usr/share/doc/anti-evil-maid/README for details."
exit 1
fi
# regenerate the freshness token and store its hash in TPM
head -c 20 /dev/random > "$AEM_DIR/$LABEL/secret.fre"
updatefreshness "$AEM_DIR/$LABEL/secret.fre" "$LABEL_SUFFIX"
# seal and save secret(s) to root partition
mkdir -p "$TPM_DIR/$LABEL"
tpmsealprepare "$TPM_DIR/$LABEL"
SEALED=0
for ext in txt key otp fre; do
input=$AEM_DIR/$LABEL/secret.$ext
output=$TPM_DIR/$LABEL/secret.$ext.sealed2
if [ ! -e "$input" ]; then
message "Absent $input"
elif tpmsealdata "$Z" "$input" "$output" "$TPM_DIR/$LABEL"; then
rm -f "${output%2}"
SEALED=$((SEALED + 1))
message "Sealed $input using $SEAL"
else
message "Failed $input"
fi
done
if [ "$SEALED" = 0 ]; then
exit 1
fi
# mount device
waitfor -b "$DEV"
if CUR_MNT=$(devtomnt "$DEV") && [ -n "$CUR_MNT" ]; then
MNT=$CUR_MNT
else
CUR_MNT=
MNT=/mnt/anti-evil-maid/$LABEL
mkdir -p "$MNT"
mount "$DEV" "$MNT"
fi
# copy secret(s) to device
synctpms "$LABEL" "$MNT"
# unmount device
if [ -z "$CUR_MNT" ]; then
umount "$MNT"
if external "$DEV" && removable "$DEV"; then
waitfor ! -b "$DEV"
fi
fi
================================================
FILE: sbin/anti-evil-maid-tpm-setup
================================================
#!/bin/bash
set -euo pipefail
shopt -s expand_aliases
source anti-evil-maid-lib
validatetpm || exit 1
if ! { [ $# = 0 ] || { [ $# = 1 ] && [ "$1" = "-z" ]; }; } then
echo "Usage: ${0##*/} [-z]"
exit 1
fi
if [ "$(id -ur)" != 0 ]; then
log "This command must be run as root!"
exit 1
fi
if tpmowned; then
log "You must reset/clear your TPM chip first!"
exit 1
fi
# - take ownership of TPM
OWNERPW=$(head -c 16 /dev/random | hex)
srkpw=
if [ $# = 0 ]; then # set an SRK password
for try in 1 2 3; do
read -r -s -p "Choose SRK password: " srkpw
echo
read -r -s -p "Confirm SRK password: " srkpw2
echo
[ "$srkpw" != "$srkpw2" ] || break
log "Passwords didn't match"
[ "$try" != 3 ] || exit 1
done
fi
tpmrestartservices
log "Taking ownership of the TPM..."
tpmtakeownership "$OWNERPW" "$srkpw"
echo "$OWNERPW" >"$TPM_OWNER_PASSWORD_FILE"
# - generate NVRAM ID
alias provisioned_id=false
if [[ $(tpmid 2>/dev/null) == "unknown" ]]; then
# TPM reset does not clear NVRAM, reusing old ID is fine though
log "Creating TPM ID..."
provisiontpmid
alias provisioned_id=true
fi
# - create freshness token area
if checktpmnvram; then
# delete old freshness area as the old access password is most likely lost
# (in case it isn't, the area will simply get recreated with the same pw)
log "Deleting old freshness token NVRAM area..."
destroytpmnvram "$OWNERPW"
fi
log "Creating freshness token NVRAM area..."
createtpmnvram "$OWNERPW"
# - update TPMs directory after provisioning
if provisioned_id; then
postprovisioning
fi
================================================
FILE: systemd/system/anti-evil-maid-check-mount-devs.service
================================================
[Unit]
Description=Anti Evil Maid system mount dev check
DefaultDependencies=no
ConditionKernelCommandLine=aem.uuid
Before=initrd-root-fs.target sysroot.mount swap.target
After=dracut-initqueue.service
After=cryptsetup.target
[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/sbin/anti-evil-maid-check-mount-devs
StandardOutput=journal+console
StandardError=journal+console
================================================
FILE: systemd/system/anti-evil-maid-seal.service
================================================
[Unit]
Description=Anti Evil Maid sealing
DefaultDependencies=false
Requires=local-fs.target
After=local-fs.target plymouth-start.service
Before=basic.target
ConditionKernelCommandLine=aem.uuid
ConditionPathIsDirectory=/run/anti-evil-maid
[Service]
ExecStart=/usr/sbin/anti-evil-maid-seal
Type=oneshot
StandardOutput=journal+console
StandardError=inherit
TimeoutStartSec=300
[Install]
WantedBy=basic.target
================================================
FILE: systemd/system/anti-evil-maid-unseal.service
================================================
[Unit]
Description=Anti Evil Maid unsealing
DefaultDependencies=no
Wants=cryptsetup-pre.target
Before=cryptsetup-pre.target
After=plymouth-start.service
ConditionKernelCommandLine=aem.uuid
[Service]
Type=oneshot
RemainAfterExit=no
ExecStart=/sbin/anti-evil-maid-unseal
StandardInput=null
StandardOutput=tty
StandardError=journal+console
TimeoutStartSec=300
================================================
FILE: systemd/system/tcsd.service.d/anti-evil-maid-seal.conf
================================================
# start early enough for anti-evil-maid-seal.service
[Unit]
DefaultDependencies=false
# for trousers_changer_identify:
Requires=local-fs.target
After=local-fs.target
================================================
FILE: version
================================================
4.2.1
gitextract_b_nyytam/ ├── .gitignore ├── .gitlab-ci.yml ├── .qubesbuilder ├── 90anti-evil-maid/ │ ├── anti-evil-maid-check-mount-devs │ ├── anti-evil-maid-unseal │ ├── hosts │ └── module-setup.sh ├── Makefile.builder ├── README ├── anti-evil-maid.spec.in ├── etc/ │ ├── anti-evil-maid.conf │ ├── dracut.conf.d/ │ │ └── anti-evil-maid.conf │ └── grub.d/ │ └── 19_linux_xen_tboot ├── sbin/ │ ├── anti-evil-maid-install │ ├── anti-evil-maid-lib │ ├── anti-evil-maid-lib-tpm1 │ ├── anti-evil-maid-lib-tpm2 │ ├── anti-evil-maid-seal │ └── anti-evil-maid-tpm-setup ├── systemd/ │ └── system/ │ ├── anti-evil-maid-check-mount-devs.service │ ├── anti-evil-maid-seal.service │ ├── anti-evil-maid-unseal.service │ └── tcsd.service.d/ │ └── anti-evil-maid-seal.conf └── version
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (90K chars).
[
{
"path": ".gitignore",
"chars": 6,
"preview": "pkgs/\n"
},
{
"path": ".gitlab-ci.yml",
"chars": 562,
"preview": "checks:shellcheck:\n stage: checks\n tags:\n - docker\n script:\n - cd sbin/\n - shellcheck -s bash * ../etc/anti-"
},
{
"path": ".qubesbuilder",
"chars": 50,
"preview": "host:\n rpm:\n build:\n - anti-evil-maid.spec\n"
},
{
"path": "90anti-evil-maid/anti-evil-maid-check-mount-devs",
"chars": 1755,
"preview": "#!/bin/bash\n\n# shellcheck disable=SC1091\n. /lib/dracut-lib.sh\n\n# this cannot contain -u option because it causes an erro"
},
{
"path": "90anti-evil-maid/anti-evil-maid-unseal",
"chars": 6794,
"preview": "#!/bin/bash\nset -euo pipefail\nshopt -s expand_aliases\n\n# Anti Evil Maid for dracut by Invisible Things Lab\n# Copyright ("
},
{
"path": "90anti-evil-maid/hosts",
"chars": 158,
"preview": "127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4\n::1 localhost localhost.localdoma"
},
{
"path": "90anti-evil-maid/module-setup.sh",
"chars": 2924,
"preview": "#!/bin/bash\n\ncheck() {\n which tpm_unsealdata tpm2_unseal >/dev/null 2>&1 || return 1\n}\n\n\n#depends() {\n#}\n\n\ninstallke"
},
{
"path": "Makefile.builder",
"chars": 75,
"preview": "ifeq ($(PACKAGE_SET),dom0)\n RPM_SPEC_FILES := anti-evil-maid.spec\nendif\n"
},
{
"path": "README",
"chars": 19485,
"preview": "Intro\n======\n\nAnti Evil Maid is an implementation of a TPM-based dynamic (Intel TXT) trusted\nboot for dracut/initramfs-b"
},
{
"path": "anti-evil-maid.spec.in",
"chars": 2406,
"preview": "Name:\t\tanti-evil-maid\nVersion:\t@VERSION@\nRelease:\t1%{?dist}\nSummary: \tAnti Evil Maid for initramfs-based systems.\nReq"
},
{
"path": "etc/anti-evil-maid.conf",
"chars": 563,
"preview": "# List of PCRs -- but note that Qubes DOESN'T USE TrustedGRUB:\n#\n# 0-3: (SRTM) BIOS, option ROMs, platform config\n# "
},
{
"path": "etc/dracut.conf.d/anti-evil-maid.conf",
"chars": 38,
"preview": "add_dracutmodules+=\" anti-evil-maid \"\n"
},
{
"path": "etc/grub.d/19_linux_xen_tboot",
"chars": 11123,
"preview": "#! /bin/sh\nset -e\n\n# grub-mkconfig helper script.\n# Copyright (C) 2006,2007,2008,2009,2010 Free Software Foundation, In"
},
{
"path": "sbin/anti-evil-maid-install",
"chars": 9866,
"preview": "#!/bin/bash\nset -euo pipefail\nshopt -s expand_aliases\n. anti-evil-maid-lib\nLABEL_SUFFIX_CHARS=0-9a-zA-Z=.-\nBOOT_DIR=/boo"
},
{
"path": "sbin/anti-evil-maid-lib",
"chars": 4624,
"preview": "LABEL_PREFIX=aem\nSYSFS_TPM_DIR=/sys/class/tpm/tpm0\nAEM_DIR=/var/lib/anti-evil-maid\nTPM_DIR=/var/lib/tpm\nTPMS_DIR=${TPM_D"
},
{
"path": "sbin/anti-evil-maid-lib-tpm1",
"chars": 7012,
"preview": "tpmid() {\n tpm_id\n}\n\ntpmzsrk() {\n tpm_z_srk\n}\n\nchecktpmnvram() {\n # checks whether the TPM NVRAM area is define"
},
{
"path": "sbin/anti-evil-maid-lib-tpm2",
"chars": 10626,
"preview": "# Value recommended by TCG TPM v2.0 Provisioning Guidance\n# https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM"
},
{
"path": "sbin/anti-evil-maid-seal",
"chars": 2846,
"preview": "#!/bin/bash\nset -euo pipefail -o errtrace\nshopt -s expand_aliases\n\nalias plymouth_message=\"plymouth message --text\"\nsour"
},
{
"path": "sbin/anti-evil-maid-tpm-setup",
"chars": 1654,
"preview": "#!/bin/bash\nset -euo pipefail\nshopt -s expand_aliases\n\nsource anti-evil-maid-lib\n\nvalidatetpm || exit 1\n\n\nif ! { [ $# = "
},
{
"path": "systemd/system/anti-evil-maid-check-mount-devs.service",
"chars": 378,
"preview": "[Unit]\nDescription=Anti Evil Maid system mount dev check\nDefaultDependencies=no\nConditionKernelCommandLine=aem.uuid\nBefo"
},
{
"path": "systemd/system/anti-evil-maid-seal.service",
"chars": 409,
"preview": "[Unit]\nDescription=Anti Evil Maid sealing\nDefaultDependencies=false\nRequires=local-fs.target\nAfter=local-fs.target plymo"
},
{
"path": "systemd/system/anti-evil-maid-unseal.service",
"chars": 358,
"preview": "[Unit]\nDescription=Anti Evil Maid unsealing\nDefaultDependencies=no\nWants=cryptsetup-pre.target\nBefore=cryptsetup-pre.tar"
},
{
"path": "systemd/system/tcsd.service.d/anti-evil-maid-seal.conf",
"chars": 167,
"preview": "# start early enough for anti-evil-maid-seal.service\n\n[Unit]\nDefaultDependencies=false\n# for trousers_changer_identify:\n"
},
{
"path": "version",
"chars": 6,
"preview": "4.2.1\n"
}
]
About this extraction
This page contains the full source code of the QubesOS/qubes-antievilmaid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (81.9 KB), approximately 25.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.