TLS server doesn't receive certificate from client, fails to terminate handshake - c

I have a client and a server attempting to mutually authenticate one another and initiate a TLS connection. The certificates I'm using right now are self-signed.
In the server code I have SSL_VERIFY_FAIL_IF_NO_PEER_CERT set. The handshake succeeds, but SSL_get_peer_certificate returns a NULL pointer on the server side. If the client didn't return a certificate, why did the handshake not fail?
If I comment out the SSL_get_peer_certificate check on the server side, the client and server do connect and are able to communicate, but it isn't a TLS connection. When I watch them exchange packets over wireshark, I only see TCP traffic.
Server code:
BIO *acceptTLSConnection(char *port) {
BIO *sbio, *bbio, *acpt = NULL;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
SSL_library_init();
ctx = SSL_CTX_new(TLSv1_server_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
if(!SSL_CTX_use_certificate_file(ctx,"servercert.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_use_PrivateKey_file(ctx,"serverkey.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stderr);
fatalError("Error setting up SSL_CTX.");
}
if(!SSL_CTX_load_verify_locations(ctx, "clientcert.pem", NULL))
fatalError("Could not load trusted CA certificates.");
sbio=BIO_new_ssl(ctx,0);
BIO_get_ssl(sbio, &ssl);
if(!ssl) {
fatalError("Can't locate BIO SSL pointer.");
}
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
bbio = BIO_new(BIO_f_buffer());
sbio = BIO_push(bbio, sbio);
acpt=BIO_new_accept(port);
BIO_set_accept_bios(acpt,sbio);
/* Setup accept BIO */
if(BIO_do_accept(acpt) <= 0) {
ERR_print_errors_fp(stderr);
fatalError("Error in setting up accept BIO");
}
/* Now wait for incoming connection */
if(BIO_do_accept(acpt) <= 0) {
ERR_print_errors_fp(stderr);
fatalError("Error in connection");
}
sbio = BIO_pop(acpt);
BIO_free_all(acpt);
if(BIO_do_handshake(sbio) <= 0) {
ERR_print_errors_fp(stderr);
fatalError("Error in SSL handshake");
}
/* Verify a client certificate was presented during the negotiation */
X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); } /* Free immediately */
if(NULL == cert) fatalError("Client did not present a cert during handshake.");
/* Verify the result of chain verification */
int res = SSL_get_verify_result(ssl);
if(!(X509_V_OK == res)) fatalError("Cert presented by client couldn't be verified.");
return sbio;
}
Client code:
BIO *makeTLSConnection(char *servIP, char *servPort) {
char *servLoc = calloc(strlen(servIP) + strlen(servPort) + 2, sizeof(char));
strcat(servLoc, servIP);
strcat(servLoc, ":");
strcat(servLoc, servPort);
BIO *sbio = NULL;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
SSL_library_init();
ctx = SSL_CTX_new(TLSv1_client_method());
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
if(!SSL_CTX_use_certificate_file(ctx,"clientcert.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_use_PrivateKey_file(ctx,"clientkey.pem",SSL_FILETYPE_PEM)
|| !SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stderr);
fatalError("Error setting up SSL_CTX.");
}
if(!SSL_CTX_load_verify_locations(ctx, "servercert.pem", NULL))
fatalError("Could not load trusted CA certificates.");
sbio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(sbio, &ssl);
if(!ssl) {
fatalError("Can't locate SSL pointer.");
}
BIO_set_conn_hostname(sbio, servLoc);
if(BIO_do_connect(sbio) <= 0) {
ERR_print_errors_fp(stderr);
fatalError("Error connecting to server.");
}
if(BIO_do_handshake(sbio) <= 0) {
ERR_print_errors_fp(stderr);
fatalError("Error establishing SSL connection.");
}
/* Verify a server certificate was presented during the negotiation */
X509* cert = SSL_get_peer_certificate(ssl);
if(cert) { X509_free(cert); } /* Free immediately */
if(NULL == cert) fatalError("Server did not present a cert during handshake.");
/* Verify the result of chain verification */
int res = SSL_get_verify_result(ssl);
if(!(X509_V_OK == res)) fatalError("Cert presented by server couldn't be verified.");
return sbio;
}

It looks like you are missing the call to SSL_CTX_use_certificate_chain_file and SSL_CTX_set_client_CA_list on the server. I believe SSL_CTX_set_client_CA_list triggers the machinery to perform client authentication (i.e., it elicits the ClientCertificate message from the RFC in section 7.4.6 during the exchange).
If SSL_CTX_set_client_CA_list causes the failure you are looking for, then I'm inclined to believe its a bug in the OpenSSL library. You should get a failure when specifying SSL_VERIFY_PEER and SSL_VERIFY_FAIL_IF_NO_PEER_CERT at the server because that's what the docs say.
Also see Is verification supposed to fail with SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT without SSL_CTX_set_client_CA_list on the OpenSSL Users mailing list. Its in response to this question.
Also see Testing SSL/TLS Client Authentication with OpenSSL and OpenSSL client not sending client certificate.

Related

Construct a openssl service with cipher ECDHE-RSA-AES128-GCM-SHA256

I am building a HTTPS service with Mongoose using using OPENSSL (openssl-1.0.2) on an embedded Linux. I tried it first using the following cipher list "AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384", and it works fine. However, when I try to use only "ECDHE-RSA-AES128-GCM-SHA256", the compilation still works, but when attempting actual HTTPS connection between server and client (Chrome on Windows 10), the connection always failed at handshake.
Here's the openssl supported cipher list:
# openssl ciphers | sed 's/:/\n/g'
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-SHA384
ECDHE-ECDSA-AES256-SHA384
ECDHE-RSA-AES256-SHA
ECDHE-ECDSA-AES256-SHA
...
ECDH-RSA-AES256-GCM-SHA384
ECDH-ECDSA-AES256-GCM-SHA384
ECDH-RSA-AES256-SHA384
ECDH-ECDSA-AES256-SHA384
ECDH-RSA-AES256-SHA
ECDH-ECDSA-AES256-SHA
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
ECDHE-ECDSA-AES128-SHA256
ECDHE-RSA-AES128-SHA
ECDHE-ECDSA-AES128-SHA
ECDHE-RSA-DES-CBC3-SHA
ECDHE-ECDSA-DES-CBC3-SHA
...
#
According to the packet capture between server and client, the client does support ECDHE-RSA-AES128-GCM-SHA256, so logically the handshake should work. The certificate is signed using RSA Key.
Here's my code for setting up SSL connection.
const char *pem = ctx->config[SSL_CERTIFICATE];
const char *chain = ctx->config[SSL_CHAIN_FILE];
if (!load_dll(ctx, SSL_LIB, ssl_sw) || !load_dll(ctx, CRYPTO_LIB, crypto_sw))
return 0;
// Initialize SSL crap
SSL_library_init();
SSL_load_error_strings();
**if ((CTX = SSL_CTX_new(TLSv1_2_server_method())) == NULL)
printf("SSL_CTX_new error: %s", ssl_error());**
/** Set cipher list */
//char cipher[128]="AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384";
char cipher[128]="ECDHE-RSA-AES128-GCM-SHA256";
if (SSL_CTX_set_cipher_list(CTX, cipher) <= 0) {
printf("Failed setting the cipher list.\n");
return 0;
}
if (CTX != NULL && SSL_CTX_use_certificate_file(CTX, pem, SSL_FILETYPE_PEM) == 0) {
printf("%s: cannot open %s: %s", __func__, pem, ssl_error());
return 0;
}else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(CTX, pem, SSL_FILETYPE_PEM) == 0) {
printf("%s: cannot open %s: %s", NULL, pem, ssl_error());
return 0;
}
if (CTX != NULL && chain != NULL && SSL_CTX_use_certificate_chain_file(CTX, chain) == 0) {
printf("%s: cannot open %s: %s", NULL, chain, ssl_error());
return 0;
}
All I did are,
Changing the method from SSLv23_server_method() to TLSv1_2_server_method(),
Changing the cipher list from "AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384" to "ECDHE-RSA-AES128-GCM-SHA256"
Any help would be helpful, thanks.

check trusted certificate with openssl

I developp a xmpp client using libmesode(fork libstrophe).
I want to secure the authentication with certification.
the server side has a certificate server.pem
the client side has its certificate client.pem
in lidmesode the functions used to check the certificate:
tls_t *tls_new(xmpp_ctx_t *ctx, sock_t sock, xmpp_certfail_handler certfail_handler, char *tls_cert_path)
{
_xmppctx = ctx;
_certfail_handler = certfail_handler;
_cert_handled = 0;
_last_cb_res = 0;
tls_t *tls = xmpp_alloc(ctx, sizeof(*tls));
if (tls) {
int ret;
memset(tls, 0, sizeof(*tls));
tls->ctx = ctx;
tls->sock = sock;
tls->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (tls->ssl_ctx == NULL)
goto err;
SSL_CTX_set_client_cert_cb(tls->ssl_ctx, NULL);
SSL_CTX_set_mode(tls->ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_verify(tls->ssl_ctx, SSL_VERIFY_PEER, verify_callback);
if (tls_cert_path) {
SSL_CTX_load_verify_locations(tls->ssl_ctx, NULL, tls_cert_path);
//SSL_CTX_use_certificate_file(tls->ssl_ctx, certif, SSL_FILETYPE_PEM);
}
tls->ssl = SSL_new(tls->ssl_ctx);
if (tls->ssl == NULL)
goto err_free_ctx;
ret = SSL_set_fd(tls->ssl, sock);
if (ret <= 0)
goto err_free_ssl;
}
return tls;
err_free_ssl:
SSL_free(tls->ssl);
err_free_ctx:
SSL_CTX_free(tls->ssl_ctx);
err:
xmpp_free(ctx, tls);
_tls_log_error(ctx);
return NULL;
}
static int
verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
const STACK_OF(X509) *sk = X509_STORE_CTX_get1_chain(x509_ctx);
int slen = sk_X509_num(sk);
unsigned i;
X509 *certsk;
xmpp_debug(_xmppctx, "TLS", "STACK");
for(i=0; i<slen; i++) {
certsk = sk_X509_value(sk, i);
_print_certificate(certsk);
}
xmpp_debug(_xmppctx, "TLS", "ENDSTACK");
if (preverify_ok) {
sk_X509_pop_free(sk, X509_free);
printf("=========> 1\n");
return 1;
} else if (_cert_handled) {
if (_last_cb_res == 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION);
}
sk_X509_pop_free(sk, X509_free);
return _last_cb_res;
} else {
int err = X509_STORE_CTX_get_error(x509_ctx);
const char *errstr = X509_verify_cert_error_string(err);
xmpp_debug(_xmppctx, "TLS", "ERROR: %s", errstr);
X509 *user_cert = sk_X509_value(sk, 0);
struct _tlscert_t *tlscert = _x509_to_tlscert(_xmppctx, user_cert);
int cb_res = 0;
if (_certfail_handler) {
cb_res = _certfail_handler(tlscert, errstr);
}
xmpp_conn_free_tlscert(_xmppctx, tlscert);
_cert_handled = 1;
_last_cb_res = cb_res;
if (cb_res == 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION);
}
sk_X509_pop_free(sk, X509_free);
return cb_res;
}
}
when the xmpp client connect to the ejabbed xmpp server with TLS, openssl check the server certificate that is self signed but continue to connect !!.
the second thing is: the openssl doesn't check the server certificate with the client certificate to check of the server certificate is trusted or no.
is openssl do these check ?
1) disconnect if self signed certificate
2) check if certificate is trusted.
the following capture show that the server send the certificate
and then the client send the key.
is this key should be taken from client certification !

Server with ECDHE key and cert not working

I use the below server.c source, i generated
sinful-host-cert.pem
sinful-host.key
as described here: Elliptic Curve CA Guide
When running the program get the following errors:
140722397161136:error:10071065:elliptic curve routines:func(113):reason(101):ec_lib.c:995:
140722397161136:error:0B080075:x509 certificate routines:func(128):reason(117):x509_cmp.c:346:
I compiled using:
gcc server.c -ldl -lcrypto -lssl -o Server
The error occurs at this line I think
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
server.c
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"
#define FAIL -1
int OpenListener(int port)
{ int sd;
struct sockaddr_in addr;
sd = socket(PF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
inet_aton("10.8.0.26", &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
//addr.sin_addr.s_addr = INADDR_ANY;
if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
{
perror("can't bind port");
abort();
}
if ( listen(sd, 10) != 0 )
{
perror("Can't configure listening port");
abort();
}
return sd;
}
SSL_CTX* InitServerCTX(void)
{ const SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
//method = SSLv23_server_method();
method = TLSv1_2_server_method(); /* create new server-method instance */
ctx = SSL_CTX_new(method); /* create new context from method */
if ( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
//New lines
if (SSL_CTX_set_cipher_list(ctx, "ECDHE-ECDSA-AES128-GCM-SHA256") != 1)
ERR_print_errors_fp(stderr);
if (SSL_CTX_load_verify_locations(ctx, CertFile, KeyFile) != 1)
ERR_print_errors_fp(stderr);
if (SSL_CTX_set_default_verify_paths(ctx) != 1)
ERR_print_errors_fp(stderr);
//End new lines
/* set the local certificate from CertFile */
if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
abort();
}
printf("FFFF\n");
/* set the private key from KeyFile (may be the same as CertFile) */
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
abort();
}
printf("GGGG\n");
/* verify private key */
if (!SSL_CTX_check_private_key(ctx))
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
//New lines - Force the client-side have a certificate
//SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
//SSL_CTX_set_verify_depth(ctx, 4);
//End new lines
}
void ShowCerts(SSL* ssl)
{ X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */
if ( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line);
X509_free(cert);
}
else
printf("No certificates.\n");
}
void Servlet(SSL* ssl) /* Serve the connection -- threadable */
{ char buf[1024];
char reply[1024];
int sd, bytes, err;
const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
printf("huhupre\n");
err = SSL_accept(ssl);
if ( err <= 0 ) { /* do SSL-protocol accept */
printf("%d\n",err);
ERR_print_errors_fp(stderr);
}
else
{
printf("XXXXXX\n");
//SSL_write(ssl, "huhu\n\r", 8);
ShowCerts(ssl); /* get any certificates */
bytes = SSL_read(ssl, buf, sizeof(buf)); /* get request */
if ( bytes > 0 )
{
buf[bytes] = 0;
printf("Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); /* construct reply */
SSL_write(ssl, reply, strlen(reply)); /* send reply */
}
else
ERR_print_errors_fp(stderr);
}
sd = SSL_get_fd(ssl); /* get socket connection */
SSL_free(ssl); /* release SSL state */
close(sd); /* close connection */
}
int main()
{ SSL_CTX *ctx;
int server;
char portnum[]="5000";
char CertFile[] = "sinful-host-cert.pem";
char KeyFile[] = "sinful-host.key";
SSL_library_init();
ctx = InitServerCTX(); /* initialize SSL */
LoadCertificates(ctx, CertFile, KeyFile); /* load certs */
server = OpenListener(atoi(portnum)); /* create server socket */
printf("%d while\n", server);
while (1)
{ struct sockaddr_in addr;
socklen_t len = sizeof(addr);
SSL *ssl;
int client = accept(server, (struct sockaddr*)&addr, &len); /* accept connection as usual */
printf("Connection: %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
ssl = SSL_new(ctx); /* get new SSL state with context */
if (ssl == NULL) {
ERR_print_errors_fp(stderr);
return 0;
}
SSL_set_fd(ssl, client); /* set connection socket to SSL state */
Servlet(ssl); /* service connection */
}
close(server); /* close server socket */
SSL_CTX_free(ctx); /* release context */
}
as described here: Elliptic Curve CA Guide...
This page has so many errors and omissions I would discard it. The first red flag is the white text and black background. That tells me someone less experienced is providing the page...
From the page:
openssl ecparam -list-curves
This should be -list_curves, not -list-curves.
From the page:
openssl ecparam -out sinful.key -name sect283k1 -genkey
This should be:
openssl ecparam -param_enc named_curve -out sinful.key -name sect283k1 -genkey
If you don't use a named curve, then you will have lots of problems later, like when a client attempts to connect to the server. Here, named curve is the OID for a curve like secp256k1, and not the domain parameters like p, a, b, G, etc.
The "lots of problems later" is documented at the OpenSSL wiki Elliptic Curve Cryptography, Named Curves. Here are some of the problems you will experience:
Client: 139925962778272:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s3_pkt.c:1256:SSL alert number 40
Client: 139925962778272:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:596
Server: 140339533272744:error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher:s3_srvr.c:1353
Also, for maximum interoperability, you should use secp256k1. A close second is secp521r1.
Also, use of/lack of -*form in the openssl ecparam and openssl req commands are discussed below.
SSL_CTX* InitServerCTX(void) { ... }
This code block has quite a few problems. The most notable is lack of the ECDH callback. Where are you setting the SSL_CTX_set_tmp_ecdh callback (OpenSSL 1.0.1 and below), or where is the call to SSL_CTX_set_ecdh_auto (OpenSSL 1.0.2 and above)?
Others include the default protocol, the default cipher list, weak and wounded ciphers, the inclusion of anonymous protocols, and compression. For a partial example of code to provide a server context, see 'No Shared Cipher' Error with EDH-RSA-DES-CBC3-SHA.
The error occurs at this line I think
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
I think that traces back to that defective page you referenced. This:
openssl req -x509 -new -key sinful.key -out sinful-ca.pem -outform PEM -days 3650
Should probably be (note the addition of -keyform)
openssl req -x509 -new -key sinful.key -keyform PEM -out sinful-ca.pem -outform PEM -days 3650
Or
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_ASN1) <= 0)
In general, always use the *form option for a command, whether its -keyform, -certform, -inform, -outform, etc. OpenSSL does not always get it right (even though its supposed to use PEM by default).
The error occurs at this line I think
if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
If the private key has a password, then you will need to provide a Password Callback or strip the password from the file.
Its OK to strip the password because there's no difference in storing a plaintext private key; or a encrypted private key with the passphrase in a configuration file next to the key. In both cases, the only effective security you have is the filesystem ACLs.
Related, this is known as the Unattended Key Storage problem. Guttman discusses it in his book Engineering Security. Its a problem without a solution.
Here's some more complete error information... It looks like you are using an old version of OpenSSL, and that does not provide the newer error codes.
When running the program get the following errors:
140722397161136:error:10071065:elliptic curve routines:func(113):reason(101):ec_lib.c:995
140722397161136:error:0B080075:x509 certificate routines:func(128):reason(117):x509_cmp.c:346
First, the 0x10071065 error:
$ /usr/local/ssl/macosx-x64/bin/openssl errstr 0x10071065
error:10071065:elliptic curve routines:EC_POINT_cmp:incompatible objects
The 0x10071065 usually means the client and the server are using incompatible EC fields. In this case, you should use either secp256k1 or secp521r1.
Second, the 0x0B080075 error:
$ /usr/local/ssl/macosx-x64/bin/openssl errstr 0x0B080075
error:0B080075:x509 certificate routines:X509_check_private_key:unknown key type
I'm guessing that there's a mismatch in the certificate and private key. But its only a guess. I would (1) clear the named curve issue, (2) clear the sect283k1 issue, and (3) clear the down level library issue (see below). After clearing those issues, then see if this issue remains.
It looks like you are using an old version of OpenSSL, and that does not provide the newer error codes...
Be sure you are running OpenSSL 1.0.0 or above. 0.9.8 had limited EC support, but it was not really cut-in in force until 1.0.0. Better, use OpenSSL 1.0.2.
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
Also see Library Initialization on the OpenSSL wiki.
if (SSL_CTX_set_cipher_list(ctx, "ECDHE-ECDSA-AES128-GCM-SHA256")
This will get you into trouble on some versions of OS X and iOS due to a bug in the SecureTransport library. Apple only fixed it on some versions of their operating systems.
If you plan on servicing Apple hardwarez, then you will need one additional non-ECDHE-ECDSA cipher. And you need to use the server side context option SSL_OP_SAFARI_ECDHE_ECDSA_BUG.
Related, Apple is pretty bold about not fixing their security bugs. You have the broken ECDHE-ECDSA cipher suites; and gems like CVE-2015-1130 (Hidden Backdoor with Root).
Here's what my ECDH callback looks like in OpenSSL 1.0.1 and below. OpenSSL 1.0.2 should use SSL_CTX_set_ecdh_auto. Its C++ code, but its easy enough to convert back to C code. Also see SL_CTX_set_tmp_ecdh_callback semantics in 1.0.1 on the OpenSSL mailing list.
The code below could be more robust. The callback should fetch the certificate with SSL_get_certificate (not SSL_get_peer_certificate), query the certificate for the EC field, and then provide a temporary key in the appropriate field, like secp256k1 or secp571k1. (It works because my certificates use secp256, and EcdhCallback uses secp256 as its default).
SSL_get_certificate is not documented. But it is used in <openssl src>/apps/s_cb.c. That's the "self documenting" code OpenSSL is famous for.
using SSL_ptr = std::shared_ptr<SSL>;
using SSL_CTX_ptr = std::shared_ptr<SSL_CTX>;
using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>;
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>;
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
...
SSL_CTX* CreateServerContext(const string & domain)
{
const SSL_METHOD* method = SSLv23_server_method();
ASSERT(method != NULL);
SSL_CTX_ptr t(SSL_CTX_new(method), ::SSL_CTX_free);
ASSERT(t.get() != NULL);
long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
flags |= SSL_OP_NO_COMPRESSION;
flags |= SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
flags |= SSL_OP_CIPHER_SERVER_PREFERENCE;
SSL_CTX_set_options(t.get(), flags);
string ciphers = "HIGH:!aNULL:!RC4:!MD5";
rc = SSL_CTX_set_cipher_list(t.get(), ciphers.c_str());
...
LogDebug("GetServerContext: setting ECDH callback");
SSL_CTX_set_tmp_ecdh_callback(t.get(), EcdhCallback);
...
return t.release();
}
EC_KEY* EcdhCallback(SSL *ssl, int is_export, int keylength)
{
UNUSED(ssl);
UNUSED(is_export);
UNUSED(keylength);
/* This callback is OK, but OpenSSL calls it in a broken fashion. */
/* With 1.0.1e and 1.0.1f, the value is 1024-bits. That is more */
/* appropriate for RSA.... We'll try and rewrite it here. */
if (keylength >= 1024)
{
keylength = 256;
LogRelevant("EcdhCallback: field size is wrong, using 256-bit group");
}
#if defined(ALLOW_ECDH_192_PARAMS)
if (keylength <= 192 + 4)
return ECDH192();
#endif
if (keylength <= 224 + 4)
return ECDH224();
else if (keylength <= 256 + 4)
return ECDH256();
else if (keylength <= 384 + 4)
return ECDH384();
else if (keylength <= 521 + 4)
return ECDH521();
return ECDH521();
}
#if defined(ALLOW_ECDH_192_PARAMS)
static EC_KEY* ECDH192()
{
static EC_KEY_ptr key(NULL, NULL);
static once_flag flag;
call_once(flag, []()
{
key = EC_KEY_ptr(InitEcdhkey(192), ::EC_KEY_free);
ASSERT(key.get());
if(!key.get())
LogError("ECDH192: InitEcdhkey failed");
});
return key.get();
}
#endif
static EC_KEY* ECDH224()
{
static EC_KEY_ptr key(NULL, NULL);
static once_flag flag;
call_once(flag, []()
{
key = EC_KEY_ptr(InitEcdhkey(224), ::EC_KEY_free);
ASSERT(key.get());
if(!key.get())
LogError("ECDH224: InitEcdhkey failed");
});
return key.get();
}
static EC_KEY* ECDH256()
{
static EC_KEY_ptr key(NULL, NULL);
static once_flag flag;
call_once(flag, []()
{
key = EC_KEY_ptr(InitEcdhkey(256), ::EC_KEY_free);
ASSERT(key.get());
if(!key.get())
LogError("ECDH256: InitEcdhkey failed");
});
return key.get();
}
static EC_KEY* ECDH384()
{
static EC_KEY_ptr key(NULL, NULL);
static once_flag flag;
call_once(flag, []()
{
key = EC_KEY_ptr(InitEcdhkey(384), ::EC_KEY_free);
ASSERT(key.get());
if(!key.get())
LogError("ECDH384: InitEcdhkey failed");
});
return key.get();
}
static EC_KEY* ECDH521()
{
static EC_KEY_ptr key(NULL, NULL);
static once_flag flag;
call_once(flag, []()
{
key = EC_KEY_ptr(InitEcdhkey(521), ::EC_KEY_free);
ASSERT(key.get());
if(!key.get())
LogError("ECDH521: InitEcdhkey failed");
});
return key.get();
}
static EC_KEY* InitEcdhkey(int bits)
{
if (bits <= 160 + 4)
bits = 160;
else if (bits <= 192 + 4)
bits = 192;
else if (bits <= 224 + 4)
bits = 224;
else if (bits <= 256 + 4)
bits = 256;
else if (bits <= 384 + 4)
bits = 384;
else if (bits <= 521 + 4)
bits = 521;
else
bits = 521;
EC_KEY* key = EC_KEY_new_by_curve_name(CurveToNidByBits(bits));
unsigned long err = ERR_get_error();
ASSERT(key != NULL);
if (key == NULL)
{
ostringstream oss;
oss << "InitEcdhkey: EC_KEY_new_by_curve_name failed for ";
oss << bits << "-bit key, error " << err << ", 0x" << err;
LogError(oss);
}
return key;
}

SMTP: SSL_connect fails but no errors

I'm having trouble with the openssl library. I'm calling the following function after sending EHLO and STARTTLS:
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
void CreateTLSSession(int sockfd)
{
printf("///////////////creating TLS Session/////////////////////\n");
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
{
printf("failed to initialize context\n");
return;
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
ssl = SSL_new(ctx);
if (!SSL_set_fd(ssl, sockfd))
{
printf("failed to bind to socket fd\n");
return;
}
if (SSL_connect(ssl) < 1)
{
ERR_print_errors_fp(stdout);
fflush(stdout);
printf("SSL_connect failed\n");
return;
}
}
However, SSL_connect fails but does not print any errors!
Here's the output of my code: (including the replies of the server)
220 mx.google.com ESMTP x3sm39000551eep.17 - gsmtp
//////////////////////////EHLO//////////////////////////
250-mx.google.com at your service, [80.149.109.201]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250 CHUNKING
//////////////////////////STARTTLS//////////////////////////
220 2.0.0 Ready to start TLS
///////////////creating TLS Session/////////////////////
SSL_connect failed
so ERR_print_errors_fp(stdout) is not doing anything!
Any idea why this is happening?
BTW, I'm trying to connect to smtp.gmail.com:587
I don't see a load of private key file or certificate file. use SSL_CTX_use_certificate_file and SSL_CTX_use_PrivateKey_file.
I managed to finally solve the problem. the underlying socket was non-blocking, so SSL_connect does not connect instantly. Using select solved the issue

How to set connection timeout and operation timeout in OpenSSL

libcurl has timeout options like these:
CURLOPT_CONNECTTIMEOUT - maximum time in seconds that you allow the connection to the server to take.
CURLOPT_TIMEOUT - maximum time in seconds that you allow the libcurl transfer operation to take.
I'd like to implement a similar timeout mechanism in OpenSSL.
What changes would be required in the code below so that a timeout value is applied to BIO_do_connect(), BIO_write() and BIO_read()?
I'm connecting to a server and sending/receiving data to/from the server using BIO_write()/BIO_read() that OpenSSL provides. My code is based on the following sample code available from here.
int main()
{
BIO * bio;
SSL * ssl;
SSL_CTX * ctx;
int p;
char * request = "GET / HTTP/1.1\x0D\x0AHost: www.verisign.com\x0D\x0A\x43onnection: Close\x0D\x0A\x0D\x0A";
char r[1024];
/* Set up the library */
ERR_load_BIO_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
/* Set up the SSL context */
ctx = SSL_CTX_new(SSLv23_client_method());
/* Load the trust store */
if(! SSL_CTX_load_verify_locations(ctx, "TrustStore.pem", NULL))
{
fprintf(stderr, "Error loading trust store\n");
ERR_print_errors_fp(stderr);
SSL_CTX_free(ctx);
return 0;
}
/* Setup the connection */
bio = BIO_new_ssl_connect(ctx);
/* Set the SSL_MODE_AUTO_RETRY flag */
BIO_get_ssl(bio, & ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
/* Create and setup the connection */
BIO_set_conn_hostname(bio, "www.verisign.com:https");
if(BIO_do_connect(bio) <= 0)
{
fprintf(stderr, "Error attempting to connect\n");
ERR_print_errors_fp(stderr);
BIO_free_all(bio);
SSL_CTX_free(ctx);
return 0;
}
/* Check the certificate */
if(SSL_get_verify_result(ssl) != X509_V_OK)
{
fprintf(stderr, "Certificate verification error: %i\n", SSL_get_verify_result(ssl));
BIO_free_all(bio);
SSL_CTX_free(ctx);
return 0;
}
/* Send the request */
BIO_write(bio, request, strlen(request));
/* Read in the response */
for(;;)
{
p = BIO_read(bio, r, 1023);
if(p <= 0) break;
r[p] = 0;
printf("%s", r);
}
/* Close the connection and free the context */
BIO_free_all(bio);
SSL_CTX_free(ctx);
return 0;
}
I'm cross-compiling for ARM on Ubuntu (Eclipse with CodeSourcery Lite).
I ended up doing something like the following (pseudocode):
int nRet;
int fdSocket;
fd_set connectionfds;
struct timeval timeout;
BIO_set_nbio(pBio, 1);
nRet = BIO_do_connect(pBio);
if ((nRet <= 0) && !BIO_should_retry(pBio))
// failed to establish connection.
if (BIO_get_fd(pBio, &fdSocket) < 0)
// failed to get fd.
if (nRet <= 0)
{
FD_ZERO(&connectionfds);
FD_SET(fdSocket, &connectionfds);
timeout.tv_usec = 0;
timeout.tv_sec = 10;
nRet = select(fdSocket + 1, NULL, &connectionfds, NULL, &timeout);
if (nRet == 0)
// timeout has occurred.
}
You can use the same approach for BIO_read() too.
You might find this link useful.
For connecting, #jpen gave the best answer there. You have to mark the BIO as non-blocking and use select for determining whether it connected and/or timed out.
Reads are a little different. Because OpenSSL may buffer decrypted data (depending on the TLS cipher suite used), select may timeout when you are trying to read - even if data actually is available. The proper way to handle read timeouts is to first check SSL_pending or BIO_pending. If the pending function returns zero, then use select to set a timeout. If the pending function returns greater than zero, then just call SSL_read or BIO_read or any other read function.
Take a look at SSL_CTX_set_timeout () function, which does similar to libcurl's CURLOPT_TIMEOUT variable:
From http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html :
SSL_CTX_set_timeout() sets the timeout for newly created sessions for ctx to t. The timeout value t must be given in seconds.
In your case you could add the following line after you create ctx object:
SSL_CTX_set_timeout (ctx, 60);
Hope it helps !

Resources