CORE & PROV: Add a generic callback structure and upcall#10390
CORE & PROV: Add a generic callback structure and upcall#10390levitte wants to merge 3 commits intoopenssl:masterfrom
Conversation
It contains only one function, which should only get added to non-FIPS providers.
The idea is that libcrypto code will be able to build up the structure it needs for any callback, pass that to the provider where the provider will accept it, and the provider passes that back along with an OSSL_PARAM array with data from the provider to the core_generic_callback upcall, which knows how to handle the passed structure. The structure is very simple, and includes a function pointer to a libcrypto callback, as well as a pointer to some libcrypto data. That libcrypto callback needs to know what to do with that data to call the application callback with the arguments it requires. This method permits the provider to call application callbacks without having any information of what signature they have, and caters to those who may want to build a provider module into a secure environment where all the calls simply can't be any random function call, but rather must be in form of up- or downcalls in the libcrypto<->provider interface.
This is a very simple change, just make sure to get the function, and add general provider support for calling it.
|
For the curious, the way this is structured is inspired from |
|
For what it's worth, I don't like this very much, for a couple of reasons. In no particular order
I would prefer "upcall" to be added as a new primitive OSSL_PARAM type. The provider would know the calling sequence and return value of what the upcall returns, if anything. It would have to be documented. Anything other than a simple int return would need careful handling to avoid heap issues. |
It depends, actually. The
I you look, you'll see this in /* Typedef in openssl/core.h */
struct ossl_callback_st {
int (*callback)(const OSSL_PARAM params[], void *arg);
void *arg;
};See the
Er...... you really need to study that structure a little better.
And therein lies the issue that I see, that the provider must know all those things that may be internal, or depend on the application (if we choose to pass functions directly), which also potentially means that applications must know the provider's exact expectations. |
|
Another thing that we don't talk much about but that I factor in is that there are architectures out there that provide secure environment with a very sharp edge between the application environment and the secure environment, and basically need to have all calls between the two as dispatches along the boundary between the two (i.e. downcalls and upcalls). In such an environment, a call from the secure environment directly into a random spot in application space is absolutely forbidden. So imagine someone wanting to place, say, the FIPS module in such a secure environment while keeping libcrypto in application space. If we allow passing random function pointers, and even worse, calling them across the application<->secure environment boundary with no chance of control on the boundary, then we're making that kind of job impossibly hard. |
Which is what this generic callback adds :) My #10390 (comment) would require documentation of the calling sequence (and not said, but implied) semantics of the upcalls. BN_GENCB is not generic, it's called for keygen whenever big numbers are generated. It just so happens that this is important in crypto, and therefore called from RSA, etc. But like i said, that's just because BN is involved. How would this mechanism be used for Having one single function which then dispatches (if it knows how to do so) is less secure. It requires the application to trust non-FIPS code. It is safer to put the "BN_GENCB callback" function in the FIPS module call that "generates a BN" and then have that validated code do an upcall. A FIPS application might, or might not, want to do that. Having a generic upcall receiver in the non-validated crypto code is a security issue. A single upcall wrapping all possible upcalls is wrong. Invidiual provider operations that could make an upcall should specify the upcall (and a void*) at the time the function is invoked. |
Er...... the application calls libcrypto functions. That's non-FIPS code... so I don't quite understand how this particular bit of libcrypto code is suddenly less trustable than the rest... |
|
Either way, documentation! You're quite correct, I do need to add that. |
|
Another way to see this is simply that |
Yes, and I think that is the wrong approach. Providers can know the exact parameters and make the call directly. They shouldn't have to wrap it up as PARAMS and then call a generic upcall-dispatch function to get to the right call in the application. |
Not doing this requires that both ends must share exact function signature knowledge, which may be quite internal. We wanted to avoid exactly that. Also, such direct calls is directly harmful in the secure environment scenario I talked about in #10390 (comment) |
|
|
||
| int ossl_prov_callback_from_dispatch(const OSSL_DISPATCH *fns) | ||
| { | ||
| for (; fns->function_id != 0; fns++) { |
There was a problem hiding this comment.
I find it kind of strange that you loop over all the dispatch functions twice. Once here, and once in defltprov.c/fipsprov.c/legacyprov.c
There was a problem hiding this comment.
Yeah well... it's essentially to avoid copying the same set of cases all over the place (and potentially getting it wrong somewhere). It's quite possible that we should just have a central generic get-me-everything type of call and be done with it. I can certainly make such a thing, question is if that should happen in this PR or in a separate one... it's kind of off-topic for this one...
|
I do not see how a callback can be internal. More importantly, I do not see why a callback should be internal. This is NOT the same as "hide details of an RSA key" This is "I will call this function when generating a BIGNUM" Why does that last part need to have details obscured? As for direct calls being harmful, I believe you are wrong. (This is a stronger statement than I disagree.) From a security perspective trusted->untrusted is the same as trusted->runtime->untrusted. Because the runtime is not inside the trust boundary. In fact, it is less secure than the one-step call because the runtime is now involved in EVERY CALLBACK which it doesn't have to be. |
|
As for internal callbacks, see #10391 to see an example. |
|
As for secure environments, you and I may not talk about the same thing. |
|
If you have an SGX provider, you'll need some runtime to go with that, and that can do the dispatch as such if needed; I don't see a need to make every upcall go through a runtime dispatcher. And if someone is trusting SGX to be secure, they should need to do more research :( |
That means that every application that sets up a callback will have to do that.
Do note that using However, I will certainly use this mechanism in code to come, unless someone is absolutely adamant at forcing my hand. |
No. Pass in the function pointer when you set up the rest of the parameters. And then the provider does an upcall to your function. I just cannot understand why you think provider->runtime->application is better than just provider->application. I guess I'm just not seeing something that you do. |
Please remain in context, that I said was for an SGX environment, where your "No" is simply not correct. |
|
No, my no is correct :) http://www.sgx101.com/portfolio/enclave_lifecycle/ an SGX ocall maps exactly to what I am proposing: a per-operation function callback without going through a generic runtime dispatcher. |
|
[sigh] From the page you linked to:
Which part of the "calling by proxy function" tells you that you can make a direct call from inside the trusted environment into some random spot in the application? |
|
Yes the SXG lib does it. Not the openssl runtime. |
Not automagically. The application will have to actively set up that proxy function, and somehow, the module will have to know to call that (via its own proxy function). |
|
It's automatic in the same way RPCGEN or IDL is automatic. It generates stubs that are ocall-specific and that are linked into your enclave code and your application. This is different from the single re-dispatch at runtime proposed here. |
|
I'll need to think on this one after reading it more deeply. Call backs should know what call back that are -- I don't think this should be a param, it could be an argument (which leaves one big switch statement to decode the reason) or it could be multiple distinct call backs. I'm not obviously seeing which way is the intention here. |
Why would that be necessary? |
|
I want a seperate optional callback that I can test if it is NULL in the fips provider. If it is null then this optional callback will not try to call back into the app. A single generic callback is not a good way of doing this. |
|
@slontis... with this PR, the pointer to a But, do note that I'm no trying to force your hand. If you want your very own callback specifically for FIPS self test stuff, by all means do. There's also a difference between that stuff and the stuff I deal with... yours is a global kind of callback, that an application sets and then forgets about, which mine are callbacks that must be considered unique every time they are passed (such as the callback for |
|
So here's another thing I was thinking of this morning; there's an objective with the libcrypto<->provider interface that we don't mention very often and maybe should mention more, and that's compatibility, both upward and downward. In other words, providers should not have to become sensitive to the libcrypto version. So then, let's say that we do allow direct calls from the provider into an application callback, using a callback signature that we have defined. typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx);Obviously, libcrypto will have to pass that callback pointer to the appropriate provider side function, along with the pointer to the Along comes an update, a breaking change, where that callback gains another argument. That means that in the libcrypto<->provider interface, a new FUNC number and associated downcall will have to be added, and dealt with. The way this PR defines things, the exact function signature remains with libcrypto, and can be handled in such a way that of an older provider doesn't pass some data, libcrypto can apply a "I dunno" default, and if a provider made for a newer libcrypto passes some extra unknown param, it will simply be ignored. So at the very least, I remain convinced of this way of doing things for stuff I do. |
|
It was stated old providers work with new code, not that new providers work with old runtimes; is that now a requirement? Either way, the way to handle it is simple: you treat the provider-initiated upcall/callback, the same as any other callback -- changing the argument list is a change in the ABI.
Consistency across the code base is important. |
This benefit comes from using OSSL_PARAMs to pass data between libcrypto and a provider - it doesn't come from having a single upcall between the provider and libcrypto. We could equally have a separate callback everytime we need one that uses OSSL_PARAMs to pass the data and it would have the same end result. In fact we're going to need to pass OSSL_PARAM type data between libcrypto and providers in any case, where we have to pass complex data back and forth. For example if an end user callback expects a BIGNUM, then the providers will not understand that datatype. Therefore we're going to have to pass OSSL_PARAM data and there will have to be some translation callback in libcrypto which converts the OSSL_PARAM data understood by the providers into callbacks used by end applications. |
Looking at the diversity of callback types we have, "consistency" isn't something that comes to my mind. |
|
So what I hear from you, @mattcaswell, is that instead of passing a typedef int (*OSSL_CALLBACK_fn)(const OSSL_PARAM params[], void *arg);and have the provider functions take that plus a void arg pointer... so something like this for key generation: OSSL_CORE_MAKE_FUNC(void *, OP_keymgmt_genkey,
(void *genctx, void *domparams, OSSL_CALLBACK_fn *cb, void *cbarg)) |
It has been mentioned in passing more than made an actual requirement... but something we have said is that the providers shouldn't need to care about versions, they should just offer functionality, and use whatever upcalls they can get. By consequence, running a newer provider with an older libcrypto should be perfectly valid, as long as the necessary up- and down-calls are present. |
That is not what i meant. You said "I am going to do this" Even if the rest of the code goes another way, such as by using per-function upcalls. |
|
I think if the project wants to make new providers work with old runtimes, they should vote and say that explicitly. |
|
I've heard all argument, and am shifting to specify a "standard" callback signature, define like this: typedef int (*OSSL_CALLBACK)(const OSSL_PARAM params[], void *arg);With the expectation that provider side functions that want to use it take a pointer to that type as well as a void argument that's simply passed to the callback function. |
|
(this will become a very small PR) UPDATE: or I'll make a separate PR |
|
Thanks for adapting part of the way. Next question: why is a provider->app code upcall different form, say OpenSSL malloc upcalls? |
|
libcrypto<->provider interface stabilty. The callbacks in the public API are ad-hoc in nature so far. There's no reason why the providers should be exposed to that when they can use a much more generic interface and leave it to libcrypto to translate as needed to the application callbacks. I see it as entirely possible to expose the applications directly to this generic callback form, but that lies in the future. |
|
The reason is compile-time type safety. This is C, not a duck-typing language like Python or perl or php. you refer to the current callbacks as ad hoc, I prefer to call them specific. This PR, while better, still treats C like a scripting language because of the misplaced concern that things will evolve. Sigh. maybe someone else shares my view. |
|
Side note: I've been reminded that the compatibility objective is a requirement, and has been voiced during earlier meetings. |
|
Old providers work with new code, yes. New providers (using new callbacks) work with old code, was not discussed. That is either compatible or an API/ABI break, just like if someone changes the calling sequence (or semantics) of an existing callback, like the X509 verify callback. |
Dude, I take offence at that statement. If you look at OpenSSL's history, from real old DEPRECATEDIN_0_9_8(RSA *RSA_generate_key(int bits, unsigned long e,
void (*callback) (int, int, void *),
void *cb_arg))... through an attempt to deal with evolution (the int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);struct bn_gencb_st {
unsigned int ver; /* To handle binary (in)compatibility */
void *arg; /* callback-specific data */
union {
/* if (ver==1) - handles old style callbacks */
void (*cb_1) (int, int, void *);
/* if (ver==2) - new callback style */
int (*cb_2) (int, int, BN_GENCB *);
} cb;
};... and all the way to the typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx);
void EVP_PKEY_CTX_set_cb(EVP_PKEY_CTX *ctx, EVP_PKEY_gen_cb *cb);So er, evolution has already happened... and a generic callback that takes an My concern is having the libcrypto<->provider getting stuck by the public API. We've had that problem before, and you might remember that making structures opaque, i.e. hiding the inner workings from the public eye was our way out of being stuck. |
|
All that being said, are we still discussing the I plan on closing this PR fairly soon anyway. |
|
Since #10412 just got approved, it's time to close this one. |
|
Sorry for offending. My concern is that losing type-safety to handle evolution is a misplaced concern. Oh well. I lost. |
The idea is that libcrypto code will be able to build up the structure
it needs for any callback, pass that to the provider where the provider
will accept it, and the provider passes that back along with an
OSSL_PARAM array with data from the provider to the core_generic_callback
upcall, which knows how to handle the passed structure.
The structure is very simple, and includes a function pointer to a
libcrypto callback, as well as a pointer to some libcrypto data. That
libcrypto callback needs to know what to do with that data to call the
application callback with the arguments it requires.
This method permits the provider to call application callbacks without
having any information of what signature they have, and caters to those
who may want to build a provider module into a secure environment where
all the calls simply can't be any random function call, but rather must
be in form of up- or downcalls in the libcrypto<->provider interface.
On the provider side, support is added, and is fairly minimal