diff options
Diffstat (limited to 'lib/ext/srtp.c')
-rw-r--r-- | lib/ext/srtp.c | 672 |
1 files changed, 672 insertions, 0 deletions
diff --git a/lib/ext/srtp.c b/lib/ext/srtp.c new file mode 100644 index 0000000..b2e36b3 --- /dev/null +++ b/lib/ext/srtp.c @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2012 Free Software Foundation + * + * Author: Martin Storsjo + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "auth.h" +#include "errors.h" +#include "num.h" +#include <ext/srtp.h> + +static int _gnutls_srtp_recv_params(gnutls_session_t session, + const uint8_t * data, + size_t data_size); +static int _gnutls_srtp_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata); + +static int _gnutls_srtp_unpack(gnutls_buffer_st * ps, + gnutls_ext_priv_data_t * _priv); +static int _gnutls_srtp_pack(gnutls_ext_priv_data_t _priv, + gnutls_buffer_st * ps); +static void _gnutls_srtp_deinit_data(gnutls_ext_priv_data_t priv); + + +const hello_ext_entry_st ext_mod_srtp = { + .name = "SRTP", + .tls_id = 14, + .gid = GNUTLS_EXTENSION_SRTP, + .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_DTLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | + GNUTLS_EXT_FLAG_EE | GNUTLS_EXT_FLAG_TLS12_SERVER_HELLO, + .client_parse_point = GNUTLS_EXT_APPLICATION, + .server_parse_point = GNUTLS_EXT_APPLICATION, + .recv_func = _gnutls_srtp_recv_params, + .send_func = _gnutls_srtp_send_params, + .pack_func = _gnutls_srtp_pack, + .unpack_func = _gnutls_srtp_unpack, + .deinit_func = _gnutls_srtp_deinit_data, + .cannot_be_overriden = 1 +}; + +typedef struct { + const char *name; + gnutls_srtp_profile_t id; + unsigned int key_length; + unsigned int salt_length; +} srtp_profile_st; + +static const srtp_profile_st profile_names[] = { + { + "SRTP_AES128_CM_HMAC_SHA1_80", + GNUTLS_SRTP_AES128_CM_HMAC_SHA1_80, + 16, 14}, + { + "SRTP_AES128_CM_HMAC_SHA1_32", + GNUTLS_SRTP_AES128_CM_HMAC_SHA1_32, + 16, 14}, + { + "SRTP_NULL_HMAC_SHA1_80", + GNUTLS_SRTP_NULL_HMAC_SHA1_80, + 16, 14}, + { + "SRTP_NULL_SHA1_32", + GNUTLS_SRTP_NULL_HMAC_SHA1_32, + 16, 14}, + { + NULL, + 0, 0, 0} +}; + +static const srtp_profile_st *get_profile(gnutls_srtp_profile_t profile) +{ + const srtp_profile_st *p = profile_names; + while (p->name != NULL) { + if (p->id == profile) + return p; + p++; + } + return NULL; +} + +static gnutls_srtp_profile_t find_profile(const char *str, const char *end) +{ + const srtp_profile_st *prof = profile_names; + unsigned int len; + if (end != NULL) { + len = end - str; + } else { + len = strlen(str); + } + + while (prof->name != NULL) { + if (strlen(prof->name) == len + && !strncmp(str, prof->name, len)) { + return prof->id; + } + prof++; + } + return 0; +} + +/** + * gnutls_srtp_get_profile_id + * @name: The name of the profile to look up + * @profile: Will hold the profile id + * + * This function allows you to look up a profile based on a string. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + * + * Since 3.1.4 + **/ +int gnutls_srtp_get_profile_id(const char *name, + gnutls_srtp_profile_t * profile) +{ + *profile = find_profile(name, NULL); + if (*profile == 0) { + return GNUTLS_E_ILLEGAL_PARAMETER; + } + return 0; +} + +#define MAX_PROFILES_IN_SRTP_EXTENSION 256 + +/** + * gnutls_srtp_get_profile_name + * @profile: The profile to look up a string for + * + * This function allows you to get the corresponding name for a + * SRTP protection profile. + * + * Returns: On success, the name of a SRTP profile as a string, + * otherwise NULL. + * + * Since 3.1.4 + **/ +const char *gnutls_srtp_get_profile_name(gnutls_srtp_profile_t profile) +{ + const srtp_profile_st *p = get_profile(profile); + + if (p != NULL) + return p->name; + + return NULL; +} + +static int +_gnutls_srtp_recv_params(gnutls_session_t session, + const uint8_t * data, size_t data_size) +{ + unsigned int i; + int ret; + const uint8_t *p = data; + size_t len; + srtp_ext_st *priv; + gnutls_ext_priv_data_t epriv; + uint16_t profile; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) + return 0; + + priv = epriv; + + DECR_LENGTH_RET(data_size, 2, 0); + len = _gnutls_read_uint16(p); + p += 2; + + if (len + 1 > data_size) + return + gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (session->security_parameters.entity == GNUTLS_SERVER) { + if (len > MAX_PROFILES_IN_SRTP_EXTENSION * 2) + return 0; + } else { + if (len != 2) + return + gnutls_assert_val + (GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } + + priv->selected_profile = 0; + + while (len > 0) { + DECR_LEN(data_size, 2); + profile = _gnutls_read_uint16(p); + + for (i = 0; + i < priv->profiles_size + && priv->selected_profile == 0; i++) { + if (priv->profiles[i] == profile) { + priv->selected_profile = profile; + break; + } + } + p += 2; + len -= 2; + } + + DECR_LEN(data_size, 1); + priv->mki_size = *p; + p++; + + if (priv->mki_size > 0) { + DECR_LEN(data_size, priv->mki_size); + memcpy(priv->mki, p, priv->mki_size); + priv->mki_received = 1; + } + + return 0; +} + +static int +_gnutls_srtp_send_params(gnutls_session_t session, + gnutls_buffer_st * extdata) +{ + unsigned i; + int total_size = 0, ret; + srtp_ext_st *priv; + gnutls_ext_priv_data_t epriv; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) + return 0; + + priv = epriv; + + if (priv->profiles_size == 0) + return 0; + + if (session->security_parameters.entity == GNUTLS_SERVER) { + /* Don't send anything if no matching profile was found */ + if (priv->selected_profile == 0) + return 0; + + ret = _gnutls_buffer_append_prefix(extdata, 16, 2); + if (ret < 0) + return gnutls_assert_val(ret); + ret = + _gnutls_buffer_append_prefix(extdata, 16, + priv->selected_profile); + if (ret < 0) + return gnutls_assert_val(ret); + total_size = 4; + } else { + ret = + _gnutls_buffer_append_prefix(extdata, 16, + 2 * priv->profiles_size); + if (ret < 0) + return gnutls_assert_val(ret); + + for (i = 0; i < priv->profiles_size; i++) { + ret = + _gnutls_buffer_append_prefix(extdata, 16, + priv-> + profiles[i]); + if (ret < 0) + return gnutls_assert_val(ret); + } + total_size = 2 + 2 * priv->profiles_size; + } + + /* use_mki */ + ret = + _gnutls_buffer_append_data_prefix(extdata, 8, priv->mki, + priv->mki_size); + if (ret < 0) + return gnutls_assert_val(ret); + total_size += 1 + priv->mki_size; + + return total_size; +} + +/** + * gnutls_srtp_get_selected_profile: + * @session: is a #gnutls_session_t type. + * @profile: will hold the profile + * + * This function allows you to get the negotiated SRTP profile. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + * + * Since 3.1.4 + **/ +int +gnutls_srtp_get_selected_profile(gnutls_session_t session, + gnutls_srtp_profile_t * profile) +{ + srtp_ext_st *priv; + int ret; + gnutls_ext_priv_data_t epriv; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) { + gnutls_assert(); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + priv = epriv; + + if (priv->selected_profile == 0) { + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + *profile = priv->selected_profile; + + return 0; +} + +/** + * gnutls_srtp_get_mki: + * @session: is a #gnutls_session_t type. + * @mki: will hold the MKI + * + * This function exports the negotiated Master Key Identifier, + * received by the peer if any. The returned value in @mki should be + * treated as constant and valid only during the session's lifetime. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + * + * Since 3.1.4 + **/ +int gnutls_srtp_get_mki(gnutls_session_t session, gnutls_datum_t * mki) +{ + srtp_ext_st *priv; + int ret; + gnutls_ext_priv_data_t epriv; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) + return + gnutls_assert_val + (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + priv = epriv; + + if (priv->mki_received == 0) + return + gnutls_assert_val + (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + mki->data = priv->mki; + mki->size = priv->mki_size; + + return 0; +} + +/** + * gnutls_srtp_set_mki: + * @session: is a #gnutls_session_t type. + * @mki: holds the MKI + * + * This function sets the Master Key Identifier, to be + * used by this session (if any). + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + * + * Since 3.1.4 + **/ +int +gnutls_srtp_set_mki(gnutls_session_t session, const gnutls_datum_t * mki) +{ + int ret; + srtp_ext_st *priv; + gnutls_ext_priv_data_t epriv; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) { + priv = gnutls_calloc(1, sizeof(*priv)); + if (priv == NULL) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + epriv = priv; + _gnutls_hello_ext_set_priv(session, + GNUTLS_EXTENSION_SRTP, epriv); + } else + priv = epriv; + + if (mki->size > 0 && mki->size <= sizeof(priv->mki)) { + priv->mki_size = mki->size; + memcpy(priv->mki, mki->data, mki->size); + } else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + return 0; +} + +/** + * gnutls_srtp_set_profile: + * @session: is a #gnutls_session_t type. + * @profile: is the profile id to add. + * + * This function is to be used by both clients and servers, to declare + * what SRTP profiles they support, to negotiate with the peer. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, + * otherwise a negative error code is returned. + * + * Since 3.1.4 + **/ +int +gnutls_srtp_set_profile(gnutls_session_t session, + gnutls_srtp_profile_t profile) +{ + int ret; + srtp_ext_st *priv; + gnutls_ext_priv_data_t epriv; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) { + priv = gnutls_calloc(1, sizeof(*priv)); + if (priv == NULL) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + epriv = priv; + _gnutls_hello_ext_set_priv(session, + GNUTLS_EXTENSION_SRTP, epriv); + } else + priv = epriv; + + if (priv->profiles_size < MAX_SRTP_PROFILES) + priv->profiles_size++; + priv->profiles[priv->profiles_size - 1] = profile; + + return 0; +} + +/** + * gnutls_srtp_set_profile_direct: + * @session: is a #gnutls_session_t type. + * @profiles: is a string that contains the supported SRTP profiles, + * separated by colons. + * @err_pos: In case of an error this will have the position in the string the error occurred, may be NULL. + * + * This function is to be used by both clients and servers, to declare + * what SRTP profiles they support, to negotiate with the peer. + * + * Returns: On syntax error %GNUTLS_E_INVALID_REQUEST is returned, + * %GNUTLS_E_SUCCESS on success, or an error code. + * + * Since 3.1.4 + **/ +int +gnutls_srtp_set_profile_direct(gnutls_session_t session, + const char *profiles, const char **err_pos) +{ + int ret; + srtp_ext_st *priv; + gnutls_ext_priv_data_t epriv; + int set = 0; + const char *col; + gnutls_srtp_profile_t id; + + ret = + _gnutls_hello_ext_get_priv(session, GNUTLS_EXTENSION_SRTP, + &epriv); + if (ret < 0) { + set = 1; + priv = gnutls_calloc(1, sizeof(*priv)); + if (priv == NULL) { + if (err_pos != NULL) + *err_pos = profiles; + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + epriv = priv; + } else + priv = epriv; + + do { + col = strchr(profiles, ':'); + id = find_profile(profiles, col); + if (id == 0) { + if (set != 0) + gnutls_free(priv); + if (err_pos != NULL) + *err_pos = profiles; + return GNUTLS_E_INVALID_REQUEST; + } + + if (priv->profiles_size < MAX_SRTP_PROFILES) { + priv->profiles_size++; + } + priv->profiles[priv->profiles_size - 1] = id; + profiles = col + 1; + } while (col != NULL); + + if (set != 0) + _gnutls_hello_ext_set_priv(session, + GNUTLS_EXTENSION_SRTP, epriv); + + return 0; +} + +/** + * gnutls_srtp_get_keys: + * @session: is a #gnutls_session_t type. + * @key_material: Space to hold the generated key material + * @key_material_size: The maximum size of the key material + * @client_key: The master client write key, pointing inside the key material + * @server_key: The master server write key, pointing inside the key material + * @client_salt: The master client write salt, pointing inside the key material + * @server_salt: The master server write salt, pointing inside the key material + * + * This is a helper function to generate the keying material for SRTP. + * It requires the space of the key material to be pre-allocated (should be at least + * 2x the maximum key size and salt size). The @client_key, @client_salt, @server_key + * and @server_salt are convenience datums that point inside the key material. They may + * be %NULL. + * + * Returns: On success the size of the key material is returned, + * otherwise, %GNUTLS_E_SHORT_MEMORY_BUFFER if the buffer given is not + * sufficient, or a negative error code. + * + * Since 3.1.4 + **/ +int +gnutls_srtp_get_keys(gnutls_session_t session, + void *key_material, + unsigned int key_material_size, + gnutls_datum_t * client_key, + gnutls_datum_t * client_salt, + gnutls_datum_t * server_key, + gnutls_datum_t * server_salt) +{ + int ret; + const srtp_profile_st *p; + gnutls_srtp_profile_t profile; + unsigned int msize; + uint8_t *km = key_material; + + ret = gnutls_srtp_get_selected_profile(session, &profile); + if (ret < 0) + return gnutls_assert_val(ret); + + p = get_profile(profile); + if (p == NULL) + return gnutls_assert_val(GNUTLS_E_UNKNOWN_ALGORITHM); + + msize = 2 * (p->key_length + p->salt_length); + if (msize > key_material_size) + return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + + if (msize == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + ret = + gnutls_prf(session, sizeof("EXTRACTOR-dtls_srtp") - 1, + "EXTRACTOR-dtls_srtp", 0, 0, NULL, msize, + key_material); + if (ret < 0) + return gnutls_assert_val(ret); + + if (client_key) { + client_key->data = km; + client_key->size = p->key_length; + } + + if (server_key) { + server_key->data = km + p->key_length; + server_key->size = p->key_length; + } + + if (client_salt) { + client_salt->data = km + 2 * p->key_length; + client_salt->size = p->salt_length; + } + + if (server_salt) { + server_salt->data = + km + 2 * p->key_length + p->salt_length; + server_salt->size = p->salt_length; + } + + return msize; +} + +static void _gnutls_srtp_deinit_data(gnutls_ext_priv_data_t priv) +{ + gnutls_free(priv); +} + +static int +_gnutls_srtp_pack(gnutls_ext_priv_data_t epriv, gnutls_buffer_st * ps) +{ + srtp_ext_st *priv = epriv; + unsigned int i; + int ret; + + BUFFER_APPEND_NUM(ps, priv->profiles_size); + for (i = 0; i < priv->profiles_size; i++) { + BUFFER_APPEND_NUM(ps, priv->profiles[i]); + } + + BUFFER_APPEND_NUM(ps, priv->mki_received); + if (priv->mki_received) { + BUFFER_APPEND_NUM(ps, priv->selected_profile); + BUFFER_APPEND_PFX4(ps, priv->mki, priv->mki_size); + } + return 0; +} + +static int +_gnutls_srtp_unpack(gnutls_buffer_st * ps, gnutls_ext_priv_data_t * _priv) +{ + srtp_ext_st *priv; + unsigned int i; + int ret; + gnutls_ext_priv_data_t epriv; + + priv = gnutls_calloc(1, sizeof(*priv)); + if (priv == NULL) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + + BUFFER_POP_NUM(ps, priv->profiles_size); + for (i = 0; i < priv->profiles_size; i++) { + BUFFER_POP_NUM(ps, priv->profiles[i]); + } + BUFFER_POP_NUM(ps, priv->selected_profile); + + BUFFER_POP_NUM(ps, priv->mki_received); + if (priv->mki_received) { + BUFFER_POP_NUM(ps, priv->mki_size); + BUFFER_POP(ps, priv->mki, priv->mki_size); + } + + epriv = priv; + *_priv = epriv; + + return 0; + + error: + gnutls_free(priv); + return ret; +} |