Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Fuhry committed Jun 27, 2016
0 parents commit 8634128
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pkg.tar.gz
*.pkg.tar.xz
28 changes: 28 additions & 0 deletions PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
pkgname=initramfs-scencrypt
pkgdesc="initramfs hook that adds PGP smartcard support for LUKS FDE"
pkgver=1.0
pkgrel=1
arch=(any)
depends=(gnupg pcsclite libusb-compat)
source=(scencrypt-hook
scencrypt-install
README.md)

build() {
return 0
}

package() {
mkdir -p "${pkgdir}/usr/lib/initcpio/hooks"
mkdir -p "${pkgdir}/usr/lib/initcpio/install"

cp "${srcdir}/scencrypt-hook" "${pkgdir}/usr/lib/initcpio/hooks/scencrypt"
cp "${srcdir}/scencrypt-install" "${pkgdir}/usr/lib/initcpio/install/scencrypt"

mkdir -p "${pkgdir}/usr/share/doc/${pkgname}"
cp -a "${srcdir}/README.md" "${pkgdir}/usr/share/doc/${pkgname}/"
}

md5sums=('159bfe688d4e2784c9c9882bb33c6fac'
'a497d9a7ae1918db780377c76004afa3'
'6c68b216a5346c9e936a06cd4b839f7f')
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# GnuPG hook for Arch Linux initcpio

This initcpio hook allows you to use a PGP-compatible smart card during early boot to decrypt your full disk encrypted system.

# Why?

This is a great solution if you want the best possible encryption strength, but without having to type a huge passphrase in every time you boot. Using a hardware-backed RSA key on a PGP smart card, with the card performing decryption of the FDE key on-chip without releasing the private key to the OS, gives you extremely strong protection from attacks, even sophisticated/well-equipped adversaries.

As is the case with all LUKS systems, anyone who gets root on your box after it's booted can run `dmsetup table --showkeys` to dump the master key, the changing of which requires a complete wipe of your disk, in contrast to LUKS user key changing which requires only wiping and replacing the key slot. So don't let an adversary get root - watch your setuid programs, sudo rights, running services, etc. carefully.

PGP smart cards have varying options for PIN limits and reset or self-destruct functionality - choose one that fits your needs.

This hook has only been tested with the Yubikey NEO.

## Disclaimer

Use this hook at your own risk. It's highly recommended to have a backup key somewhere, in case you lose or destroy your primary key.

# Configuration process

1. Install Arch onto a LUKS encrypted system and get it booting using the stock `encrypt` hook and passphrase. (Beyond the scope of this document)
1. Configure your smartcard and get it working to the point that you can encrypt and decrypt things on your machine using the card. (Beyond the scope of this document)
1. Generate a new random key and encrypt it: `dd if=/dev/random bs=64 count=1 | gpg --encrypt -r [email protected] > disk.bin.gpg`
1. Decrypt the key into memory so you can add it to your LUKS volume: `gpg --decrypt -o /dev/shm/disk.bin disk.bin.gpg`
1. `sudo cryptsetup luksAddKey /dev/your_luks_device /dev/shm/disk.bin`
1. `shred -u -n1 /dev/shm/disk.bin` to delete the decrypted `disk.bin` file from memory.
1. Edit `/etc/crypttab` to include your encrypted device. The line will look somewhat like:
`arch_crypt /dev/your_luks_device /home/you/disk.bin.gpg discard`
1. Edit `/etc/mkinitcpio.conf` and replace the `encrypt` hook with `scencrypt`. Do not leave both `encrypt` and `scencrypt` enabled.
1. Run `mkinitcpio -p linux`. If there are no errors, reboot with your smart card plugged in to find out if it works.
1. (Optional) `sudo cryptSetup luksRemoveKey /dev/your_luks_device` and type the passphrase you added when you were installing Arch. This will remove the old passphrase so that only your GPG-encrypted key file can unseal the disk.

# Technical details

The hook works by copying your encrypted key file to the initramfs, decrypting it in memory, passing it to LUKS to unseal the disk, and then using `shred` to overwrite it in memory.

