The Qpid Proton C transport code is responsible for receiving and sending all the protocol bytes into and out of the Proton-C engine. It encapsulates all the protocol processing. Effectively turning the wire level protocol into more abstract AMQP model state changes and vice versa turning AMQP model state changes into wire level protocol to send.

The transport code is divided into 3 layers that divide up the protocol processing:

Loosely speaking, SSL is mostly associated with encrypting the connection, SASL with authenticating the connection, and of course AMQP carries the actual messaging protocol we're "really" interested in. However despite common knowledge, actually SSL and SASL can both do encryption and authentication of the connection. It's important to note that the SSL and SASL layers are optional in the sense that if authentication and/or encryption are not required then they need not be used - This will be covered in more detail later on.

In the current Proton codebase (0.8) the SSL layer is fairly comprehensively implemented (using the OpenSSL and Windows SChannel implementations) and allows access to both encryption and authentication (although SSL authentication is somewhat complex to set up and use).

Conversely the SASL layer is very rudimentary and essentially punts to the application to handle the SASL protocol interchange. The SASL code does have some built in capability to use the ANONYMOUS mechanism and some code to simplify implementing the PLAIN SASL protocol exchange. Using the Proton-C 0.9 SASL codebase requires the application to directly read the SASL protocol bytes from the peer process them and send raw protocol bytes back to the peer.

I have been re-implementing the SASL layer using the Cyrus-SASL library which supports many SASL mechanisms via a range of plugins (including ANONYMOUS, EXTERNAL, PLAIN, DIGEST-MD5, CRAM-MD5, GSSAPI etc.) and which is extensible to new SASL mechanisms by writing new mechanism plugins.

This work has led me re-evaluate some of the transport API as a whole as well as more specifically the SASL API which more directly needs reworking because of these changes.

Specifically I'd like to somewhat simplify using authentication and encryption with the transport by unifying the most commonly used concepts into the transport code and only requiring delving directly into the SSL/SASL APIs for more complex uses.

API Changes

From the authentication point of view the API flow is much simpler than before:

Instead of the Proton-C 0.9 situation where the application has to directly read and write AMQP SASL frames using pn_sasl_recv(), pn_sasl_send() etc. the meat of the protocol interchange happens behind the scenes; the application only has to set up authentication parameters, and then allow Proton-C to handle SASL without application intervention.

At the client the parameters that can be set up include username/password (with pn_connection_set_user/pn_connection_set_password() ), if necessary the SASL mechanisms used can be set (pn_sasl_allowed_mechs() ).

By default the server will adapt to whatever layers the client uses to communicate. But the server application can specify that the connection must be encrypted or authenticated (using pn_transport_require_encryption() and pn_transport_require_auth() ). Also if required the server application can force the SASL layer to use the ANONYMOUS mechanism or to exclude some mechanisms that might be installed on the system (by using pn_sasl_force_anonymous() or pn_sasl_exclude_mechs() ). The location and name of the configuration file used by the SASL implementation can be changed by using the pn_sasl_config_name() and pn_sasl_config_path() APIs.

The outcome of the authentication process at both server and client sides is indicated by a transport event. In the case of authentication failure it will be a PN_TRANSPORT_ERROR event, and the case of success it will a PN_TRANSPORT_AUTHENTICATED event. Once the authentication event has been received the server can discover who it is talking to by using the pn_transport_get_user() API. If required the mechanism used can be recovered using pn_sasl_get_mech().

Note that on the server you can also use the PN_CONNECTION_REMOTE_OPEN event to signal that authentication has succeeded if you don't need to deal with authentication by itself.

SASL

The biggest functional changes happen in the SASL layer code where the API has largely changed, very little backwards compatibility has been kept, because it is fairly clear that very few (if any) people have been using the current API to implement their own SASL mechanisms.

If this turns out to be untrue, then we could add some further measure of backwards compatibility as required.

Functions Removed

These functions were used to specify the SASL layer as either a client or server, they have been deprecated since functionality went into the transport code to specify whether it is an authentication client or server. Currently, on creation,  the SASL layer will determine from the transport whether it should be a server or a client. As there is a good deal of SASL churn in these current changes this is a good opportunity to remove these deprecated APIs.

These functions are the ones which support the client directly reading and writing the SASL protocol frames. They are not needed as this functionality has been removed and superseded by internal functionality.

These functionality here has been replaced by pn_sasl_exclude_mechs() and pn_sasl_get_mech().

This function is a helper function that constructs and sends a SASL PLAIN mechanism frame. As directly sending the frames is no longer the responsibility of the application it is not needed, however it is broadly replaced with pn_transport_set_user_password() which allows the client to set the authentication username and password.

This function is replaced with pn_transport_require_auth(pn_transport_t *transport, bool required) although the sense is reversed. By default anonymous connections are allowed. This is not really a change as previously there was no API guard against the SASL ANONYMOUS mechanism which is no better authenticated than not using SASL at all.

Functions Added

Transport

Functions Added

The default if this option is not set is to allow unauthenticated connections.

Connection

Functions Added

New Events

Authentication and Encryption Combinations

This table summarises the possible combinations of SSL and SASL use. The implicit assumption of the table is that there is no point in doing double encryption or double authentication so this is not captured in the table. Double encryption would be using both SSL and SASL to encrypt the connection and likewise double authentication would be using both for authentication.

Classically in protocols where SASL was not optional the way to avoid double authentication was to use the EXTERNAL SASL mechanism. With AMQP, SASL is optional, so if SSL is used for client authentication the SASL layer could be entirely omitted and so using EXTERNAL is not necessary.

Similarly in protocols where the SASL layer is not optional the ANONYMOUS mechanism is used when client authentication is not required. With AMQP as the SASL layer can just be entirely omitted, and the SSL layer if present used only for encryption (and server authentication).

↓Auth\Encryp→

NoneSSLSASL
None1.Privacy only 
SSL 2. 
SASLAuth only4.3.

In this table "auth" means client authentication.

The numbered boxes are the combinations that are reasonably important:

  1. Anonymous, "public" connection
  2. Encrypted connection with client certificate
  3. Kerberos or DIGEST-MD5
  4. Usually SSL encryption with PLAIN authentication.

Current State of Code

A substantial portion of this work is complete and ready to be included for the 0.10 Proton release (subject to code review of course). You can follow along with my changes by looking in my Github Proton repository:

https://github.com/astitcher/qpid-proton/commits/PROTON-334

The items of work that will remain to be completed are: