* Jacques A. Vidrine ([EMAIL PROTECTED]) wrote:
> Prepare for a possibly dumb question. :-)

No, it's a good question.

> How are existing applications generally supposed to take advantage of
> ENGINE?  I notice that in crypto/evp/c_all.c, there is an
> OpenBSD-specific hook to enable the `cryptodev' engine.  This appears to
> result in (naive, existing) applications using the `cryptodev' engine
> whether they want to or not.
> 
> Is there some configuration infrastructure that can be used to affect
> the behavior of existing OpenSSL-based applications with regard to the
> use of ENGINE?  If not, should there be?  What is the direction here?

If the application makes no ENGINE API calls, then no ENGINEs will be
brought into scope (ie. if you use static-linking, you will only get the
ENGINE core but won't link against ENGINE implementations). The ENGINE
core code maintains an implementation table against a variety of
"categories", check out crypto/engine/tb_*.c. These tables support
sub-classification by "nid" but only some of them make use of them, ie.
cipher, and digest will actually divide the table up according to the
'nid' of the algorithm required, whereas RSA is always RSA and so
there's only one division used - when 'nid' is hard-coded as 1.

Within these tables are some semantics that "just work" - they allow
ENGINE to function in a useful way whilst not causing compatibility
problems for applications that don't care and don't want to know.
However these semantics may not be everyone's "Right Way", so don't feel
embarassed if you find them odd.

Hm, where to start. What follows will be an ad-hoc run-down on this
stuff, <warning>please skip if you're not interested.</you were warned>

--- README.how-engine-implementations-are-registered-and-managed ---

The tables are implemented in crypto/engine/eng_table.c (see the
ENGINE_PILE structure, ENGINE_TABLE is merely an 'nid'-indexed LHASH
table of ENGINE_PILE items). An ENGINE_PILE represents all
implementations of a single algorithm - eg. be it "des_ede3_ecb",
"sha1", or "RSA".

When the "regular" openssl API is about to initialise a context
structure for performing one of these algorithms, it will either use a
supplied "ENGINE" from the caller, or if a NULL is supplied it will
instead request from the core engine code to nominate a "default". Eg.
RSA_new_method() in crypto/rsa/rsa_lib.c uses ENGINE_get_default_RSA(),
and that in turns calls engine_table_select() in crypto/engine/tb_rsa.c.
Unless an ENGINE is "registered" into an ENGINE_TABLE as supporting the
corresponding algorithm, that table remains NULL and so the
engine_table_select() calls trivially return NULL - causing everything
to happen in software. One reason for this is that then no cleanup is
required - the ENGINE_TABLE is never allocated and used unless an ENGINE
is actually registered first. At that same moment, a callback is
registered into the ENGINE_CLEANUP for the same table so that an
application can call ENGINE_cleanup(void) and know it will clean up
*whatever* is required (including, possibly, nothing). If you don't
register ENGINEs, you don't even need to call ENGINE_cleanup() - again,
some slippery coding tricks to ensure backwards compatibility.

So, where are we then - engine_table_select(). This uses the 'nid' to
home in on the required ENGINE_PILE in the ENGINE_TABLE, and this
ENGINE_PILE contains all the possible implementations of the required
algorithm plus a variety of caching/optimising magic. I'll try to
explain as briefly as possible how that behaves, but it'd help first to
forget all about ENGINE_TABLE, ENGINE_PILE, etc. That stuff above was
mentioned in case you find the below a little too hand-wavey - if so,
you can use the stuff above to dig further and see for yourself. Let's
just say that for any given algorithm, there's a table of
implementations stored in the ENGINE code, *OR* the table doesn't exist
because no implementations are registered. That "table" happens to be
an ENGINE_PILE structure if you care to know, but you certainly don't
have to.

Each table of implementations contains a STACK of structural references
to ENGINEs that implement the required algorithm. Many of these
references may not be able to function (ie. ENGINE_init() would fail on
them at run-time), but they exist and were registered by the application
so we store them. In addition to this STACK of implementations, we also
store an ENGINE pointer that will cache a functional reference to a
"default" ENGINE or NULL, and finally we also have a "touchfile" boolean
to prevent us performing a lot of logic if we know the table hasn't been
changed since we last did it.

Whenever you register or unregister an implementation from a table, the
"touchfile" is unset so the next call to engine_table_select() will
cause some logic to be performed to choose a functional reference to an
ENGINE to be returned to the caller (or NULL for no ENGNE). The
"selection logic" in engine_table_select() goes something like this
(whether touchfile is set or not);
  - if our cached functional reference is non-NULL, it represents an
    ENGINE that is already initialised and so is clearly valid and
    working and used as our table's default. We call ENGINE_init() on
    the reference to generate a new functional reference and return that
    to the caller. Done.
  - if our "touchfile" is set and the cached value is actually NULL,
    then that is what we return anyway. This is because we don't want to
    repeatedly try to initialise implementations that fail to
    initialise, at least not unless the list of implementations has
    changed. As this list is usually set once-and-for-all when the
    application starts up, this means run-time performance should not be
    affected once the used tables set their "touchfile"s.
  - finally, we loop through our list of implementations and attempt to
    ENGINE_init() each of them in turn. The first that succeeds, will be
    set as our cached functional reference and a second functional
    reference will be returned to the caller.
  - in all cases, the "touchfile" is set before returning to the caller.

This may seem a little arbitrary, but the side-effects justify it. They
include;

  - If you register a few ENGINEs for any/all algorithms they support,
    then at run time you can simply use the RSA, DSA, DH, EVP_CIPHER,
    etc APIs and they will automatically use whatever ENGINEs support
    the required algorithms and are functioning. Moreover, the order you
    add these ENGINEs dictates the order in which they are "tried".
    Moreover, there are "set_default" alternatives to the various
    "register" ENGINE API functions that will automatically try to
    initialise the ENGINE before putting it in the table(s), and if it
    initialises, will automatically cache the ENGINE as the default for
    future use. The only way to switch defaults from then on is to set
    a new default later, or unregister the ENGINE that has become the
    default.
  - An ENGINE that is functioning and being used for, say, RSA will have
    a cached functional reference in the RSA table - as a consequence,
    rapidly creating and destroying RSA keys will cause the ENGINE's
    functional reference to rapidly oscillate between 1 and 2. If we
    didn't cache a functional reference, it would rapidly oscillate
    between 0 and 1 which would result in the ENGINE's initialisation
    and shutdown processing to get thrashed. As this can include loading
    shared-libraries and initialising hardware, it makes sense to ensure
    this doesn't happen.
  - Applications making no ENGINE API calls to register any ENGINEs will
    not be "transparently" using ENGINEs at run-time, so the principle
    of least surprise is satisfied.
  - Applications wanting "easy acceleration" simply need to ensure that
    any ENGINEs they are interested in are registered "for anything they
    support", and if the corresponding devices exist and can be
    initialised successfully, will be automatically be used for any/all
    algorithms they support. Ie. KISS works for KISS requirements.
  - Applications wanting fine-tuned control over which ENGINEs are
    registered for which algorithms, particularly if they want to
    support multiple ENGINEs for different roles, can be as specific as
    they want to be. Eg. setting up an external hardware-PRNG, a
    distributed-computing driver for RSA, and an on-board chip for
    accelerating various DES modes.

And *finally* (I know, about time) you can globally control how all
these implementation tables work using the ENGINE_set_table_flags() API
function. Currently there's only one flag supported,
ENGINE_TABLE_FLAG_NOINIT, which if set will prevent any transparent
attempts to initialise ENGINEs. In this mode of operation, you can
register engines until you're blue in the face, but unless you
explicitly set one of them as a default they serve no purpose except
perhaps to make sure they are stored somewhere (so you can immediately
destroy any handles you had in application code and know
ENGINE_cleanup() will dismiss everything at application termination).
This means no ENGINE gets magically used without the application's
explicit say-so.

Suggestions for improvements in the behaviour or implementation are
welcome, and "diff -u" patches to make such changes are even more
welcome. :-)

Sorry if that's more than you wanted - I think this little splurge had
been a while coming so this seemed as good a time as any. I'll probably
also bookmark this one in the mail archives for future reference. Is
there anything from your point of view that I haven't adequately
explained?

Cheers,
Geoff

-- 
Geoff Thorpe
[EMAIL PROTECTED]
http://www.openssl.org/

______________________________________________________________________
OpenSSL Project                                 http://www.openssl.org
Development Mailing List                       [EMAIL PROTECTED]
Automated List Manager                           [EMAIL PROTECTED]

Reply via email to