Explorar el Código

auth_identity: module relocated to archive

Daniel-Constantin Mierla hace 1 año
padre
commit
5fa45ab681

+ 46 - 0
src/modules/auth_identity/Makefile

@@ -0,0 +1,46 @@
+
+include ../../Makefile.defs
+auto_gen=
+NAME=auth_identity.so
+
+ifeq ($(CROSS_COMPILE),)
+CURL_BUILDER=$(shell \
+	if pkg-config --exists libcurl; then \
+		echo 'pkg-config libcurl'; \
+	else \
+		which curl-config; \
+	fi)
+SSL_BUILDER=$(shell \
+	if pkg-config --exists libssl; then \
+		echo 'pkg-config libssl'; \
+	fi)
+endif
+
+ifneq ($(CURL_BUILDER),)
+	DEFS += $(shell $(CURL_BUILDER) --cflags )
+	LIBS += $(shell $(CURL_BUILDER) --libs)
+else
+	DEFS+=-I$(LOCALBASE)/include
+	LIBS+=-L$(LOCALBASE)/lib -lcurl
+endif
+
+ifneq ($(SSL_BUILDER),)
+	DEFS += $(shell $(SSL_BUILDER) --cflags)
+	LIBS += $(shell $(SSL_BUILDER) --libs)
+else
+	DEFS += -I$(LOCALBASE)/ssl/include
+	LIBS += -L$(LOCALBASE)/lib -L$(LOCALBASE)/ssl/lib \
+			-L$(LOCALBASE)/lib64 -L$(LOCALBASE)/ssl/lib64 \
+			-lssl -lcrypto
+	# NOTE: depending on the way in which libssl was compiled you might
+	#       have to add -lz -lkrb5   (zlib and kerberos5).
+	#       E.g.: make TLS_HOOKS=1 TLS_EXTRA_LIBS="-lz -lkrb5"
+endif
+LIBS+= $(TLS_EXTRA_LIBS)
+
+# Static linking, if you'd like to use TLS and AUTH_IDENTITY at the same time
+#
+#LIBS+= /usr/lib/libcurl.a /usr/lib/libssl.a /usr/lib/libcrypto.a -lkrb5 -lidn -lz -lgssapi_krb5 -lrt -lldap
+
+include ../../Makefile.modules
+

+ 548 - 0
src/modules/auth_identity/README

@@ -0,0 +1,548 @@
+SIP Authenticated Identity Module
+
+Gergely Kovacs
+
+   Iptel.org
+
+   Copyright © 2007 Iptel.org
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+        3. Compilation
+        4. Installation And Running
+        5. Parameters
+
+              5.1. privatekey_path (string)
+              5.2. certificate_path (string)
+              5.3. certificate_url (string)
+              5.4. msg_timeout (integer)
+              5.5. auth_validity_time (integer)
+              5.6. callid_cache_limit (integer)
+              5.7. certificate_cache_limit (integer)
+              5.8. cainfo_path (string)
+              5.9. accept_pem_certs (int)
+
+        6. Functions
+
+              6.1. auth_date_proc()
+
+                    6.1.1. Dependencies
+
+              6.2. auth_add_identity()
+
+                    6.2.1. Dependencies
+
+              6.3. vrfy_check_date()
+
+                    6.3.1. Dependencies
+
+              6.4. vrfy_get_certificate()
+
+                    6.4.1. Dependencies
+
+              6.5. vrfy_check_certificate()
+
+                    6.5.1. Dependencies
+
+              6.6. vrfy_check_msgvalidity()
+
+                    6.6.1. Dependencies
+
+              6.7. vrfy_check_callid()
+
+                    6.7.1. Dependencies
+
+        7. Authorizer service examples
+        8. Verifier service examples
+        9. Remarks
+
+   List of Examples
+
+   1.1. Set privatekey_path parameter
+   1.2. Set certificate_path parameter
+   1.3. Set certificate_url parameter
+   1.4. Set msg_timeout parameter
+   1.5. Set auth_validity_time parameter
+   1.6. Set auth_validity_time parameter
+   1.7. Set certificate_cache_limit parameter
+   1.8. Set cainfo_path parameter
+   1.9. Set accept_pem_certs parameter
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+   3. Compilation
+   4. Installation And Running
+   5. Parameters
+
+        5.1. privatekey_path (string)
+        5.2. certificate_path (string)
+        5.3. certificate_url (string)
+        5.4. msg_timeout (integer)
+        5.5. auth_validity_time (integer)
+        5.6. callid_cache_limit (integer)
+        5.7. certificate_cache_limit (integer)
+        5.8. cainfo_path (string)
+        5.9. accept_pem_certs (int)
+
+   6. Functions
+
+        6.1. auth_date_proc()
+
+              6.1.1. Dependencies
+
+        6.2. auth_add_identity()
+
+              6.2.1. Dependencies
+
+        6.3. vrfy_check_date()
+
+              6.3.1. Dependencies
+
+        6.4. vrfy_get_certificate()
+
+              6.4.1. Dependencies
+
+        6.5. vrfy_check_certificate()
+
+              6.5.1. Dependencies
+
+        6.6. vrfy_check_msgvalidity()
+
+              6.6.1. Dependencies
+
+        6.7. vrfy_check_callid()
+
+              6.7.1. Dependencies
+
+   7. Authorizer service examples
+   8. Verifier service examples
+   9. Remarks
+
+1. Overview
+
+   Auth Identity module provides functionalities for securely identifying
+   originators of SIP messages. It implements the SIP Identity standard
+   where a SIP proxy signs messages that is sent to other domains. This
+   module has two basic services:
+     * authorizer - authorizes a message and adds Identity and
+       Identity-Info headers
+     * verifier - verifies an authorized message
+
+   Known limitations in this version:
+     * authorizer and verifier support all SIP requests except for CANCEL
+       and REGISTER
+     * verifier does not support the subjectAltName extension of
+       certificates
+
+2. Dependencies
+
+   This module does not depend any other module.
+
+3. Compilation
+
+   This module needs the following headers and libraries:
+     * OpenSSL (version 0.9.8 or higher) for cryptographic functions
+     * libcurl for HTTP, HTTPS functions
+
+   If you'd like to use TLS module too then use the corresponding LIB line
+   in auth_identity's Makefile
+
+4. Installation And Running
+
+   the Authorizer service needs to make the public key, which conveyed in
+   a certificate, available over HTTPS or HTTP for verifiers. The domain
+   the authorizer is responsible for and the domain part of the URL of the
+   certificate must be the same. This service needs access to the private
+   key too.
+
+5. Parameters
+
+   5.1. privatekey_path (string)
+   5.2. certificate_path (string)
+   5.3. certificate_url (string)
+   5.4. msg_timeout (integer)
+   5.5. auth_validity_time (integer)
+   5.6. callid_cache_limit (integer)
+   5.7. certificate_cache_limit (integer)
+   5.8. cainfo_path (string)
+   5.9. accept_pem_certs (int)
+
+5.1. privatekey_path (string)
+
+   Note: this parameter is for authorizer service.
+
+   The path of private key of the authentication service. The key must be
+   in PEM format.
+
+   This parameter is required by authentication service.
+
+   Example 1.1. Set privatekey_path parameter
+...
+modparam("auth_identity","privatekey_path","/etc/ssl/private/key.pem")
+...
+
+5.2. certificate_path (string)
+
+   Note: this parameter is for authorizer service.
+
+   The path of certificate of the authentication service. The certificate
+   must be in PEM format.
+
+   This parameter is required by authentication service.
+
+   Example 1.2. Set certificate_path parameter
+...
+modparam("auth_identity","certificate_path","/var/www/ssl/mycert.pem")
+...
+
+5.3. certificate_url (string)
+
+   Note: this parameter is for authorizer service.
+
+   The url where certificate is available for other verifier services.
+   (value of Identity-info header) The certificate should be in DER
+   format.
+
+   This parameter is required by authentication service.
+
+   Example 1.3. Set certificate_url parameter
+...
+modparam("auth_identity","certificate_url","https://foo.bar/mycert.der")
+...
+
+5.4. msg_timeout (integer)
+
+   Note: this parameter is for authorizer service.
+
+   If the Date header of message which is needed to be authenticated
+   contains a time different by more than this seconds from the current
+   time noted by the authentication service then it rejects the message.
+
+   This parameter is optional. The default value is "600".
+
+   Example 1.4. Set msg_timeout parameter
+...
+modparam("auth_identity","msg_timeout",600)
+...
+
+5.5. auth_validity_time (integer)
+
+   Note: this parameter is for verifier service.
+
+   The validity time of an authenticated message. The message will be
+   refused if it contains a time different by more than this seconds from
+   the current time noted by the verification service.
+
+   This parameter is optional. The default value is "3600".
+
+   Example 1.5. Set auth_validity_time parameter
+...
+modparam("auth_identity","auth_validity_time",3600)
+...
+
+5.6. callid_cache_limit (integer)
+
+   Note: this parameter is for verifier service.
+
+   The number of Call-IDs stored in order to recognize call replay
+   attacks. A Call-ID is stored auth_validity_time long and uses
+   approximately 100 bytes memory.
+
+   This parameter is optional. The default value is "32768". (you should
+   increase the size of shared memory with -m command line switch if you
+   liked to store more callid than 10000)
+
+   Example 1.6. Set auth_validity_time parameter
+...
+modparam("auth_identity","callid_cache_limit",32768)
+...
+
+5.7. certificate_cache_limit (integer)
+
+   Note: this parameter is for verifier service.
+
+   The number of certificates stored in order to avoid needless download.
+   A certificate is stored until its expiration date and uses
+   approximately 600 bytes memory.
+
+   This parameter is optional. The default value is "4096".
+
+   Example 1.7. Set certificate_cache_limit parameter
+...
+modparam("auth_identity","certificate_cache_limit",4096)
+...
+
+5.8. cainfo_path (string)
+
+   Note: this parameter is for verifier service.
+
+   A file of trusted certificates. The file should contain multiple
+   certificates in PEM format concatenated together. It could be useful
+   for verifying a certificate signed by a private CA.
+
+   This parameter is optional. It has not got default value.
+
+   Example 1.8. Set cainfo_path parameter
+...
+modparam("auth_identity","cainfo_path","/etc/ssl/certs/ca-certificates.crt")
+...
+
+5.9. accept_pem_certs (int)
+
+   Note: this parameter is for verifier service.
+
+   Enables the acquired certificate processing if it is in PEM format.
+   Value can be 0 or 1.
+
+   This parameter is optional. The default value is "0".
+
+   Example 1.9. Set accept_pem_certs parameter
+...
+modparam("auth_identity","accept_pem_certs",1)
+...
+
+6. Functions
+
+   6.1. auth_date_proc()
+
+        6.1.1. Dependencies
+
+   6.2. auth_add_identity()
+
+        6.2.1. Dependencies
+
+   6.3. vrfy_check_date()
+
+        6.3.1. Dependencies
+
+   6.4. vrfy_get_certificate()
+
+        6.4.1. Dependencies
+
+   6.5. vrfy_check_certificate()
+
+        6.5.1. Dependencies
+
+   6.6. vrfy_check_msgvalidity()
+
+        6.6.1. Dependencies
+
+   6.7. vrfy_check_callid()
+
+        6.7.1. Dependencies
+
+6.1.  auth_date_proc()
+
+   Note: this function is for authorizer service.
+
+   If a message, the auth service should authorize, contains Date header
+   then this function checks whether it falls in message timeout (set by
+   msg_timeout parameter). If there is not any Date header then the module
+   adds one. This function also checks whether the certificate of the
+   authentication service (set by certificate_path parameter) has expired.
+
+6.1.1. Dependencies
+
+   No dependencies
+
+6.2.  auth_add_identity()
+
+   Note: this function is for authorizer service.
+
+   Assembles digest-string from the message, calculates its SHA1 hash,
+   encrypts it with the private key (set by privatekey_path parameter) of
+   the authorizer service, base64 encodes it and adds to the outgoing
+   message as the value of Identity header. This function also adds
+   Identity-Info header which contains an URI (set by certificate_url
+   parameter) from which the certificate of auth service can be acquired.
+
+   Note: this function needs the final outgoing message for authorization,
+   so no module may modify any digest string related headers (From, To,
+   Call-ID, CSeq, Date, Contact) and body after auth_add_identity()'s been
+   called
+
+6.2.1. Dependencies
+
+   auth_date_proc() must be called before
+
+6.3.  vrfy_check_date()
+
+   Note: this function is for verifier service.
+
+   Checks Date header of the incoming message whether falls in validity
+   time (set by auth_validity_time parameter)
+
+6.3.1. Dependencies
+
+   No dependencies
+
+6.4.  vrfy_get_certificate()
+
+   Note: this function is for verifier service.
+
+   Tries to get certificate defined by the value of Identity-info header
+   from certificate table (which size is set by certificate_cache_limit
+   parameter). If the required certificate is not found there then this
+   function downloads it.
+
+6.4.1. Dependencies
+
+   No dependencies
+
+6.5.  vrfy_check_certificate()
+
+   Note: this function is for verifier service.
+
+   Checks whether the downloaded certificate is valid (is not expired, its
+   subject and the domain part of the URL are the same) and adds it to
+   certificate table.
+
+6.5.1. Dependencies
+
+   vrfy_get_certificate() must be called before
+
+6.6.  vrfy_check_msgvalidity()
+
+   Note: this function is for verifier service.
+
+   Assembles digest-string from the message, create SHA1 hash and compares
+   it with the decrypted value of Identity header.
+
+6.6.1. Dependencies
+
+   vrfy_get_certificate() must be called before and
+   vrfy_check_certificate() should be called before
+
+6.7.  vrfy_check_callid()
+
+   Note: this function is for verifier service.
+
+   Checks whether the current call's been already processed in validity
+   time (set by auth_validity_time) to recognize call replay attacks. If
+   this call (identified by Call-id, Cseq, and tag of From header triple)
+   has not been replayed then adds it to callid table (which size is set
+   by callid_cache_limit parameter).
+
+6.7.1. Dependencies
+
+   This function should be called for the last time.
+
+7. Authorizer service examples
+
+...
+route[INIT]
+{
+        # we process new transactions only
+        if (!t_newtran()) {
+                sl_reply("500", "Internal error newtran");
+                drop;
+        }
+...
+route[OUTBOUND]
+{
+        # If we are responsible for the domain of the sender of this message
+        if ($f.did && !$t.did) {
+                # Authentication service
+                if (method=="INVITE" || method=="BYE"
+                        || method=="OPTION" || method=="ACK") {
+                        # Identity and Identity-info headers must not exist
+                        if (@identity) {
+                                t_reply("403", "Invalid Identity header");
+                                drop;
+                        }
+                        if (@identity_info) {
+                                t_reply("403", "Invalid Identity-info header");
+                                drop;
+                        }
+
+                        if (!auth_date_proc()) {
+                                t_reply("403", "Invalid Date value");
+                                drop;
+                        }
+
+                        if (!auth_add_identity()) {
+                                t_reply("480", "Authentication error");
+                                drop;
+                        }
+                }
+                route(FORWARD);
+        }
+}
+...
+
+8. Verifier service examples
+
+...
+route[INIT]
+{
+        # we process new transactions only
+        if (!t_newtran()) {
+                sl_reply("500", "Internal error newtran");
+                drop;
+        }
+...
+route[VERIFY]
+{
+        # if we've already processed this message then we drop it
+        if (!t_newtran()) {
+                sl_reply("500", "Internal error newtran");
+                drop;
+        }
+
+        if (method=="INVITE" || method=="BYE"
+                || method=="OPTION" || method=="ACK") {
+                # Identity and Identity-info are required for verification
+                if (!@identity) {
+                        t_reply("428", "Use Identity Header");
+                        drop;
+                }
+                if (!@identity_info) {
+                        t_reply("436", "Bad Identity-Info");
+                        drop;
+                }
+
+                if (!vrfy_check_date()) {
+                        t_reply("403", "Outdated Date header value");
+                        drop;
+                }
+
+                if (!vrfy_get_certificate()) {
+                        t_reply("436", "Bad Identity-Info");
+                        drop;
+                }
+
+                if (!vrfy_check_certificate()) {
+                        t_reply("437", "Unsupported Certificate");
+                        drop;
+                }
+
+                if (!vrfy_check_msgvalidity()) {
+                        t_reply("438", "Invalid Identity Header");
+                        drop;
+                }
+
+                if (!vrfy_check_callid()) {
+                        t_reply("403", "Message is replayed");
+                        drop;
+                }
+        }
+}
+...
+
+9. Remarks
+
+   Note: libcurl leak in CentOS 6 - this module uses libcurl library and
+   in case if you are using CentOS 6, be aware that standard
+   libcurl-7.19.7-52 has a memory leak. To fix this memory, install
+   libcurl from city-fan repository. More details at:
+   https://www.digitalocean.com/community/questions/how-to-upgrade-curl-in
+   -centos6

+ 438 - 0
src/modules/auth_identity/auth_crypt.c

@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: Crypt
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/crypto.h>
+#include <openssl/x509_vfy.h>
+
+#include "../../core/mem/mem.h"
+#include "../../core/parser/parse_uri.h"
+
+#include "auth_identity.h"
+
+
+int retrieve_x509(X509 **pcert, str *scert, int bacceptpem)
+{
+	BIO *bcer = NULL;
+	char serr[160];
+	int iRet = 0;
+
+
+	if(!(bcer = BIO_new(BIO_s_mem()))) {
+		LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to create BIO\n");
+
+		return -1;
+	}
+
+	do {
+		if(BIO_write(bcer, scert->s, scert->len) != scert->len) {
+			LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to write BIO\n");
+			iRet = -2;
+			break;
+		}
+
+		/* RFC 4474 only accepts certs in the DER form but it can not harm
+		 * to be a little bit more flexible and accept PEM as well. */
+		if(bacceptpem && scert->len > BEGIN_PEM_CERT_LEN
+				&& memmem(scert->s, scert->len, BEGIN_PEM_CERT,
+						BEGIN_PEM_CERT_LEN)) {
+			if(!(*pcert = PEM_read_bio_X509(bcer, NULL, NULL, NULL))) {
+				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
+				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: PEM Certificate %s\n",
+						serr);
+				iRet = -4;
+			}
+		} else {
+			if(!(*pcert = d2i_X509_bio(bcer, NULL))) {
+				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
+				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: DER Certificate %s\n",
+						serr);
+				iRet = -3;
+			}
+		}
+	} while(0);
+
+	BIO_free(bcer);
+
+	return iRet;
+}
+
+int check_x509_subj(X509 *pcert, str *sdom)
+{
+	STACK_OF(GENERAL_NAME) * altnames;
+	int ialts, i1, ilen, altlen;
+	const GENERAL_NAME *actname;
+	char scname[AUTH_DOMAIN_LENGTH];
+	char *altptr;
+	struct sip_uri suri;
+	int ret = 0;
+
+
+	/* we're looking for subjectAltName for the first time */
+	altnames = X509_get_ext_d2i(pcert, NID_subject_alt_name, NULL, NULL);
+
+	if(altnames) {
+		ialts = sk_GENERAL_NAME_num(altnames);
+
+		for(i1 = 0; i1 < ialts; i1++) {
+			actname = sk_GENERAL_NAME_value(altnames, i1);
+
+			if(actname->type == GEN_DNS || actname->type == GEN_URI) {
+				/* we've found one */
+#if OPENSSL_VERSION_NUMBER >= 0x010100000L
+				altptr = (char *)ASN1_STRING_get0_data(actname->d.ia5);
+#else
+				altptr = (char *)ASN1_STRING_data(actname->d.ia5);
+#endif
+				if(actname->type == GEN_URI) {
+					if(parse_uri(altptr, strlen(altptr), &suri) != 0) {
+						continue;
+					}
+					if(!(suri.type == SIP_URI_T || suri.type == SIPS_URI_T)) {
+						continue;
+					}
+					if(suri.user.len != 0 || suri.passwd.len != 0) {
+						continue;
+					}
+					altptr = suri.host.s;
+					altlen = suri.host.len;
+				} else {
+					altlen = strlen(altptr);
+				}
+				if(sdom->len != altlen
+						|| strncasecmp(altptr, sdom->s, sdom->len)) {
+					LOG(L_INFO, "AUTH_IDENTITY VERIFIER: subAltName of "
+								"certificate doesn't match host name\n");
+					ret = -1;
+				} else {
+					ret = 1;
+					break;
+				}
+			}
+		}
+		GENERAL_NAMES_free(altnames);
+	}
+
+	if(ret != 0) {
+		return ret == 1 ? 0 : ret;
+	}
+
+	/* certificate supplier host and certificate subject match check */
+	ilen = X509_NAME_get_text_by_NID(X509_get_subject_name(pcert),
+			NID_commonName, scname, sizeof(scname));
+	if(sdom->len != ilen || strncasecmp(scname, sdom->s, sdom->len)) {
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: common name of certificate "
+					"doesn't match host name\n");
+		return -2;
+	}
+
+	return 0;
+}
+
+int verify_x509(X509 *pcert, X509_STORE *pcacerts)
+{
+	X509_STORE_CTX *ca_ctx = NULL;
+	char *strerr;
+
+	ca_ctx = X509_STORE_CTX_new();
+	if(ca_ctx == NULL) {
+		LM_ERR("cannot get a x509 context\n");
+		return -1;
+	}
+
+	if(X509_STORE_CTX_init(ca_ctx, pcacerts, pcert, NULL) != 1) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:verify_x509: Unable to init X509 store ctx\n");
+		X509_STORE_CTX_free(ca_ctx);
+		return -1;
+	}
+
+	if(X509_verify_cert(ca_ctx) != 1) {
+		strerr = (char *)X509_verify_cert_error_string(
+				X509_STORE_CTX_get_error(ca_ctx));
+		LOG(L_ERR,
+				"AUTH_IDENTITY VERIFIER: Certificate verification error: %s\n",
+				strerr);
+		X509_STORE_CTX_cleanup(ca_ctx);
+		X509_STORE_CTX_free(ca_ctx);
+		return -2;
+	}
+	X509_STORE_CTX_cleanup(ca_ctx);
+	X509_STORE_CTX_free(ca_ctx);
+
+	LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Certificate is valid\n");
+
+	return 0;
+}
+
+int rsa_sha1_enc(
+		dynstr *sdigeststr, dynstr *senc, dynstr *sencb64, RSA *hmyprivkey)
+{
+	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
+	int ires;
+	char serr[160];
+
+
+	SHA1((unsigned char *)getstr_dynstr(sdigeststr).s,
+			getstr_dynstr(sdigeststr).len, sstrcrypted);
+
+#ifdef NEW_RSA_PROC
+	ires = senc->size;
+	if(RSA_sign(NID_sha1, sstrcrypted, sizeof sstrcrypted,
+			   (unsigned char *)getstr_dynstr(senc).s, (unsigned int *)&ires,
+			   hmyprivkey)
+			!= 1) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
+		return -2;
+	}
+#else
+	ires = RSA_private_encrypt(sizeof sstrcrypted, sstrcrypted,
+			(unsigned char *)getstr_dynstr(senc).s, hmyprivkey,
+			RSA_PKCS1_PADDING);
+	if(ires < 0) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
+		return -1;
+	}
+#endif
+
+	base64encode(getstr_dynstr(senc).s, senc->size, getstr_dynstr(sencb64).s,
+			&getstr_dynstr(sencb64).len);
+
+	return 0;
+}
+
+int rsa_sha1_dec(char *sencedsha, int iencedshalen, char *ssha, int sshasize,
+		int *ishalen, X509 *pcertx509)
+{
+	EVP_PKEY *pkey;
+	RSA *hpubkey;
+	unsigned long lerr;
+	char serr[160];
+
+
+	pkey = X509_get_pubkey(pcertx509);
+	if(pkey == NULL) {
+		lerr = ERR_get_error();
+		ERR_error_string_n(lerr, serr, sizeof(serr));
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Pubkey %s\n", serr);
+		return -1;
+	}
+
+	X509_free(pcertx509);
+
+	hpubkey = EVP_PKEY_get1_RSA(pkey);
+	EVP_PKEY_free(pkey);
+	if(hpubkey == NULL) {
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Error getting RSA key\n");
+		return -2;
+	}
+
+#ifdef NEW_RSA_PROC
+	if(RSA_verify(NID_sha1, (unsigned char *)ssha, sshasize,
+			   (unsigned char *)sencedsha, iencedshalen, hpubkey)
+			!= 1) {
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify returned: '%s'\n",
+				ERR_error_string(ERR_get_error(), NULL));
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify failed -> Invalid "
+					"Identity Header\n");
+		RSA_free(hpubkey);
+		return -5;
+	}
+#else
+	/* it is bigger than the output buffer */
+	if(RSA_size(hpubkey) > sshasize) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:decrypt_identity: Unexpected Identity hash "
+				"length (%d > %d)\n",
+				RSA_size(hpubkey), sshasize);
+		RSA_free(hpubkey);
+		return -3;
+	}
+	*ishalen = RSA_public_decrypt(iencedshalen, (unsigned char *)sencedsha,
+			(unsigned char *)ssha, hpubkey, RSA_PKCS1_PADDING);
+	if(*ishalen <= 0) {
+		lerr = ERR_get_error();
+		ERR_error_string_n(lerr, serr, sizeof(serr));
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: RSA operation error %s\n",
+				serr);
+		RSA_free(hpubkey);
+		return -4;
+	}
+#endif
+
+	RSA_free(hpubkey);
+
+	return 0;
+}
+
+/* copypasted from ser/modules/rr/avp_cookie.c + this adds '=' sign! ) */
+void base64encode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len)
+{
+	static char code64[64 + 1] =
+			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	int pos;
+	for(pos = 0, *tgt_len = 0; pos < src_len; pos += 3, *tgt_len += 4) {
+		tgt_buf[*tgt_len + 0] = code64[(unsigned char)src_buf[pos + 0] >> 2];
+		tgt_buf[*tgt_len + 1] =
+				code64[(((unsigned char)src_buf[pos + 0] & 0x03) << 4)
+						| ((pos + 1 < src_len)
+										? ((unsigned char)src_buf[pos + 1] >> 4)
+										: 0)];
+		if(pos + 1 < src_len)
+			tgt_buf[*tgt_len + 2] =
+					code64[(((unsigned char)src_buf[pos + 1] & 0x0F) << 2)
+							| ((pos + 2 < src_len) ? (
+									   (unsigned char)src_buf[pos + 2] >> 6)
+												   : 0)];
+		else
+			tgt_buf[*tgt_len + 2] = '=';
+		if(pos + 2 < src_len)
+			tgt_buf[*tgt_len + 3] =
+					code64[(unsigned char)src_buf[pos + 2] & 0x3F];
+		else
+			tgt_buf[*tgt_len + 3] = '=';
+	}
+}
+
+
+/* copypasted from ser/modules/rr/avp_cookie.c */
+void base64decode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len)
+{
+	int pos, i, n;
+	unsigned char c[4];
+	for(pos = 0, i = 0, *tgt_len = 0; pos < src_len; pos++) {
+		if(src_buf[pos] >= 'A' && src_buf[pos] <= 'Z')
+			c[i] = src_buf[pos] - 65; /* <65..90>  --> <0..25> */
+		else if(src_buf[pos] >= 'a' && src_buf[pos] <= 'z')
+			c[i] = src_buf[pos] - 71; /* <97..122>  --> <26..51> */
+		else if(src_buf[pos] >= '0' && src_buf[pos] <= '9')
+			c[i] = src_buf[pos] + 4; /* <48..56>  --> <52..61> */
+		else if(src_buf[pos] == '+')
+			c[i] = 62;
+		else if(src_buf[pos] == '/')
+			c[i] = 63;
+		else /* '=' */
+			c[i] = 64;
+		i++;
+		if(pos == src_len - 1) {
+			while(i < 4) {
+				c[i] = 64;
+				i++;
+			}
+		}
+		if(i == 4) {
+			if(c[0] == 64)
+				n = 0;
+			else if(c[2] == 64)
+				n = 1;
+			else if(c[3] == 64)
+				n = 2;
+			else
+				n = 3;
+			switch(n) {
+				case 3:
+					tgt_buf[*tgt_len + 2] = (char)(((c[2] & 0x03) << 6) | c[3]);
+					/* no break */
+				case 2:
+					tgt_buf[*tgt_len + 1] =
+							(char)(((c[1] & 0x0F) << 4) | (c[2] >> 2));
+					/* no break */
+				case 1:
+					tgt_buf[*tgt_len + 0] = (char)((c[0] << 2) | (c[1] >> 4));
+					break;
+			}
+			i = 0;
+			*tgt_len += n;
+		}
+	}
+}
+
+int x509_get_validitytime(time_t *tout, ASN1_UTCTIME *tin)
+{
+	char *sasn1;
+	int i1;
+	struct tm tmptm;
+
+
+	memset(&tmptm, 0, sizeof(tmptm));
+	i1 = tin->length;
+	sasn1 = (char *)tin->data;
+
+	if(i1 < 10)
+		return -1;
+	/*	if (sasn1[i1-1]!='Z')
+		return -1;*/
+	for(i1 = 0; i1 < 10; i1++)
+		if((sasn1[i1] > '9') || (sasn1[i1] < '0'))
+			return -2;
+
+	tmptm.tm_year = (sasn1[0] - '0') * 10 + (sasn1[1] - '0');
+	if(tmptm.tm_year < 50)
+		tmptm.tm_year += 100;
+
+	tmptm.tm_mon = (sasn1[2] - '0') * 10 + (sasn1[3] - '0') - 1;
+	if((tmptm.tm_mon > 11) || (tmptm.tm_mon < 0))
+		return -3;
+
+	tmptm.tm_mday = (sasn1[4] - '0') * 10 + (sasn1[5] - '0');
+	tmptm.tm_hour = (sasn1[6] - '0') * 10 + (sasn1[7] - '0');
+	tmptm.tm_min = (sasn1[8] - '0') * 10 + (sasn1[9] - '0');
+
+	if((sasn1[10] >= '0') && (sasn1[10] <= '9') && (sasn1[11] >= '0')
+			&& (sasn1[11] <= '9'))
+		tmptm.tm_sec = (sasn1[10] - '0') * 10 + (sasn1[11] - '0');
+
+#ifdef HAVE_TIMEGM
+	*tout = timegm(&tmptm);
+#else
+	*tout = _timegm(&tmptm);
+#endif
+
+	return 0;
+}
+
+int x509_get_notbefore(time_t *tout, X509 *pcert)
+{
+	return (x509_get_validitytime(tout, X509_get_notBefore(pcert)));
+}
+
+int x509_get_notafter(time_t *tout, X509 *pcert)
+{
+	return (x509_get_validitytime(tout, X509_get_notAfter(pcert)));
+}

+ 119 - 0
src/modules/auth_identity/auth_dynstr.c

@@ -0,0 +1,119 @@
+/*
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: Dynamic strings
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#include <errno.h>
+
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_cseq.h"
+#include "../../core/parser/parse_content.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/contact/parse_contact.h"
+
+#include "../../core/data_lump.h"
+#include "../../core/msg_translator.h"
+#include "auth_identity.h"
+
+/*
+ * Dynamic string functions
+ */
+
+int initdynstr(dynstr *sout, int isize)
+{
+	memset(sout, 0, sizeof(*sout));
+	getstr_dynstr(sout).s = pkg_malloc(isize);
+	if(!getstr_dynstr(sout).s) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	sout->size = isize;
+
+	return 0;
+}
+
+int cpy2dynstr(dynstr *sout, str *s2app)
+{
+	char *stmp;
+	int isize = s2app->len;
+
+	if(isize > sout->size) {
+		stmp = pkg_realloc(sout->sd.s, isize);
+		if(!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:cpy2dynstr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s = stmp;
+		sout->size = isize;
+	}
+
+	memcpy(sout->sd.s, s2app->s, s2app->len);
+	sout->sd.len = isize;
+
+	return 0;
+}
+
+int app2dynchr(dynstr *sout, char capp)
+{
+	char *stmp;
+	int isize = sout->sd.len + 1;
+
+	if(isize > sout->size) {
+		stmp = pkg_realloc(sout->sd.s, isize);
+		if(!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:app2dynchr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s = stmp;
+		sout->size++;
+	}
+
+	sout->sd.s[sout->sd.len] = capp;
+	sout->sd.len++;
+
+	return 0;
+}
+
+int app2dynstr(dynstr *sout, str *s2app)
+{
+	char *stmp;
+	int isize = sout->sd.len + s2app->len;
+
+	if(isize > sout->size) {
+		stmp = pkg_realloc(sout->sd.s, isize);
+		if(!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:app2dynstr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s = stmp;
+		sout->size = isize;
+	}
+
+	memcpy(&sout->sd.s[sout->sd.len], s2app->s, s2app->len);
+	sout->sd.len = isize;
+
+	return 0;
+}

+ 790 - 0
src/modules/auth_identity/auth_hdrs.c

@@ -0,0 +1,790 @@
+/*
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: Authentication headers
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#include <errno.h>
+
+#include "../../core/parser/parser_f.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_cseq.h"
+#include "../../core/parser/parse_content.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/keys.h"
+#include "../../core/parser/contact/parse_contact.h"
+
+#include "../../modules/tm/ut.h"
+#include "../../core/data_lump.h"
+#include "../../core/msg_translator.h"
+#include "auth_identity.h"
+
+
+struct hdr_field glb_contact;
+char *glb_siphdr = NULL;
+char *glb_msgbody = NULL;
+
+static int tohdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int in_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int out_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int in_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int out_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static void free_out_contacthdr(void);
+static void free_out_msgbody(void);
+
+
+/* macros from the core parser */
+#define LOWER_BYTE(b) ((b) | 0x20)
+#define LOWER_DWORD(d) ((d) | 0x20202020)
+
+#define READ(val) \
+	(*(val + 0) + (*(val + 1) << 8) + (*(val + 2) << 16) + (*(val + 3) << 24))
+
+static char *auth_next_line(char *buf, char *buf_end);
+static inline char *skip_ws(char *p, unsigned int size);
+static char *auth_get_hf_name(char *begin, char *end, enum _hdr_types_t *type);
+static int get_contact_body(char *buf, unsigned int len, str *sout);
+
+
+/*
+ *	Header parsing functions
+ */
+
+/* From */
+int fromhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if((!msg->from) && (parse_headers(msg, HDR_FROM_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:fromhdr_proc: Error while parsing FROM "
+				   "header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->from) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:fromhdr_proc: FROM header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_from_header explicitly */
+	if((!(msg->from)->parsed) && (parse_from_header(msg) < 0)) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:fromhdr_proc: Error while parsing FROM body\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = get_from(msg)->uri;
+
+	if(soutopt)
+		*soutopt = get_from(msg)->tag_value;
+
+	return AUTH_OK;
+}
+
+/* To */
+static int tohdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->to && (parse_headers(msg, HDR_TO_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: Error while parsing TO header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->to) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: TO header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	if(!msg->to->parsed) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: TO is not parsed\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = ((struct to_body *)msg->to->parsed)->uri;
+
+	return AUTH_OK;
+}
+
+/* Call-ID */
+int callidhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->callid && (parse_headers(msg, HDR_CALLID_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:callidhdr_proc: error while parsing CALLID "
+				   "header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->callid) {
+		LOG(L_ERR, "AUTH_IDENTITY:callidhdr_proc: CALLID header field is not "
+				   "found\n");
+		return AUTH_NOTFOUND;
+	}
+
+	if(sout)
+		*sout = msg->callid->body;
+
+	return AUTH_OK;
+}
+
+/* CSeq */
+int cseqhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->cseq && (parse_headers(msg, HDR_CSEQ_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:cseqhdr_proc: Error while parsing CSEQ "
+				   "header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->cseq) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:cseqhdr_proc: CSEQ header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	if(!msg->cseq->parsed) {
+		LOG(L_ERR, "AUTH_IDENTITY:cseqhdr_proc: CSEQ is not parsed\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = get_cseq(msg)->number;
+	if(soutopt)
+		*soutopt = get_cseq(msg)->method;
+
+	return AUTH_OK;
+}
+
+/* Date */
+int datehdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if((!msg->date) && (parse_headers(msg, HDR_DATE_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:datehdr_proc: Error while parsing DATE "
+				   "header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->date) {
+		LOG(AUTH_DBG_LEVEL,
+				"AUTH_IDENTITY:datehdr_proc: DATE header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_date_header explicitly */
+	if((!(msg->date)->parsed) && (parse_date_header(msg) < 0)) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:datehdr_proc: Error while parsing DATE body\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = msg->date->body;
+
+	return AUTH_OK;
+}
+
+/* Contact header of the incoming SIP message */
+static int in_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->contact && (parse_headers(msg, HDR_CONTACT_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:in_contacthdr_proc: Error while parsing "
+				   "CONTACT header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->contact) {
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_contact explicitly */
+	if(!msg->contact->parsed && (parse_contact(msg->contact) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:in_contacthdr_proc: Error while parsing "
+				   "CONTACT body\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = ((contact_body_t *)msg->contact->parsed)->contacts->uri;
+
+	return AUTH_OK;
+}
+
+/* Contact header of the outgoing SIP message */
+static int out_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	unsigned int ulen;
+	int ierror;
+	struct dest_info dst;
+	int ires;
+
+
+#ifdef USE_DNS_FAILOVER
+	/* get info about outbound socket */
+	if((uri2dst(NULL, &dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#else
+	if((uri2dst(&dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#endif
+			|| (dst.send_sock == 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:out_contacthdr_proc: Can't determinate "
+				   "destination socket\n");
+		return -1;
+	}
+
+	/* we save it to global variable because we'll process it later */
+	glb_siphdr = build_only_headers(msg, 1, &ulen, &ierror, &dst);
+
+	if(ierror)
+		return -2;
+
+	memset(&glb_contact, 0, sizeof(glb_contact));
+
+	/* parse_contact() needs only the body element of "struct hdr_field" */
+	ires = get_contact_body(glb_siphdr, ulen, &glb_contact.body);
+	if(ires == AUTH_NOTFOUND) {
+		pkg_free(glb_siphdr);
+		glb_siphdr = NULL;
+		return AUTH_NOTFOUND;
+	}
+	if(ires != AUTH_OK) {
+		pkg_free(glb_siphdr);
+		glb_siphdr = NULL;
+		return AUTH_ERROR;
+	}
+
+	if(parse_contact(&glb_contact) < 0) {
+		pkg_free(glb_siphdr);
+		glb_siphdr = NULL;
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = ((contact_body_t *)glb_contact.parsed)->contacts->uri;
+
+	return AUTH_OK;
+}
+
+/* Identity */
+int identityhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->identity && (parse_headers(msg, HDR_IDENTITY_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityhdr_proc: Error while parsing "
+				   "IDENTITY header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->identity) {
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_identityinfo_header explicitly */
+	if((!(msg->identity)->parsed) && (parse_identity_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityhdr_proc: Error while parsing "
+				   "IDENTITY body\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = get_identity(msg)->hash;
+
+	return AUTH_OK;
+}
+
+/* Identity-info */
+int identityinfohdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!msg->identity_info
+			&& (parse_headers(msg, HDR_IDENTITY_INFO_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: Error while parsing "
+				   "IDENTITY-INFO header\n");
+		return AUTH_ERROR;
+	}
+	if(!msg->identity_info) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: IDENTITY-INFO header "
+				   "field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_identityinfo_header explicitly */
+	if((!(msg->identity_info)->parsed)
+			&& (parse_identityinfo_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: Error while parsing "
+				   "IDENTITY-INFO body\n");
+		return AUTH_ERROR;
+	}
+
+	if(sout)
+		*sout = get_identityinfo(msg)->uri;
+	if(soutopt)
+		*soutopt = get_identityinfo(msg)->domain;
+
+	return AUTH_OK;
+}
+
+/* body of the incoming SIP message */
+static int in_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if(!sout)
+		return AUTH_OK;
+
+	sout->s = get_body(msg);
+	if(!sout->s || sout->s[0] == 0) {
+		sout->len = 0;
+	} else {
+		if(!msg->content_length) {
+			LOG(L_ERR, "AUTH_IDENTITY:route_msgbody_proc: no Content-Length "
+					   "header found!\n");
+			return AUTH_ERROR;
+		}
+		sout->len = get_content_length(msg);
+	}
+
+	return AUTH_OK;
+}
+
+/* body of the outgoing SIP message */
+static int out_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+
+	unsigned int len;
+	int err;
+	struct dest_info dst;
+	char scontentlen[AUTH_CONTENTLENGTH_LENGTH];
+
+
+	if(!sout)
+		return AUTH_OK;
+
+#ifdef USE_DNS_FAILOVER
+	/* get info about outbound socket */
+	if((uri2dst(NULL, &dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#else
+	if((uri2dst(&dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#endif
+			|| (dst.send_sock == 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:rtend_msgbody_proc: Can't determinate "
+				   "destination socket\n");
+		return -1;
+	}
+
+	/* we save it to global variable too to be able to free it later */
+	sout->s = glb_msgbody = build_body(msg, &len, &err, &dst);
+	if(err) {
+		LOG(L_ERR, "AUTH_IDENTITY:rtend_msgbody_proc: Can't build body (%d)\n",
+				err);
+		return -2;
+	}
+
+	sout->len = (int)len;
+
+	/* authentication services MUST add a Content-Length header field to
+	 * SIP requests if one is not already present
+	 *
+	 * content-length (if present) must be already parsed and if destination
+	 * protocol is not UDP then core will append Content-Length
+	 */
+	if(!msg->content_length && dst.proto == PROTO_UDP) {
+		snprintf(scontentlen, sizeof(scontentlen), "Content-Length: %d\r\n",
+				len);
+		scontentlen[sizeof(scontentlen) - 1] = 0;
+		/* if HDR_CONTENTLENGTH_T's specified then the header won't be added! */
+		if(append_hf(msg, scontentlen, HDR_OTHER_T)) {
+			pkg_free(glb_msgbody);
+			glb_msgbody = NULL;
+			return -3;
+		}
+	}
+
+	return AUTH_OK;
+}
+
+/* Contact header deinitializer of outgoing message */
+static void free_out_contacthdr(void)
+{
+	void **h_parsed;
+
+	h_parsed = &glb_contact.parsed; /*strict aliasing warnings workaround */
+	if(glb_siphdr) {
+		pkg_free(glb_siphdr);
+		glb_siphdr = NULL;
+	}
+
+	if(glb_contact.parsed)
+		free_contact((contact_body_t **)h_parsed);
+}
+
+/* body deinitializer of the outgoing message */
+static void free_out_msgbody(void)
+{
+	if(glb_msgbody) {
+		pkg_free(glb_msgbody);
+		glb_msgbody = NULL;
+	}
+}
+
+/* Digest-string assebmler function (RFC 4474 [9] */
+int digeststr_asm(dynstr *sout, struct sip_msg *msg, str *sdate, int iflags)
+{
+	/* incoming SIP message parser describer */
+	dgst_part incoming_sip_digest_desc[] = {
+			{DS_FROM, fromhdr_proc, NULL, DS_REQUIRED},
+			{DS_TO, tohdr_proc, NULL, DS_REQUIRED},
+			{DS_CALLID, callidhdr_proc, NULL, DS_REQUIRED},
+			{DS_CSEQ, cseqhdr_proc, NULL, DS_REQUIRED},
+			{DS_DATE, datehdr_proc, NULL, DS_NOTREQUIRED},
+			{DS_CONTACT, in_contacthdr_proc, NULL, DS_NOTREQUIRED},
+			{DS_BODY, in_msgbody_proc, NULL, DS_NOTREQUIRED},
+			{0, NULL, NULL, 0}};
+	/* outgoing SIP message parser describer */
+	dgst_part outgoing_sip_digest_desc[] = {
+			{DS_FROM, fromhdr_proc, NULL, DS_REQUIRED},
+			{DS_TO, tohdr_proc, NULL, DS_REQUIRED},
+			{DS_CALLID, callidhdr_proc, NULL, DS_REQUIRED},
+			{DS_CSEQ, cseqhdr_proc, NULL, DS_REQUIRED},
+			{DS_DATE, datehdr_proc, NULL, DS_NOTREQUIRED},
+			{DS_CONTACT, out_contacthdr_proc, free_out_contacthdr,
+					DS_NOTREQUIRED},
+			{DS_BODY, out_msgbody_proc, free_out_msgbody, DS_NOTREQUIRED},
+			{0, NULL, NULL, 0}};
+	dgst_part *pactpart;
+	dgst_part *sip_digest_desc;
+	str sact, sactopt;
+	int i1;
+	int iRes;
+
+
+	if((iflags & AUTH_INCOMING_BODY) ^ (iflags & AUTH_OUTGOING_BODY)) {
+		(iflags & AUTH_INCOMING_BODY)
+				? (sip_digest_desc = incoming_sip_digest_desc)
+				: (sip_digest_desc = outgoing_sip_digest_desc);
+	} else
+		/* AUTH_INCOMING_BODY or AUTH_OUTGOING_BODY flag must set */
+		return -1;
+
+	resetstr_dynstr(sout);
+
+	for(pactpart = &sip_digest_desc[0], i1 = 0; pactpart[i1].itype; i1++) {
+		iRes = pactpart[i1].pfunc(&sact, &sactopt, msg);
+
+		/* there was an error or the required header is missing */
+		if(iRes == AUTH_ERROR
+				|| (iRes == AUTH_NOTFOUND
+						&& (pactpart[i1].iflag & DS_REQUIRED)))
+			return -1;
+
+		switch(pactpart[i1].itype) {
+			/* Cseq handle (we need SP instead of LWS (RFC4474 [9])) */
+			case DS_CSEQ:
+				if(app2dynstr(sout, &sact))
+					return -1;
+				if(app2dynchr(sout, ' '))
+					return -2;
+				if(app2dynstr(sout, &sactopt))
+					return -3;
+				break;
+			case DS_DATE:
+				if(iRes == AUTH_NOTFOUND) {
+					if(iflags & AUTH_ADD_DATE) {
+						if(app2dynstr(sout, sdate))
+							return -8;
+					} else {
+						/* Date header must exist */
+						LOG(L_ERR, "AUTH_IDENTITY:digeststr_asm: DATE header "
+								   "is not found\n");
+						return -9;
+					}
+					break;
+				}
+				if(app2dynstr(sout, &sact))
+					return -10;
+				break;
+			default:
+				if(iRes == AUTH_NOTFOUND)
+					break;
+				if(app2dynstr(sout, &sact))
+					return -10;
+		}
+
+		/* if there is desctructor function available then we call it */
+		if(pactpart[i1].pfreefunc)
+			pactpart[i1].pfreefunc();
+
+		/* we don't add separator after message body */
+		if(pactpart[i1 + 1].itype) {
+			/* we append the separator */
+			if(app2dynchr(sout, '|'))
+				return -11;
+		}
+	}
+
+	return 0;
+}
+
+/* copypasted and ripped from ser/modules/textops/textops.c) */
+int append_hf(struct sip_msg *msg, char *str1, enum _hdr_types_t type)
+{
+	struct lump *anchor;
+	char *s;
+	int len;
+
+	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Error while parsing message\n");
+		return -1;
+	}
+
+	anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, type);
+	if(anchor == 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Can't get anchor\n");
+		return -1;
+	}
+
+	len = strlen(str1);
+
+	s = (char *)pkg_malloc(len + 1);
+	if(!s) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+
+	memcpy(s, str1, len);
+	s[len] = '\0';
+
+	if(insert_new_lump_before(anchor, s, len, type) == 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Can't insert lump\n");
+		pkg_free(s);
+		return -1;
+	}
+	return 0;
+}
+
+/* get the current system date and appends it to the message */
+int append_date(str *sdate, int idatesize, time_t *tout, struct sip_msg *msg)
+{
+	char date_hf[AUTH_TIME_LENGTH];
+	char date_str[AUTH_TIME_LENGTH];
+	time_t tdate_now;
+	struct tm *bd_time;
+	size_t ilen;
+	int istrlen;
+
+
+	if((tdate_now = time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: time error %s\n",
+				strerror(errno));
+		return -1;
+	}
+	if(!(bd_time = gmtime(&tdate_now))) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: gmtime error\n");
+		return -2;
+	}
+
+	ilen = strftime(date_str, sizeof(date_str), AUTH_TIME_FORMAT, bd_time);
+	if(ilen >= sizeof(date_hf) - strlen("Date: \r\n.") || ilen == 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: unexpected time length\n");
+		return -3;
+	}
+
+	/* we append the date header to the message too */
+	istrlen = strlen("Date: ");
+	memcpy(date_hf, "Date: ", istrlen);
+	memcpy(date_hf + istrlen, date_str, ilen);
+	istrlen += ilen;
+	date_hf[istrlen] = '\r';
+	date_hf[istrlen + 1] = '\n';
+	date_hf[istrlen + 2] = 0;
+	if(append_hf(msg, date_hf, HDR_DATE_T))
+		return -4;
+
+	if(sdate && idatesize >= ilen) {
+		memcpy(sdate->s, date_str, ilen);
+		sdate->len = ilen;
+	} else {
+		return -5;
+	}
+
+	if(tout)
+		*tout = tdate_now;
+
+	return 0;
+}
+
+/*
+ *
+ *	"Contact" header parser part
+ *
+ */
+
+
+/* returns a pointer to the next line */
+static char *auth_next_line(char *buf, char *buf_end)
+{
+	char *c;
+
+	c = buf;
+	do {
+		while((c < buf_end) && (*c != '\n'))
+			c++;
+		if(c < buf_end)
+			c++;
+		if((c < buf_end) && (*c == '\r'))
+			c++;
+
+	} while((c < buf_end)
+			&& ((*c == ' ')
+					|| (*c == '\t'))); /* next line begins with whitespace line folding */
+
+	return c;
+}
+
+/*
+ * Skip all white-chars and return position of the first
+ * non-white char
+ */
+static inline char *skip_ws(char *p, unsigned int size)
+{
+	char *end;
+
+	end = p + size;
+	for(; p < end; p++) {
+		if((*p != ' ') && (*p != '\t'))
+			return p;
+	}
+	return p;
+}
+
+/* looks for "Contact" header */
+static char *auth_get_hf_name(char *begin, char *end, enum _hdr_types_t *type)
+{
+	char *p;
+	unsigned int val;
+
+
+	if(end - begin < 4) {
+		*type = HDR_ERROR_T;
+		return begin;
+	}
+
+	p = begin;
+	val = LOWER_DWORD(READ(p));
+
+	switch(val) {
+		case _cont_: /* Content-Length */
+			p += 4;
+			switch(LOWER_DWORD(READ(p))) {
+				case _act1_:
+					*type = HDR_CONTACT_T;
+					return (p + 4);
+				case _act2_:
+					*type = HDR_CONTACT_T;
+					p += 4;
+					goto dc_end;
+			}
+			*type = HDR_OTHER_T;
+			break;
+		default:
+			/* compact headers */
+			switch(LOWER_BYTE(*p)) {
+				case 'm':
+					switch(*(p + 1)) {
+						case ' ':
+							*type = HDR_CONTACT_T;
+							p += 2;
+							goto dc_end;
+						case ':':
+							*type = HDR_CONTACT_T;
+							return (p + 2);
+					}
+					*type = HDR_OTHER_T;
+					break;
+				default:
+					*type = HDR_OTHER_T;
+					break;
+			}
+	}
+
+dc_end:
+	p = skip_ws(p, end - p);
+	if(*p != ':') {
+		goto other;
+	} else {
+		return (p + 1);
+	}
+
+	/* Unknown header type */
+other:
+	p = q_memchr(p, ':', end - p);
+	if(!p) { /* No double colon found, error.. */
+		*type = HDR_ERROR_T;
+		return 0;
+	} else {
+		*type = HDR_OTHER_T;
+		return (p + 1);
+	}
+
+	return p;
+}
+
+/* parses buffer that contains a SIP message header, looks for "Contact"
+ * header field and returns the value of that */
+static int get_contact_body(char *buf, unsigned int len, str *sout)
+{
+	char *end, *s, *tmp, *match;
+	enum _hdr_types_t hf_type;
+
+
+	end = buf + len;
+	s = buf;
+
+	memset(sout, 0, sizeof(*sout));
+
+	while(s < end) {
+		if((*s == '\n') || (*s == '\r')) {
+			/* end of SIP msg */
+			hf_type = HDR_EOH_T;
+		} else {
+			/* parse HF name */
+			if(!(s = auth_get_hf_name(s, end, &hf_type)))
+				return AUTH_ERROR;
+		}
+
+		switch(hf_type) {
+			case HDR_CONTACT_T:
+				tmp = eat_lws_end(s, end);
+				if(tmp >= end) {
+					LOG(L_ERR, "AUTH_IDENTITY:get_contact_body: get_hdr_field: "
+							   "HF empty\n");
+					return AUTH_ERROR;
+				}
+				sout->s = tmp;
+				/* find lf */
+				do {
+					match = q_memchr(tmp, '\n', end - tmp);
+					if(match) {
+						match++;
+					} else {
+						LOG(L_ERR, "AUTH_IDENTITY:get_contact_body: bad msg "
+								   "body\n");
+						return AUTH_ERROR;
+					}
+					tmp = match;
+				} while(match < end && ((*match == ' ') || (*match == '\t')));
+				tmp = match;
+				sout->len = match - sout->s;
+				trim_r(*sout);
+				return AUTH_OK;
+				break;
+			case HDR_ERROR_T:
+				return AUTH_ERROR;
+			default:
+				s = auth_next_line(s, end);
+		}
+	}
+
+	return AUTH_NOTFOUND;
+}

+ 112 - 0
src/modules/auth_identity/auth_http.c

@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: HTTP
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <openssl/bio.h>
+
+
+#include "../../core/mem/mem.h"
+#include "../../core/data_lump.h"
+
+#include "auth_identity.h"
+
+size_t curlmem_cb(void *ptr, size_t size, size_t nmemb, void *data)
+{
+	size_t irealsize = size * nmemb;
+
+	/* too big certificate */
+	if(((str *)data)->len + irealsize >= CERTIFICATE_LENGTH)
+		return 0;
+
+	memcpy(&(((str *)data)->s[((str *)data)->len]), ptr, irealsize);
+	((str *)data)->len += irealsize;
+
+	return irealsize;
+}
+
+int download_cer(str *suri, CURL *hcurl)
+{
+	CURLcode iRes;
+	long lerr = 200;
+	char snulled[CERTIFICATE_URL_LENGTH], *snulledptr = NULL;
+	int iRet = 0;
+
+	if(suri->len < sizeof(snulled)) {
+		memcpy(snulled, suri->s, suri->len);
+		snulled[suri->len] = 0;
+	} else {
+		/* +1 for the terminating \0 byte */
+		if(!(snulledptr = pkg_malloc(suri->len + 1))) {
+			PKG_MEM_ERROR;
+			return -1;
+		}
+		memcpy(snulledptr, suri->s, suri->len);
+		snulledptr[suri->len] = 0;
+	}
+
+	do {
+		if((iRes = curl_easy_setopt(
+					hcurl, CURLOPT_URL, snulledptr ? snulledptr : snulled))
+				!= 0) {
+			LOG(L_ERR,
+					"AUTH_IDENTITY:download_cer: Unable to set the url of "
+					"certificate: %s\n",
+					curl_easy_strerror(iRes));
+			iRet = -2;
+			break;
+		}
+
+		if((iRes = curl_easy_perform(hcurl)) != 0) {
+			LOG(L_ERR,
+					"AUTH_IDENTITY:download_cer: Error while downloading "
+					"certificate '%s'\n",
+					curl_easy_strerror(iRes));
+			iRet = -3;
+			break;
+		}
+
+		curl_easy_getinfo(hcurl, CURLINFO_RESPONSE_CODE, &lerr);
+		if(lerr / 100 != 2) {
+			LOG(L_ERR, "AUTH_IDENTITY:download_cer: Bad HTTP response: %ld\n",
+					lerr);
+			iRet = -4;
+		}
+	} while(0);
+
+	if(snulledptr)
+		pkg_free(snulledptr);
+
+	return iRet;
+}

+ 878 - 0
src/modules/auth_identity/auth_identity.c

@@ -0,0 +1,878 @@
+/*
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: Module interface
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+/*! \defgroup auth-identity Kamailio SIP identity support
+ *
+ * Auth Identity module provides functionalities for securely identifying
+ * originators of SIP messages. This module has two basic service:
+ *   - authorizer - authorizes a message and adds Identity and Identity-Info headers
+ *   - verifier - verifies an authorized message
+ *
+ */
+
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include "../../core/dprint.h"
+#include "../../core/ut.h"
+#include "../../core/sr_module.h"
+#include "../../core/mem/mem.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_cseq.h"
+#include "../../core/parser/parse_content.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/contact/parse_contact.h"
+#include "../../core/timer.h"
+
+#include "auth_identity.h"
+
+MODULE_VERSION;
+
+static int mod_init(void); /* Module initialization function */
+static void mod_deinit(void);
+static int add_identity(struct sip_msg *msg, char *srt1, char *str2);
+static int get_certificate(struct sip_msg *msg, char *srt1, char *str2);
+static int check_validity(struct sip_msg *msg, char *srt1, char *str2);
+static int check_date(struct sip_msg *msg, char *srt1, char *str2);
+static int check_callid(struct sip_msg *msg, char *srt1, char *str2);
+static int date_proc(struct sip_msg *msg, char *srt1, char *str2);
+static int check_certificate(struct sip_msg *msg, char *srt1, char *str2);
+void callid_gc(unsigned int tick, void *param);
+
+/*
+ * Module parameter variables
+ */
+char *glb_sprivkeypath = ""; /* private key of the authentication service */
+char *glb_sservercerturl =
+		""; /* URL of the certificate of the authentication service */
+char *glb_sservercertpath =
+		""; /* Path of the certificate of the authentication service */
+int glb_icertlimit = CERTIFICATE_TABLE_ITEM_LIMIT;
+char *glb_scainfo = "";
+int glb_iauthval =
+		AUTH_MSG_VALIDITY_TIME; /* Message validity time in seconds (verification service)*/
+int glb_imsgtime =
+		AUTH_MSG_TO_AUTH_VALIDITY_TIME; /* Message validity time in seconds (authentication service)*/
+int glb_icallidlimit = CALLID_TABLE_ITEM_LIMIT;
+
+CURL *glb_hcurl; /* global cURL handle */
+X509 *glb_pcertx509 = NULL;
+X509_STORE *glb_cacerts = NULL;
+
+RSA *glb_hmyprivkey = NULL; /* private key of the authentication service */
+time_t glb_imycertnotafter = 0;
+
+int glb_authservice_disabled = 0;
+int glb_acceptpem = 0;
+
+dynstr glb_sdgst = {{0, 0}, 0};			/* Digest string */
+dynstr glb_sidentity = {{0, 0}, 0};		/* Identity message header */
+dynstr glb_sidentityinfo = {{0, 0}, 0}; /* Identity-info message header */
+dynstr glb_sdate = {{0, 0}, 0};			/* Date  message header */
+
+dynstr glb_encedmsg = {{0, 0}, 0}; /* buffer for rsa encrypted string */
+dynstr glb_b64encedmsg = {
+		{0, 0}, 0}; /* buffer for base64, rsa encrypted string */
+
+ttable *glb_tcert_table = 0; /* Certificate Table */
+char glb_certisdownloaded = 0;
+tcert_item glb_tcert = {{0, 0}, {0, 0}, 0}; /* Actually Used Certificate */
+
+ttable *glb_tcallid_table = 0; /* Certificate Table */
+typedef struct timeparams
+{			   /* sturct of the callid garbage collector */
+	int ibnow; /* the actual bucket we've not checked yet */
+	int ibnum; /* number of the buckets we've to check */
+	int ibcir; /* timer function's called this times during the whole table check */
+} ttimeparams;
+ttimeparams glb_ttimeparams = {0, 0, 0};
+
+/*
+ * Exported functions
+ */
+static cmd_export_t glb_cmds[] = {
+		{"auth_date_proc", date_proc, 0, 0, 0, REQUEST_ROUTE},
+		{"auth_add_identity", add_identity, 0, 0, 0, REQUEST_ROUTE},
+		{"vrfy_get_certificate", get_certificate, 0, 0, 0, REQUEST_ROUTE},
+		{"vrfy_check_msgvalidity", check_validity, 0, 0, 0, REQUEST_ROUTE},
+		{"vrfy_check_certificate", check_certificate, 0, 0, 0, REQUEST_ROUTE},
+		{"vrfy_check_date", check_date, 0, 0, 0, REQUEST_ROUTE},
+		{"vrfy_check_callid", check_callid, 0, 0, 0, REQUEST_ROUTE},
+		{0, 0, 0, 0, 0, 0}};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t glb_params[] = {
+		{"privatekey_path", PARAM_STRING, &glb_sprivkeypath},
+		{"certificate_url", PARAM_STRING, &glb_sservercerturl},
+		{"certificate_cache_limit", PARAM_INT, &glb_icertlimit},
+		{"callid_cache_limit", PARAM_INT, &glb_icallidlimit},
+		{"certificate_path", PARAM_STRING, &glb_sservercertpath},
+		{"auth_validity_time", PARAM_INT, &glb_iauthval},
+		{"msg_timeout", PARAM_INT, &glb_imsgtime},
+		{"cainfo_path", PARAM_STRING, &glb_scainfo},
+		{"accept_pem_certs", PARAM_INT, &glb_acceptpem}, {0, 0, 0}};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+		"auth_identity", DEFAULT_DLFLAGS, /* dlopen flags */
+		glb_cmds,						  /* Exported functions */
+		glb_params,						  /* Exported parameters */
+		0,								  /* RPC methods */
+		0,								  /* pseudo-variables exports */
+		0,								  /* response function */
+		mod_init,						  /* module initialization function */
+		0,								  /* child initialization function */
+		mod_deinit						  /* destroy function */
+};
+
+
+static int mod_init(void)
+{
+	CURLcode iRet;
+	str sstr;
+	FILE *hpemfile;
+	char serr[160];
+	X509 *pmycert = NULL; /* certificate of the authentication service */
+	time_t tnow, ttmp;
+
+	/*
+	 *
+	 * Parameter check
+	 *
+	 */
+	if(glb_sprivkeypath[0] == 0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Private key path is missing! "
+					"Authorization service is disabled\n");
+		glb_authservice_disabled = 1;
+	}
+	if(!glb_authservice_disabled && glb_sservercerturl[0] == 0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: URL of certificate of the server "
+					"is missing! Authorization service is disabled\n");
+		glb_authservice_disabled = 1;
+	}
+	if(!glb_authservice_disabled && glb_sservercertpath[0] == 0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Path of certificate of the server "
+					"is missing! Authorization service is disabled\n");
+		glb_authservice_disabled = 1;
+	}
+
+	/*
+	 *
+	 * Init the curl session and download buffer
+	 *
+	 */
+	curl_global_init(CURL_GLOBAL_ALL);
+	if((glb_hcurl = curl_easy_init()) == NULL) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Unable to init cURL library!\n");
+		return -1;
+	}
+	/* send all data to this function  */
+	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_WRITEFUNCTION, curlmem_cb))
+			!= 0) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL write function "
+				"option: %s\n",
+				curl_easy_strerror(iRet));
+		return -2;
+	}
+	/* we pass our 'glb_tcert' struct to the callback function */
+	if((iRet = curl_easy_setopt(
+				glb_hcurl, CURLOPT_WRITEDATA, (void *)(&glb_tcert.scertpem)))
+			!= 0) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL writedata option: "
+				"%s\n",
+				curl_easy_strerror(iRet));
+		return -4;
+	}
+	if(!(glb_tcert.scertpem.s = pkg_malloc(CERTIFICATE_LENGTH))) {
+		PKG_MEM_ERROR;
+		return -3;
+	}
+	/* some servers don't like requests that are made without a user-agent
+	   field, so we provide one */
+	if((iRet = curl_easy_setopt(
+				glb_hcurl, CURLOPT_USERAGENT, NAME "-Agent/1.0"))
+			!= 0) {
+		LOG(L_WARN,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL useragent option: "
+				"%s\n",
+				curl_easy_strerror(iRet));
+	}
+	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYPEER, 1)) != 0) {
+		LOG(L_WARN,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL verifypeer option: "
+				"%s\n",
+				curl_easy_strerror(iRet));
+	}
+	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYHOST, 2)) != 0) {
+		LOG(L_WARN,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL verifyhost option: "
+				"%s\n",
+				curl_easy_strerror(iRet));
+	}
+
+	/* cainfo_path module parameter's been set */
+	if(glb_scainfo[0]) {
+		if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_CAINFO, glb_scainfo))
+				!= 0) {
+			LOG(L_WARN,
+					"AUTH_IDENTITY:mod_init: Unable to set cURL cainfo option: "
+					"%s\n",
+					curl_easy_strerror(iRet));
+		}
+	}
+
+
+	/*
+	 *
+	 * OpenSSL certificate verification initialization
+	 *
+	 */
+	OpenSSL_add_all_algorithms();
+	if(!(glb_cacerts = X509_STORE_new())) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to initialize X509 store\n");
+		return -16;
+	}
+	if(X509_STORE_set_default_paths(glb_cacerts) != 1) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to set X509 store default "
+				   "path\n");
+		return -17;
+	}
+	if(glb_scainfo[0]
+			&& X509_STORE_load_locations(glb_cacerts, glb_scainfo, NULL) != 1)
+		LOG(L_WARN,
+				"AUTH_IDENTITY:mod_init: unable to load X509 store location\n");
+
+
+	/*
+	 *
+	 * Init the Date, Digest-String, Identity and Identity-Info
+	 *
+	 */
+	if(initdynstr(&glb_sdgst, DGST_STR_INIT_SIZE))
+		return -5;
+
+	/*
+	 * Init certificate table
+	 */
+	if(init_table(&glb_tcert_table, CERTIFICATE_TABLE_ENTRIES, glb_icertlimit,
+			   cert_item_cmp, cert_item_init, cert_item_least, cert_item_free,
+			   NULL))
+		return -5;
+
+	/*
+	 * Init call-id table
+	 */
+	if(init_table(&glb_tcallid_table, CALLID_TABLE_ITEM_LIMIT, glb_icallidlimit,
+			   cid_item_cmp, cid_item_init, cid_item_least, cid_item_free,
+			   cid_item_gc))
+		return -5;
+
+	glb_ttimeparams.ibnow = 0;
+	/* we've to check the whole table in glb_imsgtime, so the number of
+	   buckets we've to check in every timer call is
+	   CALLID_TABLE_ENTRIES/glb_imsgtime/CALLID_GARBAGE_COLLECTOR_INTERVAL */
+	glb_ttimeparams.ibcir = glb_iauthval / CALLID_GARBAGE_COLLECTOR_INTERVAL;
+	if(!glb_ttimeparams.ibcir)
+		glb_ttimeparams.ibcir = 1;
+	glb_ttimeparams.ibnum = CALLID_TABLE_ENTRIES / glb_ttimeparams.ibcir;
+
+	if(register_timer(callid_gc, (void *)&glb_ttimeparams /* param*/,
+			   CALLID_GARBAGE_COLLECTOR_INTERVAL /* period */)
+			< 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Can not register timer\n");
+		return -5;
+	}
+
+	/*
+	 * If there were not enough parameter set then we could not initialize
+	 * the authorizer part
+	 */
+	if(glb_authservice_disabled)
+		return 0;
+
+
+	if(initdynstr(&glb_sidentity, DGST_STR_INIT_SIZE))
+		return -6;
+
+	if(initdynstr(&glb_sdate, AUTH_TIME_LENGTH))
+		return -7;
+
+	if(initdynstr(&glb_sidentityinfo, AUTH_URL_LENGTH))
+		return -8;
+
+	/* we initialize indentity info header */
+	sstr.s = IDENTITY_INFO_FIRST_PART;
+	sstr.len = strlen(IDENTITY_INFO_FIRST_PART);
+	if(cpy2dynstr(&glb_sidentityinfo, &sstr))
+		return -9;
+	sstr.s = glb_sservercerturl;
+	sstr.len = strlen(glb_sservercerturl);
+	if(app2dynstr(&glb_sidentityinfo, &sstr))
+		return -10;
+	sstr.s = IDENTITY_INFO_LAST_PART;
+	/* we copy the trailing \0 because append_hf expects strings */
+	sstr.len = strlen(IDENTITY_INFO_LAST_PART) + 1;
+	if(app2dynstr(&glb_sidentityinfo, &sstr))
+		return -11;
+
+	/*
+  	 * Get my certificate
+	 */
+	if(!(hpemfile = fopen(glb_sservercertpath, "r"))) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open certificate '%s'\n",
+				strerror(errno));
+		return -12;
+	}
+	if(!(pmycert = PEM_read_X509(hpemfile, NULL, NULL, NULL))) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
+		fclose(hpemfile);
+		return -13;
+	}
+	if(x509_get_notafter(&glb_imycertnotafter, pmycert)) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate "
+				   "expiration date\n");
+		fclose(hpemfile);
+		return -13;
+	}
+	if(x509_get_notbefore(&ttmp, pmycert)) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate validity "
+				   "date\n");
+		fclose(hpemfile);
+		return -13;
+	}
+	if((tnow = time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: time error %s\n", strerror(errno));
+		fclose(hpemfile);
+		return -13;
+	}
+	if(tnow < ttmp || tnow > glb_imycertnotafter) {
+		LOG(L_ERR,
+				"AUTH_IDENTITY:mod_init: Date of certificate is invalid (%s)\n",
+				glb_sservercertpath);
+		fclose(hpemfile);
+		return -14;
+	}
+
+	if(fclose(hpemfile))
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");
+	X509_free(pmycert);
+
+	/*
+	 *
+ 	 * Init RSA-SHA1 encoder
+	 *
+	 */
+	hpemfile = fopen(glb_sprivkeypath, "r");
+	if(!hpemfile) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open private key '%s'\n",
+				strerror(errno));
+		return -12;
+	}
+	glb_hmyprivkey = PEM_read_RSAPrivateKey(hpemfile, NULL, NULL, NULL);
+	if(!glb_hmyprivkey) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
+		fclose(hpemfile);
+		return -13;
+	}
+	if(fclose(hpemfile))
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");
+
+	/* we encrypt the digest string hash to this buffer */
+	if(initdynstr(&glb_encedmsg, RSA_size(glb_hmyprivkey)))
+		return -14;
+
+	/* we base64 encode the encrypted digest string hash to this buffer */
+	if(initdynstr(&glb_b64encedmsg, (RSA_size(glb_hmyprivkey) / 3 + 1) * 4))
+		return -15;
+
+	return 0;
+}
+
+
+static void mod_deinit(void)
+{
+	curl_easy_cleanup(glb_hcurl);
+	if(glb_tcert.scertpem.s)
+		pkg_free(glb_tcert.scertpem.s);
+	free_dynstr(&glb_sdgst);
+	free_dynstr(&glb_sidentity);
+	free_dynstr(&glb_sdate);
+	free_table(glb_tcert_table);
+	free_table(glb_tcallid_table);
+
+	if(glb_cacerts)
+		X509_STORE_free(glb_cacerts);
+}
+
+
+/*
+ *
+ *	VERIFIER FUNCTIONS
+ *
+ */
+
+
+static int get_certificate(struct sip_msg *msg, char *srt1, char *str2)
+{
+	if(identityinfohdr_proc(&glb_tcert.surl, NULL, msg))
+		return -3;
+
+	/* we support rsa-sha1 only (alg.len==0 then we use rsa-sha1) */
+	if(get_identityinfo(msg)->alg.len
+			&& (get_identityinfo(msg)->alg.len != strlen("rsa-sha1")
+					|| strncasecmp("rsa-sha1", get_identityinfo(msg)->alg.s,
+							get_identityinfo(msg)->alg.len))) {
+		LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Unsupported Identity-Info "
+				   "algorithm\n");
+		return -5;
+	}
+
+	/* this case ivalidbefore==0 signs that this certificate was downloaded */
+	glb_tcert.ivalidbefore = 0;
+
+	/* check whether this certificate is our certificate table */
+	if(get_cert_from_table(glb_tcert_table, &glb_tcert.surl, &glb_tcert)) {
+		/* we did not found it in the table, so we've to download it */
+		/* we reset the PEM buffer */
+		glb_tcert.scertpem.len = 0;
+		if(download_cer(&glb_tcert.surl, glb_hcurl))
+			return -6;
+		glb_certisdownloaded = 1;
+	} else
+		glb_certisdownloaded = 0;
+
+	if(retrieve_x509(&glb_pcertx509, &glb_tcert.scertpem, glb_acceptpem))
+		return -7;
+
+
+	return 1;
+}
+
+/*
+ * If the digest-string, assembled from the message, corresponds to the string
+ * decoded from the Identity header by the acquired public key then the message
+ * is valid. RFC 4474 [6] Step 3
+ */
+static int check_validity(struct sip_msg *msg, char *srt1, char *str2)
+{
+	str sidentity;
+	char sencedsha[HASH_STR_SIZE];
+	int iencedshalen;
+#ifndef NEW_RSA_PROC
+	char ssha[HASH_STR_SIZE];
+#endif
+	int ishalen;
+	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
+	int iRet = 1;
+
+
+	if(!glb_pcertx509) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_validity: Certificate uninitialized! "
+				   "(has vrfy_get_certificate been called?)\n");
+		return -1;
+	}
+
+	do {
+		/* get the value of identity header parsed */
+		if(identityhdr_proc(&sidentity, NULL, msg)) {
+			iRet = -1;
+			break;
+		}
+
+		/* the length of identity value should be 172 octets long */
+		if(sidentity.len > sizeof(sencedsha)) {
+			LOG(L_ERR,
+					"AUTH_IDENTITY:check_validity: Unexpected Identity length "
+					"(%d)\n",
+					sidentity.len);
+			iRet = -2;
+			break;
+		}
+
+		/* base64 decode the value of Identity header */
+		base64decode(sidentity.s, sidentity.len, sencedsha, &iencedshalen);
+
+		/* assemble the digest string to be able to compare it with decrypted one */
+		if(digeststr_asm(&glb_sdgst, msg, NULL, AUTH_INCOMING_BODY)) {
+			iRet = -5;
+			break;
+		}
+		/* calculate hash */
+		SHA1((unsigned char *)getstr_dynstr(&glb_sdgst).s,
+				getstr_dynstr(&glb_sdgst).len, sstrcrypted);
+
+#ifdef NEW_RSA_PROC
+		/* decrypt with public key retrieved from the downloaded certificate
+		   and compare it with the calculated digest hash */
+		if(rsa_sha1_dec(sencedsha, iencedshalen, (char *)sstrcrypted,
+				   sizeof(sstrcrypted), &ishalen, glb_pcertx509)) {
+			iRet = -3;
+			break;
+		} else
+			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
+#else
+		/* decrypt with public key retrieved from the downloaded certificate */
+		if(rsa_sha1_dec(sencedsha, iencedshalen, ssha, sizeof(ssha), &ishalen,
+				   glb_pcertx509)) {
+			iRet = -3;
+			break;
+		}
+
+		/* check size */
+		if(ishalen != sizeof(sstrcrypted)) {
+			LOG(L_ERR,
+					"AUTH_IDENTITY:check_validity: Unexpected decrypted hash "
+					"length (%d != %d)\n",
+					ishalen, SHA_DIGEST_LENGTH);
+			iRet = -4;
+			break;
+		}
+		/* compare */
+		if(memcmp(sstrcrypted, ssha, ishalen)) {
+			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: comparing hashes failed -> "
+						"Invalid Identity Header\n");
+			iRet = -6;
+			break;
+		} else
+			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
+#endif
+	} while(0);
+
+	glb_pcertx509 = NULL;
+
+	return iRet;
+}
+
+/*
+ * The Date header must indicate a time within 3600 seconds of the receipt of a
+ * message. RFC 4474 [6] Step 4
+ */
+static int check_date(struct sip_msg *msg, char *srt1, char *str2)
+{
+	time_t tnow, tmsg;
+	int ires;
+
+	ires = datehdr_proc(NULL, NULL, msg);
+	if(ires)
+		return -1;
+
+
+#ifdef HAVE_TIMEGM
+	tmsg = timegm(&get_date(msg)->date);
+#else
+	tmsg = _timegm(&get_date(msg)->date);
+#endif
+	if(tmsg < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_date: timegm error\n");
+		return -2;
+	}
+
+	if((tnow = time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_date: time error %s\n",
+				strerror(errno));
+		return -3;
+	}
+
+	if(tnow > tmsg + glb_iauthval) {
+		LOG(L_INFO,
+				"AUTH_IDENTITY VERIFIER: Outdated date header value "
+				"(%" TIME_T_FMT " sec)\n",
+				TIME_T_CAST(tnow - tmsg + glb_iauthval));
+		return -4;
+	} else
+		LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Date header value OK\n");
+
+	return 1;
+}
+
+
+int check_certificate(struct sip_msg *msg, char *srt1, char *str2)
+{
+	struct sip_uri tfrom_uri;
+	str suri;
+
+	if(!glb_pcertx509) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_certificate: Certificate "
+				   "uninitialized! (has vrfy_get_certificate been called?)\n");
+		return -1;
+	}
+	/* this certificate was downloaded so we've to verify and add it to table */
+	if(glb_certisdownloaded) {
+		if(fromhdr_proc(&suri, NULL, msg))
+			return -1;
+
+		if(parse_uri(suri.s, suri.len, &tfrom_uri)) {
+			LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Error while parsing "
+					   "FROM URI\n");
+			return -2;
+		}
+
+		if(verify_x509(glb_pcertx509, glb_cacerts))
+			return -3;
+
+		if(check_x509_subj(glb_pcertx509, &tfrom_uri.host))
+			return -4;
+
+		/* we retrieve expiration date from the certificate (it needs for
+		   certificate table garbage collector) */
+		if(x509_get_notafter(&glb_tcert.ivalidbefore, glb_pcertx509))
+			return -5;
+
+		if(addcert2table(glb_tcert_table, &glb_tcert))
+			return -6;
+	}
+	return 1;
+}
+
+static int check_callid(struct sip_msg *msg, char *srt1, char *str2)
+{
+	str scid, sftag, scseqnum;
+	unsigned int ucseq;
+	int ires;
+	time_t ivalidbefore;
+
+
+	if(callidhdr_proc(&scid, NULL, msg))
+		return -1;
+
+	if(cseqhdr_proc(&scseqnum, NULL, msg))
+		return -2;
+	if(str2int(&scseqnum, &ucseq))
+		return -3;
+
+	if(fromhdr_proc(NULL, &sftag, msg))
+		return -4;
+
+	if((ivalidbefore = time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_callid: time error %s\n",
+				strerror(errno));
+		return -5;
+	}
+
+	ires = proc_cid(glb_tcallid_table, &scid, &sftag, ucseq,
+			ivalidbefore + glb_iauthval);
+	if(ires) {
+		if(ires == AUTH_FOUND)
+			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: Call is replayed!\n");
+		return -6;
+	}
+
+	return 1;
+}
+
+
+void callid_gc(unsigned int tick, void *param)
+{
+	/* check the last slice */
+	if(((ttimeparams *)param)->ibnow + 1 == ((ttimeparams *)param)->ibcir) {
+		garbage_collect(glb_tcallid_table,
+				(((ttimeparams *)param)->ibnow) * ((ttimeparams *)param)->ibnum,
+				CALLID_TABLE_ENTRIES - 1);
+		/* we step to the first slice */
+		((ttimeparams *)param)->ibnow = 0;
+	} else {
+		garbage_collect(glb_tcallid_table,
+				(((ttimeparams *)param)->ibnow) * ((ttimeparams *)param)->ibnum,
+				((((ttimeparams *)param)->ibnow + 1)
+						* ((ttimeparams *)param)->ibnum)
+						- 1);
+		/* we step to the next slice */
+		((ttimeparams *)param)->ibnow++;
+	}
+}
+
+/*
+ *
+ *	AUTHORIZER FUNCTIONS
+ *
+ */
+
+/* Checks the Date header of the message. RFC4474 [5] Step 3 */
+static int date_proc(struct sip_msg *msg, char *srt1, char *str2)
+{
+	str sdate;
+	int iRes;
+	time_t tmsg, tnow;
+
+	if(glb_authservice_disabled) {
+		LOG(L_WARN, "AUTH_IDENTITY:date_proc: Authentication Service is "
+					"disabled\n");
+		return -1;
+	}
+
+	getstr_dynstr(&glb_sdate).len = 0;
+
+	/* we'd like to get the DATE header of the message */
+	iRes = datehdr_proc(&sdate, NULL, msg);
+	switch(iRes) {
+		case AUTH_ERROR:
+			return -1;
+		case AUTH_NOTFOUND:
+			if(append_date(
+					   &getstr_dynstr(&glb_sdate), glb_sdate.size, &tmsg, msg))
+				return -2;
+			break;
+		/* Message has Date header so we check that */
+		case AUTH_OK:
+#ifdef HAVE_TIMEGM
+			tmsg = timegm(&get_date(msg)->date);
+#else
+			tmsg = _timegm(&get_date(msg)->date);
+#endif
+			if(tmsg < 0) {
+				LOG(L_ERR, "AUTH_IDENTITY:date_proc: timegm error\n");
+				return -3;
+			}
+			if((tnow = time(NULL)) < 0) {
+				LOG(L_ERR, "AUTH_IDENTITY:date_proc: time error\n");
+				return -4;
+			}
+			/*
+			 * If the value of this field contains a time different by more than
+			 * ten minutes from the current time noted by the authentication
+			 * service then it should reject the message.
+			 */
+			if(tmsg + glb_imsgtime < tnow || tnow + glb_imsgtime < tmsg) {
+				LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: Date header overdue\n");
+				return -6;
+			}
+			break;
+		default:
+			/* unknown result */
+			return -7;
+	}
+
+	/*
+	 * The authentication service MUST verify that the Date header
+	 * falls within the validity period of its certificate
+	 * RFC 4474 [6] Step 3
+	 */
+	if(glb_imycertnotafter < tmsg) {
+		LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: My certificate has expired\n");
+		return -8;
+	}
+
+	return 1;
+}
+
+/*
+ * Concates the message From, To, Call-ID, Cseq, Date,  Contact header fields
+ * and the message body to digest-string, signs with the domain private-key,
+ * BASE64 encodes that, and finally adds it to the message as the 'Identity'
+ * header value. RFC4474 [5] Step 4
+ *
+ * Adds Identity-Info header to the message which contains an URI from which
+ * its certificate can be acquired. RFC4474 [5] Step 4
+ */
+static int add_identity(struct sip_msg *msg, char *srt1, char *str2)
+{
+	int iRes;
+	str sstr;
+
+
+	if(glb_authservice_disabled) {
+		LOG(L_WARN, "AUTH_IDENTITY:add_identity: Authentication Service is "
+					"disabled\n");
+		return -1;
+	}
+
+	/* check Date */
+	iRes = datehdr_proc(NULL, NULL, msg);
+	switch(iRes) {
+		case AUTH_ERROR:
+			return -1;
+		case AUTH_NOTFOUND:
+			if(!getstr_dynstr(&glb_sdate).len) {
+				/*
+				 * date_proc() must be called before add_identity() because
+				 * that function initializes the Date if that not exists
+				 * in the SIP message
+				 */
+				LOG(L_ERR, "AUTH_IDENTITY:add_identity: Date header is not "
+						   "found (has auth_date_proc been called?)\n");
+				return -2;
+			}
+			/*  assemble the digest string and the DATE header is missing in the original message */
+			if(digeststr_asm(&glb_sdgst, msg, &getstr_dynstr(&glb_sdate),
+					   AUTH_OUTGOING_BODY | AUTH_ADD_DATE))
+				return -3;
+			break;
+		default:
+			/*  assemble the digest string and the DATE header is available in the message */
+			if(digeststr_asm(&glb_sdgst, msg, NULL, AUTH_OUTGOING_BODY))
+				return -4;
+			break;
+	}
+
+	/* calculate the SHA1 hash and encrypt with our provate key */
+	if(rsa_sha1_enc(
+			   &glb_sdgst, &glb_encedmsg, &glb_b64encedmsg, glb_hmyprivkey))
+		return -5;
+
+	/* we assemble the value of the Identity haader */
+	sstr.s = IDENTITY_FIRST_PART;
+	sstr.len = strlen(IDENTITY_FIRST_PART);
+	if(cpy2dynstr(&glb_sidentity, &sstr))
+		return -6;
+
+	if(app2dynstr(&glb_sidentity, &getstr_dynstr(&glb_b64encedmsg)))
+		return -7;
+
+	sstr.s = IDENTITY_LAST_PART;
+	/* +1 : we need the trailing \0 character too */
+	sstr.len = strlen(IDENTITY_LAST_PART) + 1;
+	if(app2dynstr(&glb_sidentity, &sstr))
+		return -8;
+
+	if(append_hf(msg, getstr_dynstr(&glb_sidentity).s, HDR_IDENTITY_T))
+		return -9;
+
+	if(append_hf(msg, getstr_dynstr(&glb_sidentityinfo).s, HDR_IDENTITY_INFO_T))
+		return -10;
+
+	return 1;
+}

