Explorar o código

Merge pull request #639 from AndreasHuber-CH/authinfo_hdr

Add support for Authentication-Info header in auth module
Daniel-Constantin Mierla %!s(int64=9) %!d(string=hai) anos
pai
achega
c8ac7f99a3

+ 89 - 1
modules/auth/api.c

@@ -27,9 +27,12 @@
 #include "../../parser/digest/digest.h"
 #include "../../sr_module.h"
 #include "../../ut.h"
+#include "../../data_lump_rpl.h"
 #include "auth_mod.h"
 #include "nonce.h"
+#include "ot_nonce.h"
 #include "rfc2617_sha256.h"
+#include "challenge.h"
 
 static int auth_check_hdr_md5(struct sip_msg* msg, auth_body_t* auth_body,
 		auth_result_t* auth_res);
@@ -142,14 +145,60 @@ static int auth_check_hdr_md5(struct sip_msg* msg, auth_body_t* auth,
 	return 1;
 }
 
+/**
+ * Adds the Authentication-Info header, based on the credentials sent by a successful REGISTER.
+ * @param msg - SIP message to add the header to
+ * @returns 1 on success, 0 on error
+ */
+static int add_authinfo_resp_hdr(struct sip_msg *msg, char* next_nonce, int nonce_len, str qop, char* rspauth, str cnonce, str nc)
+{
+	str authinfo_hdr;
+	static const char authinfo_fmt[] = "Authentication-Info: "
+			"nextnonce=\"%.*s\", "
+			"qop=%.*s, "
+			"rspauth=\"%.*s\", "
+			"cnonce=\"%.*s\", "
+			"nc=%.*s\r\n";
+
+	authinfo_hdr.len = sizeof (authinfo_fmt) + nonce_len + qop.len + hash_hex_len + cnonce.len + nc.len - 20 /* format string parameters */ - 1 /* trailing \0 */;
+	authinfo_hdr.s = pkg_malloc(authinfo_hdr.len + 1);
+
+	if (!authinfo_hdr.s) {
+		LM_ERR("add_authinfo_resp_hdr: Error allocating %d bytes\n", authinfo_hdr.len);
+		goto error;
+	}
+	snprintf(authinfo_hdr.s, authinfo_hdr.len + 1, authinfo_fmt,
+			nonce_len, next_nonce,
+			qop.len, qop.s,
+			hash_hex_len, rspauth,
+			cnonce.len, cnonce.s,
+			nc.len, nc.s);
+	LM_DBG("authinfo hdr built: %.*s", authinfo_hdr.len, authinfo_hdr.s);
+	if (add_lump_rpl(msg, authinfo_hdr.s, authinfo_hdr.len, LUMP_RPL_HDR)!=0) {
+		LM_DBG("authinfo hdr added");
+		pkg_free(authinfo_hdr.s);
+		return 1;
+	}
+error:
+	if (authinfo_hdr.s) pkg_free(authinfo_hdr.s);
+	return 0;
+}
+
 /*
  * Purpose of this function is to do post authentication steps like
  * marking authorized credentials and so on.
  */
-auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr)
+auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr, char* ha1)
 {
 	int res = AUTHENTICATED;
 	auth_body_t* c;
+	dig_cred_t* d;
+	HASHHEX_SHA256 rspauth;
+#ifdef USE_OT_NONCE
+	char next_nonce[MAX_NONCE_LEN];
+	int nonce_len;
+	int cfg;
+#endif
 
 	c = (auth_body_t*)((hdr)->parsed);
 
@@ -167,6 +216,45 @@ auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr)
 			res = NOT_AUTHENTICATED;
 		}
 	}
+	else if (add_authinfo_hdr) {
+		if (unlikely(!ha1)) {
+			LM_ERR("add_authinfo_hdr is configured but the auth_* module "
+					"you are using does not provide the ha1 value to post_auth\n");
+		}
+		else {
+			d = &c->digest;
+
+			/* calculate rspauth */
+			calc_response(ha1,
+					&d->nonce,
+					&d->nc,
+					&d->cnonce,
+					&d->qop.qop_str,
+					d->qop.qop_parsed == QOP_AUTHINT,
+					0, /* method is empty for rspauth */
+					&d->uri,
+					NULL, /* TODO should be H(entity-body) if auth-int should be supported */
+					rspauth);
+
+			/* calculate new next nonce if otn is enabled */
+#ifdef USE_OT_NONCE
+			if (otn_enabled) {
+				cfg = get_auth_checks(msg);
+				nonce_len = sizeof(next_nonce);
+				if (unlikely(calc_new_nonce(next_nonce, &nonce_len, cfg, msg) != 0)) {
+					LM_ERR("auth: calc_nonce failed (len %d, needed %d). authinfo hdr is not added.\n",
+							(int) sizeof(next_nonce), nonce_len);
+				}
+				else {
+					add_authinfo_resp_hdr(msg, next_nonce, nonce_len, d->qop.qop_str, rspauth, d->cnonce, d->nc);
+				}
+			}
+			else
+#endif
+			/* use current nonce as next nonce */
+			add_authinfo_resp_hdr(msg, d->nonce.s, d->nonce.len, d->qop.qop_str, rspauth, d->cnonce, d->nc);
+		}
+	}
 
 	return res;
 }

