|
Using SASL: CMU's Cyrus SASL Libraryby Marshall T. Rose, author of BEEP: The Definitive Guide04/30/2002 |
The Simple Authentication and Security Layer (SASL) allows administrators to configure just the right amount of security for the networked applications in their environments. A previous article explained SASL; in this article, we see how to program with SASL using an open source library.
In a previous article, we took a look at a "meta API" called the Simple Authentication and Security Layer (SASL). The idea behind SASL is that when you design an application protocol, you can treat the communications security technologies as loadable modules.
SASL shifts the burden of picking and choosing the technologies used for authentication, integrity checking, and privacy from you, the application writer, to the network administrator.
For example, depending on the environment, some administrators may choose to configure authentication and integrity-checking, but no privacy. As an application writer, your job isn't to make decisions like this -- instead, your job is to provide the network administrator with maximal flexibility to pick the appropriate level of security for the job at hand.
Once you decide to use SASL, your next choice is whether to roll your own SASL implementation or to use an existing library. Speaking as someone who's done both, I'll tell you that while it may be necessary -- in very constrained environments -- to write your own SASL layer, it's almost always better to use an off-the-shelf library.
In this article, we'll look at Carnegie Mellon University's (CMU) Cyrus SASL. This is the SASL library used by sendmail, OpenLDAP, and the Cyrus ACAP and IMAP servers. The native library is written in the C programming language, and it also comes with a Java interface. (In addition, for you Tcl aficionados, you can find a Tcl extension to Cyrus SASL in the beepcore.tcl project on SourceForge.)
|
Related Reading
BEEP: The Definitive Guide |
There are two major versions of Cyrus SASL:
For the rest of this article, we'll be talking about Cyrus SASLv2. Also, just to set expectations, this article doesn't give detailed API information -- it's an overview, not a reference manual, for Cyrus SASL.
All of the Cyrus SASL releases can be found
here,
so start by looking for a distribution named cyrus-sasl-2.*.tar.gz.
On my production systems,
I use Cyrus SASL v2.1.2,
but on my development system,
I use anonymous CVS to stay in sync with the Cyrus developers.
Once you've extracted the tar image,
do the two usual things:
look at the README file and then run configure --help to
determine which configuration options are available.
You might also want to browse doc/install.html for some tips on
where to find the libraries that Cyrus SASL likes.
After configure successfully runs,
it's just a make, su, make install,
and we're ready to go.
The procedure calls that "do SASL" are pretty simple, even if the work they do is somewhat complex. However, before you actually "do SASL," you have to setup some data structures so that Cyrus SASL will interface correctly to your program and your environment. There are two of these "universal" data structures.
The first is the callback data structure.
When Cyrus SASL is in the middle of doing something and it needs more
information,
it consults a callback.
Each callback has three fields:
SASL_CB_PASS);
void * that's passed to the procedure.
The parameter list for the procedure depends on the value of the identifier -- each callback has a different parameter list. (Because of the way C handles indirect procedure calls, you must be very careful to make sure that the procedure you define provides the exact parameter list expected by Cyrus SASL -- if not, there will be trouble, and there's nothing that Cyrus SASL can do about it.)
Cyrus SASL has a lot of callbacks, and although some are "generic," most are either server-only or client-only. Fortunately, you can pretty much get by with defining only two:
SASL_CB_LOG,
which is called to write an entry to your program's logfile;
and,
SASL_CB_GETOPT,
which is called to get configuration information.
For example,
if your program has its own config file,
the procedure you point to in the callback should look at your
program's config file for SASL-specific information.
Cyrus SASL gives you two opportunities to define your callback list.
The first is when you initialize the library,
and the second is whenever you establish a network connection.
This leads us to the second data structure,
called a context, which keeps track of the SASL state associated with a network connection,
from cradle to grave.
The first step is to call the routine that initializes Cyrus SASL and pass it your global callback list. Cyrus SASL actually provides two initialization routines, one for client-side and the other for server-side behavior. Based on how your program is going to behave, feel free to call just one or both.
The second step is to make an outgoing connection and then create a client context for it. Now at this point your program has to do a bit of thinking about whether it should negotiate the use of TLS. (After you do SASL, it's too late to start TLS.) How you make this choice is up to you, but here are some things to consider:
Regardless, at this point you tell Cyrus SASL the actual and desired security level of the connection, expressed as an integer. Typical values are 0 (nothing), 1 (integrity-checking only) 56, 112, and 128. Excluding zero and one, it's easiest to think of this as the number of bits in the session key being used for privacy.
Now comes the fun part -- you call a routine that takes the SASL mechanisms advertised by the server, and it tells you:
(Or, if your requirements aren't compatible with what the server offers, you'll get back an error return instead.) The key thing to understand here is the division of labor: your program is responsible for moving the bits back and forth, and Cyrus SASL is responsible for figuring out what bits are sent and interpreting whatever bits are sent back.
Although this sounds straightforward, there's a little bit of ugliness we have to digress into. Some protocols don't let the client supply initial data to the server when they ask to start a SASL mechanism. For example, SMTP allows initial data, but IMAP doesn't. When you call the routine to start the client negotiation, you tell Cyrus SASL whether initial data is allowed.
Let's look at an example. Say you're writing an SMTP client and you want to do SASL stuff. When you connect to an SMTP server:
S: 220 example.com ESMTP server ready
C: EHLO rubble.com
S: 250-example.com yabba, dabba, doo!
S: 250-AUTH ANONYMOUS CRAM-MD5 DIGEST-MD5 OTP
S: 250-STARTTLS
S: 250 HELP
The server ("S:") will identify itself with a 220 greeting,
your program,
the client ("C:"),
sends an EHLO command to find out what services
are offered,
and the server says it supports SASL with four mechanisms (AUTH ...),
TLS (STARTTLS),
and the help command.
Now your program has to decide whether to do TLS,
since the server advertised it.
If so,
your program tells the server to start TLS,
and ultimately your program gets back another 220 greeting,
and sends another EHLO.
(Obviously, it's a bit more complicated than this -- check out RFC 3207
for all the details.)
Regardless of whether you're doing TLS,
your program creates a client context for Cyrus SASL,
and sets the actual and desired security properties for that context.
Next,
your program strips the 250-AUTH from the server's response and
tells Cyrus SASL that you're ready to do SASL.
The routine that decides what your program should tell the server is pretty complicated. First, it has to determine which available SASL mechanisms (termed "plug-ins") meet your security requirements and are supported by the server. Then it tries to initialize them in order of preference. During this process, the plug-in may need additional information from your program (e.g., a username or a password). This is where one or more client-side callbacks are invoked. This is also where you'll find the most complexity in the Cyrus SASL client-side interface.
|
Earlier, you might recall that a callback was defined as having a procedure (that your program supplies) that gets invoked when Cyrus SASL needs some information. The only weakness in this approach is if you need to ask the user several related questions for the same transaction -- e.g., "what is your username and password?" To get around this, Cyrus SASL lets you specify a special "interaction" callback, which gets invoked with a bunch of questions at once. When the callback procedure is invoked, it can construct whatever kind of dialog is appropriate to get whatever information is needed, without having to keep going back to the user.
So,
what's the callback procedure do?
Well,
that's entirely up to you -- maybe it gets the necessary information from the command line,
a configuration file, or a database; or maybe it just prompts the user.
In practice,
the answer is often different depending on what Cyrus SASL needs to know.
(Take a look at the file doc/options.html for a list of the
configuration options that Cyrus SASL and its plug-ins look for.)
OK, so Cyrus SASL has decided which plug-ins meet everyone's requirements, and has found the one that has all the information it needs to run. It's smooth sailing from here on out:
The only "tricky" part in this loop is at Step 4 where you may have to call Cyrus SASL if the server returns "success." The reason is that some protocols are able to pass back data and indicate success at the same time. (A lot of times this is used for mutual authentication -- the server authenticates itself to the client.) If this is the case with your protocol, you have to tell Cyrus SASL and invoke Step 4 on success.
Let's continue with using SMTP as an example. Your program tells Cyrus SASL that it's ready to start (we've already dealt with the TLS issue and created a client context). Cyrus SASL tells your program:
OTP mechanism; and
The data in a SASL exchange is binary, so SMTP says you always have to base64-encode it.
So this is what your program, the client, sends to the server and what the server sends back in return (Steps 1 and 2, above):
C: AUTH OTP AGJsb2NrbWFzdGVy
S: 334 b3RwLXNoYTEgOTk5NyBwaXh5bWlzYXM4NTgwNSBleHQ=
Since your program didn't get back "error,"
Step 3 doesn't apply;
but Step 4 does apply,
so your program undoes the base64-encoding and gives the result to
Cyrus SASL,
which gives back some new data and indicates that another round-trip
is needed.
Since the server is waiting for more data (334),
everything's in sync (Step 5),
and Step 6 doesn't apply.
So,
Step 7 says to go back to Step 1.
This time, the exchange looks like this:
C: d29yZDpmZXJuIGhhbmcgYnJvdyBib25nIGhlcmQgdG9n
S: 235
The 235 response from the server indicates "success," so Step 3 doesn't apply.
The server didn't supply any more data for Step 4
(but you still call Cyrus SASL because SMTP can pass back data on success),
which doesn't return any data,
and indicates that you should be done (Step 5).
This is what the server said (Step 6),
so your program has successfully authenticated itself and we're
done.
Before using Cyrus SASL in your server, you need to think about how each plug-in is going to validate the authentication information provided by a remote user. (Note that we didn't use terms like "password" here -- the plug-in may not be given a password, it may get a response to a challenge or some other bit of security trickery!) The answer, of course, is that Cyrus SASL will use a callback to figure this out. However, instead of requiring that the network administrator write a customized callback procedure, Cyrus SASL provides a couple of easy options.
To begin, you probably have some kind of a password system already running in your enterprise. For example, if you're already running Kerberos version 4 or 5, then the right thing just happens -- providing, of course, that the client requests the use of the KERBEROS_V4 or GSSAPI mechanism.
Otherwise,
you probably have something that takes a username and a password
and comes back with either a "yes" or "no."
Cyrus SASL comes with a program called saslauthd that knows about
several different mechanisms,
such as password files, shadow password files, DCE, PAM, SIA, and so on.
(The reason Cyrus SASL provides this as a separate program is so that your server doesn't have to run as root;
instead,
it talks to saslauthd using a named pipe.)
Perhaps the most amusing mechanism is "remote IMAP," where saslauthd will take the username and password it's given and
use that information to try to login to an IMAP server.
Finally, Cyrus SASL has its own authentication database. There are a couple of reasons why you might want to go down this path:
Cyrus SASL provides another program,
saslpasswd,
that lets the administrator create, modify, or remove SASL-login accounts.
So, how do you tell Cyrus SASL which option to select? The easiest way is to create a Cyrus SASL configuration file for your Server -- when your program initializes the library, one of the arguments it provides is used to determine the name of the appropriate configuration file. The other way is to define a callback to get configuration information.
Now that we've gotten the configuration story out of the way, let's look at how it comes together. Server-side code tends to be more event-driven than procedural, but the duality with the client-side is obvious.
The first step is to call the routine that initializes Cyrus SASL and pass it your global callback list. Then, whenever you get an incoming connection, your program creates a server context for it. Your program also remembers that it's in the "unauthenticated" state.
Next, whenever your program, the server, is in the unauthenticated state and the client asks for a list of available mechanisms, you tell Cyrus SASL the actual and desired security level for this connection, and ask it what mechanisms are available. Then, your program, the server, tells the client what Cyrus SASL said:
Similarly, whenever your program is in the "authentication in-progress" state, and the client sends back more SASL information, your program gives the information to Cyrus SASL. Then, your program tells the client what Cyrus SASL state:
Once you're in the authenticated state, you can ask Cyrus SASL to tell you what username the client is authorized as -- this lets your program apply whatever authorization policy is appropriate. For example, a client that authenticates as "mrose" probably has a different set of permissions than one that authenticates as "postmaster". SASL's job is to handle the authentication; the authorization part is up to you.
This leads us to one of the more clever things SASL does. It distinguishes between:
In Cyrus SASL,
these are referred to as the authid and authzid,
respectively.
Thus far,
everything we've talked about deals with authentication,
not authorization.
However,
when a client authenticates,
it optionally provides an authorization as well.
In that case,
in addition to all the other authentication-specific activities,
Cyrus SASL will invoke a server-only callback asking if the authid
is allowed to act on behalf of the authzid.
Your program uses its configuration information to decide the answer.
For example,
the mail server will probably allow "postmaster" to do things on
behalf of "mrose";
however,
the backup server probably will not.
The nice part,
of course,
is that once you're in the authenticated state and you ask Cyrus
SASL to tell you what username the client is authorized as,
it returns the authzid (if provided).
So your authorization code works transparently.
Regardless of whether you're doing client-side or server-side SASL, after authentication is complete, you should check the actual security level for the connection. If Cyrus SASL negotiated the use of either integrity-checking or encryption, then instead of reading/writing data directly to the network, you call a routine that applies the appropriate security transformations.
For example, perhaps the client authenticated with DIGEST-MD5 and integrity-checking was enabled. In that case, whenever the client or server receives data from the network, it calls a Cyrus SASL routine that verifies the checksum, and returns the actual data (or returns an error if there's evidence of tampering). Similarly, before doing any writes to the network, both the client and server call a Cyrus SASL routine to put a security wrapper around the data.
Cyrus SASL is a well-architected piece of software that offers a lot of integration flexibility. In this article, we've really just talked about the "vanilla" usage scenarios. There are several interesting features that Cyrus SASL supports that we've skipped over.
For example:
Fortunately,
Cyrus SASL comes with a lot of documentation for both administrators
and programmers -- start your browser at doc/index.html.
Marshall T. Rose is the prime mover of the BEEP Protocol. In his former position as the Internet Engineering Task Force (IETF) area director for network management, he was one of a dozen individuals who oversaw the Internet’s standardization process.
Copyright © 2007 O'Reilly Media, Inc.