How to load LUKS passphrase from USB, falling back to keyboard?

前端 未结 5 573
轻奢々
轻奢々 2021-01-30 23:58

I want to set up a headless Linux (Debian Wheezy) PC with whole disk encryption, with the ability to unlock the disk either with a USB drive, or by entering a passphrase by keyb

相关标签:
5条回答
  • 2021-01-31 00:37

    It would be ideal to me if I could simply have a small USB stick containing a passphrase that will unlock the disk. Not only would that be handy for servers (where you could leave the USB stick in the server - the goal is to be able to return broken harddisks without having to worry about confidential data), it would also be great for my laptop: Insert the USB stick when booting and remove it after unlocking the cryptodisk.

    I have now written a patch that will search the root dir of all devices for the file 'cryptkey.txt' and try decrypting with each line as a key. If that fails: Revert to typing in the pass phrase.

    It does mean the key cannot contain \n, but that would apply to any typed in key, too. The good part is that you can use the same USB disk to store the key for multiple machines: You do not need a separate USB disk for each. So if you have a USB drive in your physical key ring, you can use the same drive for all the machines you boot when being physically close.

    You add the key with:

    cryptsetup luksAddKey /dev/sda5
    

    And then put the same key as a line in a file on the USB/MMC disk called 'cryptkey.txt'. The patch is here:

    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=864647

    If the USB drivers, MMC drivers or the filesystems are not present in your initramfs, you need to add them by adding to /etc/initramfs-tools/modules:

    uhci_hcd
    ehci_hcd
    usb_storage
    nls_utf8
    nls_cp437
    vfat
    fat
    sd_mod
    mmc_block
    tifm_sd
    tifm_core
    mmc_core
    tifm_7xx1
    sdhci
    sdhci_pci
    

    When all is done, update the initramfs:

    update-initramfs -u
    

    It can be found as patch and file at: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb

    0 讨论(0)
  • 2021-01-31 00:44

    Here is a solution similar to the one by Andrew, but

    • using CRYPTTAB_TRIED described in the Debian crypttab man page to distinguish tries, and

    • calling the existing standard keyscript /lib/cryptsetup/scripts/passdev on the first try.

    1. Create your keyfile or keypartition as usual for the passdev script.

    2. Create the following file /usr/local/bin/key-from-usb and make it executable.

      #!/bin/sh
      set -e
      if [ $CRYPTTAB_TRIED -ge 1 ]; then
        /lib/cryptsetup/askpass "Second try to unlock $CRYPTTAB_SOURCE ($CRYPTTAB_NAME). Please enter passphrase: "
      else
        /lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY
      fi
      
    3. In /etc/crypttab use the parameter keyscript=/usr/local/bin/key-from-usb.

    4. Create /etc/initramfs-tools/hooks/key-from-usb with this content:

      #!/bin/sh
      
      PREREQ=""
      
      prereqs() {
              echo "$PREREQ"
      }
      
      case "$1" in
               prereqs)
                       prereqs
                       exit 0
               ;;
      esac
      
      . "${CONFDIR}/initramfs.conf"
      . /usr/share/initramfs-tools/hook-functions
      
      manual_add_modules vfat
      
      copy_exec /usr/lib/cryptsetup/scripts/passdev /usr/lib/cryptsetup/scripts/passdev
      
      copy_exec /usr/local/bin/key-from-usb /usr/local/bin/key-from-usb
      

      The first copy_exec line here is needed because passdev is not copied if it is not mentioned in crypttab. Similarly, manual_add_modules vfat will ensure that a vfat usb disk can still be used.

    Hint: Use lsinitramfs /boot/initrd.img-... and diff/compare the results to check that the script and all its dependencies are included.

    0 讨论(0)
  • 2021-01-31 00:50

    A lot of my solution is derived from the post, Using A USB Key For The LUKS Passphrase.

    1. Create a random passphrase:

      dd if=/dev/urandom bs=1 count=256 > passphrase
      
    2. Insert a USB drive. dmesg output will show the device name; assume /dev/sdd. Figure out its size:

      blockdev --getsize64 /dev/sdd
      
    3. I decided to install the passphrase at the end of the raw device, figuring it might survive any accidental use of the USB drive.

      dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
      
    4. Add the passphrase to the LUKS volume:

      cryptsetup luksAddKey /dev/sda5 passphrase
      

      This does not affect the existing hand-entered passphrase from the installer. The passphrase file can be deleted:

      rm passphrase
      
    5. Find a unique name for the USB stick, so we can identify it when present:

      ls -l /dev/disk/by-id | grep -w sdd
      

      You should see one symlink. I will call it /dev/disk/by-id/<ID>.

    6. Edit /etc/crypttab. You should see a line like:

      sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 none luks
      

      Modify it to:

      sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
      
    7. The keyscript referred to above will need to read the passphrase from the USB device. However, it needs to do more than that. To understand how it is used, check /usr/share/initramfs-tools/scripts/local-top/cryptroot, the script that runs at boot time to unlock the root device. Note when a keyscript is set, it is simply run and the output piped to luksOpen with no other checking. There is no way to signal an error (USB drive not present) or fall back to keyboard input. If the passphrase fails, the keyscript is run again in a loop, up to some number of times; however we are not told which iteration we are on. Also, we have no control over when the keyscript is run, so we can't be sure Linux has recognized the USB drive.

      I addressed this with some hacks:

      1. Poll on the USB drive and wait 3 seconds for it to appear. This works for me, but I would love to know a better way.

      2. Create a dummy file /passphrase-from-usb-tried on first run to indicate that we have been run at least once.

      3. If we have been run at least once, or the USB drive cannot be found, run the askpass program used by cryptroot for keyboard input.

      The final script:

      #!/bin/sh
      
      set -e
      
      if ! [ -e /passphrase-from-usb-tried ]; then
          touch /passphrase-from-usb-tried
          if ! [ -e "$CRYPTTAB_KEY" ]; then
              echo "Waiting for USB stick to be recognized..." >&2
              sleep 3
          fi
          if [ -e "$CRYPTTAB_KEY" ]; then
              echo "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) from USB key" >&2
              dd if="$CRYPTTAB_KEY" bs=1 skip=129498880 count=256 2>/dev/null
              exit
          else
              echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
          fi
      fi
      
      /lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME)\nEnter passphrase: "
      

      Finally, we need to ensure that this script is available in the initramfs. Create /etc/initramfs-tools/hooks/passphrase-from-usb containing:

      #!/bin/sh
      
      PREREQ=""
      
      prereqs() {
              echo "$PREREQ"
      }
      
      case "$1" in
              prereqs)
                      prereqs
                      exit 0
              ;;
      esac
      
      . "${CONFDIR}/initramfs.conf"
      . /usr/share/initramfs-tools/hook-functions
      
      copy_exec /bin/passphrase-from-usb /bin
      
    8. The USB drivers were not present in my initramfs. (It appears they are by default in later versions of Debian.) I had to add them by adding to /etc/initramfs-tools/modules:

      uhci_hcd
      ehci_hcd
      usb_storage
      
    9. When all is done, update the initramfs:

      update-initramfs -u
      
    0 讨论(0)
  • 2021-01-31 00:59

    To accompany excellent answers above please see C routines you could use to write/generate and read raw block device key. The "readkey.c" extracts key of given size from block device and "writekey.c" can generate or write existing key to raw device. The "readkey.c" once compiled can be used in custom script to extract key of known size from raw block device like so:

    readkey </path/to/device> <keysize>
    

    To see usage for "writekey", after compiled run it with no flags.
    To compile just use:

    gcc readkey.c -o readkey
    
    gcc writekey.c -o writekey
    

    I tested both on Verbatim 16GB USB 2.0 USB flash drive with custom "keyscript=" in crypttab also published below. The idea for "crypto-usb.sh" is from "debian etch" cryptsetup guide.

    crypto-usb.sh:

    #!/bin/sh
    echo ">>> Trying to get the key from agreed space <<<" >&2
    modprobe usb-storage >/dev/null 2>&1
    sleep 4
    OPENED=0
    disk="/sys/block/sdb"
    boot_dir="/boot"
    readkey="/boot/key/readkey"
    echo ">>> Trying device: $disk <<<" >&2
    F=$disk/dev
    if [ 0`cat $disk/removable` -eq 1 -a -f $F ]; then
        mkdir -p $boot_dir
        mount /dev/sda1 $boot_dir -t ext2 >&2
        echo ">>> Attempting key extraction <<<" >&2
        if [ -f $readkey ]; then
            # prints key array to the caller
            $readkey /dev/sdb 4096
            OPENED=1
        fi
        umount $boot_dir >&2
    fi
    
    
    if [ $OPENED -eq 0 ]; then
        echo "!!! FAILED to find suitable key !!!" >&2
        echo -n ">>> Try to enter your password: " >&2
        read -s -r A
        echo -n "$A"
    else
        echo ">>> Success loading key <<<" >&2
    fi
    

    When generating the key size of the key has to be provided, generated key is saved to ".tmpckey" file with file permissions 0600 for later use. When writing existing key, size is determined by measuring the existing key size. This looks like complex approach however once compiled with simple "gcc" it can provide easy way of manipulating the raw key content.

    readkey.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void main(int argc, char *argv[])
    {
        int blockSize = 512;
        int keySize = 2048; 
    
        FILE *device;       
    
        if (  argc == 3 
               && (sizeof(argv[1]) / sizeof(char)) > 1
               && (sizeof(argv[2]) / sizeof(char)) > 1
           && (atoi(argv[2]) % 512) == 0
           ) {
            device = fopen(argv[1], "r");
            if(device == NULL) { 
                printf("\nI got trouble opening the device %s\n", argv[1]);
                exit(EXIT_FAILURE);
            }
            keySize = atoi(argv[2]);        
        }
        else if (  argc == 2 
                && (sizeof(argv[1]) / sizeof(char)) > 1
            ) {
            device = fopen(argv[1], "r");
            if(device == NULL) { 
                printf("\nI got trouble opening the device %s\n", argv[1]);
                exit(EXIT_FAILURE);
            }
    
        }
        else {
    
            printf("\nUsage: \n");
            printf("\nKey Size Provided: \n");
            printf("\n\t\treadkey </path/to/device> <keysize> \n");
            printf("\nDefault key size: %d\n", keySize);
            printf("\n\t\treadkey </path/to/device>\n");
            exit(1);
        }
    
        int count;
    
        char *block;
    
        /* Verify if key is multiple of blocks */
        int numBlocks = 0;
        if (keySize % 512 != 0) {
           printf("\nSory but key size is not multiple of block size, try again. TA.\n");
           exit(1);
        }
    
        /* Seek till the end to get disk size and position to start */
        fseek(device, 0, SEEK_END);
    
        /* Determine where is the end */
        long endOfDisk = ftell(device);
    
        /* Make sure we start again */
        rewind(device); // Do I need it ???
    
        /* Get the required amount minus block size */
        long startFrom = endOfDisk - blockSize - keySize;
    
        /* Allocate space for bloc */
        block = calloc(keySize, sizeof(char));
    
        /* Start reading from specified block */
        fseek(device, startFrom, SEEK_SET);
        fread(block, 1, keySize, device);
    
        /* Do something with the data */
        for(count = 0; count < keySize/*sizeof(block)*/; count++){
            printf("%c", block[count]);
        }
    
        /* Close file */
        fclose(device);
    
        /* Make sure freed array is zeroed */
        memset(block, 0, keySize);
        free(block);
    }
    

    writekey.c:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char *argv[])
    {
        int blockSize = 512;
        int keySize = 2048;
    
        int count;
    
        unsigned char *block;
    
        /*
            Thing to always remember that argv starts from 0 - the name of the program, and argc starts from 1 i.e. 1 is the name of the program.
        */
        if ( argc == 3 
           && strcmp(argv[1], "genwrite") != 0
           && (sizeof(argv[2]) / sizeof(char)) > 2
           ) {
            char ch;
            FILE *keyF;
            keyF = fopen(argv[1], "r");
            if (keyF == NULL) exit(EXIT_FAILURE);
    
            /* Tell key Size */
            fseek(keyF, 0, SEEK_END);
            keySize = ftell(keyF);
            rewind(keyF);
            printf("\nKey Size: %d\n", keySize);
    
            block = calloc(keySize, sizeof(char));
            printf("\n-- Start Key --:\n");
                    for(count = 0; count < keySize/*sizeof(block)*/; count++){
                char ch = fgetc(keyF);
                            block[count] = ch;
                /*
                  Uncomment below to see your key on screen
                */
                // printf("%c",ch);
    
                    }
            printf("\n-- End Key --:\n");
            fclose(keyF);
        }
        else if (  argc == 3 
            && strcmp(argv[1], "genwrite") == 0 
            && (sizeof(argv[2]) / sizeof(char)) > 2
            ) 
            {
            printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
            block = calloc(keySize, sizeof(char));
            int count;
            for(count = 0; count < keySize/*sizeof(block)*/; count++){
                block[count] = (char) rand();
            }
            FILE *tmpfile;
            tmpfile = fopen(".tmpckey", "w");
            if(tmpfile == NULL) exit(EXIT_FAILURE);
            fwrite(block, 1, keySize, tmpfile);
            fclose(tmpfile);
            chmod(".tmpckey", 0600);
        }
        else if (  argc == 4 
            && strcmp(argv[1], "genwrite") == 0
            && (sizeof(argv[2]) / sizeof(char)) > 2
            && ((atoi(argv[3]) % 512) == 0)
            ) 
            {
            keySize = atoi(argv[3]);
            printf("\n-- Attempting to create random key(ish --) of size: %d\n", keySize);
            block = calloc(keySize, sizeof(char));
            int count;
            for(count = 0; count < keySize/*sizeof(block)*/; count++){
                block[count] = (char) rand();
            }
            FILE *tmpfile;
            tmpfile = fopen(".tmpckey", "w");
            if(tmpfile == NULL) exit(EXIT_FAILURE);
            fwrite(block, 1, keySize, tmpfile);
            fclose(tmpfile);
            chmod(".tmpckey", 0600);
        }   
        else {
            printf("\n");
            printf("################################################################################\n");
            printf("#                                                                              #\n");
            printf("#                              Usage:                                          #\n");
            printf("#                                                                              #\n");
            printf("################################################################################\n");
            printf("#> To write existing key to device:                                            #\n");
            printf("#                                                                              #\n");
            printf("#     writekey </path/to/keyfile> </path/to/removable/sd*>                     #\n");
            printf("#                                                                              #\n");
            printf("#> To generate and write pseudo random key,                                    #\n");
            printf("#> key will be saved to temporary file .tmpckey                                #\n");
            printf("#                                                                              #\n");
            printf("#     writekey genwrite </path/to/removable/sd*> <keysize in multiples of 512> #\n");
            printf("#                                                                              #\n");
            printf("#> When keysize is not provided default size is set to %d.                     #\n", keySize);
            printf("#                                                                              #\n");
            printf("################################################################################\n");
            exit(1);
        }
    
        /*
            Some printf debugging below, uncomment when needed to see what is going on.
        */
        /*
        printf("\nNumber of Args: %d\n", argc);
        printf("\nCurrently block array contains: \n");
        for(count = 0; count < keySize; count++){
            printf("%c", block[count]);
        }
        printf("\n-- End block -- \n");
        */
        /* Open Device itp... */
        FILE *device = fopen(argv[2], "a");
        if(device == NULL) exit(EXIT_FAILURE);
    
        printf("\nDevice to write: %s\n", argv[2]);
    
        fseek(device, 0, SEEK_END);
    
        /* Determine where is the end */
        long endOfDisk = ftell(device);
        printf("\nDevice Size: %ld\n", endOfDisk);
    
        /* Verify if key is multiple of blocks */
        int numBlocks = 0;
        if (keySize % 512 != 0 || endOfDisk < (blockSize + keySize) ) {
                printf("\nSorry but key size is not multiple of block size or device you trying to write to is too small, try again. TA.\n");
            fclose(device);
                exit(1);
        }
    
    
    
        /* Make sure we start again */
        rewind(device);
    
        /* Get the required amount sunbstracting block size */
        long startFrom = endOfDisk - blockSize - keySize;
    
        /* Write some data to the disk */
        printf("\nWriting data starting from: %ld\n", startFrom);
        fseek(device, startFrom, SEEK_SET);
        fwrite(block, 1, keySize, device);
        printf("\nBlock Position after data write procedure : %ld\n", ftell(device));
    
        /*
            Below is just for convenience, to read what was written,
            can aid in debugging hence left commented for later.
        */
        /*
        printf("\nAmount of Data written : %ld\n", ftell(device) - startFrom);
    
        // Start reading from specified block 
        printf("\n>>>>>>>> DEBUGGING SECTION <<<<<<<<<\n");
        rewind(device); //
        fseek(device, startFrom, SEEK_SET);
        printf("\nBlock Position before read attempted: %d\n", ftell(device));
        printf("\nKey size: %d\n", keySize);
        fread(block, 1, keySize, device);
    
        // Do something with the data
        printf("\nBlock Position startFrom: %ld\n", startFrom);
        printf("\nBlock Position after read: %d\n", ftell(device));
        printf("\n-- Buffer Read: --\n");
        for(count = 0; count < keySize; count++){
            printf("%c", block[count]);
        }
        printf("\n-- End block -- \n");
        printf("\n--  -- \n");
        printf("\n--  -- \n");
        */
    
        /* Close file */
        fclose(device);
    
        /* Make sure freed array is zeroed */
        memset(block, 0, keySize);
        free(block);
    
    /* Return success, might change it to be useful return not place holder */
    return 0;
    }
    

    To verify key written to raw device is the same as the one in file(below will output nothing if keys are identical):

    diff -B <(./readkey </path/to/device> 4096) <(cat .tmpckey)
    

    Or for existing key generated using own means:

    diff -B <(./readkey </path/to/device> <generated elsewhere key size>) <(cat </path/to/keyfile>)
    

    Thank You

    0 讨论(0)
  • 2021-01-31 01:01

    despite the great answer from @Andrew which works in previous versions. The solution actually is outdated and needs lots of tuning for ubuntu 18.04 and 19.10. So I want to share my research on this.

    There are several catches about crypttab. The sepcs actually changed a lot from 14.04 to 18.04 and to 19.10. It starts to support more parameters for cryptsetup. For example keyfile-offset, keyfile-size, etc. Some of the options e.g. nobootwait are gone. Some parameters supported in other distro already but is not supported in ubuntu yet (for example very nice parameter keyfile-timeout. This can eliminate the entire keyscript since it will automatically fallback to keyboard input after the keyfile-timeout.)

    The major pit-fall for crypttab on ubuntu is that it actually processed by 2 different processes. One is the traditionally initramfs and another is the modern systemd. Systemd is supposed to be more advanced and flexiable in many aspect. However, systemd has poor support for crypptab, there are many options such as keyscript just silently ignored. so I have no idea what is going on, until I spotted this post. Almost all the posts online about crypttab settings is for initramfs not for systemd. So we need to add initramfs to all the entries in crypttab to avoid problems.

    I also discovered a nice way to debug our keyscript and crypttab without VM or repeatedly rebooting. It is cryptdisks_start. Before we actually propagate our changes to initramfs, we should always test it with this nice command. Otherwise, you have to end-up locked out from your system and can only recover it through chroot environment.

    @andrew posted a nice way to use data hide in the raw area of a file system. However, I found it is very annoying when we want to automatically create partitions and dd the raw data to lots of usbkeys, we have to calculate the offset for all different file systems and different partition sizes. Moreover, if a user accidentally write onto the FS, there is some risk that the key got overritten. A raw partition without any FS on it makes more sense in this case. However raw partition does not have UUID which is not very useful for automatic unlocking. Thus, I would like introduce a way just use normal passphrase files on the usbkey filesystem. The major issue of passdev is it does not seek/stop during reading the file. Thus we cannot use the keyfile-offset and keyfile-size option when we want to fallback to keyboard input. Because cryptsetup will actually try to skip in the input content and if the content is shorter than keyfile-size, it raises an error. This also means if there is large offset, passdev can be very slow since it always read from beginning. However, there is no point to implement offset and keyfile size for a actual file on file system. I believe those are created for raw device.

    The crypttab

    luks-part UUID="<uuid>" /dev/disk/by-uuid/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec> luks,keyfile-offset=<seek to the key>,keyfile-size=<>,keyscript=/bin/passphrase-from-usbfs.sh,tries=<number of times to try>,initramfs
    

    the keyscript passphrase-from-usbfs.sh utilized the /lib/cryptsetup/scripts/passdev which will wait the usb device and mount the fs then pipe out the file content. It supports the CRYPTTAB_KEY in format of /device-path/<keyfile FS uuid>:/<keyfile path relative to usbkey root>:<timeout in sec>.

    #!/bin/sh
    #all message need to echo to stderr, the stdout is used for passphrase
    # TODO: we may need to do something about the plymouth
    echo "CRYPTTAB_KEY=$CRYPTTAB_KEY" >&2
    echo "CRYPTTAB_OPTION_keyfile_offset=$CRYPTTAB_OPTION_keyfile_offset" >&2
    #set your offset and file size here if your system does not support those paramters
    #CRYPTTAB_OPTION_keyfile_offset=
    #CRYPTTAB_OPTION_keyfile_size=
    echo "timeout=$CRYPTTAB_OPTION_keyfile_timeout" >&2
    CRYPTTAB_OPTION_keyfile_timeout=10 # keyfile-timeout is not supported yet 
    pass=$(/lib/cryptsetup/scripts/passdev $CRYPTTAB_KEY)
    rc=$?
    if ! [ $rc -eq 0 ]; then
        echo "Can't find $CRYPTTAB_KEY; USB stick not present?" >&2
        /lib/cryptsetup/askpass "Unlocking the disk $CRYPTTAB_SOURCE ($CRYPTTAB_NAME) Enter passphrase: "
    else
        echo "successfully load passphrase." >&2
        echo -n $pass
    fi
    
    

    The hook tell update-initramfs to copy our scripts.

    #!/bin/sh
    
    PREREQ=""
    
    prereqs() {
            echo "$PREREQ"
    }
    
    case "$1" in
            prereqs)
                    prereqs
                    exit 0
            ;;
    esac
    
    . "${CONFDIR}/initramfs.conf"
    . /usr/share/initramfs-tools/hook-functions
    copy_exec /bin/passphrase-from-usbfs.sh
    copy_exec /bin/passphrase-from-usb.sh
    #when using passdev we need to hook additionaly FS and binary
    copy_exec /lib/cryptsetup/scripts/passdev
    manual_add_modules ext4 ext3 ext2 vfat btrfs reiserfs xfs jfs ntfs iso9660 udf
    

    Finally I posted the updated version of passphrase-from-usb.sh which can use the new parameters in crypttab:

    0 讨论(0)
提交回复
热议问题