package org.corfudb.security.sasl.plaintext; import lombok.extern.slf4j.Slf4j; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Map; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; /** * Created by sneginhal on 01/27/2017. * Implementation of the plain text SASL server. * Please refer to the following resources for more information: * https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html * https://tools.ietf.org/html/rfc4616 * https://tools.ietf.org/html/rfc4422 */ @Slf4j public class PlainTextSaslServer implements SaslServer { public static final String MECHANISM = "PLAIN"; private boolean authenticated; private String authorizationId; public PlainTextSaslServer() { authenticated = false; } @Override public String getMechanismName() { return MECHANISM; } private void verify(String authzid, String authcid, String passwd) throws SaslException { if (authcid.isEmpty()) { throw new SaslException("Authentication failed due to empty username"); } if (passwd.isEmpty()) { throw new SaslException("Authentication failed due to empty password"); } if (authzid.isEmpty()) { authorizationId = authcid; } else { authorizationId = authzid; } try { LoginContext lc = new LoginContext("CorfuDB", new PlainTextCallbackHandler(authcid, passwd)); lc.login(); } catch (LoginException le) { throw new SaslException("Login attempt by '" + authcid + "' failed"); } log.debug("Login by {} is successful", authcid); authenticated = true; } @Override public byte[] evaluateResponse(byte[] response) throws SaslException { String[] tokens; try { tokens = new String(response, "UTF-8").split("\u0000"); } catch (UnsupportedEncodingException ue) { throw new SaslException("Unsupported charset"); } if (tokens.length != 3) { throw new SaslException("Malformed plain text response received"); } verify(tokens[0], tokens[1], tokens[2]); return null; } @Override public boolean isComplete() { return authenticated; } @Override public String getAuthorizationID() { if (!authenticated) { throw new IllegalStateException("Authentication is incomplete"); } return authorizationId; } @Override public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { if (!authenticated) { throw new IllegalStateException("Authentication is incomplete"); } return Arrays.copyOfRange(incoming, offset, offset + len); } @Override public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { if (!authenticated) { throw new IllegalStateException("Authentication is incomplete"); } return Arrays.copyOfRange(outgoing, offset, offset + len); } @Override public Object getNegotiatedProperty(String propName) { if (!authenticated) { throw new IllegalStateException("Authentication is incomplete"); } return null; } @Override public void dispose() throws SaslException { } public static class PlainTextSaslServerFactory implements SaslServerFactory { @Override public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException { if (!mechanism.equals(MECHANISM)) { throw new SaslException("Unsupported mechanism: " + mechanism); } return new PlainTextSaslServer(); } @Override public String[] getMechanismNames(Map<String, ?> props) { String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT); if (noPlainText.equals("true")) return new String[]{}; else return new String[]{MECHANISM}; } } }