Behind the scenes, `gpg` starts `scdaemon`, which talks to `pcscd` and `pinentry-tty` to get your PIN and pass it to the card along with the payload for decryption. The private key itself is held securely on the smartcard - it cannot be released even with the PIN on hand. But the decryption is quick because the payload is small. Once the disk is mounted, the smartcard can safely be removed from the system - the result of the decryption is merely a "user key" that LUKS uses to decrypt the volume's master key. There is an excellent [white paper](http://clemens.endorphin.org/nmihde/nmihde-A4-ds.pdf) written by one of the original LUKS authors detailing LUKS's extensive anti-forensic hardening.


10 changes: 10 additions & 0 deletions initramfs-scencrypt.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
post_install() {
echo " >> initramfs-scencrypt - READ ME!"
echo " >> Make sure to add your encrypted disks to /etc/crypttab. There is"
echo " >> no support for cryptdevice=... on the kernel command line."
echo ""
echo " >> Key file path must end in .gpg and the private key must be"
echo " >> accessible by root."
echo ""
echo " >> For more information, read /usr/share/doc/initramfs-scencrypt/README.md"
}
108 changes: 108 additions & 0 deletions scencrypt-hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/ash

run_hook() {
modprobe -a -q dm-crypt >/dev/null 2>&1
[ "${quiet}" = "y" ] && CSQUIET=">/dev/null"

sed -re 's;#.*$;;g' -e '/^[ ]*$/ d' -i /etc/crypttab

lineno=1
while true; do
# fetch the next line from crypttab
line="$(tail -n+$lineno /etc/crypttab | tail -1)"
[ -z "$line" ] && break
lineno=$(( $lineno + 1 ))

# parse fields in the crypttab line
read mapped_name device_path key_spec options <<EOF
$line
EOF

IFS=: read key_file keyarg1 keyarg2 <<EOF
$key_spec
EOF
# handle case of no key file
if [ "$key_file" = "-" -o "$key_file" = "none" ]; then
key_file=
elif [ -c "${key_file}" ]; then
# key file is a character device
length=${keyarg1:-32}
dd if=$key_file of=/keyfile.bin bs=1 count=$length >/dev/null 2>&1
key_file=/keyfile.bin
elif [ -b "${key_file}" ]; then
echo "ERROR: Key files on block devices are not supported yet."
key_file=
elif [ -r "${key_file}" -a "${key_file%.gpg}" != "${key_file}" ]; then
# if the key file ends in ".gpg" then handle as encrypted with GPG
# pcscd is needed for smartcard communication
/usr/bin/pcscd --auto-exit

# /.gnupg is where the scdaemon socket lives
test -d /.gnupg || mkdir -p /.gnupg
chmod -R go-rwx /.gnupg /etc/initcpio/gpg

# test communication with card - this is also needed for decryption
# to work at all
/usr/bin/gpg --homedir "/etc/initcpio/gpg" --card-status >/dev/null 2>&1

# now attempt to decrypt
if /usr/bin/gpg --homedir "/etc/initcpio/gpg" --decrypt -o "/keyfile.bin" "${key_file}"; then
# we got it!
key_file="/keyfile.bin"
else
# if decryption fails, still prompt for a passphrase
echo "Failed to decrypt key file with GPG."
echo "Falling back to passphrase."
key_file=
fi
elif [ -r "${key_file}" ]; then
cp "${key_file}" /keyfile.bin
key_file=/keyfile.bin
fi

## end key retrieval
## start device setup

# parse options
luksoptions=""
for option in ${options//,/ }; do
case "$option" in
discard)
luksoptions="$luksoptions --allow-discards"
;;
*)
echo "Warning: ignoring unknown crypttab option: $option"
esac
done

# resolve block device
if resolved=$(resolve_device "${device_path}" "${rootdelay}"); then
if cryptsetup isLuks "${device_path}"; then
# LUKS devices

