implement utility to add gpg based data as token in LUKS header (DONE)
implement user space decrypt process using gpg data in token (DONE)
implement C based cryptsetup plugin to handle gpg data in token
I am improving integration of smartcards with LUKS. When using a smartcard the user enters a PIN rather than typing a passphrase to decrypt a LUKS container. With LUKS1 this requires storing a key in a file that is decrypted using the smartcard.
This model requires that the smartcard protects a key which involves storing the key somewhere. Currently the Linux distros support specifying the key location as part of the decrypt process. This is suboptimal as
the keyfile is not connected to the LUKS container
keyfile could be lost, deleted, overwritten, etc.
keyfile sits on a file system which requires another file system to exist somewhere that is NOT encrypted
decrypt process includes more potential points of failure than necessary
Using LUKS2 we can store the smartcard protected key in the JSON data in the LUKS header of the corresponding container.
do not need a keyfile at mkinitramfs (loose coupling)
boot process does not need a separate file system and keyfile for decrypt (loose coupling)
key information is kept in the container it corresponds to (tight cohesion)
The current reference implementation uses:
json features of the LUKS2 header
gpg based smartcard
gpg
custom scripts for setup and decrypt
Some models call this enrolling a smartcard. To add the smartcard feature to an existing LUKS2 container (/dev/sdb in this example) using the passphrase that already exists in keyslot 0:
cryptsetup-smartcard add /dev/sdb -p -k 0
That will interactively ask for the passphrase. Next, when I want to open the LUKS container using the smartcard I call the handle-token.sh script (this will be a C plugin in the future and work automagicly)
tokens/smartcard/handle-token.sh /dev/sdb 0
If I want a larger binary key to be added to another keyslot for use with the smartcard the command is simpler. This will interactively ask for the passphrase and create the binary key from random data. This model allows the container to be decrypted either using the passphrase or the smartcard based key:
cryptsetup-smartcard add /dev/sdb
Why would we do this? It allows us to use a complex passphrase that we don't have to type all the time--only when absolutely necessary. Some models call this the recovery passphrase. For most cases we type only the PIN associated with the smartcard and can be assured that the key is next to impossible for someone to guess or remember as it is random binary data.
Create LUKS container using typical passphrase setup
Add a large text block as the key in a new keyslot
Use gpg with a smartcard to ascii armor encrypt the text key
Add the encrypted text to a LUKS2 json token
Currently, there is a user space utility that will decrypt the container using the smartcard. The utility will extract the encrypted key and use gpg to decrypt it. Lastly, the key will be used to open the luks container. Of course gpg will prompt the user for a PIN during this process. If the smartcard cannot be found the process falls back to typical cryptsetup. Generally, that is to ask the user for a passphrase.
handle-token.sh /dev/sdX 0
I plan to create a token plugin for cryptsetup to make this automaticly work. When we run cryptsetup luksOpen the token subsystem should activate since the token information is in the keyslot.
Since we are working with json data using tools designed for it will reduce our work. I'm starting with jq for the bash implementation of the decrypt process. Once moving to a C based version I will have libjson available as it's already used by cryptsetup.
I have been looking at the 2.4.0 code base for cryptsetup. This is where the token features are most feature complete though tokens have been around since 2.0.0 release.
There is an example plugin for fetching a key over ssh. I am studying the ssh code because it is similar to what I want to do which is to use an external process to retrieve the key information needed to decrypt.
I first considered using an unbound keyslot to store the smartcard encrypted data but after discussions in the mailing list this is probably not the best approach. The unbound keyslot can hold any secret not related to LUKS but it gets encrypted the same as any other key. In my use case the encryption happens with an external smartcard based process.
LUKS2 headers include space for json data associated with keyslots. This is 'in the clear' data that is not encrypted. Since the smartcard is doing the encryption I have data that can be stored 'in the clear'. I put the encrypted key in a token in the json data.
There are cryptsetup commands to read/write the json data using the latest 2.4.0 release.
echo '{ "type" : "smartcard" , "keyslots" : [ ], "key" : "ascii armor data here" }' | cryptsetup token import /dev/sdb2 --key-slot 0
cryptsetup token export /dev/sdb2 --token-id 1
cryptsetup luksDump /dev/sdb2 --dump-json-metadata
cryptsetup luksDump $DEVICE --debug-json | tr -d '\n' | sed -E 's/^[^{]*//;s/[^}]*$//'
The first implementation for smartcard integration uses bash shell scripts with stock cryptsetup 2.4.0+ and LUKS2. There is a setup utility that can initialize a LUKS2 device or ADD the smartcard support to an existing device. The script cryptsetup-smartcard is currently in my fork of cryptsetup.
There is also a script for decrypting the LUKS2 container using the smartcard. It is currently at tokens/smartcard/handle-token.sh in my fork of the cryptsetup project.
cryptsetup-smartcard add /dev/sdb
That command will interactively add a binary key protected by gpg to the specified LUKS container on /dev/sdb. If you look at the luks header you will see that there is a token added and another keyslot filled. To decrypt and mount the LUKS container using the smartcard we pass the device and token id to the handler script.
./tokens/smartcard/handle-token.sh /dev/sdb 0
The default will add a binary key but you can elect to simply gpg encrypt your current passphrase. This is opened the same way with the handler. This option requires that the keyslot be specified so that the token header is set properly. In this example we specify keyslot 0 by passing -k 0 and we use -p to denote that we are just encrypting the passphrase:
cryptsetup-smartcard add /dev/sdb -p -k 0
There is a test suite at tests/smartcard/test.sh. It tests all functionality that I could think of.
A future implementation of this feature will use a generic C based token plugin in cryptsetup.
If every use case has to code a plugin in C it raises the barrier to entry for using LUKS tokens. Though the cleanest implementation is probably a C plugin it would make token access much easier if there were a generic plugin that calls a specified external program.