Coming back after a month and two weeks, it’s time to resume with the next engine lesson, this time building an engine implementing a digest.
It doesn’t matter much what digest algorithm we choose. Being lazy, I’ve chosen one with a well defined reference implementation, MD5 (reference implementation is found in RFC 1321)
In this example, I’ve extracted the three files global.h
, md5.h
and md5c.c
into rfc1321/
. According to the license, I need to
identify them as “RSA Data Security, Inc. MD5 Message-Digest
Algorithm”, hereby done. [^1]
From an engine point of view, there are three things that need to be done to implement a digest:
- Create an OpenSSL digest method structure with pointers to the functions that will be OpenSSL’s interface to the reference implementation.
- Create the interface functions.
- Create a digest selector function.
Let’s begin with the structure by example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
NOTE: the EVP_MD will become opaque in future OpenSSL versions, starting with version 1.1, and the structure init above will have to be replaced with a number of function calls to initialise the different parts of the structure. More on that later.
A slightly complicating factor with this structure is that it also involves private/public key ciphers. I’m ignoring the associated fields in this lesson, along with the control function (all marked “IGNORED:”) and will get back to them in a future lesson where I’ll get into public/private key algo implementations.
The numerical ID is an OpenSSL number that identifies the digest
algorithm, and is an index to the OID database.
The flags are not really used for pure digests and can be left zero.
The copy and cleanup functions are hooks to be used if there’s more to
copying and cleanup EVP_MD_CTX
for our implementation than OpenSSL
can handle on its own. The MD5_CTX
structure in the reference MD5
implementation has nothing magic about it, and there is therefore no
need to use the copy
and cleanup
hooks.
The internal block size is present for optimal use of the algorithm.
On to implementing the interface functions md5_init
, md5_update
and md5_final
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
With the reference implementation, things are very straight forward,
no errors can happen, all we do is pass data back and forth. A note:
ctx->md_data
is preallocated by OpenSSL using the size given in the
EVP_MD
structure. In a more complex implementation, these functions
are expected to return 0 on error, 1 on success.
The selector function deserves some special attention, as it really performs two separate functions. The prototype is as follows:
1 2 |
|
OpenSSL calls it in the following ways:
- with
digest
beingNULL
. In this case,*nids
is expected to be assigned a zero-terminated array of NIDs and the call returns with the number of available NIDs. OpenSSL uses this to determine what digests are supported by this engine. - with
digest
being non-NULL
. In this case,*digest
is expected to be assigned the pointer to theEVP_MD
structure corresponding to the NID given bynid
. The call returns with 1 if the request NID was one supported by this engine, otherwise 0.
The implementation would be this for our little engine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
What remains to do is the following call as part of setting up this engine:
1 2 3 4 |
|
Combine with the code from last lesson results in this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
|
Building is just as easy as last time:
1 2 3 |
|
Let’s start with checking that OpenSSL loads it correctly. Remember what Lesson 1 taught us, that we can pass an absolute path for an engine? Let’s use that again.
1 2 3 4 5 |
|
Finally, we can compare the result of a run with OpenSSL’s own implementation.
1 2 3 4 5 |
|
And there we have it, a functioning implementation of an MD5 engine.
Just as in the previous lesson, I made a slightly more elaborate
variant of this engine,
again using the GNU auto* tools. This variant also has a different
way of initialising digest_md5
by taking a copy of the OpenSSL
builtin structure and just replacing the appropriate fields. This
allows it to be used for operations involving public/private keys as
well.
Next lesson will be about adding a symmetric cipher implementation.
[^1] I needed to make a small change in rfc1321/global.h
1 2 3 4 5 6 7 8 |
|
The reason is that I’m running on a 64-bit machine, so UINT4
ended
up not being 32 bits, and the reference MD5 gave me faulty results.