diff --git a/include/srtp.h b/include/srtp.h index f5d872f08..b25ab3826 100644 --- a/include/srtp.h +++ b/include/srtp.h @@ -351,6 +351,8 @@ typedef struct srtp_policy_t { uint8_t *enc_xtn_hdr; /**< List of header ids to encrypt. */ size_t enc_xtn_hdr_count; /**< Number of entries in list of header */ /**< ids. */ + bool use_cryptex; /**< Encrypt header block and CSRCS with */ + /**< cryptex. */ struct srtp_policy_t *next; /**< Pointer to next stream policy. */ } srtp_policy_t; diff --git a/include/srtp_priv.h b/include/srtp_priv.h index d5ff85bec..72e87cd94 100644 --- a/include/srtp_priv.h +++ b/include/srtp_priv.h @@ -125,6 +125,7 @@ typedef struct srtp_stream_ctx_t_ { uint8_t *enc_xtn_hdr; size_t enc_xtn_hdr_count; uint32_t pending_roc; + bool use_cryptex; } strp_stream_ctx_t_; /* diff --git a/srtp/srtp.c b/srtp/srtp.c index b0df1bee0..058b723d0 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -90,6 +90,12 @@ static const size_t octets_in_rtp_header = 12; static const size_t octets_in_rtcp_header = 8; static const size_t octets_in_rtp_xtn_hdr = 4; +static const uint16_t xtn_hdr_one_byte_profile = 0xbede; +static const uint16_t xtn_hdr_two_byte_profile = 0x1000; + +static const uint16_t cryptex_one_byte_profile = 0xc0de; +static const uint16_t cryptex_two_byte_profile = 0xc2de; + static size_t srtp_get_rtp_hdr_len(const srtp_hdr_t *hdr) { return octets_in_rtp_header + 4 * hdr->cc; @@ -121,6 +127,182 @@ static size_t srtp_get_rtp_xtn_hdr_len(const srtp_hdr_t *hdr, return (ntohs(xtn_hdr->length) + 1u) * 4u; } +static uint16_t srtp_get_rtp_xtn_hdr_profile(const srtp_hdr_t *hdr, + const uint8_t *rtp) +{ + const srtp_hdr_xtnd_t *xtn_hdr = + (const srtp_hdr_xtnd_t *)(rtp + srtp_get_rtp_hdr_len(hdr)); + return ntohs(xtn_hdr->profile_specific); +} + +static void srtp_cryptex_adjust_buffer(const srtp_hdr_t *hdr, uint8_t *rtp) +{ + uint8_t tmp[4]; + uint8_t *ptr = rtp + srtp_get_rtp_hdr_len(hdr); + memcpy(tmp, ptr, 4); + for (size_t i = hdr->cc; i > 0; --i) { + memcpy(ptr, ptr - 4, 4); + ptr -= 4; + } + memcpy(ptr, tmp, 4); +} + +static void srtp_cryptex_restore_buffer(const srtp_hdr_t *hdr, uint8_t *rtp) +{ + uint8_t tmp[4]; + uint8_t *ptr = rtp + octets_in_rtp_header; + memcpy(tmp, ptr, 4); + for (size_t i = 0; i < hdr->cc; ++i) { + memcpy(ptr, ptr + 4, 4); + ptr += 4; + } + memcpy(ptr, tmp, 4); +} + +static srtp_err_status_t srtp_cryptex_protect_init( + const srtp_stream_ctx_t *stream, + const srtp_hdr_t *hdr, + const uint8_t *rtp, + const uint8_t *srtp, + bool *inuse, + bool *inplace, + size_t *enc_start) +{ + if (stream->use_cryptex && stream->rtp_services & sec_serv_conf) { + if (hdr->cc && hdr->x == 0) { + /* Cryptex can only encrypt CSRCS if header extension is present */ + return srtp_err_status_parse_err; + } + *inuse = hdr->x == 1; + } else { + *inuse = false; + } + + *inplace = *inuse && rtp == srtp; + + if (*inuse) { + *enc_start -= + (srtp_get_rtp_xtn_hdr_len(hdr, rtp) - octets_in_rtp_xtn_hdr); + if (*inplace) { + *enc_start -= (hdr->cc * 4); + } + } + + return srtp_err_status_ok; +} + +static srtp_err_status_t srtp_cryptex_protect(bool inplace, + const srtp_hdr_t *hdr, + uint8_t *srtp, + srtp_cipher_t *rtp_cipher) +{ + srtp_hdr_xtnd_t *xtn_hdr = srtp_get_rtp_xtn_hdr(hdr, srtp); + uint16_t profile = ntohs(xtn_hdr->profile_specific); + if (profile == xtn_hdr_one_byte_profile) { + xtn_hdr->profile_specific = htons(cryptex_one_byte_profile); + } else if (profile == xtn_hdr_two_byte_profile) { + xtn_hdr->profile_specific = htons(cryptex_two_byte_profile); + } else { + return srtp_err_status_parse_err; + } + + if (inplace) { + srtp_cryptex_adjust_buffer(hdr, srtp); + } else { + if (hdr->cc) { + uint8_t *cc_list = srtp + octets_in_rtp_header; + size_t cc_list_size = hdr->cc * 4; + /* CSRCs are in dst header already, enc in place */ + srtp_err_status_t status = srtp_cipher_encrypt( + rtp_cipher, cc_list, cc_list_size, cc_list, &cc_list_size); + if (status) { + return srtp_err_status_cipher_fail; + } + } + } + + return srtp_err_status_ok; +} + +static void srtp_cryptex_protect_cleanup(bool inplace, + const srtp_hdr_t *hdr, + uint8_t *srtp) +{ + if (inplace) { + srtp_cryptex_restore_buffer(hdr, srtp); + } +} + +static srtp_err_status_t srtp_cryptex_unprotect_init( + const srtp_stream_ctx_t *stream, + const srtp_hdr_t *hdr, + const uint8_t *srtp, + const uint8_t *rtp, + bool *inuse, + bool *inplace, + size_t *enc_start) +{ + if (stream->use_cryptex && hdr->x == 1) { + uint16_t profile = srtp_get_rtp_xtn_hdr_profile(hdr, rtp); + *inuse = profile == cryptex_one_byte_profile || + profile == cryptex_two_byte_profile; + } else { + *inuse = false; + } + + *inplace = *inuse && srtp == rtp; + + if (*inuse) { + *enc_start -= + (srtp_get_rtp_xtn_hdr_len(hdr, rtp) - octets_in_rtp_xtn_hdr); + if (*inplace) { + *enc_start -= (hdr->cc * 4); + } + } + + return srtp_err_status_ok; +} + +static srtp_err_status_t srtp_cryptex_unprotect(bool inplace, + const srtp_hdr_t *hdr, + uint8_t *rtp, + srtp_cipher_t *rtp_cipher) +{ + if (inplace) { + srtp_cryptex_adjust_buffer(hdr, rtp); + } else { + if (hdr->cc) { + uint8_t *cc_list = rtp + octets_in_rtp_header; + size_t cc_list_size = hdr->cc * 4; + /* CSRCs are in dst header already, enc in place */ + srtp_err_status_t status = srtp_cipher_decrypt( + rtp_cipher, cc_list, cc_list_size, cc_list, &cc_list_size); + if (status) { + return srtp_err_status_cipher_fail; + } + } + } + + return srtp_err_status_ok; +} + +static void srtp_cryptex_unprotect_cleanup(bool inplace, + const srtp_hdr_t *hdr, + uint8_t *rtp) +{ + if (inplace) { + srtp_cryptex_restore_buffer(hdr, rtp); + } + + srtp_hdr_xtnd_t *xtn_hdr = srtp_get_rtp_xtn_hdr(hdr, rtp); + uint16_t profile = ntohs(xtn_hdr->profile_specific); + if (profile == cryptex_one_byte_profile) { + xtn_hdr->profile_specific = htons(xtn_hdr_one_byte_profile); + } else if (profile == cryptex_two_byte_profile) { + xtn_hdr->profile_specific = htons(xtn_hdr_two_byte_profile); + } +} + static srtp_err_status_t srtp_validate_rtp_header(const uint8_t *rtp, size_t pkt_octet_len) { @@ -563,6 +745,8 @@ static srtp_err_status_t srtp_stream_alloc(srtp_stream_ctx_t **str_ptr, str->enc_xtn_hdr_count = 0; } + str->use_cryptex = p->use_cryptex; + return srtp_err_status_ok; } @@ -673,7 +857,7 @@ static srtp_err_status_t srtp_stream_clone( /* copy information about extensions header encryption */ str->enc_xtn_hdr = stream_template->enc_xtn_hdr; str->enc_xtn_hdr_count = stream_template->enc_xtn_hdr_count; - + str->use_cryptex = stream_template->use_cryptex; return srtp_err_status_ok; } @@ -1598,7 +1782,7 @@ static srtp_err_status_t srtp_process_header_encryption( uint8_t *xtn_hdr_end = xtn_hdr_data + (ntohs(xtn_hdr->length) * sizeof(uint32_t)); - if (ntohs(xtn_hdr->profile_specific) == 0xbede) { + if (ntohs(xtn_hdr->profile_specific) == xtn_hdr_one_byte_profile) { /* RFC 5285, section 4.2. One-Byte Header */ while (xtn_hdr_data < xtn_hdr_end) { uint8_t xid = (*xtn_hdr_data & 0xf0) >> 4; @@ -1637,7 +1821,8 @@ static srtp_err_status_t srtp_process_header_encryption( xtn_hdr_data++; } } - } else if ((ntohs(xtn_hdr->profile_specific) & 0xfff0) == 0x1000) { + } else if ((ntohs(xtn_hdr->profile_specific) & 0xfff0) == + xtn_hdr_two_byte_profile) { /* RFC 5285, section 4.3. Two-Byte Header */ while (xtn_hdr_data + 1 < xtn_hdr_end) { uint8_t xid = *xtn_hdr_data; @@ -1911,6 +2096,11 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + /* check output length */ + if (*srtp_len < rtp_len + tag_len + stream->mki_size) { + return srtp_err_status_buffer_small; + } + /* * find starting point for encryption and length of data to be * encrypted - the encrypted portion starts after the rtp header @@ -1922,17 +2112,25 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, enc_start += srtp_get_rtp_xtn_hdr_len(hdr, rtp); } + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_protect_init(stream, hdr, rtp, srtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + + if (cryptex_inuse && !cryptex_inplace && hdr->cc) { + debug_print0(mod_srtp, + "unsupported cryptex mode, AEAD, CC and not inplace io"); + return srtp_err_status_bad_param; + } + /* note: the passed size is without the auth tag */ if (enc_start > rtp_len) { return srtp_err_status_parse_err; } enc_octet_len = rtp_len - enc_start; - /* check output length */ - if (*srtp_len < rtp_len + tag_len + stream->mki_size) { - return srtp_err_status_buffer_small; - } - /* if not-inplace then need to copy full rtp header */ if (rtp != srtp) { memcpy(srtp, rtp, enc_start); @@ -1996,6 +2194,14 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, } } + if (cryptex_inuse) { + status = srtp_cryptex_protect(cryptex_inplace, hdr, srtp, + session_keys->rtp_cipher); + if (status) { + return status; + } + } + /* * Set the AAD over the RTP header */ @@ -2019,6 +2225,10 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, stream->mki_size); } + if (cryptex_inuse) { + srtp_cryptex_protect_cleanup(cryptex_inplace, hdr, srtp); + } + *srtp_len = enc_start + enc_octet_len; /* increase the packet length by the length of the mki_size */ @@ -2082,6 +2292,19 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, enc_start += srtp_get_rtp_xtn_hdr_len(hdr, srtp); } + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_unprotect_init(stream, hdr, srtp, rtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + + if (cryptex_inuse && !cryptex_inplace && hdr->cc) { + debug_print0(mod_srtp, + "unsupported cryptex mode, AEAD, CC and not inplace io"); + return srtp_err_status_bad_param; + } + if (enc_start > srtp_len - tag_len - stream->mki_size) { return srtp_err_status_parse_err; } @@ -2128,6 +2351,14 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, break; } + if (cryptex_inuse) { + status = srtp_cryptex_unprotect(cryptex_inplace, hdr, rtp, + session_keys->rtp_cipher); + if (status) { + return status; + } + } + /* * Set the AAD for AES-GCM, which is the RTP header */ @@ -2157,6 +2388,10 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, } } + if (cryptex_inuse) { + srtp_cryptex_unprotect_cleanup(cryptex_inplace, hdr, rtp); + } + /* * verify that stream is for received traffic - this check will * detect SSRC collisions, since a stream that appears in both @@ -2347,6 +2582,11 @@ srtp_err_status_t srtp_protect(srtp_t ctx, /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + /* check output length */ + if (*srtp_len < rtp_len + stream->mki_size + tag_len) { + return srtp_err_status_buffer_small; + } + /* * find starting point for encryption and length of data to be * encrypted - the encrypted portion starts after the rtp header @@ -2358,16 +2598,18 @@ srtp_err_status_t srtp_protect(srtp_t ctx, enc_start += srtp_get_rtp_xtn_hdr_len(hdr, rtp); } + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_protect_init(stream, hdr, rtp, srtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + if (enc_start > rtp_len) { return srtp_err_status_parse_err; } enc_octet_len = rtp_len - enc_start; - /* check output length */ - if (*srtp_len < rtp_len + stream->mki_size + tag_len) { - return srtp_err_status_buffer_small; - } - /* if not-inplace then need to copy full rtp header */ if (rtp != srtp) { memcpy(srtp, rtp, enc_start); @@ -2482,6 +2724,14 @@ srtp_err_status_t srtp_protect(srtp_t ctx, } } + if (cryptex_inuse) { + status = srtp_cryptex_protect(cryptex_inplace, hdr, srtp, + session_keys->rtp_cipher); + if (status) { + return status; + } + } + /* if we're encrypting, exor keystream into the message */ if (stream->rtp_services & sec_serv_conf) { status = srtp_cipher_encrypt(session_keys->rtp_cipher, rtp + enc_start, @@ -2495,6 +2745,10 @@ srtp_err_status_t srtp_protect(srtp_t ctx, memcpy(srtp + enc_start, rtp + enc_start, enc_octet_len); } + if (cryptex_inuse) { + srtp_cryptex_protect_cleanup(cryptex_inplace, hdr, srtp); + } + /* * if we're authenticating, run authentication function and put result * into the auth_tag @@ -2681,6 +2935,13 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, enc_start += srtp_get_rtp_xtn_hdr_len(hdr, srtp); } + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_unprotect_init(stream, hdr, srtp, rtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + if (enc_start > srtp_len - tag_len - stream->mki_size) { return srtp_err_status_parse_err; } @@ -2789,6 +3050,14 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, } } + if (cryptex_inuse) { + status = srtp_cryptex_unprotect(cryptex_inplace, hdr, rtp, + session_keys->rtp_cipher); + if (status) { + return status; + } + } + /* if we're decrypting, add keystream into ciphertext */ if (stream->rtp_services & sec_serv_conf) { status = @@ -2802,6 +3071,10 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, memcpy(rtp + enc_start, srtp + enc_start, enc_octet_len); } + if (cryptex_inuse) { + srtp_cryptex_unprotect_cleanup(cryptex_inplace, hdr, rtp); + } + /* * verify that stream is for received traffic - this check will * detect SSRC collisions, since a stream that appears in both diff --git a/test/srtp_driver.c b/test/srtp_driver.c index b486c9845..7ab15c9cb 100644 --- a/test/srtp_driver.c +++ b/test/srtp_driver.c @@ -66,8 +66,12 @@ srtp_err_status_t srtp_validate_mki(void); srtp_err_status_t srtp_validate_null(void); +srtp_err_status_t srtp_validate_cryptex(void); + #ifdef GCM srtp_err_status_t srtp_validate_gcm(void); + +srtp_err_status_t srtp_validate_gcm_cryptex(void); #endif srtp_err_status_t srtp_validate_encrypted_extensions_headers(void); @@ -167,6 +171,12 @@ const uint8_t rtp_test_packet_extension_header[12] = { #define TEST_MKI_ID_SIZE 4 +typedef struct test_vectors_t { + const char *name; + const char *plaintext; + const char *ciphertext; +} test_vectors_t; + extern uint8_t test_key[46]; extern uint8_t test_key_2[46]; extern uint8_t test_mki_id[TEST_MKI_ID_SIZE]; @@ -698,6 +708,15 @@ int main(int argc, char *argv[]) exit(1); } + printf("testing srtp_protect and srtp_unprotect against " + "reference cryptex packet\n"); + if (srtp_validate_cryptex() == srtp_err_status_ok) { + printf("passed\n\n"); + } else { + printf("failed\n"); + exit(1); + } + #ifdef GCM printf("testing srtp_protect and srtp_unprotect against " "reference packet using GCM\n"); @@ -707,6 +726,15 @@ int main(int argc, char *argv[]) printf("failed\n"); exit(1); } + + printf("testing srtp_protect and srtp_unprotect against " + "reference cryptex packet using GCM\n"); + if (srtp_validate_gcm_cryptex() == srtp_err_status_ok) { + printf("passed\n\n"); + } else { + printf("failed\n"); + exit(1); + } #endif printf("testing srtp_protect and srtp_unprotect against " @@ -2705,6 +2733,272 @@ srtp_err_status_t srtp_validate_null(void) return srtp_err_status_ok; } +/* + * srtp_validate_cryptex() verifies the correctness of libsrtp by comparing + * some computed packets against some pre-computed reference values. + * These packets were made with the default SRTP policy. + */ +srtp_err_status_t srtp_validate_cryptex(void) +{ + // clang-format off + /* Plaintext packet with 1-byte header extension */ + const char *srtp_1bytehdrext_ref = + "900f1235" + "decafbad" + "cafebabe" + "bede0001" + "51000200" + "abababab" + "abababab" + "abababab" + "abababab"; + + /* AES-CTR/HMAC-SHA1 Ciphertext packet with 1-byte header extension */ + const char *srtp_1bytehdrext_cryptex = + "900f1235" + "decafbad" + "cafebabe" + "c0de0001" + "eb923652" + "51c3e036" + "f8de27e9" + "c27ee3e0" + "b4651d9f" + "bc4218a7" + "0244522f" + "34a5"; + + /* Plaintext packet with 2-byte header extension */ + const char *srtp_2bytehdrext_ref = + "900f1236" + "decafbad" + "cafebabe" + "10000001" + "05020002" + "abababab" + "abababab" + "abababab" + "abababab"; + + /* AES-CTR/HMAC-SHA1 Ciphertext packet with 2-byte header extension */ + const char *srtp_2bytehdrext_cryptex = + "900f1236" + "decafbad" + "cafebabe" + "c2de0001" + "4ed9cc4e" + "6a712b30" + "96c5ca77" + "339d4204" + "ce0d7739" + "6cab6958" + "5fbce381" + "94a5"; + + /* Plaintext packet with 1-byte header extension and CSRC fields. */ + const char *srtp_1bytehdrext_cc_ref = + "920f1238" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "bede0001" + "51000200" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_1bytehdrext_cc_cryptex = + "920f1238" + "decafbad" + "cafebabe" + "8bb6e12b" + "5cff16dd" + "c0de0001" + "92838c8c" + "09e58393" + "e1de3a9a" + "74734d67" + "45671338" + "c3acf11d" + "a2df8423" + "bee0"; + + /* Plaintext packet with 2-byte header extension and CSRC fields. */ + const char *srtp_2bytehdrext_cc_ref = + "920f1239" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "10000001" + "05020002" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_2bytehdrext_cc_cryptex = + "920f1239" + "decafbad" + "cafebabe" + "f70e513e" + "b90b9b25" + "c2de0001" + "bbed4848" + "faa64466" + "5f3d7f34" + "125914e9" + "f4d0ae92" + "3c6f479b" + "95a0f7b5" + "3133"; + + /* Plaintext packet with empty 1-byte header extension and CSRC fields. */ + const char *srtp_1byte_empty_hdrext_cc_ref = + "920f123a" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "bede0000" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_1byte_empty_hdrext_cc_cryptex = + "920f123a" + "decafbad" + "cafebabe" + "7130b6ab" + "fe2ab0e3" + "c0de0000" + "e3d9f64b" + "25c9e74c" + "b4cf8e43" + "fb92e378" + "1c2c0cea" + "b6b3a499" + "a14c"; + + /* Plaintext packet with empty 2-byte header extension and CSRC fields. */ + const char *srtp_2byte_empty_hdrext_cc_ref = + "920f123b" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "10000000" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_2byte_empty_hdrext_cc_cryptex = + "920f123b" + "decafbad" + "cafebabe" + "cbf24c12" + "4330e1c8" + "c2de0000" + "599dd45b" + "c9d687b6" + "03e8b59d" + "771fd38e" + "88b170e0" + "cd31e125" + "eabe"; + + // clang-format on + + struct test_vectors_t vectors[6] = { + { "Plaintext packet with 1-byte header extension", srtp_1bytehdrext_ref, + srtp_1bytehdrext_cryptex }, + { "Plaintext packet with 2-byte header extension", srtp_2bytehdrext_ref, + srtp_2bytehdrext_cryptex }, + { "Plaintext packet with 1-byte header extension and CSRC fields", + srtp_1bytehdrext_cc_ref, srtp_1bytehdrext_cc_cryptex }, + { "Plaintext packet with 2-byte header extension and CSRC fields", + srtp_2bytehdrext_cc_ref, srtp_2bytehdrext_cc_cryptex }, + { "Plaintext packet with empty 1-byte header extension and CSRC fields", + srtp_1byte_empty_hdrext_cc_ref, srtp_1byte_empty_hdrext_cc_cryptex }, + { "Plaintext packet with empty 2-byte header extension and CSRC fields", + srtp_2byte_empty_hdrext_cc_ref, srtp_2byte_empty_hdrext_cc_cryptex }, + }; + + srtp_t srtp_snd, srtp_recv; + size_t len, ref_len, enc_len; + srtp_policy_t policy; + + /* + * create a session with a single stream using the default srtp + * policy and with the SSRC value 0xcafebabe + */ + memset(&policy, 0, sizeof(policy)); + srtp_crypto_policy_set_rtp_default(&policy.rtp); + srtp_crypto_policy_set_rtcp_default(&policy.rtcp); + policy.ssrc.type = ssrc_specific; + policy.ssrc.value = 0xcafebabe; + policy.key = test_key; + policy.window_size = 128; + policy.allow_repeat_tx = 0; + policy.use_cryptex = true; + policy.next = NULL; + + for (size_t i = 0; i < 6; ++i) { + uint8_t packet[1400]; + uint8_t reference[1400]; + uint8_t ciphertext[1400]; + + /* Initialize reference test vectors */ + ref_len = hex_string_to_octet_string(reference, vectors[i].plaintext, + sizeof(reference)) / + 2; + enc_len = hex_string_to_octet_string(ciphertext, vectors[i].ciphertext, + sizeof(ciphertext)) / + 2; + + /* Initialize test packet */ + len = ref_len; + memcpy(packet, reference, len); + printf("%s\n", vectors[i].name); + /* + * protect plaintext, then compare with ciphertext + */ + debug_print(mod_driver, "test vector: %s\n", vectors[i].name); + + CHECK_OK(srtp_create(&srtp_snd, &policy)); + + CHECK_OK(call_srtp_protect(srtp_snd, packet, &len, 0)); + CHECK(len == enc_len); + + debug_print(mod_driver, "ciphertext:\n %s", + octet_string_hex_string(packet, len)); + debug_print(mod_driver, "ciphertext reference:\n %s", + octet_string_hex_string(ciphertext, len)); + + CHECK_BUFFER_EQUAL(packet, ciphertext, len); + + CHECK_OK(srtp_dealloc(srtp_snd)); + + CHECK_OK(srtp_create(&srtp_recv, &policy)); + + /* + * unprotect ciphertext, then compare with plaintext + */ + CHECK_OK(call_srtp_unprotect(srtp_recv, packet, &len)); + CHECK(len == ref_len); + + CHECK_BUFFER_EQUAL(packet, reference, len); + + CHECK_OK(srtp_dealloc(srtp_recv)); + } + + return srtp_err_status_ok; +} + #ifdef GCM /* * srtp_validate_gcm() verifies the correctness of libsrtp by comparing @@ -2872,6 +3166,300 @@ srtp_err_status_t srtp_validate_gcm(void) return srtp_err_status_ok; } + +/* + * srtp_validate_gcm() verifies the correctness of libsrtp by comparing + * an computed packet against the known ciphertext for the plaintext. + */ +srtp_err_status_t srtp_validate_gcm_cryptex(void) +{ + // clang-format off + unsigned char test_key_gcm_cryptex[28] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab + }; + + /* Plaintext packet with 1-byte header extension */ + const char *srtp_1bytehdrext_ref = + "900f1235" + "decafbad" + "cafebabe" + "bede0001" + "51000200" + "abababab" + "abababab" + "abababab" + "abababab"; + + /* GCM Ciphertext packet with 1-byte header extension */ + const char *srtp_1bytehdrext_cryptex_gcm = + "900f1235" + "decafbad" + "cafebabe" + "c0de0001" + "39972dc9" + "572c4d99" + "e8fc355d" + "e743fb2e" + "94f9d8ff" + "54e72f41" + "93bbc5c7" + "4ffab0fa" + "9fa0fbeb"; + + /* Plaintext packet with 2-byte header extension */ + const char *srtp_2bytehdrext_ref = + "900f1236" + "decafbad" + "cafebabe" + "10000001" + "05020002" + "abababab" + "abababab" + "abababab" + "abababab"; + + /* GCM Ciphertext packet with 2-byte header extension */ + const char *srtp_2bytehdrext_cryptex_gcm = + "900f1236" + "decafbad" + "cafebabe" + "c2de0001" + "bb75a4c5" + "45cd1f41" + "3bdb7daa" + "2b1e3263" + "de313667" + "c9632490" + "81b35a65" + "f5cb6c88" + "b394235f"; + + /* Plaintext packet with 1-byte header extension and CSRC fields. */ + const char *srtp_1bytehdrext_cc_ref = + "920f1238" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "bede0001" + "51000200" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_1bytehdrext_cc_cryptex_gcm = + "920f1238" + "decafbad" + "cafebabe" + "63bbccc4" + "a7f695c4" + "c0de0001" + "8ad7c71f" + "ac70a80c" + "92866b4c" + "6ba98546" + "ef913586" + "e95ffaaf" + "fe956885" + "bb0647a8" + "bc094ac8"; + + + /* Plaintext packet with 2-byte header extension and CSRC fields. */ + const char *srtp_2bytehdrext_cc_ref = + "920f1239" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "10000001" + "05020002" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_2bytehdrext_cc_cryptex_gcm = + "920f1239" + "decafbad" + "cafebabe" + "3680524f" + "8d312b00" + "c2de0001" + "c78d1200" + "38422bc1" + "11a7187a" + "18246f98" + "0c059cc6" + "bc9df8b6" + "26394eca" + "344e4b05" + "d80fea83"; + + /* Plaintext packet with empty 1-byte header extension and CSRC fields. */ + const char *srtp_1byte_empty_hdrext_cc_ref = + "920f123a" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "bede0000" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_1byte_empty_hdrext_cc_cryptex_gcm = + "920f123a" + "decafbad" + "cafebabe" + "15b6bb43" + "37906fff" + "c0de0000" + "b7b96453" + "7a2b03ab" + "7ba5389c" + "e9331712" + "6b5d974d" + "f30c6884" + "dcb651c5" + "e120c1da"; + + /* Plaintext packet with empty 2-byte header extension and CSRC fields. */ + const char *srtp_2byte_empty_hdrext_cc_ref = + "920f123b" + "decafbad" + "cafebabe" + "0001e240" + "0000b26e" + "10000000" + "abababab" + "abababab" + "abababab" + "abababab"; + + const char *srtp_2byte_empty_hdrext_cc_cryptex_gcm = + "920f123b" + "decafbad" + "cafebabe" + "dcb38c9e" + "48bf95f4" + "c2de0000" + "61ee432c" + "f9203170" + "76613258" + "d3ce4236" + "c06ac429" + "681ad084" + "13512dc9" + "8b5207d8"; + // clang-format on + + struct test_vectors_t vectors[6] = { + { "Plaintext packet with 1-byte header extension", srtp_1bytehdrext_ref, + srtp_1bytehdrext_cryptex_gcm }, + { "Plaintext packet with 2-byte header extension", srtp_2bytehdrext_ref, + srtp_2bytehdrext_cryptex_gcm }, + { "Plaintext packet with 1-byte header extension and CSRC fields", + srtp_1bytehdrext_cc_ref, srtp_1bytehdrext_cc_cryptex_gcm }, + { "Plaintext packet with 2-byte header extension and CSRC fields", + srtp_2bytehdrext_cc_ref, srtp_2bytehdrext_cc_cryptex_gcm }, + { "Plaintext packet with empty 1-byte header extension and CSRC fields", + srtp_1byte_empty_hdrext_cc_ref, + srtp_1byte_empty_hdrext_cc_cryptex_gcm }, + { "Plaintext packet with empty 2-byte header extension and CSRC fields", + srtp_2byte_empty_hdrext_cc_ref, + srtp_2byte_empty_hdrext_cc_cryptex_gcm }, + }; + + srtp_t srtp_snd, srtp_recv; + size_t len, ref_len, enc_len; + srtp_policy_t policy; + + /* + * create a session with a single stream using the default srtp + * policy and with the SSRC value 0xcafebabe + */ + memset(&policy, 0, sizeof(policy)); + srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp); + srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp); + policy.ssrc.type = ssrc_specific; + policy.ssrc.value = 0xcafebabe; + policy.key = test_key_gcm_cryptex; + policy.window_size = 128; + policy.allow_repeat_tx = 0; + policy.use_cryptex = true; + policy.next = NULL; + + CHECK_OK(srtp_create(&srtp_snd, &policy)); + + for (int i = 0; i < 6; ++i) { + uint8_t packet[1400]; + uint8_t reference[1400]; + uint8_t ciphertext[1400]; + + /* Initialize reference test vectors */ + ref_len = hex_string_to_octet_string(reference, vectors[i].plaintext, + sizeof(reference)) / + 2; + enc_len = hex_string_to_octet_string(ciphertext, vectors[i].ciphertext, + sizeof(ciphertext)) / + 2; + + /* Initialize test packet */ + len = ref_len; + memcpy(packet, reference, len); + printf("%s\n", vectors[i].name); + /* + * protect plaintext, then compare with ciphertext + */ + debug_print(mod_driver, "test vector: %s\n", vectors[i].name); + + const srtp_hdr_t *hdr = (const srtp_hdr_t *)reference; + if (use_srtp_not_in_place_io_api && hdr->cc) { + // the combination of cryptex, cc, GCM & not inplace is not + // supported + CHECK_RETURN(call_srtp_protect(srtp_snd, packet, &len, 0), + srtp_err_status_bad_param); + continue; + } + CHECK_OK(call_srtp_protect(srtp_snd, packet, &len, 0)); + CHECK(len == enc_len); + + debug_print(mod_driver, "ciphertext:\n %s", + octet_string_hex_string(packet, len)); + debug_print(mod_driver, "ciphertext reference:\n %s", + octet_string_hex_string(ciphertext, len)); + + CHECK_BUFFER_EQUAL(packet, ciphertext, len); + + /* + * create a receiver session context comparable to the one created + * above - we need to do this so that the replay checking doesn't + * complain + */ + CHECK_OK(srtp_create(&srtp_recv, &policy)); + + /* + * unprotect ciphertext, then compare with plaintext + */ + CHECK_OK(call_srtp_unprotect(srtp_recv, packet, &len)); + CHECK(len == ref_len); + + CHECK_BUFFER_EQUAL(packet, reference, len); + + CHECK_OK(srtp_dealloc(srtp_recv)); + } + + CHECK_OK(srtp_dealloc(srtp_snd)); + + return srtp_err_status_ok; +} + #endif /* @@ -4837,6 +5425,7 @@ const srtp_policy_t default_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -4867,6 +5456,7 @@ const srtp_policy_t aes_only_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -4897,6 +5487,7 @@ const srtp_policy_t hmac_only_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -4930,6 +5521,7 @@ const srtp_policy_t aes128_gcm_8_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -4962,6 +5554,7 @@ const srtp_policy_t aes128_gcm_8_cauth_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -4994,6 +5587,7 @@ const srtp_policy_t aes256_gcm_8_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -5026,6 +5620,7 @@ const srtp_policy_t aes256_gcm_8_cauth_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; #endif @@ -5057,6 +5652,7 @@ const srtp_policy_t null_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -5125,6 +5721,7 @@ const srtp_policy_t aes_256_hmac_policy = { false, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -5158,6 +5755,7 @@ const srtp_policy_t aes_256_hmac_32_policy = { false, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -5188,6 +5786,7 @@ const srtp_policy_t hmac_only_with_no_master_key = { false, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL }; @@ -5255,6 +5854,7 @@ const srtp_policy_t wildcard_policy = { 0, /* retransmission not allowed */ NULL, /* no encrypted extension headers */ 0, /* list of encrypted extension headers is empty */ + false, /* cryptex */ NULL };