# open device
if [ -n "$key_file" ]; then
if ! eval cryptsetup luksOpen --key-file="${key_file}" $luksoptions "${resolved}" "${mapped_name}"; then
echo "WARNING: Failed to luksOpen crypto device ${device_path}"
fi
rm -f "$key_file"
else
if ! eval cryptsetup luksOpen $luksoptions "${resolved}" "${mapped_name}"; then
echo "WARNING: Failed to luksOpen crypto device ${device_path}"
fi
fi
else
# non-LUKS
echo "ERROR: ${device_path} is not a LUKS volume."
echo "Plain dm-crypt is not supported by the scencrypt hook."
fi
else
echo "WARNING: Failed to resolve crypto device ${device_path}"
fi
done

rm -rf /etc/initcpio/gpg
killall gpg-agent scdaemon pcscd
}

# vim: set ft=sh ts=4 sw=4 et:
75 changes: 75 additions & 0 deletions scencrypt-install
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash

build() {
local mod

add_module dm-crypt
if [[ $CRYPTO_MODULES ]]; then
for mod in $CRYPTO_MODULES; do
add_module "$mod"
done
else
add_all_modules '/crypto/'
fi

if [ -d /etc/initcpio/gpg ]; then
rm -rf /etc/initcpio/gpg
fi

mkdir -p /etc/initcpio/gpg
chmod 0700 /etc/initcpio/gpg
echo "pinentry-program /usr/bin/pinentry-tty" > /etc/initcpio/gpg/gpg-agent.conf

add_binary "cryptsetup"
add_binary "dmsetup"
add_binary "gpg"
add_binary "gpg-agent"
add_binary "pinentry-tty"
add_binary "pcscd"
add_binary "/usr/lib/gnupg/scdaemon"
add_file "/etc/initcpio/gpg/gpg-agent.conf"
add_file "/usr/lib/udev/rules.d/10-dm.rules"
add_file "/usr/lib/udev/rules.d/13-dm-disk.rules"
add_file "/usr/lib/udev/rules.d/95-dm-notify.rules"
add_file "/usr/lib/initcpio/udev/11-dm-initramfs.rules" "/usr/lib/udev/rules.d/11-dm-initramfs.rules"

add_file "/etc/crypttab"

sed -re 's;#.*$;;g' -e '/^[ \t]*$/ d' /etc/crypttab | awk '{print $3;}' | \
while read f; do
if [ "${f:0:1}" = "/" -a -f "$f" ]; then
add_file "$f"
keyid=($(file $f | egrep -o '[A-F0-9]{8}' | sed -re 's;([0-9A-F]{2});\1 ;g'))
keyid=${keyid[3]}${keyid[2]}${keyid[1]}${keyid[0]}${keyid[7]}${keyid[6]}${keyid[5]}${keyid[4]}
gpg --homedir /root/.gnupg --export-secret-keys 0x${keyid} | gpg --homedir /etc/initcpio/gpg --import
fi
done

add_file "/etc/initcpio/gpg/pubring.kbx"
#for f in /root/.gnupg/private-keys-v1.d/*.key; do
# add_file "$f" "/etc/initcpio/gpg/${f#/root/.gnupg/}"
#done

add_runscript
}

help() {
cat <<HELPEOF
This hook allows for an encrypted root device. Users should specify the device
to be unlocked using 'cryptdevice=device:dmname' on the kernel command line,
where 'device' is the path to the raw device, and 'dmname' is the name given to
the device after unlocking, and will be available as /dev/mapper/dmname.
For unlocking via keyfile, 'cryptkey=device:fstype:path' should be specified on
the kernel cmdline, where 'device' represents the raw block device where the key
exists, 'fstype' is the filesystem type of 'device' (or auto), and 'path' is
the absolute path of the keyfile within the device.
Without specifying a keyfile, you will be prompted for the password at runtime.
This means you must have a keyboard available to input it, and you may need
the keymap hook as well to ensure that the keyboard is using the layout you
expect.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:

0 comments on commit 8634128

Please sign in to comment.