+ 266 - 0
src/modules/auth_identity/auth_identity.h

@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+/*!
+ * \file
+ * \brief SIP-router auth-identity :: Module interface
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#ifndef AUTH_IDENT_H
+#define AUTH_IDENT_H
+
+#include <openssl/x509.h>
+#include <curl/curl.h>
+
+#include "../../core/locking.h"
+#include "../../core/mem/mem.h"
+#include "../../core/parser/msg_parser.h" /* struct sip_msg */
+#include "../../core/str.h"				  /* struct str */
+#include "../../core/parser/parse_identity.h"
+#include "../../core/parser/parse_identityinfo.h"
+#include "../../core/parser/parse_date.h"
+
+#define NEW_RSA_PROC
+
+#define AUTH_DBG_LEVEL L_DBG
+
+#define AUTH_URL_LENGTH 512
+#define CERTIFICATE_URL_LENGTH AUTH_URL_LENGTH
+#define CERTIFICATE_LENGTH 8 * 1024
+#define DGST_STR_INIT_SIZE 8 * 1024
+#define HASH_STR_SIZE 1024
+#define AUTH_TIME_FORMAT "%a, %d %b %Y %H:%M:%S GMT"
+#define AUTH_TIME_LENGTH 64
+#define AUTH_CONTENTLENGTH_LENGTH AUTH_TIME_LENGTH
+#define AUTH_DOMAIN_LENGTH 256
+#define IDENTITY_INFO_FIRST_PART "Identity-Info: <"
+#define IDENTITY_INFO_LAST_PART ">;alg=rsa-sha1\r\n"
+
+#define IDENTITY_FIRST_PART "Identity: \""
+#define IDENTITY_LAST_PART "\"\r\n"
+
+#define ITEM_IN_BUCKET_LIMIT 8
+
+#define CERTIFICATE_TABLE_ENTRIES (2 << 10)
+#define CERTIFICATE_TABLE_ITEM_LIMIT \
+	CERTIFICATE_TABLE_ENTRIES *ITEM_IN_BUCKET_LIMIT * 2
+
+/* callid table garbage collector defines */
+#define CALLID_GARBAGE_COLLECTOR_INTERVAL 10
+
+#define CALLID_TABLE_ENTRIES (2 << 13)
+#define CALLID_TABLE_ITEM_LIMIT CALLID_TABLE_ENTRIES *ITEM_IN_BUCKET_LIMIT * 2
+
+#define AUTH_MSG_VALIDITY_TIME 3600
+#define AUTH_MSG_TO_AUTH_VALIDITY_TIME 600
+
+#define BEGIN_PEM_CERT "-----BEGIN CERTIFICATE-----"
+#define BEGIN_PEM_CERT_LEN (sizeof(BEGIN_PEM_CERT) - 1)
+
+enum msg_part
+{
+	DS_FROM = 1,
+	DS_TO,
+	DS_CALLID,
+	DS_CSEQ,
+	DS_DATE,
+	DS_CONTACT,
+	DS_BODY
+};
+
+enum msg_part_flag
+{
+	DS_REQUIRED = 0,
+	DS_NOTREQUIRED = 1
+};
+
+typedef int(msg_part_proc)(str *, str *, struct sip_msg *);
+typedef void(msg_part_free_proc)(void);
+
+typedef struct _dgst_part
+{
+	int itype;
+	msg_part_proc *pfunc;
+	msg_part_free_proc *pfreefunc;
+	int iflag;
+} dgst_part;
+
+enum dgststr_asm_flags
+{
+	AUTH_ADD_DATE = 1,
+	AUTH_INCOMING_BODY = 1 << 1,
+	AUTH_OUTGOING_BODY = 1 << 2
+};
+
+enum proc_ret_val
+{
+	AUTH_OK,
+	AUTH_NOTFOUND,
+	AUTH_FOUND,
+	AUTH_ERROR
+};
+
+
+typedef struct _dstr
+{
+	str sd;
+	int size;
+} dynstr;
+
+int app2dynstr(dynstr *sout, str *s2app);
+int app2dynchr(dynstr *sout, char capp);
+int cpy2dynstr(dynstr *sout, str *s2app);
+int initdynstr(dynstr *sout, int isize);
+#define free_dynstr(sdyn)       \
+	if((sdyn)->sd.s) {          \
+		pkg_free((sdyn)->sd.s); \
+		(sdyn)->size = 0;       \
+	}
+#define resetstr_dynstr(sdyn) (sdyn)->sd.len = 0
+#define getstr_dynstr(sdyn) (sdyn)->sd
+
+
+/* Table declarations */
+/*
+fleast(s1, s2) return values:
+ 1	s2 is less than s1
+ 0	s1 and s2 are equal
+-1  s1 is less than s2
+-2	s1 is the least
+-3  s2 is the least
+
+fcmp(s1, s2) return values:
+ 0  s1 and s2 are the same
+ any other	s1 and s2 are not the same
+
+fgc(s1) return values:
+ 1 s1 is garbage
+ 0 s1 is not garbage
+*/
+typedef int(table_item_cmp)(const void *, const void *);
+typedef void(table_item_free)(const void *);
+typedef void(table_item_searchinit)();
+typedef int(table_item_gc)(const void *); /* garbage collector function */
+typedef struct item
+{
+	void *pdata;
+	unsigned int uhash;
+	struct item *pnext;
+	struct item *pprev;
+} titem;
+typedef struct bucket
+{
+	titem *pfirst;
+	titem *plast;
+	gen_lock_t lock;
+} tbucket;
+typedef struct table
+{
+	unsigned int unum;	   /* number of items */
+	unsigned int ubuckets; /* number of buckets */
+	unsigned int uitemlim; /* maximum of items */
+	gen_lock_t lock;	   /* lock for unum modifiing */
+	table_item_cmp *fcmp;  /* compare function (used by search) */
+	table_item_searchinit *
+			fsearchinit; /* init function (used by least item search, garbage collect) */
+	table_item_cmp *fleast; /* init function (used by least item search) */
+	table_item_free *ffree; /* free function */
+	table_item_gc *fgc;		/* garbage signer function */
+	tbucket *entries;
+} ttable;
+
+
+int init_table(ttable **ptable, unsigned int ubucknum, unsigned int uitemlim,
+		table_item_cmp *fcmp, table_item_searchinit *searchinit,
+		table_item_cmp *fleast, table_item_free *ffree, table_item_gc *fgc);
+void free_table(ttable *ptable);
+void garbage_collect(ttable *ptable, int ihashstart, int ihashend);
+
+/* Certificate table declarations */
+typedef struct cert_item
+{
+	str surl;
+	str scertpem;
+	time_t ivalidbefore; /* expiration time */
+	unsigned int uaccessed;
+} tcert_item;
+int cert_item_cmp(const void *s1, const void *s2);
+void cert_item_init();
+int cert_item_least(const void *s1, const void *s2);
+void cert_item_free(const void *sitem);
+int get_cert_from_table(ttable *ptable, str *skey, tcert_item *ptarget);
+int addcert2table(ttable *ptable, tcert_item *pcert);
+
+/* Call-ID table declarations */
+typedef struct dlg_item
+{
+	str sftag;				/* tag of the From header */
+	unsigned int ucseq;		/* number part of the cseq */
+	struct dlg_item *pnext; /* next dialog concerned the same call-id */
+} tdlg_item;
+
+typedef struct cid_item
+{
+	str scid; /* call-id of the message */
+	time_t ivalidbefore; /* the later expiration time among dialogs concerned this call-id*/
+	tdlg_item *pdlgs; /* Cseqs and From tags */
+} tcid_item;
+int proc_cid(ttable *ptable, str *scid, str *sftag, unsigned int ucseq,
+		time_t ivalidbefore);
+int cid_item_cmp(const void *s1, const void *s2);
+int cid_item_least(const void *s1, const void *s2);
+void cid_item_free(const void *sitem);
+void cid_item_init();
+int cid_item_gc();
+
+/* cURL functions */
+size_t curlmem_cb(void *ptr, size_t size, size_t nmemb, void *data);
+int download_cer(str *suri, CURL *hcurl);
+
+/* OpenSSL, Base64 functions */
+int retrieve_x509(X509 **pcert, str *scert, int bacceptpem);
+int check_x509_subj(X509 *pcert, str *sdom);
+int verify_x509(X509 *pcert, X509_STORE *pcacerts);
+int rsa_sha1_dec(char *sencedsha, int iencedshalen, char *ssha, int sshasize,
+		int *ishalen, X509 *pcertx509);
+int rsa_sha1_enc(
+		dynstr *sdigeststr, dynstr *senc, dynstr *sencb64, RSA *hmyprivkey);
+void base64decode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len);
+void base64encode(char *src_buf, int src_len, char *tgt_buf, int *tgt_len);
+int x509_get_notafter(time_t *tout, X509 *pcert);
+int x509_get_notbefore(time_t *tout, X509 *pcert);
+
+/* Common functions */
+int digeststr_asm(dynstr *sout, struct sip_msg *msg, str *sdate, int iflags);
+
+int fromhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int cseqhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int callidhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int datehdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int identityhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int identityinfohdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+
+int append_date(str *sdate, int idatesize, time_t *tout, struct sip_msg *msg);
+int append_hf(struct sip_msg *msg, char *str1, enum _hdr_types_t type);
+
+#endif

