Commit message: totemcrypto: fix hmac key initialization

Signed-off-by: Fabio M. Di Nitto <fdinitto@redhat.com>
Reviewed-by: Jan Friesse <jfriesse@redhat.com>
CVE: CVE-2013-0250
CWE: CWE-703
Vulnerability description: The init_nss_hash function in exec/totemcrypto.c in Corosync 2.0 before 2.3 does not properly initialize the HMAC key, which allows remote attackers to cause a denial of service (crash) via a crafted packet.

Project: corosync
File: totemcrypto.c

static int authenticate_and_decrypt_nss_2_0 (
	struct crypto_instance *instance,
	unsigned char *buf,
	int *buf_len)
{
	if (hash_to_nss[instance->crypto_hash_type]) {
		unsigned char	tmp_hash[hash_len[instance->crypto_hash_type]];
		unsigned char	*hash = buf;
		unsigned char	*data = hash + hash_len[instance->crypto_hash_type];
		int		datalen = *buf_len - hash_len[instance->crypto_hash_type];

		if (calculate_nss_hash(instance, data, datalen, tmp_hash) < 0) {
			return -1;
		}

		if (memcmp(tmp_hash, hash, hash_len[instance->crypto_hash_type]) != 0) {
			log_printf(instance->log_level_error, "Digest does not match");
			return -1;
		}

		memmove(buf, data, datalen);
		*buf_len = datalen;
	}

	if (decrypt_nss(instance, buf, buf_len) < 0) {
		return -1;
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int authenticate_nss_2_2 (
	struct crypto_instance *instance,
	unsigned char *buf,
	int *buf_len)
{
	if (hash_to_nss[instance->crypto_hash_type]) {
		unsigned char	tmp_hash[hash_len[instance->crypto_hash_type]];
		int             datalen = *buf_len - hash_len[instance->crypto_hash_type];

		if (calculate_nss_hash(instance, buf, datalen, tmp_hash) < 0) {
			return -1;
		}

		if (memcmp(tmp_hash, buf + datalen, hash_len[instance->crypto_hash_type]) != 0) {
			log_printf(instance->log_level_error, "Digest does not match");
			return -1;
		}
		*buf_len = datalen;
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int calculate_nss_hash(
	struct crypto_instance *instance,
	const unsigned char *buf,
	const size_t buf_len,
	unsigned char *hash)
{
	PK11Context*	hash_context = NULL;
	SECItem		hash_param;
	unsigned int	hash_tmp_outlen = 0;
	unsigned char	hash_block[hash_block_len[instance->crypto_hash_type]];
	int		err = -1;

	/* Now do the digest */
	hash_param.type = siBuffer;
	hash_param.data = 0;
	hash_param.len = 0;

	hash_context = PK11_CreateContextBySymKey(hash_to_nss[instance->crypto_hash_type],
						 CKA_SIGN,
						 instance->nss_sym_key_sign,
						 &hash_param);

	if (!hash_context) {
		log_printf(instance->log_level_security,
			   "PK11_CreateContext failed (hash) hash_type=%d (err %d)",
			   (int)hash_to_nss[instance->crypto_hash_type],
			   PR_GetError());
		goto out;
	}

	if (PK11_DigestBegin(hash_context) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_DigestBegin failed (hash) hash_type=%d (err %d)",
			   (int)hash_to_nss[instance->crypto_hash_type],
			   PR_GetError());
		goto out;
	}

	if (PK11_DigestOp(hash_context,
			  buf,
			  buf_len) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_DigestOp failed (hash) hash_type=%d (err %d)",
			   (int)hash_to_nss[instance->crypto_hash_type],
			   PR_GetError());
		goto out;
	}

	if (PK11_DigestFinal(hash_context,
			     hash_block,
			     &hash_tmp_outlen,
			     hash_block_len[instance->crypto_hash_type]) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_DigestFinale failed (hash) hash_type=%d (err %d)",
			   (int)hash_to_nss[instance->crypto_hash_type],
			   PR_GetError());
		goto out;
	}

	memcpy(hash, hash_block, hash_len[instance->crypto_hash_type]);
	err = 0;

out:
	if (hash_context) {
		PK11_DestroyContext(hash_context, PR_TRUE);
	}

	return err;
}

Project: corosync
File: totemcrypto.c

int crypto_authenticate_and_decrypt (struct crypto_instance *instance,
	unsigned char *buf,
	int *buf_len)
{
	struct crypto_config_header *cch = (struct crypto_config_header *)buf;

	switch (instance->crypto_compat_type) {
		case CRYPTO_COMPAT_2_0:

			/*
			 * decode crypto config of incoming packets
			 */

			if (cch->crypto_cipher_type != instance->crypto_cipher_type) {
				log_printf(instance->log_level_security,
					   "Incoming packet has different crypto type. Rejecting");
				return -1;
			}

			if (cch->crypto_hash_type != instance->crypto_hash_type) {
				log_printf(instance->log_level_security,
					   "Incoming packet has different hash type. Rejecting");
				return -1;
			}

			if ((cch->__pad0 != 0) || (cch->__pad1 != 0)) {
				log_printf(instance->log_level_security,
					   "Incoming packet appears to have features not supported by this version of corosync. Rejecting");
				return -1;
			}

			/*
			 * invalidate config header and kill it
			 */

			cch = NULL;
			*buf_len -= sizeof(struct crypto_config_header);
			memmove(buf, buf + sizeof(struct crypto_config_header), *buf_len);

			return authenticate_and_decrypt_nss_2_0(instance, buf, buf_len);
			break;
		case CRYPTO_COMPAT_2_2:
			if (cch->crypto_cipher_type != CRYPTO_CIPHER_TYPE_2_2) {
				log_printf(instance->log_level_security,
					   "Incoming packet has different crypto type. Rejecting");
				return -1;
			}

			if (cch->crypto_hash_type != CRYPTO_HASH_TYPE_2_2) {
				log_printf(instance->log_level_security,
					   "Incoming packet has different hash type. Rejecting");
				return -1;
			}

			/*
			 * authenticate packet first
			 */

			if (authenticate_nss_2_2(instance, buf, buf_len) != 0) {
				return -1;
			}

			/*
			 * now we can "trust" the padding bytes/future features
			 */

			if ((cch->__pad0 != 0) || (cch->__pad1 != 0)) {
				log_printf(instance->log_level_security,
					   "Incoming packet appears to have features not supported by this version of corosync. Rejecting");
				return -1;
			}

			/*
			 * decrypt
			 */

			if (decrypt_nss_2_2(instance, buf, buf_len) != 0) {
				return -1;
			}

			/*
			 * invalidate config header and kill it
			 */
			cch = NULL;
			memmove(buf, buf + sizeof(struct crypto_config_header), *buf_len);

			return 0;
			break;
		default:
			return -1;
			break;
	}
}

Project: corosync
File: totemcrypto.c

int crypto_encrypt_and_sign (
	struct crypto_instance *instance,
	const unsigned char *buf_in,
	const size_t buf_in_len,
	unsigned char *buf_out,
	size_t *buf_out_len)
{
	struct crypto_config_header *cch = (struct crypto_config_header *)buf_out;
	int err;

	switch (instance->crypto_compat_type) {
		case CRYPTO_COMPAT_2_0:
			cch->crypto_cipher_type = instance->crypto_cipher_type;
			cch->crypto_hash_type = instance->crypto_hash_type;
			cch->__pad0 = 0;
			cch->__pad1 = 0;

			buf_out += sizeof(struct crypto_config_header);

			err = encrypt_and_sign_nss_2_0(instance,
						       buf_in, buf_in_len,
						       buf_out, buf_out_len);

			*buf_out_len = *buf_out_len + sizeof(struct crypto_config_header);
			break;
		case CRYPTO_COMPAT_2_2:
			cch->crypto_cipher_type = CRYPTO_CIPHER_TYPE_2_2;
			cch->crypto_hash_type = CRYPTO_HASH_TYPE_2_2;
			cch->__pad0 = 0;
			cch->__pad1 = 0;

			err = encrypt_and_sign_nss_2_2(instance,
						   buf_in, buf_in_len,
						   buf_out, buf_out_len);
			break;
		default:
			err = -1;
			break;
	}

	return err;
}

Project: corosync
File: totemcrypto.c

size_t crypto_sec_header_size(
	const char *crypto_cipher_type,
	const char *crypto_hash_type)
{
	int crypto_cipher = string_to_crypto_cipher_type(crypto_cipher_type);
	int crypto_hash = string_to_crypto_hash_type(crypto_hash_type);
	size_t hdr_size = 0;

	hdr_size = sizeof(struct crypto_config_header);

	if (crypto_hash) {
		hdr_size += hash_len[crypto_hash];
	}

	if (crypto_cipher) {
		hdr_size += SALT_SIZE;
		hdr_size += cypher_block_len[crypto_cipher];
	}

	return hdr_size;
}

Project: corosync
File: totemcrypto.c

static int decrypt_nss (
	struct crypto_instance *instance,
	unsigned char *buf,
	int *buf_len)
{
	PK11Context*	decrypt_context = NULL;
	SECItem		decrypt_param;
	int		tmp1_outlen = 0;
	unsigned int	tmp2_outlen = 0;
	unsigned char	*salt = buf;
	unsigned char	*data = salt + SALT_SIZE;
	int		datalen = *buf_len - SALT_SIZE;
	unsigned char	outbuf[FRAME_SIZE_MAX];
	int		outbuf_len;
	int		err = -1;

	if (!cipher_to_nss[instance->crypto_cipher_type]) {
		return 0;
	}

	/* Create cipher context for decryption */
	decrypt_param.type = siBuffer;
	decrypt_param.data = salt;
	decrypt_param.len = SALT_SIZE;

	decrypt_context = PK11_CreateContextBySymKey(cipher_to_nss[instance->crypto_cipher_type],
						     CKA_DECRYPT,
						     instance->nss_sym_key, &decrypt_param);
	if (!decrypt_context) {
		log_printf(instance->log_level_security,
			   "PK11_CreateContext (decrypt) failed (err %d)",
			   PR_GetError());
		goto out;
	}

	if (PK11_CipherOp(decrypt_context, outbuf, &tmp1_outlen,
			  sizeof(outbuf), data, datalen) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_CipherOp (decrypt) failed (err %d)",
			   PR_GetError());
		goto out;
	}

	if (PK11_DigestFinal(decrypt_context, outbuf + tmp1_outlen, &tmp2_outlen,
			     sizeof(outbuf) - tmp1_outlen) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_DigestFinal (decrypt) failed (err %d)",
			   PR_GetError()); 
		goto out;
	}

	outbuf_len = tmp1_outlen + tmp2_outlen;

	memset(buf, 0, *buf_len);
	memcpy(buf, outbuf, outbuf_len);

	*buf_len = outbuf_len;

	err = 0;

out:
	if (decrypt_context) {
		PK11_DestroyContext(decrypt_context, PR_TRUE);
	}

	return err;
}

Project: corosync
File: totemcrypto.c

static int decrypt_nss_2_2 (
	struct crypto_instance *instance,
	unsigned char *buf,
	int *buf_len)
{
	*buf_len -= sizeof(struct crypto_config_header);

	if (decrypt_nss(instance, buf + sizeof(struct crypto_config_header), buf_len) < 0) {
		return -1;
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int encrypt_and_sign_nss_2_2 (
	struct crypto_instance *instance,
	const unsigned char *buf_in,
	const size_t buf_in_len,
	unsigned char *buf_out,
	size_t *buf_out_len)
{
	if (encrypt_nss(instance,
			buf_in, buf_in_len,
			buf_out + sizeof(struct crypto_config_header), buf_out_len) < 0) {
		return -1;
	}

	*buf_out_len += sizeof(struct crypto_config_header);

	if (hash_to_nss[instance->crypto_hash_type]) {
		if (calculate_nss_hash(instance, buf_out, *buf_out_len, buf_out + *buf_out_len) < 0) {
			return -1;
		}
		*buf_out_len += hash_len[instance->crypto_hash_type];
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int encrypt_nss(
	struct crypto_instance *instance,
	const unsigned char *buf_in,
	const size_t buf_in_len,
	unsigned char *buf_out,
	size_t *buf_out_len)
{
	PK11Context*	crypt_context = NULL;
	SECItem		crypt_param;
	SECItem		*nss_sec_param = NULL;
	int		tmp1_outlen = 0;
	unsigned int	tmp2_outlen = 0;
	unsigned char	*salt = buf_out;
	unsigned char	*data = buf_out + SALT_SIZE;
	int		err = -1;

	if (!cipher_to_nss[instance->crypto_cipher_type]) {
		memcpy(buf_out, buf_in, buf_in_len);
		*buf_out_len = buf_in_len;
		return 0;
	}

	if (PK11_GenerateRandom (salt, SALT_SIZE) != SECSuccess) {
		log_printf(instance->log_level_security,
			"Failure to generate a random number %d",
			PR_GetError());
		goto out;
	}

	crypt_param.type = siBuffer;
	crypt_param.data = salt;
	crypt_param.len = SALT_SIZE;

	nss_sec_param = PK11_ParamFromIV (cipher_to_nss[instance->crypto_cipher_type],
					  &crypt_param);
	if (nss_sec_param == NULL) {
		log_printf(instance->log_level_security,
			   "Failure to set up PKCS11 param (err %d)",
			   PR_GetError());
		goto out;
	}

	/*
	 * Create cipher context for encryption
	 */
	crypt_context = PK11_CreateContextBySymKey (cipher_to_nss[instance->crypto_cipher_type],
						    CKA_ENCRYPT,
						    instance->nss_sym_key,
						    nss_sec_param);
	if (!crypt_context) {
		log_printf(instance->log_level_security,
			   "PK11_CreateContext failed (encrypt) crypt_type=%d (err %d)",
			   (int)cipher_to_nss[instance->crypto_cipher_type],
			   PR_GetError());
		goto out;
	}

	if (PK11_CipherOp(crypt_context, data,
			  &tmp1_outlen,
			  FRAME_SIZE_MAX - instance->crypto_header_size,
			  (unsigned char *)buf_in, buf_in_len) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_CipherOp failed (encrypt) crypt_type=%d (err %d)",
			   (int)cipher_to_nss[instance->crypto_cipher_type],
			   PR_GetError());
		goto out;
	}

	if (PK11_DigestFinal(crypt_context, data + tmp1_outlen,
			     &tmp2_outlen, FRAME_SIZE_MAX - tmp1_outlen) != SECSuccess) {
		log_printf(instance->log_level_security,
			   "PK11_DigestFinal failed (encrypt) crypt_type=%d (err %d)",
			   (int)cipher_to_nss[instance->crypto_cipher_type],
			   PR_GetError());
		goto out;

	}

	*buf_out_len = tmp1_outlen + tmp2_outlen + SALT_SIZE;

	err = 0;

out:
	if (crypt_context) {
		PK11_DestroyContext(crypt_context, PR_TRUE);
	}
	if (nss_sec_param) {
		SECITEM_FreeItem(nss_sec_param, PR_TRUE);
	}
	return err;
}

Project: corosync
File: totemcrypto.c

static int init_nss(struct crypto_instance *instance,
		    const char *crypto_cipher_type,
		    const char *crypto_hash_type,
		    const char *crypto_compat_type)
{
	log_printf(instance->log_level_notice,
		   "Initializing transmit/receive security (NSS) crypto: %s hash: %s compat: %s",
		   crypto_cipher_type, crypto_hash_type, crypto_compat_type);

	if (init_nss_db(instance) < 0) {
		return -1;
	}

	if (init_nss_crypto(instance) < 0) {
		return -1;
	}

	if (init_nss_hash(instance) < 0) {
		return -1;
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int init_nss_crypto(struct crypto_instance *instance)
{
	PK11SlotInfo*	crypt_slot = NULL;
	SECItem		crypt_param;

	if (!cipher_to_nss[instance->crypto_cipher_type]) {
		return 0;
	}

	crypt_param.type = siBuffer;
	crypt_param.data = instance->private_key;
	crypt_param.len = cipher_key_len[instance->crypto_cipher_type];

	crypt_slot = PK11_GetBestSlot(cipher_to_nss[instance->crypto_cipher_type], NULL);
	if (crypt_slot == NULL) {
		log_printf(instance->log_level_security, "Unable to find security slot (err %d)",
			   PR_GetError());
		return -1;
	}

	instance->nss_sym_key = PK11_ImportSymKey(crypt_slot,
						  cipher_to_nss[instance->crypto_cipher_type],
						  PK11_OriginUnwrap, CKA_ENCRYPT|CKA_DECRYPT,
						  &crypt_param, NULL);
	if (instance->nss_sym_key == NULL) {
		log_printf(instance->log_level_security, "Failure to import key into NSS (err %d)",
			   PR_GetError());
		return -1;
	}

	PK11_FreeSlot(crypt_slot);

	return 0;
}

Project: corosync
File: totemcrypto.c

static int init_nss_db(struct crypto_instance *instance)
{
	if ((!cipher_to_nss[instance->crypto_cipher_type]) &&
	    (!hash_to_nss[instance->crypto_hash_type])) {
		return 0;
	}

	if (NSS_NoDB_Init(".") != SECSuccess) {
		log_printf(instance->log_level_security, "NSS DB initialization failed (err %d)",
			   PR_GetError());
		return -1;
	}

	return 0;
}

Project: corosync
File: totemcrypto.c

static int string_to_crypto_cipher_type(const char* crypto_cipher_type)
{
	if (strcmp(crypto_cipher_type, "none") == 0) {
		return CRYPTO_CIPHER_TYPE_NONE;
	} else if (strcmp(crypto_cipher_type, "aes256") == 0) {
		return CRYPTO_CIPHER_TYPE_AES256;
	} else if (strcmp(crypto_cipher_type, "aes192") == 0) {
		return CRYPTO_CIPHER_TYPE_AES192;
	} else if (strcmp(crypto_cipher_type, "aes128") == 0) {
		return CRYPTO_CIPHER_TYPE_AES128;
	} else if (strcmp(crypto_cipher_type, "3des") == 0) {
		return CRYPTO_CIPHER_TYPE_3DES;
	}
	return CRYPTO_CIPHER_TYPE_AES256;
}

Project: corosync
File: totemcrypto.c

static int string_to_crypto_compat_type(const char* crypto_compat_type)
{
	if (strcmp(crypto_compat_type, "2.0") == 0) {
		return CRYPTO_COMPAT_2_0;
	} else if (strcmp(crypto_compat_type, "2.1") == 0) {
		return CRYPTO_COMPAT_2_2;
	}
	return CRYPTO_COMPAT_2_2;
}

Project: corosync
File: totemcrypto.c

static int string_to_crypto_hash_type(const char* crypto_hash_type)
{
	if (strcmp(crypto_hash_type, "none") == 0) {
		return CRYPTO_HASH_TYPE_NONE;
	} else if (strcmp(crypto_hash_type, "md5") == 0) {
		return CRYPTO_HASH_TYPE_MD5;
	} else if (strcmp(crypto_hash_type, "sha1") == 0) {
		return CRYPTO_HASH_TYPE_SHA1;
	} else if (strcmp(crypto_hash_type, "sha256") == 0) {
		return CRYPTO_HASH_TYPE_SHA256;
	} else if (strcmp(crypto_hash_type, "sha384") == 0) {
		return CRYPTO_HASH_TYPE_SHA384;
	} else if (strcmp(crypto_hash_type, "sha512") == 0) {
		return CRYPTO_HASH_TYPE_SHA512;
	}

	return CRYPTO_HASH_TYPE_SHA1;
}