web-dev-qa-db-ja.com

USBからLUKSパスフレーズをロードしてキーボードにフォールバックする方法は?

USBドライブを使用するか、キーボードでパスフレーズを入力してディスクのロックを解除できる、ディスク全体を暗号化したヘッドレスLinux(Debian Wheezy)PCをセットアップしたいと思います。私の出発点は、Debianインストーラの基本的な全ディスク暗号化オプションを使用した新規インストールです。これは/ boot以外のすべてをLUKS暗号化論理ボリュームグループとして管理し、キーボードオプションを提供します。私の現在の解決策を回答で説明します。それが有用であり、他の人がそれを改善できることを願っています。

これが私が抱えていた問題のいくつかです:

  • パスフレーズを設定してUSBドライブに保存します。

  • USBモジュールを時間内にロードします。

  • USBドライブがLinuxによって認識されるのを待ってから、読み取りを試みます。

  • 正しいUSBドライブを特定する(たまたま挿入された他のドライブではない)。

  • USBドライブからパスフレーズを引き出すための「キースクリプト」を書く。

  • すべてのUSB障害の場合に、キーボードへのフォールバックが確実に開始されるようにします。

大幅に改善された回答を受け入れ、貢献を提供する回答に賛成します。

13
Andrew

私の解決策の多くは、投稿から派生しています LUKSパスフレーズにUSBキーを使用

  1. ランダムなパスフレーズを作成します。

    dd if=/dev/urandom bs=1 count=256 > passphrase
    
  2. USBドライブを挿入します。 dmesg出力にはデバイス名が表示されます。 /dev/sddを想定します。そのサイズを把握します。

    blockdev --getsize64 /dev/sdd
    
  3. USBドライブを誤って使用しても耐えられると考えて、rawデバイスの最後にパスフレーズをインストールすることにしました。

    dd if=passphrase of=/dev/sdd bs=1 seek=<size-256>
    
  4. LUKSボリュームにパスフレーズを追加します。

    cryptsetup luksAddKey /dev/sda5 passphrase
    

    これは、インストーラーからの既存の手入力パスフレーズには影響しません。パスフレーズファイルは削除できます。

    rm passphrase
    
  5. USBスティックの一意の名前を見つけて、存在する場合に識別できるようにします。

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

    1つのシンボリックリンクが表示されます。私はそれを/dev/disk/by-id/<ID>と呼びます。

  6. /etc/crypttabを編集します。次のような行が表示されます。

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

    次のように変更します。

    sdc5_crypt UUID=b9570e0f-3bd3-40b0-801f-ee20ac460207 /dev/disk/by-id/<ID> luks,keyscript=/bin/passphrase-from-usb
    
  7. 上記のkeyscriptは、USBデバイスからパスフレーズを読み取る必要があります。ただし、それ以上のことを行う必要があります。使用方法を理解するには、ブート時に実行されてルートデバイスのロックを解除するスクリプトである/usr/share/initramfs-tools/scripts/local-top/cryptrootを確認してください。 keyscriptが設定されている場合、それは単に実行され、出力は他のチェックなしでluksOpenにパイプされることに注意してください。エラー(USBドライブが存在しない)を通知したり、キーボード入力にフォールバックしたりする方法はありません。パスフレーズが失敗した場合、キースクリプトはループ内で最大数回実行されます。ただし、どの反復を行っているかはわかりません。また、キースクリプトがいつ実行されるかを制御できないため、LinuxがUSBドライブを認識したかどうかを確認できません。

    私はいくつかのハックでこれに対処しました:

    1. USBドライブをポーリングし、表示されるまで3秒待ちます。これは私にとってはうまくいきますが、もっと良い方法を知りたいです。

    2. 最初の実行時にダミーファイル/passphrase-from-usb-triedを作成して、少なくとも1回実行されたことを示します。

    3. 少なくとも1回実行したことがある場合、またはUSBドライブが見つからない場合は、キーボード入力にaskpassが使用するcryptrootプログラムを実行します。

    最終的なスクリプト:

    #!/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: "
    

    最後に、このスクリプトがinitramfsで使用可能であることを確認する必要があります。以下を含む/etc/initramfs-tools/hooks/passphrase-from-usbを作成します。

    #!/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. USBドライバーが私のinitramfsに存在しませんでした。 (Debianの新しいバージョンではデフォルトであるようです。)/etc/initramfs-tools/modulesに追加して追加する必要がありました。

    uhci_hcd
    ehci_hcd
    usb_storage
    
  9. すべて完了したら、initramfsを更新します。

    update-initramfs -u
    
21
Andrew

ディスクのロックを解除するパスフレーズを含む小さなUSBスティックを手に入れることができれば理想的です。それはサーバーにとって便利であるだけでなく(サーバーにUSBスティックを残すことができます-目標は機密データを心配することなく壊れたハードディスクを返すことができることです)、それは私のラップトップにも最適です:挿入起動時にUSBスティックを使用し、暗号ディスクのロックを解除してから取り外します。

これで、すべてのデバイスのルートディレクトリでファイル「cryptkey.txt」を検索し、各行をキーとして復号化を試みるパッチを作成しました。それが失敗した場合:パスフレーズの入力に戻ります。

これは、キーに\ nを含めることができないことを意味しますが、これは入力されたキーにも当てはまります。良い点は、同じUSBディスクを使用して複数のマシンのキーを保存できることです。それぞれに個別のUSBディスクは必要ありません。したがって、物理キーリングにUSBドライブがある場合は、物理的に近いときに起動するすべてのマシンに同じドライブを使用できます。

次のコマンドでキーを追加します。

cryptsetup luksAddKey /dev/sda5