+ 577 - 0
src/modules/auth_identity/auth_tables.c

@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2007 iptelorg GmbH
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/*!
+ * \file
+ * \brief Kamailio auth-identity :: Tables
+ * \ingroup auth-identity
+ * Module: \ref auth-identity
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+
+#include "../../core/mem/shm_mem.h"
+#include "../../core/hashes.h"
+#include "auth_identity.h"
+
+#define lock_element(_cell) lock_get(&((_cell)->lock))
+#define release_element(_cell) lock_release(&((_cell)->lock))
+
+static int insert_into_table(ttable *ptable, void *pdata, unsigned int uhash);
+static void remove_from_table_unsafe(ttable *ptable, titem *pitem);
+static void remove_least(ttable *ptable, unsigned int uhash);
+static void *search_item_in_table_unsafe(
+		ttable *ptable, const void *pneedle, unsigned int uhash);
+
+time_t glb_tnow = 0; /* we need for this for certificate expiration check when
+					 * we've to remove the least item from a table */
+
+int init_table(ttable **ptable, /* table we'd like to init */
+		unsigned int ubucknum,	/* number of buckets */
+		unsigned int uitemlim,	/* maximum number of table intems */
+		table_item_cmp *fcmp,	/* compare function used by search */
+		table_item_searchinit
+				*fsinit,		/* inits the least item searcher function */
+		table_item_cmp *fleast, /* returns the less item;
+										 * used by item remover */
+		table_item_free *ffree, /* frees the data part of an item */
+		table_item_gc *fgc)		/* tells whether an item is garbage  */
+{
+	int i1;
+
+	if(!(*ptable = (ttable *)shm_malloc(sizeof(**ptable)))) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+	memset(*ptable, 0, sizeof(**ptable));
+
+	if(!((*ptable)->entries =
+					   (tbucket *)shm_malloc(sizeof(tbucket) * ubucknum))) {
+		SHM_MEM_ERROR;
+		shm_free(*ptable);
+		return -1;
+	}
+	memset((*ptable)->entries, 0, sizeof(tbucket) * ubucknum);
+	for(i1 = 0; i1 < ubucknum; i1++) {
+		(*ptable)->entries[i1].pfirst = NULL;
+		lock_init(&(*ptable)->entries[i1].lock);
+	}
+
+	(*ptable)->uitemlim = uitemlim;
+	(*ptable)->ubuckets = ubucknum;
+
+	(*ptable)->fcmp = fcmp;
+	(*ptable)->fsearchinit = fsinit;
+	(*ptable)->fleast = fleast;
+	(*ptable)->ffree = ffree;
+	(*ptable)->fgc = fgc;
+
+	return 0;
+}
+
+void free_table(ttable *ptable)
+{
+	unsigned int u1;
+	titem *pitem, *previtem;
+
+	if(ptable) {
+		for(u1 = 0; u1 < ptable->ubuckets; u1++) {
+			pitem = ptable->entries[u1].pfirst;
+			while(pitem) {
+				previtem = pitem;
+				pitem = pitem->pnext;
+
+				ptable->ffree(previtem->pdata);
+				shm_free(previtem);
+			}
+		}
+		shm_free(ptable->entries);
+		shm_free(ptable);
+	}
+}
+
+/* appends an item at the end of the bucket specified by uhash */
+static int insert_into_table(ttable *ptable, void *pdata, unsigned int uhash)
+{
+	tbucket *pbucket;
+	titem *pitem;
+	char bneed2remove = 0;
+
+	if(!(pitem = (titem *)shm_malloc(sizeof(*pitem)))) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+
+	memset(pitem, 0, sizeof(*pitem));
+	pitem->uhash = uhash;
+	pitem->pdata = pdata;
+
+	lock_element(ptable);
+	/* if there is not enough room for this item then we'll remove one */
+	if(ptable->unum >= ptable->uitemlim)
+		bneed2remove = 1;
+	ptable->unum++;
+	release_element(ptable);
+
+	if(bneed2remove)
+		remove_least(ptable, uhash);
+
+	/* locates the appropriate bucket */
+	pbucket = &ptable->entries[uhash];
+
+	/* insert into that bucket */
+	lock_element(pbucket);
+	if(pbucket->plast) {
+		pbucket->plast->pnext = pitem;
+		pitem->pprev = pbucket->plast;
+	} else
+		pbucket->pfirst = pitem;
+	pbucket->plast = pitem;
+	release_element(pbucket);
+
+	return 0;
+}
+
+
+/*  Un-link a cell from hash_table */
+static void remove_from_table_unsafe(ttable *ptable, titem *pitem)
+{
+	tbucket *pbucket = &(ptable->entries[pitem->uhash]);
+
+	/* unlink the cell from entry list */
+	if(pitem->pprev)
+		pitem->pprev->pnext = pitem->pnext;
+	else
+		pbucket->pfirst = pitem->pnext;
+
+	if(pitem->pnext)
+		pitem->pnext->pprev = pitem->pprev;
+	else
+		pbucket->plast = pitem->pprev;
+
+	if(ptable->ffree)
+		ptable->ffree(pitem->pdata);
+
+	shm_free(pitem);
+}
+
+/* removes the least important item from its bucket or from the following first
+   bucket which contains item */
+static void remove_least(ttable *ptable, unsigned int uhash)
+{
+	tbucket *pbucket;
+	unsigned int u1, uhashnow;
+	titem *pleastitem = NULL, *pnow;
+	int ires;
+
+	if(!ptable->fleast)
+		return;
+	if(ptable->fsearchinit)
+		ptable->fsearchinit();
+
+	for(uhashnow = uhash, u1 = 0, pbucket = &(ptable->entries[uhash]);
+			u1 < ptable->ubuckets;
+			u1++, pbucket = &(ptable->entries[uhashnow])) {
+
+		lock_element(pbucket);
+		/* if there any item in this bucket */
+		for(pnow = pbucket->pfirst; pnow; pnow = pnow->pnext) {
+			if(!pleastitem) {
+				pleastitem = pnow;
+				continue;
+			}
+
+			/*
+ 			fleast() return values:
+			 1	s2 is less than s1
+			 0	s1 and s2 are equal
+			-1  s1 is less than s2
+			-2	s1 is the least
+			-3  s2 is the least
+			 */
+			ires = ptable->fleast(pleastitem->pdata, pnow->pdata);
+			if(ires == 1)
+				pleastitem = pnow;
+			if(ires == -2)
+				break;
+			if(ires == -3) {
+				pleastitem = pnow;
+				break;
+			}
+		}
+		/* we found the least item in this bucket */
+		if(pleastitem) {
+
+			lock_element(ptable);
+			ptable->unum--;
+			release_element(ptable);
+
+			remove_from_table_unsafe(ptable, pleastitem);
+			release_element(pbucket);
+			return;
+		}
+		release_element(pbucket);
+
+
+		/* we're in the last bucket so we start with the first one */
+		if(uhashnow + 1 == ptable->ubuckets)
+			uhashnow = 0;
+		else
+			/* we step to the next bucket */
+			uhashnow++;
+	}
+}
+
+/* looks for an item in the scepifiad bucket */
+static void *search_item_in_table_unsafe(
+		ttable *ptable, const void *pneedle, unsigned int uhash)
+{
+	tbucket *pbucket = &(ptable->entries[uhash]);
+	titem *pnow;
+	void *pret = NULL;
+
+	if(!ptable->fcmp)
+		return NULL;
+
+	for(pnow = pbucket->pfirst; pnow; pnow = pnow->pnext) {
+		if(!ptable->fcmp(pneedle, pnow->pdata)) {
+			pret = pnow->pdata;
+			break;
+		}
+	}
+
+	return pret;
+}
+
+/* looks for garbage in the hash interval specified by ihashstart and ihashend */
+void garbage_collect(ttable *ptable, int ihashstart, int ihashend)
+{
+	unsigned int unum, uremoved;
+	int i1;
+	tbucket *pbucket;
+	titem *pnow;
+
+
+	/* there is not any garbage collector function available */
+	if(!ptable->fgc)
+		return;
+
+	if(ptable->fsearchinit)
+		ptable->fsearchinit();
+
+	lock_element(ptable);
+	unum = ptable->unum;
+	release_element(ptable);
+
+	/* if the half of the table is used or there is not so many items in a bucket
+	   then we return */
+	// 	if (unum < ptable->uitemlim/2 && unum < ptable->ubuckets*ITEM_IN_BUCKET_LIMIT)
+	// 		return ;
+	if(!unum)
+		return;
+
+	for(i1 = ihashstart; i1 <= ihashend; i1++) {
+		uremoved = 0;
+		pbucket = &(ptable->entries[i1]);
+
+		lock_element(pbucket);
+		for(pnow = pbucket->pfirst; pnow; pnow = pnow->pnext) {
+			if(ptable->fgc(pnow->pdata)) {
+				remove_from_table_unsafe(ptable, pnow);
+				uremoved++;
+			}
+		}
+		/* if we removed any item from table then we would update the item counter */
+		if(uremoved) {
+			lock_element(ptable);
+			ptable->unum -= uremoved;
+			release_element(ptable);
+		}
+		release_element(pbucket);
+	}
+}
+
+
+/*
+ * Make a copy of a str structure using shm_malloc
+ */
+static int str_duplicate(str *_d, str *_s)
+{
+
+	_d->s = (char *)shm_malloc(sizeof(char) * (_s->len));
+	if(!_d->s) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+
+	memcpy(_d->s, _s->s, _s->len);
+	_d->len = _s->len;
+	return 0;
+}
+
+/*
+ *
+ * Certificate table specific functions
+ *
+ */
+int cert_item_cmp(const void *s1, const void *s2)
+{
+	tcert_item *p1 = (tcert_item *)s1, *p2 = (tcert_item *)s2;
+
+	return !(p1->surl.len == p2->surl.len
+			 && !memcmp(p1->surl.s, p2->surl.s, p2->surl.len));
+}
+
+void cert_item_init()
+{
+	/* we need for this for certificate expiration check when
+	 * we've to remove an item from the table */
+	glb_tnow = time(0);
+}
+
+/* we remove a certificate if expired or if accessed less than another */
+int cert_item_least(const void *s1, const void *s2)
+{
+	if(((tcert_item *)s1)->ivalidbefore < glb_tnow)
+		return -2;
+	if(((tcert_item *)s2)->ivalidbefore < glb_tnow)
+		return -3;
+	return (((tcert_item *)s1)->uaccessed < ((tcert_item *)s2)->uaccessed) ? -1
+																		   : 1;
+}
+
+/* frees a certificate item */
+void cert_item_free(const void *sitem)
+{
+	shm_free(((tcert_item *)sitem)->surl.s);
+	shm_free(((tcert_item *)sitem)->scertpem.s);
+	shm_free((tcert_item *)sitem);
+}
+
+/* looks for a certificate in a table and increases access counter of that
+   table item */
+int get_cert_from_table(ttable *ptable, str *skey, tcert_item *ptarget)
+{
+	tcert_item *tmp_tcert_item;
+	unsigned int uhash;
+	int iret = 0;
+
+	uhash = get_hash1_raw(skey->s, skey->len) & (CERTIFICATE_TABLE_ENTRIES - 1);
+
+	/* we lock the whole bucket */
+	lock_element(&ptable->entries[uhash]);
+
+	tmp_tcert_item =
+			search_item_in_table_unsafe(ptable, (const void *)skey, uhash);
+	/* make a copy of found certificate and after the certificate
+	 * verification we'll add it to certificate table */
+	if(tmp_tcert_item) {
+		memcpy(ptarget->scertpem.s, tmp_tcert_item->scertpem.s,
+				tmp_tcert_item->scertpem.len);
+		ptarget->scertpem.len = tmp_tcert_item->scertpem.len;
+		/* we accessed this certificate */
+		tmp_tcert_item->uaccessed++;
+	} else
+		iret = 1;
+
+	release_element(&ptable->entries[uhash]);
+
+	return iret;
+}
+
+/* inserts an item to table, and removes the least item if the table is full */
+int addcert2table(ttable *ptable, tcert_item *pcert)
+{
+	tcert_item *pshmcert;
+	unsigned int uhash;
+
+	if(!(pshmcert = (tcert_item *)shm_malloc(sizeof(*pshmcert)))) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+	memset(pshmcert, 0, sizeof(*pshmcert));
+	if(str_duplicate(&pshmcert->surl, &pcert->surl))
+		return -2;
+
+	if(str_duplicate(&pshmcert->scertpem, &pcert->scertpem))
+		return -3;
+
+	pshmcert->ivalidbefore = pcert->ivalidbefore;
+	pshmcert->uaccessed = 1;
+
+	uhash = get_hash1_raw(pcert->surl.s, pcert->surl.len)
+			& (CERTIFICATE_TABLE_ENTRIES - 1);
+
+	if(insert_into_table(ptable, (void *)pshmcert, uhash))
+		return -4;
+
+	return 0;
+}
+
+/*
+ *
+ * Call-ID table specific functions
+ *
+ */
+
+int cid_item_cmp(const void *s1, const void *s2)
+{
+	tcid_item *p1 = (tcid_item *)s1, *p2 = (tcid_item *)s2;
+
+	return !(p1->scid.len == p2->scid.len
+			 && !memcmp(p1->scid.s, p2->scid.s, p2->scid.len));
+}
+
+void cid_item_init()
+{
+	glb_tnow = time(0);
+}
+
+/* we remove a call-id if older than another */
+int cid_item_least(const void *s1, const void *s2)
+{
+	if(((tcid_item *)s1)->ivalidbefore < glb_tnow)
+		return -2;
+	if(((tcid_item *)s2)->ivalidbefore < glb_tnow)
+		return -3;
+
+	return (((tcid_item *)s1)->ivalidbefore < ((tcid_item *)s2)->ivalidbefore)
+				   ? -1
+				   : 1;
+}
+
+/* tells whether an item is garbage */
+int cid_item_gc(const void *s1)
+{
+	return (((tcid_item *)s1)->ivalidbefore < glb_tnow);
+}
+
+/* frees a call-id item */
+void cid_item_free(const void *sitem)
+{
+	tcid_item *pcid = (tcid_item *)sitem;
+	tdlg_item *pdlgs, *pdlgs_next;
+
+	shm_free(pcid->scid.s);
+
+	pdlgs_next = pcid->pdlgs;
+	while(pdlgs_next) {
+		pdlgs = pdlgs_next;
+		pdlgs_next = pdlgs_next->pnext;
+		shm_free(pdlgs->sftag.s);
+		shm_free(pdlgs);
+	}
+
+	shm_free((tcert_item *)sitem);
+}
+
+/* inserts a callid item to table, and removes the least item if the table is full */
+int proc_cid(ttable *ptable, str *scid, str *sftag, unsigned int ucseq,
+		time_t ivalidbefore)
+{
+	tcid_item *pshmcid, *pcid_item;
+	tdlg_item *pshmdlg, *pdlg_item, *pdlg_item_prev;
+	unsigned int uhash;
+
+	/* we suppose that this SIP request is not replayed so it doesn't exist in
+	   the table so we prepare to insert */
+	if(!(pshmdlg = (tdlg_item *)shm_malloc(sizeof(*pshmdlg)))) {
+		SHM_MEM_ERROR;
+		return -1;
+	}
+	memset(pshmdlg, 0, sizeof(*pshmdlg));
+	if(str_duplicate(&pshmdlg->sftag, sftag))
+		return -2;
+	pshmdlg->ucseq = ucseq;
+
+
+	/* we're looking for this call-id item if exists */
+	uhash = get_hash1_raw(scid->s, scid->len) & (CALLID_TABLE_ENTRIES - 1);
+
+	lock_element(&ptable->entries[uhash]);
+
+	pcid_item = search_item_in_table_unsafe(ptable,
+			(const void *)scid, /* Call-id is the key */
+			uhash);
+	/* we've found one call-id so we're looking for the required SIP request */
+	if(pcid_item) {
+		for(pdlg_item = pcid_item->pdlgs, pdlg_item_prev = NULL; pdlg_item;
+				pdlg_item = pdlg_item->pnext) {
+			if(pdlg_item->sftag.len == sftag->len
+					&& !memcmp(pdlg_item->sftag.s, sftag->s, sftag->len)) {
+				/* we found this call with this from tag */
+				if(pdlg_item->ucseq >= ucseq) {
+					/* we've found this or older request in the table!
+					   this call is replayed! */
+					release_element(&ptable->entries[uhash]);
+
+					shm_free(pshmdlg->sftag.s);
+					shm_free(pshmdlg);
+					return AUTH_FOUND;
+				} else {
+					/* this is another later request whithin this dialog so we
+					   update the saved cseq */
+					pdlg_item->ucseq = ucseq;
+					release_element(&ptable->entries[uhash]);
+
+					shm_free(pshmdlg->sftag.s);
+					shm_free(pshmdlg);
+					return 0;
+				}
+			}
+			/* we save the previous dialog item in order to append a new item more easily */
+			pdlg_item_prev ? (pdlg_item_prev = pdlg_item_prev->pnext)
+						   : (pdlg_item_prev = pdlg_item);
+		}
+		/* we append this to item dialogs*/
+		pdlg_item_prev->pnext = pshmdlg;
+		/* this is the latest request; we hold all request concerned this
+		   call-id until the latest request is valid */
+		pcid_item->ivalidbefore = ivalidbefore;
+	}
+
+	release_element(&ptable->entries[uhash]);
+
+	if(!pcid_item) {
+		/* this is the first request with this call-id */
+		if(!(pshmcid = (tcid_item *)shm_malloc(sizeof(*pshmcid)))) {
+			SHM_MEM_ERROR;
+			shm_free(pshmdlg);
+			return -4;
+		}
+		memset(pshmcid, 0, sizeof(*pshmcid));
+		if(str_duplicate(&pshmcid->scid, scid)) {
+			return -5;
+		}
+		pshmcid->ivalidbefore = ivalidbefore;
+		pshmcid->pdlgs = pshmdlg;
+		if(insert_into_table(ptable, (void *)pshmcid, uhash))
+			return -6;
+	}
+
+	return 0;
+}