+ 2 - 2
modules/auth/api.h

@@ -99,8 +99,8 @@ auth_result_t pre_auth(struct sip_msg* msg, str* realm, hdr_types_t hftype,
  * marking authorized credentials and so on.
  */
 typedef auth_result_t (*post_auth_t)(struct sip_msg* msg,
-		struct hdr_field* hdr);
-auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr);
+		struct hdr_field* hdr, char* ha1);
+auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr, char* ha1);
 
 typedef int (*check_response_t)(dig_cred_t* cred, str* method, char* ha1);
 int auth_check_response(dig_cred_t* cred, str* method, char* ha1);

+ 3 - 1
modules/auth/auth_mod.c

@@ -137,6 +137,7 @@ static struct qp auth_qauthint = {
 /* Hash algorithm used for digest authentication, MD5 if empty */
 str auth_algorithm = {"", 0};
 int hash_hex_len;
+int add_authinfo_hdr = 0; /* should an Authentication-Info header be added on 200 OK responses? */
 
 calc_HA1_t calc_HA1;
 calc_response_t calc_response;
@@ -204,6 +205,7 @@ static param_export_t params[] = {
 	{"realm_prefix",           PARAM_STRING, &auth_realm_prefix.s   },
 	{"use_domain",             PARAM_INT,    &auth_use_domain       },
 	{"algorithm",              PARAM_STR,    &auth_algorithm        },
+	{"add_authinfo_hdr",       INT_PARAM,    &add_authinfo_hdr      },
 	{0, 0, 0}
 };
 
@@ -538,7 +540,7 @@ int pv_authenticate(struct sip_msg *msg, str *realm, str *passwd,
 	ret = auth_check_response(&(cred->digest), method, ha1);
 	if(ret==AUTHENTICATED) {
 		ret = AUTH_OK;
-		switch(post_auth(msg, h)) {
+		switch(post_auth(msg, h, ha1)) {
 			case AUTHENTICATED:
 				break;
 			default:

+ 1 - 0
modules/auth/auth_mod.h

@@ -44,6 +44,7 @@ extern str proxy_challenge_header;
 extern str www_challenge_header;
 extern struct qp auth_qop;
 extern str auth_algorithm;
+extern int add_authinfo_hdr; /* should an Authentication-Info header be added on 200 OK responses? */
 
 extern int hash_hex_len;
 extern calc_HA1_t calc_HA1;

+ 66 - 41
modules/auth/challenge.c

@@ -73,6 +73,66 @@ void strip_realm(str* _realm)
 	return;
 }
 
+/**
+ * Calculate a new nonce.
+ * @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 three module parameters that
+ *            control which optional checks are enabled/disabled and which
+ *            parts of the message will be included in the nonce string.
+ * @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_new_nonce(char* nonce, int *nonce_len, int cfg, struct sip_msg* msg)
+{
+	int t;
+#if defined USE_NC || defined USE_OT_NONCE
+	unsigned int n_id;
+	unsigned char pool;
+	unsigned char pool_flags;
+#endif
+
+	t=time(0);
+#if defined USE_NC || defined USE_OT_NONCE
+	if (nc_enabled || otn_enabled){
+		pool=nid_get_pool();
+		n_id=nid_inc(pool);
+		pool_flags=0;
+#ifdef USE_NC
+		if (nc_enabled){
+			nc_new(n_id, pool);
+			pool_flags|=  NF_VALID_NC_ID;
+		}
+#endif
+#ifdef USE_OT_NONCE
+		if (otn_enabled){
+			otn_new(n_id, pool);
+			pool_flags|= NF_VALID_OT_ID;
+		}
+#endif
+	}else{
+		pool=0;
+		pool_flags=0;
+		n_id=0;
+	}
+	return calc_nonce(nonce, nonce_len, cfg, t, t + nonce_expire, n_id,
+				pool | pool_flags,
+				&secret1, &secret2, msg);
+#else  /* USE_NC || USE_OT_NONCE*/
+	return calc_nonce(nonce, nonce_len, cfg, t, t + nonce_expire,
+				&secret1, &secret2, msg);
+#endif /* USE_NC || USE_OT_NONCE */
+}
+
 
 /**
  * Create and return {WWW,Proxy}-Authenticate header field
@@ -93,12 +153,6 @@ int get_challenge_hf(struct sip_msg* msg, int stale, str* realm,
 	char *p;
 	str* hfn, hf;
 	int nonce_len, l, cfg;
-	int t;
-#if defined USE_NC || defined USE_OT_NONCE
-	unsigned int n_id;
-	unsigned char pool;
-	unsigned char pool_flags;
-#endif
 
 	if(!ahf)
 	{
@@ -183,42 +237,13 @@ int get_challenge_hf(struct sip_msg* msg, int stale, str* realm,
 	}
 	else {
 		l=nonce_len;
-		t=time(0);
-#if defined USE_NC || defined USE_OT_NONCE
-		if (nc_enabled || otn_enabled){
-			pool=nid_get_pool();
-			n_id=nid_inc(pool);
-			pool_flags=0;
-#ifdef USE_NC
-			if (nc_enabled){
-				nc_new(n_id, pool);
-				pool_flags|=  NF_VALID_NC_ID;
-			}
-#endif
-#ifdef USE_OT_NONCE
-			if (otn_enabled){
-				otn_new(n_id, pool);
-				pool_flags|= NF_VALID_OT_ID;
-			}
-#endif
-		}else{
-			pool=0;
-			pool_flags=0;
-			n_id=0;
+		if (calc_new_nonce(p, &l, cfg, msg) != 0)
+		{
+			ERR("auth: calc_nonce failed (len %d, needed %d)\n",
+					nonce_len, l);
+			pkg_free(hf.s);
+			return -1;
 		}
-		if (calc_nonce(p, &l, cfg, t, t + nonce_expire, n_id,
-					pool | pool_flags,
-					&secret1, &secret2, msg) != 0)
-#else  /* USE_NC || USE_OT_NONCE*/
-			if (calc_nonce(p, &l, cfg, t, t + nonce_expire,
-						&secret1, &secret2, msg) != 0)
-#endif /* USE_NC || USE_OT_NONCE */
-			{
-				ERR("auth: calc_nonce failed (len %d, needed %d)\n",
-						nonce_len, l);
-				pkg_free(hf.s);
-				return -1;
-			}
 		p += l;
 	}
 	*p = '"'; p++;

+ 2 - 0
modules/auth/challenge.h

@@ -49,4 +49,6 @@ int get_challenge_hf(struct sip_msg* msg, int stale, str* realm,
 
 void strip_realm(str* _realm);
 
+int calc_new_nonce(char* nonce, int *nonce_len, int cfg, struct sip_msg* msg);
+
 #endif /* CHALLENGE_H */

+ 46 - 20
modules/auth/doc/auth_params.xml

@@ -695,29 +695,55 @@ modparam("auth", "use_domain", 1)
 	</section>
 
 	<section id="auth.p.algorithm">
-	<title><varname>algorithm</varname> (string)</title>
-	<para>
-	   Configure hash algorithm used for digest authentication.
-	   Possible values are "MD5" or "SHA-256". If left empty MD5 is used.
-	   If specified, the specified algorithm is used and is also but in
-	   the 'algorithm' field of the challenge header.
-   </para>
-   <para>
-	   Warning: SHA-256 hash values take twice the space of MD5 hash values.
-	   So a buffer overflow might occur if this option is used in combination
-	   with another auth_* module that does not allocate at least 65 bytes to
-	   store hash values.
-	   SHA-256 can safely be used with the module auth_db as it allocates 256 bytes
-	   to store HA1 values.
-	</para>
-	<example>
-	   <title>use SHA-256 example</title>
-	   <programlisting>
+		<title><varname>algorithm</varname> (string)</title>
+		<para>
+			Configure hash algorithm used for digest authentication.
+			Possible values are "MD5" or "SHA-256". If left empty MD5 is used.
+			If specified, the specified algorithm is used and is also put in
+			the 'algorithm' field of the challenge header.
+		</para>
+		<para>
+			Warning: SHA-256 hash values take twice the space of MD5 hash values.
+			So a buffer overflow might occur if this option is used in combination
+			with another auth_* module that does not allocate at least 65 bytes to
+			store hash values.
+			SHA-256 can safely be used with the module auth_db as it allocates 256 bytes
+			to store HA1 values.
+		</para>
+		<example>
+			<title>use SHA-256 example</title>
+			<programlisting>
 ...
 modparam("auth", "algorithm", "SHA-256")
 ...
-	   </programlisting>
-	</example>
+			</programlisting>
+		</example>
+	</section>
+
+	<section id="auth.p.add_authinfo_hdr">
+		<title><varname>add_authinfo_hdr</varname> (boolean)</title>
+		<para>
+			Should an Authentication-Info header be added on 200 OK responses?
+			The Authentication-Info header offers mutual authentication.
+			The server proves to the client that it knows the user's secret.
+		</para>
+		<para>
+			The header also includes the next nonce which may be used by the client
+			in a future request.
+			If one_time_nonce is enabled, a new nonce is calculated for the next nonce.
+			Otherwise the current nonce is used for the next nonce.
+		</para>
+		<para>
+			The default value is 0 (no).
+		</para>
+		<example>
+			<title>add Authentication-Info header example</title>
+			<programlisting>
+...
+modparam("auth", "add_authinfo_hdr", yes)
+...
+			</programlisting>
+		</example>
 	</section>
 
 </section>

+ 1 - 1
modules/auth/nonce.c

@@ -178,7 +178,7 @@ inline static int calc_bin_nonce_md5(union bin_nonce* b_nonce, int cfg,
  *                  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
+ * @param cfg This is the value of one of the three 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>

+ 3 - 1
modules/auth/rfc2617.c

@@ -110,7 +110,9 @@ void calc_response_md5(HASHHEX _ha1,      /* H(A1) */
 
 	/* calculate H(A2) */
 	MD5Init(&Md5Ctx);
-	MD5Update(&Md5Ctx, _method->s, _method->len);
+	if (_method) {
+		MD5Update(&Md5Ctx, _method->s, _method->len);
+	}
 	MD5Update(&Md5Ctx, ":", 1);
 	MD5Update(&Md5Ctx, _uri->s, _uri->len);
 

+ 3 - 1
modules/auth/rfc2617_sha256.c

@@ -115,7 +115,9 @@ void calc_response_sha256(HASHHEX_SHA256 _ha1,      /* H(A1) */
 
 	/* calculate H(A2) */
 	sr_SHA256_Init(&Sha256Ctx);
-	SHA256_Update(&Sha256Ctx, _method->s, _method->len);
+	if (_method) {
+		SHA256_Update(&Sha256Ctx, _method->s, _method->len);
+	}
 	SHA256_Update(&Sha256Ctx, ":", 1);
 	SHA256_Update(&Sha256Ctx, _uri->s, _uri->len);
 

+ 1 - 1
modules/auth_db/authorize.c

@@ -299,7 +299,7 @@ static int digest_authenticate_hdr(sip_msg_t* msg, str *realm,
 	ret = auth_api.check_response(&(cred->digest), method, ha1);
 	if(ret==AUTHENTICATED) {
 		ret = AUTH_OK;
-		switch(auth_api.post_auth(msg, h)) {
+		switch(auth_api.post_auth(msg, h, ha1)) {
 			case AUTHENTICATED:
 				generate_avps(msg, result);
 				break;

+ 1 - 1
modules/auth_ephemeral/authorize.c

@@ -101,7 +101,7 @@ static inline int do_auth(struct sip_msg *_m, struct hdr_field *_h, str *_realm,
 	ret = eph_auth_api.check_response(&cred->digest, _method, ha1);
 	if (ret == AUTHENTICATED)
 	{
-		if (eph_auth_api.post_auth(_m, _h) != AUTHENTICATED)
+		if (eph_auth_api.post_auth(_m, _h, ha1) != AUTHENTICATED)
 		{
 			return AUTH_ERROR;
 		}

+ 1 - 1
modules/auth_radius/authorize.c

@@ -172,7 +172,7 @@ static inline int authorize(struct sip_msg* _msg, pv_elem_t* _realm,
     }
 
     if (res == 1) {
-	switch(auth_api.post_auth(_msg, h)) {
+	switch(auth_api.post_auth(_msg, h, NULL)) {
 	default:
 	    BUG("unexpected reply '%d'.\n",
 		auth_api.pre_auth(_msg, &domain, _hftype, &h, NULL));

+ 2 - 2
modules/uid_auth_db/authorize.c

@@ -288,7 +288,7 @@ static inline int check_all_ha1(struct sip_msg* msg, struct hdr_field* hdr,
 					}
 
 					if (!check_response(dig, method, ha1)) {
-						if (auth_api.post_auth(msg, hdr) == AUTHENTICATED) {
+						if (auth_api.post_auth(msg, hdr, ha1) == AUTHENTICATED) {
 							generate_avps(*res, row);
 							return 0;
 						}
@@ -416,7 +416,7 @@ static inline int authenticate(struct sip_msg* msg, str* realm, authdb_table_inf
     
 	/* Recalculate response, it must be same to authorize successfully */
 	if (!check_response(&(cred->digest), &msg->first_line.u.request.method, ha1)) {
-		switch(auth_api.post_auth(msg, h)) {
+		switch(auth_api.post_auth(msg, h, ha1)) {
 		case ERROR:
 		case BAD_CREDENTIALS:
 			ret = -2;