#include "crypto.h"

#include <string.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <glib.h>

#include "str.h"
#include "helpers.h"
#include "rtp.h"
#include "rtcp.h"
#include "log.h"
#include "rtplib.h"
#include "rtcplib.h"
#include "main.h"
#include "ssllib.h"
#include "types.h"

#include "xt_RTPENGINE.h"

static int aes_cm_encrypt_rtp(struct crypto_context *, struct rtp_header *, str *, uint32_t);
static int aes_cm_encrypt_rtcp(struct crypto_context *, struct rtcp_packet *, str *, uint32_t);
static int aes_gcm_encrypt_rtp(struct crypto_context *, struct rtp_header *, str *, uint32_t);
static int aes_gcm_decrypt_rtp(struct crypto_context *, struct rtp_header *, str *, uint32_t);
static int aes_gcm_encrypt_rtcp(struct crypto_context *, struct rtcp_packet *, str *, uint32_t);
static int aes_gcm_decrypt_rtcp(struct crypto_context *, struct rtcp_packet *, str *, uint32_t);
static int hmac_sha1_rtp(struct crypto_context *, char *out, str *in, uint32_t);
static int hmac_sha1_rtcp(struct crypto_context *, char *out, str *in);
static int aes_f8_encrypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx);
static int aes_f8_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx);
static int aes_cm_session_key_init(struct crypto_context *c);
static int aes_gcm_session_key_init(struct crypto_context *c);
static int aes_f8_session_key_init(struct crypto_context *c);
static int evp_session_key_cleanup(struct crypto_context *c);
static int null_crypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx);
static int null_crypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx);

/* all lengths are in bytes */
struct crypto_suite __crypto_suites[] = {
	{
		.name			= "AEAD_AES_256_GCM",
		.dtls_name		= "SRTP_AEAD_AES_256_GCM",
		.master_key_len		= 32,
		.master_salt_len	= 12,
		.session_key_len	= 32,
		.session_salt_len	= 12,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AEAD_AES_GCM_256,
		.kernel_hmac		= REH_NULL,
		.srtp_auth_tag		= 0,
		.srtcp_auth_tag		= 0,
		.srtp_auth_key_len	= 0,
		.srtcp_auth_key_len	= 0,
		.encrypt_rtp		= aes_gcm_encrypt_rtp,
		.decrypt_rtp		= aes_gcm_decrypt_rtp,
		.encrypt_rtcp		= aes_gcm_encrypt_rtcp,
		.decrypt_rtcp		= aes_gcm_decrypt_rtcp,
		.session_key_init	= aes_gcm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
		.aead_evp		= EVP_aes_256_gcm,
	},
	{
		.name			= "AEAD_AES_128_GCM",
		.dtls_name		= "SRTP_AEAD_AES_128_GCM",
		.master_key_len		= 16,
		.master_salt_len	= 12,
		.session_key_len	= 16,
		.session_salt_len	= 12,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AEAD_AES_GCM_128,
		.kernel_hmac		= REH_NULL,
		.srtp_auth_tag		= 0,
		.srtcp_auth_tag		= 0,
		.srtp_auth_key_len	= 0,
		.srtcp_auth_key_len	= 0,
		.encrypt_rtp		= aes_gcm_encrypt_rtp,
		.decrypt_rtp		= aes_gcm_decrypt_rtp,
		.encrypt_rtcp		= aes_gcm_encrypt_rtcp,
		.decrypt_rtcp		= aes_gcm_decrypt_rtcp,
		.session_key_init	= aes_gcm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
		.aead_evp		= EVP_aes_128_gcm,
	},
	{
		.name			= "AES_256_CM_HMAC_SHA1_80",
		//.dtls_name		= "SRTP_AES128_CM_SHA1_80",
		.master_key_len		= 32,
		.master_salt_len	= 14,
		.session_key_len	= 32,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_256,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 10,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "AES_256_CM_HMAC_SHA1_32",
		//.dtls_name		= "SRTP_AES128_CM_SHA1_32",
		.master_key_len		= 32,
		.master_salt_len	= 14,
		.session_key_len	= 32,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_256,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 4,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "AES_192_CM_HMAC_SHA1_80",
		//.dtls_name		= "SRTP_AES128_CM_SHA1_80",
		.master_key_len		= 24,
		.master_salt_len	= 14,
		.session_key_len	= 24,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_192,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 10,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "AES_192_CM_HMAC_SHA1_32",
		//.dtls_name		= "SRTP_AES128_CM_SHA1_32",
		.master_key_len		= 24,
		.master_salt_len	= 14,
		.session_key_len	= 24,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_192,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 4,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "AES_CM_128_HMAC_SHA1_80",
		.dtls_name		= "SRTP_AES128_CM_SHA1_80",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 16,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_128,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 10,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "AES_CM_128_HMAC_SHA1_32",
		.dtls_name		= "SRTP_AES128_CM_SHA1_32",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 16,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_CM_128,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 4,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_cm_encrypt_rtp,
		.decrypt_rtp		= aes_cm_encrypt_rtp,
		.encrypt_rtcp		= aes_cm_encrypt_rtcp,
		.decrypt_rtcp		= aes_cm_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_cm_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "F8_128_HMAC_SHA1_80",
//		.dtls_name		= "SRTP_AES128_F8_SHA1_80",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 16,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_F8,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 10,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_f8_encrypt_rtp,
		.decrypt_rtp		= aes_f8_encrypt_rtp,
		.encrypt_rtcp		= aes_f8_encrypt_rtcp,
		.decrypt_rtcp		= aes_f8_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_f8_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "F8_128_HMAC_SHA1_32",
//		.dtls_name		= "SRTP_AES128_F8_SHA1_32",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 16,
		.session_salt_len	= 14,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_AES_F8,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 4,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= aes_f8_encrypt_rtp,
		.decrypt_rtp		= aes_f8_encrypt_rtp,
		.encrypt_rtcp		= aes_f8_encrypt_rtcp,
		.decrypt_rtcp		= aes_f8_encrypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_init	= aes_f8_session_key_init,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "NULL_HMAC_SHA1_80",
//		.dtls_name		= "SRTP_NULL_SHA1_80",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 0,
		.session_salt_len	= 0,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_NULL,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 10,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= null_crypt_rtp,
		.decrypt_rtp		= null_crypt_rtp,
		.encrypt_rtcp		= null_crypt_rtcp,
		.decrypt_rtcp		= null_crypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
	{
		.name			= "NULL_HMAC_SHA1_32",
//		.dtls_name		= "SRTP_NULL_SHA1_32",
		.master_key_len		= 16,
		.master_salt_len	= 14,
		.session_key_len	= 0,
		.session_salt_len	= 0,
		.srtp_lifetime		= 1ULL << 48,
		.srtcp_lifetime		= 1ULL << 31,
		.kernel_cipher		= REC_NULL,
		.kernel_hmac		= REH_HMAC_SHA1,
		.srtp_auth_tag		= 4,
		.srtcp_auth_tag		= 10,
		.srtp_auth_key_len	= 20,
		.srtcp_auth_key_len	= 20,
		.encrypt_rtp		= null_crypt_rtp,
		.decrypt_rtp		= null_crypt_rtp,
		.encrypt_rtcp		= null_crypt_rtcp,
		.decrypt_rtcp		= null_crypt_rtcp,
		.hash_rtp		= hmac_sha1_rtp,
		.hash_rtcp		= hmac_sha1_rtcp,
		.session_key_cleanup	= evp_session_key_cleanup,
	},
};

/* those crypto suites we can */
const struct crypto_suite *crypto_suites = __crypto_suites;
const unsigned int num_crypto_suites = G_N_ELEMENTS(__crypto_suites);




const struct crypto_suite * crypto_find_suite(const str *s) {
	int i;
	const struct crypto_suite *cs;

	for (i = 0; i < num_crypto_suites; i++) {
		cs = &crypto_suites[i];

		if (str_casecmp_str(&cs->name_str, s) == 0)
			return cs;
	}

	return NULL;
}

/* rfc 3711 section 4.1 and 4.1.1
 * "in" and "out" MAY point to the same buffer */
static void aes_ctr(unsigned char *out, str *in, EVP_CIPHER_CTX *ecc, const unsigned char *iv) {
	unsigned char ivx[16];
	unsigned char key_block[16];
	unsigned char *p, *q;
	unsigned int left;
	int outlen, i;
	gboolean aligned = TRUE;
	uint64_t *pi, *qi, *ki;

	if (!ecc)
		return;

	memcpy(ivx, iv, 16);
	pi = (void *) in->s;
	qi = (void *) out;
	ki = (void *) key_block;
	left = in->len;

	if ((GPOINTER_TO_UINT(pi) % sizeof(*pi)) != 0)
		aligned = FALSE;
	if ((GPOINTER_TO_UINT(qi) % sizeof(*qi)) != 0)
		aligned = FALSE;

	while (left) {
		EVP_EncryptUpdate(ecc, key_block, &outlen, ivx, 16);
		assert(outlen == 16);

		if (left < 16 || !aligned) {
			p = (void *) pi;
			q = (void *) qi;
			for (i = 0; i < 16; i++) {
				*q++ = *p++ ^ key_block[i];
				left--;
				if (!left)
					goto done;
			}
		}
		else {
			qi[0] = pi[0] ^ ki[0];
			qi[1] = pi[1] ^ ki[1];
			left -= 16;
		}
		qi += 2;
		pi += 2;

		for (i = 15; i >= 0; i--) {
			ivx[i]++;
			if (G_LIKELY(ivx[i]))
				break;
		}
	}

done:
	;
}

static void aes_ctr_no_ctx(unsigned char *out, str *in, const unsigned char *key, const EVP_CIPHER *ciph,
		const unsigned char *iv)
{
	EVP_CIPHER_CTX *ctx;
	unsigned char block[16];
	int len;

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	ctx = EVP_CIPHER_CTX_new();
#else
	EVP_CIPHER_CTX ctx_s;
	ctx = &ctx_s;
	EVP_CIPHER_CTX_init(ctx);
#endif
	EVP_EncryptInit_ex(ctx, ciph, NULL, key, NULL);
	aes_ctr(out, in, ctx, iv);
	EVP_EncryptFinal_ex(ctx, block, &len);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_CIPHER_CTX_free(ctx);
#else
	EVP_CIPHER_CTX_cleanup(ctx);
#endif
}

/* rfc 3711 section 4.3.1 and 4.3.3
 * key: 128 bits
 * x: 112 bits
 * n <= 256
 * out->len := n / 8 */
static void prf_n(str *out, const unsigned char *key, const EVP_CIPHER *ciph, const unsigned char *x) {
	unsigned char iv[16];
	unsigned char o[32];
	unsigned char in[32];
	str in_s;

	assert(sizeof(o) >= out->len);

	ZERO(iv);
	memcpy(iv, x, 14);
	/* iv[14] = iv[15] = 0;   := x << 16 */
	ZERO(in); /* outputs the key stream */
	in_s = STR_LEN(in, out->len > 16 ? 32 : 16);
	aes_ctr_no_ctx(o, &in_s, key, ciph, iv);

	memcpy(out->s, o, out->len);
}



/* rfc 3711 section 4.3.1 */
int crypto_gen_session_key(struct crypto_context *c, str *out, unsigned char label, unsigned int index_len) {
	unsigned char key_id[7]; /* [ label, 48-bit ROC || SEQ ] */
	unsigned char x[14];
	int i;

	if (!out->len)
		return 0;

	ZERO(key_id);
	/* key_id[1..6] := r; or 1..4 for rtcp
	 * key_derivation_rate == 0 --> r == 0 */

	key_id[0] = label;
	assert(sizeof(x) >= c->params.crypto_suite->master_salt_len);
	memcpy(x, c->params.master_salt, c->params.crypto_suite->master_salt_len);
	// AEAD uses 12 bytes master salt; pad on the right to get 14
	// Errata: https://www.rfc-editor.org/errata_search.php?rfc=7714
	if (c->params.crypto_suite->master_salt_len == 12)
		x[12] = x[13] = '\x00';
	for (i = 13 - index_len; i < 14; i++)
		x[i] = key_id[i - (13 - index_len)] ^ x[i];

	prf_n(out, c->params.master_key, c->params.crypto_suite->aes_evp, x);

	ilogs(srtp, LOG_DEBUG, "Generated session key: master key "
			"%02x%02x%02x%02x..., "
			"master salt "
			"%02x%02x%02x%02x..., "
			"label %02x, length %zu, result "
			"%02x%02x%02x%02x...",
			c->params.master_key[0],
			c->params.master_key[1],
			c->params.master_key[2],
			c->params.master_key[3],
			c->params.master_salt[0],
			c->params.master_salt[1],
			c->params.master_salt[2],
			c->params.master_salt[3],
			label, out->len,
			(unsigned char) out->s[0],
			(unsigned char) out->s[1],
			(unsigned char) out->s[2],
			(unsigned char) out->s[3]);

	return 0;
}

/*
 * All versions of libsrtp w/openssl prior to 1.6 and 2.1 have
 * a bug in iv generation for AES-256 SRTCP only (SRTP is ok).
 * https://github.com/cisco/libsrtp/issues/264
 * Example: FreeSWITCH 1.6.x.
 * The bug is equivalent to:
 *
 * // idx <= 16 - no left shift
 * // ivi[1] ^= ssrc - don't use ssrc
 * // ivi[2] ^= idxh - don't use idxh
 */

/* rfc 3711 section 4.1.1 */
static int aes_cm_encrypt(struct crypto_context *c, uint32_t ssrc, str *s, uint32_t idx) {
	unsigned char iv[16];
	uint32_t *ivi;
	uint32_t idxh, idxl;

	memcpy(iv, c->session_salt, 14);
	iv[14] = iv[15] = '\0';
	ivi = (void *) iv;
	idxh = htonl((idx & 0xffff0000ULL) >> 16);
	idxl = htonl((idx & 0x0000ffffULL) << 16);

	ivi[1] ^= ssrc;
	ivi[2] ^= idxh;
	ivi[3] ^= idxl;

	aes_ctr((void *) s->s, s, c->session_key_ctx[0], iv);

	return 0;
}

/* rfc 3711 section 4.1 */
static int aes_cm_encrypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx) {
	return aes_cm_encrypt(c, r->ssrc, s, idx);
}

/* rfc 3711 sections 3.4 and 4.1 */
static int aes_cm_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx) {
	return aes_cm_encrypt(c, r->ssrc, s, idx);
}

/* rfc 7714 section 8 */

union aes_gcm_rtp_iv {
	unsigned char bytes[12];
	struct {
		uint16_t zeros;
		uint32_t ssrc;
		uint32_t roq;
		uint16_t seq;
	} __attribute__((__packed__));
} __attribute__((__packed__));

_Static_assert(offsetof(union aes_gcm_rtp_iv, seq) == 10,
               "union aes_gcm_rtp_iv not packed");

static int aes_gcm_encrypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx) {
	union aes_gcm_rtp_iv iv;
	int len, ciphertext_len;

	memcpy(iv.bytes, c->session_salt, 12);

	iv.ssrc ^= r->ssrc;
	iv.roq ^= htonl((idx & 0xffffff0000ULL) >> 16);
	iv.seq ^= htons( idx & 0x000000ffffULL);

	EVP_EncryptInit_ex(c->session_key_ctx[0], c->params.crypto_suite->aead_evp(), NULL,
			(const unsigned char *) c->session_key, iv.bytes);

	// nominally 12 bytes of AAD
	EVP_EncryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)r, s->s - (char *)r);

	EVP_EncryptUpdate(c->session_key_ctx[0], (unsigned char *) s->s, &len,
			(const unsigned char *) s->s, s->len);
	ciphertext_len = len;
	if (!EVP_EncryptFinal_ex(c->session_key_ctx[0], (unsigned char *) s->s+len, &len))
		return 1;
	ciphertext_len += len;
	// append the tag to the str buffer
	EVP_CIPHER_CTX_ctrl(c->session_key_ctx[0], EVP_CTRL_GCM_GET_TAG, 16, s->s+ciphertext_len);
	s->len = ciphertext_len + 16;

	return 0;
}

static int aes_gcm_decrypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx) {
	union aes_gcm_rtp_iv iv;
	int len, plaintext_len;

	if (s->len < 16)
		return -1;

	memcpy(iv.bytes, c->session_salt, 12);

	iv.ssrc ^= r->ssrc;
	iv.roq ^= htonl((idx & 0xffff0000ULL) >> 16);
	iv.seq ^= htons( idx & 0x0000ffffULL);

	EVP_DecryptInit_ex(c->session_key_ctx[0], c->params.crypto_suite->aead_evp(), NULL,
			(const unsigned char *) c->session_key, iv.bytes);

	// nominally 12 bytes of AAD
	EVP_DecryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)r, s->s - (char *)r);

	// decrypt partial buffer - the last 16 bytes are the tag
	EVP_DecryptUpdate(c->session_key_ctx[0], (unsigned char *) s->s, &len,
			(const unsigned char *) s->s, s->len-16);
	plaintext_len = len;
	EVP_CIPHER_CTX_ctrl(c->session_key_ctx[0], EVP_CTRL_GCM_SET_TAG, 16, s->s + s->len-16);
	if (!EVP_DecryptFinal_ex(c->session_key_ctx[0], (unsigned char *) s->s+len, &len))
		return 1;
	plaintext_len += len;
	s->len = plaintext_len;

	return 0;
}

