/* SPDX-License-Identifier: LGPL-3.0-or-later */
/*
* Copyright (C) 2008 Banco do Brasil S.A.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* along with this program. If not, see .
*/
/**
* @brief Negotiate OpenSSL session.
*
*/
#include "private.h"
#include
#include
static int import_crl(H3270 *hSession, SSL_CTX * ssl_ctx, LIB3270_NET_CONTEXT * context, const char *url) {
X509_CRL * x509_crl = NULL;
const char *error_message = NULL;
if(strncasecmp(url,"ldap",4) == 0) {
// Download using LDAP
#ifdef HAVE_LDAP
x509_crl = lib3270_crl_get_using_ldap(hSession, url, &error_message);
#else
error_message = _("No LDAP support");
#endif // HAVE_LDAP
} else {
// Download with URL
lib3270_autoptr(char) crl_text = lib3270_url_get(hSession, url, &error_message);
lib3270_autoptr(BIO) bio = BIO_new_mem_buf(crl_text,-1);
BIO * b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
if(!d2i_X509_CRL_bio(bio, &x509_crl)) {
trace_ssl(hSession,"Can't decode CRL data:\n%s\n",crl_text);
error_message = _("Can't decode CRL data");
}
}
if(error_message)
trace_ssl(hSession,"Error downloading CRL from %s: %s\n",url,error_message);
if(!x509_crl)
return -1;
lib3270_openssl_crl_free(context);
context->crl.cert = x509_crl;
if(lib3270_get_toggle(hSession,LIB3270_TOGGLE_SSL_TRACE)) {
lib3270_autoptr(BIO) bio = BIO_new(BIO_s_mem());
X509_CRL_print(bio,x509_crl);
unsigned char *data = NULL;
int n = BIO_get_mem_data(bio, &data);
lib3270_autoptr(char) text = (char *) lib3270_malloc(n+1);
memcpy(text,data,n);
text[n] ='\0';
trace_ssl(hSession,"CRL Data:\n%s\n",text);
}
// Add CRL in the store.
X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
if(X509_STORE_add_crl(store, x509_crl)) {
trace_ssl(hSession,"CRL was added to context cert store\n");
return 0;
}
trace_ssl(hSession,"CRL was not added to context cert store\n");
return -1;
}
static int download_crl_from_peer(H3270 *hSession, SSL_CTX * ctx_context, LIB3270_NET_CONTEXT * context, X509 *peer) {
debug("%s peer=%p",__FUNCTION__,(void *) peer);
if(!peer)
return -1;
lib3270_autoptr(LIB3270_STRING_ARRAY) uris = lib3270_openssl_get_crls_from_peer(hSession, peer);
if(!uris) {
trace_ssl(hSession,"Can't get distpoints from peer certificate\n");
return -1;
}
size_t ix;
const char *prefer = lib3270_crl_get_preferred_protocol(hSession);
if(!prefer) {
// No preferred protocol, try all uris.
for(ix = 0; ix < uris->length; ix++) {
if(!import_crl(hSession,ctx_context,context,uris->str[ix])) {
trace_ssl(hSession,"Got CRL from %s\n",uris->str[ix]);
return 0;
}
}
return -1;
}
// Try preferred protocol.
trace_ssl(hSession,"CRL download protocol is set to %s\n",prefer);
size_t length = strlen(prefer);
for(ix = 0; ix < uris->length; ix++) {
if(strncasecmp(prefer,uris->str[ix],length))
continue;
if(!import_crl(hSession,ctx_context,context,uris->str[ix])) {
trace_ssl(hSession,"Got CRL from %s\n",uris->str[ix]);
return 0;
}
}
// Not found; try other ones
for(ix = 0; ix < uris->length; ix++) {
if(!strncasecmp(prefer,uris->str[ix],length))
continue;
if(!import_crl(hSession,ctx_context,context,uris->str[ix])) {
trace_ssl(hSession,"Got CRL from %s\n",uris->str[ix]);
return 0;
}
}
return -1;
}
int x509_store_ctx_error_callback(int ok, X509_STORE_CTX GNUC_UNUSED(*ctx)) {
debug("%s(%d)",__FUNCTION__,ok);
/*
55 {
56 if (!ok) {
57 Category::getInstance("OpenSSL").error(
58 "path validation failure at depth(%d): %s",
59 X509_STORE_CTX_get_error_depth(ctx),
60 X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx))
61 );
62 }
63 return ok;
64 }
*/
return ok;
}
int openssl_network_start_tls(H3270 *hSession) {
SSL_CTX * ctx_context = (SSL_CTX *) lib3270_openssl_get_context(hSession);
if(!ctx_context) {
if(!hSession->ssl.message) {
static const LIB3270_SSL_MESSAGE message = {
.type = LIB3270_NOTIFY_SECURE,
.summary = N_( "Cant get SSL context for current connection." )
};
hSession->ssl.message = &message;
}
return -1;
}
LIB3270_NET_CONTEXT * context = hSession->network.context;
debug("%s",__FUNCTION__);
context->con = SSL_new(ctx_context);
if(context->con == NULL) {
static const LIB3270_SSL_MESSAGE message = {
.type = LIB3270_NOTIFY_SECURE,
.summary = N_( "Cant create a new SSL structure for current connection." )
};
hSession->ssl.message = &message;
return -1;
}
SSL_set_ex_data(context->con,lib3270_openssl_get_ex_index(hSession),(char *) hSession);
// SSL_set_verify(context->con, SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// SSL_set_verify(context->con, SSL_VERIFY_PEER, NULL);
SSL_set_verify(context->con, SSL_VERIFY_NONE, NULL);
if(SSL_set_fd(context->con, context->sock) != 1) {
trace_ssl(hSession,"%s","SSL_set_fd failed!\n");
static const LIB3270_SSL_MESSAGE message = {
.summary = N_( "SSL negotiation failed" ),
.body = N_( "Cant set the file descriptor for the input/output facility for the TLS/SSL (encrypted) side of ssl." )
};
hSession->ssl.message = &message;
return -1;
}
trace_ssl(hSession, "%s","Running SSL_connect\n");
hSession->ssl.error = 0;
int rv = SSL_connect(context->con);
trace_ssl(hSession, "SSL_connect exits with rc=%d\n",rv);
if (rv != 1) {
LIB3270_SSL_MESSAGE message = {
.type = LIB3270_NOTIFY_ERROR,
.title = N_( "Connection failed" ),
.summary = N_("Unable to negotiate a secure connection with the host"),
};
if(!hSession->ssl.error)
hSession->ssl.error = SSL_get_error(context->con,rv);
if(hSession->ssl.error == SSL_ERROR_SYSCALL) {
// Some I/O error occurred.
// The OpenSSL error queue may contain more information on the error.
// If the error queue is empty (i.e. ERR_get_error() returns 0), ret
// can be used to find out more about the error:
// If ret == 0, an EOF was observed that violates the protocol.
// If ret == -1, the underlying BIO reported an I/O error
// (for socket I/O on Unix systems, consult errno for details).
if(rv == 0) {
message.body = N_("An EOF was observed that violates the protocol");
} else if(errno)
message.body = strerror(errno);
else
message.body = N_("Unexpected I/O error");
} else {
message.body = ERR_reason_error_string(hSession->ssl.error);
}
debug("SSL_connect failed: %s (rc=%d)\n",message.body ? message.body : message.summary, hSession->ssl.error);
trace_ssl(hSession,"SSL_connect failed: %s (rc=%d)\n",message.body ? message.body : message.summary, hSession->ssl.error);
hSession->ssl.message = &message;
return -1;
}
// Get peer certificate, notify application before validation.
lib3270_autoptr(X509) peer = SSL_get_peer_certificate(context->con);
if(peer) {
if(lib3270_get_toggle(hSession,LIB3270_TOGGLE_SSL_TRACE)) {
BIO * out = BIO_new(BIO_s_mem());
unsigned char * data;
unsigned char * text;
int n;
X509_print(out,peer);
n = BIO_get_mem_data(out, &data);
text = (unsigned char *) malloc (n+1);
text[n] ='\0';
memcpy(text,data,n);
trace_ssl(hSession,"TLS/SSL peer certificate:\n%s\n",text);
free(text);
BIO_free(out);
}
}
// Do we really need to download a new CRL?
if(lib3270_ssl_get_crl_download(hSession) && SSL_get_verify_result(context->con) == X509_V_ERR_UNABLE_TO_GET_CRL) {
// CRL download is enabled and verification has failed; look for CRL file.
trace_ssl(hSession,"CRL Validation has failed, requesting CRL download\n");
set_ssl_state(hSession,LIB3270_SSL_VERIFYING);
int rc_download = -1;
if(context->crl.url) {
rc_download = import_crl(hSession, ctx_context,context,context->crl.url);
} else {
rc_download = download_crl_from_peer(hSession, ctx_context, context, peer);
}
debug("Download rc=%d",rc_download);
if(!rc_download) {
// Got CRL, verify it!
// Reference: https://stackoverflow.com/questions/10510850/how-to-verify-the-certificate-for-the-ongoing-ssl-session
X509_STORE_CTX *csc = X509_STORE_CTX_new();
X509_STORE_CTX_set_verify_cb(csc, x509_store_ctx_error_callback);
X509_STORE_CTX_init(csc, SSL_CTX_get_cert_store(ctx_context), peer, NULL);
if(X509_verify_cert(csc) != 1)
rv = X509_STORE_CTX_get_error(csc);
else
rv = X509_V_OK;
trace_ssl(hSession, "X509_verify_cert error code was %d\n", rv);
SSL_set_verify_result(context->con, rv);
X509_STORE_CTX_free(csc);
}
}
//
// Validate SSL state.
//
long verify_result = SSL_get_verify_result(context->con);
// Get validation message.
hSession->ssl.message = lib3270_openssl_message_from_id(verify_result);
debug("Verify message: %s",hSession->ssl.message->summary);
// Trace cypher
if(lib3270_get_toggle(hSession,LIB3270_TOGGLE_SSL_TRACE)) {
char buffer[4096];
int alg_bits = 0;
const SSL_CIPHER * cipher = SSL_get_current_cipher(context->con);
trace_ssl(hSession,"TLS/SSL cipher description: %s",SSL_CIPHER_description((SSL_CIPHER *) cipher, buffer, 4095));
SSL_CIPHER_get_bits(cipher, &alg_bits);
trace_ssl(hSession,"%s version %s with %d bits\n",
SSL_CIPHER_get_name(cipher),
SSL_CIPHER_get_version(cipher),
alg_bits);
}
// Check results.
if(hSession->ssl.message)
trace_ssl(hSession,"%s\n",hSession->ssl.message->summary);
else
trace_ssl(hSession,"TLS/SSL verify result was %ld\n", verify_result);
set_ssl_state(hSession,LIB3270_SSL_NEGOTIATED);
return 0;
}