diff --git a/modules/tls_mgm/README b/modules/tls_mgm/README index bce74783b08..79f105b4db5 100644 --- a/modules/tls_mgm/README +++ b/modules/tls_mgm/README @@ -58,6 +58,8 @@ Marius Cristian Eseanu 1.5. Exported Functions 1.5.1. is_peer_verified + 1.5.2. tls_check_to + 1.5.3. tls_check_from 1.6. Exported MI Functions @@ -199,6 +201,8 @@ Marius Cristian Eseanu 1.32. Example of $tls_[peer|my]_[subject|issuer] 1.33. Script with TLS support 1.34. Example of TLS logging + 1.35. Example of tls_check_to + 1.36. Example of tls_check_from Chapter 1. Admin Guide @@ -272,6 +276,44 @@ if (is_peer_verified()) { } ... +1.5.2. tls_check_to + + Check To username against peer certificate CN. + Returns 1 on success, negative on failure. + + This function can be used from REQUEST_ROUTE. + + Example 1.35. tls_check_to usage +... +# Authenticate for REGISTER +if (proto==TLS && is_peer_verified()){ + if (!tls_check_to()){ + xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Processing '$rm' auth TO check failed\n"); + sl_send_reply("403","Forbidden auth ID"); + exit; + } +} +... + +1.5.3. tls_check_from + + Check From username against peer certificate CN. + Returns 1 on success, negative on failure. + + This function can be used from REQUEST_ROUTE. + + Example 1.35. is_peer_verified usage +... +# Authenticate for MESSAGE / INVITE +if (proto==TLS && tls_check_from()){ + if (!tls_check_from()){ + xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Processing '$rm' auth FROM check failed\n"); + sl_send_reply("403","Forbidden auth ID"); + exit; + } +} +... + 1.6. Exported MI Functions 1.6.1. tls_list diff --git a/modules/tls_mgm/doc/tls_mgm_admin.xml b/modules/tls_mgm/doc/tls_mgm_admin.xml index 463b03ce44e..91d082cc721 100644 --- a/modules/tls_mgm/doc/tls_mgm_admin.xml +++ b/modules/tls_mgm/doc/tls_mgm_admin.xml @@ -111,6 +111,60 @@ if (is_peer_verified()) { xlog("L_INFO","request not verified\n"); } ... + + + +
+ + <function moreinfo="none">tls_check_to</function> + + + Check To username against peer certificate CN. + Returns 1 on success, negative on failure. + + + This function can be used from REQUEST_ROUTE. + + + <function>tls_check_to</function> usage + +... +# Authenticate for REGISTER +if (proto==TLS && is_peer_verified()){ + if (!tls_check_to()){ + xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Processing '$rm' auth TO check failed\n"); + sl_send_reply("403","Forbidden auth ID"); + exit; + } +} +... + + +
+
+ + <function moreinfo="none">tls_check_from</function> + + + Check From username against peer certificate CN. + Returns 1 on success, negative on failure. + + + This function can be used from REQUEST_ROUTE. + + + <function>tls_check_from</function> usage + +... +# Authenticate for MESSAGE / INVITE +if (proto==TLS && tls_check_from()){ + if (!tls_check_from()){ + xlog("L_NOTICE","[$pr:$fU@$si:$sp]: Processing '$rm' auth FROM check failed\n"); + sl_send_reply("403","Forbidden auth ID"); + exit; + } +} +...
diff --git a/modules/tls_mgm/tls_mgm.c b/modules/tls_mgm/tls_mgm.c index bd8724ea906..3a9187b9b39 100644 --- a/modules/tls_mgm/tls_mgm.c +++ b/modules/tls_mgm/tls_mgm.c @@ -34,6 +34,7 @@ #include "tls_select.h" #include "tls.h" #include "api.h" +#include "../../parser/parse_from.h" #define DB_CAP DB_CAP_QUERY | DB_CAP_UPDATE #define len(s) s == NULL?0:strlen(s) @@ -77,6 +78,11 @@ int tls_db_enabled = 0; /* definition of exported functions */ static int is_peer_verified(struct sip_msg*, char*, char*); +static int tls_check_to(struct sip_msg*, char*, char*); +static int tls_check_from(struct sip_msg*, char*, char*); +#define ERR_USERNOTFOUND -4 /* No found username error */ +#define ERR_SPOOFEDUSER -9 /* Spoofed User Error */ +#define ERR_NOMATCH -10 /* No match Error */ static param_export_t params[] = { { "client_domain_avp", STR_PARAM, &tls_domain_avp }, @@ -119,6 +125,10 @@ static param_export_t params[] = { static cmd_export_t cmds[] = { {"is_peer_verified", (cmd_function)is_peer_verified, 0, 0, 0, REQUEST_ROUTE}, + {"tls_check_to", (cmd_function)tls_check_to, 0, 0, 0, + REQUEST_ROUTE}, + {"tls_check_from", (cmd_function)tls_check_from, 0, 0, 0, + REQUEST_ROUTE}, {"load_tls_mgm", (cmd_function)load_tls_mgm, 0, 0, 0, 0}, {0,0,0,0,0,0} }; @@ -1468,6 +1478,118 @@ static int is_peer_verified(struct sip_msg* msg, char* foo, char* foo2) return -1; } +#define USERNAME_BUFF_SIZE 256 + +/* + * Check if the provided username matches certificate CN. + */ +static int tls_check_username(struct sip_msg* _m, const str *usr) { + str cn = {0,0}; + char cn_buff[USERNAME_BUFF_SIZE]; + + /* Get CN from the peer certificate */ + if (tlsops_get_peer_cn(_m, &cn, cn_buff, USERNAME_BUFF_SIZE) != 0) { + LM_ERR("Could not extract CN\n"); + return -1; + } + + /* Check URI match to the CN match */ + if (usr->len == cn.len) { + if (!strncasecmp(usr->s, cn.s, (size_t)usr->len)) { + LM_DBG("Digest username and URI username match\n"); + return 1; + + } else { + LM_INFO("Spoofed user '%.*s' should be '%.*s' as in CN\n", + usr->len, usr->s, + cn.len, cn.s); + return ERR_SPOOFEDUSER; + } + } + + LM_INFO("Digest username and URI username do NOT match, '%.*s' should be '%.*s' as in CN\n", + usr->len, usr->s, + cn.len, cn.s); + return ERR_NOMATCH; +} + +/* + * Check if the provided uri matches peer CN. + * user@hostname is taken from the URI. + */ +static int tls_check_username_uri(struct sip_msg* _m, struct sip_uri *_uri) { + str usr = {0,0}; + char usr_buff[USERNAME_BUFF_SIZE]; + + if (_uri == NULL) { + LM_ERR("Bad parameter\n"); + return -1; + } + + /* Parse To/From URI */ + /* Make sure that the URI contains username */ + if (_uri->user.len == 0 || _uri->host.len == 0) { + LM_ERR("Username not found in URI\n"); + return ERR_USERNOTFOUND; + } + + /* make CN like identifier */ + if ((_uri->user.len + _uri->host.len + 4) >= USERNAME_BUFF_SIZE){ + LM_ERR("Username buffer too short\n"); + return -1; + } + + snprintf(usr_buff, USERNAME_BUFF_SIZE, "%.*s@%.*s", + _uri->user.len, _uri->user.s, + _uri->host.len, _uri->host.s); + usr.s = usr_buff; + usr.len = _uri->user.len + _uri->host.len + 1; + + return tls_check_username(_m, &usr); +} + +/* + * Check username part in To header field matches peer certificate Common Name (CN). + */ +static int tls_check_to(struct sip_msg* _m, char* _s1, char* _s2) +{ + if (_m->rcv.proto != PROTO_TLS) { + LM_ERR("proto != TLS --> peer can't be verified, return -1\n"); + return -1; + } + if (!_m->to && ((parse_headers(_m, HDR_TO_F, 0) == -1) || (!_m->to))) { + LM_ERR("Error while parsing To header field\n"); + return -1; + } + if(parse_to_uri(_m)==NULL) { + LM_ERR("Error while parsing To header URI\n"); + return -1; + } + + return tls_check_username_uri(_m, &get_to(_m)->parsed_uri); +} + +/* + * Check username part in From header field matches peer certificate Common Name (CN). + */ +static int tls_check_from(struct sip_msg* _m, char* _s1, char* _s2) +{ + if (_m->rcv.proto != PROTO_TLS) { + LM_ERR("proto != TLS --> peer can't be verified, return -1\n"); + return -1; + } + if (parse_from_header(_m) < 0) { + LM_ERR("Error while parsing From header field\n"); + return -1; + } + if(parse_from_uri(_m)==NULL) { + LM_ERR("Error while parsing From header URI\n"); + return -1; + } + + return tls_check_username_uri(_m, &get_from(_m)->parsed_uri); +} + static int tls_get_handshake_timeout(void) { return tls_handshake_timeout; diff --git a/modules/tls_mgm/tls_select.c b/modules/tls_mgm/tls_select.c index 6f918fd7b50..3e94d195428 100644 --- a/modules/tls_mgm/tls_select.c +++ b/modules/tls_mgm/tls_select.c @@ -414,6 +414,55 @@ int tlsops_sn(struct sip_msg *msg, pv_param_t *param, return 0; } +int tlsops_get_peer_cn(struct sip_msg *msg, str *res, char * buff, size_t buff_size) +{ + X509* cert; + struct tcp_connection* c; + X509_NAME* name; + X509_NAME_ENTRY* e; + ASN1_STRING* asn1; + int index; + str text; + + res->s = 0; + res->len = 0; + text.s = 0; + if (get_cert(&cert, &c, msg, 0) < 0) { + return -1; + } + + name = X509_get_subject_name(cert); + if (!name) { + LM_ERR("cannot extract subject or issuer name from peer" + " certificate\n"); + goto err; + } + + index = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + e = X509_NAME_get_entry(name, index); + asn1 = X509_NAME_ENTRY_get_data(e); + text.len = ASN1_STRING_to_UTF8((unsigned char**)(void*)&text.s, asn1); + if (text.len < 0 || text.len >= buff_size) { + LM_ERR("failed to convert ASN1 string\n"); + goto err; + } + memcpy(buff, text.s, text.len >= buff_size ? buff_size : (size_t)text.len); + res->s = buff; + res->len = text.len; + + OPENSSL_free(text.s); + + X509_free(cert); + tcp_conn_release(c,0); + return 0; + +err: + if (text.s) OPENSSL_free(text.s); + X509_free(cert); + tcp_conn_release(c,0); + return -2; +} + int tlsops_comp(struct sip_msg *msg, pv_param_t *param, pv_value_t *res) { diff --git a/modules/tls_mgm/tls_select.h b/modules/tls_mgm/tls_select.h index 290581dbaea..c79be12278f 100644 --- a/modules/tls_mgm/tls_select.h +++ b/modules/tls_mgm/tls_select.h @@ -97,6 +97,9 @@ int tlsops_validity(struct sip_msg *msg, pv_param_t *param, int tlsops_sn(struct sip_msg *msg, pv_param_t *param, pv_value_t *res); +int tlsops_get_peer_cn(struct sip_msg *msg, str *res, char * buff, + size_t buff_size); + int tlsops_comp(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);