Verifying my self-signed certificate with openSSL - authentication

I own the server and I own the customer's executable. I'd like to establish a secure TLS connection between them.
I can embed whatever I want into the client executable but I'm not sure how to validate a self-assigned certificate that my client received from a connection to the server, i.e. from a SSL_get_peer_certificate call.
I read around that certificates are just public keys with metadata parts signed with the private key. Can I somehow verify that the certificate the server sent me has indeed all the metadata correctly signed by embedding the public key into my client application? Is this possible (and if it is, how?)

I'm not sure how to validate a self-assigned certificate that my client received from a connection to the server ...
Depending on the OpenSSL library you are using, you have to perform two or three steps for verification. The two versions bisect at OpenSSL 1.1.0. OpenSSL 1.1.0 and above performs hostname validation so it only takes two steps. OpenSSL 1.0.2 and below does not perform hostname validation so it requires three steps.
The steps detailed below are from SSL/TLS Client on the OpenSSL wiki.
Server Certificate
Both OpenSSL 1.0.2 and 1.1.0 require you to check for the presence of a certificate. If you use ADH (Anonymous Diffie-Hellman), TLS-PSK (Preshared Key), TLS_SRP (Secure Remote Password), then there may not be a server certificate to verify.
You get the server's certificate with SSL_get_peer_certificate. If it returns non-NULL, then a certificate is present. Lack of a certificate may or may not be a reason to fail.
Certificate Chain
Both OpenSSL 1.0.2 and 1.1.0 require you to check the result of chain validation. Chain validation is part of path building, and its detailed in RFC 4158, Certification Path Building.
You get the result of path validation with SSL_get_verify_result.
Certificate Names
OpenSSL 1.0.2 an below requires you to verify the hostname matches a name listed in the certificate. Its a big topic, but the short of it is: any hostname or dns name needs to be present in the certifcate's Subject Alternative Name (SAN), and not the Common Name (CN). Also see How do you sign Certificate Signing Request with your Certification Authority and How to create a self-signed certificate with openssl? It provides a lot of background information on X.509 server certificates, how to present names, and where the various rules come from.
Effectively, you fetch the SANs with X509_get_ext_d2i(cert, NID_subject_alt_name, ...). Then you loop over the list and extract each name with sk_GENERAL_NAME_num. Then, you extract a GENERAL_NAME entry and ASN1_STRING_to_UTF8, and see if it matches the name you tried to connect to.
Below are the routines for printing the Subject Alternative Name (SAN) and the Common Name (CN). It came from the example on the OpenSSL wiki page.
void print_san_name(const char* label, X509* const cert)
{
int success = 0;
GENERAL_NAMES* names = NULL;
unsigned char* utf8 = NULL;
do
{
if(!cert) break; /* failed */
names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0 );
if(!names) break;
int i = 0, count = sk_GENERAL_NAME_num(names);
if(!count) break; /* failed */
for( i = 0; i < count; ++i )
{
GENERAL_NAME* entry = sk_GENERAL_NAME_value(names, i);
if(!entry) continue;
if(GEN_DNS == entry->type)
{
int len1 = 0, len2 = -1;
len1 = ASN1_STRING_to_UTF8(&utf8, entry->d.dNSName);
if(utf8) {
len2 = (int)strlen((const char*)utf8);
}
if(len1 != len2) {
fprintf(stderr, " Strlen and ASN1_STRING size do not match (embedded null?): %d vs %d\n", len2, len1);
}
/* If there's a problem with string lengths, then */
/* we skip the candidate and move on to the next. */
/* Another policy would be to fails since it probably */
/* indicates the client is under attack. */
if(utf8 && len1 && len2 && (len1 == len2)) {
fprintf(stdout, " %s: %s\n", label, utf8);
success = 1;
}
if(utf8) {
OPENSSL_free(utf8), utf8 = NULL;
}
}
else
{
fprintf(stderr, " Unknown GENERAL_NAME type: %d\n", entry->type);
}
}
} while (0);
if(names)
GENERAL_NAMES_free(names);
if(utf8)
OPENSSL_free(utf8);
if(!success)
fprintf(stdout, " %s: <not available>\n", label);
}
void print_cn_name(const char* label, X509_NAME* const name)
{
int idx = -1, success = 0;
unsigned char *utf8 = NULL;
do
{
if(!name) break; /* failed */
idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
if(!(idx > -1)) break; /* failed */
X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, idx);
if(!entry) break; /* failed */
ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
if(!data) break; /* failed */
int length = ASN1_STRING_to_UTF8(&utf8, data);
if(!utf8 || !(length > 0)) break; /* failed */
fprintf(stdout, " %s: %s\n", label, utf8);
success = 1;
} while (0);
if(utf8)
OPENSSL_free(utf8);
if(!success)
fprintf(stdout, " %s: <not available>\n", label);
}
Verifying my self-signed certificate with openSSL
Because its your self-signed certificate, you can do even better than above. You have a priori knowledge of the host's public key. You can pin the public key, and just use the certificate to deliever the public key or as a presentation detail.
To pin the public key, see Public Key Pinning over at OWASP.
You should also avoid the IETF's RFC 7469, Public Key Pinning Extension for HTTP with Overrides. The IETF's rendition allows the attacker to break a known good pinset so the attacker can MitM the connection. They also suppress reporting the problem, so the user agent becomes complicit in the coverup.

