Return-Path: X-Original-To: apmail-qpid-commits-archive@www.apache.org Delivered-To: apmail-qpid-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B845311D11 for ; Mon, 21 Jul 2014 18:20:53 +0000 (UTC) Received: (qmail 76283 invoked by uid 500); 21 Jul 2014 18:20:53 -0000 Delivered-To: apmail-qpid-commits-archive@qpid.apache.org Received: (qmail 76256 invoked by uid 500); 21 Jul 2014 18:20:53 -0000 Mailing-List: contact commits-help@qpid.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@qpid.apache.org Delivered-To: mailing list commits@qpid.apache.org Received: (qmail 76247 invoked by uid 99); 21 Jul 2014 18:20:53 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 21 Jul 2014 18:20:53 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED,T_FILL_THIS_FORM_FRAUD_PHISH,T_FILL_THIS_FORM_SHORT X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 21 Jul 2014 18:20:51 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id EDEF82388860; Mon, 21 Jul 2014 18:20:30 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1612369 - in /qpid/trunk/qpid/java: amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java Date: Mon, 21 Jul 2014 18:20:30 -0000 To: commits@qpid.apache.org From: rgodfrey@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140721182030.EDEF82388860@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: rgodfrey Date: Mon Jul 21 18:20:30 2014 New Revision: 1612369 URL: http://svn.apache.org/r1612369 Log: QPID-5884 : Add additional SASL mechanisms to the JMS AMQP 1.0 client Modified: qpid/trunk/qpid/java/amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java qpid/trunk/qpid/java/amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java Modified: qpid/trunk/qpid/java/amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java?rev=1612369&r1=1612368&r2=1612369&view=diff ============================================================================== --- qpid/trunk/qpid/java/amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java (original) +++ qpid/trunk/qpid/java/amqp-1-0-client/src/main/java/org/apache/qpid/amqp_1_0/client/TCPTransportProvier.java Mon Jul 21 18:20:30 2014 @@ -20,15 +20,6 @@ */ package org.apache.qpid.amqp_1_0.client; -import org.apache.qpid.amqp_1_0.framing.ConnectionHandler; -import org.apache.qpid.amqp_1_0.framing.ExceptionHandler; -import org.apache.qpid.amqp_1_0.transport.ConnectionEndpoint; -import org.apache.qpid.amqp_1_0.type.FrameBody; -import org.apache.qpid.amqp_1_0.type.SaslFrameBody; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -36,6 +27,16 @@ import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.qpid.amqp_1_0.framing.ConnectionHandler; +import org.apache.qpid.amqp_1_0.framing.ExceptionHandler; +import org.apache.qpid.amqp_1_0.transport.ConnectionEndpoint; +import org.apache.qpid.amqp_1_0.type.FrameBody; +import org.apache.qpid.amqp_1_0.type.SaslFrameBody; + class TCPTransportProvier implements TransportProvider { private final String _transport; @@ -70,6 +71,7 @@ class TCPTransportProvier implements Tra SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket(address, port); + conn.setExternalPrincipal(sslSocket.getSession().getLocalPrincipal()); s=sslSocket; } else Modified: qpid/trunk/qpid/java/amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java?rev=1612369&r1=1612368&r2=1612369&view=diff ============================================================================== --- qpid/trunk/qpid/java/amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java (original) +++ qpid/trunk/qpid/java/amqp-1-0-common/src/main/java/org/apache/qpid/amqp_1_0/transport/ConnectionEndpoint.java Mon Jul 21 18:20:30 2014 @@ -21,21 +21,30 @@ package org.apache.qpid.amqp_1_0.transport; +import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; +import javax.xml.bind.DatatypeConverter; import org.apache.qpid.amqp_1_0.codec.DescribedTypeConstructorRegistry; import org.apache.qpid.amqp_1_0.codec.ValueWriter; @@ -82,7 +91,7 @@ public class ConnectionEndpoint implemen private static final short DEFAULT_CHANNEL_MAX = Integer.getInteger("amqp.channel_max", 255).shortValue(); private static final int DEFAULT_MAX_FRAME = Integer.getInteger("amqp.max_frame_size", 1 << 15); - private static final long DEFAULT_SYNC_TIMEOUT = Long.getLong("amqp.connection_sync_timeout",5000l); + private static final long DEFAULT_SYNC_TIMEOUT = Long.getLong("amqp.connection_sync_timeout", 5000l); private ConnectionState _state = ConnectionState.UNOPENED; @@ -137,6 +146,8 @@ public class ConnectionEndpoint implemen private long _syncTimeout = DEFAULT_SYNC_TIMEOUT; private String _localHostname; + private boolean _secure; + private Principal _externalPrincipal; public ConnectionEndpoint(Container container, SaslServerProvider cbs) { @@ -157,7 +168,7 @@ public class ConnectionEndpoint implemen public void setPrincipal(Principal user) { - if(_user == null) + if (_user == null) { _user = user; _requiresSASLClient = user != null; @@ -199,7 +210,7 @@ public class ConnectionEndpoint implemen _frameOutputHandler = frameOutputHandler; } - public void setProperties(Map properties) + public void setProperties(Map properties) { _properties = properties; } @@ -260,12 +271,12 @@ public class ConnectionEndpoint implemen { Open open = new Open(); - if(_receivingSessions == null) + if (_receivingSessions == null) { - _receivingSessions = new SessionEndpoint[channelMax+1]; - _sendingSessions = new SessionEndpoint[channelMax+1]; + _receivingSessions = new SessionEndpoint[channelMax + 1]; + _sendingSessions = new SessionEndpoint[channelMax + 1]; } - if(channelMax < _channelMax) + if (channelMax < _channelMax) { _channelMax = channelMax; } @@ -273,7 +284,7 @@ public class ConnectionEndpoint implemen open.setContainerId(_container.getId()); open.setMaxFrameSize(getDesiredMaxFrameSize()); open.setHostname(getRemoteHostname()); - if(_properties != null) + if (_properties != null) { open.setProperties(_properties); } @@ -322,7 +333,7 @@ public class ConnectionEndpoint implemen error.setDescription("Frame received on channel " + channel + " which is not known as a begun session."); this.handleError(error); } - + return session; } @@ -335,18 +346,18 @@ public class ConnectionEndpoint implemen ? open.getChannelMax().shortValue() : _channelMax; - if(_receivingSessions == null) + if (_receivingSessions == null) { - _receivingSessions = new SessionEndpoint[_channelMax+1]; - _sendingSessions = new SessionEndpoint[_channelMax+1]; + _receivingSessions = new SessionEndpoint[_channelMax + 1]; + _sendingSessions = new SessionEndpoint[_channelMax + 1]; } UnsignedInteger remoteDesiredMaxFrameSize = open.getMaxFrameSize() == null ? UnsignedInteger.valueOf(DEFAULT_MAX_FRAME) : open.getMaxFrameSize(); _maxFrameSize = (remoteDesiredMaxFrameSize.compareTo(_desiredMaxFrameSize) < 0 - ? remoteDesiredMaxFrameSize - : _desiredMaxFrameSize).intValue(); + ? remoteDesiredMaxFrameSize + : _desiredMaxFrameSize).intValue(); _remoteContainerId = open.getContainerId(); _localHostname = open.getHostname(); @@ -411,7 +422,7 @@ public class ConnectionEndpoint implemen switch (_state) { case UNOPENED: - sendOpen((short)0,0); + sendOpen((short) 0, 0); sendClose(close); _state = ConnectionState.CLOSED; break; @@ -433,7 +444,7 @@ public class ConnectionEndpoint implemen if (!_closedForInput) { _closedForInput = true; - switch(_state) + switch (_state) { case UNOPENED: case AWAITING_OPEN: @@ -448,7 +459,7 @@ public class ConnectionEndpoint implemen default: } - if(_receivingSessions != null) + if (_receivingSessions != null) { for (int i = 0; i < _receivingSessions.length; i++) { @@ -508,7 +519,7 @@ public class ConnectionEndpoint implemen endpoint.setReceivingChannel(channel); endpoint.setNextIncomingId(begin.getNextOutgoingId()); endpoint.setOutgoingSessionCredit(begin.getIncomingWindow()); - + if (endpoint.getState() == SessionState.END_SENT) { _sendingSessions[myChannelId] = null; @@ -728,19 +739,24 @@ public class ConnectionEndpoint implemen Close close = new Close(); close.setError(error); send((short) 0, close); - + this.setClosedForOutput(true); } } + public void setExternalPrincipal(final Principal externalPrincipal) + { + _externalPrincipal = externalPrincipal; + } + public static interface FrameReceiptLogger { boolean isEnabled(); + void received(final SocketAddress remoteAddress, short channel, Object frame); } - private FrameReceiptLogger _logger = new FrameReceiptLogger() { @@ -911,52 +927,82 @@ public class ConnectionEndpoint implemen } } + private final AmqpSaslClient[] _supportedSaslClientMechanisms = + new AmqpSaslClient[]{new ScramSHA256SaslClient(), new ScramSHA1SaslClient(), new ExternalSaslClient(), + new CramMD5SaslClient(), new CramMD5HashedSaslClient(), new PlainSaslClient(), new AnonymousSaslClient()}; + + private AmqpSaslClient _saslClient; + public void receiveSaslMechanisms(final SaslMechanisms saslMechanisms) { SaslInit init = new SaslInit(); init.setHostname(_remoteHostname); Set mechanisms = new HashSet(Arrays.asList(saslMechanisms.getSaslServerMechanisms())); - if (mechanisms.contains(SASL_PLAIN) && _password != null) - { - - init.setMechanism(SASL_PLAIN); - - byte[] usernameBytes = _user.getName().getBytes(Charset.forName("UTF-8")); - byte[] passwordBytes = _password.getBytes(Charset.forName("UTF-8")); - byte[] initResponse = new byte[usernameBytes.length + passwordBytes.length + 2]; - System.arraycopy(usernameBytes, 0, initResponse, 1, usernameBytes.length); - System.arraycopy(passwordBytes, 0, initResponse, usernameBytes.length + 2, passwordBytes.length); - init.setInitialResponse(new Binary(initResponse)); - } - else if (mechanisms.contains(SASL_ANONYMOUS)) + for (AmqpSaslClient saslClient : _supportedSaslClientMechanisms) { - init.setMechanism(SASL_ANONYMOUS); + if (mechanisms.contains(saslClient.getMechanismName()) && saslClient.canSupportMechanism()) + { + _saslClient = saslClient; + break; + } } - else if (mechanisms.contains(SASL_EXTERNAL)) + if (_saslClient != null) { - init.setMechanism(SASL_EXTERNAL); + try + { + + init.setMechanism(_saslClient.getMechanismName()); + if (_saslClient.hasInitialResponse()) + { + init.setInitialResponse(new Binary(_saslClient.getResponse(new byte[0]))); + } + _saslFrameOutput.send(new SASLFrame(init), null); + + } + catch (SaslException e) + { + closeSaslWithFailure(); + + } } else { - synchronized (getLock()) - { - _saslComplete = true; - _authenticated = false; - getLock().notifyAll(); - } - setClosedForInput(true); - _saslFrameOutput.close(); + closeSaslWithFailure(); + + } + } - return; + public void closeSaslWithFailure() + { + synchronized (getLock()) + { + _saslComplete = true; + _authenticated = false; + getLock().notifyAll(); } - _saslFrameOutput.send(new SASLFrame(init), null); + setClosedForInput(true); + _saslFrameOutput.close(); } public void receiveSaslChallenge(final SaslChallenge saslChallenge) { - //To change body of implemented methods use File | Settings | File Templates. + try + { + ByteBuffer challenge = saslChallenge.getChallenge().asByteBuffer(); + final byte[] challengeBytes = new byte[challenge.remaining()]; + challenge.get(challengeBytes); + byte[] responseBytes = _saslClient.getResponse(challengeBytes); + SaslResponse response = new SaslResponse(); + response.setResponse(new Binary(responseBytes)); + _saslFrameOutput.send(new SASLFrame(response), null); + } + catch (SaslException e) + { + closeSaslWithFailure(); + } + } public void receiveSaslResponse(final SaslResponse saslResponse) @@ -1036,14 +1082,7 @@ public class ConnectionEndpoint implemen } else { - synchronized (getLock()) - { - _saslComplete = true; - _authenticated = false; - getLock().notifyAll(); - } - setClosedForInput(true); - _saslFrameOutput.close(); + closeSaslWithFailure(); } } @@ -1151,16 +1190,16 @@ public class ConnectionEndpoint implemen { long endTime = System.currentTimeMillis() + timeout; - synchronized(getLock()) + synchronized (getLock()) { - while(!predicate.isSatisfied()) + while (!predicate.isSatisfied()) { getLock().wait(timeout); - if(!predicate.isSatisfied()) + if (!predicate.isSatisfied()) { timeout = endTime - System.currentTimeMillis(); - if(timeout <= 0l) + if (timeout <= 0l) { throw new TimeoutException(); } @@ -1169,4 +1208,503 @@ public class ConnectionEndpoint implemen } } + + private interface AmqpSaslClient + { + boolean canSupportMechanism(); + + Symbol getMechanismName(); + + boolean hasInitialResponse(); + + byte[] getResponse(byte[] challenge) throws SaslException; + } + + private class AnonymousSaslClient implements AmqpSaslClient + { + + @Override + public boolean canSupportMechanism() + { + return true; + } + + @Override + public Symbol getMechanismName() + { + return Symbol.valueOf("ANONYMOUS"); + } + + @Override + public boolean hasInitialResponse() + { + return false; + } + + @Override + public byte[] getResponse(final byte[] challenge) + { + return new byte[0]; + } + } + + private class ExternalSaslClient implements AmqpSaslClient + { + + @Override + public boolean canSupportMechanism() + { + return ConnectionEndpoint.this._externalPrincipal != null; + } + + @Override + public Symbol getMechanismName() + { + return Symbol.valueOf("EXTERNAL"); + } + + @Override + public boolean hasInitialResponse() + { + return false; + } + + @Override + public byte[] getResponse(final byte[] challenge) + { + return new byte[0]; + } + } + + private class PlainSaslClient implements AmqpSaslClient + { + + private boolean _initResponseSent; + + @Override + public boolean canSupportMechanism() + { + return ConnectionEndpoint.this._user != null + && ConnectionEndpoint.this._password != null; + } + + @Override + public Symbol getMechanismName() + { + return Symbol.valueOf("PLAIN"); + } + + @Override + public boolean hasInitialResponse() + { + return true; + } + + @Override + public byte[] getResponse(final byte[] challenge) + { + if (_initResponseSent) + { + return new byte[0]; + } + else + { + _initResponseSent = true; + byte[] usernameBytes = _user.getName().getBytes(Charset.forName("UTF-8")); + byte[] passwordBytes = _password.getBytes(Charset.forName("UTF-8")); + byte[] initResponse = new byte[usernameBytes.length + passwordBytes.length + 2]; + System.arraycopy(usernameBytes, 0, initResponse, 1, usernameBytes.length); + System.arraycopy(passwordBytes, 0, initResponse, usernameBytes.length + 2, passwordBytes.length); + return initResponse; + } + } + } + + + private static final Charset ASCII = Charset.forName("ASCII"); + + abstract static private class AbstractScramSaslClient implements AmqpSaslClient + { + + + private static final byte[] INT_1 = new byte[]{0, 0, 0, 1}; + private static final String GS2_HEADER = "n,,"; + + private final String _digestName; + private final String _hmacName; + private final ConnectionEndpoint _endpoint; + + private String _username; + private final String _clientNonce = UUID.randomUUID().toString(); + private String _serverNonce; + private byte[] _salt; + private int _iterationCount; + private String _clientFirstMessageBare; + private byte[] _serverSignature; + + enum State + { + INITIAL, + CLIENT_FIRST_SENT, + CLIENT_PROOF_SENT, + COMPLETE + } + + public final Symbol _mechanism; + + + private State _state = State.INITIAL; + + public AbstractScramSaslClient(ConnectionEndpoint endpoint, + final Symbol mechanism, + final String digestName, + final String hmacName) + { + _endpoint = endpoint; + _mechanism = mechanism; + _digestName = digestName; + _hmacName = hmacName; + + } + + + @Override + public boolean canSupportMechanism() + { + return _endpoint._user != null + && _endpoint._password != null; + } + + + @Override + public Symbol getMechanismName() + { + return _mechanism; + } + + @Override + public boolean hasInitialResponse() + { + return true; + } + + @Override + public byte[] getResponse(final byte[] challenge) throws SaslException + { + byte[] response; + switch (_state) + { + case INITIAL: + response = initialResponse(); + _state = State.CLIENT_FIRST_SENT; + break; + case CLIENT_FIRST_SENT: + response = calculateClientProof(challenge); + _state = State.CLIENT_PROOF_SENT; + break; + case CLIENT_PROOF_SENT: + evaluateOutcome(challenge); + response = null; + _state = State.COMPLETE; + break; + default: + throw new SaslException("No challenge expected in state " + _state); + } + return response; + } + + private void evaluateOutcome(final byte[] challenge) throws SaslException + { + String serverFinalMessage = new String(challenge, ASCII); + String[] parts = serverFinalMessage.split(","); + if (!parts[0].startsWith("v=")) + { + throw new SaslException("Server final message did not contain verifier"); + } + byte[] serverSignature = DatatypeConverter.parseBase64Binary(parts[0].substring(2)); + if (!Arrays.equals(_serverSignature, serverSignature)) + { + throw new SaslException("Server signature did not match"); + } + } + + private byte[] calculateClientProof(final byte[] challenge) throws SaslException + { + try + { + String serverFirstMessage = new String(challenge, ASCII); + String[] parts = serverFirstMessage.split(","); + if (parts.length < 3) + { + throw new SaslException("Server challenge '" + serverFirstMessage + "' cannot be parsed"); + } + else if (parts[0].startsWith("m=")) + { + throw new SaslException("Server requires mandatory extension which is not supported: " + parts[0]); + } + else if (!parts[0].startsWith("r=")) + { + throw new SaslException("Server challenge '" + + serverFirstMessage + + "' cannot be parsed, cannot find nonce"); + } + String nonce = parts[0].substring(2); + if (!nonce.startsWith(_clientNonce)) + { + throw new SaslException("Server challenge did not use correct client nonce"); + } + _serverNonce = nonce; + if (!parts[1].startsWith("s=")) + { + throw new SaslException("Server challenge '" + + serverFirstMessage + + "' cannot be parsed, cannot find salt"); + } + String base64Salt = parts[1].substring(2); + _salt = DatatypeConverter.parseBase64Binary(base64Salt); + if (!parts[2].startsWith("i=")) + { + throw new SaslException("Server challenge '" + + serverFirstMessage + + "' cannot be parsed, cannot find iteration count"); + } + String iterCountString = parts[2].substring(2); + _iterationCount = Integer.parseInt(iterCountString); + if (_iterationCount <= 0) + { + throw new SaslException("Iteration count " + _iterationCount + " is not a positive integer"); + } + + byte[] passwordBytes = saslPrep(_endpoint._password).getBytes("UTF-8"); + + byte[] saltedPassword = generateSaltedPassword(passwordBytes); + + + String clientFinalMessageWithoutProof = + "c=" + DatatypeConverter.printBase64Binary(GS2_HEADER.getBytes(ASCII)) + + ",r=" + _serverNonce; + + String authMessage = + _clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof; + + byte[] clientKey = computeHmac(saltedPassword, "Client Key"); + byte[] storedKey = MessageDigest.getInstance(_digestName).digest(clientKey); + + byte[] clientSignature = computeHmac(storedKey, authMessage); + + byte[] clientProof = clientKey.clone(); + for (int i = 0; i < clientProof.length; i++) + { + clientProof[i] ^= clientSignature[i]; + } + byte[] serverKey = computeHmac(saltedPassword, "Server Key"); + _serverSignature = computeHmac(serverKey, authMessage); + + String finalMessageWithProof = clientFinalMessageWithoutProof + + ",p=" + DatatypeConverter.printBase64Binary(clientProof); + return finalMessageWithProof.getBytes(); + } + catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) + { + throw new SaslException(e.getMessage(), e); + } + } + + private byte[] computeHmac(final byte[] key, final String string) + throws SaslException, UnsupportedEncodingException + { + Mac mac = createHmac(key); + mac.update(string.getBytes(ASCII)); + return mac.doFinal(); + } + + private byte[] generateSaltedPassword(final byte[] passwordBytes) throws SaslException + { + Mac mac = createHmac(passwordBytes); + + mac.update(_salt); + mac.update(INT_1); + byte[] result = mac.doFinal(); + + byte[] previous = null; + for (int i = 1; i < _iterationCount; i++) + { + mac.update(previous != null ? previous : result); + previous = mac.doFinal(); + for (int x = 0; x < result.length; x++) + { + result[x] ^= previous[x]; + } + } + + return result; + } + + private Mac createHmac(final byte[] keyBytes) + throws SaslException + { + try + { + SecretKeySpec key = new SecretKeySpec(keyBytes, _hmacName); + Mac mac = Mac.getInstance(_hmacName); + mac.init(key); + return mac; + } + catch (NoSuchAlgorithmException | InvalidKeyException e) + { + throw new SaslException(e.getMessage(), e); + } + } + + + private byte[] initialResponse() throws SaslException + { + StringBuffer buf = new StringBuffer("n="); + _username = _endpoint.getUser().getName(); + buf.append(saslPrep(_username)); + buf.append(",r="); + buf.append(_clientNonce); + _clientFirstMessageBare = buf.toString(); + return (GS2_HEADER + _clientFirstMessageBare).getBytes(ASCII); + } + + private String saslPrep(String name) throws SaslException + { + // TODO - a real implementation of SaslPrep + + if (!ASCII.newEncoder().canEncode(name)) + { + throw new SaslException("Can only encode names and passwords which are restricted to ASCII characters"); + } + + name = name.replace("=", "=3D"); + name = name.replace(",", "=2C"); + return name; + } + + public boolean isComplete() + { + return _state == State.COMPLETE; + } + + } + + private final class ScramSHA1SaslClient extends AbstractScramSaslClient + { + + public ScramSHA1SaslClient() + { + super(ConnectionEndpoint.this, Symbol.valueOf("SCRAM-SHA-1"), "SHA-1", "HmacSHA1"); + } + } + + + private final class ScramSHA256SaslClient extends AbstractScramSaslClient + { + + public ScramSHA256SaslClient() + { + super(ConnectionEndpoint.this, Symbol.valueOf("SCRAM-SHA-256"), "SHA-256", "HmacSHA256"); + } + } + + private class CramMD5SaslClient implements AmqpSaslClient + { + + @Override + public boolean canSupportMechanism() + { + return ConnectionEndpoint.this._user != null + && ConnectionEndpoint.this._password != null; + } + + @Override + public Symbol getMechanismName() + { + return Symbol.valueOf("CRAM-MD5"); + } + + @Override + public boolean hasInitialResponse() + { + return false; + } + + @Override + public byte[] getResponse(final byte[] challenge) throws SaslException + { + + try + { + SecretKeySpec key = new SecretKeySpec(getSharedSecretBytes(), "HmacMD5"); + + Mac mac = Mac.getInstance("HmacMD5"); + mac.init(key); + + mac.update(challenge); + byte[] result = mac.doFinal(); + + StringBuilder responseBeforeBase64 = new StringBuilder(ConnectionEndpoint.this.getUser().getName()); + responseBeforeBase64.append(" "); + for (byte b : result) + { + responseBeforeBase64.append(String.format("%02x", b)); + } + + return responseBeforeBase64.toString().getBytes(ASCII); + } + catch (NoSuchAlgorithmException | InvalidKeyException e) + { + throw new SaslException(e.getMessage(), e); + } + } + + public byte[] getSharedSecretBytes() throws SaslException + { + return ConnectionEndpoint.this._password.getBytes(ASCII); + } + } + + private final class CramMD5HashedSaslClient extends CramMD5SaslClient + { + + @Override + public Symbol getMechanismName() + { + return Symbol.valueOf("CRAM-MD5-HASHED"); + } + + public byte[] getSharedSecretBytes() throws SaslException + { + + try + { + + byte[] data = ConnectionEndpoint.this._password.getBytes("utf-8"); + MessageDigest md = MessageDigest.getInstance("MD5"); + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + char[] hash = new char[digest.length]; + + int index = 0; + for (byte b : digest) + { + hash[index++] = (char) b; + } + + return new String(hash).getBytes("utf-8"); + } + catch (NoSuchAlgorithmException | UnsupportedEncodingException e) + { + throw new SaslException(e.getMessage(), e); + } + + } + + } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org For additional commands, e-mail: commits-help@qpid.apache.org