{"action":"created","comment":{"url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/comments/100688090","pull_request_review_id":21403991,"id":100688090,"diff_hunk":"@@ -0,0 +1,1350 @@\n+/*\n+ * CDDL HEADER START\n+ *\n+ * This file and its contents are supplied under the terms of the\n+ * Common Development and Distribution License (\"CDDL\"), version 1.0.\n+ * You may only use this file in accordance with the terms of version\n+ * 1.0 of the CDDL.\n+ *\n+ * A full copy of the text of the CDDL should have accompanied this\n+ * source.  A copy of the CDDL is also available via the Internet at\n+ * http://www.illumos.org/license/CDDL.\n+ *\n+ * CDDL HEADER END\n+ */\n+\n+/*\n+ * Copyright (c) 2017, Datto, Inc. All rights reserved.\n+ */\n+\n+#include <sys/zio_crypt.h>\n+#include <sys/dmu.h>\n+#include <sys/dnode.h>\n+#include <sys/fs/zfs.h>\n+#include <sys/zio.h>\n+#include <sys/zil.h>\n+\n+/*\n+ * This file is responsible for handling all of the details of generating\n+ * encryption parameters and performing encryption.\n+ *\n+ * BLOCK ENCRYPTION PARAMETERS:\n+ * Encryption Algorithm (crypt):\n+ * The encryption algorithm and mode we are going to use. We currently support\n+ * AES-GCM and AES-CCM in 128, 192, and 256 bits. All encryption parameters are\n+ * stored in little endian format (regardless of the host machine's byteorder)\n+ * on-disk and in memory.\n+ *\n+ * Plaintext:\n+ * The unencrypted data that we want to encrypt\n+ *\n+ * Initialization Vector (IV):\n+ * An initialization vector for the encryption algorithms. This is\n+ * used to \"tweak\" the encryption algorithms so that equivalent blocks of\n+ * data are encrypted into different ciphertext outputs. Different modes\n+ * of encryption have different requirements for the IV. AES-GCM and AES-CCM\n+ * require that an IV is never reused with the same encryption key. This\n+ * value is stored unencrypted and must simply be provided to the decryption\n+ * function. We use a 96 bit IV (as recommended by NIST). For non-dedup blocks\n+ * we derive the IV randomly. The first 64 bits of the IV are stored in the\n+ * second word of DVA[2] and the remaining 32 bits are stored in the upper 32\n+ * bits of blk_fill. For most object types this is safe because we only encrypt\n+ * level 0 blocks which means that the fill count will be 1. For DMU_OT_DNODE\n+ * blocks the fill count is instead used to indicate the number of allocated\n+ * dnodes beneath the bp. The on-disk format supports at most 2^15 slots per\n+ * L0 dnode block, because the maximum block size is 16MB (2^24). In either\n+ * case, for level 0 blocks this number will still be smaller than UINT32_MAX\n+ * so it is safe to store the IV in the top 32 bits of blk_fill, while leaving\n+ * the bottom 32 bits of the fill count for the dnode code.\n+ *\n+ * Master key:\n+ * This is the most important secret data of an encrypted dataset. It is used\n+ * along with the salt to generate that actual encryption keys via HKDF. We\n+ * do not use the master key to encrypt any data because there are theoretical\n+ * limits on how much data can actually be safely encrypted with any encryption\n+ * mode. The master key is stored encrypted on disk with the user's key. It's\n+ * length is determined by the encryption algorithm. For details on how this is\n+ * stored see the block comment in dsl_crypt.c\n+ *\n+ * Salt:\n+ * Used as an input to the HKDF function, along with the master key. We use a\n+ * 64 bit salt, stored unencrypted in the first word of DVA[2]. Any given salt\n+ * can be used for encrypting many blocks, so we cache the current salt and the\n+ * associated derived key in zio_crypt_t so we do not need to derive it again\n+ * needlessly.\n+ *\n+ * Encryption Key:\n+ * A secret binary key, generated from an HKDF function used to encrypt and\n+ * decrypt data.\n+ *\n+ * Message Authenication Code (MAC)\n+ * The MAC is an output of authenticated encryption modes such as AES-GCM and\n+ * AES-CCM. Its purpose is to ensure that an attacker cannot modify encrypted\n+ * data on disk and return garbage to the application. Effectively, it is a\n+ * checksum that can not be reproduced by an attacker. We store the MAC in the\n+ * second 128 bits of blk_cksum, leaving the first 128 bits for a truncated\n+ * regular checksum of the ciphertext which can be used for scrubbing.\n+ *\n+ * ZIL ENCRYPTION:\n+ * ZIL blocks have their bp written to disk ahead of the associated data, so we\n+ * cannot store encryption paramaters there as we normally do. For these blocks\n+ * the MAC is stored in the zil_chain_t header (in zc_mac) in a previously\n+ * unused 8 bytes. The salt and IV are generated for the block on bp allocation.\n+ * Since ZIL blocks are rewritten many times as new log records are added it is\n+ * important that we do not reuse the IV with the same salt. To accomplish this\n+ * we add in zc_nused from the zil_chain_t which should be incremented on each\n+ * rewrite.\n+ *\n+ * CONSIDERATIONS FOR DEDUP:\n+ * In order for dedup to work, blocks that we want to dedup with one another\n+ * need to use the same IV and encryption key, so that they will have the same\n+ * cyphertext. Normally, one should never reuse an IV with the same encryption\n+ * key or else AES-GCM and AES-CCM can both actually leak the plaintext of both\n+ * blocks. In this case, however, since we are using the same plaindata as\n+ * well all that we end up with is a duplicate of the original data we already\n+ * had. As a result, an attacker with read access to the raw disk will be able\n+ * to tell which blocks are the same but this information is already given away\n+ * by dedup anyway. In order to get the same IVs and encryption keys for\n+ * equivalent blocks of data we use a HMAC of the plaindata. We use an HMAC\n+ * here so there is never a reproducible checksum of the plaindata available\n+ * to the attacker. The HMAC key is kept alongside the master key, encrypted\n+ * on disk. The first 64 bits of the HMAC are used in place of the random salt,\n+ * and the next 96 bits are used as the IV.\n+ */\n+\n+zio_crypt_info_t zio_crypt_table[ZIO_CRYPT_FUNCTIONS] = {\n+\t{\"\",\t\t\tZC_TYPE_NONE,\t0,\t\"inherit\"},\n+\t{\"\",\t\t\tZC_TYPE_NONE,\t0,\t\"on\"},\n+\t{\"\",\t\t\tZC_TYPE_NONE,\t0,\t\"off\"},\n+\t{SUN_CKM_AES_CCM,\tZC_TYPE_CCM,\t16,\t\"aes-128-ccm\"},\n+\t{SUN_CKM_AES_CCM,\tZC_TYPE_CCM,\t24,\t\"aes-192-ccm\"},\n+\t{SUN_CKM_AES_CCM,\tZC_TYPE_CCM,\t32,\t\"aes-256-ccm\"},\n+\t{SUN_CKM_AES_GCM,\tZC_TYPE_GCM,\t16,\t\"aes-128-gcm\"},\n+\t{SUN_CKM_AES_GCM,\tZC_TYPE_GCM,\t24,\t\"aes-192-gcm\"},\n+\t{SUN_CKM_AES_GCM,\tZC_TYPE_GCM,\t32,\t\"aes-256-gcm\"}\n+};\n+\n+static int\n+hkdf_sha256_extract(uint8_t *salt, uint_t salt_len, uint8_t *key_material,\n+    uint_t km_len, uint8_t *out_buf)\n+{\n+\tint ret;\n+\tcrypto_mechanism_t mech;\n+\tcrypto_key_t key;\n+\tcrypto_data_t input_cd, output_cd;\n+\n+\t/* initialize sha 256 hmac mechanism */\n+\tmech.cm_type = crypto_mech2id(SUN_CKM_SHA256_HMAC);\n+\tmech.cm_param = NULL;\n+\tmech.cm_param_len = 0;\n+\n+\t/* initialize the salt as a crypto key */\n+\tkey.ck_format = CRYPTO_KEY_RAW;\n+\tkey.ck_length = BYTES_TO_BITS(salt_len);\n+\tkey.ck_data = salt;\n+\n+\t/* initialize crypto data for the input and output data */\n+\tinput_cd.cd_format = CRYPTO_DATA_RAW;\n+\tinput_cd.cd_offset = 0;\n+\tinput_cd.cd_length = km_len;\n+\tinput_cd.cd_raw.iov_base = (char *)key_material;\n+\tinput_cd.cd_raw.iov_len = km_len;\n+\n+\toutput_cd.cd_format = CRYPTO_DATA_RAW;\n+\toutput_cd.cd_offset = 0;\n+\toutput_cd.cd_length = SHA_256_DIGEST_LEN;\n+\toutput_cd.cd_raw.iov_base = (char *)out_buf;\n+\toutput_cd.cd_raw.iov_len = SHA_256_DIGEST_LEN;\n+\n+\tret = crypto_mac(&mech, &input_cd, &key, NULL, &output_cd, NULL);\n+\tif (ret != CRYPTO_SUCCESS) {\n+\t\tret = SET_ERROR(EIO);\n+\t\tgoto error;\n+\t}\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+static int\n+hkdf_sha256_expand(uint8_t *extract_key, uint8_t *info, uint_t info_len,\n+    uint8_t *out_buf, uint_t out_len)\n+{\n+\tint ret;\n+\tcrypto_mechanism_t mech;\n+\tcrypto_context_t ctx;\n+\tcrypto_key_t key;\n+\tcrypto_data_t T_cd, info_cd, c_cd;\n+\tuint_t i, T_len = 0, pos = 0;\n+\tuint8_t c;\n+\tuint_t N = (out_len + SHA_256_DIGEST_LEN) / SHA_256_DIGEST_LEN;\n+\tuint8_t T[SHA_256_DIGEST_LEN];\n+\n+\tif (N > 255)\n+\t\treturn (SET_ERROR(EINVAL));\n+\n+\t/* initialize sha 256 hmac mechanism */\n+\tmech.cm_type = crypto_mech2id(SUN_CKM_SHA256_HMAC);\n+\tmech.cm_param = NULL;\n+\tmech.cm_param_len = 0;\n+\n+\t/* initialize the salt as a crypto key */\n+\tkey.ck_format = CRYPTO_KEY_RAW;\n+\tkey.ck_length = BYTES_TO_BITS(SHA_256_DIGEST_LEN);\n+\tkey.ck_data = extract_key;\n+\n+\t/* initialize crypto data for the input and output data */\n+\tT_cd.cd_format = CRYPTO_DATA_RAW;\n+\tT_cd.cd_offset = 0;\n+\tT_cd.cd_raw.iov_base = (char *)T;\n+\n+\tc_cd.cd_format = CRYPTO_DATA_RAW;\n+\tc_cd.cd_offset = 0;\n+\tc_cd.cd_length = 1;\n+\tc_cd.cd_raw.iov_base = (char *)&c;\n+\tc_cd.cd_raw.iov_len = 1;\n+\n+\tinfo_cd.cd_format = CRYPTO_DATA_RAW;\n+\tinfo_cd.cd_offset = 0;\n+\tinfo_cd.cd_length = info_len;\n+\tinfo_cd.cd_raw.iov_base = (char *)info;\n+\tinfo_cd.cd_raw.iov_len = info_len;\n+\n+\tfor (i = 1; i <= N; i++) {\n+\t\tc = i;\n+\n+\t\tT_cd.cd_length = T_len;\n+\t\tT_cd.cd_raw.iov_len = T_len;\n+\n+\t\tret = crypto_mac_init(&mech, &key, NULL, &ctx, NULL);\n+\t\tif (ret != CRYPTO_SUCCESS) {\n+\t\t\tret = SET_ERROR(EIO);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tret = crypto_mac_update(ctx, &T_cd, NULL);\n+\t\tif (ret != CRYPTO_SUCCESS) {\n+\t\t\tret = SET_ERROR(EIO);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tret = crypto_mac_update(ctx, &info_cd, NULL);\n+\t\tif (ret != CRYPTO_SUCCESS) {\n+\t\t\tret = SET_ERROR(EIO);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tret = crypto_mac_update(ctx, &c_cd, NULL);\n+\t\tif (ret != CRYPTO_SUCCESS) {\n+\t\t\tret = SET_ERROR(EIO);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tT_len = SHA_256_DIGEST_LEN;\n+\t\tT_cd.cd_length = T_len;\n+\t\tT_cd.cd_raw.iov_len = T_len;\n+\n+\t\tret = crypto_mac_final(ctx, &T_cd, NULL);\n+\t\tif (ret != CRYPTO_SUCCESS) {\n+\t\t\tret = SET_ERROR(EIO);\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tbcopy(T, out_buf + pos,\n+\t\t    (i != N) ? SHA_256_DIGEST_LEN : (out_len - pos));\n+\t\tpos += SHA_256_DIGEST_LEN;\n+\t}\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+/*\n+ * HKDF is designed to be a relatively fast function for deriving keys from a\n+ * master key + a salt. We use this function to generate new encryption keys\n+ * so as to avoid hitting the cryptographic limits of the underlying\n+ * encryption modes. Note that, for the sake of deriving encryption keys, the\n+ * info parameter is called the \"salt\" everywhere else in the code.\n+ */\n+static int\n+hkdf_sha256(uint8_t *key_material, uint_t km_len, uint8_t *salt,\n+    uint_t salt_len, uint8_t *info, uint_t info_len, uint8_t *output_key,\n+    uint_t out_len)\n+{\n+\tint ret;\n+\tuint8_t extract_key[SHA_256_DIGEST_LEN];\n+\n+\tret = hkdf_sha256_extract(salt, salt_len, key_material, km_len,\n+\t    extract_key);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\tret = hkdf_sha256_expand(extract_key, info, info_len, output_key,\n+\t    out_len);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+void\n+zio_crypt_key_destroy(zio_crypt_key_t *key)\n+{\n+\trw_destroy(&key->zk_salt_lock);\n+\n+\t/* free crypto templates */\n+\tcrypto_destroy_ctx_template(key->zk_current_tmpl);\n+\tcrypto_destroy_ctx_template(key->zk_hmac_tmpl);\n+\n+\t/* zero out sensitive data */\n+\tbzero(key, sizeof (zio_crypt_key_t));\n+}\n+\n+int\n+zio_crypt_key_init(uint64_t crypt, zio_crypt_key_t *key)\n+{\n+\tint ret;\n+\tcrypto_mechanism_t mech;\n+\tuint_t keydata_len;\n+\n+\tASSERT(key != NULL);\n+\tASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS);\n+\n+\tkeydata_len = zio_crypt_table[crypt].ci_keylen;\n+\n+\t/* fill keydata buffers and salt with random data */\n+\tret = random_get_bytes(key->zk_master_keydata, keydata_len);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\tret = random_get_bytes(key->zk_hmac_keydata, HMAC_SHA256_KEYLEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\tret = random_get_bytes(key->zk_salt, DATA_SALT_LEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* derive the current key from the master key */\n+\tret = hkdf_sha256(key->zk_master_keydata, keydata_len, NULL, 0,\n+\t    key->zk_salt, DATA_SALT_LEN, key->zk_current_keydata, keydata_len);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* initialize keys for the ICP */\n+\tkey->zk_current_key.ck_format = CRYPTO_KEY_RAW;\n+\tkey->zk_current_key.ck_data = key->zk_current_keydata;\n+\tkey->zk_current_key.ck_length = BYTES_TO_BITS(keydata_len);\n+\n+\tkey->zk_hmac_key.ck_format = CRYPTO_KEY_RAW;\n+\tkey->zk_hmac_key.ck_data = &key->zk_hmac_key;\n+\tkey->zk_hmac_key.ck_length = BYTES_TO_BITS(HMAC_SHA256_KEYLEN);\n+\n+\t/*\n+\t * Initialize the crypto templates. It's ok if this fails because\n+\t * this is just an optimization.\n+\t */\n+\tmech.cm_type = crypto_mech2id(zio_crypt_table[crypt].ci_mechname);\n+\tret = crypto_create_ctx_template(&mech, &key->zk_current_key,\n+\t    &key->zk_current_tmpl, KM_SLEEP);\n+\tif (ret != CRYPTO_SUCCESS)\n+\t\tkey->zk_current_tmpl = NULL;\n+\n+\tmech.cm_type = crypto_mech2id(SUN_CKM_SHA256_HMAC);\n+\tret = crypto_create_ctx_template(&mech, &key->zk_hmac_key,\n+\t    &key->zk_hmac_tmpl, KM_SLEEP);\n+\tif (ret != CRYPTO_SUCCESS)\n+\t\tkey->zk_hmac_tmpl = NULL;\n+\n+\tkey->zk_crypt = crypt;\n+\tkey->zk_salt_count = 0;\n+\trw_init(&key->zk_salt_lock, NULL, RW_DEFAULT, NULL);\n+\n+\treturn (0);\n+\n+error:\n+\tzio_crypt_key_destroy(key);\n+\treturn (ret);\n+}\n+\n+static int\n+zio_crypt_key_change_salt(zio_crypt_key_t *key)\n+{\n+\tint ret;\n+\tuint8_t salt[DATA_SALT_LEN];\n+\tcrypto_mechanism_t mech;\n+\tuint_t keydata_len = zio_crypt_table[key->zk_crypt].ci_keylen;\n+\n+\t/* generate a new salt */\n+\tret = random_get_bytes(salt, DATA_SALT_LEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\trw_enter(&key->zk_salt_lock, RW_WRITER);\n+\n+\t/* derive the current key from the master key and the new salt */\n+\tret = hkdf_sha256(key->zk_master_keydata, keydata_len, NULL, 0,\n+\t    salt, DATA_SALT_LEN, key->zk_current_keydata, keydata_len);\n+\tif (ret != 0)\n+\t\tgoto error_unlock;\n+\n+\t/* assign the salt and reset the usage count */\n+\tbcopy(salt, key->zk_salt, DATA_SALT_LEN);\n+\tkey->zk_salt_count = 0;\n+\n+\t/* destroy the old context template and create the new one */\n+\tcrypto_destroy_ctx_template(key->zk_current_tmpl);\n+\tret = crypto_create_ctx_template(&mech, &key->zk_current_key,\n+\t    &key->zk_current_tmpl, KM_SLEEP);\n+\tif (ret != CRYPTO_SUCCESS)\n+\t\tkey->zk_current_tmpl = NULL;\n+\n+\trw_exit(&key->zk_salt_lock);\n+\n+\treturn (0);\n+\n+error_unlock:\n+\trw_exit(&key->zk_salt_lock);\n+error:\n+\treturn (ret);\n+}\n+\n+/* See comment above ZIO_CRYPT_MAX_SALT_USAGE definition for details */\n+int\n+zio_crypt_key_get_salt(zio_crypt_key_t *key, uint8_t *salt)\n+{\n+\tint ret;\n+\tboolean_t salt_change;\n+\n+\trw_enter(&key->zk_salt_lock, RW_READER);\n+\n+\tbcopy(key->zk_salt, salt, DATA_SALT_LEN);\n+\tsalt_change = (atomic_inc_64_nv(&key->zk_salt_count) ==\n+\t    ZIO_CRYPT_MAX_SALT_USAGE);\n+\n+\trw_exit(&key->zk_salt_lock);\n+\n+\tif (salt_change) {\n+\t\tret = zio_crypt_key_change_salt(key);\n+\t\tif (ret != 0)\n+\t\t\tgoto error;\n+\t}\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+/*\n+ * This function handles all encryption and decryption in zfs. When\n+ * encrypting it expects puio to refernce the plaintext and cuio to\n+ * have enough space for the ciphertext + room for a MAC. On decrypting\n+ * it expects both puio and cuio to have enough room for a MAC, although\n+ * the plaintext uio can be dsicarded afterwards. datalen should be the\n+ * length of only the plaintext / ciphertext in either case.\n+ */\n+static int\n+zio_do_crypt_uio(boolean_t encrypt, uint64_t crypt, crypto_key_t *key,\n+    crypto_ctx_template_t tmpl, uint8_t *ivbuf, uint_t datalen,\n+    uio_t *puio, uio_t *cuio)\n+{\n+\tint ret;\n+\tcrypto_data_t plaindata, cipherdata;\n+\tCK_AES_CCM_PARAMS ccmp;\n+\tCK_AES_GCM_PARAMS gcmp;\n+\tcrypto_mechanism_t mech;\n+\tzio_crypt_info_t crypt_info;\n+\tuint_t plain_full_len, maclen;\n+\n+\tASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS);\n+\tASSERT3U(key->ck_format, ==, CRYPTO_KEY_RAW);\n+\n+\t/* lookup the encryption info */\n+\tcrypt_info = zio_crypt_table[crypt];\n+\n+\t/* the mac will always be the last iovec_t in the cipher uio */\n+\tmaclen = cuio->uio_iov[cuio->uio_iovcnt - 1].iov_len;\n+\n+\tASSERT(maclen <= DATA_MAC_LEN);\n+\n+\t/* setup encryption mechanism (same as crypt) */\n+\tmech.cm_type = crypto_mech2id(crypt_info.ci_mechname);\n+\n+\t/* plain length will include the MAC if we are decrypting */\n+\tif (encrypt) {\n+\t\tplain_full_len = datalen;\n+\t} else {\n+\t\tplain_full_len = datalen + maclen;\n+\t}\n+\n+\t/*\n+\t * setup encryption params (currently only AES CCM and AES GCM\n+\t * are supported)\n+\t */\n+\tif (crypt_info.ci_crypt_type == ZC_TYPE_CCM) {\n+\t\tccmp.ulNonceSize = DATA_IV_LEN;\n+\t\tccmp.ulAuthDataSize = 0;\n+\t\tccmp.authData = NULL;\n+\t\tccmp.ulMACSize = maclen;\n+\t\tccmp.nonce = ivbuf;\n+\t\tccmp.ulDataSize = plain_full_len;\n+\n+\t\tmech.cm_param = (char *)(&ccmp);\n+\t\tmech.cm_param_len = sizeof (CK_AES_CCM_PARAMS);\n+\t} else {\n+\t\tgcmp.ulIvLen = DATA_IV_LEN;\n+\t\tgcmp.ulIvBits = BYTES_TO_BITS(DATA_IV_LEN);\n+\t\tgcmp.ulAADLen = 0;\n+\t\tgcmp.pAAD = NULL;\n+\t\tgcmp.ulTagBits = BYTES_TO_BITS(maclen);\n+\t\tgcmp.pIv = ivbuf;\n+\n+\t\tmech.cm_param = (char *)(&gcmp);\n+\t\tmech.cm_param_len = sizeof (CK_AES_GCM_PARAMS);\n+\t}\n+\n+\t/* populate the cipher and plain data structs. */\n+\tplaindata.cd_format = CRYPTO_DATA_UIO;\n+\tplaindata.cd_offset = 0;\n+\tplaindata.cd_uio = puio;\n+\tplaindata.cd_miscdata = NULL;\n+\tplaindata.cd_length = plain_full_len;\n+\n+\tcipherdata.cd_format = CRYPTO_DATA_UIO;\n+\tcipherdata.cd_offset = 0;\n+\tcipherdata.cd_uio = cuio;\n+\tcipherdata.cd_miscdata = NULL;\n+\tcipherdata.cd_length = datalen + maclen;\n+\n+\t/* perform the actual encryption */\n+\tif (encrypt) {\n+\t\tret = crypto_encrypt(&mech, &plaindata, key, tmpl, &cipherdata,\n+\t\t    NULL);\n+\t} else {\n+\t\tret = crypto_decrypt(&mech, &cipherdata, key, tmpl, &plaindata,\n+\t\t    NULL);\n+\t}\n+\n+\tif (ret != CRYPTO_SUCCESS) {\n+\t\tret = SET_ERROR(EIO);\n+\t\tgoto error;\n+\t}\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+int\n+zio_crypt_key_wrap(crypto_key_t *cwkey, zio_crypt_key_t *key, uint8_t *iv,\n+    uint8_t *mac, uint8_t *keydata_out, uint8_t *hmac_keydata_out)\n+{\n+\tint ret;\n+\tuio_t puio, cuio;\n+\tiovec_t plain_iovecs[2], cipher_iovecs[3];\n+\tuint64_t crypt = key->zk_crypt;\n+\tuint_t enc_len, keydata_len;\n+\n+\tASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS);\n+\tASSERT3U(cwkey->ck_format, ==, CRYPTO_KEY_RAW);\n+\n+\tkeydata_len = zio_crypt_table[crypt].ci_keylen;\n+\n+\t/* generate iv for wrapping the master and hmac key */\n+\tret = random_get_pseudo_bytes(iv, WRAPPING_IV_LEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* initialize uio_ts */\n+\tplain_iovecs[0].iov_base = key->zk_master_keydata;\n+\tplain_iovecs[0].iov_len = keydata_len;\n+\tplain_iovecs[1].iov_base = key->zk_hmac_keydata;\n+\tplain_iovecs[1].iov_len = HMAC_SHA256_KEYLEN;\n+\n+\tcipher_iovecs[0].iov_base = keydata_out;\n+\tcipher_iovecs[0].iov_len = keydata_len;\n+\tcipher_iovecs[1].iov_base = hmac_keydata_out;\n+\tcipher_iovecs[1].iov_len = HMAC_SHA256_KEYLEN;\n+\tcipher_iovecs[2].iov_base = mac;\n+\tcipher_iovecs[2].iov_len = WRAPPING_MAC_LEN;\n+\n+\tenc_len = zio_crypt_table[crypt].ci_keylen + HMAC_SHA256_KEYLEN;\n+\tpuio.uio_iov = plain_iovecs;\n+\tpuio.uio_iovcnt = 2;\n+\tpuio.uio_segflg = UIO_SYSSPACE;\n+\tcuio.uio_iov = cipher_iovecs;\n+\tcuio.uio_iovcnt = 3;\n+\tcuio.uio_segflg = UIO_SYSSPACE;\n+\n+\t/* encrypt the keys and store the resulting ciphertext and mac */\n+\tret = zio_do_crypt_uio(B_TRUE, crypt, cwkey, NULL, iv, enc_len,\n+\t    &puio, &cuio);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+int\n+zio_crypt_key_unwrap(crypto_key_t *cwkey, uint64_t crypt, uint8_t *keydata,\n+    uint8_t *hmac_keydata, uint8_t *iv, uint8_t *mac, zio_crypt_key_t *key)\n+{\n+\tint ret;\n+\tcrypto_mechanism_t mech;\n+\tuio_t puio, cuio;\n+\tiovec_t plain_iovecs[3], cipher_iovecs[3];\n+\tuint8_t outmac[WRAPPING_MAC_LEN];\n+\tuint_t enc_len, keydata_len;\n+\n+\tASSERT3U(crypt, <, ZIO_CRYPT_FUNCTIONS);\n+\tASSERT3U(cwkey->ck_format, ==, CRYPTO_KEY_RAW);\n+\n+\tkeydata_len = zio_crypt_table[crypt].ci_keylen;\n+\n+\t/* initialize uio_ts */\n+\tplain_iovecs[0].iov_base = key->zk_master_keydata;\n+\tplain_iovecs[0].iov_len = keydata_len;\n+\tplain_iovecs[1].iov_base = key->zk_hmac_keydata;\n+\tplain_iovecs[1].iov_len = HMAC_SHA256_KEYLEN;\n+\tplain_iovecs[2].iov_base = outmac;\n+\tplain_iovecs[2].iov_len = WRAPPING_MAC_LEN;\n+\n+\tcipher_iovecs[0].iov_base = keydata;\n+\tcipher_iovecs[0].iov_len = keydata_len;\n+\tcipher_iovecs[1].iov_base = hmac_keydata;\n+\tcipher_iovecs[1].iov_len = HMAC_SHA256_KEYLEN;\n+\tcipher_iovecs[2].iov_base = mac;\n+\tcipher_iovecs[2].iov_len = WRAPPING_MAC_LEN;\n+\n+\tenc_len = keydata_len + HMAC_SHA256_KEYLEN;\n+\tpuio.uio_iov = plain_iovecs;\n+\tpuio.uio_segflg = UIO_SYSSPACE;\n+\tpuio.uio_iovcnt = 3;\n+\tcuio.uio_iov = cipher_iovecs;\n+\tcuio.uio_iovcnt = 3;\n+\tcuio.uio_segflg = UIO_SYSSPACE;\n+\n+\t/* decrypt the keys and store the result in the output buffers */\n+\tret = zio_do_crypt_uio(B_FALSE, crypt, cwkey, NULL, iv, enc_len,\n+\t    &puio, &cuio);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* generate a fresh salt */\n+\tret = random_get_bytes(key->zk_salt, DATA_SALT_LEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* derive the current key from the master key */\n+\tret = hkdf_sha256(key->zk_master_keydata, keydata_len, NULL, 0,\n+\t    key->zk_salt, DATA_SALT_LEN, key->zk_current_keydata, keydata_len);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\t/* initialize keys for ICP */\n+\tkey->zk_current_key.ck_format = CRYPTO_KEY_RAW;\n+\tkey->zk_current_key.ck_data = key->zk_current_keydata;\n+\tkey->zk_current_key.ck_length = BYTES_TO_BITS(keydata_len);\n+\n+\tkey->zk_hmac_key.ck_format = CRYPTO_KEY_RAW;\n+\tkey->zk_hmac_key.ck_data = key->zk_hmac_keydata;\n+\tkey->zk_hmac_key.ck_length = BYTES_TO_BITS(HMAC_SHA256_KEYLEN);\n+\n+\t/*\n+\t * Initialize the crypto templates. It's ok if this fails because\n+\t * this is just an optimization.\n+\t */\n+\tmech.cm_type = crypto_mech2id(zio_crypt_table[crypt].ci_mechname);\n+\tret = crypto_create_ctx_template(&mech, &key->zk_current_key,\n+\t    &key->zk_current_tmpl, KM_SLEEP);\n+\tif (ret != CRYPTO_SUCCESS)\n+\t\tkey->zk_current_tmpl = NULL;\n+\n+\tmech.cm_type = crypto_mech2id(SUN_CKM_SHA256_HMAC);\n+\tret = crypto_create_ctx_template(&mech, &key->zk_hmac_key,\n+\t    &key->zk_hmac_tmpl, KM_SLEEP);\n+\tif (ret != CRYPTO_SUCCESS)\n+\t\tkey->zk_hmac_tmpl = NULL;\n+\n+\tkey->zk_crypt = crypt;\n+\tkey->zk_salt_count = 0;\n+\trw_init(&key->zk_salt_lock, NULL, RW_DEFAULT, NULL);\n+\n+\treturn (0);\n+\n+error:\n+\tzio_crypt_key_destroy(key);\n+\treturn (ret);\n+}\n+\n+int\n+zio_crypt_generate_iv(uint8_t *ivbuf)\n+{\n+\tint ret;\n+\n+\t/* randomly generate the IV */\n+\tret = random_get_pseudo_bytes(ivbuf, DATA_IV_LEN);\n+\tif (ret != 0)\n+\t\tgoto error;\n+\n+\treturn (0);\n+\n+error:\n+\tbzero(ivbuf, DATA_IV_LEN);\n+\treturn (ret);\n+}\n+\n+int\n+zio_crypt_generate_iv_salt_dedup(zio_crypt_key_t *key, uint8_t *data,\n+    uint_t datalen, uint8_t *ivbuf, uint8_t *salt)\n+{\n+\tint ret;\n+\tcrypto_mechanism_t mech;\n+\tcrypto_data_t in_data, digest_data;\n+\tuint8_t digestbuf[SHA_256_DIGEST_LEN];\n+\n+\t/* initialize sha256-hmac mechanism and crypto data */\n+\tmech.cm_type = crypto_mech2id(SUN_CKM_SHA256_HMAC);\n+\tmech.cm_param = NULL;\n+\tmech.cm_param_len = 0;\n+\n+\t/* initialize the crypto data */\n+\tin_data.cd_format = CRYPTO_DATA_RAW;\n+\tin_data.cd_offset = 0;\n+\tin_data.cd_length = datalen;\n+\tin_data.cd_raw.iov_base = (char *)data;\n+\tin_data.cd_raw.iov_len = datalen;\n+\n+\tdigest_data.cd_format = CRYPTO_DATA_RAW;\n+\tdigest_data.cd_offset = 0;\n+\tdigest_data.cd_length = SHA_256_DIGEST_LEN;\n+\tdigest_data.cd_raw.iov_base = (char *)digestbuf;\n+\tdigest_data.cd_raw.iov_len = SHA_256_DIGEST_LEN;\n+\n+\t/* generate the hmac */\n+\tret = crypto_mac(&mech, &in_data, &key->zk_hmac_key, key->zk_hmac_tmpl,\n+\t    &digest_data, NULL);\n+\tif (ret != CRYPTO_SUCCESS) {\n+\t\tret = SET_ERROR(EIO);\n+\t\tgoto error;\n+\t}\n+\n+\t/* truncate and copy the digest into the output buffer */\n+\tbcopy(digestbuf, salt, DATA_SALT_LEN);\n+\tbcopy(digestbuf + DATA_SALT_LEN, ivbuf, DATA_IV_LEN);\n+\n+\treturn (0);\n+\n+error:\n+\treturn (ret);\n+}\n+\n+void\n+zio_crypt_encode_params_bp(blkptr_t *bp, uint8_t *salt, uint8_t *iv)\n+{\n+\tuint32_t *iv2 = (uint32_t *)(iv + sizeof (uint64_t));\n+\n+\tASSERT(BP_IS_ENCRYPTED(bp));\n+\tbp->blk_dva[2].dva_word[0] = LE_64(*((uint64_t *)salt));","path":"module/zfs/zio_crypt.c","position":760,"original_position":760,"commit_id":"8594e088ae071ced1353f7ace82eff8d70a1938a","original_commit_id":"8594e088ae071ced1353f7ace82eff8d70a1938a","user":{"login":"tcaputi","id":1920053,"avatar_url":"https://avatars.githubusercontent.com/u/1920053?v=3","gravatar_id":"","url":"https://api.github.com/users/tcaputi","html_url":"https://github.com/tcaputi","followers_url":"https://api.github.com/users/tcaputi/followers","following_url":"https://api.github.com/users/tcaputi/following{/other_user}","gists_url":"https://api.github.com/users/tcaputi/gists{/gist_id}","starred_url":"https://api.github.com/users/tcaputi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tcaputi/subscriptions","organizations_url":"https://api.github.com/users/tcaputi/orgs","repos_url":"https://api.github.com/users/tcaputi/repos","events_url":"https://api.github.com/users/tcaputi/events{/privacy}","received_events_url":"https://api.github.com/users/tcaputi/received_events","type":"User","site_admin":false},"body":"will fix.","created_at":"2017-02-12T10:12:24Z","updated_at":"2017-02-12T10:12:24Z","html_url":"https://github.com/zfsonlinux/zfs/pull/5769#discussion_r100688090","pull_request_url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769","_links":{"self":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/comments/100688090"},"html":{"href":"https://github.com/zfsonlinux/zfs/pull/5769#discussion_r100688090"},"pull_request":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769"}}},"pull_request":{"url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769","id":105505588,"html_url":"https://github.com/zfsonlinux/zfs/pull/5769","diff_url":"https://github.com/zfsonlinux/zfs/pull/5769.diff","patch_url":"https://github.com/zfsonlinux/zfs/pull/5769.patch","issue_url":"https://api.github.com/repos/zfsonlinux/zfs/issues/5769","number":5769,"state":"open","locked":false,"title":"ZFS Encryption","user":{"login":"tcaputi","id":1920053,"avatar_url":"https://avatars.githubusercontent.com/u/1920053?v=3","gravatar_id":"","url":"https://api.github.com/users/tcaputi","html_url":"https://github.com/tcaputi","followers_url":"https://api.github.com/users/tcaputi/followers","following_url":"https://api.github.com/users/tcaputi/following{/other_user}","gists_url":"https://api.github.com/users/tcaputi/gists{/gist_id}","starred_url":"https://api.github.com/users/tcaputi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tcaputi/subscriptions","organizations_url":"https://api.github.com/users/tcaputi/orgs","repos_url":"https://api.github.com/users/tcaputi/repos","events_url":"https://api.github.com/users/tcaputi/events{/privacy}","received_events_url":"https://api.github.com/users/tcaputi/received_events","type":"User","site_admin":false},"body":"## Native encryption in zfsonlinux (See issue #494)\r\n\r\nThe change incorporates 2 major pieces:\r\n\r\nThe first feature is a keystore that manages wrapping and encryption keys for encrypted datasets. The commands are similar to that of Solaris but with a few key enhancements to make it more predictable, more consistent, and require less manual maintenance. It is fully integrated with the existing `zfs create` functions and `zfs clone` functions. It also exposes a new set of commands via `zfs key` for managing the keystore. For more info on the issues with the Solaris implementation see my comments [here](https://github.com/zfsonlinux/zfs/issues/494#issuecomment-178853634) and [here](https://github.com/openzfs/openzfs/pull/124#issuecomment-235724801). The keystore operates on a few rules.\r\n- All wrapping keys are 32 bytes (256 bits), even for 128 and 192 bit encryption types.\r\n- Encryption must be specified at dataset creation time.\r\n- Specifying a `keylocation` or `keyformat` while creating a dataset causes the dataset to become the root of an encryption tree.\r\n- All members of an encryption tree share the same wrapping key.\r\n- Each dataset can have up to 1 keychain (if it is encrypted) that is not shared with anybody.\r\n\r\nThe second feature is the actual data and metadata encryption. All user data in an encrypted dataset is stored encrypted on-disk. User-provided metadata is also encrypted, but metadata structures have been left plain so that scrubbing and resilvering still works without the keys loaded. The design was originallly inspired by [this article](https://blogs.oracle.com/darren/entry/zfs_encryption_what_is_on) but has been changed fairly significantly since.\r\n### Implementation details that should be looked at\r\n- Encrypting data going to disk requires creating a `key_mapping_t` during `dsl_dataset_tryown()`. I added a flag to this function for code that wishes to own the dataset, but that does not require encrypted data, such as the scrub functions. I did my best to confirm that all owners set this flag correctly, but someone should confirm them, just to be sure.\r\n- `zfs send` and `zfs recv` do not currently do anything special with regards to encryption. The format of the send file has not changed and zfs send requires the keys to be loaded in order to work. At some point there should probably be a way to do raw sends.\r\n- I altered the prototype of `lzc_create() and lzc_clone()` to support hidden arguments. I understand that the purpose of libzfs_core is to have a stable api interacting with the ZFS ioctls. However, these functions need to accept wrapping keys separately from the rest of their parameters because they need to use the (new) hidden_args framework to support hiding arguments from the logs. Without this, the wrapping keys would get printed to the zpool history.\r\n\r\nEDIT 5/4/16: Updated to reflect the current state of the PR\r\nEDIT 1/3/17: Updated to reflect the current state of the PR\r\nEDIT 2/9/17: reopened under a new PR due to long Github load times (previous PR was #4329)","created_at":"2017-02-09T20:20:10Z","updated_at":"2017-02-12T10:12:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"172b84fbbf9af136c69e8d55c943bae771947970","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769/commits","review_comments_url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769/comments","review_comment_url":"https://api.github.com/repos/zfsonlinux/zfs/pulls/comments{/number}","comments_url":"https://api.github.com/repos/zfsonlinux/zfs/issues/5769/comments","statuses_url":"https://api.github.com/repos/zfsonlinux/zfs/statuses/8594e088ae071ced1353f7ace82eff8d70a1938a","head":{"label":"tcaputi:master","ref":"master","sha":"8594e088ae071ced1353f7ace82eff8d70a1938a","user":{"login":"tcaputi","id":1920053,"avatar_url":"https://avatars.githubusercontent.com/u/1920053?v=3","gravatar_id":"","url":"https://api.github.com/users/tcaputi","html_url":"https://github.com/tcaputi","followers_url":"https://api.github.com/users/tcaputi/followers","following_url":"https://api.github.com/users/tcaputi/following{/other_user}","gists_url":"https://api.github.com/users/tcaputi/gists{/gist_id}","starred_url":"https://api.github.com/users/tcaputi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tcaputi/subscriptions","organizations_url":"https://api.github.com/users/tcaputi/orgs","repos_url":"https://api.github.com/users/tcaputi/repos","events_url":"https://api.github.com/users/tcaputi/events{/privacy}","received_events_url":"https://api.github.com/users/tcaputi/received_events","type":"User","site_admin":false},"repo":{"id":47715883,"name":"zfs","full_name":"tcaputi/zfs","owner":{"login":"tcaputi","id":1920053,"avatar_url":"https://avatars.githubusercontent.com/u/1920053?v=3","gravatar_id":"","url":"https://api.github.com/users/tcaputi","html_url":"https://github.com/tcaputi","followers_url":"https://api.github.com/users/tcaputi/followers","following_url":"https://api.github.com/users/tcaputi/following{/other_user}","gists_url":"https://api.github.com/users/tcaputi/gists{/gist_id}","starred_url":"https://api.github.com/users/tcaputi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tcaputi/subscriptions","organizations_url":"https://api.github.com/users/tcaputi/orgs","repos_url":"https://api.github.com/users/tcaputi/repos","events_url":"https://api.github.com/users/tcaputi/events{/privacy}","received_events_url":"https://api.github.com/users/tcaputi/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tcaputi/zfs","description":"Native ZFS for Linux","fork":true,"url":"https://api.github.com/repos/tcaputi/zfs","forks_url":"https://api.github.com/repos/tcaputi/zfs/forks","keys_url":"https://api.github.com/repos/tcaputi/zfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tcaputi/zfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tcaputi/zfs/teams","hooks_url":"https://api.github.com/repos/tcaputi/zfs/hooks","issue_events_url":"https://api.github.com/repos/tcaputi/zfs/issues/events{/number}","events_url":"https://api.github.com/repos/tcaputi/zfs/events","assignees_url":"https://api.github.com/repos/tcaputi/zfs/assignees{/user}","branches_url":"https://api.github.com/repos/tcaputi/zfs/branches{/branch}","tags_url":"https://api.github.com/repos/tcaputi/zfs/tags","blobs_url":"https://api.github.com/repos/tcaputi/zfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tcaputi/zfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tcaputi/zfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/tcaputi/zfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tcaputi/zfs/statuses/{sha}","languages_url":"https://api.github.com/repos/tcaputi/zfs/languages","stargazers_url":"https://api.github.com/repos/tcaputi/zfs/stargazers","contributors_url":"https://api.github.com/repos/tcaputi/zfs/contributors","subscribers_url":"https://api.github.com/repos/tcaputi/zfs/subscribers","subscription_url":"https://api.github.com/repos/tcaputi/zfs/subscription","commits_url":"https://api.github.com/repos/tcaputi/zfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/tcaputi/zfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/tcaputi/zfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/tcaputi/zfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/tcaputi/zfs/contents/{+path}","compare_url":"https://api.github.com/repos/tcaputi/zfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tcaputi/zfs/merges","archive_url":"https://api.github.com/repos/tcaputi/zfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tcaputi/zfs/downloads","issues_url":"https://api.github.com/repos/tcaputi/zfs/issues{/number}","pulls_url":"https://api.github.com/repos/tcaputi/zfs/pulls{/number}","milestones_url":"https://api.github.com/repos/tcaputi/zfs/milestones{/number}","notifications_url":"https://api.github.com/repos/tcaputi/zfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tcaputi/zfs/labels{/name}","releases_url":"https://api.github.com/repos/tcaputi/zfs/releases{/id}","deployments_url":"https://api.github.com/repos/tcaputi/zfs/deployments","created_at":"2015-12-09T20:08:08Z","updated_at":"2017-01-19T02:43:47Z","pushed_at":"2017-02-11T10:25:08Z","git_url":"git://github.com/tcaputi/zfs.git","ssh_url":"git@github.com:tcaputi/zfs.git","clone_url":"https://github.com/tcaputi/zfs.git","svn_url":"https://github.com/tcaputi/zfs","homepage":"http://zfsonlinux.org/","size":37632,"stargazers_count":5,"watchers_count":5,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":0,"forks":2,"open_issues":0,"watchers":5,"default_branch":"master"}},"base":{"label":"zfsonlinux:master","ref":"master","sha":"b291029e8661dfc2f03118921e854eec4e5bbb75","user":{"login":"zfsonlinux","id":575083,"avatar_url":"https://avatars.githubusercontent.com/u/575083?v=3","gravatar_id":"","url":"https://api.github.com/users/zfsonlinux","html_url":"https://github.com/zfsonlinux","followers_url":"https://api.github.com/users/zfsonlinux/followers","following_url":"https://api.github.com/users/zfsonlinux/following{/other_user}","gists_url":"https://api.github.com/users/zfsonlinux/gists{/gist_id}","starred_url":"https://api.github.com/users/zfsonlinux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zfsonlinux/subscriptions","organizations_url":"https://api.github.com/users/zfsonlinux/orgs","repos_url":"https://api.github.com/users/zfsonlinux/repos","events_url":"https://api.github.com/users/zfsonlinux/events{/privacy}","received_events_url":"https://api.github.com/users/zfsonlinux/received_events","type":"Organization","site_admin":false},"repo":{"id":437011,"name":"zfs","full_name":"zfsonlinux/zfs","owner":{"login":"zfsonlinux","id":575083,"avatar_url":"https://avatars.githubusercontent.com/u/575083?v=3","gravatar_id":"","url":"https://api.github.com/users/zfsonlinux","html_url":"https://github.com/zfsonlinux","followers_url":"https://api.github.com/users/zfsonlinux/followers","following_url":"https://api.github.com/users/zfsonlinux/following{/other_user}","gists_url":"https://api.github.com/users/zfsonlinux/gists{/gist_id}","starred_url":"https://api.github.com/users/zfsonlinux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zfsonlinux/subscriptions","organizations_url":"https://api.github.com/users/zfsonlinux/orgs","repos_url":"https://api.github.com/users/zfsonlinux/repos","events_url":"https://api.github.com/users/zfsonlinux/events{/privacy}","received_events_url":"https://api.github.com/users/zfsonlinux/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/zfsonlinux/zfs","description":"ZFS on Linux - the official OpenZFS implementation for Linux.","fork":false,"url":"https://api.github.com/repos/zfsonlinux/zfs","forks_url":"https://api.github.com/repos/zfsonlinux/zfs/forks","keys_url":"https://api.github.com/repos/zfsonlinux/zfs/keys{/key_id}","collaborators_url":"https://api.github.com/repos/zfsonlinux/zfs/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/zfsonlinux/zfs/teams","hooks_url":"https://api.github.com/repos/zfsonlinux/zfs/hooks","issue_events_url":"https://api.github.com/repos/zfsonlinux/zfs/issues/events{/number}","events_url":"https://api.github.com/repos/zfsonlinux/zfs/events","assignees_url":"https://api.github.com/repos/zfsonlinux/zfs/assignees{/user}","branches_url":"https://api.github.com/repos/zfsonlinux/zfs/branches{/branch}","tags_url":"https://api.github.com/repos/zfsonlinux/zfs/tags","blobs_url":"https://api.github.com/repos/zfsonlinux/zfs/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/zfsonlinux/zfs/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/zfsonlinux/zfs/git/refs{/sha}","trees_url":"https://api.github.com/repos/zfsonlinux/zfs/git/trees{/sha}","statuses_url":"https://api.github.com/repos/zfsonlinux/zfs/statuses/{sha}","languages_url":"https://api.github.com/repos/zfsonlinux/zfs/languages","stargazers_url":"https://api.github.com/repos/zfsonlinux/zfs/stargazers","contributors_url":"https://api.github.com/repos/zfsonlinux/zfs/contributors","subscribers_url":"https://api.github.com/repos/zfsonlinux/zfs/subscribers","subscription_url":"https://api.github.com/repos/zfsonlinux/zfs/subscription","commits_url":"https://api.github.com/repos/zfsonlinux/zfs/commits{/sha}","git_commits_url":"https://api.github.com/repos/zfsonlinux/zfs/git/commits{/sha}","comments_url":"https://api.github.com/repos/zfsonlinux/zfs/comments{/number}","issue_comment_url":"https://api.github.com/repos/zfsonlinux/zfs/issues/comments{/number}","contents_url":"https://api.github.com/repos/zfsonlinux/zfs/contents/{+path}","compare_url":"https://api.github.com/repos/zfsonlinux/zfs/compare/{base}...{head}","merges_url":"https://api.github.com/repos/zfsonlinux/zfs/merges","archive_url":"https://api.github.com/repos/zfsonlinux/zfs/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/zfsonlinux/zfs/downloads","issues_url":"https://api.github.com/repos/zfsonlinux/zfs/issues{/number}","pulls_url":"https://api.github.com/repos/zfsonlinux/zfs/pulls{/number}","milestones_url":"https://api.github.com/repos/zfsonlinux/zfs/milestones{/number}","notifications_url":"https://api.github.com/repos/zfsonlinux/zfs/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/zfsonlinux/zfs/labels{/name}","releases_url":"https://api.github.com/repos/zfsonlinux/zfs/releases{/id}","deployments_url":"https://api.github.com/repos/zfsonlinux/zfs/deployments","created_at":"2009-12-14T20:20:34Z","updated_at":"2017-02-11T23:23:28Z","pushed_at":"2017-02-11T20:57:57Z","git_url":"git://github.com/zfsonlinux/zfs.git","ssh_url":"git@github.com:zfsonlinux/zfs.git","clone_url":"https://github.com/zfsonlinux/zfs.git","svn_url":"https://github.com/zfsonlinux/zfs","homepage":"http://zfsonlinux.org/","size":36829,"stargazers_count":2234,"watchers_count":2234,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":553,"mirror_url":null,"open_issues_count":874,"forks":553,"open_issues":874,"watchers":2234,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769"},"html":{"href":"https://github.com/zfsonlinux/zfs/pull/5769"},"issue":{"href":"https://api.github.com/repos/zfsonlinux/zfs/issues/5769"},"comments":{"href":"https://api.github.com/repos/zfsonlinux/zfs/issues/5769/comments"},"review_comments":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769/comments"},"review_comment":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/zfsonlinux/zfs/pulls/5769/commits"},"statuses":{"href":"https://api.github.com/repos/zfsonlinux/zfs/statuses/8594e088ae071ced1353f7ace82eff8d70a1938a"}}}}
{"actor":{"display_login":"tcaputi"}}