Related

TLS certificate verification failure

I need to have a code which performs 2-way authentication (client and server authenticates each other). My server is a TCP server. I intend to have TLS security added.
https://github.com/ospaarmann/exdgraph/wiki/TLS-client-authentication
I generated client and server, CA certificates and key files using the link above.
Server side code:
{
SSL_CTX_set_options(
ret,
SSL_OP_NO_SSLv2 |
SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION
);
SSL_CTX_set_verify(
ret,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
NULL
);
if (SSL_CTX_load_verify_locations(ret, NULL, "/home/ml5/tls_bio/MyRootCA.pem") == 0) {
fprintf(stderr, "Failed to load root certificates\n");
SSL_CTX_free(ret);
return NULL;
}
/*
* We won't set any verification settings this time. Instead
* we need to give OpenSSL our certificate and private key.
*/
if (SSL_CTX_use_certificate_chain_file(ret, "MyServer.pem") != 1) {
ssl_perror("SSL_CTX_use_certificate_file");
SSL_CTX_free(ret);
return NULL;
}
if (SSL_CTX_use_PrivateKey_file(ret, "MyServer.key", SSL_FILETYPE_PEM) != 1) {
ssl_perror("SSL_CTX_use_PrivateKey_file");
SSL_CTX_free(ret);
return NULL;
}
printf("Loaded root certificates\n");
/*
* Check that the certificate (public key) and private key match.
*/
if (SSL_CTX_check_private_key(ret) != 1) {
fprintf(stderr, "certificate and private key do not match!\n");
SSL_CTX_free(ret);
return NULL;
}
}
client side code:
=======================================================================
SSL_CTX *ret;
/* create a new SSL context */
ret = SSL_CTX_new(SSLv23_client_method( ));
if (ret == NULL) {
fprintf(stderr, "SSL_CTX_new failed!\n");
return NULL;
}
/*
* set our desired options
*
* We don't want to talk to old SSLv2 or SSLv3 servers because
* these protocols have security issues that could lead to the
* connection being compromised.
*
* Return value is the new set of options after adding these
* (we don't care).
*/
SSL_CTX_set_options(
ret,
SSL_OP_NO_SSLv2 |
SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION
);
/*
* set up certificate verification
*
* We want the verification to fail if the peer doesn't
* offer any certificate. Otherwise it's easy to impersonate
* a legitimate server just by offering no certificate.
*
* No error checking, not because I'm being sloppy, but because
* these functions don't return error information.
*/
SSL_CTX_set_verify(
ret,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
NULL
);
SSL_CTX_set_verify_depth(ret, 4);
/*
* Point our context at the root certificates.
* This may vary depending on your system.
*/
if (SSL_CTX_load_verify_locations(ret, NULL, "/home/ml5/tls_bio_l1/MyRootCA.pem") == 0) {
fprintf(stderr, "Failed to load root certificates\n");
SSL_CTX_free(ret);
return NULL;
}
/*
* We won't set any verification settings this time. Instead
* we need to give OpenSSL our certificate and private key.
*/
if (SSL_CTX_use_certificate_chain_file(ret, "MyClient.pem") != 1) {
SSL_CTX_free(ret);
return NULL;
}
if (SSL_CTX_use_PrivateKey_file(ret, "MyClient.key", SSL_FILETYPE_PEM) != 1) {
SSL_CTX_free(ret);
return NULL;
}
printf("Loaded root certificates\n");
/*
* Check that the certificate (public key) and private key match.
*/
if (SSL_CTX_check_private_key(ret) != 1) {
fprintf(stderr, "certificate and private key do not match!\n");
SSL_CTX_free(ret);
return NULL;
}
I am unsure what is wrong because of which when I start the server and the client,
I get the error as shown below on the client side:
BIO_do_connect failed: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
If I have my own verify callback on client and server ofcourse the 2-way authentication succeeds
SSL_CTX_set_cert_verify_callback(ctx, always_true_callback, NULL);
But I think thats not how it is to be done. Any help in this regard to solve the error shown above, will be greatly appreciated.
Just to those who come-in here. The issue was relating to how I generated CA certificate, client/server certificates. Once I corrected those, it started working.

How to use http websocket on Mongoose embedded web server with SSL?

I'm trying to use Http WebSocket on Mongoose embedded web server with SSL.
And I tried this mongoose example called "simplest_web_server_ssl".
But when I executed the program, it printed out this message below.
"Failed to create listener: Invalid SSL cert"
I think it's because the program doesn't know where the "server.pem" file is.
I put these "server.pem" and "server.key" files from the example folder into a "release" folder where the .exe file is created and runs.
Actually I'm quite new to Mongoose and SSL.
Please anybody could help me?
Thanks, regards.
/*
* Copyright (c) 2016 Cesanta Software Limited
* All rights reserved
*/
/*
* This example starts an SSL web server on https://localhost:8443/
*
* Please note that the certificate used is a self-signed one and will not be
* recognised as valid. You should expect an SSL error and will need to
* explicitly allow the browser to proceed.
*/
#include "mongoose.h"
static const char *s_http_port = "8443";
static const char *s_ssl_cert = "server.pem";
static const char *s_ssl_key = "server.key";
static struct mg_serve_http_opts s_http_server_opts;
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
if (ev == MG_EV_HTTP_REQUEST) {
mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_connection *nc;
struct mg_bind_opts bind_opts;
const char *err;
mg_mgr_init(&mgr, NULL);
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = s_ssl_cert;
bind_opts.ssl_key = s_ssl_key;
bind_opts.error_string = &err;
printf("Starting SSL server on port %s, cert from %s, key from %s\n",
s_http_port, bind_opts.ssl_cert, bind_opts.ssl_key);
nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts);
if (nc == NULL) {
printf("Failed to create listener: %s\n", err);
return 1;
}
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = "."; // Serve current directory
s_http_server_opts.enable_directory_listing = "yes";
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
I've had the same issue. You can step into mongoose using gdb or similar tool to find the actual reason for that error message. If you think mongoose isn't finding your files, try using absolute paths. If it finds the file, you might need to regenerate (and/or register) the cert files on your computer.

How to read client certificate in polarssl?

How to read client certificate from server side using mbedtls(polarssl)?
I had a server that was coded using mbedtls(polarssl). I want to read the client certificate and fetch some information from that certificate. Can anyone know what function will be used to read client certificate?
I think you could use mbedtls_x509_crt_info which returns an informational string about the certificate.
You can get the peer certificate from the ssl session when the client connects and then print the info out.
mbedtls_ssl_context ssl;
...
mbedtls_x509_crt *crt = ssl.session->peer_cert;
unsigned char buf[1024];
int ret = mbedtls_x509_crt_info((char *) buf, sizeof( buf ) - 1, "", crt);
if( ret != -1 )
{
mbedtls_printf( "%s\n", buf );
}
I didn't test this, just checked the examples.

