Commit b7cc142d authored by Ilya Dryomov's avatar Ilya Dryomov
Browse files

libceph: add support for CEPH_CRYPTO_AES256KRB5



This is based on AES256-CTS-HMAC384-192 crypto algorithm per RFC 8009
(i.e. Kerberos 5, hence the name) with custom-defined key usage numbers.
The implementation allows a given key to have/be linked to between one
and three usage numbers.

The existing CEPH_CRYPTO_AES remains in place and unchanged.  The
usage_slot parameter that needed to be added to ceph_crypt() and its
wrappers is simply ignored there.

Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent 6cec0b61
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ struct ceph_dir_layout {
/* crypto algorithms */
#define CEPH_CRYPTO_NONE       0x0
#define CEPH_CRYPTO_AES        0x1
#define CEPH_CRYPTO_AES256KRB5 0x2 /* AES256-CTS-HMAC384-192 */

#define CEPH_AES_IV "cephsageyudagreg"

+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ config CEPH_LIB
	select CRYPTO_AES
	select CRYPTO_CBC
	select CRYPTO_GCM
	select CRYPTO_KRB5
	select CRYPTO_LIB_SHA256
	select CRYPTO
	select KEYS
+61 −27
Original line number Diff line number Diff line
@@ -17,6 +17,22 @@
#include "auth_x.h"
#include "auth_x_protocol.h"

static const u32 ticket_key_usages[] = {
	CEPHX_KEY_USAGE_TICKET_SESSION_KEY,
	CEPHX_KEY_USAGE_TICKET_BLOB,
	CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET
};

static const u32 authorizer_key_usages[] = {
	CEPHX_KEY_USAGE_AUTHORIZE,
	CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE,
	CEPHX_KEY_USAGE_AUTHORIZE_REPLY
};

static const u32 client_key_usages[] = {
	CEPHX_KEY_USAGE_TICKET_SESSION_KEY
};

static void ceph_x_validate_tickets(struct ceph_auth_client *ac, int *pneed);

static int ceph_x_is_authenticated(struct ceph_auth_client *ac)
@@ -57,6 +73,7 @@ static int ceph_x_encrypt_offset(const struct ceph_crypto_key *key)

/*
 * AES: ciphertext_len | hdr | data... | padding
 * AES256KRB5: ciphertext_len | confounder | hdr | data... | hmac
 */
static int ceph_x_encrypt_buflen(const struct ceph_crypto_key *key,
				 int data_len)
@@ -65,19 +82,19 @@ static int ceph_x_encrypt_buflen(const struct ceph_crypto_key *key,
	return sizeof(u32) + ceph_crypt_buflen(key, encrypt_len);
}

static int ceph_x_encrypt(struct ceph_crypto_key *secret, void *buf,
			  int buf_len, int plaintext_len)
static int ceph_x_encrypt(const struct ceph_crypto_key *key, int usage_slot,
			  void *buf, int buf_len, int plaintext_len)
{
	struct ceph_x_encrypt_header *hdr;
	int ciphertext_len;
	int ret;

	hdr = buf + sizeof(u32) + ceph_crypt_data_offset(secret);
	hdr = buf + sizeof(u32) + ceph_crypt_data_offset(key);
	hdr->struct_v = 1;
	hdr->magic = cpu_to_le64(CEPHX_ENC_MAGIC);

	ret = ceph_crypt(secret, true, buf + sizeof(u32), buf_len - sizeof(u32),
			 plaintext_len + sizeof(struct ceph_x_encrypt_header),
	ret = ceph_crypt(key, usage_slot, true, buf + sizeof(u32),
			 buf_len - sizeof(u32), plaintext_len + sizeof(*hdr),
			 &ciphertext_len);
	if (ret)
		return ret;
@@ -86,19 +103,19 @@ static int ceph_x_encrypt(struct ceph_crypto_key *secret, void *buf,
	return sizeof(u32) + ciphertext_len;
}

static int __ceph_x_decrypt(struct ceph_crypto_key *secret, void *p,
			    int ciphertext_len)
static int __ceph_x_decrypt(const struct ceph_crypto_key *key, int usage_slot,
			    void *p, int ciphertext_len)
{
	struct ceph_x_encrypt_header *hdr;
	int plaintext_len;
	int ret;

	ret = ceph_crypt(secret, false, p, ciphertext_len, ciphertext_len,
			 &plaintext_len);
	ret = ceph_crypt(key, usage_slot, false, p, ciphertext_len,
			 ciphertext_len, &plaintext_len);
	if (ret)
		return ret;

	hdr = p + ceph_crypt_data_offset(secret);
	hdr = p + ceph_crypt_data_offset(key);
	if (le64_to_cpu(hdr->magic) != CEPHX_ENC_MAGIC) {
		pr_err("%s bad magic\n", __func__);
		return -EINVAL;
@@ -107,7 +124,8 @@ static int __ceph_x_decrypt(struct ceph_crypto_key *secret, void *p,
	return plaintext_len - sizeof(*hdr);
}

static int ceph_x_decrypt(struct ceph_crypto_key *secret, void **p, void *end)
static int ceph_x_decrypt(const struct ceph_crypto_key *key, int usage_slot,
			  void **p, void *end)
{
	int ciphertext_len;
	int ret;
@@ -115,7 +133,7 @@ static int ceph_x_decrypt(struct ceph_crypto_key *secret, void **p, void *end)
	ceph_decode_32_safe(p, end, ciphertext_len, e_inval);
	ceph_decode_need(p, end, ciphertext_len, e_inval);

	ret = __ceph_x_decrypt(secret, *p, ciphertext_len);
	ret = __ceph_x_decrypt(key, usage_slot, *p, ciphertext_len);
	if (ret < 0)
		return ret;

@@ -207,7 +225,9 @@ static int process_one_ticket(struct ceph_auth_client *ac,

	/* blob for me */
	dp = *p + ceph_x_encrypt_offset(secret);
	ret = ceph_x_decrypt(secret, p, end);
	ret = ceph_x_decrypt(secret,
			     0 /* CEPHX_KEY_USAGE_TICKET_SESSION_KEY */,
			     p, end);
	if (ret < 0)
		goto out;
	dout(" decrypted %d bytes\n", ret);
@@ -221,7 +241,8 @@ static int process_one_ticket(struct ceph_auth_client *ac,
	if (ret)
		goto out;

	ret = ceph_crypto_key_prepare(&new_session_key);
	ret = ceph_crypto_key_prepare(&new_session_key, ticket_key_usages,
				      ARRAY_SIZE(ticket_key_usages));
	if (ret)
		goto out;

@@ -238,7 +259,9 @@ static int process_one_ticket(struct ceph_auth_client *ac,
	if (is_enc) {
		/* encrypted */
		tp = *p + ceph_x_encrypt_offset(&th->session_key);
		ret = ceph_x_decrypt(&th->session_key, p, end);
		ret = ceph_x_decrypt(&th->session_key,
				     1 /* CEPHX_KEY_USAGE_TICKET_BLOB */,
				     p, end);
		if (ret < 0)
			goto out;
		dout(" encrypted ticket, decrypted %d bytes\n", ret);
@@ -341,7 +364,9 @@ static int encrypt_authorizer(struct ceph_x_authorizer *au,
		msg_b->server_challenge_plus_one = 0;
	}

	ret = ceph_x_encrypt(&au->session_key, p, end - p, sizeof(*msg_b));
	ret = ceph_x_encrypt(&au->session_key,
			     0 /* CEPHX_KEY_USAGE_AUTHORIZE */,
			     p, end - p, sizeof(*msg_b));
	if (ret < 0)
		return ret;

@@ -384,7 +409,8 @@ static int ceph_x_build_authorizer(struct ceph_auth_client *ac,
	if (ret)
		goto out_au;

	ret = ceph_crypto_key_prepare(&au->session_key);
	ret = ceph_crypto_key_prepare(&au->session_key, authorizer_key_usages,
				      ARRAY_SIZE(authorizer_key_usages));
	if (ret)
		goto out_au;

@@ -542,7 +568,8 @@ static int ceph_x_build_request(struct ceph_auth_client *ac,
		get_random_bytes(&auth->client_challenge, sizeof(u64));
		blob->client_challenge = auth->client_challenge;
		blob->server_challenge = cpu_to_le64(xi->server_challenge);
		ret = ceph_x_encrypt(&xi->secret, enc_buf, CEPHX_AU_ENC_BUF_LEN,
		ret = ceph_x_encrypt(&xi->secret, 0 /* dummy */,
				     enc_buf, CEPHX_AU_ENC_BUF_LEN,
				     sizeof(*blob));
		if (ret < 0)
			return ret;
@@ -656,7 +683,9 @@ static int handle_auth_session_key(struct ceph_auth_client *ac, u64 global_id,
	dout("%s connection secret blob len %d\n", __func__, len);
	if (len > 0) {
		dp = *p + ceph_x_encrypt_offset(&th->session_key);
		ret = ceph_x_decrypt(&th->session_key, p, *p + len);
		ret = ceph_x_decrypt(&th->session_key,
				     2 /* CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET */,
				     p, *p + len);
		if (ret < 0)
			return ret;

@@ -820,7 +849,9 @@ static int decrypt_authorizer_challenge(struct ceph_crypto_key *secret,
	int ret;

	/* no leading len */
	ret = __ceph_x_decrypt(secret, challenge, challenge_len);
	ret = __ceph_x_decrypt(secret,
			       1 /* CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE */,
			       challenge, challenge_len);
	if (ret < 0)
		return ret;

@@ -873,7 +904,8 @@ static int decrypt_authorizer_reply(struct ceph_crypto_key *secret,
	int ret;

	dp = *p + ceph_x_encrypt_offset(secret);
	ret = ceph_x_decrypt(secret, p, end);
	ret = ceph_x_decrypt(secret, 2 /* CEPHX_KEY_USAGE_AUTHORIZE_REPLY */,
			     p, end);
	if (ret < 0)
		return ret;

@@ -1004,8 +1036,9 @@ static int calc_signature(struct ceph_x_authorizer *au, struct ceph_msg *msg,
		sigblock->middle_crc = msg->footer.middle_crc;
		sigblock->data_crc =  msg->footer.data_crc;

		ret = ceph_x_encrypt(&au->session_key, enc_buf,
				     CEPHX_AU_ENC_BUF_LEN, sizeof(*sigblock));
		ret = ceph_x_encrypt(&au->session_key, 0 /* dummy */,
				     enc_buf, CEPHX_AU_ENC_BUF_LEN,
				     sizeof(*sigblock));
		if (ret < 0)
			return ret;

@@ -1036,9 +1069,9 @@ static int calc_signature(struct ceph_x_authorizer *au, struct ceph_msg *msg,
		sigblock->seq_lower_word = *(__le32 *)&msg->hdr.seq;

		/* no leading len, no ceph_x_encrypt_header */
		ret = ceph_crypt(&au->session_key, true, enc_buf,
				 CEPHX_AU_ENC_BUF_LEN, sizeof(*sigblock),
				 &ciphertext_len);
		ret = ceph_crypt(&au->session_key, 0 /* dummy */,
				 true, enc_buf, CEPHX_AU_ENC_BUF_LEN,
				 sizeof(*sigblock), &ciphertext_len);
		if (ret)
			return ret;

@@ -1130,7 +1163,8 @@ int ceph_x_init(struct ceph_auth_client *ac)
		goto err_xi;
	}

	ret = ceph_crypto_key_prepare(&xi->secret);
	ret = ceph_crypto_key_prepare(&xi->secret, client_key_usages,
				      ARRAY_SIZE(client_key_usages));
	if (ret) {
		pr_err("cannot prepare key: %d\n", ret);
		goto err_secret;
+38 −0
Original line number Diff line number Diff line
@@ -6,6 +6,44 @@
#define CEPHX_GET_PRINCIPAL_SESSION_KEY 0x0200
#define CEPHX_GET_ROTATING_KEY          0x0400

/* Client <-> AuthMonitor */
/*
 * The AUTH session's connection secret: encrypted with the AUTH
 * ticket session key
 */
#define CEPHX_KEY_USAGE_AUTH_CONNECTION_SECRET	0x03
/*
 * The ticket's blob for the client ("blob for me", contains the
 * session key): encrypted with the client's secret key in case of
 * the AUTH ticket and the AUTH ticket session key in case of other
 * service tickets
 */
#define CEPHX_KEY_USAGE_TICKET_SESSION_KEY	0x04
/*
 * The ticket's blob for the service (ceph_x_ticket_blob): possibly
 * encrypted with the old AUTH ticket session key in case of the AUTH
 * ticket and not encrypted in case of other service tickets
 */
#define CEPHX_KEY_USAGE_TICKET_BLOB		0x05

/* Client <-> Service */
/*
 * The client's authorization request (ceph_x_authorize_b):
 * encrypted with the service ticket session key
 */
#define CEPHX_KEY_USAGE_AUTHORIZE		0x10
/*
 * The service's challenge (ceph_x_authorize_challenge):
 * encrypted with the service ticket session key
 */
#define CEPHX_KEY_USAGE_AUTHORIZE_CHALLENGE	0x11
/*
 * The service's final reply (ceph_x_authorize_reply + the service
 * session's connection secret): encrypted with the service ticket
 * session key
 */
#define CEPHX_KEY_USAGE_AUTHORIZE_REPLY		0x12

/* common bits */
struct ceph_x_ticket_blob {
	__u8 struct_v;
+142 −15
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
#include <linux/sched.h>
#include <linux/slab.h>
#include <crypto/aes.h>
#include <crypto/krb5.h>
#include <crypto/skcipher.h>
#include <linux/key-type.h>
#include <linux/sched/mm.h>
@@ -22,28 +23,68 @@ static int set_aes_tfm(struct ceph_crypto_key *key)
	int ret;

	noio_flag = memalloc_noio_save();
	key->tfm = crypto_alloc_sync_skcipher("cbc(aes)", 0, 0);
	key->aes_tfm = crypto_alloc_sync_skcipher("cbc(aes)", 0, 0);
	memalloc_noio_restore(noio_flag);
	if (IS_ERR(key->tfm)) {
		ret = PTR_ERR(key->tfm);
		key->tfm = NULL;
	if (IS_ERR(key->aes_tfm)) {
		ret = PTR_ERR(key->aes_tfm);
		key->aes_tfm = NULL;
		return ret;
	}

	ret = crypto_sync_skcipher_setkey(key->tfm, key->key, key->len);
	ret = crypto_sync_skcipher_setkey(key->aes_tfm, key->key, key->len);
	if (ret)
		return ret;

	return 0;
}

int ceph_crypto_key_prepare(struct ceph_crypto_key *key)
static int set_krb5_tfms(struct ceph_crypto_key *key, const u32 *key_usages,
			 int key_usage_cnt)
{
	struct krb5_buffer TK = { .len = key->len, .data = key->key };
	unsigned int noio_flag;
	int ret = 0;
	int i;

	if (WARN_ON_ONCE(key_usage_cnt > ARRAY_SIZE(key->krb5_tfms)))
		return -EINVAL;

	key->krb5_type = crypto_krb5_find_enctype(
			     KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192);
	if (!key->krb5_type)
		return -ENOPKG;

	/*
	 * Despite crypto_krb5_prepare_encryption() taking a gfp mask,
	 * crypto_alloc_aead() inside of it allocates with GFP_KERNEL.
	 */
	noio_flag = memalloc_noio_save();
	for (i = 0; i < key_usage_cnt; i++) {
		key->krb5_tfms[i] = crypto_krb5_prepare_encryption(
					key->krb5_type, &TK, key_usages[i],
					GFP_NOIO);
		if (IS_ERR(key->krb5_tfms[i])) {
			ret = PTR_ERR(key->krb5_tfms[i]);
			key->krb5_tfms[i] = NULL;
			goto out_flag;
		}
	}

out_flag:
	memalloc_noio_restore(noio_flag);
	return ret;
}

int ceph_crypto_key_prepare(struct ceph_crypto_key *key,
			    const u32 *key_usages, int key_usage_cnt)
{
	switch (key->type) {
	case CEPH_CRYPTO_NONE:
		return 0; /* nothing to do */
	case CEPH_CRYPTO_AES:
		return set_aes_tfm(key);
	case CEPH_CRYPTO_AES256KRB5:
		return set_krb5_tfms(key, key_usages, key_usage_cnt);
	default:
		return -ENOTSUPP;
	}
@@ -123,12 +164,25 @@ int ceph_crypto_key_unarmor(struct ceph_crypto_key *key, const char *inkey)

void ceph_crypto_key_destroy(struct ceph_crypto_key *key)
{
	if (key) {
	int i;

	if (!key)
		return;

	kfree_sensitive(key->key);
	key->key = NULL;
		if (key->tfm) {
			crypto_free_sync_skcipher(key->tfm);
			key->tfm = NULL;

	if (key->type == CEPH_CRYPTO_AES) {
		if (key->aes_tfm) {
			crypto_free_sync_skcipher(key->aes_tfm);
			key->aes_tfm = NULL;
		}
	} else if (key->type == CEPH_CRYPTO_AES256KRB5) {
		for (i = 0; i < ARRAY_SIZE(key->krb5_tfms); i++) {
			if (key->krb5_tfms[i]) {
				crypto_free_aead(key->krb5_tfms[i]);
				key->krb5_tfms[i] = NULL;
			}
		}
	}
}
@@ -208,7 +262,7 @@ static void teardown_sgtable(struct sg_table *sgt)
static int ceph_aes_crypt(const struct ceph_crypto_key *key, bool encrypt,
			  void *buf, int buf_len, int in_len, int *pout_len)
{
	SYNC_SKCIPHER_REQUEST_ON_STACK(req, key->tfm);
	SYNC_SKCIPHER_REQUEST_ON_STACK(req, key->aes_tfm);
	struct sg_table sgt;
	struct scatterlist prealloc_sg;
	char iv[AES_BLOCK_SIZE] __aligned(8);
@@ -224,7 +278,7 @@ static int ceph_aes_crypt(const struct ceph_crypto_key *key, bool encrypt,
		return ret;

	memcpy(iv, aes_iv, AES_BLOCK_SIZE);
	skcipher_request_set_sync_tfm(req, key->tfm);
	skcipher_request_set_sync_tfm(req, key->aes_tfm);
	skcipher_request_set_callback(req, 0, NULL, NULL);
	skcipher_request_set_crypt(req, sgt.sgl, sgt.sgl, crypt_len, iv);

@@ -269,7 +323,68 @@ static int ceph_aes_crypt(const struct ceph_crypto_key *key, bool encrypt,
	return ret;
}

int ceph_crypt(const struct ceph_crypto_key *key, bool encrypt,
static int ceph_krb5_encrypt(const struct ceph_crypto_key *key, int usage_slot,
			     void *buf, int buf_len, int in_len, int *pout_len)
{
	struct sg_table sgt;
	struct scatterlist prealloc_sg;
	int ret;

	if (WARN_ON_ONCE(usage_slot >= ARRAY_SIZE(key->krb5_tfms)))
		return -EINVAL;

	ret = setup_sgtable(&sgt, &prealloc_sg, buf, buf_len);
	if (ret)
		return ret;

	ret = crypto_krb5_encrypt(key->krb5_type, key->krb5_tfms[usage_slot],
				  sgt.sgl, sgt.nents, buf_len, AES_BLOCK_SIZE,
				  in_len, false);
	if (ret < 0) {
		pr_err("%s encrypt failed: %d\n", __func__, ret);
		goto out_sgt;
	}

	*pout_len = ret;
	ret = 0;

out_sgt:
	teardown_sgtable(&sgt);
	return ret;
}

static int ceph_krb5_decrypt(const struct ceph_crypto_key *key, int usage_slot,
			     void *buf, int buf_len, int in_len, int *pout_len)
{
	struct sg_table sgt;
	struct scatterlist prealloc_sg;
	size_t data_off = 0;
	size_t data_len = in_len;
	int ret;

	if (WARN_ON_ONCE(usage_slot >= ARRAY_SIZE(key->krb5_tfms)))
		return -EINVAL;

	ret = setup_sgtable(&sgt, &prealloc_sg, buf, in_len);
	if (ret)
		return ret;

	ret = crypto_krb5_decrypt(key->krb5_type, key->krb5_tfms[usage_slot],
				  sgt.sgl, sgt.nents, &data_off, &data_len);
	if (ret) {
		pr_err("%s decrypt failed: %d\n", __func__, ret);
		goto out_sgt;
	}

	WARN_ON(data_off != AES_BLOCK_SIZE);
	*pout_len = data_len;

out_sgt:
	teardown_sgtable(&sgt);
	return ret;
}

int ceph_crypt(const struct ceph_crypto_key *key, int usage_slot, bool encrypt,
	       void *buf, int buf_len, int in_len, int *pout_len)
{
	switch (key->type) {
@@ -279,6 +394,12 @@ int ceph_crypt(const struct ceph_crypto_key *key, bool encrypt,
	case CEPH_CRYPTO_AES:
		return ceph_aes_crypt(key, encrypt, buf, buf_len, in_len,
				      pout_len);
	case CEPH_CRYPTO_AES256KRB5:
		return encrypt ?
		    ceph_krb5_encrypt(key, usage_slot, buf, buf_len, in_len,
				      pout_len) :
		    ceph_krb5_decrypt(key, usage_slot, buf, buf_len, in_len,
				      pout_len);
	default:
		return -ENOTSUPP;
	}
@@ -290,6 +411,9 @@ int ceph_crypt_data_offset(const struct ceph_crypto_key *key)
	case CEPH_CRYPTO_NONE:
	case CEPH_CRYPTO_AES:
		return 0;
	case CEPH_CRYPTO_AES256KRB5:
		/* confounder */
		return AES_BLOCK_SIZE;
	default:
		BUG();
	}
@@ -304,6 +428,9 @@ int ceph_crypt_buflen(const struct ceph_crypto_key *key, int data_len)
		/* PKCS#7 padding at the end */
		return data_len + AES_BLOCK_SIZE -
		       (data_len & (AES_BLOCK_SIZE - 1));
	case CEPH_CRYPTO_AES256KRB5:
		/* confounder at the beginning and 192-bit HMAC at the end */
		return AES_BLOCK_SIZE + data_len + 24;
	default:
		BUG();
	}
Loading