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

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.

Related

Getting SSL_connect segmentation fault in Linux application

I am preparing a sample C application to stream file from one remote(FTP) location to another remote location(HTTP). In the application I want to download file using curl API & uploading the downloaded content using ssl API. I am getting segmentation-fault in SSL_connect(), Here is the init code,
SSL* ssl = NULL;//Global variable
SSL_CTX* ssl_ctx = NULL;//Global variable
/* initialize OpenSSL first */
SSL_library_init();
SSL_load_error_strings();
do
{
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (!ssl_ctx)
{
fprintf(stderr, "Failed to SSL_CTX_new\n");
break;
}
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
if (!ssl)
{
fprintf(stderr, "Failed to SSL_new\n");
break;
}
if (SSL_set_fd(ssl, sock) != 1)
{
fprintf(stderr, "Failed to SSL_set_fd\n");
break;
}
int err = SSL_connect(ssl);
if (err != 1)
{
fprintf(stderr, "Failed to SSL_connect\n");
break;
}
return_code = 0;
} while(0);
Here 'sock' is open socket connected with remote opened before this call.
I verified pointers passing & type-casting, Looks good to me.
Can anyone suggest any break-through?
Thanks in advance.
Try running gdb and set up a breakpoint at the line int err = SSL_connect(ssl);, check if ss1 is correctly initialized. Here is a simple tutorial on how to use gdb: https://cseweb.ucsd.edu/classes/fa09/cse141/tutorial_gcc_gdb.html

Verify public key matches signature of private key

I'm new to openssl API.
my goal is to verify that my public key is "related" to my hidden private key.
solution using openssl cli:
I have a certificate , the private key is hidden (in HSM)
I have a buffer:
echo "hello world!!!!" > sign.txt
using following commands I create sha256 signature of my buffer similar to what my HSM will do :
openssl dgst -sha256 -sign myrootca.key.insecure -out sign.sha256 sign.txt
extract public key:
openssl x509 -pubkey -noout -in myrootca.crt > myrootca.publicKey.pem
verify public key :
openssl dgst -sha256 -verify myrootca.publicKey.pem -signature sign.sha256 sign.txt
I think I know how to represent my public key and signature file correctly
sigkey = load_pubkey(bio_err, keyfile, keyform, 0, NULL,
e, "key file");
sigbio = BIO_new_file(sigfile, "rb");
but can't find a proper API to continue from here
Issue I've encountered was figuring out what is the signature length before hand to allocate place for the signature that must have prior knowledge on RSA key size and type of hash
sha256 + RSA1024 ~ 128byte
sha256 + RSA2048 ~ 256byte
using openssl API based of this guide I've managed to verify signature using following code :
Verifyx509VsPrivKeySig(X509* x509Cert,char* signature,size_t sigLen,char* message,size_t messageLen)
{
int rc;
EVP_PKEY* pPubkey = NULL;
EVP_MD_CTX* ctx = NULL;
rc = getX509Publickey(x509Cert, &pPubkey);
if (rc != SSL_OK)
{
goto err_verify;
}
ctx = EVP_MD_CTX_create();
if(ctx == NULL) {
PRINT_LOG_ERR("EVP_MD_CTX_create failed, error 0x%lx\n", ERR_get_error());
/* failed */
goto err_verify;
}
rc = EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, pPubkey);
if(rc != 1) {
PRINT_LOG_ERR("EVP_DigestVerifyInit failed, error 0x%lx\n", ERR_get_error());
goto err_EVP_XTX_destroy; /* failed */
}
rc = EVP_DigestVerifyUpdate(ctx, message, messageLen);
if(rc != 1) {
PRINT_LOG_ERR("EVP_DigestVerifyUpdate failed, error 0x%lx\n", ERR_get_error());
goto err_EVP_XTX_destroy; /* failed */
}
/* Clear any errors for the call below */
ERR_clear_error();
rc = EVP_DigestVerifyFinal(ctx,(const unsigned char*) signature, sigLen);
if(rc != 1) {
PRINT_LOG_ERR("EVP_DigestVerifyFinal failed, error 0x%lx\n", ERR_get_error());
goto err_EVP_XTX_destroy; /* failed */
}
/*if we got to here , verify sig finished with good result!!*/
if(ctx) {
EVP_MD_CTX_destroy(ctx);
ctx = NULL;
}
return true;
//////////////////// ERROR HANDLING ///////////////////////////
err_EVP_XTX_destroy:
EVP_MD_CTX_destroy(ctx);
err_verify:
return false;
}

Mutual Authentication and restricting user certificates to specific set at server

I'm looking for a way to restrict client certificates to specific set of self-signed certificates on the server side using the OpenSSL API.
There is a set of trusted self-signed certificates, say ./dir/*.pem. I want to reject connections, if they don't supply one of those certificates.
I can achieve almost desired behaviour by comparing server and client certificate fingerprints in the SSL context verification callback:
SSL_CTX *ctx;
...
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);
static inline int get_fingerprint(X509* cert, unsigned char *md, unsigned int *n)
{
return X509_digest(cert, EVP_sha1(), md, n);
}
static inline int compare_certificates(X509 *c1, X509 *c2)
{
unsigned char md1[EVP_MAX_MD_SIZE], md2[EVP_MAX_MD_SIZE];
unsigned int n1, n2;
if (!(get_fingerprint(c1, md1, &n1) && get_fingerprint(c2, md2, &n2))) {
return -1;
}
return memcmp(md1, md2, n1);
}
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
int err = X509_STORE_CTX_get_error(ctx);
/* Allow self-signed certificates */
if (!preverify_ok && err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
preverify_ok = 1;
}
if (0 != compare_certificates(ctx->current_cert, SSL_CTX_get0_certificate(ssl->ctx))) {
/* Peer certificate doesn't match the server certificate */
preverify_ok = 0;
}
/* More checks ... */
return preverify_ok;
}
So if the server and client certificate fingerprints match, verification passes. Otherwise, connection is closed by the server.
I might compute fingerprints of the trusted certificates somewhere in initialization phase, then check them in a loop within the verify_callback. However, I don't like this idea. There should be easier way to do this.
I thought SSL_CTX_load_verify_locations() is just what I was looking for(but it looks like it's not; i'll explain why):
SSL_CTX_load_verify_locations() specifies the locations for ctx, at which CA certificates for verification purposes are located.
...
If CAfile is not NULL, it points to a file of CA certificates in PEM format. The file can contain several CA certificates...
The certificates in CApath are only looked up when required, e.g. when building the certificate chain or when actually performing the verification of a peer certificate.
(man 3 SSL_CTX_load_verify_locations)
Well, I guess SSL_VERIFY_FAIL_IF_NO_PEER_CERT implies verifying the peer certificate. Then it looks like all I need to do is to make a bundle of trusted certificates and pass it to SSL_CTX_load_verify_locations():
bundle_file=CAbundle.pem
cd ./dir
rm -f $bundle_file
for i in *.pem; do
openssl x509 -in $i -text >> $bundle_file
done
c_rehash .
SSL_CTX *ctx;
const char *cafile = "dir/CAbundle.pem";
const char *capath = NULL;
...
if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) {
/* Unable to set verify locations ... */
}
cert_names = SSL_load_client_CA_file(cafile);
if (cert_names != NULL) {
SSL_CTX_set_client_CA_list(ctx, cert_names);
} else {
/* Handle error ... */
}
All looks good. But the server still accepts connections with different peer certificates.
I've reproduced this behaviour using standard OpenSSL utilities here: https://gist.github.com/rosmanov/d960a5d58a96bdb730303c5b8e86f951
So my question is: how do I configure the server to accept only peers providing only specific certificates?
Update
I've found that the "whitelist" of certificates (CA bundle) actually works,
when I remove the following from the verify_callback:
if (!preverify_ok && err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
preverify_ok = 1;
}
So without this block everything just works. The server responds to a client connected with one of certificates listed in CAbundle.pem. If a client connects with different certificate, the server closes connection.
However, there is a strange thing. In both cases openssl s_client outputs:
Verify return code: 18 (self signed certificate)
Then maybe
if (!preverify_ok
&& err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
&& allow_self_signed
&& !cafile
&& !capath) {
preverify_ok = 1;
}
?
Update 2
Now I understand why openssl s_client outputs Verify return code: 18 (self signed...). It doesn't trust server's certificate unless -CAfile or -CApath contains the server certificate. And the server certificate is self-signed.
An explanation (for commandline) and a halfanswer (for library):
I (this time fully) redid your gist and was reminded of an inconsistency here. Commandline openssl xxx utilities are mostly designed as test/debugging tools, and in particular:
s_client normally (except anonymous, SRP, etc) receives a cert chain from the server, but uses a callback that only logs what it got and ignores/overrides any error; this is the block
depth=0 C = AU, ST = StateA, L = CityA, O = CompanyA, CN = localhost, emailAddress = a#gmail.com
verify error:num=18:self signed certificate
verify return:1
depth=0 C = AU, ST = StateA, L = CityA, O = CompanyA, CN = localhost, emailAddress = a#gmail.com
verify return:1
just after CONNECTED(fd) in your s_client output, but as you see in spite of the error it continues with the handshake resulting in a usable connection.
s_server is a more complicated. It does not request a cert from client by default, only if you specify -verify or -Verify (which set SSL_VERIFY_PEER which is not the default for server), and if it does request a cert client has the option whether to send one (with associated proof in CertVerify). If client does send chain, s_server uses the same callback as s_client which overrides any error and continues with the connection; this in your s_server output with the same verify error:num-18... which actually means 'root (including selfsigned which is its own root) in received chain but not in local truststore'. If client does not send chain, -verify continues, but -Verify (which also sets SSL_VERIFY_FAIL_IF_NO_PEER_CERT) aborts the handshake with alert 40 and returns an error, so the s_server output is very different:
verify depth is 0, must return a certificate
Using default temp DH parameters
Using default temp ECDH parameters
ACCEPT
ERROR
140679792887624:error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate:s3_srvr.c:3271:
shutting down SSL
CONNECTION CLOSED
ACCEPT
But a program using the library should work. I hacked up this simple test from parts of some other programs (hence the odd indentation):
/* SO36821430 2016-04-25 */
#include <stdio.h>
#if defined(_WIN32)&&!defined(WIN32)
#define WIN32 /*anything*/
#endif
#ifdef WIN32
#include <winsock2.h>
typedef int socklen_t;
#define SOCKERR WSAGetLastError()
#include "openssl/applink.c"
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef INADDR_NONE
#define INADDR_NONE (ipaddr_t)-1
#endif
typedef int SOCKET;
enum { INVALID_SOCKET = -1, SOCKET_ERROR = -1 };
#define SOCKERR errno
#define closesocket close
#endif
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/rand.h"
void sockerr (const char *what){
fprintf (stderr, "%s %d %s\n", what, SOCKERR, strerror(SOCKERR));
}
void sslerrn (const char *what){
fprintf (stderr, "* %s failed:\n", what);
ERR_print_errors_fp (stderr);
}
void sslerr (const char *what, int rv){
fprintf (stderr, "* %s return %d:\n", what, rv);
ERR_print_errors_fp (stderr);
}
void sslerrx (SSL * ssl, const char *what, int rv){
int rc = SSL_get_error (ssl, rv);
if( rv == -1 && rc == SSL_ERROR_SYSCALL ) sockerr (what);
else fprintf (stderr, "* %s return %d,%d\n", what, rv, rc);
ERR_print_errors_fp (stderr);
}
void subj_oneline (X509 * cert, FILE *fp){
X509_NAME * subj = X509_get_subject_name (cert);
BIO *bmem = BIO_new (BIO_s_mem()); char *ptr; int n;
X509_NAME_print_ex (bmem, subj, 0, XN_FLAG_ONELINE);
n = (int) BIO_get_mem_data (bmem, &ptr);
if( n <= 0 ) ptr = "?", n = 1;
fwrite (ptr,1,n,fp);
}
const char * inaddr;
int inport;
char buf [9999];
int main (int argc, char* argv[] )
{
int rv;
struct sockaddr_in sin; socklen_t sinlen;
SOCKET s1, s2; SSL_CTX *ctx = NULL;
time_t now; struct tm * tm;
#ifdef WIN32
struct WSAData wsa;
rv = WSAStartup (MAKEWORD(1,1), &wsa);
if(rv){ printf ("WSAStartup %d\n", rv); exit(1); }
#endif
if( argc < 2 || argc > 6 )
printf ("usage: %s port key cert CAcerts\n", argv[0]), exit(1);
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons (atoi(argv[1]));
sin.sin_family = AF_INET;
/**/
SSL_library_init();
SSL_load_error_strings();
ctx = SSL_CTX_new (SSLv23_server_method());
if( !ctx ){ sslerrn("CTX_new"); exit(1); }
SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
rv = SSL_CTX_use_PrivateKey_file (ctx, argv[2], SSL_FILETYPE_PEM);
if( rv != 1 ){ sslerr ("use_PrivateKey_file",rv); exit(1); }
rv = SSL_CTX_use_certificate_file (ctx, argv[3], SSL_FILETYPE_PEM);
if( rv != 1 ){ sslerr ("use_certificate_file",rv); exit(1); }
SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
if( !SSL_CTX_load_verify_locations (ctx, argv[4], NULL) ){
sslerrn ("load_verify_locations"); exit(1); }
SSL_CTX_set_client_CA_list (ctx, SSL_load_client_CA_file (argv[4]));
/**/
if( (s1 = socket (AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET ){
sockerr ("socket()"); exit(1); }
if( bind (s1, (struct sockaddr*)&sin, sizeof sin) < 0 ){
sockerr ("bind()"); exit(1); }
if( listen (s1, 5) < 0 ){
sockerr ("listen()"); exit(1); }
do{
sinlen = sizeof sin;
if( (s2 = accept (s1, (struct sockaddr*)&sin, &sinlen)) == INVALID_SOCKET ){
sockerr ("accept()"); exit(1); }
now = time(NULL); tm = localtime(&now);
printf ("+ %s %u #%02d.%02d.%02d\n", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port), tm->tm_hour, tm->tm_min, tm->tm_sec);
/**/
SSL * ssl = SSL_new (ctx);
if( !ssl ){ sslerrn("SSL_new"); goto next; }
SSL_set_fd (ssl, s2);
if( (rv = SSL_accept(ssl)) < 0 ){
sslerrx (ssl, "SSL_accept", rv); goto next; }
{ X509 * cert = SSL_get_peer_certificate (ssl);
/*EVP_PKEY * key = cert? X509_get_pubkey (cert): NULL;*/
fprintf (stdout, "=%ld", SSL_get_verify_result (ssl));
if( cert ) putchar (':'), subj_oneline (cert, stdout);
putchar ('\n');
}
while( (rv = SSL_read (ssl, buf, sizeof buf)) > 0 )
printf ("%d: %.*s\n", rv, rv, buf);
sslerrx (ssl, "SSL_read", rv);
next:
if( ssl ) SSL_free (ssl);
/**/
now = time(NULL); tm = localtime(&now);
printf ("- %s %u #%02d.%02d.%02d\n", inet_ntoa (sin.sin_addr),
ntohs (sin.sin_port), tm->tm_hour, tm->tm_min, tm->tm_sec);
closesocket (s2);
} while (1);
return 0;
}
When run with $port cert1.key cert1.pem CAbundle.pem and connected from client using cert2.key & cert2.pem this aborts the handshake with alert 48 unknown_ca and returns an error as desired:
+ 127.0.0.1 46765 #22.07.36
* SSL_accept return -1,1
140240689366696:error:14089086:SSL routines:ssl3_get_client_certificate:certificate verify failed:s3_srvr.c:3270:
- 127.0.0.1 46765 #22.07.36
HTH.
If you want a whitelist of specific client certificates, you can prepare an indexed list in memory when you initialize.
For example, you can use PEM_X509_INFO_read to read a concatenated file of all client certificates in PEM format. This will give you a STACK_OF(X509_INFO)* of certificates. The number of certificates can be found with sk_X509_INFO_num, and you can see each certificate at sk_X509_INFO_value(..)->x509.
Then, for example, you can simply build an in-memory index and qsort by compare_x509.
Now, when your verify callback is called, just do a bsearch on your index by compare_x509, and either the certificate is on your whitelist, or it isn't.
You can accept the match on the result of compare_x5099, or of course you could double-check by verifying the full certificate once the search finds a match in the index.

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

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.

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

Resources