For some reason SUN includes support for sasl PLAIN in their SaslClient, but not SaslServer. Here’s a quick hack to implement a plain sasl server. As per rfc4616.txt, there is only message, three UTF-8 strings, max length 255 delimited by null (\u0000) sent from the client. There is no server response.
import java.io.IOException; import java.io.UnsupportedEncodingException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; /** * Simple sasl server to decode a * @author toaster */ public class PlainSaslServer implements SaslServer { public boolean complete = false; private String authID = null; private CallbackHandler cbh; PlainSaslServer(CallbackHandler cbh) { this.cbh = cbh; } public String getMechanismName() { return SaslConstants.SASL_SERVICE; } public byte[] evaluateResponse(byte[] response) throws SaslException { String authnID = null; String authzID = null; String password = null; // Decode response as per rfc4616.txt // The formal grammar for the client message using Augmented BNF [ABNF] // follows. // // message = [authzid] UTF8NUL authcid UTF8NUL passwd // authcid = 1*SAFE ; MUST accept up to 255 octets // authzid = 1*SAFE ; MUST accept up to 255 octets // passwd = 1*SAFE ; MUST accept up to 255 octets // UTF8NUL = %x00 ; UTF-8 encoded NUL character // // SAFE = UTF1 / UTF2 / UTF3 / UTF4 // ;; any UTF-8 encoded Unicode character except NUL // // UTF1 = %x01-7F ;; except NUL // UTF2 = %xC2-DF UTF0 // UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / // %xED %x80-9F UTF0 / %xEE-EF 2(UTF0) // UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / // %xF4 %x80-8F 2(UTF0) // UTF0 = %x80-BF int start = 0; int end = 0; int elementIdx = 0; try { for (byte b : response) { if (b == '\u0000') { // empty string, only authzid allows this if (end - start == 0) { if (elementIdx != 0) { throw new SaslException("null auth data"); } } // data, wa-hoo else { String element = new String(response, start, end - start, "UTF-8"); start = end + 1; switch (elementIdx) { case 0: authzID = element; break; case 1: authnID = element; break; default: throw new SaslException("Unexpected data in packet"); } } elementIdx++; } end++; } if (start == end) { throw new SaslException("null auth data"); } password = new String(response, start, end - start, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new SaslException("utf-8 encoding"); } ExternalValidationCallback evc = new ExternalValidationCallback(authnID, password); AuthorizeCallback ac = new AuthorizeCallback(authnID, authzID); Callback[] cbList = new Callback[2]; cbList[0] = evc; cbList[1] = ac; try { if (password == null || authnID == null) { throw new SaslException("null auth data"); } cbh.handle(cbList); if (!evc.isValidated()) { throw new SaslException("cannot validate password"); } if (!ac.isAuthorized()) { throw new SaslException("user not authorized"); } complete = true; return null; } catch (UnsupportedCallbackException ex) { throw new SaslException("unsupported callback", ex); } catch (IOException e) { if (e instanceof SaslException) { throw (SaslException) e; } throw new SaslException("Callback error", e); } } public boolean isComplete() { return complete; } public String getAuthorizationID() { if (!complete) { throw new IllegalStateException("not complete"); } return authID; } public byte[] unwrap(byte[] incoming, int offset, int len) { if (!complete) { throw new IllegalStateException("not complete"); } final byte[] result = new byte[len]; System.arraycopy(incoming, offset, result, 0, len); return result; } public byte[] wrap(byte[] outgoing, int offset, int len) { if (!complete) { throw new IllegalStateException("not complete"); } final byte[] result = new byte[len]; System.arraycopy(outgoing, offset, result, 0, len); return result; } public Object getNegotiatedProperty(String propName) { return null; } public void dispose() throws SaslException { } }
This also uses the following callback to validate the client’s username and password. The SaslServer will throw a SaslException if the isValidated returns false.
public class ExternalValidationCallback implements Callback { private String username; private String password; private boolean validated = false; public ExternalValidationCallback(String username, String password) { this.username = username; this.password = password; } public String getPassword() { return password; } public String getUsername() { return username; } public boolean isValidated() { return validated; } public void setValidated(boolean validated) { this.validated = validated; } }
Now after we have the new callback and server we need to register it as a provider w/in java. This requires that we create a server factory and simple security provider to register the factory. The server factory will need to check the sasl property POLICY_NOPLAINTEXT to see if its allowed to return the PLAIN server.
public class PlainSaslServerFactory implements SaslServerFactory { private static String[] mechanisms = { "PLAIN" }; public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Mapprops, CallbackHandler cbh) throws SaslException { if (!mechanisms[0].equals(mechanism) || cbh == null) return null; return new PlainSaslServer(cbh); } public String[] getMechanismNames(Map props) { if ("true".equals(props.get(Sasl.POLICY_NOPLAINTEXT))) return new String[0]; return mechanisms; } }
Now, create the java security provider.
public final class MySecurityProvider extends Provider { public MySecurityProvider() { super("My Provider", 1.0 , "Simple sasl plain provider"); put("SaslServerFactory.PLAIN", "org.swap.provider.PlainSaslServerFactory"); } }
Putting it all together
// register provider Security.addProvider(new MySecurityProvider()); // create callback CallbackHandler myHandler = new CallbackHandler() { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback cb : callbacks) { if (cb instanceof ExternalValidationCallback) { ExternalValidationCallback evc = (ExternalValidationCallback) cb; // Add your password validation, unless bob works for you evc.setValidated("bob".equals(evc.getUsername() && "password".equals()evc.getPassword()); } else if (cb instanceof AuthorizeCallback) { AuthorizeCallback ac = (AuthorizeCallback) cb; // Add your test to see if client is authorized to use requested ac.setAuthorized(true); } else { throw new UnsupportedCallbackException(cb, "Unrecognized Callback"); } } }; SaslServer ss = Sasl.createSaslServer("PLAIN", "myProtocol", serverName, null, myHandler); ss.evaluateResponse(clientPacket);