[swift-server-dev] Draft proposal for TLS Service APIs (please review)

Gelareh Taban gtaban at us.ibm.com
Tue Apr 4 11:32:47 CDT 2017


Hi Brent,

I have some answers in line.

I think perhaps there is misunderstanding about the role of
TransportManager (perhaps naming is the problem after all!). This is not a
singleton type of manager - rather it is a per connection manager and it
"manages" for example the system socket calls. An instance of transport
manager handles one connection. An instance of transport manager has one
TLS delegate associated with that connection. Therefore, the delegate is
associated with only one connection at a time.

If we assume the above, then it is not hard to see that once a context is
created for a specific TLS connection, and stored by the TLS service
object, the context can be passed between the various OpenSSL or Secure
Transport calls.
With OpenSSL, we set the connection fd say to the socket pointer.
In Secure Transport, we implement connection read, write callback functions
that read/write from the socket.

Hopefully that makes more sense!

gelareh





From:	Brent Royal-Gordon <brent at architechies.com>
To:	Gelareh Taban/Austin/IBM at IBMUS
Cc:	Bill Abt/Cambridge/IBM at IBMUS, swift-server-dev at swift.org
Date:	04/04/2017 07:06 AM
Subject:	Re: [swift-server-dev] Draft proposal for TLS Service APIs
            (please review)



      On Apr 3, 2017, at 12:40 PM, Gelareh Taban <gtaban at us.ibm.com> wrote:



      Hi Brent,
      The answers are in line.
      gelareh


Thank you for your detailed answers. I'm going to try to synthesize your
answers about the lifecycle into a single description of how the TLSService
is used. For clarity, I'll use the names from the proposal, even when we've
discussed changing the names.

      Some high-level part of the application configures a
      TLSService-conforming instance and conveys it through several layers
      to the transport management layer. At this point, the TLSService
      knows the certificates, domains, security settings, etc. it should
      use, but does not know whether it will be handling the client side or
      the server side of a connection.

      Time passes. Eventually, the transport manager looks at its
      configuration and figures out whether it's a client or a server.

      If it is a client, it:

            Calls `onClientCreate()`.

            Attempts to establish one or more TCP connections to other
            hosts.

            For each connection it successfully establishes, creates a
            `TransportManagementDelegate` and passes it as the parameter to
            `onConnect(IORef:)`.

            Goes to step 5 below.

      If it is a server, it:

            Calls `onServerCreate()`.

            Starts listening for TCP connections from other hosts.

            For each connection it receives and accepts, creates a
            `TransportManagementDelegate` and passes it as the parameter to
            `onAccept(IORef:)`.

            Goes to step 5 below.

      It enters a "connected" state where:

            After it receives data from the connection, it allocates an
            empty buffer and passes it to `onReceive(buffer:bufSize:)`.
            `onReceive(buffer:bufSize:)` reads bytes from the network from
            [where?], interpreting TLS protocol messages and extracting
            whatever plaintext is present in them, using state from
            [where?] to distinguish between this connection and other
            simultaneous connections. `onReceive(buffer:bufSize:)` then
            writes the extracted plaintext to the buffer and returns.


            [g] Assume IORef is a socket pointer. If using OpenSSL, the SSL
            socket is created using SSL_set_fd() and from then on,
            onReceive, once data is received on the socket, it is extracted
            and decrypted from the socket by OpenSSL using SSL_read and
            copied into buffer. This is all handled by OpenSSL, for a given
            context that is passed between the various OpenSSL calls for a
            specific connection.

            Before it sends data to the connection, it puts that data in a
            buffer and passes it to `onSend(buffer:bufSize:)`. `onSend
            (buffer:bufSize:)` processes the data, converting it to
            ciphertext and adding TLS protocol messages, using state from
            [where?] to distinguish between this connection and other
            simultaneous connections. `onSend(buffer:bufSize:)` then [does
            what?] with the TLS protocol data to cause it to be sent and
            returns. `onSend(buffer:bufSize:)` can indicate that the
            connection should be closed by returning 0.


            [g] SImilar to above, once a given context is created
            associated with an SSL socket, then SSL_write  would obtain
            data from buffer, and encrypt and pass to connection to send.

            If the connection is closed. either because the peer closed it
            or because `onSend(buffer:bufSize:)` indicated it should be
            closed, it calls `onDestroy()`. `onDestroy()` tears down the
            SSL engine's state for the connection, using state from
            [where?] to distinguish between this connection and other
            simultaneous connections.

      Eventually, the transport manager may stop listening or shut down
      entirely. The TLSService gets no signal that this has happened.

First of all, if any of the above is inaccurate, please correct me.

Assuming I haven't misunderstood any of the above in a material way: As you
can see, there are a number of holes in my understanding. I would like your
help in filling them in.

      Since this is to be used for a server, I assume that the transport
      manager may have several TLS connections open to different clients at
      the same time. However, I don't see a means for the TLSService to
      know which of these connections the transport manager is currently
      talking about. Only the `onAccept(IORef:)` and `onConnect(IORef:)`
      calls are passed some kind of connection object, so how do `onReceive
      (buffer:bufSize:)`, `onSend(buffer:bufSize:)`, and `onDestroy()` know
      which connection is experiencing the event in question?


      [g] once socket is passed, a context is created and stored by that
      instance of TLS service which is then passed to other
      OpenSSL/SecureTranport calls.
      https://github.com/IBM-Swift/BlueSSLService/blob/master/Sources/SSLService.swift#L308
        for OpenSSL



      There are a few possibilities I can think of, but none of them really
      fit my understanding of the design:

            The transport manager makes sure that there is only one TLS
            connection open at a time—it accepts one connection, handles it
            from start to finish with the help of the TLSService, and then
            accepts the next connection and begins handling it. `onAccept
            (IORef:)` and `onConnection(IORef:)` should store the `IORef`
            parameter into a property on `self`, and `onSend`, `onReceive`,
            and `onDestroy` should use that property later.

            The transport manager handles many TLS connections at a time.
            At the beginning of a connection, it copies the TLSService
            instance [how?] and uses the copy for that connection.
            `onAccept(IORef:)` and `onConnection(IORef:)` should store the
            `IORef` parameter into a property on `self`, and `onSend`,
            `onReceive`, and `onDestroy` should use that property later.

            The transport manager handles many TLS connections at a time.
            At the beginning of a connection, it asks higher layers in the
            stack to make a new TLSService instance [how?] and uses the new
            instance for that connection. `onAccept(IORef:)` and
            `onConnection(IORef:)` should store the `IORef` parameter into
            a property on `self`, and `onSend`, `onReceive`, and
            `onDestroy` should use that property later.

            The transport manager handles many TLS connections at a time,
            up to the number of TLSService instances provided by higher
            layers [how?] to use as a pool of TLSService instances. At the
            beginning of a connection, it chooses an unused TLSService
            instance from its pool and assigns it to the connection.
            `onAccept(IORef:)` and `onConnection(IORef:)` should store the
            `IORef` parameter into a property on `self`, and `onSend`,
            `onReceive`, and `onDestroy` should use that property later.

            The transport manager handles many TLS connections at a time
            using a single TLSService instance. Thus, a TLSService instance
            may receive calls concerning many different connections, all
            interleaved with one another. `onAccept(IORef:)` and
            `onConnection(IORef:)` know which connection to use because
            it's passed in their `IORef` parameter; `onSend`, `onReceive`,
            and `onDestroy` know [how?].

      How does `onReceive(buffer:bufSize:)` get the data that was received
      from the network? Does it call some not-yet-specified method on the
      `IORef` that fetches data to act upon, or does it use some other
      mechanism?

      What does `onSend(buffer:bufSize:)` do with the data to cause it to
      be sent across the network? Does it call some not-yet-specified
      method on the `IORef` that sends data over the network, or does it
      use some other mechanism?

I think that, once I have these final questions locked down, I'll be able
to understand what's being proposed here, and I can make more detailed
suggestions about names and similar issues.

Thank you for your patience with me on this!


                  4.1 - TLS service protocol

                  The TLS service protocol describes the methods that the
                  transport layer calls to handle transport-level events
                  for the TLS service object.



      I have a bunch of questions about the design you're presenting, and I
      think many of them ultimately stem from not understanding some of the
      high-level aspects of the proposal. For instance:

      > * What types conform to this protocol? From the diagram, it looks
      like there's a type for each "engine"—a SecureTransportService, an
      OpenSSLService, etc.—and each instance represents a particular
      configuration of that engine. So you create a WhateverTLSService,
      configure it, and then hand it off (through several layers) to the
      transport management layer, which calls methods on it to handle
      various events. The transport management layer then uses that one
      TLSService to handle many connections. Is that correct?


      [g]
      More or less. As long as the implementation conforms to the protocol,
      it can use whatever underlying security library it wants. This way we
      can have a plug and play architecture which allows the user to pick
      the security library implementation of its choice (eg. LibreSSL,
      OpenSSL, etc)


      > * What is the lifecycle of a connection? Does the TLSService create
      them itself, does the transport management layer create them and hand
      them off, or does the transport management layer retain control over
      them from beginning to end?


      [g] The connection life cycle is handled by levels higher than TLS.
      For all intents and purposes, the transport management layers owns a
      TLS service delegate which calls the appropriate TLS-related
      functionality at the right time (eg. at socket creation time, at
      connection time, etc).


      > * You discuss the higher layers creating a TLSService object and
      caching it in a property, then ultimately handing it down to the
      transport management layer, which then attaches it to socket objects.
      But presumably you can have many socket objects, possibly
      simultaneously. Are they all served by a single TLSService instance,
      or by many? If they share a TLSService, how does the TLSService know
      which socket is talking to it at a given moment? If they have
      separate ones, how does the transport management layer acquire a new
      one when it needs it?

      [g]
      Each socket instance would have its own TLS service delegate -- this
      is important because each socket might have its own specific TLS
      channel properties.


      > You mention that this proposal is very small in scope, and it's
      fine to describe some of these details in general ways. For instance,
      you don't need to describe the interface to a Swift socket or the
      transport layer in detail. But currently, the description of these
      surrounding systems is *so* vague that I'm struggling to assess this
      design.

      Perhaps some of these details have been described in other documents
      or meetings; if so, they really need to be presented in this
      document, too.

      [g] fair enough. The problem is that right now in the servers working
      group, we have not defined *any* of our interfaces. This is the first
      proposal and we are trying to only pin the things which this service
      requires.
      In any case, if you think other things should be included, please let
      us know.

                  - onClientCreate
      > Why do these methods all have "on" prefixes? I'm not totally sure I
      understand the intended usage here, but I see two possibilities:

      > * These are imperative commands. `onAccept` says that the TLS
      engine should accept a connection, `onSend` means it should send some
      data, etc. In that case, these should not have any prefix—they should
      just be `accept`, `send`, etc.

      > * These are essentially delegate methods notifying the TLS engine
      of an event. `onAccept` says that the system has accepted a
      connection and the TLS engine should do what it needs to do with it,
      `onSend` means the system is about to send data and it needs the TLS
      engine to modify it, etc. If so, Swift APIs more often use words like
      `should`, `will`, or `did` than `on`, particularly since they're more
      precise about the timing of the delegate method compared to the
      actual event.

      > In either case, I don't think "on" is the best naming for these. It
      needlessly bucks platform conventions.

      [g]
      The latter description is the intended. Your points are fair and
      perhaps we can modify the delegate method names to, perhaps:

      onClientCreate --> didClientCreate
      onServerCreate --> didServerCreate
      onDestroy --> willDestroy
      onAccept --> didAccept(on connection: TransportManagementDelegate)
      onConnect --> didConnect(on connection: TransportManagementDelegate)
      onSend --> willSend(with data: UnsafeRawPointer, dataSize: Int))
      onReceive --> willReceive(with data: UnsafeRawPointer, dataSize:
      Int))
                  This will be called when a client I/O connection is
                  created and appropriate TLS connection needs to be
                  configured, including context creation, the handshake and
                  connection verification.

                  This is a client only method.

                  ///
                  /// Setup the contexts and process the TLSService
                  configurations (certificates, etc)
                  ///

                  func onClientCreate() throws


                  - onServerCreate

                  This will be called when a server I/O connection is
                  created and appropriate TLS connection needs to be setup,
                  including context creation, the handshake and connection
                  verification.

                  This is a server only method.

                  ///
                  /// Setup the contexts and process the TLSService
                  configurations (certificates, etc)
                  ///

                  func onServerCreate() throws


      > What are these methods supposed to do, exactly?

      > * Do they put `self` into either a client state or a server state?
      If so, what happens if you call both, or neither, or call one twice?
      Would it be better to do this as part of initialization, or to have
      them make a client TLS object or server TLS object, or to require
      whatever code hands the TLSService to the TransportManager to
      pre-configure it as either client or server?


      [g]
      They setup the TLS contexts and process the certificates. They do put
      self in either a client or server state via associated parameter sets
      in OpenSSL and SecureTransport.
      (I am pretty sure) if you call them multiple times, they simply over
      write the previous setting. But we can test this during
      implementation.

      These methods are essentially initialization that gets called when
      the TransportManager decides it has enough information to set it as
      client or server.


      > * Do they create a new instance that's either a client or a server?
      If so, how do they return it?
      > * Do they configure something recently created as either client or
      server? If so, how do they access whatever they need to configure?

      > Basically, what state are these supposed to operate upon?

      [g]
      Most of the functionality is the same for client or server except for
      setting the server/client flag/function in the underlying security
      library. The instance is stored in the delegate.

                  - onDestroy

                  This will be called when an I/O instance connection is
                  closed and any remaining TLS context needs to be
                  destroyed.

                  This is both a client and server method.

                  ///
                  /// Destroy any remaining contexts
                  ///
                  func onDestroy()
      > Is this called at the end of each connection, or is it called once
      when the transport management layer is totally finished with the
      TLSService, or are these the same thing?

      > If per-connection, how does it know which connection?

      > If during destruction, should we just class-constrain and use
      `deinit` for this purpose?

      [g] this is per-connection and the TLS service would keep a context
      for the connection.
      So the transport manager has a delegate instance pointing to this TLS
      service instance which itself has a context.
                  - onAccept

                  This will be called once an I/O instance connection has
                  been accepted, to setup the TLS connection, do the
                  handshake and connection verification.

                  This is both a client and server method.

                  ///
                  /// Processing on acceptance from a listening connection
                  ///
                  ///
                  /// - Parameter IORef: The connected I/O instance
                  ///
                  func onAccept(IORef: TransportManagementDelegate) throws
      > I take it the parameter is some sort of abstraction wrapping e.g. a
      socket. So why is it called a `TransportManagementDelegate`?
      Shouldn't its name include words like `Connection` or `Socket` or
      `IOHandle` or something?


      [g] we can simply call it ConnectionDelegate. I was trying to be
      consistent in my terminology.

      > Do we want the parameter to be labeled `IORef`? That's not very
      idiomatic; it doesn't read well or follow the Swift naming
      guidelines.

      [g] great feedback. Thanks!


      > You say this is for both clients and servers. When does a TLS
      client have a listening connection that it `accept`s connections on?

      Is it called at different times or in different ways than
      `onServerCreate`?

      [g] Bad copy/paste! for both onConnect and onAccept. They are
      obviously server and client specific.
                  - onConnect

                  This will be called once a socket connection has been
                  made, to setup the TLS connection, do the handshake and
                  connection verification.

                  This is both a client and server method.

                  ///
                  /// Processing on connection to a listening connection
                  ///
                  /// - Parameter connectionRef: The connected I/O instance
                  ///
                  func onConnect(IORef: TransportManagementDelegate) throws
      The same as above, with appropriate substitutions.
                  - onSend

                  This will be called when data is to be written to an I/O
                  instance. The input data buffer is written to the TLS
                  connection associated with that I/O instance.

                  This is both a client and server method.

                  ///
                  /// Low level writer
                  ///
                  /// - Parameters:
                  /// - buffer: Buffer pointer
                  /// - bufSize: Size of the buffer
                  ///
                  /// - Returns the number of bytes written. Zero indicates
                  TLS shutdown, less than zero indicates error.
                  ///
                  func onSend(buffer: UnsafeRawPointer, bufSize: Int)
                  throws -> Int
      > Is there a reason you use an UnsafeRawPointer and a buffer size,
      instead of using an UnsafeRawBufferPointer which would encapsulate
      both?


      [g] it was pointed our rightly by others that it's not good to have
      either of them :-) Right now, we are looking at have Data and more
      efficient versions.




      > Why is shutdown indicated with zero, rather than the return value
      being Optional and being nil? Why are errors signaled with negative
      values instead of being thrown? (Or are you saying that negative
      returns are invalid? That's different from saying "indicates error".)

      [g] This is actually implementation details which shouldnt have been
      included in this pitch in the first place.


      > If a TLSService return less than `bufSize`, will the enclosing
      later try to.re-send the remaining data in subsequent calls?

      This sounds like the TLS engine owns the network connection (at least
      by this point) and is responsible for writing to it. Does that mean
      `accept` and `connect` take ownership of the connection and hold on
      to it? If you have several different simultaneous connections, how do
      you know which connection this should write to? Or does a given
      TLSService only own one connection at a time? If so, does the
      transport management layer create a new TLSService instance for each
      connection? How? If each TLSService is bound to one connection,
      shouldn't it be created already knowing the connection it's going to
      use?

      [g] I believe I have talked about the relationship between
      connections and TLS service.
      BlueSSLService implements a very similar protocol and you can get
      more information on it here:
      https://github.com/IBM-Swift/BlueSSLService/blob/master/Sources/SSLService.swift




                  - onReceive

                  This will be called when data is to be read from an I/O
                  instance. Encrypted data is read from TLS connection
                  associated with that I/O instance and decrypted and
                  written to the buffer passed in.

                  This is both a client and server method.

                  ///
                  /// Low level reader
                  ///
                  /// - Parameters:
                  /// - buffer: Buffer pointer
                  /// - bufSize: Size of the buffer
                  ///
                  /// - Returns the number of bytes read. Zero indicates
                  TLS shutdown, less than zero indicates error.
                  ///
                  func onReceive(buffer: UnsafeMutableRawPointer, bufSize:
                  Int) throws -> Int
      > If I understand correctly, `buffer` is an uninitialized memory
      region that the type should fill with data. Is that correct?

      Otherwise, the same as above, with appropriate substitutions.
                  5 - Non-goals

                  This proposal:

                  1- DOES NOT describe the TLS service configuration, which
                  includes information on certificate types, formats and
                  chains, cipher suites, etc. We expect this to be
                  specified in a future proposal.

                  2- DOES NOT describe the TLS service trust policies,
                  which define trust and validation policies of the
                  incoming connection. We expect this to be specified in a
                  future proposal.

                  3- DOES NOT describe the interface between the TLS
                  service and the transport layer and any dependencies. We
                  expect this to be specified in a future proposal.
      > I feel like #3 in particular really hurts this proposal. It's
      impossible to evaluate this without at least a general idea of how
      the TLS service and the transport layer communicate. It's okay to
      handwave the details—for instance, you could say "Type X represents a
      network connection, and has methods to read, write, and close it",
      without describing those methods in detail—but without at least an
      overview of how this will be used, it's very difficult to evaluate.

      I think this is probably a good design that just isn't being
      explained very clearly. I hope you can clarify some of these points.

      --
      Brent Royal-Gordon
      Architechies



      <graycol.gif>Brent Royal-Gordon ---04/01/2017 01:15:32 AM---> On Mar
      20, 2017, at 1:04 PM, Gelareh Taban via swift-server-dev <
      swift-server-dev at swift.org> wrote

      From: Brent Royal-Gordon <brent at architechies.com>
      To: Gelareh Taban/Austin/IBM at IBMUS
      Cc: swift-server-dev at swift.org, Bill Abt/Cambridge/IBM at IBMUS
      Date: 04/01/2017 01:15 AM
      Subject: Re: [swift-server-dev] Draft proposal for TLS Service APIs
      (please review)


                  On Mar 20, 2017, at 1:04 PM, Gelareh Taban via
                  swift-server-dev <swift-server-dev at swift.org> wrote:


                  4.1 - TLS service protocol

                  The TLS service protocol describes the methods that the
                  transport layer calls to handle transport-level events
                  for the TLS service object.



      I have a bunch of questions about the design you're presenting, and I
      think many of them ultimately stem from not understanding some of the
      high-level aspects of the proposal. For instance:

      * What types conform to this protocol? From the diagram, it looks
      like there's a type for each "engine"—a SecureTransportService, an
      OpenSSLService, etc.—and each instance represents a particular
      configuration of that engine. So you create a WhateverTLSService,
      configure it, and then hand it off (through several layers) to the
      transport management layer, which calls methods on it to handle
      various events. The transport management layer then uses that one
      TLSService to handle many connections. Is that correct?

      * What is the lifecycle of a connection? Does the TLSService create
      them itself, does the transport management layer create them and hand
      them off, or does the transport management layer retain control over
      them from beginning to end?

      * You discuss the higher layers creating a TLSService object and
      caching it in a property, then ultimately handing it down to the
      transport management layer, which then attaches it to socket objects.
      But presumably you can have many socket objects, possibly
      simultaneously. Are they all served by a single TLSService instance,
      or by many? If they share a TLSService, how does the TLSService know
      which socket is talking to it at a given moment? If they have
      separate ones, how does the transport management layer acquire a new
      one when it needs it?

      You mention that this proposal is very small in scope, and it's fine
      to describe some of these details in general ways. For instance, you
      don't need to describe the interface to a Swift socket or the
      transport layer in detail. But currently, the description of these
      surrounding systems is *so* vague that I'm struggling to assess this
      design.

      Perhaps some of these details have been described in other documents
      or meetings; if so, they really need to be presented in this
      document, too.
                  - onClientCreate
      Why do these methods all have "on" prefixes? I'm not totally sure I
      understand the intended usage here, but I see two possibilities:

      * These are imperative commands. `onAccept` says that the TLS engine
      should accept a connection, `onSend` means it should send some data,
      etc. In that case, these should not have any prefix—they should just
      be `accept`, `send`, etc.

      * These are essentially delegate methods notifying the TLS engine of
      an event. `onAccept` says that the system has accepted a connection
      and the TLS engine should do what it needs to do with it, `onSend`
      means the system is about to send data and it needs the TLS engine to
      modify it, etc. If so, Swift APIs more often use words like `should`,
      `will`, or `did` than `on`, particularly since they're more precise
      about the timing of the delegate method compared to the actual event.

      In either case, I don't think "on" is the best naming for these. It
      needlessly bucks platform conventions.
                  This will be called when a client I/O connection is
                  created and appropriate TLS connection needs to be
                  configured, including context creation, the handshake and
                  connection verification.

                  This is a client only method.

                  ///
                  /// Setup the contexts and process the TLSService
                  configurations (certificates, etc)
                  ///

                  func onClientCreate() throws


                  - onServerCreate

                  This will be called when a server I/O connection is
                  created and appropriate TLS connection needs to be setup,
                  including context creation, the handshake and connection
                  verification.

                  This is a server only method.

                  ///
                  /// Setup the contexts and process the TLSService
                  configurations (certificates, etc)
                  ///

                  func onServerCreate() throws


      What are these methods supposed to do, exactly?

      * Do they put `self` into either a client state or a server state? If
      so, what happens if you call both, or neither, or call one twice?
      Would it be better to do this as part of initialization, or to have
      them make a client TLS object or server TLS object, or to require
      whatever code hands the TLSService to the TransportManager to
      pre-configure it as either client or server?

      * Do they create a new instance that's either a client or a server?
      If so, how do they return it?

      * Do they configure something recently created as either client or
      server? If so, how do they access whatever they need to configure?

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-server-dev/attachments/20170404/65e66a3f/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: graycol.gif
Type: image/gif
Size: 105 bytes
Desc: not available
URL: <https://lists.swift.org/pipermail/swift-server-dev/attachments/20170404/65e66a3f/attachment.gif>


More information about the swift-server-dev mailing list