Full Code of QubesOS/qubes-antievilmaid for AI

main c085d776c499 cached
24 files
81.9 KB
25.2k tokens
1 requests
Download .txt
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
Download .txt
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.

Copied to clipboard!