📄 readme
字号:
Notes: 2001-09-24-----------------This "description" (if one chooses to call it that) needed some major updatingso here goes. This update addresses a change being made at the same time toOpenSSL, and it pretty much completely restructures the underlying mechanics ofthe "ENGINE" code. So it serves a double purpose of being a "ENGINE internalsfor masochists" document *and* a rather extensive commit log message. (I'd getlynched for sticking all this in CHANGES or the commit mails :-).ENGINE_TABLE underlies this restructuring, as described in the internal header"eng_int.h", implemented in eng_table.c, and used in each of the "class" files;tb_rsa.c, tb_dsa.c, etc.However, "EVP_CIPHER" underlies the motivation and design of ENGINE_TABLE soI'll mention a bit about that first. EVP_CIPHER (and most of this appliesequally to EVP_MD for digests) is both a "method" and a algorithm/modeidentifier that, in the current API, "lingers". These cipher description +implementation structures can be defined or obtained directly by applications,or can be loaded "en masse" into EVP storage so that they can be catalogued andsearched in various ways, ie. two ways of encrypting with the "des_cbc"algorithm/mode pair are;(i) directly; const EVP_CIPHER *cipher = EVP_des_cbc(); EVP_EncryptInit(&ctx, cipher, key, iv); [ ... use EVP_EncryptUpdate() and EVP_EncryptFinal() ...](ii) indirectly; OpenSSL_add_all_ciphers(); cipher = EVP_get_cipherbyname("des_cbc"); EVP_EncryptInit(&ctx, cipher, key, iv); [ ... etc ... ]The latter is more generally used because it also allows ciphers/digests to belooked up based on other identifiers which can be useful for automatic cipherselection, eg. in SSL/TLS, or by user-controllable configuration.The important point about this is that EVP_CIPHER definitions and structures arepassed around with impunity and there is no safe way, without requiring massiverewrites of many applications, to assume that EVP_CIPHERs can be referencecounted. One an EVP_CIPHER is exposed to the caller, neither it nor anything itcomes from can "safely" be destroyed. Unless of course the way of getting tosuch ciphers is via entirely distinct API calls that didn't exist before.However existing API usage cannot be made to understand when an EVP_CIPHERpointer, that has been passed to the caller, is no longer being used.The other problem with the existing API w.r.t. to hooking EVP_CIPHER supportinto ENGINE is storage - the OBJ_NAME-based storage used by EVP to registerciphers simultaneously registers cipher *types* and cipher *implementations* -they are effectively the same thing, an "EVP_CIPHER" pointer. The problem withhooking in ENGINEs is that multiple ENGINEs may implement the same ciphers. Thesolution is necessarily that ENGINE-provided ciphers simply are not registered,stored, or exposed to the caller in the same manner as existing ciphers. This isespecially necessary considering the fact ENGINE uses reference counts to allowfor cleanup, modularity, and DSO support - yet EVP_CIPHERs, as exposed tocallers in the current API, support no such controls.Another sticking point for integrating cipher support into ENGINE is linkage.Already there is a problem with the way ENGINE supports RSA, DSA, etc wherebythey are available *because* they're part of a giant ENGINE called "openssl".Ie. all implementations *have* to come from an ENGINE, but we get round that byhaving a giant ENGINE with all the software support encapsulated. This createslinker hassles if nothing else - linking a 1-line application that calls 2 basicRSA functions (eg. "RSA_free(RSA_new());") will result in large quantities ofENGINE code being linked in *and* because of that DSA, DH, and RAND also. If wecontinue with this approach for EVP_CIPHER support (even if it *was* possible)we would lose our ability to link selectively by selectively loading certainimplementations of certain functionality. Touching any part of any kind ofcrypto would result in massive static linkage of everything else. So thesolution is to change the way ENGINE feeds existing "classes", ie. how thehooking to ENGINE works from RSA, DSA, DH, RAND, as well as adding new hookingfor EVP_CIPHER, and EVP_MD.The way this is now being done is by mostly reverting back to how things used towork prior to ENGINE :-). Ie. RSA now has a "RSA_METHOD" pointer again - thiswas previously replaced by an "ENGINE" pointer and all RSA code that requiredthe RSA_METHOD would call ENGINE_get_RSA() each time on its ENGINE handle totemporarily get and use the ENGINE's RSA implementation. Apart from being moreefficient, switching back to each RSA having an RSA_METHOD pointer also allowsus to conceivably operate with *no* ENGINE. As we'll see, this removes any needfor a fallback ENGINE that encapsulates default implementations - we can simplyhave our RSA structure pointing its RSA_METHOD pointer to the softwareimplementation and have its ENGINE pointer set to NULL.A look at the EVP_CIPHER hooking is most explanatory, the RSA, DSA (etc) casesturn out to be degenerate forms of the same thing. The EVP storage of ciphers,and the existing EVP API functions that return "software" implementations anddescriptions remain untouched. However, the storage takes more meaning in termsof "cipher description" and less meaning in terms of "implementation". When anEVP_CIPHER_CTX is actually initialised with an EVP_CIPHER method and is about tobegin en/decryption, the hooking to ENGINE comes into play. What happens is thatcipher-specific ENGINE code is asked for an ENGINE pointer (a functionalreference) for any ENGINE that is registered to perform the algo/mode that theprovided EVP_CIPHER structure represents. Under normal circumstances, thatENGINE code will return NULL because no ENGINEs will have had any cipherimplementations *registered*. As such, a NULL ENGINE pointer is stored in theEVP_CIPHER_CTX context, and the EVP_CIPHER structure is left hooked into thecontext and so is used as the implementation. Pretty much how things work nowexcept we'd have a redundant ENGINE pointer set to NULL and doing nothing.Conversely, if an ENGINE *has* been registered to perform the algorithm/modecombination represented by the provided EVP_CIPHER, then a functional referenceto that ENGINE will be returned to the EVP_CIPHER_CTX during initialisation.That functional reference will be stored in the context (and released oncleanup) - and having that reference provides a *safe* way to use an EVP_CIPHERdefinition that is private to the ENGINE. Ie. the EVP_CIPHER provided by theapplication will actually be replaced by an EVP_CIPHER from the registeredENGINE - it will support the same algorithm/mode as the original but will be acompletely different implementation. Because this EVP_CIPHER isn't stored in theEVP storage, nor is it returned to applications from traditional API functions,there is no associated problem with it not having reference counts. And ofcourse, when one of these "private" cipher implementations is hooked intoEVP_CIPHER_CTX, it is done whilst the EVP_CIPHER_CTX holds a functionalreference to the ENGINE that owns it, thus the use of the ENGINE's EVP_CIPHER issafe.The "cipher-specific ENGINE code" I mentioned is implemented in tb_cipher.c butin essence it is simply an instantiation of "ENGINE_TABLE" code for use byEVP_CIPHER code. tb_digest.c is virtually identical but, of course, it is foruse by EVP_MD code. Ditto for tb_rsa.c, tb_dsa.c, etc. These instantiations ofENGINE_TABLE essentially provide linker-separation of the classes so that evenif ENGINEs implement *all* possible algorithms, an application using onlyEVP_CIPHER code will link at most code relating to EVP_CIPHER, tb_cipher.c, coreENGINE code that is independant of class, and of course the ENGINEimplementation that the application loaded. It will *not* however link anyclass-specific ENGINE code for digests, RSA, etc nor will it bleed over intoother APIs, such as the RSA/DSA/etc library code.ENGINE_TABLE is a little more complicated than may seem necessary but this ismostly to avoid a lot of "init()"-thrashing on ENGINEs (that may have to loadDSOs, and other expensive setup that shouldn't be thrashed unnecessarily) *and*to duplicate "default" behaviour. Basically an ENGINE_TABLE instantiation, forexample tb_cipher.c, implements a hash-table keyed by integer "nid" values.These nids provide the uniquenness of an algorithm/mode - and each nid will hashto a potentially NULL "ENGINE_PILE". An ENGINE_PILE is essentially a list ofpointers to ENGINEs that implement that particular 'nid'. Each "pile" uses somecaching tricks such that requests on that 'nid' will be cached and all futurerequests will return immediately (well, at least with minimal operation) unlessa change is made to the pile, eg. perhaps an ENGINE was unloaded. The reason isthat an application could have support for 10 ENGINEs statically linkedin, and the machine in question may not have any of the hardware those 10ENGINEs support. If each of those ENGINEs has a "des_cbc" implementation, wewant to avoid every EVP_CIPHER_CTX setup from trying (and failing) to initialiseeach of those 10 ENGINEs. Instead, the first such request will try to do thatand will either return (and cache) a NULL ENGINE pointer or will return afunctional reference to the first that successfully initialised. In the lattercase it will also cache an extra functional reference to the ENGINE as a"default" for that 'nid'. The caching is acknowledged by a 'uptodate' variablethat is unset only if un/registration takes place on that pile. Ie. ifimplementations of "des_cbc" are added or removed. This behaviour can betweaked; the ENGINE_TABLE_FLAG_NOINIT value can be passed toENGINE_set_table_flags(), in which case the only ENGINEs that tb_cipher.c willtry to initialise from the "pile" will be those that are already initialised(ie. it's simply an increment of the functional reference count, and no real"initialisation" will take place).RSA, DSA, DH, and RAND all have their own ENGINE_TABLE code as well, and thedifference is that they all use an implicit 'nid' of 1. Whereas EVP_CIPHERs areactually qualitatively different depending on 'nid' (the "des_cbc" EVP_CIPHER isnot an interoperable implementation of "aes_256_cbc"), RSA_METHODs arenecessarily interoperable and don't have different flavours, only differentimplementations. In other words, the ENGINE_TABLE for RSA will either be empty,or will have a single ENGING_PILE hashed to by the 'nid' 1 and that pilerepresents ENGINEs that implement the single "type" of RSA there is.Cleanup - the registration and unregistration may pose questions about howcleanup works with the ENGINE_PILE doing all this caching nonsense (ie. when theapplication or EVP_CIPHER code releases its last reference to an ENGINE, theENGINE_PILE code may still have references and thus those ENGINEs will stayhooked in forever). The way this is handled is via "unregistration". With thesenew ENGINE changes, an abstract ENGINE can be loaded and initialised, but thatis an algorithm-agnostic process. Even if initialised, it will not haveregistered any of its implementations (to do so would link all class "table"code despite the fact the application may use only ciphers, for example). Thisis deliberately a distinct step. Moreover, registration and unregistration hasnothing to do with whether an ENGINE is *functional* or not (ie. you can evenregister an ENGINE and its implementations without it being operational, you maynot even have the drivers to make it operate). What actually happens withrespect to cleanup is managed inside eng_lib.c with the "engine_cleanup_***"functions. These functions are internal-only and each part of ENGINE code thatcould require cleanup will, upon performing its first allocation, register acallback with the "engine_cleanup" code. The other part of this that makes ittick is that the ENGINE_TABLE instantiations (tb_***.c) use NULL as theirinitialised state. So if RSA code asks for an ENGINE and no ENGINE hasregistered an implementation, the code will simply return NULL and the tb_rsa.cstate will be unchanged. Thus, no cleanup is required unless registration takesplace. ENGINE_cleanup() will simply iterate across a list of registered cleanupcallbacks calling each in turn, and will then internally delete its own storage(a STACK). When a cleanup callback is next registered (eg. if the cleanup() ispart of a gracefull restart and the application wants to cleanup all state thenstart again), the internal STACK storage will be freshly allocated. This is muchthe same as the situation in the ENGINE_TABLE instantiations ... NULL is theinitialised state, so only modification operations (not queries) will cause thatcode to have to register a cleanup.What else? The bignum callbacks and associated ENGINE functions have beenremoved for two obvious reasons; (i) there was no way to generalise them to themechanism now used by RSA/DSA/..., because there's no such thing as a BIGNUMmethod, and (ii) because of (i), there was no meaningful way for library orapplication code to automatically hook and use ENGINE supplied bignum functionsanyway. Also, ENGINE_cpy() has been removed (although an internal-only versionexists) - the idea of providing an ENGINE_cpy() function probably wasn't a goodone and now certainly doesn't make sense in any generalised way. Some of theRSA, DSA, DH, and RAND functions that were fiddled during the original ENGINEchanges have now, as a consequence, been reverted back. This is because thehooking of ENGINE is now automatic (and passive, it can interally use a NULLENGINE pointer to simply ignore ENGINE from then on).Hell, that should be enough for now ... comments welcome: geoff@openssl.org
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -