Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a USB security key as a TPM work-alike in the absence of a physical TPM #836

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions initrd/bin/flash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set -e -o pipefail

case "$CONFIG_FLASHROM_OPTIONS" in
-* )
echo "Board $CONFIG_BOARD detected, continuing..."
[ "$1" != "-s" ] && echo "Board $CONFIG_BOARD detected, continuing..."
;;
* )
die "ERROR: No board has been configured!\n\nEach board requires specific flashrom options and it's unsafe to flash without them.\n\nAborting."
Expand All @@ -30,6 +30,10 @@ flash_rom() {
else
die "$ROM: Read inconsistent"
fi
elif [ "$SHA" -eq 1 ]; then
flashrom $CONFIG_FLASHROM_OPTIONS -r "${ROM}" 1&>2 >/dev/null \
|| die "$ROM: Read failed"
sha256sum ${ROM} | cut -f1 -d ' '
Copy link
Collaborator

@tlaurion tlaurion Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sha1sum fitting in available 40 chars as per TPM 1.1 equivalent?

else
cp "$ROM" /tmp/${CONFIG_BOARD}.rom
sha256sum /tmp/${CONFIG_BOARD}.rom
Expand All @@ -52,20 +56,29 @@ flash_rom() {
if [ "$1" == "-c" ]; then
CLEAN=1
READ=0
SHA=0
ROM="$2"
elif [ "$1" == "-r" ]; then
CLEAN=0
READ=1
SHA=0
ROM="$2"
touch $ROM
elif [ "$1" == "-s" ]; then
CLEAN=0
READ=0
SHA=1
ROM="$2"
touch $ROM
else
CLEAN=0
READ=0
SHA=0
ROM="$1"
fi

if [ ! -e "$ROM" ]; then
die "Usage: $0 [-c|-r] <path_to_image.rom>"
die "Usage: $0 [-c|-r|-s] <path_to_image.rom>"
fi

flash_rom $ROM
Expand Down
31 changes: 18 additions & 13 deletions initrd/bin/seal-hotpkey
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,24 @@ else
HOTPKEY_BRANDING="HOTP USB Security Dongle"
fi

tpm nv_readvalue \
-in 4d47 \
-sz 312 \
-of "$HOTP_SEALED" \
|| die "Unable to retrieve sealed file from TPM NV"

tpm unsealfile \
-hk 40000000 \
-if "$HOTP_SEALED" \
-of "$HOTP_SECRET" \
|| die "Unable to unseal HOTP secret"

shred -n 10 -z -u "$HOTP_SEALED" 2> /dev/null
if [ "$CONFIG_TPM" = "y" ]; then
tpm nv_readvalue \
-in 4d47 \
-sz 312 \
-of "$HOTP_SEALED" \
|| die "Unable to retrieve sealed file from TPM NV"

tpm unsealfile \
-hk 40000000 \
-if "$HOTP_SEALED" \
-of "$HOTP_SECRET" \
|| die "Unable to unseal HOTP secret"

shred -n 10 -z -u "$HOTP_SEALED" 2> /dev/null
else
# without a TPM, use the first 20 characters of the ROM SHA256sum
secret_from_rom_hash > $HOTP_SECRET
fi

# Store counter in file instead of TPM for now, as it conflicts with Heads
# config TPM counter as TPM 1.2 can only increment one counter between reboots
Expand Down
123 changes: 65 additions & 58 deletions initrd/bin/seal-totp
Original file line number Diff line number Diff line change
Expand Up @@ -17,72 +17,79 @@ fi
TOTP_SECRET="/tmp/secret/totp.key"
TOTP_SEALED="/tmp/secret/totp.sealed"

dd \
if=/dev/urandom \
of="$TOTP_SECRET" \
count=1 \
bs=20 \
2>/dev/null \
|| die "Unable to generate 20 random bytes"

secret="`base32 < $TOTP_SECRET`"

# Use the current values of the PCRs, which will be read
# from the TPM as part of the sealing ("X").
# PCR4 == 0 means that we are still in the boot process and
# not a recovery shell.
# should this read the storage root key?
if ! tpm sealfile2 \
-if "$TOTP_SECRET" \
-of "$TOTP_SEALED" \
-hk 40000000 \
-ix 0 X \
-ix 1 X \
-ix 2 X \
-ix 3 X \
-ix 4 0000000000000000000000000000000000000000 \
-ix 7 X \
; then
if [ "$CONFIG_TPM" = "y" ]; then
dd \
if=/dev/urandom \
of="$TOTP_SECRET" \
count=1 \
bs=20 \
2>/dev/null \
|| die "Unable to generate 20 random bytes"

secret="`base32 < $TOTP_SECRET`"

# Use the current values of the PCRs, which will be read
# from the TPM as part of the sealing ("X").
# PCR4 == 0 means that we are still in the boot process and
# not a recovery shell.
# should this read the storage root key?
if ! tpm sealfile2 \
-if "$TOTP_SECRET" \
-of "$TOTP_SEALED" \
-hk 40000000 \
-ix 0 X \
-ix 1 X \
-ix 2 X \
-ix 3 X \
-ix 4 0000000000000000000000000000000000000000 \
-ix 7 X \
; then
shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null
die "Unable to seal secret"
fi

shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null
die "Unable to seal secret"
fi

shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null

# to create an nvram space we need the TPM owner password
# and the TPM physical presence must be asserted.
#
# The permissions are 0 since there is nothing special
# about the sealed file
tpm physicalpresence -s \
|| warn "Warning: Unable to assert physical presence"

# to create an nvram space we need the TPM owner password
# and the TPM physical presence must be asserted.
#
# The permissions are 0 since there is nothing special
# about the sealed file
tpm physicalpresence -s \
|| warn "Warning: Unable to assert physical presence"

# Try to write it without the password first, and then create
# the NVRAM space using the owner password if it fails for some reason.
if ! tpm nv_writevalue \
-in $TPM_NVRAM_SPACE \
-if "$TOTP_SEALED" \
; then
warn 'NVRAM space does not exist? Owner password is required'
read -s -p "TPM Owner password: " tpm_password
echo

tpm nv_definespace \
-in $TPM_NVRAM_SPACE \
-sz 312 \
-pwdo "$tpm_password" \
-per 0 \
|| die "Unable to define NVRAM space"

tpm nv_writevalue \
# Try to write it without the password first, and then create
# the NVRAM space using the owner password if it fails for some reason.
if ! tpm nv_writevalue \
-in $TPM_NVRAM_SPACE \
-if "$TOTP_SEALED" \
|| die "Unable to write sealed secret to NVRAM"
; then
warn 'NVRAM space does not exist? Owner password is required'
read -s -p "TPM Owner password: " tpm_password
echo

tpm nv_definespace \
-in $TPM_NVRAM_SPACE \
-sz 312 \
-pwdo "$tpm_password" \
-per 0 \
|| die "Unable to define NVRAM space"

tpm nv_writevalue \
-in $TPM_NVRAM_SPACE \
-if "$TOTP_SEALED" \
|| die "Unable to write sealed secret to NVRAM"
fi

shred -n 10 -z -u "$TOTP_SEALED" 2> /dev/null
else
# without a TPM, use the first 20 characters of the ROM SHA256sum
secret_from_rom_hash > $TOTP_SECRET
secret="`base32 < $TOTP_SECRET`"
shred -n 10 -z -u "$TOTP_SECRET" 2> /dev/null
fi

shred -n 10 -z -u "$TOTP_SEALED" 2> /dev/null

url="otpauth://totp/$HOST?secret=$secret"
secret=""

Expand Down
31 changes: 18 additions & 13 deletions initrd/bin/unseal-hotp
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,24 @@ fi

#counter_value=$(printf "%d" 0x${counter_value})

tpm nv_readvalue \
-in 4d47 \
-sz 312 \
-of "$HOTP_SEALED" \
|| die "Unable to retrieve sealed file from TPM NV"

tpm unsealfile \
-hk 40000000 \
-if "$HOTP_SEALED" \
-of "$HOTP_SECRET" \
|| die "Unable to unseal HOTP secret"

shred -n 10 -z -u "$HOTP_SEALED" 2> /dev/null
if [ "$CONFIG_TPM" = "y" ]; then
tpm nv_readvalue \
-in 4d47 \
-sz 312 \
-of "$HOTP_SEALED" \
|| die "Unable to retrieve sealed file from TPM NV"

tpm unsealfile \
-hk 40000000 \
-if "$HOTP_SEALED" \
-of "$HOTP_SECRET" \
|| die "Unable to unseal HOTP secret"

shred -n 10 -z -u "$HOTP_SEALED" 2> /dev/null
else
# without a TPM, use the first 20 characters of the ROM SHA256sum
secret_from_rom_hash > $HOTP_SECRET
fi

if ! hotp $counter_value < "$HOTP_SECRET"; then
shred -n 10 -z -u "$HOTP_SECRET" 2> /dev/null
Expand Down
13 changes: 13 additions & 0 deletions initrd/etc/functions
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,19 @@ combine_configs() {
cat /etc/config* > /tmp/config
}

# Generate secret value using first 40 chars of ROM SHA256 hash
secret_from_rom_hash() {
local ROM_IMAGE="/tmp/coreboot-notpm.rom"

echo -e "\nTPM not detected; measuring ROM directly\n" 1>&2
# use a previously-copied image if it exists
if [ -f ${ROM_IMAGE} ]; then
sha256sum ${ROM_IMAGE} | cut -f1 -d ' ' | cut -c 1-40 | tr -d '\n'
else
flash.sh -s ${ROM_IMAGE} | cut -c 1-40 | tr -d '\n'
fi
}
Comment on lines +271 to +281
Copy link
Collaborator

@tlaurion tlaurion Feb 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrChromebox @daringer @jans23 @kylerankin @szszszsz @osresearch @fhvyhjriur @Tonux599 @Thrilleratplay @irelativism @blobless @lrvick

Current security of TPM-less hardware, secured solely by USB Security dongle if we merge this :

  1. Boot hardware secured by Librem Key/Nitrokey Pro/Nitrokey Storage on TPM-less supported devices. User owning those devices trusted marketing speech and left said hardware unattended because remotely attested with the USB Security dongle they keep diligently in their pockets. Press 'r' frenetically until reaching recovery shell. Plug formatted usb thumbdrive. Keep calm. If you are cought, you just wanted to see the security of the system, right? Play dumb. Be cute. Be prepared, plan accordingly. You're there for a maximum of 2 ~minutes with largest SPI flash hardware out there, the backup is actually taking 3 backups of the SPI with flashrom internally.
  2. Run the following from shell prompt/a script: mount-usb rw && flash.sh -r /media/backuped.rom && umount /media && poweroff. Wonder around, get back your USB dongle when screen goes off. Leave the scene with USB thumbdrive.
  3. Craft your own evil maid aimed rom at leasure. Tamper also with /boot content! If there is enough space in SPI flash of the hardware in question, add as much stuff as you need! Make scripts to copy them from decompressed initrd under /boot if not existing, modify Heads scripts to your liking so that signed digest verification lies to the user... If you want, you can craft an evil xen, kernel, initrd and grub config if you'd like, script enough so that next updates are still triggering a resign request. Or if the user is so blindly trusting the system, he might never notice that there is no /boot resign warnings after having updated /boot content... Know your target, plan accordingly.
  4. Obtain secret from backuped rom at leasure. That "secret" is used as remote attestation secret in the HOTP challenge with user's Nitrokey Pro/Nitrokey Storage/Librem Key, by running the following on obtained backuped.rom: sha256sum backuped.rom | cut -f1 -d ' ' | cut -c 1-40 | tr -d '\n'
  5. Replace the content of secret_from_rom_hash function commented here with the value obtained from precedent step, simply replacing secret in between the following quotes: echo "secret"
  6. Build your crafted rom. Put it back on usb thumbdrive as file new.rom
  7. Redo step 1 on unattended hardware because trusted by user that the rom is protected with his USB Security dongle kept securely with him.
  8. Run the following from shell/script: mount-usb rw && flash.sh /media/new.rom && umount /media && poweroff. This will take more time then step 2, since writing takes more time. Keep looking discretely at the screen for it to go dark. Get back your thumbdrive and leave for good.
  9. Be confident that on every next boot, HOTP challenge will succeed over Nitrokey Pro/Nitrokey Storage/Librem Key... until the user decides to upgrade firmware on said trusted hardware. Repeat at will on that hardware. Code was merged upstream under Heads. Keep in mind that all TPM-less hardware out there now supported by Heads can be hacked easily the same way by users bragging about their security dongles and the hardware you know doesn't have a TPM. Keep track of supported devices upstream (devices with boards configs having CONFIG_TPM=n and CONFIG_HOTPKEY=y), own them and test locally for guaranteed success!

Notes:

  • Part of Pureboot actual codebase since a year)
  • Advertised in April 2019 by Purism : "This means that if you have a Librem 13 version 2 or Librem 15 version 3 without a TPM, you can now use a Librem Key in place of your TPM chip and get similar protection against tampering!" (and a librem_mini, and people wants this for GM45 based boards...)

@osresearch : We merge? Please press the button if you will.
I can't merge without #361 + flash functions restrictions nor without #836 (comment) being implemented so that the user is well aware of the risks of this approach while leaving hardware unattended (blind trust)...

Without a TPM, I would reflash my x230-hotp-maximized rom on my laptop prior of each use when left unattended....

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hilarious (although realistic) story @tlaurion , my favorite:

Play dumb. Be cute. Be prepared, plan accordingly

but seriously: seeing which misconceptions this (not even upstream) patch has already produced,
I think I also re-cast my vote to "no". I still see a gap between Heads and coreboot+seabios in terms of security (no password, no hotp, not even the chance to check the firmware, no /boot signing), but have to admit that the whole: Heads-implies-another-level-of-security argument, is stronger than I expected.


update_checksums()
{
# clear screen
Expand Down
14 changes: 14 additions & 0 deletions initrd/init
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ hwclock -l -s
. /etc/functions
. /etc/config

# set CONFIG_TPM dynamically before init
if [ -e /dev/tpm0 ]; then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if TPM lines are cut? What is the behavior and conditional codepath effects here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd assume that a TPM with cut lines would behave exactly like a device without a TPM, since there would be no communication and /dev/tpm0 would not exist

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrChromebox my point here is that since the codepath is dynamic and not set inside of ROM inside of /etc/config anymore, cutting the lines of TPM would simply result in a different codepath without the user knowing the his TPM lines were cut, and from the LIBREM KEY/ Nitrokey Pro Nitrokey Storage being used for validation. I'm not sure this is desirable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the user would absolutely be notified, since there would be text indicating no TPM on the main menu, and LK/NK verification would fail as the secret would have changed from the TPM-backed one to the hash-based one.

export CONFIG_TPM='y'
else
export CONFIG_TPM='n'
fi

if [ "$CONFIG_COREBOOT" = "y" ]; then
/bin/cbfs-init
fi
Expand Down Expand Up @@ -89,6 +96,13 @@ if [ "$boot_option" = "r" ]; then
exit
fi

# Override CONFIG_TPM and persist via user config
if [ -e /dev/tpm0 ]; then
echo "CONFIG_TPM=y" >> /etc/config.user
else
echo "CONFIG_TPM=n" >> /etc/config.user
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not measured. No change detected.

fi

combine_configs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combined in ram, no change detected.

Copy link
Collaborator

@tlaurion tlaurion Aug 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point here is what is in cbfs is measured, but not the result of combine_configs, where the result is taken as the new applied config ( next line importing the actual config . /tmp/config)

. /tmp/config

Expand Down