Signed-off-by: Vladimir Serbinenko <phco...@gmail.com> --- grub-core/fs/zfs/zfs.c | 502 ++++++++++++++++++++++++++++++++---- grub-core/fs/zfs/zfscrypt.c | 335 +++++++++++++++++++++--- include/grub/zfs/zfs.h | 54 +++- 3 files changed, 793 insertions(+), 98 deletions(-)
diff --git a/grub-core/fs/zfs/zfs.c b/grub-core/fs/zfs/zfs.c index b88a2b032..da1661680 100644 --- a/grub-core/fs/zfs/zfs.c +++ b/grub-core/fs/zfs/zfs.c @@ -227,6 +227,10 @@ struct subvolume grub_uint64_t txg; grub_uint64_t algo; } *keyring; + + struct grub_zfs_datto_key key_datto; + + int is_datto_encrypted; }; struct grub_zfs_data @@ -264,16 +268,19 @@ struct grub_zfs_dir_ctx struct grub_zfs_data *data; }; -grub_err_t (*grub_zfs_decrypt) (grub_crypto_cipher_handle_t cipher, - grub_uint64_t algo, - const void *nonce, - char *buf, grub_size_t size, - const grub_uint32_t *expected_mac, - grub_zfs_endian_t endian) = NULL; -grub_crypto_cipher_handle_t (*grub_zfs_load_key) (const struct grub_zfs_key *key, - grub_size_t keysize, - grub_uint64_t salt, - grub_uint64_t algo) = NULL; +struct grub_zfs_decryptor *grub_zfs_decrypt = NULL; + +struct grub_zfs_crypt_datto_data +{ + grub_uint8_t *iv; + grub_size_t ivlen; + grub_uint8_t *mac; + grub_size_t maclen; + grub_uint8_t *master; + grub_size_t masterlen; + grub_uint8_t *hmac; + grub_size_t hmaclen; +}; /* * List of pool features that the grub implementation of ZFS supports for * read. Note that features that are only required for write do not need @@ -289,6 +296,7 @@ static const char *spa_feature_names[] = { "com.klarasystems:vdev_zaps_v2", "com.delphix:head_errlog", "org.freebsd:zstd_compress", + "com.datto:encryption", NULL }; @@ -400,6 +408,47 @@ static decomp_entry_t decomp_table[ZIO_COMPRESS_FUNCTIONS] = { static grub_err_t zio_read_data (const blkptr_t * bp, void *buf, struct grub_zfs_data *data); +static int +fill_crypt_datto_data (const void *name, + grub_size_t namelen __attribute__ ((unused)), + const void *val_in, + grub_size_t nelem, + grub_size_t elemsize, + void *data_in) +{ + struct grub_zfs_crypt_datto_data *data = data_in; + if (grub_strcmp(name, "DSL_CRYPTO_IV") == 0 && elemsize == 1) + { + data->ivlen = nelem; + data->iv = grub_malloc(nelem); + if (data->iv) + grub_memcpy(data->iv, val_in, nelem); + } + else if (grub_strcmp(name, "DSL_CRYPTO_MAC") == 0 && elemsize == 1) + { + data->maclen = nelem; + data->mac = grub_malloc(nelem); + if (data->mac) + grub_memcpy(data->mac, val_in, nelem); + } + else if (grub_strcmp(name, "DSL_CRYPTO_MASTER_KEY_1") == 0 && elemsize == 1) + { + data->masterlen = nelem; + data->master = grub_malloc(nelem); + if (data->master) + grub_memcpy(data->master, val_in, nelem); + } + else if (grub_strcmp(name, "DSL_CRYPTO_HMAC_KEY_1") == 0 && elemsize == 1) + { + data->hmaclen = nelem; + data->hmac = grub_malloc(nelem); + if (data->hmac) + grub_memcpy(data->hmac, val_in, nelem); + } + + return 0; +} + /* * Our own version of log2(). Same thing as highbit()-1. */ @@ -690,7 +739,7 @@ grub_zfs_byteswap_type(void *buf, grub_size_t len, grub_uint8_t type) static grub_err_t zio_checksum_verify (zio_cksum_t zc, grub_uint32_t checksum, grub_zfs_endian_t endian, - char *buf, grub_size_t size) + char *buf, grub_size_t size, int datto_crypt) { zio_eck_t *zec = (zio_eck_t *) (buf + size) - 1; zio_checksum_info_t *ci = &zio_checksum_table[checksum]; @@ -715,6 +764,12 @@ zio_checksum_verify (zio_cksum_t zc, grub_uint32_t checksum, ci->ci_func (buf, size, endian, &actual_cksum); int cksumlen = checksum != ZIO_CHECKSUM_SHA256_MAC ? 32 : 20; + if (datto_crypt) + { + actual_cksum.zc_word[0] ^= actual_cksum.zc_word[2]; + actual_cksum.zc_word[1] ^= actual_cksum.zc_word[3]; + cksumlen = 16; + } if (ci->ci_eck && !GRUB_ZFS_IS_NATIVE_BYTEORDER(endian)) grub_zfs_byteswap_checksum(&actual_cksum); @@ -796,7 +851,7 @@ uberblock_verify (uberblock_phys_t * ub, grub_uint64_t offset, zc.zc_word[0] = grub_cpu_to_zfs64 (offset, endian); err = zio_checksum_verify (zc, ZIO_CHECKSUM_LABEL, endian, - (char *) ub, s); + (char *) ub, s, 0); return err; } @@ -1269,7 +1324,7 @@ check_pool_label (struct grub_zfs_data *data, /* Now check the integrity of the vdev_phys_t structure though checksum. */ ZIO_SET_CHECKSUM(&emptycksum, grub_cpu_to_zfs64(diskdesc->vdev_phys_sector << 9, endian), 0, 0, 0); err = zio_checksum_verify (emptycksum, ZIO_CHECKSUM_LABEL, endian, - nvlist, VDEV_PHYS_SIZE); + nvlist, VDEV_PHYS_SIZE, 0); if (err) { grub_free (nvlist); return err; @@ -2019,7 +2074,7 @@ zio_read_gang (const blkptr_t * bp, const dva_t * dva, void *buf, ZIO_SET_CHECKSUM (&zc, DVA_GET_VDEV (dva), DVA_GET_OFFSET (dva), bp->blk_birth, 0); err = zio_checksum_verify (zc, ZIO_CHECKSUM_GANG_HEADER, BP_GET_BYTEORDER(bp), - (char *) zio_gb, SPA_GANGBLOCKSIZE); + (char *) zio_gb, SPA_GANGBLOCKSIZE, 0); if (err) { grub_free (zio_gb); @@ -2113,6 +2168,57 @@ decode_embedded_bp_compressed(const blkptr_t *bp, void *buf) return GRUB_ERR_NONE; } +static int +datto_is_encrypted_type(grub_uint8_t dn_type) +{ + /* New structred types. */ + if (dn_type & 0x80) + return !!(dn_type & 0x20); + switch(dn_type) { + case DMU_OT_NONE ... DMU_OT_SPACE_MAP: + case DMU_OT_OBJSET ... DMU_OT_ZNODE: + case DMU_OT_MASTER_NODE: + case DMU_OT_ZVOL_PROP: + case DMU_OT_ZAP_OTHER ... DMU_OT_DSL_PERMS: + case DMU_OT_FUID_SIZE ... DMU_OT_SCRUB_QUEUE: + case DMU_OT_USERREFS ... DMU_OT_DDT_STATS: + return 0; + default: + return 1; + } +} + +static void +add_blkptr_to_aad (char *aad, grub_size_t *aad_offset, blkptr_t bp, grub_zfs_endian_t endian, int preswapped) +{ + if (!preswapped && !GRUB_ZFS_IS_NATIVE_BYTEORDER(endian)) + grub_zfs_byteswap_blkptr(&bp); + + grub_uint64_t blk_prop = BP_IS_HOLE(&bp) ? 0 : bp.blk_prop; + + if (BP_GET_LEVEL(&bp) != 0) { + blk_prop &= ~0x8000007fffff0000ULL; + } + + blk_prop &= ~0x4000ff0000000000ULL; + + grub_set_unaligned64(&aad[*aad_offset], grub_cpu_to_le64(blk_prop)); + *aad_offset += 8; + + if (!GRUB_ZFS_IS_NATIVE_BYTEORDER(endian)) + { + grub_set_unaligned64(&aad[*aad_offset], grub_swap_bytes64(bp.blk_cksum.zc_word[2])); + grub_set_unaligned64(&aad[*aad_offset+8], grub_swap_bytes64(bp.blk_cksum.zc_word[3])); + } + else + grub_memcpy(&aad[*aad_offset], &bp.blk_cksum.zc_word[2], 16); + + *aad_offset += 16; + + grub_memset(&aad[*aad_offset], 0, 8); + *aad_offset += 8; +} + /* * Read in a block of data, verify its checksum, decompress if needed, * and put the uncompressed data in buf. @@ -2122,17 +2228,44 @@ zio_read (const blkptr_t *bp, void **buf, grub_size_t *size, struct grub_zfs_data *data) { grub_size_t lsize, psize; - unsigned int comp, encrypted; + unsigned int comp; char *compbuf = NULL; grub_err_t err; zio_cksum_t zc = bp->blk_cksum; grub_uint32_t checksum; + int datto_encrypted = 0, datto_authenticated = 0, datto_dnode_encryption = 0, oracle_encrypted = 0; *buf = NULL; checksum = BP_GET_CHECKSUM(bp); comp = BP_GET_COMPRESS(bp); - encrypted = BP_GET_PROP_BIT_61(bp); + if (BP_GET_PROP_BIT_61(bp)) + { + if (data->subvol.is_datto_encrypted) + { + grub_uint8_t type = BP_GET_TYPE(bp); + if (BP_GET_LEVEL(bp) > 0) + datto_authenticated = 1; + else if (type == DMU_OT_DNODE) + { + datto_encrypted = 1; + datto_authenticated = 0; + datto_dnode_encryption = 1; + } + else if (type == DMU_OT_OBJSET) + { + /* Objset uses inner hmacs that we don't suport yet. + Normal checksum is unaffected . */ + } + else + { + datto_encrypted = datto_is_encrypted_type(type); + datto_authenticated = !datto_encrypted; + } + } + else + oracle_encrypted = 1; + } if (BP_IS_EMBEDDED(bp)) { if (BPE_GET_ETYPE(bp) != BP_EMBEDDED_TYPE_DATA) @@ -2186,10 +2319,11 @@ zio_read (const blkptr_t *bp, void **buf, return err; } - if (!BP_IS_EMBEDDED(bp)) + if (!BP_IS_EMBEDDED(bp) && !datto_encrypted) { err = zio_checksum_verify (zc, checksum, BP_GET_BYTEORDER(bp), - compbuf, psize); + compbuf, psize, + datto_authenticated); if (err) { grub_dprintf ("zfs", "incorrect checksum\n"); @@ -2199,48 +2333,190 @@ zio_read (const blkptr_t *bp, void **buf, } } - if (encrypted) + if (!BP_IS_EMBEDDED(bp) && datto_authenticated && data->subvol.key_datto.hmac_key && BP_GET_LEVEL(bp) == 0) { - if (!grub_zfs_decrypt) - err = grub_error (GRUB_ERR_BAD_FS, - N_("module `%s' isn't loaded"), - "zfscrypt"); - else + grub_uint64_t hmac[8]; + gcry_error_t err_gcry; + err_gcry = grub_crypto_hmac_buffer (GRUB_MD_SHA512, + data->subvol.key_datto.hmac_key, 64, + compbuf, psize, + hmac); + if (err_gcry) { - unsigned i, besti = 0; - grub_uint64_t bestval = 0; - for (i = 0; i < data->subvol.nkeys; i++) - if (data->subvol.keyring[i].txg <= bp->blk_birth - && data->subvol.keyring[i].txg > bestval) - { - besti = i; - bestval = data->subvol.keyring[i].txg; - } - if (bestval == 0) + grub_free (compbuf); + *buf = NULL; + return grub_crypto_gcry_error (err_gcry); + } + + if (!GRUB_ZFS_IS_NATIVE_BYTEORDER(BP_GET_BYTEORDER(bp))) + { + grub_zfs_byteswap_u64(&hmac[0]); + grub_zfs_byteswap_u64(&hmac[1]); + } + + if (grub_crypto_memcmp(&zc.zc_word[2], hmac, 8) != 0) + { + grub_free (compbuf); + *buf = NULL; + grub_dprintf ("zfs", "actual hmac " + "%016llx %016llx %016llx %016llx %016llx %016llx %016llx %016llx \n", + (unsigned long long) hmac[0], + (unsigned long long) hmac[1], + (unsigned long long) hmac[2], + (unsigned long long) hmac[3], + (unsigned long long) hmac[4], + (unsigned long long) hmac[5], + (unsigned long long) hmac[6], + (unsigned long long) hmac[7]); + grub_dprintf ("zfs", "expected hmac %016llx %016llx %016llx %016llx\n", + (unsigned long long) zc.zc_word[0], + (unsigned long long) zc.zc_word[1], + (unsigned long long) zc.zc_word[2], + (unsigned long long) zc.zc_word[3]); + return grub_error (GRUB_ERR_BAD_FS, N_("HMAC verification failed")); + } + } + + if (datto_dnode_encryption && (!grub_zfs_decrypt || !data->subvol.key_datto.master_key)) + { + grub_dprintf("zfs", "Skipping decrypt of bonus because of missing zfscrypt module or key\n"); + datto_encrypted = 0; + datto_dnode_encryption = 0; + } + if ((oracle_encrypted || datto_encrypted) && !grub_zfs_decrypt) + err = grub_error (GRUB_ERR_BAD_FS, + N_("module `%s' isn't loaded"), + "zfscrypt"); + else if (datto_encrypted) + { + grub_uint32_t iv[3]; + + if (!data->subvol.key_datto.master_key) + { + grub_free (compbuf); + *buf = NULL; + grub_dprintf ("zfs", "no key for txg %" PRIxGRUB_UINT64_T "\n", + bp->blk_birth); + return grub_error (GRUB_ERR_BAD_FS, "no key found in keychain"); + } + + grub_set_unaligned64(iv, grub_cpu_to_zfs64((bp)->blk_dva[2].dva_word[1], BP_GET_BYTEORDER(bp))); + iv[2] = grub_cpu_to_zfs32((bp)->blk_fill >> 32, BP_GET_BYTEORDER(bp)); + + if (datto_dnode_encryption) + { + grub_size_t offset = 0, crypt_offset = 0, aad_offset = 0; + char *crypt = grub_malloc(psize), *aad = grub_malloc(psize); + grub_zfs_endian_t endian = BP_GET_BYTEORDER(bp); + if (!crypt || !aad) { grub_free (compbuf); + grub_free (crypt); + grub_free (aad); *buf = NULL; - grub_dprintf ("zfs", "no key for txg %" PRIxGRUB_UINT64_T "\n", - bp->blk_birth); - return grub_error (GRUB_ERR_BAD_FS, "no key found in keychain"); + return grub_errno; } - grub_dprintf ("zfs", "using key %u (%" PRIxGRUB_UINT64_T - ", %p) for txg %" PRIxGRUB_UINT64_T "\n", - besti, data->subvol.keyring[besti].txg, - data->subvol.keyring[besti].cipher, - bp->blk_birth); - err = grub_zfs_decrypt (data->subvol.keyring[besti].cipher, - data->subvol.keyring[besti].algo, - &(bp)->blk_dva[2], - compbuf, psize, zc.zc_mac, - BP_GET_BYTEORDER(bp)); + for (offset = 0; offset + sizeof (dnode_phys_t) <= psize; offset += sizeof(dnode_phys_t)) + { + dnode_phys_t *dnp = (dnode_phys_t *) (void *) (compbuf + offset); + dnode_phys_t *dno = (dnode_phys_t *) (aad + aad_offset); + grub_memcpy(aad + aad_offset, dnp, 64); + dno->dn_used = 0; + dno->dn_flags &= DNODE_FLAG_SPILL_BLKPTR; + int has_spill = dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR; + unsigned i; + + aad_offset += 64; + + for (i = 0; i < dnp->dn_nblkptr; i++) { + add_blkptr_to_aad (aad, &aad_offset, dnp->dn_blkptr[i], endian, 0); + } + + if (has_spill) + add_blkptr_to_aad (aad, &aad_offset, dnp->dn_spill, endian, 0); + + char *bonus = DN_BONUS(dnp); + char *bonusmaxptr = (char *) (dnp + 1); + if (has_spill) + bonusmaxptr -= sizeof(blkptr_t); + grub_size_t bonusmaxlen = bonusmaxptr - bonus; + if (datto_is_encrypted_type(dnp->dn_bonustype)) + { + grub_memcpy(crypt + crypt_offset, bonus, bonusmaxlen); + crypt_offset += bonusmaxlen; + } + else + { + grub_memcpy(aad + aad_offset, bonus, bonusmaxlen); + grub_zfs_byteswap_type((aad + aad_offset), bonusmaxlen, dnp->dn_bonustype); + aad_offset += bonusmaxlen; + } + } + err = grub_zfs_decrypt->decrypt_datto (&data->subvol.key_datto, + iv, (bp)->blk_dva[2].dva_word[0], + crypt, crypt_offset, aad, aad_offset, + &zc.zc_word[2], + endian); + grub_size_t out_offset = 0; + for (offset = 0; offset + sizeof (dnode_phys_t) <= psize; offset += sizeof(dnode_phys_t)) + { + dnode_phys_t *dnp = (dnode_phys_t *) (void *) (compbuf + offset); + int has_spill = dnp->dn_flags & DNODE_FLAG_SPILL_BLKPTR; + if (!datto_is_encrypted_type(dnp->dn_bonustype)) + continue; + char * bonus = DN_BONUS(dnp); + char *bonusmaxptr = (char *) (dnp + 1); + if (has_spill) + bonusmaxptr -= sizeof(blkptr_t); + grub_size_t bonusmaxlen = bonusmaxptr - bonus; + grub_memcpy(bonus, crypt + out_offset, bonusmaxlen); + out_offset += bonusmaxlen; + } + } - if (err) + else + err = grub_zfs_decrypt->decrypt_datto (&data->subvol.key_datto, + iv, (bp)->blk_dva[2].dva_word[0], + compbuf, psize, NULL, 0, + &zc.zc_word[2], + BP_GET_BYTEORDER(bp)); + } + else if (oracle_encrypted) + { + unsigned i, besti = 0; + grub_uint64_t bestval = 0; + grub_dprintf("zfs", "Subvol has %d keys\n", (int) data->subvol.nkeys); + for (i = 0; i < data->subvol.nkeys; i++) + if (data->subvol.keyring[i].txg <= bp->blk_birth + && data->subvol.keyring[i].txg > bestval) + { + besti = i; + bestval = data->subvol.keyring[i].txg; + } + if (bestval == 0) { grub_free (compbuf); *buf = NULL; - return err; + grub_dprintf ("zfs", "no key for txg %" PRIxGRUB_UINT64_T "\n", + bp->blk_birth); + return grub_error (GRUB_ERR_BAD_FS, "no key found in keychain"); } + grub_dprintf ("zfs", "using key %u (%" PRIxGRUB_UINT64_T + ", %p) for txg %" PRIxGRUB_UINT64_T "\n", + besti, data->subvol.keyring[besti].txg, + data->subvol.keyring[besti].cipher, + bp->blk_birth); + err = grub_zfs_decrypt->decrypt_oracle (data->subvol.keyring[besti].cipher, + data->subvol.keyring[besti].algo, + &(bp)->blk_dva[2], + compbuf, psize, zc.zc_mac, + BP_GET_BYTEORDER(bp)); + } + if (err) + { + grub_free (compbuf); + *buf = NULL; + return err; } if (comp != ZIO_COMPRESS_OFF) @@ -2254,6 +2530,7 @@ zio_read (const blkptr_t *bp, void **buf, err = decomp_table[comp].decomp_func (compbuf, *buf, psize, lsize); grub_free (compbuf); + compbuf = NULL; if (err) { grub_free (*buf); @@ -2273,6 +2550,51 @@ zio_read (const blkptr_t *bp, void **buf, grub_zfs_byteswap_type(*buf, lsize, BP_GET_TYPE(bp)); } + if (!BP_IS_EMBEDDED(bp) && datto_authenticated && BP_GET_LEVEL(bp) > 0) + { + grub_uint64_t hash[8]; + char *aad = grub_malloc(lsize); + grub_size_t aad_offset = 0; + if (!aad) + { + grub_free (*buf); + *buf = NULL; + return grub_errno; + } + for (unsigned i = 0; i < lsize / sizeof(blkptr_t); i++) + add_blkptr_to_aad (aad, &aad_offset, ((blkptr_t *)*buf)[i], BP_GET_BYTEORDER(bp), 1); + grub_crypto_hash(GRUB_MD_SHA512, hash, aad, aad_offset); + grub_free(aad); + + if (!GRUB_ZFS_IS_NATIVE_BYTEORDER(BP_GET_BYTEORDER(bp))) + { + grub_zfs_byteswap_u64(&hash[0]); + grub_zfs_byteswap_u64(&hash[1]); + } + + if (grub_crypto_memcmp(&zc.zc_word[2], hash, 8) != 0) + { + grub_free (*buf); + *buf = NULL; + grub_dprintf ("zfs", "actual hash " + "%016llx %016llx %016llx %016llx %016llx %016llx %016llx %016llx \n", + (unsigned long long) hash[0], + (unsigned long long) hash[1], + (unsigned long long) hash[2], + (unsigned long long) hash[3], + (unsigned long long) hash[4], + (unsigned long long) hash[5], + (unsigned long long) hash[6], + (unsigned long long) hash[7]); + grub_dprintf ("zfs", "expected hash %016llx %016llx %016llx %016llx\n", + (unsigned long long) zc.zc_word[0], + (unsigned long long) zc.zc_word[1], + (unsigned long long) zc.zc_word[2], + (unsigned long long) zc.zc_word[3]); + return grub_error (GRUB_ERR_BAD_FS, N_("hash verification failed")); + } + } + return GRUB_ERR_NONE; } @@ -3519,8 +3841,8 @@ load_zap_key (const void *name, grub_size_t namelen, const void *val_in, ctx->subvol->keyring[ctx->keyn].algo = grub_le_to_cpu64 (*(grub_uint64_t *) val_in); ctx->subvol->keyring[ctx->keyn].cipher = - grub_zfs_load_key (val_in, nelem, ctx->salt, - ctx->subvol->keyring[ctx->keyn].algo); + grub_zfs_decrypt->load_key_oracle (val_in, nelem, ctx->salt, + ctx->subvol->keyring[ctx->keyn].algo); ctx->keyn++; return 0; } @@ -3587,6 +3909,82 @@ dnode_get_fullpath (const char *fullpath, struct subvolume *subvol, grub_dprintf ("zfs", "alive\n"); + grub_uint64_t crypt_obj; + + err = zap_lookup (dn, "com.datto:crypto_key_obj", &crypt_obj, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + crypt_obj = 0; + } + + grub_dprintf("zfs", "crypt obj = %lld\n", (long long) crypt_obj); + subvol->is_datto_encrypted = crypt_obj != 0; + + if (grub_zfs_decrypt && crypt_obj) + { + dnode_phys_t crypt_dn; + err = dnode_get (&(data->mos), crypt_obj, 0xc4, + &crypt_dn, data); + if (err) + { + grub_free (fsname); + grub_free (snapname); + return err; + } + + err = dnode_get (&(data->mos), crypt_obj, 0 /* ?? */, + &crypt_dn, data); + struct grub_zfs_crypt_datto_data crypt_datto_data = { 0 }; + grub_uint64_t pbkdf2iters = 0, pbkdf2salt = 0, guid = 0, algo = 0, version = 0; + zap_iterate (&crypt_dn, 1, fill_crypt_datto_data, &crypt_datto_data, data); + + err = zap_lookup (&crypt_dn, "pbkdf2iters", &pbkdf2iters, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + pbkdf2iters = 0; + } + + err = zap_lookup (&crypt_dn, "DSL_CRYPTO_GUID", &guid, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + guid = 0; + } + + err = zap_lookup (&crypt_dn, "DSL_CRYPTO_SUITE", &algo, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + algo = 0; + } + + err = zap_lookup (&crypt_dn, "DSL_CRYPTO_VERSION", &version, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + version = 0; + } + + err = zap_lookup (&crypt_dn, "pbkdf2salt", &pbkdf2salt, data, 0); + if (err) + { + grub_errno = GRUB_ERR_NONE; + pbkdf2salt = 0; + } + + pbkdf2salt = grub_cpu_to_le64(pbkdf2salt); + + subvol->key_datto = grub_zfs_decrypt->load_key_datto(crypt_datto_data.iv, crypt_datto_data.ivlen, + crypt_datto_data.mac, crypt_datto_data.maclen, + crypt_datto_data.master, crypt_datto_data.masterlen, + crypt_datto_data.hmac, crypt_datto_data.hmaclen, + (const grub_uint8_t *) &pbkdf2salt, sizeof (pbkdf2salt), + pbkdf2iters, guid, algo, version); + } + + headobj = ((dsl_dir_phys_t *) DN_BONUS (dn))->dd_head_dataset_obj; err = dnode_get (&(data->mos), headobj, 0, &subvol->mdn, data); @@ -3600,7 +3998,7 @@ dnode_get_fullpath (const char *fullpath, struct subvolume *subvol, grub_dprintf("zfs", "keychain obj = %lld\n", (long long) keychainobj); - if (grub_zfs_decrypt && keychainobj) + if (grub_zfs_decrypt && !subvol->is_datto_encrypted && keychainobj) { struct dnode_get_fullpath_ctx ctx = { .subvol = subvol, diff --git a/grub-core/fs/zfs/zfscrypt.c b/grub-core/fs/zfs/zfscrypt.c index 535fd4065..e202f4265 100644 --- a/grub-core/fs/zfs/zfscrypt.c +++ b/grub-core/fs/zfs/zfscrypt.c @@ -46,8 +46,9 @@ GRUB_MOD_LICENSE ("GPLv3+"); /* - Mostly based on following article: + Oracle part is mostly based on following article: https://blogs.oracle.com/darren/entry/zfs_encryption_what_is_on + Datto part is based on comments in OpenZFS. No actal code has been used */ enum grub_zfs_algo @@ -56,7 +57,26 @@ enum grub_zfs_algo GRUB_ZFS_ALGO_GCM, }; -struct grub_zfs_key +struct datto_algo { + grub_size_t wrapkeylen; + grub_size_t cryptkeylen; + grub_size_t masterkeylen; + enum grub_zfs_algo used_algo; +} datto_algos[] = { + { 0 }, + { 0 }, + { 0 }, + { 32, 16, 16, GRUB_ZFS_ALGO_CCM }, + { 32, 24, 24, GRUB_ZFS_ALGO_CCM }, + { 32, 32, 32, GRUB_ZFS_ALGO_CCM }, + { 32, 16, 16, GRUB_ZFS_ALGO_GCM }, + { 32, 24, 24, GRUB_ZFS_ALGO_GCM }, + { 32, 32, 32, GRUB_ZFS_ALGO_GCM }, +}; +#define MAX_WRAPCIPHERTEXTLEN 96 +#define MAX_MACLEN 16 + +struct grub_zfs_key_oracle { grub_uint64_t algo; grub_uint8_t enc_nonce[13]; @@ -102,26 +122,81 @@ grub_zfs_add_key (grub_uint8_t *key_in, static gcry_err_code_t grub_ccm_decrypt (grub_crypto_cipher_handle_t cipher, - grub_uint8_t *out, const grub_uint8_t *in, - grub_size_t psize, + grub_uint8_t *out, + const grub_uint8_t *in, grub_size_t psize, + const grub_uint8_t *aad, grub_size_t aadsize, void *mac_out, const void *nonce, - unsigned l, unsigned m) + unsigned noncelen, unsigned m) { grub_uint8_t iv[16]; grub_uint8_t mul[16]; grub_uint32_t mac[4]; - unsigned i, j; + unsigned i, j, l = 15 - noncelen, aprefixlen = 0; gcry_err_code_t err; + grub_uint8_t aprefix[16] = { 0 }; - grub_memcpy (iv + 1, nonce, 15 - l); + grub_memcpy (iv + 1, nonce, noncelen); - iv[0] = (l - 1) | (((m-2) / 2) << 3); + iv[0] = (l - 1) | (((m-2) / 2) << 3) | ((aadsize != 0) << 6); for (j = 0; j < l; j++) iv[15 - j] = psize >> (8 * j); err = grub_crypto_ecb_encrypt (cipher, mac, iv, 16); if (err) return err; + if (aadsize == 0) + aprefixlen = 0; + else if (aadsize <= 0xFEFF) + { + aprefixlen = 2; + aprefix[0] = aadsize >> 8; + aprefix[1] = aadsize; + } +#if GRUB_CPU_SIZEOF_VOID_P == 8 + else if ((aadsize >> 32) == 0) +#endif + { + aprefixlen = 6; + aprefix[0] = 0xff; + aprefix[1] = 0xfe; + grub_set_unaligned32(aprefix + 2, grub_cpu_to_be32(aadsize)); + } +#if GRUB_CPU_SIZEOF_VOID_P == 8 + else + { + aprefixlen = 10; + aprefix[0] = 0xff; + aprefix[1] = 0xff; + grub_set_unaligned64(aprefix + 2, grub_cpu_to_be64(aadsize)); + } +#endif + + if (aadsize != 0) + { + grub_size_t ablocks = (aadsize + aprefixlen + 15) / 16; + grub_size_t first_block_datalen = 16 - aprefixlen; + if (first_block_datalen > aadsize) + first_block_datalen = aadsize; + grub_memcpy (aprefix + aprefixlen, aad, first_block_datalen); + + grub_crypto_xor (mac, mac, aprefix, 16); + err = grub_crypto_ecb_encrypt (cipher, mac, mac, 16); + if (err) + return err; + + for (i = 1; i < ablocks; i++) + { + grub_size_t csize, instart = (i - 1) * 16 + first_block_datalen; + csize = 16; + if (csize > aadsize - instart) + csize = aadsize - instart; + grub_crypto_xor (mac, mac, aad + instart, csize); + err = grub_crypto_ecb_encrypt (cipher, mac, mac, 16); + if (err) + return err; + } + } + iv[0] = l - 1; for (i = 0; i < (psize + 15) / 16; i++) @@ -187,6 +262,8 @@ static gcry_err_code_t grub_gcm_decrypt (grub_crypto_cipher_handle_t cipher, grub_uint8_t *out, const grub_uint8_t *in, grub_size_t psize, + const grub_uint8_t *aad, + grub_size_t aadsize, void *mac_out, const void *nonce, unsigned nonce_len, unsigned m) { @@ -213,7 +290,7 @@ grub_gcm_decrypt (grub_crypto_cipher_handle_t cipher, else { grub_memset (iv, 0, sizeof (iv)); - grub_memcpy (iv, nonce, nonce_len); + grub_memcpy (iv, nonce, nonce_len > sizeof (iv) ? sizeof (iv) : nonce_len); grub_gcm_mul (iv, h); iv[15] ^= nonce_len * 8; grub_gcm_mul (iv, h); @@ -223,6 +300,16 @@ grub_gcm_decrypt (grub_crypto_cipher_handle_t cipher, if (err) return err; + for (i = 0; i < (aadsize + 15) / 16; i++) + { + grub_size_t csize; + csize = 16; + if (csize > aadsize - 16 * i) + csize = aadsize - 16 * i; + grub_crypto_xor (mac, mac, aad + 16 * i, csize); + grub_gcm_mul (mac, h); + } + for (i = 0; i < (psize + 15) / 16; i++) { grub_size_t csize; @@ -244,6 +331,8 @@ grub_gcm_decrypt (grub_crypto_cipher_handle_t cipher, } for (j = 0; j < 8; j++) mac[15 - j] ^= ((((grub_uint64_t) psize) * 8) >> (8 * j)); + for (j = 0; j < 8; j++) + mac[7 - j] ^= ((((grub_uint64_t) aadsize) * 8) >> (8 * j)); grub_gcm_mul (mac, h); if (mac_out) @@ -257,30 +346,33 @@ static gcry_err_code_t algo_decrypt (grub_crypto_cipher_handle_t cipher, grub_uint64_t algo, grub_uint8_t *out, const grub_uint8_t *in, grub_size_t psize, + const grub_uint8_t *aad, + grub_size_t aadsize, void *mac_out, const void *nonce, unsigned l, unsigned m) { switch (algo) { - case 0: + case GRUB_ZFS_ALGO_CCM: return grub_ccm_decrypt (cipher, out, in, psize, - mac_out, nonce, l, m); - case 1: + aad, aadsize, mac_out, nonce, + l <= 15 ? l : 0, m); + case GRUB_ZFS_ALGO_GCM: return grub_gcm_decrypt (cipher, out, in, psize, - mac_out, nonce, - 15 - l, m); + aad, aadsize, mac_out, nonce, + l <= 15 ? l : 0, m); default: return GPG_ERR_CIPHER_ALGO; } } static grub_err_t -grub_zfs_decrypt_real (grub_crypto_cipher_handle_t cipher, - grub_uint64_t algo, - const void *nonce, - char *buf, grub_size_t size, - const grub_uint32_t *expected_mac, - grub_zfs_endian_t endian) +grub_zfs_decrypt_oracle (grub_crypto_cipher_handle_t cipher, + grub_uint64_t algo, + const void *nonce, + char *buf, grub_size_t size, + const grub_uint32_t *expected_mac, + grub_zfs_endian_t endian) { grub_uint32_t mac[4]; unsigned i; @@ -298,8 +390,8 @@ grub_zfs_decrypt_real (grub_crypto_cipher_handle_t cipher, err = algo_decrypt (cipher, algo, (grub_uint8_t *) buf, (grub_uint8_t *) buf, - size, mac, - sw + 1, 3, 12); + size, NULL, 0, mac, + sw, 12, 12); if (err) return grub_crypto_gcry_error (err); @@ -310,11 +402,73 @@ grub_zfs_decrypt_real (grub_crypto_cipher_handle_t cipher, return GRUB_ERR_NONE; } +static grub_err_t +grub_zfs_decrypt_datto (const struct grub_zfs_datto_key *key, + const grub_uint32_t *nonce, grub_uint64_t salt, + char *buf, grub_size_t size, + const char *aadbuf, grub_size_t aadsize, + const grub_uint64_t *expected_mac, + grub_zfs_endian_t endian) +{ + grub_uint64_t mac[2]; + unsigned i; + grub_uint8_t extractkey[64]; + grub_uint8_t expandkey[64]; + grub_uint8_t t[9]; + gcry_err_code_t err; + grub_crypto_cipher_handle_t cipher; + + if (!key) + return grub_error (GRUB_ERR_ACCESS_DENIED, + N_("no decryption key available")); + + /* A special case of HKDF with 512 > keylen. */ + err = grub_crypto_hmac_buffer (GRUB_MD_SHA512, + "", 0, + key->master_key, key->master_keylen, + extractkey); + if (err) + return grub_crypto_gcry_error (err); + + grub_set_unaligned64(t, grub_cpu_to_zfs64(salt, endian)); + t[8] = 1; + err = grub_crypto_hmac_buffer (GRUB_MD_SHA512, + extractkey, 64, + t, 9, expandkey); + if (err) + return grub_crypto_gcry_error (err); + + cipher = grub_crypto_cipher_open (GRUB_CIPHER_AES); + if (!cipher) + return grub_crypto_gcry_error (err); + err = grub_crypto_cipher_set_key (cipher, expandkey, datto_algos[key->algo].cryptkeylen); + if (err) + { + grub_crypto_cipher_close (cipher); + return grub_crypto_gcry_error (err); + } + + err = algo_decrypt (cipher, datto_algos[key->algo].used_algo, + (grub_uint8_t *) buf, + (grub_uint8_t *) buf, + size, (grub_uint8_t *) aadbuf, aadsize, mac, + nonce, 12, 16); + grub_crypto_cipher_close (cipher); + if (err) + return grub_crypto_gcry_error (err); + + for (i = 0; i < 2; i++) + if (grub_zfs_to_cpu64 (expected_mac[i], endian) + != grub_le_to_cpu64 (mac[i])) + grub_dprintf("zfs", N_("MAC verification failed")); + return GRUB_ERR_NONE; +} + static grub_crypto_cipher_handle_t -grub_zfs_load_key_real (const struct grub_zfs_key *key, - grub_size_t keysize, - grub_uint64_t salt, - grub_uint64_t algo) +grub_zfs_load_key_oracle (const struct grub_zfs_key_oracle *key, + grub_size_t keysize, + grub_uint64_t salt, + grub_uint64_t algo) { unsigned keylen; struct grub_zfs_wrap_key *wrap_key; @@ -373,7 +527,7 @@ grub_zfs_load_key_real (const struct grub_zfs_key *key, } err = algo_decrypt (cipher, algo, decrypted, key->unknown_purpose_key, 32, - mac, key->unknown_purpose_nonce, 2, 16); + NULL, 0, mac, key->unknown_purpose_nonce, 13, 16); if (err || (grub_crypto_memcmp (mac, key->unknown_purpose_key + 32, 16) != 0)) { @@ -383,8 +537,8 @@ grub_zfs_load_key_real (const struct grub_zfs_key *key, continue; } - err = algo_decrypt (cipher, algo, decrypted, key->enc_key, keylen, mac, - key->enc_nonce, 2, 16); + err = algo_decrypt (cipher, algo, decrypted, key->enc_key, keylen, NULL, 0, + mac, key->enc_nonce, 13, 16); if (err || grub_crypto_memcmp (mac, key->enc_key + keylen, 16) != 0) { grub_dprintf ("zfs", "key loading failed\n"); @@ -413,6 +567,119 @@ grub_zfs_load_key_real (const struct grub_zfs_key *key, return NULL; } +static struct grub_zfs_datto_key +grub_zfs_load_key_datto (const grub_uint8_t *iv, grub_size_t ivlen, + const grub_uint8_t *mac_in, grub_size_t mac_inlen, + const grub_uint8_t *master, grub_size_t masterlen, + const grub_uint8_t *hmac, grub_size_t hmaclen, + const grub_uint8_t *pbkdf2salt, grub_size_t pbkdf2saltlen, + grub_uint64_t pbkdf2iters, grub_uint64_t guid, grub_uint64_t algo, grub_uint64_t version) +{ + struct grub_zfs_wrap_key *wrap_key; + struct grub_zfs_datto_key ret = { 0 }; + + if (algo <= 2 || algo >= ARRAY_SIZE(datto_algos)) + { + grub_error(GRUB_ERR_BAD_FS, "unsupported crypto algo"); + return ret; + } + + /* Note: if last two ever become variable they need to be checkecked as matching algo. Especially mac_inlen. */ + if (masterlen < datto_algos[algo].masterkeylen || hmaclen != 64 || mac_inlen != 16) + { + grub_error(GRUB_ERR_BAD_FS, "crypto keys are invalid"); + return ret; + } + + masterlen = datto_algos[algo].masterkeylen; + + grub_uint8_t ciphertext[MAX_WRAPCIPHERTEXTLEN]; + grub_uint8_t plaintext[MAX_WRAPCIPHERTEXTLEN]; + grub_uint8_t mac_computed[MAX_MACLEN]; + struct { + grub_uint64_t guid; + grub_uint64_t algo; + grub_uint64_t version; + } aad = { + grub_cpu_to_le64(guid), + grub_cpu_to_le64(algo), + grub_cpu_to_le64(version) + }; + + grub_memcpy(ciphertext, master, masterlen); + grub_memcpy(ciphertext + masterlen, hmac, hmaclen); + + for (wrap_key = zfs_wrap_keys; wrap_key; wrap_key = wrap_key->next) + { + grub_crypto_cipher_handle_t cipher; + grub_uint8_t wrap_key_real[32] = { 0 }; + gcry_err_code_t err = 0; + + cipher = grub_crypto_cipher_open (GRUB_CIPHER_AES); + if (!cipher) + { + grub_errno = GRUB_ERR_NONE; + return ret; + } + grub_memset (wrap_key_real, 0, sizeof (wrap_key_real)); + + if (!wrap_key->is_passphrase) + grub_memcpy(wrap_key_real, wrap_key->key, + wrap_key->keylen < datto_algos[algo].wrapkeylen ? wrap_key->keylen : datto_algos[algo].wrapkeylen); + else + // TODO: PBKDF2 + err = grub_crypto_pbkdf2 (GRUB_MD_SHA1, + (const grub_uint8_t *) wrap_key->key, + wrap_key->keylen, + pbkdf2salt, pbkdf2saltlen, + pbkdf2iters, wrap_key_real, datto_algos[algo].wrapkeylen); + + if (err) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + err = grub_crypto_cipher_set_key (cipher, wrap_key_real, datto_algos[algo].wrapkeylen); + if (err) + { + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + err = algo_decrypt (cipher, datto_algos[algo].used_algo, + plaintext, ciphertext, masterlen + hmaclen, + (grub_uint8_t *) &aad, sizeof(aad), + mac_computed, iv, ivlen, mac_inlen); + if (err || (grub_crypto_memcmp (mac_computed, mac_in, mac_inlen) != 0)) + { + grub_dprintf ("zfs", "key loading failed\n"); + grub_errno = GRUB_ERR_NONE; + grub_crypto_cipher_close (cipher); + continue; + } + + ret.master_keylen = masterlen; + ret.master_key = grub_malloc(ret.master_keylen); + if (!ret.master_key) + return ret; + grub_memcpy(ret.master_key, plaintext, masterlen); + ret.hmac_key = grub_malloc(hmaclen); + if (!ret.hmac_key) + { + grub_free(ret.master_key); + ret.master_key = NULL; + return ret; + } + ret.algo = algo; + grub_memcpy(ret.hmac_key, plaintext + masterlen, hmaclen); + return ret; + } + return ret; +} + static const struct grub_arg_option options[] = { {"raw", 'r', 0, N_("Assume input is raw."), 0, 0}, @@ -471,12 +738,17 @@ grub_cmd_zfs_key (grub_extcmd_context_t ctxt, int argc, char **args) && !ctxt->state[1].set)); } +struct grub_zfs_decryptor grub_zfs_decrypt_real = { + .decrypt_oracle = grub_zfs_decrypt_oracle, + .decrypt_datto = grub_zfs_decrypt_datto, + .load_key_oracle = grub_zfs_load_key_oracle, + .load_key_datto = grub_zfs_load_key_datto, +}; static grub_extcmd_t cmd_key; GRUB_MOD_INIT(zfscrypt) { - grub_zfs_decrypt = grub_zfs_decrypt_real; - grub_zfs_load_key = grub_zfs_load_key_real; + grub_zfs_decrypt = &grub_zfs_decrypt_real; cmd_key = grub_register_extcmd ("zfskey", grub_cmd_zfs_key, 0, N_("[-h|-p|-r] [FILE]"), N_("Import ZFS wrapping key stored in FILE."), @@ -486,6 +758,5 @@ GRUB_MOD_INIT(zfscrypt) GRUB_MOD_FINI(zfscrypt) { grub_zfs_decrypt = 0; - grub_zfs_load_key = 0; grub_unregister_extcmd (cmd_key); } diff --git a/include/grub/zfs/zfs.h b/include/grub/zfs/zfs.h index 0c5c40a73..fbac368b0 100644 --- a/include/grub/zfs/zfs.h +++ b/include/grub/zfs/zfs.h @@ -147,20 +147,46 @@ grub_zfs_add_key (grub_uint8_t *key_in, grub_size_t keylen, int passphrase); -extern grub_err_t (*grub_zfs_decrypt) (grub_crypto_cipher_handle_t cipher, - grub_uint64_t algo, - const void *nonce, - char *buf, grub_size_t size, - const grub_uint32_t *expected_mac, - grub_zfs_endian_t endian); - -struct grub_zfs_key; - -extern grub_crypto_cipher_handle_t (*grub_zfs_load_key) (const struct grub_zfs_key *key, - grub_size_t keysize, - grub_uint64_t salt, - grub_uint64_t algo); - +struct grub_zfs_key_oracle; + +struct grub_zfs_datto_key +{ + grub_uint8_t *master_key; + grub_size_t master_keylen; + grub_uint8_t *hmac_key; + grub_uint64_t algo; +}; + +struct grub_zfs_decryptor +{ + grub_err_t (*decrypt_oracle) (grub_crypto_cipher_handle_t cipher, + grub_uint64_t algo, + const void *nonce, + char *buf, grub_size_t size, + const grub_uint32_t *expected_mac, + grub_zfs_endian_t endian); + + grub_crypto_cipher_handle_t (*load_key_oracle) (const struct grub_zfs_key_oracle *key, + grub_size_t keysize, + grub_uint64_t salt, + grub_uint64_t algo); + + grub_err_t (*decrypt_datto) (const struct grub_zfs_datto_key *key, + const grub_uint32_t *nonce, grub_uint64_t salt, + char *buf, grub_size_t size, + const char *aadbuf, grub_size_t aadsize, + const grub_uint64_t *expected_mac, + grub_zfs_endian_t endian); + + struct grub_zfs_datto_key (*load_key_datto) (const grub_uint8_t *iv, grub_size_t ivlen, + const grub_uint8_t *mac, grub_size_t maclen, + const grub_uint8_t *master, grub_size_t masterlen, + const grub_uint8_t *hmac, grub_size_t hmaclen, + const grub_uint8_t *pbkdf2salt, grub_size_t pbkdf2saltlen, + grub_uint64_t pbkdf2iters, grub_uint64_t guid, grub_uint64_t algo, grub_uint64_t version); +}; + +extern struct grub_zfs_decryptor *grub_zfs_decrypt; #endif /* ! GRUB_ZFS_HEADER */ -- 2.49.0 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel