/* * Copyright (c) 2003-2006 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ /* ** This code contains parts of Lutz Jaenicke's TLS patch for postfix. ** http://www.aet.tu-cottbus.de/personen/jaenicke/pfixtls/ */ #include "sm/generic.h" SM_RCSID("@(#)$Id: smtls.c,v 1.44 2007/05/04 03:14:21 ca Exp $") #include "sm/error.h" #include "sm/assert.h" #include "sm/io.h" #define TLSL_LOG_DEFINES 1 #include "sm/tls.h" #if MTA_USE_TLS /* use SSL extension data instead of passing tlsl_ctx around? some callback functions don't have an explicit user context, e.g., tmp_rsa_key(), apps_ssl_info_cb(). check other programs how to do that. */ #if !TLS_NO_RSA static RSA *rsa_tmp = NULL; /* temporary RSA key */ static RSA *tmp_rsa_key(SSL *con, int _export, int k_eylength); # define RSA_KEYLENGTH 512 #endif #if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L static int tls_verify_cb(X509_STORE_CTX *); # define CONST097 #else static int tls_verify_cb(X509_STORE_CTX *, void *); # define CONST097 const #endif #define MAXNAME 256 #define bitset(bit, word) (((bit) & (word)) != 0) #if !NO_DH static DH *get_dh512(void); static uchar dh512_p[] = { 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75, 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F, 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3, 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12, 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C, 0x47,0x74,0xE8,0x33 }; static uchar dh512_g[] = { 0x02 }; static DH * get_dh512(void) { DH *dh = NULL; if ((dh = DH_new()) == NULL) return NULL; dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL); if ((dh->p == NULL) || (dh->g == NULL)) return NULL; return dh; } #endif /* !NO_DH */ /* ** TLSLOGERR -- log the errors from the TLS error stack ** ** Parameters: ** who -- server/client (for logging). ** ** Returns: ** none. */ void tlslogerr(tlsl_ctx_P tlsl_ctx, char *who) { ulong l; int line, flags; ulong es; char *file, *data; char buf[256]; # define CP (const char **) es = CRYPTO_thread_id(); while ((l = ERR_get_error_line_data(CP &file, &line, CP &data, &flags)) != 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 12, "STARTTLS=%s: %lu:%s:%s:%d:%s", who, es, ERR_error_string(l, buf), file, line, bitset(ERR_TXT_STRING, flags) ? data : ""); } } /* ** SM_TLS_INIT_LIBRARY -- setup TLS library for global use ** ** Parameters: ** ptlsl_ctx -- (pointer to) TLS library context (output) ** ** Returns: ** succeeded? */ sm_ret_T sm_tls_init_library(tlsl_ctx_P *ptlsl_ctx) { sm_ret_T ret; tlsl_ctx_P tlsl_ctx; SM_REQUIRE(ptlsl_ctx != NULL); *ptlsl_ctx = NULL; tlsl_ctx = (tlsl_ctx_P) sm_zalloc(sizeof(*tlsl_ctx)); if (tlsl_ctx == NULL) return sm_error_temp(SM_EM_TLS, ENOMEM); /* basic TLS initialization, ignore result for now */ SSL_library_init(); SSL_load_error_strings(); ret = sm_log_create(NULL, &tlsl_ctx->tlsl_lctx, &tlsl_ctx->tlsl_lcfg); if (sm_is_err(ret)) goto error; ret = sm_log_setfile(tlsl_ctx->tlsl_lctx, smioerr); if (sm_is_err(ret)) goto error; ret = sm_log_setdebuglevel(tlsl_ctx->tlsl_lctx, 9); /* XXX */ if (sm_is_err(ret)) goto error; *ptlsl_ctx = tlsl_ctx; return SM_SUCCESS; error: /* cleanup? */ return ret; } /* ** SET_TLS_LOG -- set TLS logging ** ** Parameters: ** tlsl_ctx -- TLS library context ** fp -- file to use ** fd -- fd to use ** loglevel -- loglevel ** ** Returns: ** succeeded? */ sm_ret_T sm_set_tls_log(tlsl_ctx_P tlsl_ctx, sm_file_T *fp, int fd, int loglevel) { sm_ret_T ret; ret = sm_log_setfp_fd(tlsl_ctx->tlsl_lctx, fp, fd); if (sm_is_err(ret)) goto error; ret = sm_log_setdebuglevel(tlsl_ctx->tlsl_lctx, loglevel); if (sm_is_err(ret)) goto error; return SM_SUCCESS; error: return ret; } /* ** status in initialization ** these flags keep track of the status of the initialization ** i.e., whether a file exists (_EX) and whether it can be used (_OK) ** [due to permissions] */ #define TLS_S_NONE 0x00000000 /* none yet */ #define TLS_S_CERT_EX 0x00000001 /* cert file exists */ #define TLS_S_CERT_OK 0x00000002 /* cert file is ok */ #define TLS_S_KEY_EX 0x00000004 /* key file exists */ #define TLS_S_KEY_OK 0x00000008 /* key file is ok */ #define TLS_S_CERTP_EX 0x00000010 /* CA cert path exists */ #define TLS_S_CERTP_OK 0x00000020 /* CA cert path is ok */ #define TLS_S_CERTF_EX 0x00000040 /* CA cert file exists */ #define TLS_S_CERTF_OK 0x00000080 /* CA cert file is ok */ #define TLS_S_CERT2_EX 0x00001000 /* 2nd cert file exists */ #define TLS_S_CERT2_OK 0x00002000 /* 2nd cert file is ok */ #define TLS_S_KEY2_EX 0x00004000 /* 2nd key file exists */ #define TLS_S_KEY2_OK 0x00008000 /* 2nd key file is ok */ #define TLS_S_DH_OK 0x00200000 /* DH cert is ok */ #define TLS_S_DHPAR_EX 0x00400000 /* DH param file exists */ #define TLS_S_DHPAR_OK 0x00800000 /* DH param file is ok to use */ /* ** TLS_SET_VERIFY -- request client certificate? ** ** Parameters: ** ctx -- TLS context ** ssl -- TLS structure ** vrfy -- require certificate? ** ** Returns: ** none. ** ** Side Effects: ** Sets verification state for TLS ** #if TLS_VRFY_PER_CTX ** Notice: ** This is per TLS context, not per TLS structure; ** the former is global, the latter per connection. ** It would be nice to do this per connection, but this ** doesn't work in the current TLS libraries :-( #endif * TLS_VRFY_PER_CTX * */ void tls_set_verify(SSL_CTX *ctx, SSL *ssl, bool vrfy) { #if !TLS_VRFY_PER_CTX SSL_set_verify(ssl, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); #else SSL_CTX_set_verify(ctx, vrfy ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); #endif } /* ** SM_TLS_INIT -- initialize TLS ** ** Parameters: ** tlsl_ctx -- TLS library context ** ctx -- pointer to context (output) ** req -- requirements for initialization ** srv -- server side? ** tls_cnf -- TLS configuration data ** ** Returns: ** succeeded? */ /* ** The session_id_context identifies the service that created a session. ** This information is used to distinguish between multiple TLS-based ** servers running on the same server. We use the name of the mail system. ** Note: the session cache is not persistent. */ static char server_session_id_context[] = "MeTA1"; /* 0.9.8a and b have a problem with SSL_OP_TLS_BLOCK_PADDING_BUG */ #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) # define SM_SSL_OP_TLS_BLOCK_PADDING_BUG 1 #else # define SM_SSL_OP_TLS_BLOCK_PADDING_BUG 0 #endif sm_ret_T sm_tls_init(tlsl_ctx_P tlsl_ctx, SSL_CTX **ctx, ulong req, bool srv, tls_cnf_P tls_cnf) { #if !NO_DH static DH *dh = NULL; #endif int r; bool ok; long status, options; char *who; const char *dhparam; #if SM_SSL_OP_TLS_BLOCK_PADDING_BUG long rt_version; STACK_OF(SSL_COMP) *comp_methods; #endif SM_REQUIRE(ctx != NULL); SM_REQUIRE(tls_cnf != NULL); status = TLS_S_NONE; who = srv ? "server" : "client"; dhparam = tls_cnf->tlscnf_dhparam; /* already initialized? (we could re-init...) */ if (*ctx != NULL) return SM_SUCCESS; ok = true; /* ** valid values for dhparam are (only the first char is checked) ** none no parameters: don't use DH ** 512 generate 512 bit parameters (fixed) ** 1024 generate 1024 bit parameters ** /file/name read parameters from /file/name ** default is: 1024 for server, 512 for client (OK? XXX) */ if (bitset(TLS_I_TRY_DH, req)) { if (dhparam != NULL) { char c; c = *dhparam; if (c == '1') req |= TLS_I_DH1024; else if (c == '5') req |= TLS_I_DH512; else if (c != 'n' && c != 'N' && c != '/') { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 12, "STARTTLS=%s, DHParam=%s, error=illegal_value", who, dhparam); dhparam = NULL; } } if (dhparam == NULL) dhparam = srv ? "1" : "5"; else if (*dhparam == '/') { #if 0 TLS_OK_F(dhparam, "DHParameters", bitset(TLS_I_DHPAR_EX, req), TLS_S_DHPAR_EX, srv); #endif } } if (!ok) return sm_error_perm(SM_EM_TLS, EINVAL); #define TLS_OK_F(var, fn, req, st, srv) if (ok) \ { \ r = (var != NULL && *var != '\0'); \ if (r) \ status |= st; \ else if (req) \ ok = false; \ } TLS_OK_F(tls_cnf->tlscnf_certfile, "CertFile", bitset(TLS_I_CERT_EX, req), TLS_S_CERT_EX|TLS_S_CERT_OK, srv); TLS_OK_F(tls_cnf->tlscnf_keyfile, "KeyFile", bitset(TLS_I_KEY_EX, req), TLS_S_KEY_EX|TLS_S_KEY_OK, srv); TLS_OK_F(tls_cnf->tlscnf_cacertpath, "CACertPath", bitset(TLS_I_CERTP_EX, req), TLS_S_CERTP_EX|TLS_S_CERTP_OK, srv); TLS_OK_F(tls_cnf->tlscnf_cacertfile, "CACertFile", bitset(TLS_I_CERTF_EX, req), TLS_S_CERTF_EX|TLS_S_CERTF_OK, srv); if (tls_cnf->tlscnf_dsa_certfile != NULL && tls_cnf->tlscnf_dsa_keyfile != NULL) { TLS_OK_F(tls_cnf->tlscnf_dsa_certfile, "Cert2File", bitset(TLS_I_CERT_EX, req), TLS_S_CERT2_EX, srv ? TLS_T_SRV : TLS_T_CLT); TLS_OK_F(tls_cnf->tlscnf_dsa_keyfile, "Key2File", bitset(TLS_I_KEY_EX, req), TLS_S_KEY2_EX, srv ? TLS_T_SRV : TLS_T_CLT); } /* create a method and a new context */ if ((*ctx = SSL_CTX_new(srv ? SSLv23_server_method() : SSLv23_client_method())) == NULL) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_new(SSLv23_%s_method())=failed", who, who); tlslogerr(tlsl_ctx, who); return sm_error_perm(SM_EM_TLS, EINVAL); } #if TLS_NO_RSA /* turn off backward compatibility, required for no-rsa */ SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2); #endif #if !TLS_NO_RSA /* ** Create a temporary RSA key ** XXX Maybe we shouldn't create this always (even though it ** is only at startup). ** It is a time-consuming operation and it is not always necessary. ** maybe we should do it only on demand... */ if (bitset(TLS_I_RSA_TMP, req) && (rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL)) == NULL ) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, RSA_generate_key=failed", who); tlslogerr(tlsl_ctx, who); return sm_error_perm(SM_EM_TLS, EINVAL); } #endif /* !TLS_NO_RSA */ /* load private key */ if (bitset(TLS_S_KEY_OK, status) && (r = SSL_CTX_use_PrivateKey_file(*ctx, tls_cnf->tlscnf_keyfile, SSL_FILETYPE_PEM)) <= 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_use_PrivateKey_file=failed, file=%s, ret=%d", who, tls_cnf->tlscnf_keyfile, r); tlslogerr(tlsl_ctx, who); if (bitset(TLS_I_USE_KEY, req)) return sm_error_perm(SM_EM_TLS, EINVAL); } /* get the certificate file */ if (bitset(TLS_S_CERT_OK, status) && SSL_CTX_use_certificate_file(*ctx, tls_cnf->tlscnf_certfile, SSL_FILETYPE_PEM) <= 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_use_certificate_file=failed, file=%s", who, tls_cnf->tlscnf_certfile); tlslogerr(tlsl_ctx, who); if (bitset(TLS_I_USE_CERT, req)) return sm_error_perm(SM_EM_TLS, EINVAL); } /* check the private key */ if (bitset(TLS_S_KEY_OK, status) && (r = SSL_CTX_check_private_key(*ctx)) <= 0) { /* Private key does not match the certificate public key */ sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_check_private_key=failed, file=%s, error=%d", who, tls_cnf->tlscnf_keyfile, r); tlslogerr(tlsl_ctx, who); if (bitset(TLS_I_USE_KEY, req)) return sm_error_perm(SM_EM_TLS, EINVAL); } /* XXX this code is pretty much duplicated from above! */ /* load private key */ if (bitset(TLS_S_KEY2_OK, status) && SSL_CTX_use_PrivateKey_file(*ctx, tls_cnf->tlscnf_dsa_keyfile, SSL_FILETYPE_PEM) <= 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_use_PrivateKey_file=failed, file=%s", who, tls_cnf->tlscnf_dsa_keyfile); tlslogerr(tlsl_ctx, who); } /* get the certificate file */ if (bitset(TLS_S_CERT2_OK, status) && SSL_CTX_use_certificate_file(*ctx, tls_cnf->tlscnf_dsa_certfile, SSL_FILETYPE_PEM) <= 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_use_certificate_file=failed, file=%s", who, tls_cnf->tlscnf_dsa_certfile); tlslogerr(tlsl_ctx, who); } /* also check the private key */ if (bitset(TLS_S_KEY2_OK, status) && (r = SSL_CTX_check_private_key(*ctx)) <= 0) { /* Private key does not match the certificate public key */ sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_check_private_key/2=failed, error=%d", who, r); tlslogerr(tlsl_ctx, who); } /* SSL_CTX_set_quiet_shutdown(*ctx, 1); violation of standard? */ options = (tls_cnf->tlscnf_options_isset != 0) ? tls_cnf->tlscnf_options : SSL_OP_ALL; #if SM_SSL_OP_TLS_BLOCK_PADDING_BUG /* ** In OpenSSL 0.9.8[ab], enabling zlib compression breaks the ** padding bug work-around, leading to false positives and ** failed connections. We may not interoperate with systems ** with the bug, but this is better than breaking on all 0.9.8[ab] ** systems that have zlib support enabled. ** Note: this checks the runtime version of the library, not ** just the compile time version. */ rt_version = SSLeay(); if (rt_version >= 0x00908000L && rt_version <= 0x0090802fL) { comp_methods = SSL_COMP_get_compression_methods(); if (comp_methods != NULL && sk_SSL_COMP_num(comp_methods) > 0) options &= ~SSL_OP_TLS_BLOCK_PADDING_BUG; } #endif SSL_CTX_set_options(*ctx, options); #if !NO_DH /* Diffie-Hellman initialization */ if (bitset(TLS_I_TRY_DH, req)) { if (bitset(TLS_S_DHPAR_OK, status)) { BIO *bio; if ((bio = BIO_new_file(dhparam, "r")) != NULL) { dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if (dh == NULL) { ulong err; err = ERR_get_error(); sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, DH_parameters=%s, error=cannot read, status=%s", who, dhparam, ERR_error_string(err, NULL)); tlslogerr(tlsl_ctx, who); } } else { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 5, "STARTTLS=%s, DH_parameters=%s, BIO_new_file=failed", who, dhparam); tlslogerr(tlsl_ctx, who); } } if (dh == NULL && bitset(TLS_I_DH1024, req)) { DSA *dsa; /* this takes a while! (7-130s on a 450MHz AMD K6-2) */ dsa = DSA_generate_parameters(1024, NULL, 0, NULL, NULL, 0, NULL); dh = DSA_dup_DH(dsa); DSA_free(dsa); } else if (dh == NULL && bitset(TLS_I_DH512, req)) dh = get_dh512(); if (dh == NULL) { ulong err; err = ERR_get_error(); sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 9, "STARTTLS=%s, DH_parameters=%s, error=cannot_read_or_set, status=%s", who, dhparam, ERR_error_string(err, NULL)); if (bitset(TLS_I_REQ_DH, req)) return sm_error_perm(SM_EM_TLS, EINVAL); } else { SSL_CTX_set_tmp_dh(*ctx, dh); /* important to avoid small subgroup attacks */ SSL_CTX_set_options(*ctx, SSL_OP_SINGLE_DH_USE); sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_INFO, 13, "STARTTLS=%s, Diffie-Hellman=init, key=%d, dhparam=%c", who, 8 * DH_size(dh), *dhparam); DH_free(dh); } } #endif /* !NO_DH */ /* do we need this cache here?? */ if (bitset(TLS_I_CACHE, req) && tls_cnf->tlscnf_cache_size > 0) { r = SSL_CTX_sess_set_cache_size(*ctx, tls_cnf->tlscnf_cache_size); r = SSL_CTX_set_timeout(*ctx, tls_cnf->tlscnf_cache_tmout); r = SSL_CTX_set_session_id_context(*ctx, (const unsigned char *) &server_session_id_context, sizeof(server_session_id_context)); if (r != 1) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_ERR, 6, "STARTTLS=%s, SSL_CTX_set_session_id_context=failed" , who); } (void) SSL_CTX_set_session_cache_mode(*ctx, SSL_SESS_CACHE_SERVER); } else r = 0; if (r != 1) { (void) SSL_CTX_set_session_cache_mode(*ctx, SSL_SESS_CACHE_OFF); } /* load certificate locations and default CA paths */ if (bitset(TLS_S_CERTP_EX, status) && bitset(TLS_S_CERTF_EX, status)) { if ((r = SSL_CTX_load_verify_locations(*ctx, tls_cnf->tlscnf_cacertfile, tls_cnf->tlscnf_cacertpath)) == 1) { #if !TLS_NO_RSA if (bitset(TLS_I_RSA_TMP, req)) SSL_CTX_set_tmp_rsa_callback(*ctx, tmp_rsa_key); #endif /* ** We have to install our own verify callback: ** SSL_VERIFY_PEER requests a client cert but even ** though *FAIL_IF* isn't set, the connection ** will be aborted if the client presents a cert ** that is not "liked" (can't be verified?) by ** the TLS library :-( */ /* ** XXX currently we could call tls_set_verify() ** but we hope that that function will later on ** only set the mode per connection. */ SSL_CTX_set_verify(*ctx, bitset(TLS_I_NO_VRFY, req) ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL); /* install verify callback */ SSL_CTX_set_cert_verify_callback(*ctx, tls_verify_cb, (char *) tlsl_ctx); SSL_CTX_set_client_CA_list(*ctx, SSL_load_client_CA_file( tls_cnf->tlscnf_cacertfile)); } else { /* ** can't load CA data; do we care? ** the data is necessary to authenticate the client, ** which in turn would be necessary ** if we want to allow relaying based on it. */ sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, load_verify_locs=failed, CACertPath=%s, CACertFile=%s, error=%d", who, tls_cnf->tlscnf_cacertpath, tls_cnf->tlscnf_cacertfile, r); tlslogerr(tlsl_ctx, who); if (bitset(TLS_I_VRFY_LOC, req)) return sm_error_perm(SM_EM_TLS, EINVAL); } } #if 0 /* XXX: make this dependent on an option? */ if (tTd(96, 9)) SSL_CTX_set_info_callback(*ctx, apps_ssl_info_cb); #endif #if _FFR_TLS_1 /* install our own cipher list */ if (CipherList != NULL && *CipherList != '\0') { if (SSL_CTX_set_cipher_list(*ctx, CipherList) <= 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 7, "STARTTLS=%s, SSL_CTX_set_cipher_list=failed, list=%s, comment=ignored", who, CipherList); tlslogerr(tlsl_ctx, who); /* failure if setting to this list is required? */ } } #endif /* _FFR_TLS_1 */ return ok ? SM_SUCCESS : sm_error_perm(SM_EM_TLS, EINVAL); } /* ** TLS_GET_INFO -- get information about TLS connection ** ** Parameters: ** ssl -- TLS connection structure ** flags -- server or client, and other flags ** host -- hostname of other side ** tlsi_ctx -- TLS information context ** ** Returns: ** usual error code ** ** Side Effects: ** sets data in tlsi_ctx */ sm_ret_T tls_get_info(tlsl_ctx_P tlsl_ctx, SSL *ssl, uint flags, char *host, tlsi_ctx_P tlsi_ctx) { int b, r; char *s; #if 0 char *who; #endif SSL_CIPHER *c; X509 *cert; SM_REQUIRE(ssl != NULL); SM_REQUIRE(tlsi_ctx != NULL); c = SSL_get_current_cipher(ssl); #define TLS_IS_SRV (SM_IS_FLAG(flags, TLS_F_SRV)) #define TLS_CERT_REQ (SM_IS_FLAG(flags, TLS_F_CERT_REQ)) #define TLS_SET_STR(s, str) \ do \ { \ if ((s) != NULL) \ { \ r = strlen(s); \ if ((str) == NULL) \ (str) = sm_str_new(NULL, r + 1, r + 2); \ if ((str) != NULL) \ (void) sm_str_scopy((str), (s)); \ } \ } while (0) #define TLS_SET_XSTR(s, str) \ do \ { \ if ((s) != NULL) \ { \ r = strlen(s); \ if ((str) == NULL) \ (str) = sm_str_new(NULL, r + 2, r * 3); \ else \ sm_str_clr(str); \ if ((str) != NULL) \ (void) xtextify((s), "<>\")", str); \ } \ } while (0) s = (char *) SSL_CIPHER_get_name(c); TLS_SET_STR(s, tlsi_ctx->tlsi_cipher); b = SSL_CIPHER_get_bits(c, &r); tlsi_ctx->tlsi_cipher_bits = b; tlsi_ctx->tlsi_algs_bits = r; s = SSL_CIPHER_get_version(c); TLS_SET_STR(s, tlsi_ctx->tlsi_version); #if 0 who = TLS_IS_SRV ? "server" : "client"; #endif cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { #if SM_TLSI_CERT_MD5 uint n; uchar md[EVP_MAX_MD_SIZE]; #endif char buf[MAXNAME]; X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf); TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cert_subject); X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof buf); TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cert_issuer); X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, buf, sizeof buf); TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cn_subject); X509_NAME_get_text_by_NID(X509_get_issuer_name(cert), NID_commonName, buf, sizeof buf); TLS_SET_XSTR(buf, tlsi_ctx->tlsi_cn_issuer); #if SM_TLSI_CERT_MD5 n = 0; if (X509_digest(cert, EVP_md5(), md, &n) != 0 && n > 0) { sm_str_P str; static const char hexcodes[] = "0123456789ABCDEF"; r = (n * 3) + 2; if (tlsi_ctx->tlsi_cert_md5 == NULL) tlsi_ctx->tlsi_cert_md5 = sm_str_new(NULL, r, r + 2); str = tlsi_ctx->tlsi_cert_md5; if (str != NULL) { sm_str_clr(str); for (r = 0; r < (int) n; r++) { (void) sm_str_put(str, hexcodes[(md[r] & 0xf0) >> 4]); (void) sm_str_put(str, hexcodes[(md[r] & 0x0f)]); (void) sm_str_put(str, ':'); } (void) sm_str_term(str); } } else TLS_SET_STR("", tlsi_ctx->tlsi_cert_md5); #endif /* SM_TLSI_CERT_MD5 */ } else { TLS_SET_STR("", tlsi_ctx->tlsi_cert_subject); TLS_SET_STR("", tlsi_ctx->tlsi_cert_issuer); TLS_SET_STR("", tlsi_ctx->tlsi_cn_subject); TLS_SET_STR("", tlsi_ctx->tlsi_cn_issuer); #if SM_TLSI_CERT_MD5 TLS_SET_STR("", tlsi_ctx->tlsi_cert_md5); #endif } switch (SSL_get_verify_result(ssl)) { case X509_V_OK: if (cert != NULL) r = TLS_VRFY_OK; else if (TLS_CERT_REQ) r = TLS_VRFY_NO; else r = TLS_VRFY_NOTR; break; default: r = TLS_VRFY_FAIL; break; } tlsi_ctx->tlsi_vrfy = r; if (cert != NULL) X509_free(cert); #if 0 /* do some logging? leave it to application */ sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_INFO, 9, "STARTTLS=%s, verify=%s", who, s); #endif /* 0 */ return SM_SUCCESS; } /* ** ENDTLS -- shutdown secure connection ** ** Parameters: ** ssl -- SSL connection information. ** side -- server/client (for logging). ** ** Returns: ** success? (EX_* code) */ int endtls(tlsl_ctx_P tlsl_ctx, SSL *ssl, char *side) { int ret = SM_SUCCESS; if (ssl != NULL) { int r; if ((r = SSL_shutdown(ssl)) < 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 10, "STARTTLS=%s, SSL_shutdown=failed, error=%d", side, r); tlslogerr(tlsl_ctx, side); ret = sm_error_temp(SM_EM_TLS, r); } #if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL /* ** Bug in OpenSSL (at least up to 0.9.6b): ** From: Lutz.Jaenicke@aet.TU-Cottbus.DE ** Message-ID: <20010723152244.A13122@serv01.aet.tu-cottbus.de> ** To: openssl-users@openssl.org ** Subject: Re: SSL_shutdown() woes (fwd) ** ** The side sending the shutdown alert first will ** not care about the answer of the peer but will ** immediately return with a return value of "0" ** (ssl/s3_lib.c:ssl3_shutdown()). SSL_get_error will evaluate ** the value of "0" and as the shutdown alert of the peer was ** not received (actually, the program did not even wait for ** the answer), an SSL_ERROR_SYSCALL is flagged, because this ** is the default rule in case everything else does not apply. ** ** For your server the problem is different, because it ** receives the shutdown first (setting SSL_RECEIVED_SHUTDOWN), ** then sends its response (SSL_SENT_SHUTDOWN), so for the ** server the shutdown was successfull. ** ** As is by know, you would have to call SSL_shutdown() once ** and ignore an SSL_ERROR_SYSCALL returned. Then call ** SSL_shutdown() again to actually get the server's response. ** ** In the last discussion, Bodo Moeller concluded that a ** rewrite of the shutdown code would be necessary, but ** probably with another API, as the change would not be ** compatible to the way it is now. Things do not become ** easier as other programs do not follow the shutdown ** guidelines anyway, so that a lot error conditions and ** compitibility issues would have to be caught. ** ** For now the recommondation is to ignore the error message. */ else if (r == 0) { sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_WARN, 15, "STARTTLS=%s, SSL_shutdown=not_done", side); tlslogerr(tlsl_ctx, side); ret = sm_error_temp(SM_EM_TLS, EINVAL); /* XXX */ } #endif /* !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER > 0x0090602fL */ SSL_free(ssl); ssl = NULL; } return ret; } #if !TLS_NO_RSA /* ** TMP_RSA_KEY -- return temporary RSA key ** ** Parameters: ** s -- TLS connection structure ** export -- ** keylength -- ** ** Returns: ** temporary RSA key. */ # ifndef MAX_RSA_TMP_CNT # define MAX_RSA_TMP_CNT 100 /* XXX better value? */ # endif /* ARGUSED0 */ static RSA * tmp_rsa_key(SSL *s, int export, int keylength) { static int RSATmpCnt = 0; /* ** NOT thread safe! ** for thread safety: see comment at begin of file. ** Note: currently this is not used by any program that uses ** POSIX threads (only statethreads). */ if (++RSATmpCnt < MAX_RSA_TMP_CNT) return rsa_tmp; RSATmpCnt = 0; if (rsa_tmp != NULL) RSA_free(rsa_tmp); rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, NULL); if (rsa_tmp == NULL) { /* COMPLAIN, but HOW? */ } return rsa_tmp; } #endif /* !TLS_NO_RSA */ #if 0 /* ** APPS_SSL_INFO_CB -- info callback for TLS connections ** ** Parameters: ** s -- TLS connection structure ** where -- state in handshake ** ret -- return code of last operation ** ** Returns: ** none. */ static void apps_ssl_info_cb(CONST097 SSL *s, int where, int ret) { int w; char *str; BIO *bio_err; bio_err = NULL; #if 0 if (LogLevel > 14) sm_syslog(LOG_INFO, NOQID, "STARTTLS: info_callback where=0x%x, ret=%d", where, ret); #endif w = where & ~SSL_ST_MASK; if (bio_err == NULL) bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); if (bitset(SSL_ST_CONNECT, w)) str = "SSL_connect"; else if (bitset(SSL_ST_ACCEPT, w)) str = "SSL_accept"; else str = "undefined"; if (bitset(SSL_CB_LOOP, where)) { #if 0 if (LogLevel > 12) sm_syslog(LOG_NOTICE, NOQID, "STARTTLS: %s:%s", str, SSL_state_string_long(s)); #endif } else if (bitset(SSL_CB_ALERT, where)) { str = bitset(SSL_CB_READ, where) ? "read" : "write"; #if 0 if (LogLevel > 12) sm_syslog(LOG_NOTICE, NOQID, "STARTTLS: SSL3 alert %s:%s:%s", str, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); #endif } else if (bitset(SSL_CB_EXIT, where)) { if (ret == 0) { #if 0 if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: %s:failed in %s", str, SSL_state_string_long(s)); #endif } else if (ret < 0) { #if 0 if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: %s:error in %s", str, SSL_state_string_long(s)); #endif } } } #endif /* 0 */ /* ** TLS_VERIFY_LOG -- log verify error for TLS certificates ** ** Parameters: ** ok -- verify ok? ** ctx -- x509 context ** ** Returns: ** 0 -- fatal error ** 1 -- ok */ static int tls_verify_log(int ok, X509_STORE_CTX *ctx, void *myctx) { SSL *ssl; X509 *cert; int reason, depth; char buf[512]; tlsl_ctx_P tlsl_ctx; tlsl_ctx = (tlsl_ctx_P) myctx; cert = X509_STORE_CTX_get_current_cert(ctx); reason = X509_STORE_CTX_get_error(ctx); depth = X509_STORE_CTX_get_error_depth(ctx); ssl = (SSL *) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); if (ssl == NULL) { /* internal error */ sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_ERROR, 9, "STARTTLS=internal_error, func=tls_verify_cb, ssl=NULL"); return 0; } X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf); sm_log_write(tlsl_ctx->tlsl_lctx, TL_LCAT_INIT, TL_LMOD_CONF, SM_LOG_INFO, 14, "STARTTLS=info, func=tls_verify_cb, depth=%d, cert_subject=%s, stat=%d, reason=%s", depth, buf, ok, X509_verify_cert_error_string(reason)); return 1; } /* ** TLS_VERIFY_CB -- verify callback for TLS certificates ** ** Parameters: ** ctx -- x509 context ** ** Returns: ** accept connection? ** currently: always yes. */ static int # if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L tls_verify_cb(X509_STORE_CTX *ctx) # else tls_verify_cb(X509_STORE_CTX *ctx, void *myctx) # endif { int ok; ok = X509_verify_cert(ctx); if (ok == 0) { # if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x00907000L return 1; /* override it */ # else return tls_verify_log(ok, ctx, myctx); # endif } return ok; } /* ** TLS_VRFY2TXT -- return textual representation of verification result ** ** Parameters: ** vrfy -- verification result ** ** Returns: ** textual representation of verification result */ char * tls_vrfy2txt(int vrfy) { switch (vrfy) { case TLS_VRFY_OK: return "OK"; case TLS_VRFY_NO: return "NO"; case TLS_VRFY_NOTR: return "NOT"; case TLS_VRFY_FAIL: return "FAIL"; default: return "Unknown"; } return "OOPS"; } #endif /* MTA_USE_TLS */