|
@@ -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;
|
|
|
+}
|