Skip to content

Commit 09eb09a

Browse files
author
Emile Joubert
committed
Merged bug23467 into default
2 parents db65727 + ef6459e commit 09eb09a

File tree

8 files changed

+363
-15
lines changed

8 files changed

+363
-15
lines changed

src/com/rabbitmq/client/ConnectionFactory.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.io.IOException;
3434
import java.security.KeyManagementException;
3535
import java.security.NoSuchAlgorithmException;
36-
import java.util.HashMap;
3736
import java.util.Map;
3837

3938
import java.net.Socket;
@@ -100,6 +99,7 @@ public class ConnectionFactory implements Cloneable {
10099
private int requestedHeartbeat = DEFAULT_HEARTBEAT;
101100
private Map<String, Object> _clientProperties = AMQConnection.defaultClientProperties();
102101
private SocketFactory factory = SocketFactory.getDefault();
102+
private SaslConfig saslConfig = new DefaultSaslConfig(this);
103103

104104
/**
105105
* Instantiate a ConnectionFactory with a default set of parameters.
@@ -261,6 +261,24 @@ public void setClientProperties(Map<String, Object> clientProperties) {
261261
_clientProperties = clientProperties;
262262
}
263263

264+
/**
265+
* Gets the sasl config to use when authenticating
266+
* @return
267+
* @see com.rabbitmq.client.SaslConfig
268+
*/
269+
public SaslConfig getSaslConfig() {
270+
return saslConfig;
271+
}
272+
273+
/**
274+
* Sets the sasl config to use when authenticating
275+
* @param saslConfig
276+
* @see com.rabbitmq.client.SaslConfig
277+
*/
278+
public void setSaslConfig(SaslConfig saslConfig) {
279+
this.saslConfig = saslConfig;
280+
}
281+
264282
/**
265283
* Retrieve the socket factory used to make connections with.
266284
*/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.auth.callback.CallbackHandler;
4+
import javax.security.sasl.Sasl;
5+
import javax.security.sasl.SaslClient;
6+
import javax.security.sasl.SaslException;
7+
import java.util.Map;
8+
9+
/**
10+
* Default implementation of SaslConfig that uses the standard Java
11+
* algorithm for selecting a sasl client.
12+
* @see com.rabbitmq.client.ConnectionFactory
13+
*/
14+
public class DefaultSaslConfig implements SaslConfig {
15+
private ConnectionFactory factory;
16+
private String authorizationId;
17+
private Map<String,?> mechanismProperties;
18+
private CallbackHandler callbackHandler;
19+
20+
public DefaultSaslConfig(ConnectionFactory factory) {
21+
this.factory = factory;
22+
callbackHandler = new UsernamePasswordCallbackHandler(factory);
23+
}
24+
25+
public void setAuthorizationId(String authorizationId) {
26+
this.authorizationId = authorizationId;
27+
}
28+
29+
public void setMechanismProperties(Map<String, ?> mechanismProperties) {
30+
this.mechanismProperties = mechanismProperties;
31+
}
32+
33+
public void setCallbackHandler(CallbackHandler callbackHandler) {
34+
this.callbackHandler = callbackHandler;
35+
}
36+
37+
public SaslClient getSaslClient(String[] mechanisms) throws SaslException {
38+
return Sasl.createSaslClient(mechanisms, authorizationId, "AMQP",
39+
factory.getHost(), mechanismProperties, callbackHandler);
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.sasl.SaslClient;
4+
import javax.security.sasl.SaslException;
5+
6+
/**
7+
* This interface represents a hook to allow you to control how exactly
8+
* a sasl client is selected during authentication.
9+
* @see com.rabbitmq.client.ConnectionFactory
10+
*/
11+
public interface SaslConfig {
12+
SaslClient getSaslClient(String[] mechanisms) throws SaslException;
13+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.rabbitmq.client;
2+
3+
import javax.security.auth.callback.Callback;
4+
import javax.security.auth.callback.CallbackHandler;
5+
import javax.security.auth.callback.NameCallback;
6+
import javax.security.auth.callback.PasswordCallback;
7+
import javax.security.auth.callback.UnsupportedCallbackException;
8+
import java.io.IOException;
9+
10+
public class UsernamePasswordCallbackHandler implements CallbackHandler {
11+
private ConnectionFactory factory;
12+
public UsernamePasswordCallbackHandler(ConnectionFactory factory) {
13+
this.factory = factory;
14+
}
15+
16+
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
17+
for (Callback callback: callbacks) {
18+
if (callback instanceof NameCallback) {
19+
NameCallback nc = (NameCallback)callback;
20+
nc.setName(factory.getUsername());
21+
22+
} else if (callback instanceof PasswordCallback) {
23+
PasswordCallback pc = (PasswordCallback)callback;
24+
pc.setPassword(factory.getPassword().toCharArray());
25+
26+
} else {
27+
throw new UnsupportedCallbackException
28+
(callback, "Unrecognized Callback");
29+
}
30+
}
31+
}
32+
}

src/com/rabbitmq/client/impl/AMQConnection.java

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import com.rabbitmq.utility.BlockingCell;
5252
import com.rabbitmq.utility.Utility;
5353

54+
import javax.security.sasl.SaslClient;
55+
5456
/**
5557
* Concrete class representing and managing an AMQP connection to a broker.
5658
* <p>
@@ -152,7 +154,7 @@ public void ensureIsOpen()
152154
*/
153155
private int _heartbeat;
154156

155-
private final String _username, _password, _virtualHost;
157+
private final String _virtualHost;
156158
private final int _requestedChannelMax, _requestedFrameMax, _requestedHeartbeat;
157159
private final Map<String, Object> _clientProperties;
158160

@@ -200,8 +202,6 @@ public AMQConnection(ConnectionFactory factory,
200202
{
201203
checkPreconditions();
202204

203-
_username = factory.getUsername();
204-
_password = factory.getPassword();
205205
_virtualHost = factory.getVirtualHost();
206206
_requestedChannelMax = factory.getRequestedChannelMax();
207207
_requestedFrameMax = factory.getRequestedFrameMax();
@@ -255,8 +255,9 @@ public void start()
255255
ml.setName("AMQP Connection " + getHost() + ":" + getPort());
256256
ml.start();
257257

258+
AMQP.Connection.Start connStart = null;
258259
try {
259-
AMQP.Connection.Start connStart =
260+
connStart =
260261
(AMQP.Connection.Start) connStartBlocker.getReply().getMethod();
261262

262263
_serverProperties = connStart.getServerProperties();
@@ -274,18 +275,42 @@ public void start()
274275
throw AMQChannel.wrap(sse);
275276
}
276277

277-
LongString saslResponse = LongStringHelper.asLongString("\0" + _username +
278-
"\0" + _password);
279-
AMQImpl.Connection.StartOk startOk =
280-
new AMQImpl.Connection.StartOk(_clientProperties, "PLAIN",
281-
saslResponse, "en_US");
278+
String[] mechanisms = connStart.getMechanisms().toString().split(" ");
279+
SaslClient sc = _factory.getSaslConfig().getSaslClient(mechanisms);
280+
if (sc == null) {
281+
throw new IOException("No compatible authentication mechanism found - " +
282+
"server offered [" + connStart.getMechanisms() + "]");
283+
}
282284

285+
LongString challenge = null;
286+
LongString response = LongStringHelper.asLongString(
287+
sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null);
283288
AMQP.Connection.Tune connTune = null;
289+
do {
290+
Method method = (challenge == null)
291+
? new AMQImpl.Connection.StartOk(_clientProperties,
292+
sc.getMechanismName(),
293+
response, "en_US")
294+
: new AMQImpl.Connection.SecureOk(response);
284295

285-
try {
286-
connTune = (AMQP.Connection.Tune) _channel0.rpc(startOk).getMethod();
287-
} catch (ShutdownSignalException e) {
288-
throw new PossibleAuthenticationFailureException(e);
296+
try {
297+
Method serverResponse = _channel0.rpc(method).getMethod();
298+
if (serverResponse instanceof AMQP.Connection.Tune) {
299+
connTune = (AMQP.Connection.Tune) serverResponse;
300+
} else {
301+
challenge = ((AMQP.Connection.Secure) serverResponse).getChallenge();
302+
response = LongStringHelper.asLongString(sc.evaluateChallenge(challenge.getBytes()));
303+
}
304+
} catch (ShutdownSignalException e) {
305+
throw new PossibleAuthenticationFailureException(e);
306+
}
307+
} while (connTune == null);
308+
309+
sc.dispose();
310+
311+
if (!sc.isComplete()) {
312+
throw new RuntimeException(sc.getMechanismName() +
313+
" did not complete, server thought it did");
289314
}
290315

291316
int channelMax =
@@ -714,6 +739,6 @@ public void close(int closeCode,
714739
}
715740

716741
@Override public String toString() {
717-
return "amqp://" + _username + "@" + getHost() + ":" + getPort() + _virtualHost;
742+
return "amqp://" + _factory.getUsername() + "@" + getHost() + ":" + getPort() + _virtualHost;
718743
}
719744
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.rabbitmq.client.impl;
2+
3+
import com.rabbitmq.client.ConnectionFactory;
4+
import com.rabbitmq.client.SaslConfig;
5+
import com.rabbitmq.client.UsernamePasswordCallbackHandler;
6+
7+
import javax.security.auth.callback.Callback;
8+
import javax.security.auth.callback.CallbackHandler;
9+
import javax.security.auth.callback.NameCallback;
10+
import javax.security.auth.callback.PasswordCallback;
11+
import javax.security.auth.callback.UnsupportedCallbackException;
12+
import javax.security.sasl.SaslClient;
13+
import javax.security.sasl.SaslException;
14+
import java.io.IOException;
15+
import java.io.UnsupportedEncodingException;
16+
import java.util.Arrays;
17+
18+
/**
19+
Provides equivalent security to PLAIN but demos use of Connection.Secure(Ok)
20+
START-OK: Username
21+
SECURE: "Please tell me your password"
22+
SECURE-OK: Password
23+
*/
24+
25+
public class CRDemoSaslClient implements SaslClient {
26+
private static final String NAME = "RABBIT-CR-DEMO";
27+
28+
private CallbackHandler handler;
29+
private int round = 0;
30+
31+
public CRDemoSaslClient(CallbackHandler handler) {
32+
this.handler = handler;
33+
}
34+
35+
public String getMechanismName() {
36+
return NAME;
37+
}
38+
39+
public boolean hasInitialResponse() {
40+
return true;
41+
}
42+
43+
public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
44+
byte[] resp;
45+
try {
46+
if (round == 0) {
47+
NameCallback nc = new NameCallback("Name:");
48+
handler.handle(new Callback[]{nc});
49+
resp = nc.getName().getBytes("utf-8");
50+
} else {
51+
PasswordCallback pc = new PasswordCallback("Password:", false);
52+
handler.handle(new Callback[]{pc});
53+
resp = ("My password is " + new String(pc.getPassword())).getBytes("utf-8");
54+
}
55+
} catch (UnsupportedEncodingException e) {
56+
throw new RuntimeException(e);
57+
} catch (UnsupportedCallbackException e) {
58+
throw new SaslException("Bad callback", e);
59+
} catch (IOException e) {
60+
throw new SaslException("IO Exception", e);
61+
}
62+
63+
round++;
64+
return resp;
65+
}
66+
67+
public boolean isComplete() {
68+
return round == 2;
69+
}
70+
71+
public byte[] unwrap(byte[] bytes, int i, int i1) throws SaslException {
72+
throw new UnsupportedOperationException();
73+
}
74+
75+
public byte[] wrap(byte[] bytes, int i, int i1) throws SaslException {
76+
throw new UnsupportedOperationException();
77+
}
78+
79+
public Object getNegotiatedProperty(String s) {
80+
throw new UnsupportedOperationException();
81+
}
82+
83+
public void dispose() throws SaslException {
84+
// NOOP
85+
}
86+
87+
public static class CRDemoSaslConfig implements SaslConfig {
88+
private ConnectionFactory factory;
89+
90+
public CRDemoSaslConfig(ConnectionFactory factory) {
91+
this.factory = factory;
92+
}
93+
94+
public SaslClient getSaslClient(String[] mechanisms) throws SaslException {
95+
if (Arrays.asList(mechanisms).contains(NAME)) {
96+
return new CRDemoSaslClient(new UsernamePasswordCallbackHandler(factory));
97+
}
98+
else {
99+
return null;
100+
}
101+
}
102+
}
103+
}

test/src/com/rabbitmq/client/test/functional/FunctionalTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static TestSuite suite() {
7070
suite.addTestSuite(Confirm.class);
7171
suite.addTestSuite(UnexpectedFrames.class);
7272
suite.addTestSuite(PerQueueTTL.class);
73+
suite.addTestSuite(SaslMechanisms.class);
7374

7475
return suite;
7576
}

0 commit comments

Comments
 (0)