Not able to send client certificate over openssl

I am trying to implement MSRP over TLS, which requires me to do TLS handshaking for msrp port, i.e 2855. At the time of handshaking server is requesting for client certificate as expected. At the client end I have generated the certificate and the private key,however i am unable to send the certificate. I am using Doubango stack to communicate with openssl.
"SSL_CTX_use_certificate_file(contexts[i], transport->tls.ca, SSL_FILETYPE_PEM)" is what I am using to try to set the certificate. I think it gets set properly, since it doesn't throw any error. However, no matter what i do , the certificate is never sent to the server.
The Certificates Length is always 0.
Can anyone help me regarding this problem ? These are the steps I am following to generate the client certificate.
https://gist.github.com/mtigas/952344
My code to set the certificates is something like this :
#if HAVE_OPENSSL
{
int32_t i, ret;
SSL_CTX* contexts[3] = { tsk_null };
if(transport->tls.enabled){
contexts[0] = transport->tls.ctx_client;
contexts[1] = transport->tls.ctx_server;
}
TSK_DEBUG_INFO("ca = %s, pbk = %s, pvk = %s", ca, pbk, pvk);
for(i = 0; i < sizeof(contexts)/sizeof(contexts[0]); ++i){
if(!contexts[i]){
continue;
}
SSL_CTX_set_verify(contexts[i], transport->tls.verify ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT) : SSL_VERIFY_NONE, tsk_null);
TSK_DEBUG_INFO("tls.verify :%d", transport->tls.verify);
if(!tsk_strnullORempty(transport->tls.pbk) || !tsk_strnullORempty(transport->tls.pvk) || !tsk_strnullORempty(transport->tls.ca)){
/* Sets Public key (cert) */
if((ret = SSL_CTX_use_certificate_file(contexts[i], transport->tls.ca, SSL_FILETYPE_PEM)) != 1) {
TSK_DEBUG_ERROR("SSL_CTX_use_certificate_file failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
return -3;
}
/*Sets the password of the private key*/
if(!tsk_strnullORempty(ssl_password)){
SSL_CTX_set_default_passwd_cb_userdata(contexts[i], (void*)ssl_password);
}
/* Sets Private key (cert) */
if (!tsk_strnullORempty(transport->tls.pvk) && (ret = SSL_CTX_use_PrivateKey_file(contexts[i], transport->tls.pvk, SSL_FILETYPE_PEM)) != 1) {
TSK_DEBUG_ERROR("SSL_CTX_use_PrivateKey_file failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
return -4;
}
/* Checks private key */
if(!tsk_strnullORempty(transport->tls.pvk) && SSL_CTX_check_private_key(contexts[i]) == 0) {
TSK_DEBUG_ERROR("SSL_CTX_check_private_key failed [%d,%s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
return -5;
}
/* Sets trusted CAs and CA file */
if(!tsk_strnullORempty(transport->tls.ca) && (ret = SSL_CTX_load_verify_locations(contexts[i], transport->tls.ca, /*tlsdir_cas*/tsk_null)) != 1) {
TSK_DEBUG_ERROR("SSL_CTX_load_verify_locations failed [%d, %s]", ret, ERR_error_string(ERR_get_error(), tsk_null));
return -5;
}
}
}
}
#endif /* HAVE_OPENSSL */
Since I am not getting any of these errors, I am assuming that the certificates has been properly set. But still when server request for the certificates, the client fails to send it. i.e Certificates Length = 0.
Is there a way to peek into openssl if it is throwing any errors? Where can i get the openssl logs. ?
Please help or my leaves wouldnt get approved :(
I think you should check the Certificate types in Crtificate Request message. If your client certificate encryption doesn't match, then the client may not send certificate.

Encrypting/decrypting text strings using OpenSSL ECC

How can I use OpenSSL's ECC support to encrypt or decrypt a text string? I am able to generate ECC private/public keys using OpenSSL APIs, but I don't know how to encrypt plain text using those keys.
Since its so hard to find examples showing how to use ECC to encrypt data I thought I'd post some code for others to use. For the complete listing, check out my openssl-dev posting:
http://www.mail-archive.com/openssl-dev#openssl.org/msg28042.html
Basically its a flushed out usable version of how to use ECDH to secure a block of data. ECDH is used to generate a shared secret. The shared secret is then hashed using SHA 512. The resulting 512 bits are split up, with 256 serving as the key to the symmetric cipher (AES 256 in my example) and the other 256 bits used as the key for the HMAC. My implementation is loosely based on the ECIES standard outlined by SECG working group.
The key functions are ecies_encrypt() which accepts the public key in hex form and returns the encrypted data:
secure_t * ecies_encrypt(char *key, unsigned char *data, size_t length) {
void *body;
HMAC_CTX hmac;
int body_length;
secure_t *cryptex;
EVP_CIPHER_CTX cipher;
unsigned int mac_length;
EC_KEY *user, *ephemeral;
size_t envelope_length, block_length, key_length;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], block[EVP_MAX_BLOCK_LENGTH];
// Simple sanity check.
if (!key || !data || !length) {
printf("Invalid parameters passed in.\n");
return NULL;
}
// Make sure we are generating enough key material for the symmetric ciphers.
if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH) {
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
}
// Convert the user's public key from hex into a full EC_KEY structure.
if (!(user = ecies_key_create_public_hex(key))) {
printf("Invalid public key provided.\n");
return NULL;
}
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create())) {
printf("An error occurred while trying to generate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
}
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(user), ephemeral, ecies_key_derivation) != SHA512_DIGEST_LENGTH) {
printf("An error occurred while trying to compute the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// Determine the envelope and block lengths so we can allocate a buffer for the result.
else if ((block_length = EVP_CIPHER_block_size(ECIES_CIPHER)) == 0 || block_length > EVP_MAX_BLOCK_LENGTH || (envelope_length = EC_POINT_point2oct(EC_KEY_get0_group(
ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, NULL, 0, NULL)) == 0) {
printf("Invalid block or envelope length. {block = %zu / envelope = %zu}\n", block_length, envelope_length);
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// We use a conditional to pad the length if the input buffer is not evenly divisible by the block size.
else if (!(cryptex = secure_alloc(envelope_length, EVP_MD_size(ECIES_HASHER), length, length + (length % block_length ? (block_length - (length % block_length)) : 0)))) {
printf("Unable to allocate a secure_t buffer to hold the encrypted result.\n");
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// Store the public key portion of the ephemeral key.
else if (EC_POINT_point2oct(EC_KEY_get0_group(ephemeral), EC_KEY_get0_public_key(ephemeral), POINT_CONVERSION_COMPRESSED, secure_key_data(cryptex), envelope_length,
NULL) != envelope_length) {
printf("An error occurred while trying to record the public portion of the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
secure_free(cryptex);
return NULL;
}
// The envelope key has been stored so we no longer need to keep the keys around.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// For now we use an empty initialization vector.
memset(iv, 0, EVP_MAX_IV_LENGTH);
// Setup the cipher context, the body length, and store a pointer to the body buffer location.
EVP_CIPHER_CTX_init(&cipher);
body = secure_body_data(cryptex);
body_length = secure_body_length(cryptex);
// Initialize the cipher with the envelope key.
if (EVP_EncryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_EncryptUpdate(&cipher, body,
&body_length, data, length - (length % block_length)) != 1) {
printf("An error occurred while trying to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Check whether all of the data was encrypted. If they don't match up, we either have a partial block remaining, or an error occurred.
else if (body_length != length) {
// Make sure all that remains is a partial block, and their wasn't an error.
if (length - body_length >= block_length) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Copy the remaining data into our partial block buffer. The memset() call ensures any extra bytes will be zero'ed out.
memset(block, 0, EVP_MAX_BLOCK_LENGTH);
memcpy(block, data + body_length, length - body_length);
// Advance the body pointer to the location of the remaining space, and calculate just how much room is still available.
body += body_length;
if ((body_length = secure_body_length(cryptex) - body_length) < 0) {
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
// Pass the final partially filled data block into the cipher as a complete block. The padding will be removed during the decryption process.
else if (EVP_EncryptUpdate(&cipher, body, &body_length, block, block_length) != 1) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
}
// Advance the pointer, then use pointer arithmetic to calculate how much of the body buffer has been used. The complex logic is needed so that we get
// the correct status regardless of whether there was a partial data block.
body += body_length;
if ((body_length = secure_body_length(cryptex) - (body - secure_body_data(cryptex))) < 0) {
printf("The symmetric cipher overflowed!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
else if (EVP_EncryptFinal_ex(&cipher, body, &body_length) != 1) {
printf("Unable to secure the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
secure_free(cryptex);
return NULL;
}
EVP_CIPHER_CTX_cleanup(&cipher);
// Generate an authenticated hash which can be used to validate the data during decryption.
HMAC_CTX_init(&hmac);
mac_length = secure_mac_length(cryptex);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, secure_mac_data(cryptex), &mac_length) != 1) {
printf("Unable to generate a data authentication code. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
secure_free(cryptex);
return NULL;
}
HMAC_CTX_cleanup(&hmac);
return cryptex;
}
And ecies_decrypt() which takes the private key, again in hex form, and decrypts the previously secured buffer:
unsigned char * ecies_decrypt(char *key, secure_t *cryptex, size_t *length) {
HMAC_CTX hmac;
size_t key_length;
int output_length;
EVP_CIPHER_CTX cipher;
EC_KEY *user, *ephemeral;
unsigned int mac_length = EVP_MAX_MD_SIZE;
unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], md[EVP_MAX_MD_SIZE], *block, *output;
// Simple sanity check.
if (!key || !cryptex || !length) {
printf("Invalid parameters passed in.\n");
return NULL;
}
// Make sure we are generating enough key material for the symmetric ciphers.
else if ((key_length = EVP_CIPHER_key_length(ECIES_CIPHER)) * 2 > SHA512_DIGEST_LENGTH) {
printf("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8,
(key_length * 2) / 8);
return NULL;
}
// Convert the user's public key from hex into a full EC_KEY structure.
else if (!(user = ecies_key_create_private_hex(key))) {
printf("Invalid private key provided.\n");
return NULL;
}
// Create the ephemeral key used specifically for this block of data.
else if (!(ephemeral = ecies_key_create_public_octets(secure_key_data(cryptex), secure_key_length(cryptex)))) {
printf("An error occurred while trying to recreate the ephemeral key.\n");
EC_KEY_free(user);
return NULL;
}
// Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses
// SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure.
else if (ECDH_compute_key(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key(ephemeral), user, ecies_key_derivation) != SHA512_DIGEST_LENGTH) {
printf("An error occurred while trying to compute the envelope key. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EC_KEY_free(ephemeral);
EC_KEY_free(user);
return NULL;
}
// The envelope key material has been extracted, so we no longer need the user and ephemeral keys.
EC_KEY_free(ephemeral);
EC_KEY_free(user);
// Use the authenticated hash of the ciphered data to ensure it was not modified after being encrypted.
HMAC_CTX_init(&hmac);
// At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead.
if (HMAC_Init_ex(&hmac, envelope_key + key_length, key_length, ECIES_HASHER, NULL) != 1 || HMAC_Update(&hmac, secure_body_data(cryptex), secure_body_length(cryptex))
!= 1 || HMAC_Final(&hmac, md, &mac_length) != 1) {
printf("Unable to generate the authentication code needed for validation. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
HMAC_CTX_cleanup(&hmac);
return NULL;
}
HMAC_CTX_cleanup(&hmac);
// We can use the generated hash to ensure the encrypted data was not altered after being encrypted.
if (mac_length != secure_mac_length(cryptex) || memcmp(md, secure_mac_data(cryptex), mac_length)) {
printf("The authentication code was invalid! The ciphered data has been corrupted!\n");
return NULL;
}
// Create a buffer to hold the result.
output_length = secure_body_length(cryptex);
if (!(block = output = malloc(output_length + 1))) {
printf("An error occurred while trying to allocate memory for the decrypted data.\n");
return NULL;
}
// For now we use an empty initialization vector. We also clear out the result buffer just to be on the safe side.
memset(iv, 0, EVP_MAX_IV_LENGTH);
memset(output, 0, output_length + 1);
EVP_CIPHER_CTX_init(&cipher);
// Decrypt the data using the chosen symmetric cipher.
if (EVP_DecryptInit_ex(&cipher, ECIES_CIPHER, NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding(&cipher, 0) != 1 || EVP_DecryptUpdate(&cipher, block,
&output_length, secure_body_data(cryptex), secure_body_length(cryptex)) != 1) {
printf("Unable to decrypt the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
block += output_length;
if ((output_length = secure_body_length(cryptex) - output_length) != 0) {
printf("The symmetric cipher failed to properly decrypt the correct amount of data!\n");
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
if (EVP_DecryptFinal_ex(&cipher, block, &output_length) != 1) {
printf("Unable to decrypt the data using the chosen symmetric cipher. {error = %s}\n", ERR_error_string(ERR_get_error(), NULL));
EVP_CIPHER_CTX_cleanup(&cipher);
free(output);
return NULL;
}
EVP_CIPHER_CTX_cleanup(&cipher);
*length = secure_orig_length(cryptex);
return output;
}
I'm posting this because I personally couldn't find any other examples of how to secure files using ECC and the OpenSSL library. That said its worth mentioning alternatives that don't use OpenSSL. One is seccure which follows a pattern similar to my example, only it relies libgcrypt. Since libgcrypt doesn't provide all of the underlying ECC functions needed, the seccure program fills in the gaps and implements the ECC logic missing from libgcrypt.
Another program worth looking at is SKS, which uses a similar ECC based encryption process as the example above, but doesn't have any external dependencies (so all the ECC code is right there for you to look at).
ECC itself doesn't really define any encryption/decryption operations - algorithms built on elliptic curves do.
One example is Elliptic-Curve Diffie-Hellman. You could encrypt a message using ECDH by:
Generating an ephemeral EC key.
Using that key and the public key of the recipient, generate a secret using ECDH.
Use that secret as a key to encrypt the message with a symmetric cipher, like AES.
Transmit the encrypted message and the ephemeral public key generated in step 1.
To decrypt:
Load the ephemeral public key from the message.
Use that public key together with your recipient private key to generate a secret using ECDH.
Use that secret as a key to decrypt the message with the symmetric cipher.
EDIT: The following is the basic idea to generate a secret using ECDH. First we need to define a key derivation function - this one uses the SHA1 hash.
void *KDF1_SHA1(const void *in, size_t inlen, void *out, size_t *outlen)
{
if (*outlen < SHA_DIGEST_LENGTH)
return NULL;
else
*outlen = SHA_DIGEST_LENGTH;
return SHA1(in, inlen, out);
}
This is the ECDH code for the sender side. It assumes that the recipient's public key is already in "recip_key", and you have verified it with EC_KEY_check_key(). It also omits much important error checking, for the sake of brevity, which you will definitely want to include in production code.
EC_KEY *ephemeral_key = NULL;
const EC_GROUP *group = NULL;
unsigned char buf[SHA_DIGEST_LENGTH] = { 0 };
group = EC_KEY_get0_group(recip_key);
ephemeral_key = EC_KEY_new();
EC_KEY_set_group(ephemeral_key, group);
EC_KEY_generate_key(ephemeral_key);
ECDH_compute_key(buf, sizeof buf, EC_KEY_get0_public_key(recip_key), ephemeral_key, KDF1_SHA1);
After this the buffer 'buf' contains 20 bytes of material you can use for keying. This abbreviated example is based on the code in "ecdhtest.c" in the openssl source distribution - I suggest taking a look at it.
You will want to send the public key portion of ephemeral_key with the encrypted message, and securely discard the private key portion. A MAC over the data is also a good idea, and if you need more than 20 bytes of keying material a longer hash is probably in order.
The recipient does something similar, except that its private key already exists (since the sender had to know the corresponding public key beforehand), and the public key is recieved from the sender.