FWIW, I think we mostly agree but are attacking the general issue from different angles.
On Wed, 2008-03-05 at 10:44 -0500, Thor Lancelot Simon wrote: > Well, I did not suggest that one would ever use a file descriptor per > operation. But, let me try again. > > Note first that that OpenSSL does not abstract I/O completion waiting > (the API provides SSL_read(), SSL_write(), SSL_connect(), SSL_shutdown(), > but *not* SSL_poll() or SSL_select). When I proposed correcting this, > last year, I got a pretty strongly negative reception. I was not involved in the discussion so am not familiar with the arguments. However I agree that SSL_[poll|select]() would be unwise because it presumes to make the SSL/TLS stack transport-aware, whereas the BIO scheme is an honest (if sometimes inelegant) attempt to leave the stack transport-agnostic. SSL/TLS can be fed data on all sides purely from memory, and the real "transport" could come from lots of non-fd-based sources. Eg. if you somehow didn't trust multi-user windows (though we all know windows is 100% trustworthy so this is obviously an outlandish proposition ...) then you could theoretically use SSL/TLS to encapsulate message passing - ie. the ability to watch IPC would not reveal the contents of that IPC. Hmm, I can just about smell the presence of a numbskull who'll try to build a DRM scheme around this ... Just as the SSL user needs to "know" transport specifics to create BIOs in the first place for hooking up to the SSL object, they also need to "know" how (and on what) to wait when they see WANT_READ/WANT_WRITE codes and so-forth. I think the same would essentially be true for the use of asynchronous crypto. The point is to keep openssl agnostic, not the application. (Now I've probably offended a vast array of library fascists who will exclaim that libraries are supposed to "hide the problem" rather than "hiding themselves from the problem" - to which I can only say, (1) this is security, not ODBC-for-javascript, and (2) there is a world beyond POSIX, hard to imagine as that may be.) > Note next that there are already a nontrivial number of nontrivial > applications using the existing non-blocking select()/poll() driven API > to OpenSSL, despite its warts. This API *already* requires, quite > explicitly, extracting the file descriptor from each SSL session and > adding those descriptors to a select() or poll() set. Applications are > already written to this API and it does not abstract completion waiting > as you suggest it should (I happen to agree with you, but neither you > nor I can adjust all of everyone else's code out there in the world!). Actually I don't claim that it should abstract completion waiting, only that the SSL/TLS API shouldn't. For one thing, async crypto will be a libcrypto interface - not an SSL/TLS-specific interface. Just as BIOs are a libcrypto interface. > This API also *already* requires any operations that would block to be > reissued at the SSL layer by retrying exactly the same operation again > after the file descriptor for that session comes up ready with select > or poll. Sure, but that's because you are *not* suspended miles down a code path that you need to find your way back to. It means the state-machine has advanced as far as it can go and to advance further, you will need the presence of either input on your read BIO or some drainage on your write BIO. Those events are on the exterior of the SSL/TLS state-machine and represent a stable state, the code-paths exit cleanly in that state and when progress is possible, we will go down new code paths to do new work. This is not the same as trying to pick up where you left off way down a code-path that has not been designed with suspend-and-retry semantics in mind. Eg. if you were calling SSL_read() when you got a WANT_WRITE, but you then called SSL_write() - it should *not* crash. What you're describing, about being able to replay your way down a prior code-path, would be very hard to make robust in an equivalent way. NB, this would be different if the SSL/TLS code had, since the beginning, the idea of a third BIO-like dependency - ie. if the state-machine was designed to interact with a read BIO, a write BIO, and a crypto-interconnect. If that were the case, WANT_CRYPTO would already exist, just like WANT_READ and WANT_WRITE. But the code is not like that and making it like that would amount (IMHO) to a rewrite. > I do not love this API, but it is what all the existing, non-threaded > non-blocking OpenSSL API consumers are written to, and I think it would > be a considerable mistake to change it now. I quite agree. > So, given my "note first" and "note second" I believe I have established > that, to do non-blocking I/O in a single-threaded application with OpenSSL, > one must already: > > 1) Dip beneath the SSL abstraction layer to get the file > descriptor for each session. No, this is one form it must be able to take, but this is not *the* general method for OpenSSL to support. > 2) Use select() or poll() to wait on all those file descriptors. During which time the SSL/TLS object (and "token" handling) is in *what* state exactly? > 3) Retry operations, exactly as issued prior to completion waiting, > when their sessions' descriptors come up ready in select or > poll (whether to select for read-ready or write-ready is known > by the WANT_READ or WANT_WRITE SSL errors returned when the > operation would block). I've been down this route before and the "retry" is where I lose interest in this approach. The tree of possible code-paths that *could* go asynchronous is frightening, and adapting them to return back up the call-stack cleanly in a "WANT_CRYPTO" manner, as well as going back down the call-stack to "retry" is at best going to be a usually-works hack. And if there's one place we don't want cut-corner hacks, it's in an SSL/TLS implementation. In other words, I've never seen CERT issue an security advisory on the basis of design insanity, but I don't want us pioneering in this field. > I propose, then, to extend the existing interface to accomodate non-blocking > operation of ENGINEs themselves, by: I see where you're heading, and some pieces of the idea are (IMHO) going to be part of any meaningful solution. But from a big-picture perspective, I think you're heading down a rat-hole with this approach. If we take openssl as it stands, and the considerable inertia associated with its existing user-base (a valid point you made earlier), I think the SSL/TLS implementation will just not let us go the way you're suggesting. And to your probably significant frustration, I'm not going to try and describe what I think we should do instead. At least, not in this email. I'm going to try and mock-up an overview of how I think openssl should address this and I will certainly run it by you for feedback. > Given the already existing API for non-blocking operation in OpenSSL, I > must confess I am pretty much baffled how else it _could_ work. I'll give you a clue, create a stack object, extend it with state about your current context, swapcontext(3) to the new stack (or whatever compiled-in equivalent your platform or mood provides for), call any SSL API you like, presume that leads down to crypto code that "wants" to go asynchronous, the async crypto code detects that async suspension has been made *available* (the stack object stuff), set appropriate suspension flags and swapcontext() back to the "user's" context, use the suspension flags to glue the "token" logic so the user can know when to go back to that SSL object, and when it's time, the retry is a swapcontext(3) (or sigsetjmp() - read Ralf's GNU-Pth docs and/or usenix paper if you want to know how these things work) straight back to the crypto code. Damn, there I go describing what I said I wouldn't describe yet. And trying to do so in one sentence has no doubt rendered it completely obscure. Anyway, the way to do this robustly with the existing openssl code, and do so without processing waste, is to leave the call-stack as-is when you are way down in the crypto code - you context-switch back out to suspend and context-switch back in to resume. The rest is details and having an interface that can port to unusual environments (I reiterate: not just file-descriptors). The "waiting mechanism" is really the detail of how you represent and convey that suspension between the caller/user and the the crypto code. Moreover, SSL/TLS is just one use-case of this mechanism, it's not SSL/TLS-specific, it's libcrypto-specific. More news when I have some. Cheers, Geoff ______________________________________________________________________ OpenSSL Project http://www.openssl.org Development Mailing List openssl-dev@openssl.org Automated List Manager [EMAIL PROTECTED]