Skip to content

Commit 39dcf46

Browse files
committed
Added CRAM-MD5 SASL support
1 parent bdb11c3 commit 39dcf46

2 files changed

Lines changed: 97 additions & 22 deletions

File tree

src/main/com/mongodb/DBPort.java

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,23 @@
1919
package com.mongodb;
2020

2121
import com.mongodb.util.ThreadUtil;
22+
import com.mongodb.util.Util;
2223
import org.ietf.jgss.GSSCredential;
2324
import org.ietf.jgss.GSSException;
2425
import org.ietf.jgss.GSSManager;
2526
import org.ietf.jgss.GSSName;
2627
import org.ietf.jgss.Oid;
2728

29+
import javax.security.auth.callback.Callback;
30+
import javax.security.auth.callback.CallbackHandler;
31+
import javax.security.auth.callback.NameCallback;
32+
import javax.security.auth.callback.PasswordCallback;
33+
import javax.security.auth.callback.UnsupportedCallbackException;
2834
import javax.security.sasl.Sasl;
2935
import javax.security.sasl.SaslClient;
3036
import javax.security.sasl.SaslException;
3137
import java.io.BufferedInputStream;
38+
import java.io.ByteArrayOutputStream;
3239
import java.io.IOException;
3340
import java.io.InputStream;
3441
import java.io.OutputStream;
@@ -308,10 +315,15 @@ protected void close(){
308315
}
309316

310317
void checkAuth(DB db) throws IOException {
311-
// TODO: add support for retry when credentials ticket times out
312318
if (db.getMongo().getCredentials() != null) {
313319
if (_saslAuthenticator == null) {
314-
_saslAuthenticator = new GSSAPIAuthenticator(db.getMongo());
320+
if (db.getMongo().getCredentials().getMechanism().equals(MongoClientCredentials.GSSAPI_MECHANISM)) {
321+
_saslAuthenticator = new GSSAPIAuthenticator(db.getMongo());
322+
} else if (db.getMongo().getCredentials().getMechanism().equals(MongoClientCredentials.CRAM_MD5_MECHANISM)) {
323+
_saslAuthenticator = new CRAMMD5Authenticator(db.getMongo());
324+
} else {
325+
throw new MongoException("Unsupported authentication mechanism: " + db.getMongo().getCredentials().getMechanism());
326+
}
315327
}
316328
_saslAuthenticator.acquirePrivilegeForDatabase(db);
317329
}
@@ -376,16 +388,78 @@ class ActiveState {
376388
final String threadName;
377389
}
378390

391+
class CRAMMD5Authenticator extends SaslAuthenticator {
392+
public static final String CRAM_MD5_MECHANISM = "CRAM-MD5";
393+
394+
CRAMMD5Authenticator(final Mongo mongo) {
395+
super(mongo);
396+
397+
if (!mongo.getCredentials().getMechanism().equals(MongoClientCredentials.CRAM_MD5_MECHANISM)) {
398+
throw new MongoException("Incorrect mechanism: " + mongo.getCredentials().getMechanism());
399+
}
400+
}
401+
402+
@Override
403+
protected SaslClient createSaslClient() {
404+
try {
405+
return Sasl.createSaslClient(new String[]{CRAM_MD5_MECHANISM}, mongo.getCredentials().getUserName(), MONGODB_PROTOCOL,
406+
serverAddress().getHost(), null, new CredentialsHandlingCallbackHandler());
407+
} catch (SaslException e) {
408+
throw new MongoException("Exception initializing SASL client", e);
409+
}
410+
}
411+
412+
@Override
413+
protected Object getMechanism() {
414+
return CRAM_MD5_MECHANISM;
415+
}
416+
417+
class CredentialsHandlingCallbackHandler implements CallbackHandler {
418+
419+
public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
420+
for (Callback callback : callbacks) {
421+
if (callback instanceof NameCallback) {
422+
NameCallback nameCallback = (NameCallback) callback;
423+
nameCallback.setName(mongo.getCredentials().getDatabase() + "$" + mongo.getCredentials().getUserName());
424+
}
425+
if (callback instanceof PasswordCallback) {
426+
PasswordCallback passwordCallback = (PasswordCallback) callback;
427+
String hashedPassword = new String(createHash(mongo.getCredentials().getUserName(),
428+
mongo.getCredentials().getPassword()));
429+
passwordCallback.setPassword(hashedPassword.toCharArray());
430+
}
431+
}
432+
}
433+
}
434+
// TODO: copoied from DB.AuthenticationCredentials
435+
byte[] createHash( String userName , char[] password ){
436+
ByteArrayOutputStream bout = new ByteArrayOutputStream( userName.length() + 20 + password.length );
437+
try {
438+
bout.write(userName.getBytes());
439+
bout.write( ":mongo:".getBytes() );
440+
for (final char ch : password) {
441+
if (ch >= 128)
442+
throw new IllegalArgumentException("can't handle non-ascii passwords yet");
443+
bout.write((byte) ch);
444+
}
445+
}
446+
catch ( IOException ioe ){
447+
throw new RuntimeException( "impossible" , ioe );
448+
}
449+
return Util.hexMD5(bout.toByteArray()).getBytes();
450+
}
451+
452+
}
453+
379454
class GSSAPIAuthenticator extends SaslAuthenticator {
380455
public static final String GSSAPI_OID = "1.2.840.113554.1.2.2";
381456
public static final String GSSAPI_MECHANISM = "GSSAPI";
382-
public static final String MONGODB_PROTOCOL = "mongodb";
383457

384458
GSSAPIAuthenticator(final Mongo mongo) {
385459
super(mongo);
386460

387461
if (!mongo.getCredentials().getMechanism().equals(MongoClientCredentials.GSSAPI_MECHANISM)) {
388-
throw new MongoException("Unsupported authentication mechanism: " + mongo.getCredentials().getMechanism());
462+
throw new MongoException("Incorrect mechanism: " + mongo.getCredentials().getMechanism());
389463
}
390464
}
391465

@@ -404,6 +478,11 @@ protected SaslClient createSaslClient() {
404478
}
405479
}
406480

481+
@Override
482+
protected Object getMechanism() {
483+
return GSSAPI_MECHANISM;
484+
}
485+
407486
private GSSCredential getGSSCredential(String userName) throws GSSException {
408487
Oid krb5Mechanism = new Oid(GSSAPI_OID);
409488
GSSManager manager = GSSManager.getInstance();
@@ -414,6 +493,8 @@ private GSSCredential getGSSCredential(String userName) throws GSSException {
414493
}
415494

416495
abstract class SaslAuthenticator {
496+
public static final String MONGODB_PROTOCOL = "mongodb";
497+
417498
protected final Mongo mongo;
418499
private final Map<DB, Boolean> authorizeDatabases = new ConcurrentHashMap<DB, Boolean>();
419500
private volatile boolean authenticated;
@@ -471,15 +552,15 @@ void authenticate() {
471552

472553
protected abstract SaslClient createSaslClient();
473554

555+
protected abstract Object getMechanism();
556+
474557
private CommandResult sendSaslStart(final byte[] outToken) throws IOException {
475-
// System.out.println("Sending saslStart with num bytes: " + outToken.length);
476-
DB adminDB = mongo.getDB("admin");
477-
DBObject cmd = new BasicDBObject("saslStart", 1).append("mechanism", "GSSAPI").append("payload", outToken);
478-
return runCommand(adminDB, cmd);
558+
DBObject cmd = new BasicDBObject("saslStart", 1).append("mechanism", getMechanism()).append("payload",
559+
outToken != null ? outToken : new byte[0]);
560+
return runCommand(mongo.getDB("admin"), cmd);
479561
}
480562

481563
private CommandResult sendSaslContinue(final int conversationId, final byte[] outToken) throws IOException {
482-
// System.out.println("Sending saslContinue with num bytes: " + outToken.length);
483564
DB adminDB = mongo.getDB("admin");
484565
DBObject cmd = new BasicDBObject("saslContinue", 1).append("conversationId", conversationId).
485566
append("payload", outToken);

src/main/com/mongodb/MongoClientCredentials.java

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,26 @@
2525
public class MongoClientCredentials {
2626
public static final String MONGODB_MECHANISM = "mongodb";
2727
public static final String GSSAPI_MECHANISM = "GSSAPI";
28+
public static final String CRAM_MD5_MECHANISM = "CRAM-MD5";
2829

2930
private final String mechanism;
3031
private final String userName;
3132
private final char[] password;
3233
private final String database;
3334

35+
public MongoClientCredentials(final String userName, final char[] password, String mechanism) {
36+
this(userName, password, mechanism, null);
37+
}
3438

35-
public MongoClientCredentials(final String userName, final char[] password) {
36-
this.userName = userName;
37-
this.password = password;
38-
database = null;
39-
mechanism = MONGODB_MECHANISM;
39+
public MongoClientCredentials(final String userName, final String mechanism) {
40+
this(userName, null, mechanism);
4041
}
4142

42-
public MongoClientCredentials(final String userName, final char[] password, String database) {
43+
public MongoClientCredentials(final String userName, final char[] password, String mechanism, String database) {
4344
this.userName = userName;
4445
this.password = password;
4546
this.database = database;
46-
mechanism = MONGODB_MECHANISM;
47-
}
48-
49-
public MongoClientCredentials(final String userName, final String mechanism) {
50-
this.userName = userName;
5147
this.mechanism = mechanism;
52-
this.password = null;
53-
this.database = null;
5448
}
5549

5650
public String getMechanism() {

0 commit comments

Comments
 (0)