qemu-block
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-block] [PATCH v2 07/17] crypto: implement the LUKS block encry


From: Daniel P. Berrange
Subject: Re: [Qemu-block] [PATCH v2 07/17] crypto: implement the LUKS block encryption format
Date: Mon, 8 Feb 2016 16:03:35 +0000
User-agent: Mutt/1.5.24 (2015-08-30)

On Fri, Feb 05, 2016 at 10:38:45AM -0700, Eric Blake wrote:
> On 01/20/2016 10:38 AM, Daniel P. Berrange wrote:
> > Provide a block encryption implementation that follows the
> > LUKS/dm-crypt specification.
> > 
> > This supports all combinations of hash, cipher algorithm,
> > cipher mode and iv generator that are implemented by the
> > current crypto layer.
> > 
> > The notable missing feature is support for the 'xts'
> > cipher mode, which is commonly used for disk encryption
> > instead of 'cbc'. This is because it is not provided by
> > either nettle or libgcrypt. A suitable implementation
> > will be identified & integrated later.
> > 
> > There is support for opening existing volumes formatted
> > by dm-crypt, and for formatting new volumes. In the latter
> > case it will only use key slot 0.
> > 
> > Signed-off-by: Daniel P. Berrange <address@hidden>


> > +
> > +/*
> > + * Reference for format is
> > + *
> > + * docs/on-disk-format.pdf
> > + *
> > + * In 'cryptsetup' package source code
> > + *
> 
> Worth calling out the document version number? I reviewed based on
> version 1.2.1, dated Oct 2011.

Yep


> > +};
> > +
> > +
> > +struct QCryptoBlockLUKSKeySlot {
> > +    /* state of keyslot, enabled/disable */
> > +    uint32_t active;
> 
> May be worth a comment that this struct is written to disk in
> big-endian, but used in native-endian for most operations for
> convenience; to make sure future developers remember to put byteswaps in
> the correct locations.  But it looks like you handled endianness correctly.

Yes, will note it.

> 
> > +    /* iterations for PBKDF2 */
> > +    uint32_t iterations;
> > +    /* salt for PBKDF2 */
> > +    uint8_t salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> > +    /* start sector of key material */
> > +    uint32_t key_offset;
> > +    /* number of anti-forensic stripes */
> > +    uint32_t stripes;
> > +} QEMU_PACKED;
> 
> Packed makes sense since we are writing it straight to disk; although it
> looks like natural alignment prevails and this did not need to be
> packed.  Do we want some sort of BUG_ON() that we have the right size
> struct?

Yes, I'll add QEMU_BUILD_BUG_ON

> > +
> > +struct QCryptoBlockLUKSHeader {
> > +    /* 'L', 'U', 'K', 'S', '0xBA', '0xBE' */
> > +    char magic[QCRYPTO_BLOCK_LUKS_MAGIC_LEN];
> > +
> > +    /* LUKS version, currently 1 */
> > +    uint16_t version;
> > +
> > +    /* cipher name specification (aes, etc */
> 
> Missing )
> 
> > +    char cipher_name[QCRYPTO_BLOCK_LUKS_CIPHER_NAME_LEN];
> > +
> > +    /* cipher mode specification () */
> 
> text in the ()?
> 
> > +    char cipher_mode[QCRYPTO_BLOCK_LUKS_CIPHER_MODE_LEN];
> > +
> > +    /* hash specication () */
> 
> s/specication/specification/, text in the ()?

Will fix all these examples

> 
> > +    char hash_spec[QCRYPTO_BLOCK_LUKS_HASH_SPEC_LEN];
> > +
> > +    /* start offset of the volume data (in sectors) */
> > +    uint32_t payload_offset;
> 
> Should we say "in 512-byte sectors"?  I could not see anything in the
> LUKS specification that said whether SECTOR_SIZE can be larger than 512
> - if you have a bare metal 4k sector disk, does a payload_offset of 2
> mean 1024 or 8192 bytes into the disk?  Eww. Sounds like we should
> submit a bug against the LUKS spec to have it clarified.

cryptsetup is hardcoded to use 512 byte sectors currently. IIUC
they'll need a LUKS spec update before doing 4k sectors natively.

> 
> > +
> > +    /* Number of key bytes */
> > +    uint32_t key_bytes;
> > +
> > +    /* master key checksum after PBKDF2 */
> > +    uint8_t master_key_digest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> > +
> > +    /* salt for master key PBKDF2 */
> > +    uint8_t master_key_salt[QCRYPTO_BLOCK_LUKS_SALT_LEN];
> > +
> > +    /* iterations for master key PBKDF2 */
> > +    uint32_t master_key_iterations;
> > +
> > +    /* UUID of the partition */
> 
> Should we say "in standard ASCII representation"?
> 
> > +    uint8_t uuid[QCRYPTO_BLOCK_LUKS_UUID_LEN];
> > +
> > +    /* key slots */
> > +    QCryptoBlockLUKSKeySlot key_slots[QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS];
> > +} QEMU_PACKED;
> 
> Again, alignment should mean that we could get away without packing; but
> the packing doesn't hurt; and a BUG_ON may be nice to ensure size.

Yep, add QEMU_BUILD_BUG_ON

> > +/* XXX doesn't QEMU already have a string -> enum val convertor ? */
> > +static int qcrypto_block_luks_name_lookup(const char *name,
> 
> Yes, spelled qapi_enum_parse().  Works if the enum was defined in a
> .json file.
> 
> 
> > +
> > +    error_setg(errp, "%s %s not supported", type, name);
> > +    return 0;
> > +}
> > +
> > +#define qcrypto_block_luks_cipher_mode_lookup(name, errp)               \
> > +    qcrypto_block_luks_name_lookup(name,                                \
> > +                                   QCryptoCipherMode_lookup,            \
> > +                                   QCRYPTO_CIPHER_MODE__MAX,            \
> > +                                   "Cipher mode",                       \
> > +                                   errp)
> > +
> > +#define qcrypto_block_luks_hash_name_lookup(name, errp)                 \
> > +    qcrypto_block_luks_name_lookup(name,                                \
> > +                                   QCryptoHashAlgorithm_lookup,         \
> > +                                   QCRYPTO_HASH_ALG__MAX,               \
> > +                                   "Hash algorithm",                    \
> > +                                   errp)
> 
> That said, your macro wrappers tweak the error message, so that it is
> perhaps more legible than the standard failure mode of
> qapi_enum_parse().  So up to you if you want to keep the wrappers.

I'll change my XXX note to mention qapi_enum_parse() as a future
todo if we can improve its error reporting in some manner.


> > +static int
> > +qcrypto_block_luks_load_key(QCryptoBlock *block,
> > +                            QCryptoBlockLUKSKeySlot *slot,
> > +                            const char *password,
> > +                            QCryptoCipherAlgorithm cipheralg,
> > +                            QCryptoCipherMode ciphermode,
> > +                            QCryptoHashAlgorithm hash,
> > +                            QCryptoIVGenAlgorithm ivalg,
> > +                            QCryptoHashAlgorithm ivhash,
> > +                            uint8_t *masterkey,
> > +                            size_t masterkeylen,
> > +                            QCryptoBlockReadFunc readfunc,
> > +                            void *opaque,
> > +                            Error **errp)
> > +{
> > +    QCryptoBlockLUKS *luks = block->opaque;
> > +    uint8_t *splitkey;
> > +    size_t splitkeylen;
> > +    uint8_t *possiblekey;
> > +    int ret = -1;
> > +    ssize_t rv;
> > +    QCryptoCipher *cipher = NULL;
> > +    uint8_t keydigest[QCRYPTO_BLOCK_LUKS_DIGEST_LEN];
> > +    QCryptoIVGen *ivgen = NULL;
> > +    size_t niv;
> > +
> > +    if (slot->active != QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED) {
> > +        return 0;
> > +    }
> 
> Would be worth documenting the convention of '0' (no key found, but no
> errp set), '1' (successful key), and negative (errp set).

Yes, will document that.

> > +    splitkeylen = masterkeylen * slot->stripes;
> 
> Should we do any validation that masterkeylen was in bounds and this
> doesn't overflow?  I don't want a malicious byte stream masquerading as
> a LUKS header to cause us to misbehave.

masterkeylen comes from luks->header.key_bytes. We should put some
validation in for that earlier when we first get it off disk.

> For that matter, the spec states that luks->payload_offset is a
> write-once calculation at creation time, and that it could be recomputed
> from key layout.  Should we validate that the offset listed makes
> algorithmic sense with what we know about header and key data sizes for
> the parameters listed in the header?

While it says that the offsets could be recomputed, from a technical POV
there's no reason why all impls must use the same layout / alignment for
the key slots. So I feel it safer to just rely on the header values and
not try to enforce that they match a particular offset.

> Side note: I'm a bit surprised the upstream LUKS spec does not include
> any checksum over the header contents - it does a great job at ensuring
> the master key is only going to be found by someone knowing a correct
> password and that a single byte error in a key material section
> invalidates that entire slot's usefulness, but it is not as careful on
> what happens with certain single-byte errors in the header.
> 
> > +    splitkey = g_new0(uint8_t, splitkeylen);
> > +    possiblekey = g_new0(uint8_t, masterkeylen);
> > +
> > +    /*
> > +     * The user password is used to generate a (possible)
> > +     * decryption key. This may or may not successfully
> > +     * decrypt the master key - we just blindly assume
> > +     * the key is correct and validate the results of
> > +     * decryption later.
> > +     */
> > +    if (qcrypto_pbkdf2(hash,
> > +                       (const uint8_t *)password, strlen(password),
> > +                       slot->salt, QCRYPTO_BLOCK_LUKS_SALT_LEN,
> > +                       slot->iterations,
> > +                       possiblekey, masterkeylen,
> > +                       errp) < 0) {
> > +        goto cleanup;
> > +    }
> > +
> > +    /*
> > +     * We need to read the master key material from the
> > +     * LUKS key material header. What we're reading is
> > +     * not the raw master key, but rather the data after
> > +     * it has been passed through AFSplit and the result
> > +     * then encrypted.
> > +     */
> > +    rv = readfunc(block,
> > +                  slot->key_offset * 512ll,
> 
> I tend to write LL rather than ll, to make it obvious I didn't typo '1'.
>  Looks like we're hard-coding our sector size (not the first time); but
> maybe a constant instead of a magic number will make it easier for the
> poor guy down the road that tries to switch to 4k sectors.

I'll introduce a QCRYPTO_LUKS_SECTOR_SIZE constant throughout for
now.


> > +     * it back together to get the actual master key.
> > +     */
> > +    if (qcrypto_afsplit_decode(hash,
> 
> ...
> > +
> > +    if (memcmp(keydigest, luks->header.master_key_digest,
> > +               QCRYPTO_BLOCK_LUKS_DIGEST_LEN) == 0) {
> > +        /* Success, we got the right master key */
> > +        ret = 1;
> > +        goto cleanup;
> > +    }
> > +
> > +    /* Fail, user's password was not valid for this key slot,
> > +     * tell caller to try another slot */
> > +    ret = 0;
> > +
> 
> Matches the spec.  Yay!
> 
> Side observation: The LUKS spec doesn't say anything about parallelizing
> the lookup loop across all 8 key slots, nor any randomization on which
> slot should be attempted first.  The whole point of PBKDF2 iteration
> count is to burn CPU time so that an attacker can't brute force things
> easily.  That means it is VERY easy to tell by timing how many active
> slots were attempted before a key was found, if slots are tried serially
> and we immediately break the loop on the first successful decryption.
> Is there any information leak caused by the timing observations when
> serially searching among 8 active slots, when the master key is only
> found in slot 7 vs. slot 0?  But I don't see it as your problem to
> solve, so much as something to ask the upstream LUKS design team.

FWIW, cryptsetup will short circuit the search as soon as it finds
a valid slot, so we're matching their behaviour at least.

> > +static int
> > +qcrypto_block_luks_open(QCryptoBlock *block,
> > +                        QCryptoBlockOpenOptions *options,
> > +                        QCryptoBlockReadFunc readfunc,
> > +                        void *opaque,
> > +                        unsigned int flags,
> > +                        Error **errp)
> > +{
> 
> > +    /*
> > +     * The cipher_mode header contains a string that we have
> > +     * to further parse of the format
> 
> s/parse/parse,/
> 
> > +     *
> > +     *    <cipher-mode>-<iv-generator>[:<iv-hash>]
> > +     *
> > +     * eg  cbc-essiv:sha256, cbc-plain64
> > +     */
> > +    ivgen_name = strchr(luks->header.cipher_mode, '-');
> > +    if (!ivgen_name) {
> > +        ret = -EINVAL;
> > +        error_setg(errp, "Unexpected cipher mode string format %s",
> > +                   luks->header.cipher_mode);
> > +        goto fail;
> > +    }
> > +    *ivgen_name = '\0';
> > +    ivgen_name++;
> 
> We're modifying luks->header in place; we'd better not write it back out
> to disk in the modified form.  I guess you are okay for now - your code
> only reads existing LUKS disks, and can only create a single key in slot
> 0 on conversion (that is, I don't see code for key management of an
> existing image).  Probably things we should add later, though, at which
> point we'll need to be careful that we don't overwrite too much in the
> header.

Yeah, certainly something to be careful of later.

> > +    block->payload_offset = luks->header.payload_offset;
> 
> Earlier, I argued that block->payload_offset should be in bytes.  You
> are constrained that luks->header.payload_offset is in sectors, but we
> need not propagate that craziness any higher.

Yes, I've made that change.

> > +static int
> > +qcrypto_block_luks_create(QCryptoBlock *block,
> > +                          QCryptoBlockCreateOptions *options,
> > +                          QCryptoBlockInitFunc initfunc,
> > +                          QCryptoBlockWriteFunc writefunc,
> > +                          void *opaque,
> > +                          Error **errp)
> > +{
> 
> > +
> > +    memcpy(&luks_opts, options->u.luks, sizeof(luks_opts));
> > +    if (!luks_opts.has_cipher_alg) {
> > +        luks_opts.cipher_alg = QCRYPTO_CIPHER_ALG_AES_256;
> > +    }
> > +    if (!luks_opts.has_cipher_mode) {
> > +        /* XXX switch to XTS */
> > +        luks_opts.cipher_mode = QCRYPTO_CIPHER_MODE_CBC;
> > +    }
> 
> Yeah, my (quick) reading made it seem like XTS is a slightly more secure
> default than CBC.  But as you said, XTS implementation will come later.

Yes, I still hope to post an XTS impl before 2.6, so if this code
merges before 2.6, hopefully we'll stil have time to use XTS as
the default too, matching cryptsetup

> > +    luks = g_new0(QCryptoBlockLUKS, 1);
> > +    block->opaque = luks;
> > +
> > +    memcpy(luks->header.magic, qcrypto_block_luks_magic,
> > +           QCRYPTO_BLOCK_LUKS_MAGIC_LEN);
> > +
> > +    luks->header.version = QCRYPTO_BLOCK_LUKS_VERSION;
> 
> Maybe a comment here that endianness will be addressed later, as a lot
> of code appears between here and the actual write-to-disk.

Will do

> > +    uuid_generate(uuid);
> > +    uuid_unparse(uuid, (char *)luks->header.uuid);
> > +
> > +    switch (luks_opts.cipher_alg) {
> > +    case QCRYPTO_CIPHER_ALG_AES_128:
> > +    case QCRYPTO_CIPHER_ALG_AES_192:
> > +    case QCRYPTO_CIPHER_ALG_AES_256:
> > +        cipher_alg = "aes";
> > +        break;
> > +    default:
> > +        error_setg(errp, "Cipher algorithm is not supported");
> > +        goto error;
> 
> That is, we aren't supporting "twofish", "serpent", "cast5", or "cast6".
>  Fine for now.
> 
> > +    }
> > +
> > +    cipher_mode = QCryptoCipherMode_lookup[luks_opts.cipher_mode];
> > +    ivgen_alg = QCryptoIVGenAlgorithm_lookup[luks_opts.ivgen_alg];
> > +    if (luks_opts.has_ivgen_hash_alg) {
> > +        ivgen_hash_alg = 
> > QCryptoHashAlgorithm_lookup[luks_opts.ivgen_hash_alg];
> > +        cipher_mode_spec = g_strdup_printf("%s-%s:%s", cipher_mode, 
> > ivgen_alg,
> > +                                           ivgen_hash_alg);
> > +    } else {
> > +        cipher_mode_spec = g_strdup_printf("%s-%s", cipher_mode, 
> > ivgen_alg);
> > +    }
> 
> Should we validate that the mode spec is among the set documented by the
> LUKS spec ("ecb", "cbc-plain", "cbc-essiv:hash", "xts-plain64") (well,
> we don't support xts yet), and reject combinations that aren't supported
> ("cbc-plain64" comes to mind as something the LUKS spec doesn't allow,
> even though we have the pieces to logically make it happen)?

The spec appears to be non-exhaustive, in that cryptsetup / linux allow
for cbc-plain64 and other combinations that aren't listed. So QEMU should
follow that for interoperability.

> > +    hash_alg = QCryptoHashAlgorithm_lookup[luks_opts.hash_alg];
> 
> Likewise, should we validate that the hash is one of the specified names
> ("sha1", "sha256", "sha512", "ripemd160")?

The only one we're permitting that's not listed there is 'md5' and
that should fail anyway since it provides too small a digest size
but I see we're not validating the digest size.

> > +
> > +    memcpy(luks->header.cipher_name, cipher_alg,
> > +           strlen(cipher_alg) + 1);
> > +    memcpy(luks->header.cipher_mode, cipher_mode_spec,
> > +           strlen(cipher_mode_spec) + 1);
> > +    memcpy(luks->header.hash_spec, hash_alg,
> > +           strlen(hash_alg) + 1);
> 
> Couldn't these just be strcpy()?

Yes


> > +    /* Although LUKS has multiple key slots, we're just going
> > +     * to use the first key slot */
> > +
> > +    splitkeylen = luks->header.key_bytes * QCRYPTO_BLOCK_LUKS_STRIPES;
> 
> The LUKS spec says that it is okay to allow the user to specify stripes.
>  Should that be one of our options, with a sane default?  But it can be
> added later as a followup, this patch is already quite big and I'm fine
> with hardcoding stripes for now.

Cryptsetup doesn't allow the user to specify the stripes value, just
fixing it at 4k, so I figure we're best todo the same, otherwise we'll
create images that cant be read by linux.

> 
> > +    for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) {
> > +        luks->header.key_slots[i].active = i == 0 ?
> > +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_ENABLED :
> > +            QCRYPTO_BLOCK_LUKS_KEY_SLOT_DISABLED;
> > +        luks->header.key_slots[i].stripes = QCRYPTO_BLOCK_LUKS_STRIPES;
> > +        luks->header.key_slots[i].key_offset =
> > +            (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> > +            (ROUND_UP(((splitkeylen + 511) / 512),
> > +                      (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) * i);
> 
> Eww. The spec seems buggy, saying:
> 
> > // integer divisions, result rounded down:
> > baseOffset = (size of phdr)/SECTOR SIZE + 1
> > keymaterialSectors = (stripes*masterKeyLength)/SECTOR SIZE + 1
> 
> First, look at the calculation of baseOffset.  Since size of phdr is 592
> bytes, and that is NOT a SECTOR SIZE in any disk I know of, that makes
> sense that baseOffset will be at least 2 (512-byte) sectors into the
> disk, or, if SECTOR SIZE is 4, that will result in an offset of 1
> (4096-byte) sector.  However, you've defined
> QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET as 4096, which means for our 512-byte
> sector usage, you are setting the first key material at sector 4 no
> matter what.  I guess that matches what cryptsetup does?  Maybe worth a
> comment?
> 
> Now, look at the calculation of keymaterial Sectors.  The algorithm in
> the spec mishandles the case where stripes happens to be an exact
> multiple of sector size (that is, for a keysize of 20 bytes coupled with
> 512 stripes, it would reserve 21 sectors rather than the 20 actually
> required).  I think your use of ROUND_UP() makes more sense, but it
> would be nice to file a bug against the LUKS spec to have them fix their
> math, and/or document that it is okay to use values larger than the
> absolute minimum.

Yeah, what I am doing matches the cryptsetup code. I had originally
written it to be more or less what the spec suggests, and noticed
that my images were different from those created by cryptsetup. So
I switched to use the same algorithm. I figure it is probably an
optimization for on disk alignment to stop crossing sector boundaries.


> > +     * slot headers, rounded up to the nearest sector, combined with
> > +     * the size of each master key material region, also rounded up
> > +     * to the nearest sector */
> > +    luks->header.payload_offset =
> > +        (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512) +
> > +        (ROUND_UP(((splitkeylen + 511) / 512),
> > +                  (QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / 512)) *
> > +         QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS);
> > +
> > +    block->payload_offset = luks->header.payload_offset;
> 
> Again, I argue that block->payload_offset would be saner in bytes.

Agreed

> > +    /* Reserve header space to match payload offset */
> > +    initfunc(block, block->payload_offset * 512, &local_err, opaque);
> 
> Especially since initfunc() takes bytes.
> 


> > +
> > +static int
> > +qcrypto_block_luks_decrypt(QCryptoBlock *block,
> > +                           uint64_t startsector,
> > +                           uint8_t *buf,
> > +                           size_t len,
> > +                           Error **errp)
> > +{
> > +    return qcrypto_block_decrypt_helper(block->cipher,
> > +                                        block->niv, block->ivgen,
> > +                                        startsector, buf, len, errp);
> > +}
> 
> What happens when we try to read a LUKS device with 4k sectors?  Worth
> worrying about, maybe just as a comment that we are hard-coded to 512
> bytes at the moment?

qcrypto_block_decrypt_helper() method processes 'buf' in units of 512
so even if the underling dev is 4k, we'll also do 512 byte I/O

> >  ##
> > +# QCryptoBlockOptionsLUKS:
> > +#
> > +# The options that apply to LUKS encryption format
> > +#
> > +# @key-secret: #optional the ID of a QCryptoSecret object providing the
> > +#              decryption key
> 
> Maybe add "Mandatory except when probing the image for metadata only"

Yes


> > +# Since: 2.6
> > +##
> > +{ 'struct': 'QCryptoBlockOptionsLUKS',
> > +  'data': { '*key-secret': 'str' }}
> > +
> > +
> > +##
> > +# QCryptoBlockCreateOptionsLUKS:
> > +#
> > +# The options that apply to LUKS encryption format initialization
> > +#
> > +# @cipher-alg: #optional the cipher algorithm for data encryption
> > +# @cipher-mode: #optional the cipher mode for data encryption
> > +# @ivgen-alg: #optional the initialization vector generator
> > +# @ivgen-hash-alg: #optional the initialization vector generator hash
> > +# @hash-alg: #optional the master key hash algorithm
> 
> Would be nice to mention the defaults, particularly since we may later
> change the default from cbc to xts.

Yes, will do


Regards,
Daniel
-- 
|: http://berrange.com      -o-    http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org              -o-             http://virt-manager.org :|
|: http://autobuild.org       -o-         http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org       -o-       http://live.gnome.org/gtk-vnc :|



reply via email to

[Prev in Thread] Current Thread [Next in Thread]