Expand description
rsasl
is the Rust SASL framework designed to make supporting SASL in protocols and doing SASL
authentication in application code simple and safe.
§Important note for users of this crate:
Please do check out the documentation for CHANGELOG. The module
documentation is a render of the CHANGELOG.md
distributed with rsasl. It contains
information about added features, bugfixes and other code changes in the different released
versions.
Reading the changelog can help you decide the minimum version of rsasl you need to depend on and will inform you of any bugfixes that may introduce backwards-incompatible changes.
§SASL primer
you can safely skip this section if you know your way around SASL and just want to know how to use this library.
SASL (RFC 4422) was designed to separate authentication from the remaining protocol implementation, both server- and client-side. The goal was to make authentication pluggable and more modular, so that a new protocol doesn’t have to re-invent authentication from scratch and could instead rely on existing implementations of just the authentication part.
SASL implements this by abstracting all authentication into so called ‘mechanisms’ that
authenticate in a number of steps
, with each step consisting of some amount of data being
sent from the client to the server and from the server to the client.
This data is explicitly opaque to the outer protocol (e.g. SMTP, IMAP, …). The protocol only
needs to define a way to transport this data to the respective other end. The way this is done
differs between protocols, but the format of the authentication data is always the same for any
given mechanism, so any mechanism can work with any protocol out of the box.
One of the best known mechanism for example, PLAIN
, always transports the username and
password separated by singular NULL bytes. Yet the very same plain authentication using the
username “username” and password “secret” looks very different in different protocols:
IMAP:
S: * OK IMAP4rev1 Service Ready
C: D0 CAPABILITY
S: * CAPABILITY IMAP4 IMAP4rev1 AUTH=GSSAPI AUTH=PLAIN
S: D0 OK CAPABILITY completed.
C: D1 AUTHENTICATE PLAIN
S: +
C: AHVzZXJuYW1lAHNlY3JldAo=
S: D1 OK AUTHENTICATE completed
SMTP:
S: 220 smtp.example.com Simple Mail Transfer Service Ready
C: EHLO client.example.org
S: 250-smtp.server.com Hello client.example.org
S: 250-SIZE 1000000
S: 250 AUTH GSSAPI PLAIN
C: AUTH PLAIN
S: 334
C: AHVzZXJuYW1lAHNlY3JldAo=
S: 235 2.7.0 Authentication successful
XMPP:
S: <stream:features>
S: <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
S: <mechanism>GSSAPI</mechanism>
S: <mechanism>PLAIN</mechanism>
S: </mechanisms>
S: </stream:features>
C: <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>
C: AHVzZXJuYW1lAHNlY3JldAo=
C: </auth>
But the inner authentication data (here base64 encoded as AHVzZXJuYW1lAHNlY3JldAo=
)
is always the same.
This modularity becomes an even bigger advantage when combined with cryptographically strong authentication (like SCRAM) or single-sign-on technologies (like OpenID Connect or Kerberos). Instead of every protocol and their implementations having to juggle cryptographic proofs or figure out the latest SSO mechanism by themselves they can share their implementations.
Of course a client or server application for those protocols still has to worry about authentication and authorization on some level. Which is why rsasl is designed to make authentication pluggable and enable middleware-style protocol crates that are entirely authentication-agnostic, deferring the details entirely to their downstream users.
§Where to start
There are three different use-cases rsasl provides for:
- I’m implementing a client or server for a protocol and I need to support SASL authentication
- I’m using a such a client or server and I need to configure my credentials
- I need to implement a custom SASL mechanism
§Protocol Implementations
The starting point of rsasl for protocol implementations is the
SASLConfig
struct.
This struct is created by the downstream user of the protocol crate and contains all
required configuration and data to select mechanism and authenticate using them in an opaque
and storable way.
The SASLConfig
type is designed to be long-lived and to be valid for multiple contexts and
authentication exchanges.
To start an authentication a SASLClient
or
SASLServer
is constructed from this config, allowing a
protocol crate to provide additional, context-specific, data.
The produced SASLClient
/ SASLServer
are then of course also context-specific and usually
not readily reusable, for example channel bindings are specific to a single TLS session.
Thus a new SASLClient
or SASLServer
must be constructed for every connection.
To finally start the authentication exchange itself a Session
is
constructed by having the SASLClient
or SASLServer
select the best mechanism using the
SASLClient::start_suggested
or
SASLServer::start_suggested
methods respectively.
On the resulting session the methods Session::step
or
Session::step64
are called until
State::Finished
is returned:
use rsasl::prelude::*;
// the `config` is provided by the user of this crate. The `writer` is a stand-in for sending
// data to other side of the authentication exchange.
fn sasl_authenticate(config: Arc<SASLConfig>, writer: &mut impl io::Write) {
let sasl = SASLClient::new(config);
// These would normally be provided via the protocol in question
let offered_mechs = &[Mechname::parse(b"PLAIN").unwrap(), Mechname::parse(b"GSSAPI").unwrap()];
// select the best offered mechanism that the user enabled in the `config`
let mut session = sasl.start_suggested(offered_mechs).expect("no shared mechanisms");
// Access to the name of the selected mechanism
let selected_mechanism = session.get_mechname();
let mut data: Option<Vec<u8>> = None;
// Which side needs to send the first bit of data depends on the mechanism
if !session.are_we_first() {
// Tell the other side which mechanism we selected and receive initial auth data
data = Some(get_initial_auth_data(selected_mechanism));
} else {
// If we *are* going first we still need to inform the other party of the selected
// mechanism. Many protocols allow sending the selected mechanism with the initial
// batch of data, but we're not doing that here for simplicities sake.
tell_other_side_which(selected_mechanism);
}
// stepping the authentication exchange to completion
while {
// each call to step writes the generated auth data into the provided writer.
// Normally this data would then have to be sent to the other party, but this goes
// beyond the scope of this example
let state = session.step(data.as_deref(), writer).expect("step errored!");
// returns `true` if step needs to be called again with another batch of data
state.is_running()
} {
// While we aren't finished, receive more data from the other party
data = get_more_auth_data()
}
// Wohoo, we're done!
// rsasl can in most cases not tell if the authentication was successful, this would have
// to be checked in a protocol-specific way.
}
After an authentication exchange has finished Session::validation
may be used in server contexts to extract a Validation
type from the authentication exchange.
Further details about Validation
can be found in the validate
module documentation.
To minimize dependencies protocol implementations should always depend on rsasl with the least amount of features enabled:
[dependencies]
rsasl = { version = "2", default-features = false, features = ["provider"]}
or, if they need base64 support:
[dependencies]
rsasl = { version = "2", default-features = false, features = ["provider_base64"]}
This makes use of feature unification to make rsasl a (near-)zero dependency crate. All decisions about compiled-in mechanism support and other features are put into the hand of the final user.
Specifically when depended on this way rsasl does not compile code for any mechanism or most of the selection internals, minimizing the compile-time and code size impact. Re-enabling required mechanisms and selection system is deferred to the user of the protocol implementation who will have their own dependency on rsasl if they want to make use of SASL authentication.
To this end a protocol crate should not re-export anything from the rsasl crate! Doing so may lead to a situation where users can’t use any mechanisms since they only depend on rsasl via a transient dependency that has no mechanism features enabled.
§Application Code
Applications wanting to perform authentication start by constructing a
SASLConfig
.
This config selects the mechanisms that will be available, the priority of mechanisms, and sets up provisions for any required authentication data (such the username, password, etc).
Authentication data is queried using Property
s and
SessionCallback
. The property
module documentation
contains additional details on the use of Property
.
Mechanisms will generally request a different set of properties.
The documentation for each mechanism will list the required properties and any additional
limitations that may be put on their values. Currently no discovery of queryable properties is
done, so it is the responsibility of the application code resp. the SASLConfig
to limit
mechanisms to only those where all properties can be provided.
Additionally, on the server side, SessionCallback
s can implement the
SessionCallback::validate
method to return data
from the authentication exchange to the protocol implementation, e.g. to return the user that
was just authenticated. Details for this use-case are found in the validate
module
documentation.
In addition to selecting mechanisms at runtime, available mechanisms can also be limited at
compile time using feature flags. The default feature flags enable all IANA-registered
mechanisms in COMMON
use that are implemented by rsasl. See the module documentation for
mechanisms
for further details on how to en-/disable mechanisms.
§Custom Mechanisms
NOTE: The system to add custom mechanisms is in flux and does not observe the same stability guarantees as the rest of the crate. Breaking changes in this system may happen even for minor changes of the crate version.
Custom mechanism implementations are possible but not stable as of rsasl 2.0.0
. To
implement a custom mechanism implementation the feature unstable_custom_mechanism
must be
enabled.
A custom mechanism must implement the trait Authentication
and
define a Mechanism
struct describing the implemented mechanism.
Documentation about how to add a custom mechanism is found in the registry module documentation
.
Modules§
- User-provided callbacks
- Configuration supplied by the downstream user
- Modules purely for documentation
- Mechanism traits only available with feature
unstable_custom_mechanism
- SASL Mechanism support
- Utilities for handling and validating names of Mechanisms
- prelude exporting the most commonly used types
- Type-safe queryable values
- Mechanism registry only available with feature
unstable_custom_mechanism
- Test utilities for rsasl
- Extracting information from authentication exchanges