/* rfc 7714 section 9 */

union aes_gcm_rtcp_iv {
	unsigned char bytes[12];
	struct {
		uint16_t zeros_a;
		uint32_t ssrc;
		uint16_t zeros_b;
		uint32_t srtcp;
	} __attribute__((__packed__));
} __attribute__((__packed__));

_Static_assert(offsetof(union aes_gcm_rtcp_iv, srtcp) == 8,
               "union aes_gcm_rtcp_iv not packed");

static int aes_gcm_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx) {
	union aes_gcm_rtcp_iv iv;
	uint32_t e_idx;
	int len, ciphertext_len;

	memcpy(iv.bytes, c->session_salt, 12);

	iv.ssrc ^= r->ssrc;
	iv.srtcp ^= htonl(idx & 0x007fffffffULL);
	e_idx = htonl((idx & 0x007fffffffULL) | 0x80000000ULL);

	EVP_EncryptInit_ex(c->session_key_ctx[0], c->params.crypto_suite->aead_evp(), NULL,
			(const unsigned char *) c->session_key, iv.bytes);

	// nominally 8 + 4 bytes of AAD
	EVP_EncryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)r, s->s - (char *)r);
	EVP_EncryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)&e_idx, 4);

	EVP_EncryptUpdate(c->session_key_ctx[0], (unsigned char *) s->s, &len,
			(const unsigned char *) s->s, s->len);
	ciphertext_len = len;
	if (!EVP_EncryptFinal_ex(c->session_key_ctx[0], (unsigned char *) s->s+len, &len))
		return 1;
	ciphertext_len += len;
	// append the tag to the str buffer
	EVP_CIPHER_CTX_ctrl(c->session_key_ctx[0], EVP_CTRL_GCM_GET_TAG, 16, s->s+ciphertext_len);
	s->len = ciphertext_len + 16;

	return 0;
}

static int aes_gcm_decrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx) {
	union aes_gcm_rtcp_iv iv;
	uint32_t e_idx;
	int len, plaintext_len;

	if (s->len < 16)
		return -1;

	memcpy(iv.bytes, c->session_salt, 12);

	iv.ssrc ^= r->ssrc;
	iv.srtcp ^= htonl(idx & 0x007fffffffULL);
	e_idx = htonl((idx & 0x007fffffffULL) | 0x80000000ULL);

	EVP_DecryptInit_ex(c->session_key_ctx[0], c->params.crypto_suite->aead_evp(), NULL,
			(const unsigned char *) c->session_key, iv.bytes);

	// nominally 8 + 4 bytes of AAD
	EVP_DecryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)r, s->s - (char *)r);
	EVP_DecryptUpdate(c->session_key_ctx[0], NULL, &len, (void *)&e_idx, 4);

	// decrypt partial buffer - the last 16 bytes are the tag
	EVP_DecryptUpdate(c->session_key_ctx[0], (unsigned char *) s->s, &len,
			(const unsigned char *) s->s, s->len-16);
	plaintext_len = len;
	EVP_CIPHER_CTX_ctrl(c->session_key_ctx[0], EVP_CTRL_GCM_SET_TAG, 16, s->s + s->len-16);
	if (!EVP_DecryptFinal_ex(c->session_key_ctx[0], (unsigned char *) s->s+len, &len))
		return 1;
	plaintext_len += len;
	s->len = plaintext_len;

	return 0;
}

/* rfc 3711 sections 4.1.2 and 4.1.2.1
 * encrypts in place */
static void aes_128_f8_encrypt(struct crypto_context *c, unsigned char *iv, str *s) {
	unsigned char key_block[16], last_key_block[16], /* S(j), S(j-1) */
		      ivx[16], /* IV' */
		      x[16];
	int i, outlen, left;
	uint32_t j;
	unsigned char *p;
	uint64_t *pi, *ki, *lki, *xi;
	uint32_t *xu;

	EVP_EncryptUpdate(c->session_key_ctx[1], ivx, &outlen, iv, 16);
	assert(outlen == 16);

	pi = (void *) s->s;
	ki = (void *) key_block;
	lki = (void *) last_key_block;
	xi = (void *) x;
	xu = (void *) x;
	left = s->len;
	j = 0;
	ZERO(last_key_block);

	while (left) {
		/* S(j) = E(k_e, IV' XOR j XOR S(j-1)) */
		memcpy(x, ivx, 16);

		xu[3] ^= htonl(j);

		xi[0] ^= lki[0];
		xi[1] ^= lki[1];

		EVP_EncryptUpdate(c->session_key_ctx[0], key_block, &outlen, x, 16);
		assert(outlen == 16);

		if (G_UNLIKELY(left < 16)) {
			p = (void *) pi;
			for (i = 0; i < 16; i++) {
				*p++ ^= key_block[i];
				left--;
				if (!left)
					goto done;
			}
			abort();
		}

		*pi++ ^= ki[0];
		*pi++ ^= ki[1];
		left -= 16;
		if (!left)
			break;

		j++;
		memcpy(last_key_block, key_block, 16);
	}

done:
	;
}

/* rfc 3711 section 4.1.2.2 */
static int aes_f8_encrypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx) {
	unsigned char iv[16];
	uint32_t roc;

	iv[0] = '\0';
	memcpy(&iv[1], &r->m_pt, 11); /* m, pt, seq, ts, ssrc */
	roc = htonl((idx & 0xffffffff0000ULL) >> 16);
	memcpy(&iv[12], &roc, sizeof(roc));

	aes_128_f8_encrypt(c, iv, s);

	return 0;
}