+ 4 - 0
src/modules/auth_identity/doc/Makefile

@@ -0,0 +1,4 @@
+docs = auth_identity.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 240 - 0
src/modules/auth_identity/doc/auth_identity.xml

@@ -0,0 +1,240 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	[ <!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+	 <!-- Include general documentation entities -->
+	 <!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+	 %docentities;
+	]
+>
+
+<book id="auth_identity" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+        <title>SIP Authenticated Identity Module</title>
+	<authorgroup>
+	    <author>
+		<firstname>Gergely</firstname>
+		<surname>Kovacs</surname>
+		<affiliation><orgname>Iptel.org</orgname></affiliation>
+		<address>
+		    <email>[email protected]</email>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2007</year>
+	    <holder>Iptel.org</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+    <chapter>
+	<title>Admin Guide</title>
+    <section id="auth_identity.overview">
+	<title>Overview</title>
+	<para>
+		Auth Identity module provides functionalities for securely identifying
+		originators of SIP messages. It implements the SIP Identity standard where a
+		SIP proxy signs messages that is sent to other domains.
+		This module has two basic services:
+		<itemizedlist>
+		<listitem>
+			<para>
+				<emphasis>authorizer</emphasis> - authorizes a message and adds Identity and
+				Identity-Info headers
+			</para>
+		</listitem>
+		<listitem>
+			<para>
+				<emphasis>verifier</emphasis> - verifies an authorized message
+			</para>
+		</listitem>
+		</itemizedlist>
+	</para>
+	<para>
+	    Known limitations in this version:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+			authorizer and verifier support all SIP requests except for
+			<emphasis>CANCEL</emphasis> and <emphasis>REGISTER</emphasis>
+		</para>
+	    </listitem>
+	    <listitem>
+		<para>
+			verifier does not support the subjectAltName extension of
+			certificates
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+
+    <section id="auth_identity.dep">
+	<title>Dependencies</title>
+	<para>
+		This module does not depend any other module.
+	</para>
+    </section>
+
+	<section id="auth_identity.compilation">
+		<title>Compilation</title>
+		<para>
+		This module needs the following headers and libraries:
+		<itemizedlist>
+			<listitem>
+				<para>
+					<emphasis>OpenSSL</emphasis> (version 0.9.8 or higher) for cryptographic functions
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+					<emphasis>libcurl</emphasis> for HTTP, HTTPS functions
+				</para>
+			</listitem>
+		</itemizedlist>
+		If you'd like to use <emphasis>TLS</emphasis> module too then use the
+		corresponding LIB line in auth_identity's Makefile
+		</para>
+	</section>
+
+	<section id="auth_identity.install_and_run">
+	<title>Installation And Running</title>
+	<para>
+		the <emphasis>Authorizer</emphasis> service needs to make the public key,
+		which conveyed in a certificate, available over HTTPS or HTTP for
+		verifiers. The domain the authorizer is responsible for and the
+		domain part of the URL of the certificate must be the same. This
+		service needs access to the private key too.
+	</para>
+    </section>
+
+
+    <xi:include href="auth_identity_params.xml"/>
+    <xi:include href="auth_identity_functions.xml"/>
+
+
+	<section>
+		<title>Authorizer service examples</title>
+
+			<programlisting><![CDATA[
+...
+route[INIT]
+{
+	# we process new transactions only
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+...
+route[OUTBOUND]
+{
+	# If we are responsible for the domain of the sender of this message
+	if ($f.did && !$t.did) {
+		# Authentication service
+		if (method=="INVITE" || method=="BYE"
+			|| method=="OPTION" || method=="ACK") {
+			# Identity and Identity-info headers must not exist
+			if (@identity) {
+				t_reply("403", "Invalid Identity header");
+				drop;
+			}
+			if (@identity_info) {
+				t_reply("403", "Invalid Identity-info header");
+				drop;
+			}
+
+			if (!auth_date_proc()) {
+				t_reply("403", "Invalid Date value");
+				drop;
+			}
+
+			if (!auth_add_identity()) {
+				t_reply("480", "Authentication error");
+				drop;
+			}
+		}
+		route(FORWARD);
+	}
+}
+...
+]]></programlisting>
+	</section>
+
+
+	<section>
+		<title>Verifier service examples</title>
+
+		<programlisting><![CDATA[
+...
+route[INIT]
+{
+	# we process new transactions only
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+...
+route[VERIFY]
+{
+	# if we've already processed this message then we drop it
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+
+	if (method=="INVITE" || method=="BYE"
+		|| method=="OPTION" || method=="ACK") {
+		# Identity and Identity-info are required for verification
+		if (!@identity) {
+			t_reply("428", "Use Identity Header");
+			drop;
+		}
+		if (!@identity_info) {
+			t_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_date()) {
+			t_reply("403", "Outdated Date header value");
+			drop;
+		}
+
+		if (!vrfy_get_certificate()) {
+			t_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_certificate()) {
+			t_reply("437", "Unsupported Certificate");
+			drop;
+		}
+
+		if (!vrfy_check_msgvalidity()) {
+			t_reply("438", "Invalid Identity Header");
+			drop;
+		}
+
+		if (!vrfy_check_callid()) {
+			t_reply("403", "Message is replayed");
+			drop;
+		}
+	}
+}
+...
+]]></programlisting>
+	</section>
+	<section id="auth_identity.s.remarks">
+		<title>Remarks</title>
+		<para>
+			Note: libcurl leak in CentOS 6 - this module uses libcurl library
+			and in case if you are using CentOS 6, be aware that standard
+			libcurl-7.19.7-52 has a memory leak. To fix this memory, install
+			libcurl from city-fan repository. More details at:
+			<ulink url="https://www.digitalocean.com/community/questions/how-to-upgrade-curl-in-centos6">
+				https://www.digitalocean.com/community/questions/how-to-upgrade-curl-in-centos6</ulink>
+		</para>
+	</section>
+	</chapter>
+</book>

+ 158 - 0
src/modules/auth_identity/doc/auth_identity_functions.xml

@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	[ <!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+	 <!-- Include general documentation entities -->
+	 <!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+	 %docentities;
+	]
+>
+
+	<section id="auth_identity.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+		<title>Functions</title>
+
+		<section>
+			<title>
+				<function>auth_date_proc()</function>
+			</title>
+			<para>Note: this function is for authorizer service.</para>
+			<para>
+				If a message, the auth service should authorize, contains Date header
+				then this function checks whether it falls in message timeout (set by
+				<emphasis>msg_timeout</emphasis> parameter). If there is not any Date
+				header then the module adds one. This function also checks whether the certificate
+				of the authentication service (set by <emphasis>certificate_path</emphasis> parameter)
+				has expired.
+			</para>
+			<section>
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section>
+			<title>
+				<function>auth_add_identity()</function>
+			</title>
+			<para>Note: this function is for authorizer service.</para>
+			<para>
+				Assembles digest-string from the message, calculates its SHA1 hash,
+				encrypts it with the private key (set by <emphasis>privatekey_path</emphasis>
+				parameter) of the authorizer service, base64 encodes it and adds to the
+				outgoing message as the value of <emphasis>Identity</emphasis> header.
+				This function also adds Identity-Info header which contains an URI
+				(set by <emphasis>certificate_url</emphasis> parameter) from which
+				the certificate of auth service can be acquired.
+			</para>
+			<para>
+				<emphasis>Note: this function needs the final outgoing
+				message for authorization, so no module may modify any
+				digest string related headers (From, To, Call-ID, CSeq,
+				Date, Contact) and body after auth_add_identity()'s been called</emphasis>
+			</para>
+			<section>
+				<title>Dependencies</title>
+				<para>
+					auth_date_proc() must be called before
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_date">
+			<title>
+				<function>vrfy_check_date()</function>
+			</title>
+			<para>Note: this function is for verifier service.</para>
+			<para>
+				Checks Date header of the incoming message whether falls in validity
+				time (set by <emphasis>auth_validity_time</emphasis> parameter)
+			</para>
+			<section id="vrfy_check_date.dep">
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_get_certificate">
+			<title>
+				<function>vrfy_get_certificate()</function>
+			</title>
+			<para>Note: this function is for verifier service.</para>
+			<para>
+				Tries to get certificate defined by the value of
+				<emphasis>Identity-info</emphasis> header from certificate table
+				(which size is set by <emphasis>certificate_cache_limit</emphasis>
+				parameter). If the required certificate is not found there then
+				this function downloads it.
+			</para>
+			<section id="vrfy_get_certificate.dep">
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_certificate">
+			<title>
+				<function>vrfy_check_certificate()</function>
+			</title>
+			<para>Note: this function is for verifier service.</para>
+			<para>
+				Checks whether the downloaded certificate is valid (is not expired,
+				its subject and the domain part of the URL are the same) and adds it
+				to certificate table.
+			</para>
+			<section id="vrfy_check_certificate.dep">
+				<title>Dependencies</title>
+				<para>
+					vrfy_get_certificate() must be called before
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_msgvalidity">
+			<title>
+				<function>vrfy_check_msgvalidity()</function>
+			</title>
+			<para>Note: this function is for verifier service.</para>
+			<para>
+				Assembles digest-string from the message, create SHA1 hash and
+				compares it with the decrypted value of <emphasis>Identity</emphasis>
+				header.
+			</para>
+			<section id="vrfy_check_msgvalidity.dep">
+				<title>Dependencies</title>
+				<para>
+					vrfy_get_certificate() must be called before and
+					vrfy_check_certificate() should be called before
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_callid">
+			<title>
+				<function>vrfy_check_callid()</function>
+			</title>
+			<para>Note: this function is for verifier service.</para>
+			<para>
+				Checks whether the current call's been already processed in validity
+				time (set by <emphasis>auth_validity_time</emphasis>) to recognize
+				call replay attacks. If this call (identified by Call-id, Cseq,
+				and tag of From header triple) has not been replayed then adds it to
+				callid table (which size is set by <emphasis>callid_cache_limit</emphasis>
+				parameter).
+			</para>
+			<section id="vrfy_check_callid.dep">
+				<title>Dependencies</title>
+				<para>
+					This function should be called for the last time.
+				</para>
+			</section>
+		</section>
+	</section>

+ 213 - 0
src/modules/auth_identity/doc/auth_identity_params.xml

@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+	"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	[ <!ENTITY % local.common.attrib
+	 "xmlns:xi CDATA #FIXED 'http://www.w3.org/2001/XInclude'">
+	 <!-- Include general documentation entities -->
+	 <!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+	 %docentities;
+	]
+>
+
+	<section id="auth_identity.params" xmlns:xi="http://www.w3.org/2001/XInclude">
+		<title>Parameters</title>
+		<section>
+			<title><varname>privatekey_path</varname> (string)</title>
+			<para>Note: this parameter is for authorizer service.</para>
+			<para>
+				The path of private key of the authentication service. The key
+				must be in PEM format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>privatekey_path</varname> parameter</title>
+				<programlisting>
+...
+modparam("auth_identity","privatekey_path","/etc/ssl/private/key.pem")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>certificate_path</varname> (string)</title>
+			<para>Note: this parameter is for authorizer service.</para>
+			<para>
+				The path of certificate of the authentication service. The
+				certificate must be in PEM format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>certificate_path</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_path","/var/www/ssl/mycert.pem")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>certificate_url</varname> (string)</title>
+			<para>Note: this parameter is for authorizer service.</para>
+			<para>
+				The url where certificate is available for other verifier
+				services. (value of Identity-info header) The
+				certificate should be in DER format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>certificate_url</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_url","https://foo.bar/mycert.der")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>msg_timeout</varname> (integer)</title>
+			<para>Note: this parameter is for authorizer service.</para>
+			<para>
+				If the Date header of message which is needed to be authenticated
+				contains a time different by more than this seconds from the current
+				time noted by the authentication service then it rejects the
+				message.
+			</para>
+			<para>
+				This parameter is optional. The default value is "600".
+			</para>
+			<example>
+				<title>Set <varname>msg_timeout</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","msg_timeout",600)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="auth_validity_time">
+			<title><varname>auth_validity_time</varname> (integer)</title>
+			<para>Note: this parameter is for verifier service.</para>
+			<para>
+				The validity time of an authenticated message. The message
+				will be refused if it contains a time different by more
+				than this seconds from the current time noted by the verification
+				service.
+			</para>
+			<para>
+				This parameter is optional. The default value is "3600".
+			</para>
+			<example>
+				<title>Set <varname>auth_validity_time</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","auth_validity_time",3600)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="callid_cache_limit">
+			<title><varname>callid_cache_limit</varname> (integer)</title>
+			<para>Note: this parameter is for verifier service.</para>
+			<para>
+				The number of Call-IDs stored in order to recognize call replay
+				attacks. A Call-ID is stored <varname>auth_validity_time</varname> long and
+				uses approximately 100 bytes memory.
+			</para>
+			<para>
+				This parameter is optional. The default value is "32768".
+				(you should increase the size of shared memory with -m
+				 command line switch if you liked to store more callid than
+				 10000)
+			</para>
+			<example>
+				<title>Set <varname>auth_validity_time</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","callid_cache_limit",32768)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="certificate_cache_limit">
+			<title><varname>certificate_cache_limit</varname> (integer)</title>
+			<para>Note: this parameter is for verifier service.</para>
+			<para>
+				The number of certificates stored in order to avoid needless
+				download. A certificate is stored until its expiration date and
+				uses approximately 600 bytes memory.
+			</para>
+			<para>
+				This parameter is optional. The default value is "4096".
+			</para>
+			<example>
+				<title>Set <varname>certificate_cache_limit</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_cache_limit",4096)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="cainfo_path">
+			<title><varname>cainfo_path</varname> (string)</title>
+			<para>Note: this parameter is for verifier service.</para>
+			<para>
+				A file of trusted certificates. The file should contain multiple
+				certificates in PEM format concatenated together. It could be useful
+				for verifying a certificate signed by a private CA.
+			</para>
+			<para>
+				This parameter is optional. It has not got default value.
+			</para>
+			<example>
+				<title>Set <varname>cainfo_path</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","cainfo_path","/etc/ssl/certs/ca-certificates.crt")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="accept_pem_certs">
+			<title><varname>accept_pem_certs</varname> (int)</title>
+			<para>Note: this parameter is for verifier service.</para>
+			<para>
+				Enables the acquired certificate processing if it is in PEM
+				format. Value can be 0 or 1.
+			</para>
+			<para>
+				This parameter is optional. The default value is "0".
+			</para>
+			<example>
+				<title>Set <varname>accept_pem_certs</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","accept_pem_certs",1)
+...
+				</programlisting>
+			</example>
+		</section>
+	</section>
+