次に、「cryptkey.txt」という名前のUSB/MMCディスク上のファイルの行と同じキーを配置します。パッチはここにあります:

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

USBドライバー、MMCドライバーまたはファイルシステムがinitramfsに存在しない場合は、/ 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

すべて完了したら、initramfsを更新します。

update-initramfs -u

パッチとファイルとして次の場所にあります: https://gitlab.com/ole.tange/tangetools/tree/master/decrypt-root-with-usb

4
Ole Tange

以前のバージョンで機能する@Andrewからの素晴らしい回答にもかかわらず。ソリューションは実際には時代遅れであり、ubuntu18.04と19.10のために多くの調整が必要です。それで、私はこれに関する私の研究を共有したいと思います。

Crypttabにはいくつかの落とし穴があります。 sepcsは実際には14.04から18.04そして19.10に大きく変化しました。 cryptsetupのより多くのパラメーターのサポートを開始します。たとえば、keyfile-offset、keyfile-sizeなど。いくつかのオプション。 nobootwaitはなくなりました。一部のパラメーターはすでに他のディストリビューションでサポートされていますが、ubuntuではまだサポートされていません(たとえば、非常に優れたパラメーターkeyfile-timeout。これにより、keyfile-timeout後にキーボード入力に自動的にフォールバックするため、キースクリプト全体を削除できます)。

Ubuntuのcrypttabの主な落とし穴は、実際には2つの異なるプロセスで処理されることです。 1つは伝統的なinitramfsで、もう1つは最新のsystemdです。 Systemdは、多くの面でより高度で柔軟性があると考えられています。ただし、systemdはcrypptabのサポートが不十分であり、keyscriptなどの多くのオプションが黙って無視されます。なので、何が起こっているのかわかりません。発見 この投稿 。 crypttab設定に関するオンラインのほとんどすべての投稿は、systemd用ではなくinitramfs用です。したがって、問題を回避するために、crypttabのすべてのエントリにinitramfsを追加する必要があります。

また、VMまたは繰り返し再起動せずに、keyscriptとcrypttabをデバッグするための優れた方法を発見しました。これはcryptdisks_startです。実際に変更をinitramfsに伝達する前に、常にこのNiceコマンド。それ以外の場合は、システムからロックアウトする必要があり、chroot環境を介してのみ回復できます。

@andrewは、ファイルシステムの未加工領域でデータ非表示を使用するための優れた方法を投稿しました。ただし、パーティションを自動的に作成し、生データを多くのusbkeyに追加する場合は、非常に煩わしいことがわかりました。すべての異なるファイルシステムと異なるパーティションサイズのオフセットを計算する必要があります。さらに、ユーザーが誤ってFSに書き込んだ場合、キーが上書きされるリスクがあります。この場合、FSがないrawパーティションの方が理にかなっています。ただし、rawパーティションには自動ロック解除にあまり役立たないUUIDがないため、使用する方法を紹介します。 usbkeyファイルシステム上の通常のパスフレーズファイルpassdevの主な問題は、ファイルの読み取り中にシーク/停止しないことです。したがって、必要なときにkeyfile-offsetおよびkeyfile-sizeオプションを使用できません。キーボード入力にフォールバックします。cryptsetupは実際には入力コンテンツをスキップしようとし、コンテンツがkeyfile-sizeより短い場合、エラーが発生します。これは、オフセットが大きい場合も意味します。 、passdevは常に最初から読み取るため、非常に遅くなる可能性があります。ただし、ファイルシステム上の実際のファイルにオフセットとキーファイルサイズを実装する意味はありません。これらはrawデバイス用に作成されていると思います。

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

keyscript passphrase-from-usbfs.shは、/lib/cryptsetup/scripts/passdevを利用しました。これは、usbデバイスを待機し、fsをマウントしてから、ファイルの内容をパイプアウトします。 CRYPTTAB_KEYの形式で/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

フックはupdate-initramfsにスクリプトをコピーするように指示します。

#!/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

最後に、crypttabの新しいパラメーターを使用できるpassphrase-from-usb.shの更新バージョンを投稿しました。

1
Wang

上記の優れた回答を伴うには、rawブロックデバイスキーの書き込み/生成および読み取りに使用できるCルーチンを参照してください。 「readkey.c」はブロックデバイスから指定されたサイズのキーを抽出し、「writekey.c」は既存のキーを生成したり、rawデバイスに書き込んだりできます。コンパイルされた「readkey.c」をカスタムスクリプトで使用して、次のようにrawブロックデバイスから既知のサイズのキーを抽出できます。

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

「writekey」の使用法を確認するには、コンパイル後、フラグなしで実行します。
コンパイルするには、次を使用します。

gcc readkey.c -o readkey

gcc writekey.c -o writekey

Verbatim 16GB USB 2.0 USBフラッシュドライブで両方をテストしました。crypttabのカスタム「keyscript =」も以下に公開されています。 「crypto-usb.sh」のアイデアは、「debianetch」cryptsetupガイドからのものです。

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

キーのキーサイズを生成する必要がある場合、生成されたキーは、後で使用するためにファイル権限0600で「.tmpckey」ファイルに保存されます。既存の鍵を書き込む場合、サイズは既存の鍵のサイズを測定することによって決定されます。これは複雑なアプローチのように見えますが、単純な「gcc」でコンパイルすると、生のキーコンテンツを操作する簡単な方法を提供できます。

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;
}

Rawデバイスに書き込まれたキーがファイル内のキーと同じであることを確認するには(キーが同一の場合、以下では何も出力されません)。

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

または、独自の手段を使用して生成された既存のキーの場合:

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

ありがとうございました

1
Daniel