/* rfc 3711 section 4.1.2.3 */
static int aes_f8_encrypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx) {
	unsigned char iv[16];
	uint32_t i;

	memset(iv, 0, 4);
	i = htonl(0x80000000ULL | idx);
	memcpy(&iv[4], &i, 4);
	memcpy(&iv[8], r, 8); /* v, p, rc, pt, length, ssrc */

	aes_128_f8_encrypt(c, iv, s);

	return 0;
}
/* rfc 3711, sections 4.2 and 4.2.1 */
static int hmac_sha1_rtp(struct crypto_context *c, char *out, str *in, uint32_t index) {
	unsigned char hmac[20];
	uint32_t roc;

	roc = htonl((index & 0xffffffff0000ULL) >> 16);

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
	EVP_MAC_CTX *hc;

	hc = EVP_MAC_CTX_dup(rtpe_hmac_sha1_base);
	EVP_MAC_init(hc, (unsigned char *) c->session_auth_key,
			c->params.crypto_suite->srtp_auth_key_len, NULL);
	EVP_MAC_update(hc, (unsigned char *) in->s, in->len);
	EVP_MAC_update(hc, (unsigned char *) &roc, sizeof(roc));
	size_t outsize = sizeof(hmac);
	EVP_MAC_final(hc, hmac, &outsize, outsize);
	EVP_MAC_CTX_free(hc);
#else // <3.0
	HMAC_CTX *hc;

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	hc = HMAC_CTX_new();
#else
	HMAC_CTX hc_s;
	HMAC_CTX_init(&hc_s);
	hc = &hc_s;
#endif

	HMAC_Init_ex(hc, c->session_auth_key, c->params.crypto_suite->srtp_auth_key_len, EVP_sha1(), NULL);
	HMAC_Update(hc, (unsigned char *) in->s, in->len);
	HMAC_Update(hc, (unsigned char *) &roc, sizeof(roc));
	HMAC_Final(hc, hmac, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	HMAC_CTX_free(hc);
#else
	HMAC_CTX_cleanup(hc);
#endif
#endif

	assert(sizeof(hmac) >= c->params.crypto_suite->srtp_auth_tag);
	memcpy(out, hmac, c->params.crypto_suite->srtp_auth_tag);

	return 0;
}

/* rfc 3711, sections 4.2 and 4.2.1 */
static int hmac_sha1_rtcp(struct crypto_context *c, char *out, str *in) {
	unsigned char hmac[20];

	if (!HMAC(EVP_sha1(), c->session_auth_key, c->params.crypto_suite->srtcp_auth_key_len,
			(unsigned char *) in->s, in->len, hmac, NULL))
	{
		memset(out, 0, c->params.crypto_suite->srtcp_auth_tag);
		return 1;
	}

	assert(sizeof(hmac) >= c->params.crypto_suite->srtcp_auth_tag);
	memcpy(out, hmac, c->params.crypto_suite->srtcp_auth_tag);

	return 0;
}

static int aes_cm_session_key_init(struct crypto_context *c) {
	evp_session_key_cleanup(c);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	c->session_key_ctx[0] = EVP_CIPHER_CTX_new();
#else
	c->session_key_ctx[0] = g_new(EVP_CIPHER_CTX, 1);
	EVP_CIPHER_CTX_init(c->session_key_ctx[0]);
#endif
	EVP_EncryptInit_ex(c->session_key_ctx[0], c->params.crypto_suite->aes_evp, NULL,
			(unsigned char *) c->session_key, NULL);
	return 0;
}

static int aes_gcm_session_key_init(struct crypto_context *c) {
	evp_session_key_cleanup(c);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	c->session_key_ctx[0] = EVP_CIPHER_CTX_new();
#else
	c->session_key_ctx[0] = g_new(EVP_CIPHER_CTX, 1);
	EVP_CIPHER_CTX_init(c->session_key_ctx[0]);
#endif
	return 0;
}

static int aes_f8_session_key_init(struct crypto_context *c) {
	unsigned char m[16];
	int i;
	int k_e_len, k_s_len; /* n_e, n_s */
	unsigned char *key;

	aes_cm_session_key_init(c);

	k_e_len = c->params.crypto_suite->session_key_len;
	k_s_len = c->params.crypto_suite->session_salt_len;
	key = (unsigned char *) c->session_key;

	/* m = k_s || 0x555..5 */
	memcpy(m, c->session_salt, k_s_len);
	for (i = k_s_len; i < k_e_len; i++)
		m[i] = 0x55;
	/* IV' = E(k_e XOR m, IV) */
	for (i = 0; i < k_e_len; i++)
		m[i] ^= key[i];

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	c->session_key_ctx[1] = EVP_CIPHER_CTX_new();
#else
	c->session_key_ctx[1] = g_new(EVP_CIPHER_CTX, 1);
	EVP_CIPHER_CTX_init(c->session_key_ctx[1]);
#endif
	EVP_EncryptInit_ex(c->session_key_ctx[1], EVP_aes_128_ecb(), NULL, m, NULL);

	return 0;
}

static int evp_session_key_cleanup(struct crypto_context *c) {
	unsigned char block[16];
	int len, i;

	for (i = 0; i < G_N_ELEMENTS(c->session_key_ctx); i++) {
		if (!c->session_key_ctx[i])
			continue;

		EVP_EncryptFinal_ex(c->session_key_ctx[i], block, &len);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
		EVP_CIPHER_CTX_free(c->session_key_ctx[i]);
#else
		EVP_CIPHER_CTX_cleanup(c->session_key_ctx[i]);
		g_free(c->session_key_ctx[i]);
#endif
		c->session_key_ctx[i] = NULL;
	}

	return 0;
}

static int null_crypt_rtp(struct crypto_context *c, struct rtp_header *r, str *s, uint32_t idx) {
	return 0;
}
static int null_crypt_rtcp(struct crypto_context *c, struct rtcp_packet *r, str *s, uint32_t idx) {
	return 0;
}

static void dump_key(struct crypto_context *c, int log_level) {
	char *k, *s;

	if (!c->params.crypto_suite)
		return;

	k = g_base64_encode(c->params.master_key, c->params.crypto_suite->master_key_len);
	s = g_base64_encode(c->params.master_salt, c->params.crypto_suite->master_salt_len);

	ilogs(srtp, log_level, "--- %s key %s%s%s salt %s%s%s", c->params.crypto_suite->name, FMT_M(k), FMT_M(s));

	g_free(k);
	g_free(s);
}

void crypto_dump_keys(struct crypto_context *in, struct crypto_context *out) {
	int log_level = LOG_DEBUG;
	
	if (rtpe_config.log_keys)
	    log_level = LOG_ERROR;
	    
	if (get_log_level(core) < log_level)
		return;

	ilogs(srtp, log_level, "SRTP keys, incoming:");
	dump_key(in, log_level);
	ilogs(srtp, log_level, "SRTP keys, outgoing:");
	dump_key(out, log_level);
}

char *crypto_params_sdes_dump(const struct crypto_params_sdes *cps, char **buf) {
	if (*buf)
		g_free(*buf);

	GString *s = g_string_new("");
	if (!cps || !cps->params.crypto_suite) {
		g_string_append(s, "<none>");
		goto out;
	}

	g_string_append_printf(s, "suite %s, tag %u, key ", cps->params.crypto_suite->name, cps->tag);
	char *b = g_base64_encode(cps->params.master_key, cps->params.crypto_suite->master_key_len);
	g_string_append_printf(s, "%s salt ", b);
	g_free(b);
	b = g_base64_encode(cps->params.master_salt, cps->params.crypto_suite->master_salt_len);
	g_string_append_printf(s, "%s", b);
	g_free(b);

out:
	*buf = g_string_free(s, FALSE);
	return *buf;
}

void crypto_init_main(void) {
	struct crypto_suite *cs;
	for (unsigned int i = 0; i < num_crypto_suites; i++) {
		cs = &__crypto_suites[i];
		cs->idx = i;
		cs->name_str = STR(cs->name);
		switch(cs->master_key_len) {
		case 16:
			cs->aes_evp = EVP_aes_128_ecb();
			break;
		case 24:
			cs->aes_evp = EVP_aes_192_ecb();
			break;
		case 32:
			cs->aes_evp = EVP_aes_256_ecb();
			break;
		}
	}
}

void __crypto_debug_printf(crypto_debug_string *gs, const char *fmt, ...) {
	va_list va;
	va_start(va, fmt);
	g_string_append_vprintf(gs, fmt, va);
	va_end(va);
}
