mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
473 lines
16 KiB
473 lines
16 KiB
/*
|
|
* $Id$
|
|
*
|
|
* Nonce related functions
|
|
*
|
|
* Copyright (C) 2001-2003 FhG Fokus
|
|
*
|
|
* This file is part of ser, a free SIP server.
|
|
*
|
|
* ser is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version
|
|
*
|
|
* For a license to use the ser software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* ser 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
/*
|
|
* History:
|
|
* --------
|
|
* ...
|
|
* 2007-10-19 auth extra checks: longer nonces that include selected message
|
|
* parts to protect against various reply attacks without keeping
|
|
* state (andrei)
|
|
* 2008-07-01 switched to base64 for nonces; check staleness in check_nonce
|
|
* (andrei)
|
|
* 2008-07-04 nonce-count support (andrei)
|
|
*/
|
|
|
|
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include "../../compiler_opt.h"
|
|
#include "../../md5global.h"
|
|
#include "../../md5.h"
|
|
#include "../../dprint.h"
|
|
#include "../../ut.h"
|
|
#include "../../parser/msg_parser.h"
|
|
#include "../../parser/parse_from.h"
|
|
#include "../../ip_addr.h"
|
|
#include "nonce.h"
|
|
#include "../../globals.h"
|
|
#include "../../ser_time.h"
|
|
#include <assert.h>
|
|
#ifdef USE_NC
|
|
#include "nc.h"
|
|
#endif
|
|
#ifdef USE_OT_NONCE
|
|
#include "ot_nonce.h"
|
|
#endif
|
|
|
|
|
|
int auth_checks_reg = 0;
|
|
int auth_checks_ood = 0;
|
|
int auth_checks_ind = 0;
|
|
|
|
/* maximum time drift accepted for the nonce creation time
|
|
* (e.g. nonce generated by another proxy in the same cluster with the
|
|
* clock slightly in the future)
|
|
*/
|
|
unsigned int nonce_auth_max_drift = 3; /* in s */
|
|
|
|
/** Select extra check configuration based on request type.
|
|
* This function determines which configuration variable for
|
|
* extra authentication checks is to be used based on the
|
|
* type of the request. It returns the value of auth_checks_reg
|
|
* for REGISTER requests, the value auth_checks_ind for requests
|
|
* that contain valid To tag and the value of auth_checks_ood
|
|
* otherwise.
|
|
*/
|
|
int get_auth_checks(struct sip_msg* msg)
|
|
{
|
|
str tag;
|
|
|
|
if (msg == NULL) return 0;
|
|
|
|
if (msg->REQ_METHOD == METHOD_REGISTER) {
|
|
return auth_checks_reg;
|
|
}
|
|
|
|
if (!msg->to && parse_headers(msg, HDR_TO_F, 0) == -1) {
|
|
DBG("auth: Error while parsing To header field\n");
|
|
return auth_checks_ood;
|
|
}
|
|
if (msg->to) {
|
|
tag = get_to(msg)->tag_value;
|
|
if (tag.s && tag.len > 0) return auth_checks_ind;
|
|
}
|
|
return auth_checks_ood;
|
|
}
|
|
|
|
|
|
|
|
/* takes a pre-filled bin_nonce union (see BIN_NONCE_PREPARE), fills the
|
|
* MD5s and returns the length of the binary nonce (cannot return error).
|
|
* See calc_nonce below for more details.*/
|
|
inline static int calc_bin_nonce_md5(union bin_nonce* b_nonce, int cfg,
|
|
str* secret1, str* secret2,
|
|
struct sip_msg* msg)
|
|
{
|
|
MD5_CTX ctx;
|
|
|
|
str* s;
|
|
int len;
|
|
|
|
MD5Init(&ctx);
|
|
|
|
MD5Update(&ctx, &b_nonce->raw[0], 4 + 4);
|
|
if (cfg && msg){
|
|
/* auth extra checks => 2 md5s */
|
|
len = 4 + 4 + 16 + 16;
|
|
#if defined USE_NC || defined USE_OT_NONCE
|
|
if (b_nonce->n.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)){
|
|
/* if extra auth checks enabled, nid & pf are after the 2nd md5 */
|
|
MD5Update(&ctx, (unsigned char*)&b_nonce->n.nid_i,
|
|
nonce_nid_extra_size);
|
|
len+=nonce_nid_extra_size;
|
|
}
|
|
#endif /* USE_NC || USE_OT_NONCE */
|
|
MD5Update(&ctx, secret1->s, secret1->len);
|
|
MD5Final(&b_nonce->n.md5_1[0], &ctx);
|
|
/* second MD5(auth_extra_checks) */
|
|
MD5Init(&ctx);
|
|
if (cfg & AUTH_CHECK_FULL_URI) {
|
|
s = GET_RURI(msg);
|
|
MD5Update(&ctx, s->s, s->len);
|
|
}
|
|
if ((cfg & AUTH_CHECK_CALLID) &&
|
|
!(parse_headers(msg, HDR_CALLID_F, 0) < 0 || msg->callid == 0)) {
|
|
MD5Update(&ctx, msg->callid->body.s, msg->callid->body.len);
|
|
}
|
|
if ((cfg & AUTH_CHECK_FROMTAG) &&
|
|
!(parse_from_header(msg) < 0 )) {
|
|
MD5Update(&ctx, get_from(msg)->tag_value.s,
|
|
get_from(msg)->tag_value.len);
|
|
}
|
|
if (cfg & AUTH_CHECK_SRC_IP) {
|
|
MD5Update(&ctx, msg->rcv.src_ip.u.addr, msg->rcv.src_ip.len);
|
|
}
|
|
MD5Update(&ctx, secret2->s, secret2->len);
|
|
MD5Final(&b_nonce->n.md5_2[0], &ctx);
|
|
}else{
|
|
/* no extra checks => only one md5 */
|
|
len = 4 + 4 + 16;
|
|
#if defined USE_NC || USE_OT_NONCE
|
|
if (b_nonce->n_small.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)){
|
|
/* if extra auth checks are not enabled, nid & pf are after the
|
|
* 1st md5 */
|
|
MD5Update(&ctx, (unsigned char*)&b_nonce->n_small.nid_i,
|
|
nonce_nid_extra_size);
|
|
len+=nonce_nid_extra_size;
|
|
}
|
|
#endif /* USE_NC || USE_OT_NONCE*/
|
|
MD5Update(&ctx, secret1->s, secret1->len);
|
|
MD5Final(&b_nonce->n.md5_1[0], &ctx);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
|
|
/** Calculates the nonce string for RFC2617 digest authentication.
|
|
* This function creates the nonce string as it will be sent to the
|
|
* user agent in digest challenge. The format of the nonce string
|
|
* depends on the value of three module parameters, auth_checks_register,
|
|
* auth_checks_no_dlg, and auth_checks_in_dlg. These module parameters
|
|
* control the amount of information from the SIP requst that will be
|
|
* stored in the nonce string for verification purposes.
|
|
*
|
|
* If all three parameters contain zero then the nonce string consists
|
|
* of time in seconds since 1.1. 1970 and a secret phrase:
|
|
* <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret)
|
|
* If any of the parameters is not zero (some optional checks are enabled
|
|
* then the nonce string will also contain MD5 hash of selected parts
|
|
* of the SIP request:
|
|
* <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret1) MD5(<extra_checks>, secret2)
|
|
* @param nonce Pointer to a buffer of *nonce_len. It must have enough
|
|
* space to hold the nonce. MAX_NONCE_LEN should be always
|
|
* safe.
|
|
* @param nonce_len A value/result parameter. Initially it contains the
|
|
* nonce buffer length. If the length is too small, it
|
|
* will be set to the needed length and the function will
|
|
* return error immediately. After a succesfull call it will
|
|
* contain the size of nonce written into the buffer,
|
|
* without the terminating 0.
|
|
* @param cfg This is the value of one of the tree module parameters that
|
|
* control which optional checks are enabled/disabled and which
|
|
* parts of the message will be included in the nonce string.
|
|
* @param since Time when nonce was created, i.e. nonce is valid since <valid_since> up to <expires>
|
|
* @param expires Time in seconds after which the nonce will be considered
|
|
* stale.
|
|
* @param n_id Nounce count and/or one-time nonce index value
|
|
* (32 bit counter)
|
|
* @param pf First 2 bits are flags, the rest is the index pool number
|
|
* used if nonce counts or one-time nonces are enabled.
|
|
* The possible flags values are: NF_VALID_NC_ID which means
|
|
* the nonce-count support is enabled and NF_VALID_OT_ID
|
|
* which means the one-time nonces support is enabled.
|
|
* The pool number can be obtained by and-ing with
|
|
* NF_POOL_NO_MASK
|
|
* @param secret1 A secret used for the nonce expires integrity check:
|
|
* MD5(<expire_time>, <valid_since>, secret1).
|
|
* @param secret2 A secret used for integrity check of the message parts
|
|
* selected by auth_extra_checks (if any):
|
|
* MD5(<msg_parts(auth_extra_checks)>, secret2).
|
|
* @param msg The message for which the nonce is computed. If
|
|
* auth_extra_checks is set, the MD5 of some fields of the
|
|
* message will be included in the generated nonce.
|
|
* @return 0 on success and -1 on error
|
|
*/
|
|
int calc_nonce(char* nonce, int *nonce_len, int cfg, int since, int expires,
|
|
#if defined USE_NC || defined USE_OT_NONCE
|
|
unsigned int n_id, unsigned char pf,
|
|
#endif /* USE_NC || USE_OT_NONCE */
|
|
str* secret1, str* secret2,
|
|
struct sip_msg* msg)
|
|
{
|
|
union bin_nonce b_nonce;
|
|
int len;
|
|
if (unlikely(*nonce_len < MAX_NONCE_LEN)) {
|
|
len=get_nonce_len(cfg, pf & NF_VALID_NC_ID);
|
|
if (unlikely(*nonce_len<len)){
|
|
*nonce_len=len;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
BIN_NONCE_PREPARE(&b_nonce, expires, since, n_id, pf, cfg, msg);
|
|
len=calc_bin_nonce_md5(&b_nonce, cfg, secret1, secret2, msg);
|
|
*nonce_len=base64_enc(&b_nonce.raw[0], len,
|
|
(unsigned char*)nonce, *nonce_len);
|
|
assert(*nonce_len>=0); /*FIXME*/
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/** Returns the expire time of the nonce string.
|
|
* This function returns the absolute expire time
|
|
* extracted from the nonce string in the parameter.
|
|
* @param bn is a valid pointer to a union bin_nonce (decoded nonce)
|
|
* @return Absolute time when the nonce string expires.
|
|
*/
|
|
|
|
#define get_bin_nonce_expire(bn) ((time_t)ntohl((bn)->n.expire))
|
|
|
|
/** Returns the valid_since time of the nonce string.
|
|
* This function returns the absolute time
|
|
* extracted from the nonce string in the parameter.
|
|
* @param bn is a valid pointer to a union bin_nonce (decoded nonce)
|
|
* @return Absolute time when the nonce string was created.
|
|
*/
|
|
#define get_bin_nonce_since(bn) ((time_t)ntohl((bn)->n.since))
|
|
|
|
|
|
|
|
/** Checks if nonce is stale.
|
|
* This function checks if a nonce given to it in the parameter is stale.
|
|
* A nonce is stale if the expire time stored in the nonce is in the past.
|
|
* @param b_nonce a pointer to a union bin_nonce to be checked.
|
|
* @return 1 the nonce is stale, 0 the nonce is not stale.
|
|
*/
|
|
#define is_bin_nonce_stale(b_nonce, t) (get_bin_nonce_expire(b_nonce) < (t))
|
|
|
|
static inline int l8hex2int(char* _s, unsigned int *_r)
|
|
{
|
|
unsigned int i, res = 0;
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
res *= 16;
|
|
if ((_s[i] >= '0') && (_s[i] <= '9')) {
|
|
res += _s[i] - '0';
|
|
} else if ((_s[i] >= 'a') && (_s[i] <= 'f')) {
|
|
res += _s[i] - 'a' + 10;
|
|
} else if ((_s[i] >= 'A') && (_s[i] <= 'F')) {
|
|
res += _s[i] - 'A' + 10;
|
|
} else return -1;
|
|
}
|
|
|
|
*_r = res;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** Check whether the nonce returned by UA is valid.
|
|
* This function checks whether the nonce string returned by UA
|
|
* in digest response is valid. The function checks if the nonce
|
|
* string hasn't expired, it verifies the secret stored in the nonce
|
|
* string with the secret configured on the server. If any of the
|
|
* optional extra integrity checks are enabled then it also verifies
|
|
* whether the corresponding parts in the new SIP requests are same.
|
|
* @param nonce A nonce string to be verified.
|
|
* @param secret1 A secret used for the nonce expires integrity check:
|
|
* MD5(<expire_time>,, secret1).
|
|
* @param secret2 A secret used for integrity check of the message parts
|
|
* selected by auth_extra_checks (if any):
|
|
* MD5(<msg_parts(auth_extra_checks)>, secret2).
|
|
* @param msg The message which contains the nonce being verified.
|
|
* @return 0 - success (the nonce was not tampered with and if
|
|
* auth_extra_checks are enabled - the selected message fields
|
|
* have not changes from the time the nonce was generated)
|
|
* -1 - invalid nonce
|
|
* 1 - nonce length too small
|
|
* 2 - no match
|
|
* 3 - nonce expires ok, but the auth_extra checks failed
|
|
* 4 - stale
|
|
* 5 - invalid nc value (not an unsigned int)
|
|
*/
|
|
int check_nonce(auth_body_t* auth, str* secret1, str* secret2,
|
|
struct sip_msg* msg)
|
|
{
|
|
str * nonce;
|
|
int since, b_nonce2_len, b_nonce_len, cfg;
|
|
union bin_nonce b_nonce;
|
|
union bin_nonce b_nonce2;
|
|
time_t t;
|
|
#if defined USE_NC || defined USE_OT_NONCE
|
|
unsigned int n_id;
|
|
unsigned char pf;
|
|
#endif /* USE_NC || USE_OT_NONCE */
|
|
#ifdef USE_NC
|
|
unsigned int nc;
|
|
#endif
|
|
|
|
cfg = get_auth_checks(msg);
|
|
nonce=&auth->digest.nonce;
|
|
|
|
if (unlikely(nonce->s == 0)) {
|
|
return -1; /* Invalid nonce */
|
|
}
|
|
|
|
if (unlikely(nonce->len<MIN_NONCE_LEN)){
|
|
return 1; /* length musth be >= then minimum length */
|
|
}
|
|
|
|
#if defined USE_NC || defined USE_OT_NONCE
|
|
/* clear all possible nonce flags positions prior to decoding,
|
|
* to make sure they can be used even if the nonce is shorter */
|
|
b_nonce.n.nid_pf=0;
|
|
b_nonce.n_small.nid_pf=0;
|
|
#endif /* USE_NC || USE_OT_NONCE */
|
|
|
|
/* decode nonce */
|
|
b_nonce_len=base64_dec((unsigned char*)nonce->s, nonce->len,
|
|
&b_nonce.raw[0], sizeof(b_nonce));
|
|
if (unlikely(b_nonce_len < MIN_BIN_NONCE_LEN)){
|
|
DBG("auth: check_nonce: base64_dec failed\n");
|
|
return -1; /* error decoding the nonce (invalid nonce since we checked
|
|
the len of the base64 enc. nonce above)*/
|
|
}
|
|
|
|
since = get_bin_nonce_since(&b_nonce);
|
|
if (unlikely(since < up_since)) {
|
|
/* if valid_since time is time pointing before ser was started
|
|
* then we consider nonce as stalled.
|
|
It may be the nonce generated by previous ser instance having
|
|
different length (for example because of different auth.
|
|
checks).. Therefore we force credentials to be rebuilt by UAC
|
|
without prompting for password */
|
|
return 4;
|
|
}
|
|
t=ser_time(0);
|
|
if (unlikely((since > t) && ((since-t) > nonce_auth_max_drift) )){
|
|
/* the nonce comes from the future, either because of an external
|
|
* time adjustment, or because it was generated by another host
|
|
* which has the time slightly unsynchronized */
|
|
return 4; /* consider it stale */
|
|
}
|
|
b_nonce2=b_nonce; /*pre-fill it with the values from the received nonce*/
|
|
b_nonce2.n.expire=b_nonce.n.expire;
|
|
b_nonce2.n.since=b_nonce.n.since;
|
|
#if defined USE_NC || defined USE_OT_NONCE
|
|
if (cfg){
|
|
b_nonce2.n.nid_i=b_nonce.n.nid_i;
|
|
b_nonce2.n.nid_pf=b_nonce.n.nid_pf;
|
|
pf=b_nonce.n.nid_pf;
|
|
n_id=ntohl(b_nonce.n.nid_i);
|
|
}else{
|
|
b_nonce2.n_small.nid_i=b_nonce.n_small.nid_i;
|
|
b_nonce2.n_small.nid_pf=b_nonce.n_small.nid_pf;
|
|
pf=b_nonce.n_small.nid_pf;
|
|
n_id=ntohl(b_nonce.n_small.nid_i);
|
|
}
|
|
#ifdef UE_NC
|
|
if (unlikely(nc_enabled && !(pf & NF_VALID_NC_ID)) )
|
|
/* nounce count enabled, but nonce is not marked as nonce count ready
|
|
* or is too short => either an old nonce (should
|
|
* be caught by the ser start time check) or truncated nonce */
|
|
return 4; /* return stale for now */
|
|
}
|
|
#endif /* USE_NC */
|
|
#ifdef USE_OT_NONCE
|
|
if (unlikely(otn_enabled && !(pf & NF_VALID_OT_ID))){
|
|
/* same as above for one-time-nonce */
|
|
return 4; /* return stale for now */
|
|
}
|
|
#endif /* USE_OT_NONCE */
|
|
/* don't check if we got the expected length, if the length is smaller
|
|
* then expected then the md5 check below will fail (since the nid
|
|
* members of the bin_nonce struct will be 0); if the length is bigger
|
|
* and it was not caught by the base64_dec above, and the md5 matches,
|
|
* we ignore the extra stuff */
|
|
#endif /* USE_NC || USE_OT_NONCE */
|
|
b_nonce2_len=calc_bin_nonce_md5(&b_nonce2, cfg, secret1, secret2, msg);
|
|
if (!memcmp(&b_nonce.n.md5_1[0], &b_nonce2.n.md5_1[0], 16)) {
|
|
#ifdef USE_NC
|
|
/* if nounce-count checks enabled & auth. headers has nc */
|
|
if (nc_enabled && (pf & NF_VALID_NC_ID) && auth->digest.nc.s &&
|
|
auth->digest.nc.len){
|
|
if ((auth->digest.nc.len != 8) ||
|
|
l8hex2int(auth->digest.nc.s, &nc) != 0) {
|
|
ERR("check_nonce: bad nc value %.*s\n",
|
|
auth->digest.nc.len, auth->digest.nc.s);
|
|
return 5; /* invalid nc */
|
|
}
|
|
switch(nc_check_val(n_id, pf & NF_POOL_NO_MASK, nc)){
|
|
case NC_OK:
|
|
/* don't perform extra checks or one-time nonce checks
|
|
* anymore, if we have nc */
|
|
goto check_stale;
|
|
case NC_ID_OVERFLOW: /* id too old => stale */
|
|
case NC_TOO_BIG: /* nc overlfow => force re-auth => stale */
|
|
case NC_REPLAY: /* nc seen before => re-auth => stale */
|
|
case NC_INV_POOL: /* pool-no too big, maybe ser restart?*/
|
|
return 4; /* stale */
|
|
}
|
|
}
|
|
#endif /* USE_NC */
|
|
#ifdef USE_OT_NONCE
|
|
if (otn_enabled && (pf & NF_VALID_OT_ID)){
|
|
switch(otn_check_id(n_id, pf & NF_POOL_NO_MASK)){
|
|
case OTN_OK:
|
|
/* continue in case auth extra checks are enabled */
|
|
break;
|
|
case OTN_ID_OVERFLOW:
|
|
case OTN_INV_POOL:
|
|
case OTN_REPLAY:
|
|
return 4; /* stale */
|
|
}
|
|
}
|
|
#endif
|
|
if (cfg) {
|
|
if (unlikely(b_nonce_len != b_nonce2_len))
|
|
return 2; /* someone truncated our nonce? */
|
|
if (memcmp(&b_nonce.n.md5_2[0], &b_nonce2.n.md5_2[0], 16))
|
|
return 3; /* auth_extra_checks failed */
|
|
}
|
|
#ifdef USE_NC
|
|
check_stale:
|
|
#endif /* USE_NC */
|
|
if (unlikely(is_bin_nonce_stale(&b_nonce, t)))
|
|
return 4;
|
|
return 0;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|