Introduction
The OpenSSL library takes in charge most of the aspects of establishing and managing secure connections (certificate generation and storage, key exchange, asymmetric and symmetric cryptography ...). One of its component is the crypto library which contains implementations for the most current digest and cipher algorithms (at least those required by SSL and TLS).Those algorithms are generally considered computationally intensive and some offloading solutions exist. They consist in using hardware acceleration to offload expensive cryptography computation from your server/workstation. OpenSSL supports such offloading through the engine API. This API permits the declaration and binding of external hardware to take care of digest / cipher and public key cryptography acceleration. The following blog posts will expose how to develop, configure and use your own OpenSSL engine.
Hands-on: Let's start coding a minimal engine
/** file e_ex.c */
#include <openssl/engine.h> /** Engine name and id */ static const char* engine_id = "engineX"; static const char* engine_name = "Engine example";
static int bind(ENGINE* e, const char* id) { int ret = 0; if (!ENGINE_set_id(e, engine_id)) { fprintf(stderr, "ENGINE_set_id failed\n"); goto end; } if (!ENGINE_set_name(e, engine_name)) { printf("ENGINE_set_name failed\n"); goto end; } ret = 1; end: return ret; } IMPLEMENT_DYNAMIC_BIND_FN(bind) IMPLEMENT_DYNAMIC_CHECK_FN()
OpenSSL supports several build systems for engines. Some of them are built-in with the standard OpenSSL library and are available within the engine directory of OpenSSL Source. But the easiest way to add an engine is to use the dynamic loading capability of OpenSSL engine API and to build your engine as a shared library, for example using the following commands:
$ gcc -fPIC -o e_ex.o e_ex.c $ gcc -shared -o e_ex.so -lcrypto e_ex.o
We can now try to load our engine with openssl to check that everything is OK:
$ openssl engine -t -c `pwd`/e_ex.so
(/<path_to_engine>/e_ex.so) Engine example
Loaded: (engineX) Engine example
[ available ]
Functional engine
The previous step was sufficient to build a working engine, loadable by OpenSSL but it was not a very useful one. Now let us add a bit of functionality to it.We will start by adding digest capability, that is we will declare our engine as capable of computing a hash function (namely SHA1).
To realize this we need to implement (at least) the following functions:
/* initialize hash computation */ int sha1_init(EVP_MD_CTX *ctx); /* consume more data to hash */ int sha1_update(EVP_MD_CTX* ctx, const void* data, size_t count); /* finish hashing and output digest */ int sha1_final(EVP_MD_CTX* ctx, unsigned char* md);
Those functions shall return 1 on success. In older versions of OpenSSL, those implementations needed to be inserted into a new EVP_MD structure :
static const EVP_MD digest_sha1 = { NID_sha1, /* Name id for SHA1 digest */ 0, /* pkey_type, NID with private key */ 20, /* message digest size */ 0, /* Flags */ sha1_init, /* initialization */ sha1_update, /* update */ sha1_final, /* finalize */ NULL, /* copy */ NULL, /* cleanup */ EVP_PKEY_NULL_method, /* require pkey type */ 64, /* internal block size */ sizeof(SHA_CTX), /* size of md_data structure */ NULL /* control function */ };
The next step is to implement a digest selector, a function which will list the digest implementations supported by our engine and allow the program to select one.
static digest_nids[] = {NID_sha1, 0}; int digest_selector(ENGINE* e, const EVP_MD** digest,
const int** nids,
int nid) { int ok = 1; if (!digest) { /* expected to return the list of supported NIDs */ *nids = digest_nids; return (sizeof(digest_nids) - 1) / sizeof(digest_nids[0]); } /** Request for a specific digest */ switch (nid) { case NID_sha1: *digest = &digest_sha1; break; default: ok = 0; *digest = NULL; break; } return ok; }
At last we will register this selector for use by the engine.This is done by adding a call to ENGINE_set_digest within our bind function:
static int bind(ENGINE* e, const char* id) { int ret = 0; if (!ENGINE_set_id(e, engine_id)) { fprintf(stderr, "ENGINE_set_id failed\n"); goto end; } if (!ENGINE_set_name(e, engine_name)) { printf("ENGINE_set_name failed\n"); goto end; } if (!ENGINE_set_digests(e, digest_selector)) { printf("ENGINE_set_digest failed\n"); goto end; } ret = 1; end: return ret; }
After building back our engine shared library we can load it again and see that the SHA1 functionality has been registered:
$ openssl engine -t -c `pwd`/e_ex.so
(/<path_to_engine>/e_ex.so) Engine example
Loaded: (engineX) Engine example
[SHA1]
[ available ]
Improvements
The way we declared our SHA1 digest method is deprecated in recent version of OpenSSL. It was not very future proof as we relied on an internal OpenSSL Structure susceptible to change (without notice !).More recent version of OpenSSL ,offers ad-hoc functions (e.g. EVP_MD_meth_new EVP_CIPHER_meth_new) to build dynamically message digest and cipher implementations.
In the next articles, we will see
- How to use your openssl engine using openssl CLI (e.g. openssl dgst)
- How to use your openssl engine from a C file (use the configuration interface)
- How to extend your openssl engine to support ciphers
Source Code:
The source code for the example engine can be downloaded here (branch part0):https://github.com/nibrunie/OSSL_EngineX
References
- OpenSSL website: https://www.openssl.org/
- OpenSSL official blog posts on building your own engine
- https://www.openssl.org/blog/blog/2015/10/08/engine-building-lesson-1-a-minimum-useless-engine/
- https://www.openssl.org/blog/blog/2015/11/23/engine-building-lesson-2-an-example-md5-engine/
- An other tutorial on a minimal engine (with configuration file description) https://www.sinodun.com/2009/02/developing-an-engine-for-openssl/
Updates:
- Updated on August 1st following some remarks from my brother (many typos fixed)
No comments:
Post a Comment