/*
* $Id: tls_init.c 1758 2007-03-06 17:06:36Z bogdan_iancu $
*
* Copyright (C) 2001-2003 FhG Fokus
* Copyright (C) 2004,2005 Free Software Foundation, Inc.
* Copyright (C) 2006 enum.at
*
* This file is part of openser, a free SIP server.
*
* openser 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
*
* openser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#include "tls_init.h"
#include "tls_config.h"
#include "../dprint.h"
#include "../mem/shm_mem.h"
#include "../tcp_init.h"
#include "../ut.h"
#include "tls_domain.h"
#include <openssl/ui.h>
#include <openssl/ssl.h>
#include <openssl/opensslv.h>
#include <openssl/err.h>
#include <netinet/in_systm.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <unistd.h>
#define SER_SSL_SESS_ID ((unsigned char*)"openser-tls-1.2.0")
#define SER_SSL_SESS_ID_LEN (sizeof(SER_SSL_SESS_ID)-1)
#if OPENSSL_VERSION_NUMBER < 0x00907000L
#warning ""
#warning "=============================================================="
#warning "Your version of OpenSSL is < 0.9.7."
#warning " Upgrade for better compatibility, features and security fixes!"
#warning "============================================================="
#warning ""
#endif
SSL_METHOD *ssl_methods[TLS_USE_SSLv23 + 1];
#define VERIFY_DEPTH_S 3
/* This callback is called during each verification process,
at each step during the chain of certificates (this function
is not the certificate_verification one!). */
int verify_callback(int pre_verify_ok, X509_STORE_CTX *ctx) {
char buf[256];
X509 *err_cert;
int err, depth;
depth = X509_STORE_CTX_get_error_depth(ctx);
LOG( 2, "tls_init: verify_callback: depth = %d\n",depth);
if ( depth > VERIFY_DEPTH_S ) {
LOG( 2, "tls_init: verify_callback: cert chain too long "
"( depth > VERIFY_DEPTH_S)\n");
pre_verify_ok=0;
}
if( pre_verify_ok ) {
LOG( 2, "tls_init: verify_callback: preverify is good: "
"verify return: %d\n", pre_verify_ok);
return pre_verify_ok;
}
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
X509_NAME_oneline(X509_get_subject_name(err_cert),buf,sizeof buf);
LOG( 2, "tls_init: verify_callback: subject = %s\n", buf);
LOG( 2, "tls_init: verify_callback: verify error:num=%d:%s\n",
err, X509_verify_cert_error_string(err));
LOG( 2, "tls_init: verify_callback: error code is %d\n", ctx->error);
switch (ctx->error) {
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
buf,sizeof buf);
LOG( 2, "tls_init: verify_callback: issuer= %s\n",buf);
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
case X509_V_ERR_CERT_NOT_YET_VALID:
LOG( 2, "tls_init: verify_callback: notBefore\n");
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
case X509_V_ERR_CERT_HAS_EXPIRED:
LOG( 2, "tls_init: verify_callback: notAfter\n");
break;
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
LOG( 2, "tls_init: verify_callback: unable to decrypt cert "
"signature\n");
break;
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
LOG( 2, "tls_init: verify_callback: unable to decode issuer "
"public key\n");
break;
case X509_V_ERR_OUT_OF_MEM:
LOG( 2, "tls_init: verify_callback: Out of memory \n");
break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
LOG( 2, "tls_init: verify_callback: Self signed certificate "
"issue\n");
break;
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
LOG( 2, "tls_init: verify_callback: certificate chain too long\n");
break;
case X509_V_ERR_INVALID_CA:
LOG( 2, "tls_init: verify_callback: invalid CA\n");
break;
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
LOG( 2, "tls_init: verify_callback: path length exceeded\n");
break;
case X509_V_ERR_INVALID_PURPOSE:
LOG( 2, "tls_init: verify_callback: invalid purpose\n");
break;
case X509_V_ERR_CERT_UNTRUSTED:
LOG( 2, "tls_init: verify_callback: certificate untrusted\n");
break;
case X509_V_ERR_CERT_REJECTED:
LOG( 2, "tls_init: verify_callback: certificate rejected\n");
break;
default:
LOG( 2, "tls_init: verify_callback: something wrong with the cert"
" ... error code is %d (check x509_vfy.h)\n", ctx->error);
break;
}
LOG( 2, "tls_init: verify_callback: verify return:%d\n", pre_verify_ok);
return(pre_verify_ok);
}
static int
passwd_cb(char *buf, int size, int rwflag, void *filename)
{
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
UI *ui;
const char *prompt;
ui = UI_new();
if (ui == NULL)
goto err;
prompt = UI_construct_prompt(ui, "passphrase", filename);
UI_add_input_string(ui, prompt, 0, buf, 0, size - 1);
UI_process(ui);
UI_free(ui);
return strlen(buf);
err:
LOG(L_ERR, "tls: tls_init: passwd_cb: Error in passwd_cb\n");
if (ui)
UI_free(ui);
return 0;
#else
if( des_read_pw_string(buf, size-1, "Enter Private Key password:", 0) ) {
LOG(L_ERR, "tls: tls_init: passwd_cb: Error in passwd_cb\n");
return 0;
}
return strlen( buf );
#endif
}
/*
* Wrappers around SER shared memory functions
* (which can be macros)
*/
static void *
ser_malloc(size_t size)
{
return shm_malloc(size);
}
static void *
ser_realloc(void *ptr, size_t size)
{
return shm_realloc(ptr, size);
}
static void
ser_free(void *ptr)
{
shm_free(ptr);
}
int
tls_init(struct socket_info *si)
{
DBG("tls_init: Entered\n");
/*
* reuse tcp initialization
*/
if (tcp_init(si) < 0) {
LOG(L_ERR, "tls_init: Error while initializing TCP part\n");
goto error;
}
si->proto = PROTO_TLS;
return 0;
error:
if (si->socket != -1) {
close(si->socket);
si->socket = -1;
}
return -1;
}
/*
* load a certificate from a file
* (certificate file can be a chain, starting by the user cert,
* and ending in the root CA; if not all needed certs are in this
* file, they are looked up in the caFile or caPATH (see verify
* function).
*/
static int
load_certificate(SSL_CTX * ctx, char *filename)
{
DBG("load_certificate: Entered\n");
if (!SSL_CTX_use_certificate_chain_file(ctx, filename)) {
LOG(L_ERR,
"load_certificate: Unable to load certificate file '%s'\n",
filename);
return -1;
}
DBG("load_certificate: '%s' successfuly loaded\n", filename);
return 0;
}
#define NUM_RETRIES 3
/*
* load a private key from a file
*/
static int
load_private_key(SSL_CTX * ctx, char *filename)
{
int idx, ret_pwd;
DBG("load_private_key: Entered\n");
SSL_CTX_set_default_passwd_cb(ctx, passwd_cb);
SSL_CTX_set_default_passwd_cb_userdata(ctx, filename);
for(idx = 0, ret_pwd = 0; idx < NUM_RETRIES; idx++ ) {
ret_pwd = SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM);
if ( ret_pwd ) {
break;
} else {
LOG( L_ERR,
"load_private_key: Unable to load private key file '%s'. \n"
"Retry (%d left) (check password case)\n",
filename, (NUM_RETRIES - idx -1) );
continue;
}
}
if( ! ret_pwd ) {
LOG(L_ERR,
"load_private_key: Unable to load private key file '%s'\n",
filename);
return -1;
}
if (!SSL_CTX_check_private_key(ctx)) {
LOG(L_ERR,
"load_private_key: Key '%s' does not match the public key of the certificate\n",
filename);
return -1;
}
DBG("load_private_key: Key '%s' successfuly loaded\n", filename);
return 0;
}
/*
* Load a caList, to be used to verify the client's certificate.
* The list is to be stored in a single file, containing all
* the acceptable root certificates.
*/
static int
load_ca(SSL_CTX * ctx, char *filename)
{
DBG("load_ca: Entered\n");
if (!SSL_CTX_load_verify_locations(ctx, filename, 0)) {
LOG(L_ERR, "load_ca: Unable to load ca '%s'\n", filename);
return -1;
}
DBG("load_ca: CA '%s' successfuly loaded\n", filename);
return 0;
}
/*
* initialize ssl methods
*/
static void
init_ssl_methods(void)
{
DBG("init_methods: Entered\n");
ssl_methods[TLS_USE_SSLv2_cli - 1] = SSLv2_client_method();
ssl_methods[TLS_USE_SSLv2_srv - 1] = SSLv2_server_method();
ssl_methods[TLS_USE_SSLv2 - 1] = SSLv2_method();
ssl_methods[TLS_USE_SSLv3_cli - 1] = SSLv3_client_method();
ssl_methods[TLS_USE_SSLv3_srv - 1] = SSLv3_server_method();
ssl_methods[TLS_USE_SSLv3 - 1] = SSLv3_method();
ssl_methods[TLS_USE_TLSv1_cli - 1] = TLSv1_client_method();
ssl_methods[TLS_USE_TLSv1_srv - 1] = TLSv1_server_method();
ssl_methods[TLS_USE_TLSv1 - 1] = TLSv1_method();
ssl_methods[TLS_USE_SSLv23_cli - 1] = SSLv23_client_method();
ssl_methods[TLS_USE_SSLv23_srv - 1] = SSLv23_server_method();
ssl_methods[TLS_USE_SSLv23 - 1] = SSLv23_method();
}
/*
* Setup default SSL_CTX (and SSL * ) behavior:
* verification, cipherlist, acceptable versions, ...
*/
static int
init_ssl_ctx_behavior( struct tls_domain *d ) {
int verify_mode;
if( d->ciphers_list != 0 ) {
if( SSL_CTX_set_cipher_list(d->ctx, d->ciphers_list) == 0 ) {
LOG( L_ERR, "init_ssl_ctx_behavior: failure to set SSL context "
"cipher list '%s'\n", d->ciphers_list);
return -1;
} else {
LOG( L_NOTICE, "init_ssl_ctx_behavior: cipher list set to %s\n",
d->ciphers_list);
}
} else {
DBG( "init_ssl_ctx_behavior: cipher list null ... setting default\n");
}
/* Set a bunch of options:
* do not accept SSLv2
* no session resumption
* choose cipher according to server's preference's*/
#if OPENSSL_VERSION_NUMBER >= 0x000907000
SSL_CTX_set_options(d->ctx,
SSL_OP_ALL | SSL_OP_NO_SSLv2 |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
SSL_OP_CIPHER_SERVER_PREFERENCE);
#else
SSL_CTX_set_options(d->ctx,
SSL_OP_ALL | SSL_OP_NO_SSLv2 );
#endif
/* Set verification procedure
* The verification can be made null with SSL_VERIFY_NONE, or
* at least easier with SSL_VERIFY_CLIENT_ONCE instead of
* SSL_VERIFY_FAIL_IF_NO_PEER_CERT.
* For extra control, instead of 0, we can specify a callback function:
* int (*verify_callback)(int, X509_STORE_CTX *)
* Also, depth 2 may be not enough in some scenarios ... though no need
* to increase it much further */
if (d->type & TLS_DOMAIN_SRV) {
/* Server mode:
* SSL_VERIFY_NONE
* the server will not send a client certificate request to the
* client, so the client will not send a certificate.
* SSL_VERIFY_PEER
* the server sends a client certificate request to the client.
* The certificate returned (if any) is checked. If the verification
* process fails, the TLS/SSL handshake is immediately terminated
* with an alert message containing the reason for the verification
* failure. The behaviour can be controlled by the additional
* SSL_VERIFY_FAIL_IF_NO_PEER_CERT and SSL_VERIFY_CLIENT_ONCE flags.
* SSL_VERIFY_FAIL_IF_NO_PEER_CERT
* if the client did not return a certificate, the TLS/SSL handshake
* is immediately terminated with a ``handshake failure'' alert.
* This flag must be used together with SSL_VERIFY_PEER.
* SSL_VERIFY_CLIENT_ONCE
* only request a client certificate on the initial TLS/SSL
* handshake. Do not ask for a client certificate again in case of
* a renegotiation. This flag must be used together with
* SSL_VERIFY_PEER.
*/
if( d->verify_cert ) {
verify_mode = SSL_VERIFY_PEER;
if( d->require_client_cert ) {
LOG( L_WARN, "TLS: Client verification activated. Client "
"certificates are mandatory.\n");
verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
} else
LOG( L_WARN, "TLS: Client verification activated. Client "
"certificates are NOT mandatory.\n");
} else {
verify_mode = SSL_VERIFY_NONE;
LOG( L_WARN, "TLS: Client verification NOT activated. Weaker "
"security.\n");
}
} else {
/* Client mode:
* SSL_VERIFY_NONE
* if not using an anonymous cipher (by default disabled), the
* server will send a certificate which will be checked. The result
* of the certificate verification process can be checked after the
* TLS/SSL handshake using the SSL_get_verify_result(3) function.
* The handshake will be continued regardless of the verification
* result.
* SSL_VERIFY_PEER
* the server certificate is verified. If the verification process
* fails, the TLS/SSL handshake is immediately terminated with an
* alert message containing the reason for the verification failure.
* If no server certificate is sent, because an anonymous cipher is
* used, SSL_VERIFY_PEER is ignored.
* SSL_VERIFY_FAIL_IF_NO_PEER_CERT
* ignored
* SSL_VERIFY_CLIENT_ONCE
* ignored
*/
if( d->verify_cert ) {
verify_mode = SSL_VERIFY_PEER;
LOG( L_WARN, "TLS: Server verification activated.\n");
} else {
verify_mode = SSL_VERIFY_NONE;
LOG( L_WARN, "TLS: Server verification NOT activated. Weaker "
"security.\n");
}
}
SSL_CTX_set_verify( d->ctx, verify_mode, verify_callback);
SSL_CTX_set_verify_depth( d->ctx, VERIFY_DEPTH_S);
SSL_CTX_set_session_cache_mode( d->ctx, SSL_SESS_CACHE_SERVER );
SSL_CTX_set_session_id_context( d->ctx, SER_SSL_SESS_ID,
SER_SSL_SESS_ID_LEN );
return 0;
}
static int check_for_krb()
{
SSL_CTX *xx;
int j;
xx = SSL_CTX_new(ssl_methods[tls_method - 1]);
if (xx==NULL)
return -1;
for( j=0 ; j<M_sk_num(xx->cipher_list) ; j++) {
SSL_CIPHER *yy = (SSL_CIPHER*)M_sk_value(xx->cipher_list,j);
if ( yy->id>=SSL3_CK_KRB5_DES_64_CBC_SHA &&
yy->id<=SSL3_CK_KRB5_RC4_40_MD5 ) {
LOG(L_INFO,"INFO:tls:check_for_krb: KRB5 cipher %s found\n",
yy->name);
SSL_CTX_free(xx);
return 1;
}
}
SSL_CTX_free(xx);
return 0;
}
/*
* called once from main.c (main process)
*/
int
init_tls(void)
{
int i;
#if (OPENSSL_VERSION_NUMBER >= 0x00908000L) && !defined(OPENSSL_NO_COMP)
STACK_OF(SSL_COMP)* comp_methods;
#endif
DBG("init_tls: Entered\n");
#if OPENSSL_VERSION_NUMBER < 0x00907000L
LOG(L_ERR, "WARNING! You are using an old version of OpenSSL (< 0.9.7). "
"Upgrade!\n");
#endif
/*
* this has to be called before any function calling CRYPTO_malloc,
* CRYPTO_malloc will set allow_customize in openssl to 0
*/
if (!CRYPTO_set_mem_functions(ser_malloc, ser_realloc, ser_free)) {
LOG(L_ERR,
"init_tls: Unable to set the memory allocation functions\n");
return -1;
}
#if (OPENSSL_VERSION_NUMBER >= 0x00908000L) && !defined(OPENSSL_NO_COMP)
/* disabling compression */
LOG(L_ERR, "WARNING:init_tls: disabling compression due ZLIB problems\n");
comp_methods = SSL_COMP_get_compression_methods();
if (comp_methods==0) {
LOG(L_ERR, "ERRRO:init_tls: null openssl compression methods\n");
return -1;
}
sk_SSL_COMP_zero(comp_methods);
#endif
SSL_library_init();
SSL_load_error_strings();
init_ssl_methods();
i = check_for_krb();
if (i==-1) {
LOG(L_ERR, "ERROR:init_tls: kerberos check failed\n");
return -1;
}
if ( ( i ^
#ifndef OPENSSL_NO_KRB5
1
#else
0
#endif
)!=0 ) {
LOG(L_ERR, "ERROR:init_tls: compiled agaist an openssl with %s"
"kerberos, but run with one with %skerberos\n",
(i==1)?"":"no ",(i!=1)?"no ":"");
return -1;
}
/*
* now initialize tls default domains
*/
if ( (i=init_tls_domains(tls_default_server_domain)) ) {
return i;
}
if ( (i=init_tls_domains(tls_default_client_domain)) ) {
return i;
}
/*
* now initialize tls virtual domains
*/
if ( (i=init_tls_domains(tls_server_domains)) ) {
return i;
}
if ( (i=init_tls_domains(tls_client_domains)) ) {
return i;
}
/*
* we are all set
*/
return 0;
}
/*
* initialize tls virtual domains
*/
int
init_tls_domains(struct tls_domain *d)
{
struct tls_domain *dom;
dom = d;
while (d) {
if (d->name.len) {
LOG(L_INFO, "init_tls_domains: Processing TLS domain '%.*s'\n",
d->name.len, ZSW(d->name.s));
} else {
LOG(L_INFO, "init_tls_domains: Processing TLS domain [%s:%d]\n",
ip_addr2a(&d->addr), d->port);
}
/*
* set method
*/
if (d->method == TLS_METHOD_UNSPEC) {
DBG("init_tls_domains: No method for tls[%s:%d], using default\n",
ip_addr2a(&d->addr), d->port);
d->method = tls_method;
}
/*
* create context
*/
d->ctx = SSL_CTX_new(ssl_methods[d->method - 1]);
if (d->ctx == NULL) {
LOG(L_ERR, "init_tls_domains: Cannot create ssl context for "
"tls[%s:%d]\n", ip_addr2a(&d->addr), d->port);
return -1;
}
if (init_ssl_ctx_behavior( d ) < 0)
return -1;
/*
* load certificate
*/
if (!d->cert_file) {
LOG(L_NOTICE, "init_tls_domains: No certificate for tls[%s:%d] "
"defined, using default '%s'\n", ip_addr2a(&d->addr), d->port,
tls_cert_file);
d->cert_file = tls_cert_file;
}
if (load_certificate(d->ctx, d->cert_file) < 0)
return -1;
/*
* load ca
*/
if (!d->ca_file) {
LOG(L_NOTICE, "init_tls_domains: No CA for tls[%s:%d] defined, "
"using default '%s'\n", ip_addr2a(&d->addr), d->port,
tls_ca_file);
d->ca_file = tls_ca_file;
}
if (d->ca_file && load_ca(d->ctx, d->ca_file) < 0)
return -1;
d = d->next;
}
/*
* load all private keys as the last step (may prompt for password)
*/
d = dom;
while (d) {
if (!d->pkey_file) {
LOG(L_NOTICE, "init_tls_domain: No private key for tls[%s:%d] "
"defined, using default '%s'\n", ip_addr2a(&d->addr),
d->port, tls_pkey_file);
d->pkey_file = tls_pkey_file;
}
if (load_private_key(d->ctx, d->pkey_file) < 0)
return -1;
d = d->next;
}
return 0;
}
/*
* called from main.c when openser exits (main process)
*/
void
destroy_tls(void)
{
struct tls_domain *d;
DBG("destroy_tls: Entered\n");
d = tls_server_domains;
while (d) {
if (d->ctx)
SSL_CTX_free(d->ctx);
d = d->next;
}
d = tls_client_domains;
while (d) {
if (d->ctx)
SSL_CTX_free(d->ctx);
d = d->next;
}
if (tls_default_server_domain && tls_default_server_domain->ctx) {
SSL_CTX_free(tls_default_server_domain->ctx);
}
if (tls_default_client_domain && tls_default_client_domain->ctx) {
SSL_CTX_free(tls_default_client_domain->ctx);
}
tls_free_domains();
/* library destroy */
ERR_free_strings();
/*SSL_free_comp_methods(); - this function is not on std. openssl*/
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
}
/*
* called once from main.c (main process) before
* parsing the configuration
*/
int pre_init_tls(void)
{
DBG("pre_init_tls: Entered\n");
tls_default_client_domain = tls_new_domain(TLS_DOMAIN_DEF|TLS_DOMAIN_CLI);
if (tls_default_client_domain==0) {
LOG(L_ERR, "ERROR:tls:pre_init_tls: failed to initialize "
"tls_default_client_domain\n");
return -1;
}
tls_default_client_domain->addr.af = AF_INET;
tls_default_server_domain = tls_new_domain(TLS_DOMAIN_DEF|TLS_DOMAIN_SRV);
if (tls_default_server_domain==0) {
LOG(L_ERR, "ERROR:tls:pre_init_tls: failed to initialize "
"tls_default_server_domain\n");
return -1;
}
tls_default_server_domain->addr.af = AF_INET;
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1