Let's start the engine
In Part 0, we learn how to developp a basic OpenSSL engine : a dynamic library which offloads cryptography computation from the crypto library.The second step is to use our engine.
OpenSSL is a complete solution, it provides:
- libraries for cryptography (libcrypto), for secure connections management (libssl)
- numerous command line utilities (e.g. certificate generations, encryption ..)
- openssl engine which lists available engines or load a new engine
- openssl dgst which computes a message digest from a given data source (stdin, input file ..)
- openssl enc which encrypts or decrypts messages (which we will use in a later article)
$ echo abc | openssl dgst -sha1 (stdin)= 03cfd743661f07975fa2f1220c5194cbaff48451
This first example computes the SHA1 digest of the string "abc" using the standard implementation of the SHA1 algorithm provided by openssl. The string is forwarded through a pipe but it could have been read from a file (using -in <input filename> option ).
Let us now apply the same command with an extra -engine option to force openssl to rely on our own SHA1 implementation:
$ echo abc | openssl dgst -engine `pwd`/build/engine_ex.so -sha1
engine "engineX" set.
(stdin)= 03cfd743661f07975fa2f1220c5194cbaff48451
Happilly the digests match. You can also notice that our engine was set which means the openssl registered it as requested. (Note: To make sure OpenSSL in indeed using your engine you can introduce a small error in the digest computed by the engine (e.g. in the function sha1_final in "e_ex.c" ) to make sure it is visible in the result).
Accessing our engine in a C program
OpenSSL CLI is a powerful tools but if you want to developp your own efficient cryptography application you may be more inclined to access OpenSSL capabilities through its C API.In the following sections we are going to look in details at the example tests/basic_digest.c, available on the article series github.
In those sections we will see several things: how to write an OpenSSL configuration file so OpenSSL can learn the existence of our engine and where to load it from, how to load this configuration file in a program using OpenSSL libraries (loading our engine with it), how to use explicit our engine as a digest implementation to compute a message digest and finally how to define our engine as the by default implementation of OpenSSL so each time a SHA1 digest is computed through the EVP API the computation is offloaded to our engine.
Writing an OpenSSL Configuration File
A clean way to load an engine from within a C program is to integrate the engine description to an OpenSSL configuration file and to load the configuration file in your application.The following example is a configuration file which described how to load the engine built in the previous lesson:
openssl_conf = openssl_def [openssl_def] engines = engine_section [engine_section] engine_x = engine_x_section [engine_x_section] engine_id = engineX dynamic_path = ${ENV::PWD}/build/engine_ex.so init = 1
An interesting feature of OpenSSL configuration file is the possibility to include envrionemment (shell) variables with the ${ENV::<var name>} syntax.
Initializing OpenSSL and loading our engine
There are several calls necessary to initialize OpenSSL properly, a detailed explaination can be found in OpenSSL wiki. In this example we will simply make the basic calls to enable engine load and use:// initializing OpenSSL library OPENSSL_load_builtin_modules(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); ENGINE_load_dynamic();
Once the library is initialized we may load the configuration file:
// building OpenSSL's configuration file path char openssl_cnf_path[] = "./openssl.cnf"; // loading configuration if (CONF_modules_load_file(openssl_cnf_path, "openssl_conf", 0) != 1) { fprintf(stderr, "OpenSSL failed to load required configuration\n"); ERR_print_errors_fp(stderr); return 1; }
The configuration loading will make the library load the engine described in "openssl.cnf". Thereafter, it may be accessed as follows:
ENGINE* eng = ENGINE_by_id("engineX"); if(NULL == eng) { printf("failed to retrieve engine by id (mppa)\n"); return 1; }
Forcing the use of our engine in an explicit digest call
The handle of type Engine* may be used in a EVP_DigestInit_ex call to force the use of the engine.
unsigned char digest[20]; // message digest context declaration EVP_MD_CTX sctx; EVP_MD_CTX* ctx = &sctx; EVP_MD_CTX_init(ctx); // declaring the use of SHA-1 and specifying the // engine as the implementation to use EVP_DigestInit_ex(ctx, EVP_sha1(), eng); // updating digest with input data EVP_DigestUpdate(ctx, data, len); unsigned int dlen = -1; // finishing and retrieving digest EVP_DigestFinal(ctx, digest, &dlen);
Configuring our engine to be used as default
OpenSSL provides through the ENGINE API some functions to bind an engine as the default method for any algorithm including digest:
// defining our engine as the default implementations // for digest algorithms ENGINE_set_default_digests(eng);
// Initializing a SHA-1 digest context // without specifying an implementation, // thus falling back on the default method EVP_DigestInit(ctx, EVP_sha1());
An other way to define our engine as the default method for algorithms it supports, is to add the line "default_algorithms = ALL" to our engine section in openssl configuration file :
openssl_conf = openssl_def [openssl_def] engines = engine_section [engine_section] engine_x = engine_x_section [engine_x_section] engine_id = engineX dynamic_path = ${ENV::PWD}/build/engine_ex.so default_algorithms = ALL init = 1
The "ALL" option value associates our engine as the default implementation for all algorithms (restrain to all the algorithm the engine supports). The other possible values are: RSA, DSA, DH, EC, RAND, CIPHERS, DIGESTS, PKEY, PKEY_CRYPTO and PKEY_ASN1. As our engine only support a digest (SHA1), the values ALL and DIGESTS have the same outcome in our configuration : declaring our engine as the default implementation for SHA1.
Warning: some OpenSSL non standard methods (such as SHA256(...) ) bypass the EVP API and do not depend on the default implementation. Those methods only use the software implementation built-in OpenSSL and never the engines even if defined as default methods.
References
- Wikipedia's page on SHA1 https://en.wikipedia.org/wiki/SHA-1
- OpenSSL manual page on Engine API https://wiki.openssl.org/index.php/Manual:Engine%283%29
- OpenSSL manual page on library initialization https://wiki.openssl.org/index.php/Library_Initialization
- External documentation / example of OpenSSL configuration files : file https://www.nlnetlabs.nl/downloads/publications/hsm/hsm_node18.html
- An other tutorial which talks about OpenSSL configuration files : https://www.sinodun.com/2009/02/developing-an-engine-for-openssl/
No comments:
Post a Comment