summaryrefslogtreecommitdiff
path: root/libbcache/checksum.c
diff options
context:
space:
mode:
Diffstat (limited to 'libbcache/checksum.c')
-rw-r--r--libbcache/checksum.c450
1 files changed, 435 insertions, 15 deletions
diff --git a/libbcache/checksum.c b/libbcache/checksum.c
index beae0b26..eb41f2ea 100644
--- a/libbcache/checksum.c
+++ b/libbcache/checksum.c
@@ -1,11 +1,19 @@
#include "bcache.h"
#include "checksum.h"
+#include "super.h"
+#include "super-io.h"
#include <linux/crc32c.h>
+#include <linux/crypto.h>
+#include <linux/key.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+#include <crypto/algapi.h>
#include <crypto/chacha20.h>
#include <crypto/hash.h>
#include <crypto/poly1305.h>
+#include <keys/user-type.h>
/*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group (Any
@@ -129,7 +137,35 @@ u64 bch_crc64_update(u64 crc, const void *_data, size_t len)
return crc;
}
-u64 bch_checksum_update(unsigned type, u64 crc, const void *data, size_t len)
+static u64 bch_checksum_init(unsigned type)
+{
+ switch (type) {
+ case BCH_CSUM_NONE:
+ return 0;
+ case BCH_CSUM_CRC32C:
+ return U32_MAX;
+ case BCH_CSUM_CRC64:
+ return U64_MAX;
+ default:
+ BUG();
+ }
+}
+
+static u64 bch_checksum_final(unsigned type, u64 crc)
+{
+ switch (type) {
+ case BCH_CSUM_NONE:
+ return 0;
+ case BCH_CSUM_CRC32C:
+ return crc ^ U32_MAX;
+ case BCH_CSUM_CRC64:
+ return crc ^ U64_MAX;
+ default:
+ BUG();
+ }
+}
+
+static u64 bch_checksum_update(unsigned type, u64 crc, const void *data, size_t len)
{
switch (type) {
case BCH_CSUM_NONE:
@@ -143,32 +179,416 @@ u64 bch_checksum_update(unsigned type, u64 crc, const void *data, size_t len)
}
}
-u64 bch_checksum(unsigned type, const void *data, size_t len)
+static inline void do_encrypt_sg(struct crypto_blkcipher *tfm,
+ struct nonce nonce,
+ struct scatterlist *sg, size_t len)
+{
+ struct blkcipher_desc desc = { .tfm = tfm, .info = nonce.d };
+ int ret;
+
+ ret = crypto_blkcipher_encrypt_iv(&desc, sg, sg, len);
+ BUG_ON(ret);
+}
+
+static inline void do_encrypt(struct crypto_blkcipher *tfm,
+ struct nonce nonce,
+ void *buf, size_t len)
+{
+ struct scatterlist sg;
+
+ sg_init_one(&sg, buf, len);
+ do_encrypt_sg(tfm, nonce, &sg, len);
+}
+
+int bch_chacha_encrypt_key(struct bch_key *key, struct nonce nonce,
+ void *buf, size_t len)
+{
+ struct crypto_blkcipher *chacha20 =
+ crypto_alloc_blkcipher("chacha20", 0, CRYPTO_ALG_ASYNC);
+ int ret;
+
+ if (!chacha20)
+ return PTR_ERR(chacha20);
+
+ ret = crypto_blkcipher_setkey(chacha20, (void *) key, sizeof(*key));
+ if (ret)
+ goto err;
+
+ do_encrypt(chacha20, nonce, buf, len);
+err:
+ crypto_free_blkcipher(chacha20);
+ return ret;
+}
+
+static void gen_poly_key(struct cache_set *c, struct shash_desc *desc,
+ struct nonce nonce)
+{
+ u8 key[POLY1305_KEY_SIZE];
+
+ nonce.d[3] ^= BCH_NONCE_POLY;
+
+ memset(key, 0, sizeof(key));
+ do_encrypt(c->chacha20, nonce, key, sizeof(key));
+
+ desc->tfm = c->poly1305;
+ desc->flags = 0;
+ crypto_shash_init(desc);
+ crypto_shash_update(desc, key, sizeof(key));
+}
+
+struct bch_csum bch_checksum(struct cache_set *c, unsigned type,
+ struct nonce nonce, const void *data, size_t len)
{
- u64 crc = 0xffffffffffffffffULL;
+ switch (type) {
+ case BCH_CSUM_NONE:
+ case BCH_CSUM_CRC32C:
+ case BCH_CSUM_CRC64: {
+ u64 crc = bch_checksum_init(type);
+
+ crc = bch_checksum_update(type, crc, data, len);
+ crc = bch_checksum_final(type, crc);
+
+ return (struct bch_csum) { .lo = crc };
+ }
+
+ case BCH_CSUM_CHACHA20_POLY1305_80:
+ case BCH_CSUM_CHACHA20_POLY1305_128: {
+ SHASH_DESC_ON_STACK(desc, c->poly1305);
+ u8 digest[POLY1305_DIGEST_SIZE];
+ struct bch_csum ret = { 0 };
+
+ gen_poly_key(c, desc, nonce);
+
+ crypto_shash_update(desc, data, len);
+ crypto_shash_final(desc, digest);
+
+ memcpy(&ret, digest, bch_crc_bytes[type]);
+ return ret;
+ }
+ default:
+ BUG();
+ }
+}
- crc = bch_checksum_update(type, crc, data, len);
+void bch_encrypt(struct cache_set *c, unsigned type,
+ struct nonce nonce, void *data, size_t len)
+{
+ if (!bch_csum_type_is_encryption(type))
+ return;
- return crc ^ 0xffffffffffffffffULL;
+ do_encrypt(c->chacha20, nonce, data, len);
}
-u32 bch_checksum_bio(struct bio *bio, unsigned type)
+struct bch_csum bch_checksum_bio(struct cache_set *c, unsigned type,
+ struct nonce nonce, struct bio *bio)
{
struct bio_vec bv;
struct bvec_iter iter;
- u32 csum = U32_MAX;
- if (type == BCH_CSUM_NONE)
- return 0;
+ switch (type) {
+ case BCH_CSUM_NONE:
+ return (struct bch_csum) { 0 };
+ case BCH_CSUM_CRC32C:
+ case BCH_CSUM_CRC64: {
+ u64 crc = bch_checksum_init(type);
+
+ bio_for_each_segment(bv, bio, iter) {
+ void *p = kmap_atomic(bv.bv_page) + bv.bv_offset;
+
+ crc = bch_checksum_update(type,
+ crc, p, bv.bv_len);
+ kunmap_atomic(p);
+ }
+
+ crc = bch_checksum_final(type, crc);
+ return (struct bch_csum) { .lo = crc };
+ }
+
+ case BCH_CSUM_CHACHA20_POLY1305_80:
+ case BCH_CSUM_CHACHA20_POLY1305_128: {
+ SHASH_DESC_ON_STACK(desc, c->poly1305);
+ u8 digest[POLY1305_DIGEST_SIZE];
+ struct bch_csum ret = { 0 };
+
+ gen_poly_key(c, desc, nonce);
+
+ bio_for_each_segment(bv, bio, iter) {
+ void *p = kmap_atomic(bv.bv_page) + bv.bv_offset;
+
+ crypto_shash_update(desc, p, bv.bv_len);
+ kunmap_atomic(p);
+ }
+
+ crypto_shash_final(desc, digest);
+
+ memcpy(&ret, digest, bch_crc_bytes[type]);
+ return ret;
+ }
+ default:
+ BUG();
+ }
+}
+
+void bch_encrypt_bio(struct cache_set *c, unsigned type,
+ struct nonce nonce, struct bio *bio)
+{
+ struct bio_vec bv;
+ struct bvec_iter iter;
+ struct scatterlist sgl[16], *sg = sgl;
+ size_t bytes = 0;
+
+ if (!bch_csum_type_is_encryption(type))
+ return;
+
+ sg_init_table(sgl, ARRAY_SIZE(sgl));
bio_for_each_segment(bv, bio, iter) {
- void *p = kmap_atomic(bv.bv_page);
+ if (sg == sgl + ARRAY_SIZE(sgl)) {
+ sg_mark_end(sg - 1);
+ do_encrypt_sg(c->chacha20, nonce, sgl, bytes);
+
+ le32_add_cpu(nonce.d, bytes / CHACHA20_BLOCK_SIZE);
+ bytes = 0;
+
+ sg_init_table(sgl, ARRAY_SIZE(sgl));
+ sg = sgl;
+ }
+
+ sg_set_page(sg++, bv.bv_page, bv.bv_len, bv.bv_offset);
+ bytes += bv.bv_len;
+
+ }
+
+ sg_mark_end(sg - 1);
+ do_encrypt_sg(c->chacha20, nonce, sgl, bytes);
+}
+
+#ifdef __KERNEL__
+int bch_request_key(struct bch_sb *sb, struct bch_key *key)
+{
+ char key_description[60];
+ struct key *keyring_key;
+ const struct user_key_payload *ukp;
+ int ret;
+
+ snprintf(key_description, sizeof(key_description),
+ "bcache:%pUb", &sb->user_uuid);
+
+ keyring_key = request_key(&key_type_logon, key_description, NULL);
+ if (IS_ERR(keyring_key))
+ return PTR_ERR(keyring_key);
+
+ down_read(&keyring_key->sem);
+ ukp = user_key_payload(keyring_key);
+ if (ukp->datalen == sizeof(*key)) {
+ memcpy(key, ukp->data, ukp->datalen);
+ ret = 0;
+ } else {
+ ret = -EINVAL;
+ }
+ up_read(&keyring_key->sem);
+ key_put(keyring_key);
+
+ return ret;
+}
+#else
+#include <keyutils.h>
+#include <uuid/uuid.h>
+
+int bch_request_key(struct bch_sb *sb, struct bch_key *key)
+{
+ key_serial_t key_id;
+ char key_description[60];
+ char uuid[40];
+
+ uuid_unparse_lower(sb->user_uuid.b, uuid);
+ sprintf(key_description, "bcache:%s", uuid);
+
+ key_id = request_key("user", key_description, NULL,
+ KEY_SPEC_USER_KEYRING);
+ if (key_id < 0)
+ return -errno;
+
+ if (keyctl_read(key_id, (void *) key, sizeof(*key)) != sizeof(*key))
+ return -1;
+
+ return 0;
+}
+#endif
- csum = bch_checksum_update(type, csum,
- p + bv.bv_offset,
- bv.bv_len);
- kunmap_atomic(p);
+static int bch_decrypt_sb_key(struct cache_set *c,
+ struct bch_sb_field_crypt *crypt,
+ struct bch_key *key)
+{
+ struct bch_encrypted_key sb_key = crypt->key;
+ struct bch_key user_key;
+ int ret = 0;
+
+ /* is key encrypted? */
+ if (!bch_key_is_encrypted(&sb_key))
+ goto out;
+
+ ret = bch_request_key(c->disk_sb, &user_key);
+ if (ret) {
+ bch_err(c, "error requesting encryption key");
+ goto err;
}
- return csum ^= U32_MAX;
+ /* decrypt real key: */
+ ret = bch_chacha_encrypt_key(&user_key, bch_sb_key_nonce(c),
+ &sb_key, sizeof(sb_key));
+ if (ret)
+ goto err;
+
+ if (bch_key_is_encrypted(&sb_key)) {
+ bch_err(c, "incorrect encryption key");
+ ret = -EINVAL;
+ goto err;
+ }
+out:
+ *key = sb_key.key;
+err:
+ memzero_explicit(&sb_key, sizeof(sb_key));
+ memzero_explicit(&user_key, sizeof(user_key));
+ return ret;
+}
+
+static int bch_alloc_ciphers(struct cache_set *c)
+{
+ if (!c->chacha20)
+ c->chacha20 = crypto_alloc_blkcipher("chacha20", 0,
+ CRYPTO_ALG_ASYNC);
+ if (IS_ERR(c->chacha20))
+ return PTR_ERR(c->chacha20);
+
+ if (!c->poly1305)
+ c->poly1305 = crypto_alloc_shash("poly1305", 0, 0);
+ if (IS_ERR(c->poly1305))
+ return PTR_ERR(c->poly1305);
+
+ return 0;
+}
+
+int bch_disable_encryption(struct cache_set *c)
+{
+ struct bch_sb_field_crypt *crypt;
+ struct bch_key key;
+ int ret = -EINVAL;
+
+ mutex_lock(&c->sb_lock);
+
+ crypt = bch_sb_get_crypt(c->disk_sb);
+ if (!crypt)
+ goto out;
+
+ /* is key encrypted? */
+ ret = 0;
+ if (bch_key_is_encrypted(&crypt->key))
+ goto out;
+
+ ret = bch_decrypt_sb_key(c, crypt, &key);
+ if (ret)
+ goto out;
+
+ crypt->key.magic = BCH_KEY_MAGIC;
+ crypt->key.key = key;
+
+ SET_BCH_SB_ENCRYPTION_TYPE(c->disk_sb, 0);
+ bch_write_super(c);
+out:
+ mutex_unlock(&c->sb_lock);
+
+ return ret;
+}
+
+int bch_enable_encryption(struct cache_set *c, bool keyed)
+{
+ struct bch_encrypted_key key;
+ struct bch_key user_key;
+ struct bch_sb_field_crypt *crypt;
+ int ret = -EINVAL;
+
+ mutex_lock(&c->sb_lock);
+
+ /* Do we already have an encryption key? */
+ if (bch_sb_get_crypt(c->disk_sb))
+ goto err;
+
+ ret = bch_alloc_ciphers(c);
+ if (ret)
+ goto err;
+
+ key.magic = BCH_KEY_MAGIC;
+ get_random_bytes(&key.key, sizeof(key.key));
+
+ if (keyed) {
+ ret = bch_request_key(c->disk_sb, &user_key);
+ if (ret) {
+ bch_err(c, "error requesting encryption key");
+ goto err;
+ }
+
+ ret = bch_chacha_encrypt_key(&user_key, bch_sb_key_nonce(c),
+ &key, sizeof(key));
+ if (ret)
+ goto err;
+ }
+
+ ret = crypto_blkcipher_setkey(c->chacha20,
+ (void *) &key.key, sizeof(key.key));
+ if (ret)
+ goto err;
+
+ crypt = container_of_or_null(bch_fs_sb_field_resize(c, NULL,
+ sizeof(*crypt) / sizeof(u64)),
+ struct bch_sb_field_crypt, field);
+ if (!crypt) {
+ ret = -ENOMEM; /* XXX this technically could be -ENOSPC */
+ goto err;
+ }
+
+ crypt->field.type = BCH_SB_FIELD_crypt;
+ crypt->key = key;
+
+ /* write superblock */
+ SET_BCH_SB_ENCRYPTION_TYPE(c->disk_sb, 1);
+ bch_write_super(c);
+err:
+ mutex_unlock(&c->sb_lock);
+ memzero_explicit(&user_key, sizeof(user_key));
+ memzero_explicit(&key, sizeof(key));
+ return ret;
+}
+
+void bch_cache_set_encryption_free(struct cache_set *c)
+{
+ if (!IS_ERR_OR_NULL(c->poly1305))
+ crypto_free_shash(c->poly1305);
+ if (!IS_ERR_OR_NULL(c->chacha20))
+ crypto_free_blkcipher(c->chacha20);
+}
+
+int bch_cache_set_encryption_init(struct cache_set *c)
+{
+ struct bch_sb_field_crypt *crypt;
+ struct bch_key key;
+ int ret;
+
+ crypt = bch_sb_get_crypt(c->disk_sb);
+ if (!crypt)
+ return 0;
+
+ ret = bch_alloc_ciphers(c);
+ if (ret)
+ return ret;
+
+ ret = bch_decrypt_sb_key(c, crypt, &key);
+ if (ret)
+ goto err;
+
+ ret = crypto_blkcipher_setkey(c->chacha20,
+ (void *) &key.key, sizeof(key.key));
+err:
+ memzero_explicit(&key, sizeof(key));
+ return ret;
}