SASL is used by protocols, such as the Lightweight Directory Access Protocol, version 3 (LDAP v3), and the Internet Message Access Protocol, version 4 (IMAP v4) to enable pluggable authentication. Instead of hardwiring an authentication method into the protocol, LDAP v3 and IMAP v4 use SASL to perform authentication, thus enabling authentication via various SASL mechanisms.
There are a number of standard SASL mechanisms defined by the Internet community for various levels of security and deployment scenarios. These range from no security (e.g., anonymous authentication) to high security (e.g., Kerberos authentication) and levels in between.
The Java SASL API also allows developers to use their own, custom SASL mechanisms. SASL mechanisms are installed by using the Java Cryptography Architecture (JCA).
When compared with JSSE and Java GSS, SASL is relatively lightweight and is popular among more recent protocols. It also has the advantage that several popular, lightweight (in terms infrastructure support) SASL mechanisms have been defined. Primary JSSE and Java GSS mechanisms, on the other hand, have relatively heavyweight mechanisms that require more elaborate infrastructures (Public Key Infrastructure and Kerberos, respectively).
SASL, JSSE, and Java GSS are often used together. For example, a common pattern is for an application to use JSSE for establishing a secure channel, and to use SASL for client, username/password-based authentication. There are also SASL mechanisms layered on top of GSS-API mechanisms; one popular example is a SASL GSS-API/Kerberos v5 mechanism that is used with LDAP.
Except when defining and building protocols from scratch, often the biggest factor determining which API to use is the protocol definition. For example, LDAP and IMAP are defined to use SASL, so software related to these protocols should use the Java SASL API. When building Kerberos applications and services, the API to use is Java GSS. When building applications and services that use SSL/TLS as their protocol, the API to use is JSSE. See the Java Security Documentation for further details about when to use JSSE versus Java GSS.
SASL is a challenge-response protocol. The server issues a challenge to the client, and the client sends a response based on the challenge. This exchange continues until the server is satisfied and issues no further challenge. These challenges and responses are binary tokens of arbitrary length. The encapsulating protocol (such as LDAP or IMAP) specifies how these tokens are encoded and exchanged. For example, LDAP specifies how SASL tokens are encapsulated within LDAP bind requests and responses.The Java SASL API is modeled according to this style of interaction and usage. It has interfaces, SaslClient and SaslServer, that represent client-side and server-side mechanisms, respectively. The application interacts with the mechanisms via byte arrays that represent the challenges and responses. The server-side mechanism iterates, issuing challenges and processing responses, until it is satisfied, while the client-side mechanism iterates, evaluating challenges and issuing responses, until the server is satisfied. The application that is using the mechanism drives each iteration. That is, it extracts the challenge or response from a protocol packet and supplies it to the mechanism, and then puts the response or challenge returned by the mechanism into a protocol packet and sends it to the peer.
The Sasl class is used for creating instances of SaslClient and SaslServer. Here is an example of how an application creates a SASL client mechanism using a list of possible SASL mechanisms.
String[] mechanisms = new String[]{"DIGEST-MD5", "PLAIN"}; SaslClient sc = Sasl.createSaslClient(mechanisms, authzid, protocol, serverName, props, callbackHandler);
Based on the availability of the mechanisms supported by the platform and other configuration information provided via the parameters, the Java SASL framework selects one of the listed mechanisms and return an instance of SaslClient.
The name of the selected mechanism is usually transmitted to the server via the application protocol. Upon receiving the mechanism name, the server creates a corresponding SaslServer object to process client-sent responses. Here is an example of how the server would create an instance of SaslServer.
SaslServer ss = Sasl.createSaslServer(mechanism, protocol, myName, props, callbackHandler);
Because the Java SASL API is a general framework, it must be able to accommodate many different types of mechanisms. Each mechanism needs to be initialized with input and may need input to make progress. The API provides three means by which an application gives input to a mechanism.
Mechanisms can use the callbacks defined in the javax.security.auth.callback package; these are generic callbacks useful for building applications that perform authentication. Mechanisms might also need SASL-specific callbacks, such as those for collecting realm and authorization information, or even (non-standardized) mechanism-specific callbacks. The application should be able to accommodate a variety of mechanisms. Consequently, its callback handler must be able to service all of the callbacks that the mechanisms might request. This is not possible in general for arbitrary mechanisms, but is usually feasible due to the limited number of mechanisms that are typically deployed and used.
// Get optional initial response byte[] response = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[]) : null); String mechanism = sc.getMechanismName(); // Send selected mechanism name and optional initial response to server send(mechanism, response); // Read response msg = receive(); while (!sc.isComplete() && (msg.status == CONTINUE || msg.status == SUCCESS)) { // Evaluate server challenge response = sc.evaluateChallenge(msg.contents); if (msg.status == SUCCESS) { // done; server doesn't expect any more SASL data if (response != null) { throw new IOException( "Protocol error: attempting to send response after completion"); } break; } else { send(mechanism, response); msg = receive(); }
The client application iterates through each step of the authentication by using the mechanism (sc) to evaluate the challenge gotten from the server and to get a response to send back to the server. It continues this cycle until either the mechanism or application-level protocol indicates that the authentication has completed, or if the mechanism cannot evaluate a challenge. If the mechanism cannot evaluate the challenge, it throws an exception to indicate the error and terminates the authentication. Disagreement between the mechanism and protocol about the completion state must be treated as an error because it might indicate a compromise of the authentication exchange.
Here is an example of how a server might use SaslServer.
// Read request that contains mechanism name and optional initial response msg.receive(); // Obtain a SaslServer to perform authentication SaslServer ss = Sasl.createSaslServer(msg.mechanism, protocol, myName, props, callbackHandler); // Perform authentication steps until done while (!ss.isComplete()) { try { // Process response byte[] challenge = sc.evaluateResponse(msg.contents); if (ss.isComplete()) { send(mechanism, challenge, SUCCESS); } else { send(mechanism, challenge, CONTINUE); msg.receive(); } } catch (SaslException e) { send(ERROR); sc.dispose(); break; } }The server application iterates through each step of the authentication by giving the client's response to the mechanism (ss) to process. If the response is incorrect, the mechanism indicates the error by throwing a SaslException so that the server can report the error and terminate the authentication. If the response is correct, the mechanism returns challenge data to be sent to the client and indicates whether the authentication is complete. Note that challenge data can accompany a "success" indication. This might be used, for example, to tell the client to finalize some negotiated state.
When a security layer has been negotiated, all subsequent communication with the peer must take place using the security layer. To determine whether a security layer has been negotiated, get the negotiated quality-of-protection (QOP) from the mechanism. Here is an example of how to determine whether a security layer has been negotiated.
String qop = (String) sc.getNegotiatedProperty(Sasl.QOP); boolean hasSecurityLayer = (qop != null && (qop.equals("auth-int") || qop.equals("auth-conf")));
A security layer has been negotiated if the Sasl.QOP property indicates that either integrity and/or confidentiality has been negotiated.
To communicate with the peer using the negotiated layer, the application first uses the wrap method to encode the data to be sent to the peer to produce a "wrapped" buffer. It then transfers a length field representing the number of octets in the wrapped buffer followed by the contents of the wrapped buffer to the peer. The peer receiving the stream of octets passes the buffer (without the length field) to unwrap to obtain the decoded bytes sent by the peer. Details of this protocol are described in RFC 2222. Here is an example of how a client application sends and receives application data using a security layer.
// Send outgoing application data to peer byte[] outgoing = ...; byte[] netOut = sc.wrap(outgoing, 0, outgoing.length); send(netOut.length, netOut); // send to peer // Receive incoming application data from peer byte[] netIn = receive(); // read length and ensuing bytes from peer byte[] incoming = sc.unwrap(netIn, 0, netIn.length);SASL mechanism implementations are provided by SASL security providers. Each provider may support one or more SASL mechanisms and is registered with the Java Cryptography Architecture (JCA). By default, in J2SE 5, the SunSASL provider is automatically registered as a JCA provider. To remove it or reorder its priority as a JCA provider, change the line
security.provider.7=com.sun.security.sasl.Providerin the Java Security Properties file ($JAVA_HOME/lib/security/java.security).
To add or remove a SASL provider, you add or remove the corresponding line in the Security Properties file. For example, if you want to add a SASL provider and have its mechanisms be chosen over the same ones implemented by the SunSASL provider, then you would add a line to the Security Properties file with a lower number.
security.provider.7=com.example.MyProvider security.provider.8=com.sun.security.sasl.Provider
Alternatively, you can programmatically add your own provider using the java.security.Security class. For example, the following sample code registers the com.example.MyProvider to the list of available SASL security providers.
Security.addProvider(new com.example.MyProvider());When an application requests a SASL mechanism by supplying one or more mechanism names, the SASL framework looks for registered SASL providers that support that mechanism by going through, in order, the list of registered providers. The providers must then determine whether the requested mechanism matches the selection policy properties and if so, return an implementation for the mechanism.
The selection policy properties specify the security aspects of a mechanism, such as its susceptibility to certain attacks. These are characteristics of the mechanism (definition), rather than its implementation so all providers should come to the same conclusion about a particular mechanism. For example, the PLAIN mechanism is susceptible to plaintext attacks regardless of how it is implemented. If no selection policy properties are supplied, there are no restrictions on the selected mechanism. Using these properties, an application can ensure that it does not use unsuitable mechanisms that might be deployed in the execution environment. For example, an application might use the following sample code if it does not want to allow the use of mechanisms susceptible to plaintext attacks.
Map props = new HashMap(); props.add(Sasl.POLICY_NOPLAINTEXT, "true"); SaslClient sc = Sasl.createSaslClient(mechanisms, authzid, protocol, serverName, props, callbackHandler);See the Sasl class for descriptions of the selection policy properties. The SunSASL provider supports the following client and server mechanisms.
Client Mechanism Name | Parameters/Input | Callbacks | Configuration Properties | Selection Policy |
---|---|---|---|---|
CRAM-MD5 | authorization id (as default username) |
NameCallback PasswordCallback |
Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
|
DIGEST-MD5 | authorization id protocol id server name |
NameCallback PasswordCallback RealmCallback RealmChoiceCallback |
Sasl.QOP Sasl.STRENGTH Sasl.MAX_BUFFER Sasl.SERVER_AUTH "javax.security.sasl.sendmaxbuffer" "com.sun.security.sasl.digest.cipher" |
Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
EXTERNAL | authorization id external channel |
Sasl.POLICY_NOPLAINTEXT Sasl.POLICY_NOACTIVE Sasl.POLICY_NODICTIONARY |
||
GSSAPI | JAAS Subject authorization id protocol id server name |
Sasl.QOP Sasl.MAX_BUFFER Sasl.SERVER_AUTH "javax.security.sasl.sendmaxbuffer" |
Sasl.POLICY_NOACTIVE Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
|
PLAIN | authorization id |
NameCallback PasswordCallback |
Sasl.POLICY_NOANONYMOUS |
An application that uses these mechanisms from the SunSASL provider must supply the required parameters, callbacks and properties. The properties have reasonable defaults and only need to be set if the application wants to override the defaults. Most of the parameters, callbacks, and properties are described in the API documentation. The following sections describe mechanism-specific behaviors and parameters not already covered by the API documentation.
The Cram-MD5 client mechanism uses the authorization id
parameter, if supplied, as the default username in the
NameCallback
to solicit the application/end-user for the
authentication id. The authorization id is otherwise not used by
the Cram-MD5 mechanism; only the authentication id is exchanged
with the server.
The Digest-MD5 mechanism is used for digest authentication and optional establishment of a security layer. It specifies the following ciphers for use with the security layer: Triple DES, DES and RC4 (128, 56, and 40 bits). The Digest-MD5 mechanism can support only ciphers that are available on the platform. For example, if the platform does not support the RC4 ciphers, then the Digest-MD5 mechanism will not use those ciphers.
The Sasl.STRENGTH property supports "high", "medium", and "low" settings; its default is "high,medium,low". The ciphers are mapped to the strength settings as follows.
Strength | Cipher | Cipher Id |
---|---|---|
high | Triple DES RC4 128 bits |
3des rc4 |
medium | DES RC4 56 bits |
des rc4-56 |
low | RC4 40 bits | rc4-40 |
When there is more than one choice for a particular strength, the cipher selected depends on the availability of the ciphers in the underlying platform. To explicitly name the cipher to use, set the "com.sun.security.sasl.digest.cipher" property to the corresponding cipher id. Note that this property setting must be compatible with Sasl.STRENGTH and the ciphers available in the underlying platform. For example, Sasl.STRENGTH being set to "low" and "com.sun.security.sasl.digest.cipher" being set to "3des" are incompatible. The "com.sun.security.sasl.digest.cipher" property has no default.
The "javax.security.sasl.sendmaxbuffer" property specifies (the string representation of) the maximum send buffer size in bytes. The default is 65536. The actual maximum number of bytes will be the minimum of this number and the peer's maximum receive buffer size.
Subject
to contain the client's Kerberos credentials or that the
credentials could be obtained by implicitly logging in to Kerberos.
To obtain the client's Kerberos credentials, use the Java Authentication and
Authorization Service (JAAS) to log in using the Kerberos login
module. See the Java GSS-API
and JAAS Tutorials for Use with Kerberos for details and
examples. After using JAAS authentication to obtain the Kerberos
credentials, you put the code that uses the SASL GSSAPI mechanism
within
doAs or
doAsPrivileged.
LoginContext lc = new LoginContext("JaasSample", new TextCallbackHandler()); lc.login(); lc.getSubject().doAs(new SaslAction()); class SaslAction implements java.security.PrivilegedAction { public class run() { ... String[] mechanisms = new String[]{"GSSAPI"}; SaslClient sc = Sasl.createSaslClient(mechanisms, authzid, protocol, serverName, props, callbackHandler); ... } }To obtain Kerberos credentials without doing explicit JAAS programming, see the Java GSS-API and JAAS Tutorials for Use with Kerberos. When using this approach, there is no need to wrap the code within doAs or doAsPrivileged.
The "javax.security.sasl.sendmaxbuffer" property specifies (the string representation of) the maximum send buffer size in bytes. The default is 65536. The actual maximum number of bytes will be the minimum of this number and the peer's maximum receive buffer size.
Server Mechanism Name | Parameters/Input | Callbacks | Configuration Properties | Selection Policy |
---|---|---|---|---|
CRAM-MD5 | server name | AuthorizeCallback NameCallback PasswordCallback |
Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
|
DIGEST-MD5 | protocol id server name |
AuthorizeCallback NameCallback PasswordCallback RealmCallback |
Sasl.QOP Sasl.STRENGTH Sasl.MAX_BUFFER "javax.security.sasl.sendmaxbuffer" "com.sun.security.sasl.digest.realm" "com.sun.security.sasl.digest.utf8" |
Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
GSSAPI | JAAS Subject protocol id server name |
AuthorizeCallback | Sasl.QOP Sasl.MAX_BUFFER "javax.security.sasl.sendmaxbuffer" |
Sasl.POLICY_NOACTIVE Sasl.POLICY_NOANONYMOUS Sasl.POLICY_NOPLAINTEXT |
An application that uses these mechanisms from the SunSASL provider must supply the required parameters, callbacks and properties. The properties have reasonable defaults and only need to be set if the application wants to override the defaults.
All users of server mechanisms must have a callback handler that deals with the AuthorizeCallback. This is used by the mechanisms to determine whether the authenticated user is allowed to act on behalf of the requested authorization id, and also to obtain the canonicalized name of the authorized user (if canonicalization is applicable).
Most of the parameters, callbacks, and properties are described in the API documentation. The following sections describe mechanism-specific behaviors and parameters not already covered by the API documentation.
The "javax.security.sasl.sendmaxbuffer" property is described in the Digest-MD5 client section.
The "com.sun.security.sasl.digest.realm" property is used to specify a list of space-separated realm names that the server supports. The list is sent to the client as part of the challenge. If this property has not been set, the default realm is the server's name (supplied as a parameter).
The "com.sun.security.sasl.digest.utf8" property is used to specify the character encoding to use. "true" means to use UTF-8 encoding; "false" means to use ISO Latin 1 (ISO-8859-1). The default is "true".
javax.security.sasl.level=FINEST handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=FINEST
The table below shows the mechanisms and the logging output that they generate.
Mechanism | Logging Level | Information Logged |
---|---|---|
CRAM-MD5 | FINE | Configuration properties; challenge/response messages |
DIGEST-MD5 | INFO | Message discarded due to encoding problem (e.g., unmatched MACs, incorrect padding) |
DIGEST-MD5 | FINE | Configuration properties; challenge/response messages |
DIGEST-MD5 | FINER | More detailed information about challenge/response messages |
DIGEST-MD5 | FINEST | Buffers exchanged at the security layer |
GSSAPI | FINE | Configuration properties; challenge/response messages |
GSSAPI | FINER | More detailed information about challenge/response messages |
GSSAPI | FINEST | Buffers exchanged at the security layer |
The first step involves providing an implementation for the SASL mechanism. To implement a client mechanism, you need to implement the methods declared in the SaslClient interface. Similarly, for a server mechanism, you need to implement the methods declared in the SaslServer interface. For the purposes of this discussion, suppose you are developing an implementation for the client mechanism "SAMPLE-MECH", implemented by the class, com.example.SampleMechClient. You must decide what input are needed by the mechanism and how the implementation is going to collect them. For example, if the mechanism is username/password-based, then the implementation would likely need to collect that information via the callback handler parameter.
The next step involves providing a factory class that will create instances of com.example.SampleMechClient. The factory needs to determine the characteristics of the mechanism that it supports (as described by the Sasl.POLICY_* properties) so that it can return an instance of the mechanism when the API user requests it using compatible policy properties. The factory may also check for validity of the parameters before creating the mechanism. For the purposes of this discussion, suppose the factory class is named com.example.MySampleClientFactory. Although our sample factory is responsible for only one mechanism, a single factory can be responsible for any number of mechanisms.
The final step involves creating a JCA provider. The steps for creating a JCA provider is described in detail in the document, How to Implement a Provider for the Java Cryptography Architecture. SASL client factories are registered using property names of the form
SaslClientFactory.mechName
while SASL server factories are registered using property names of the formSaslServerFactory.mechName
mechName is the SASL mechanism's name. This is what's returned by SaslClient.getMechanismName() and SaslServer.getMechanismName(). Continuing with our example, here is how the provider would register the "SAMPLE-MECH" mechanism.
put("SaslClientFactory.SAMPLE-MECH", "com.example.MySampleClientFactory");
A single SASL provider might be responsible for many mechanisms. Therefore, it might have many invocations of put to register the relevant factories. The completed SASL provider can then be made available to applications using the instructions given previously.