-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
net: Prototype a TLS convenience API based on sockets #5900
Comments
So, there could be 2 approaches:
I'd lean towards 2, because going route 1 unlikely to save much, but doing adhoc things is much less "interesting". So, the idea of 2 would be to introduce a concept of generic "stream" object, with corresponding interface of operations. Implementation of that will be polymorphic per a specific stream type, and backed by a typical "vtable" of function (method) pointers. A stream object would start with a pointer to such a vtable (shared by all instances of a stream of particular type), followed by type-specific data. Right, poor-man's C++. I'm not sure if we'll want The operations of the stream interface would be:
I'd like to specifically draw attention to the importance of flush() operation. This isn't usually considered with native sockets, but would be crucial for "userspace" objects like we discuss here to get proper data transfer semantics. Actually, one of my biggest criticisms of TLS support implementation in net_app - that there's a "bookkeeping" thread per TLS connection - seems to be routed in the lack of flush operation, so the purpose of that thread is to flush app's data on timeout. Why? App can tell itself when data needs to go down the wire. (Maybe there're other uses of that thread; my point is that I implemented TLS support without extra threads, and it worked.) There's also a close() method, for convenience, but of course, there's no open() or similar method in the stream interface, by the same reason that there're no virtual constructors in C++ - when you create an object, you always create a specific type of object, and then perform polymorphic operations on it. Finally, there're 2 types of stream objects - "leaf" streams, whose constructors accept params to create a stream, or at least to convert object of another type into a stream, and "wrappers", which take another stream as a param, and apply "transformation" on it. A socket stream would be an example of 1st kind. TLS would be example of 2nd. This is part of "choice 2, make it generic and reusable" scenario. For example, we could serve HTTP over SPI - why not, just write a stream implementation for SPI. Also, we could serve HTTP over TLS over SPI, nothing new would need to be written "over TLS" part will be the same as for a socket stream. |
@GAnthony, @jukkar, @tbursztyka, @mike-scott, @mbolivar : Comments/criticism welcome. |
@d3zd3z adding David to this as well |
An interesting issue in regard to stream concept is poll() support for them. There can be 2 approaches:
|
@d3zd3z pointed at: https://www.openbsd.org/papers/linuxconfau2017-libtls/#slide-0 https://github.com/libressl-portable/openbsd/tree/master/src/lib/libtls Something to look at for ideas. |
Thanks! I'd be interested to see the results, but I'm afraid my input isn't likely to be useful. |
Ok, I assume that at least means you don't see anything terribly wrong with it ;-). |
Keeping in mind the need to accommodate the secure socket offloading, I'd like to share some links which were shared in an earlier internal email thread:
Even if we decide not to use socket-like APIs for TLS setup the way TI does, the main point here is that, though the Zephyr NET_OFFLOAD solution would allow secure socket data transmission to be offloaded, the setup (at least for SimpleLink) must occur via these setsockopt calls, which would probably need to be hooked from this new proposed "TLS convenience API". For that reason, I think the connection/configuration APIs should be included in the scope of this proposal, similar to what is done here: |
A couple comments on creating a new "stream" API:
That's OK, but we should realize, if that's the case, it's a much larger scope than making a "TLS convenience layer" ;-)
|
Let me quick reply to these so far:
No, we don't argue all that, at once. The arguments are actually layered:
A key point is that 1) (sockets API) is "one for everyone out there", while 2) (Zephyr's TLS API-on-top-of-sockets) is a just a particular implementation of a TLS API, among many available. They're layered like that, and sufficiently independent, e.g. someone may use another TLS API, or another socket implementation (e.g. offloaded) with the proposed TLS API. |
Simple answer: they won't. The stream interface abstracts bidirectional peer-to-peer communication in terms of bytes (vs e.g. in terms of records). Socket interface is superset of stream interface, but we don't define it. That means that a server will be implemented in terms of plain sockets, and once connection is accepted, only then in will be wrapped with TLS (that's of course how it works anyway, the point is that we don't try to abstract that in any way).
Nope, because they aren't "communication in terms of bytes". UDP would be "communication in terms of records (datagrams)", but I'd need to do more homework on DTLS. One concern, as pointed by @d3zd3z is that DTLS-enabled protocols are/seems to have tighter coupling between layers. We may need to define a separate "interface" for UDP/DTLS. As for RAW_SOCK, IIRC, they allow to send full IP packets (with headers, etc.). How that would map onto (D)TLS is unclear, I never heard about that. If/when someone formulates the requirements how that's supposed to work, it will be yet another, separate usecase. |
Sorry, missed to answer this one:
Conceptually and semantically yes, more specifically, I think we should follow POSIX I/O model, with read/write operations and their properties (like short read and short write semantics). However, it apparently would be far-fetched to impose interface outlined here ("streams vtable") on all device drivers. At the very least, besides "stream of bytes" model, there's another model - "stream of records" with its own semantics differences. Trying to devise "grand unified interface" (implementational interface) for everything will lead us to bloat, e.g. https://en.wikipedia.org/wiki/STREAMS . I hope we go in the opposite direction - reuse existing experiences and best practices to design an interface scaled down for a bare minimum required to implement a specific functionality adequately. |
Right, so if TI API allows to create a plain socket and then convert it to TLS via setsockopt() calls, then this proposal covers such an implementation. We probably would need to go further C++ way, and add a bit of RTTI, in the shape of e.g. ->get_type(), to allow different stream types to differentiate each other (for full generality). The flow this proposals entails is something like:
So, as long as make_tls_stream() recognizes passed-in stream as backed by TLS-offloadable socket, it can configure it as need.
So, as I mentioned during today's discussion (on Linaro side for context), let's keep scope of the current work manageable. Let's remember that our immediate aim of this work is "how to make socket-based protocol implementations which can work with both plain and TLS sockets with as close to 100% code reuse as possible". So, we can and will "prototype" TLS connection/configuration API, but can't "standardize" on all aspects of it (in the timeframe posed, which is to have a working proof of concept in 1 month) - not without close and parallel involvement of security people, but even then I doubt we can "standardize" on all aspects of it (like e.g. offloading handling). |
http://www.ti.com/lit/an/swra509a/swra509a.pdf, section 2.2.6, hints that's possible, but the user must explicitly re-invoke accept() or connect() to upgrade security. It's not the normal mode of operation. But why would that be necessary? Wouldn't the IP protocol normally already know that it intends to deal with a secure socket, and create such a socket from the outset? (I know there are exceptions to the rule, but why not design for the expected case?). It seems the client must know it will be a secure socket, because it's calling So, the socket could be created with something like Note: From the POSIX standard on socket(): "The protocols supported by the system are implementation-defined." |
That's not my reading of that document:
"Irrespective" should give enough hints, but let's call it non-conclusive still. http://www.ti.com/lit/ug/swru455d/swru455d.pdf section 6.5.7 gives more specific information. It's still a bit confusing, because it start with talking about STARTTLS which is application protocol level feature, but following should clarify it:
(Assuming "client HELLO" is TLS' ClientHello: https://en.wikipedia.org/wiki/Transport_Layer_Security#Basic_TLS_handshake) So, TI SimpleLink should support normal socket vs TLS layering (while provides convenience API to do it all in one go). |
Responding to that literally, IP protocol doesn't know anything about secure sockets, it just carries opaque data.
Right, so it's the higher(est) level which has (can have) the complete vision of what happens on the lower levels. And yes, we could have
Yes, but how sockets got popular is that there's common, portable (sub)set of protocol which work on (almost) every system. There's no PROTOCOL_SEC_SOCKET on other systems beyond TI SimpleLink, so it doesn't make sense to base the design on it. Or we'll really have a contradiction, saying "sockets is standard API, it allows for portability across systems", and as the next step add non-standard, non-portable thing to its implementation. As I tried to elaborate above, I propose to clearly split layers: sockets don't belong to us, we just implement them as faithfully as reasonably possible with our constraints. Above sockets, the space belongs to us, and we implement things there to best suit our needs (which are apparently generality and minimal code size). |
Looks like it might work. Proof will be in testing it out. |
Result of first iteration on implementation was posted as #5985 , which should give better look&feel of the proposed API. What was not discussed in this ticket is allocation matters, but of course, they surface in the real usage. The basic is that space for a particular type of stream object is allocated by client, and there's an initializer which takes an allocated pointer. But for server use, we'd need to deal with array of objects. It's tempting to leave that to client still, but would need to see how cumbersome that looks when a server apps is converted, that's next in queue. |
The Wikipedia page on TLS has an interesting comment about why this API is hard to do:
In addition, Zephyr is likely to run into additional complexity because certificates and certificate chains are often too heavy to implement in a constrained device, and it is necessary to use other ciphersuites, such as PSK or bare pub/priv keys. |
As far as allocation, the underlying cipher, esp the asymmetric algorithms used for certificate management expect there to be an implementation of malloc/free. mbed TLS includes an implementation (that in my experience is significantly better than the tiny one included in Zephyr). Needless to say, though, that determining how much space to give to this is going to be an issue to setting up TLS/DTLS. |
Per our recent discussion adding @pfl as an assignee |
AFAIK this is already completed. |
As discussed during Zephyr Networking Forum meetings, mentioned in #5854 , etc., currently there're not even examples, far less best practices, on using TLS communication over BSD Sockets. (The same applies to DTLS, but this ticket is dedicated to TLS specifically.)
This ticket is intended to bootstrap/track this work, and establish requirements for implementation.
The text was updated successfully, but these errors were encountered: