mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 03:52:06 +01:00
Ads support for SCRAM-SHA-1-PLUS
Related to SMACK-398.
This commit is contained in:
parent
44744de003
commit
1f1bc236fd
14 changed files with 252 additions and 63 deletions
|
@ -214,7 +214,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
|
protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
|
||||||
SmackException, IOException, InterruptedException {
|
SmackException, IOException, InterruptedException {
|
||||||
// Authenticate using SASL
|
// Authenticate using SASL
|
||||||
saslAuthentication.authenticate(username, password, config.getAuthzid());
|
saslAuthentication.authenticate(username, password, config.getAuthzid(), null);
|
||||||
|
|
||||||
bindResourceAndEstablishSession(resource);
|
bindResourceAndEstablishSession(resource);
|
||||||
|
|
||||||
|
|
|
@ -462,9 +462,9 @@ public abstract class ConnectionConfiguration {
|
||||||
* @return true if the given SASL mechanism is enabled, false otherwise.
|
* @return true if the given SASL mechanism is enabled, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabledSaslMechanism(String saslMechanism) {
|
public boolean isEnabledSaslMechanism(String saslMechanism) {
|
||||||
// If enabledSaslMechanisms is not set, then all mechanisms are enabled per default
|
// If enabledSaslMechanisms is not set, then all mechanisms which are not blacklisted are enabled per default.
|
||||||
if (enabledSaslMechanisms == null) {
|
if (enabledSaslMechanisms == null) {
|
||||||
return true;
|
return !SASLAuthentication.getBlacklistedSASLMechanisms().contains(saslMechanism);
|
||||||
}
|
}
|
||||||
return enabledSaslMechanisms.contains(saslMechanism);
|
return enabledSaslMechanisms.contains(saslMechanism);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,13 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.packet.Mechanisms;
|
import org.jivesoftware.smack.packet.Mechanisms;
|
||||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
import org.jivesoftware.smack.sasl.SASLErrorException;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -65,6 +67,11 @@ public final class SASLAuthentication {
|
||||||
|
|
||||||
private static final Set<String> BLACKLISTED_MECHANISMS = new HashSet<String>();
|
private static final Set<String> BLACKLISTED_MECHANISMS = new HashSet<String>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Blacklist SCRAM-SHA-1-PLUS for now.
|
||||||
|
blacklistSASLMechanism(ScramSha1PlusMechanism.NAME);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new SASL mechanism.
|
* Registers a new SASL mechanism.
|
||||||
*
|
*
|
||||||
|
@ -137,9 +144,7 @@ public final class SASLAuthentication {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getBlacklistedSASLMechanisms() {
|
public static Set<String> getBlacklistedSASLMechanisms() {
|
||||||
synchronized(BLACKLISTED_MECHANISMS) {
|
return Collections.unmodifiableSet(BLACKLISTED_MECHANISMS);
|
||||||
return new HashSet<String>(BLACKLISTED_MECHANISMS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AbstractXMPPConnection connection;
|
private final AbstractXMPPConnection connection;
|
||||||
|
@ -173,13 +178,14 @@ public final class SASLAuthentication {
|
||||||
* @param username the username that is authenticating with the server.
|
* @param username the username that is authenticating with the server.
|
||||||
* @param password the password to send to the server.
|
* @param password the password to send to the server.
|
||||||
* @param authzid the authorization identifier (typically null).
|
* @param authzid the authorization identifier (typically null).
|
||||||
|
* @param sslSession the optional SSL/TLS session (if one was established)
|
||||||
* @throws XMPPErrorException
|
* @throws XMPPErrorException
|
||||||
* @throws SASLErrorException
|
* @throws SASLErrorException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String username, String password, EntityBareJid authzid)
|
public void authenticate(String username, String password, EntityBareJid authzid, SSLSession sslSession)
|
||||||
throws XMPPErrorException, SASLErrorException, IOException,
|
throws XMPPErrorException, SASLErrorException, IOException,
|
||||||
SmackException, InterruptedException {
|
SmackException, InterruptedException {
|
||||||
currentMechanism = selectMechanism(authzid);
|
currentMechanism = selectMechanism(authzid);
|
||||||
|
@ -189,10 +195,10 @@ public final class SASLAuthentication {
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (callbackHandler != null) {
|
if (callbackHandler != null) {
|
||||||
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid);
|
currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid);
|
currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession);
|
||||||
}
|
}
|
||||||
final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout();
|
final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout();
|
||||||
while (!authenticationSuccessful && saslException == null) {
|
while (!authenticationSuccessful && saslException == null) {
|
||||||
|
@ -340,7 +346,7 @@ public final class SASLAuthentication {
|
||||||
}
|
}
|
||||||
if (serverMechanisms.contains(mechanismName)) {
|
if (serverMechanisms.contains(mechanismName)) {
|
||||||
// Create a new instance of the SASLMechanism for every authentication attempt.
|
// Create a new instance of the SASLMechanism for every authentication attempt.
|
||||||
return mechanism.instanceForAuthentication(connection);
|
return mechanism.instanceForAuthentication(connection, configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2014 Florian Schmaus
|
* Copyright 2003-2007 Jive Software, 2014-2016 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -35,6 +35,7 @@ import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
|
import org.jivesoftware.smack.sasl.core.SASLAnonymous;
|
||||||
import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism;
|
import org.jivesoftware.smack.sasl.core.SASLXOauth2Mechanism;
|
||||||
import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism;
|
import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism;
|
||||||
|
import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism;
|
||||||
import org.jivesoftware.smack.util.FileUtils;
|
import org.jivesoftware.smack.util.FileUtils;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
@ -137,6 +138,7 @@ public final class SmackInitialization {
|
||||||
}
|
}
|
||||||
|
|
||||||
SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism());
|
SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism());
|
||||||
|
SASLAuthentication.registerSASLMechanism(new ScramSha1PlusMechanism());
|
||||||
SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism());
|
SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism());
|
||||||
SASLAuthentication.registerSASLMechanism(new SASLAnonymous());
|
SASLAuthentication.registerSASLMechanism(new SASLAnonymous());
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2014 Florian Schmaus
|
* Copyright 2003-2007 Jive Software, 2014-2016 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
@ -27,44 +28,21 @@ import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.security.auth.callback.CallbackHandler;
|
import javax.security.auth.callback.CallbackHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for SASL mechanisms. Subclasses must implement these methods:
|
* Base class for SASL mechanisms.
|
||||||
* <ul>
|
|
||||||
* <li>{@link #getName()} -- returns the common name of the SASL mechanism.</li>
|
|
||||||
* </ul>
|
|
||||||
* Subclasses will likely want to implement their own versions of these methods:
|
* Subclasses will likely want to implement their own versions of these methods:
|
||||||
* <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid)} -- Initiate authentication stanza using the
|
* <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid, SSLSession)} -- Initiate authentication stanza using the
|
||||||
* deprecated method.</li>
|
* deprecated method.</li>
|
||||||
* <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid)} -- Initiate authentication stanza
|
* <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} -- Initiate authentication stanza
|
||||||
* using the CallbackHandler method.</li>
|
* using the CallbackHandler method.</li>
|
||||||
* <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
|
* <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* Basic XMPP SASL authentication steps:
|
|
||||||
* 1. Client authentication initialization, stanza sent to the server (Base64 encoded):
|
|
||||||
* <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>
|
|
||||||
* 2. Server sends back to the client the challenge response (Base64 encoded)
|
|
||||||
* sample:
|
|
||||||
* realm=<sasl server realm>,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess
|
|
||||||
* 3. The client responds back to the server (Base 64 encoded):
|
|
||||||
* sample:
|
|
||||||
* username=<userid>,realm=<sasl server realm from above>,nonce="OA6MG9tEQGm2hh",
|
|
||||||
* cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,
|
|
||||||
* digest-uri=<digesturi>,
|
|
||||||
* response=d388dad90d4bbd760a152321f2143af7,
|
|
||||||
* charset=utf-8,
|
|
||||||
* authzid=<id>
|
|
||||||
* 4. The server evaluates if the user is present and contained in the REALM
|
|
||||||
* if successful it sends: <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/> (Base64 encoded)
|
|
||||||
* if not successful it sends:
|
|
||||||
* sample:
|
|
||||||
* <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
|
|
||||||
* cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
|
|
||||||
* </challenge>
|
|
||||||
*
|
*
|
||||||
* @author Jay Kline
|
* @author Jay Kline
|
||||||
|
* @author Florian Schmaus
|
||||||
*/
|
*/
|
||||||
public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
|
|
||||||
|
@ -92,6 +70,8 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
|
|
||||||
protected XMPPConnection connection;
|
protected XMPPConnection connection;
|
||||||
|
|
||||||
|
protected ConnectionConfiguration connectionConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a
|
* Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a
|
||||||
* "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an
|
* "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an
|
||||||
|
@ -120,10 +100,15 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
protected String password;
|
protected String password;
|
||||||
protected String host;
|
protected String host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The used SSL/TLS session (if any).
|
||||||
|
*/
|
||||||
|
protected SSLSession sslSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
|
* Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of
|
||||||
* authentication is not recommended, since it is very inflexible. Use
|
* authentication is not recommended, since it is very inflexible. Use
|
||||||
* {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid)} whenever possible.
|
* {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} whenever possible.
|
||||||
*
|
*
|
||||||
* Explanation of auth stanza:
|
* Explanation of auth stanza:
|
||||||
*
|
*
|
||||||
|
@ -165,17 +150,20 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
* serviceName format is: host [ "/" serv-name ] as per RFC-2831
|
* serviceName format is: host [ "/" serv-name ] as per RFC-2831
|
||||||
* @param password the password for this account.
|
* @param password the password for this account.
|
||||||
* @param authzid the optional authorization identity.
|
* @param authzid the optional authorization identity.
|
||||||
|
* @param sslSession the optional SSL/TLS session (if one was established)
|
||||||
* @throws SmackException If a network error occurs while authenticating.
|
* @throws SmackException If a network error occurs while authenticating.
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public final void authenticate(String username, String host, DomainBareJid serviceName, String password, EntityBareJid authzid)
|
public final void authenticate(String username, String host, DomainBareJid serviceName, String password,
|
||||||
|
EntityBareJid authzid, SSLSession sslSession)
|
||||||
throws SmackException, NotConnectedException, InterruptedException {
|
throws SmackException, NotConnectedException, InterruptedException {
|
||||||
this.authenticationId = username;
|
this.authenticationId = username;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.authorizationId = authzid;
|
this.authorizationId = authzid;
|
||||||
|
this.sslSession = sslSession;
|
||||||
assert(authorizationId == null || authzidSupported());
|
assert(authorizationId == null || authzidSupported());
|
||||||
authenticateInternal();
|
authenticateInternal();
|
||||||
authenticate();
|
authenticate();
|
||||||
|
@ -195,15 +183,17 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
* @param serviceName the xmpp service location
|
* @param serviceName the xmpp service location
|
||||||
* @param cbh the CallbackHandler to obtain user information.
|
* @param cbh the CallbackHandler to obtain user information.
|
||||||
* @param authzid the optional authorization identity.
|
* @param authzid the optional authorization identity.
|
||||||
|
* @param sslSession the optional SSL/TLS session (if one was established)
|
||||||
* @throws SmackException
|
* @throws SmackException
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid)
|
public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid, SSLSession sslSession)
|
||||||
throws SmackException, NotConnectedException, InterruptedException {
|
throws SmackException, NotConnectedException, InterruptedException {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
this.authorizationId = authzid;
|
this.authorizationId = authzid;
|
||||||
|
this.sslSession = sslSession;
|
||||||
assert(authorizationId == null || authzidSupported());
|
assert(authorizationId == null || authzidSupported());
|
||||||
authenticateInternal(cbh);
|
authenticateInternal(cbh);
|
||||||
authenticate();
|
authenticate();
|
||||||
|
@ -290,9 +280,10 @@ public abstract class SASLMechanism implements Comparable<SASLMechanism> {
|
||||||
|
|
||||||
public abstract void checkIfSuccessfulOrThrow() throws SmackException;
|
public abstract void checkIfSuccessfulOrThrow() throws SmackException;
|
||||||
|
|
||||||
public SASLMechanism instanceForAuthentication(XMPPConnection connection) {
|
public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) {
|
||||||
SASLMechanism saslMechansim = newInstance();
|
SASLMechanism saslMechansim = newInstance();
|
||||||
saslMechansim.connection = connection;
|
saslMechansim.connection = connection;
|
||||||
|
saslMechansim.connectionConfiguration = connectionConfiguration;
|
||||||
return saslMechansim;
|
return saslMechansim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,8 @@ import org.jivesoftware.smack.util.MAC;
|
||||||
|
|
||||||
public class SCRAMSHA1Mechanism extends ScramMechanism {
|
public class SCRAMSHA1Mechanism extends ScramMechanism {
|
||||||
|
|
||||||
public SCRAMSHA1Mechanism() {
|
static {
|
||||||
super(new ScramHmac() {
|
SHA_1_SCRAM_HMAC = new ScramHmac() {
|
||||||
@Override
|
@Override
|
||||||
public String getHmacName() {
|
public String getHmacName() {
|
||||||
return "SHA-1";
|
return "SHA-1";
|
||||||
|
@ -33,7 +33,16 @@ public class SCRAMSHA1Mechanism extends ScramMechanism {
|
||||||
public byte[] hmac(byte[] key, byte[] str) throws InvalidKeyException {
|
public byte[] hmac(byte[] key, byte[] str) throws InvalidKeyException {
|
||||||
return MAC.hmacsha1(key, str);
|
return MAC.hmacsha1(key, str);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
NAME = (new SCRAMSHA1Mechanism()).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String NAME;
|
||||||
|
|
||||||
|
static final ScramHmac SHA_1_SCRAM_HMAC;
|
||||||
|
|
||||||
|
public SCRAMSHA1Mechanism() {
|
||||||
|
super(SHA_1_SCRAM_HMAC);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.sasl.SASLMechanism;
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
import org.jivesoftware.smack.util.ByteUtils;
|
import org.jivesoftware.smack.util.ByteUtils;
|
||||||
import org.jivesoftware.smack.util.SHA1;
|
import org.jivesoftware.smack.util.SHA1;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||||
import org.jxmpp.util.cache.Cache;
|
import org.jxmpp.util.cache.Cache;
|
||||||
import org.jxmpp.util.cache.LruCache;
|
import org.jxmpp.util.cache.LruCache;
|
||||||
|
@ -145,7 +146,8 @@ public abstract class ScramMechanism extends SASLMechanism {
|
||||||
// Parsing and error checking is done, we can now begin to calculate the values
|
// Parsing and error checking is done, we can now begin to calculate the values
|
||||||
|
|
||||||
// First the client-final-message-without-proof
|
// First the client-final-message-without-proof
|
||||||
String clientFinalMessageWithoutProof = "c=" + Base64.encode(getGS2Header()) + ",r=" + rvalue;
|
String channelBinding = "c=" + Base64.encodeToString(getCBindInput());
|
||||||
|
String clientFinalMessageWithoutProof = channelBinding + ",r=" + rvalue;
|
||||||
|
|
||||||
// AuthMessage := client-first-message-bare + "," + server-first-message + "," +
|
// AuthMessage := client-first-message-bare + "," + server-first-message + "," +
|
||||||
// client-final-message-without-proof
|
// client-final-message-without-proof
|
||||||
|
@ -154,7 +156,9 @@ public abstract class ScramMechanism extends SASLMechanism {
|
||||||
|
|
||||||
// RFC 5802 § 5.1 "Note that a client implementation MAY cache ClientKey&ServerKey … for later reauthentication …
|
// RFC 5802 § 5.1 "Note that a client implementation MAY cache ClientKey&ServerKey … for later reauthentication …
|
||||||
// as it is likely that the server is going to advertise the same salt value upon reauthentication."
|
// as it is likely that the server is going to advertise the same salt value upon reauthentication."
|
||||||
final String cacheKey = password + ',' + salt;
|
// Note that we also mangle the mechanism's name into the cache key, since the cache is used by multiple
|
||||||
|
// mechanisms.
|
||||||
|
final String cacheKey = password + ',' + salt + ',' + getName();
|
||||||
byte[] serverKey, clientKey;
|
byte[] serverKey, clientKey;
|
||||||
Keys keys = CACHE.get(cacheKey);
|
Keys keys = CACHE.get(cacheKey);
|
||||||
if (keys == null) {
|
if (keys == null) {
|
||||||
|
@ -211,7 +215,40 @@ public abstract class ScramMechanism extends SASLMechanism {
|
||||||
if (authorizationId != null) {
|
if (authorizationId != null) {
|
||||||
authzidPortion = "a=" + authorizationId;
|
authzidPortion = "a=" + authorizationId;
|
||||||
}
|
}
|
||||||
return "n," + authzidPortion + ",";
|
|
||||||
|
String cbName = getChannelBindingName();
|
||||||
|
assert(StringUtils.isNotEmpty(cbName));
|
||||||
|
|
||||||
|
return cbName + ',' + authzidPortion + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
private final byte[] getCBindInput() throws SmackException {
|
||||||
|
byte[] cbindData = getChannelBindingData();
|
||||||
|
byte[] gs2Header = toBytes(getGS2Header());
|
||||||
|
|
||||||
|
if (cbindData == null) {
|
||||||
|
return gs2Header;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteUtils.concact(gs2Header, cbindData);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getChannelBindingName() {
|
||||||
|
if (sslSession != null && connectionConfiguration.isEnabledSaslMechanism(getName() + "-PLUS")) {
|
||||||
|
// Announce that we support Channel Binding, i.e., the '-PLUS' flavor of this SASL mechanism, but that we
|
||||||
|
// believe the server does not.
|
||||||
|
return "y";
|
||||||
|
}
|
||||||
|
return "n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the Channel Binding data.
|
||||||
|
* @throws SmackException
|
||||||
|
*/
|
||||||
|
protected byte[] getChannelBindingData() throws SmackException {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<Character, String> parseAttributes(String string) throws SmackException {
|
private static Map<Character, String> parseAttributes(String string) throws SmackException {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2016 Florian Schmaus
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.sasl.core;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.util.TLSUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SCRAM-X-PLUS implementation. Due limitations of the Java API, this mechanism only supports the 'tls-server-end-point'
|
||||||
|
* channel binding type. But on the other hand, the other relevant channel binding type 'tls-unique' has some flaws (see
|
||||||
|
* 3SHAKE, RFC 7627).
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
public abstract class ScramPlusMechanism extends ScramMechanism {
|
||||||
|
|
||||||
|
protected ScramPlusMechanism(ScramHmac scramHmac) {
|
||||||
|
super(scramHmac);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return super.getName() + "-PLUS";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getChannelBindingName() {
|
||||||
|
return "p=tls-server-end-point";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] getChannelBindingData() throws SmackException {
|
||||||
|
byte[] cbData;
|
||||||
|
try {
|
||||||
|
cbData = TLSUtils.getChannelBindingTlsServerEndPoint(sslSession);
|
||||||
|
}
|
||||||
|
catch (SSLPeerUnverifiedException | CertificateEncodingException | NoSuchAlgorithmException e) {
|
||||||
|
throw new SmackException(e);
|
||||||
|
}
|
||||||
|
return cbData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2016 Florian Schmaus
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.sasl.core;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.sasl.SASLMechanism;
|
||||||
|
|
||||||
|
public class ScramSha1PlusMechanism extends ScramPlusMechanism {
|
||||||
|
|
||||||
|
static {
|
||||||
|
NAME = (new ScramSha1PlusMechanism()).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String NAME;
|
||||||
|
|
||||||
|
public ScramSha1PlusMechanism() {
|
||||||
|
super(SCRAMSHA1Mechanism.SHA_1_SCRAM_HMAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SASLMechanism newInstance() {
|
||||||
|
return new ScramSha1PlusMechanism();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2014 Florian Schmaus
|
* Copyright 2014-2016 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
package org.jivesoftware.smack.util;
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
@ -27,10 +28,13 @@ import java.util.Set;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
|
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
|
||||||
|
@ -166,6 +170,41 @@ public class TLSUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the channel binding data for the 'tls-server-end-point' channel binding type. This channel binding type is
|
||||||
|
* defined in RFC 5929 § 4.
|
||||||
|
*
|
||||||
|
* @param sslSession the SSL/TLS session from which the data should be retrieved.
|
||||||
|
* @return the channel binding data.
|
||||||
|
* @throws SSLPeerUnverifiedException
|
||||||
|
* @throws CertificateEncodingException
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
* @see <a href="https://tools.ietf.org/html/rfc5929#section-4">RFC 5929 § 4.</a>
|
||||||
|
*/
|
||||||
|
public static byte[] getChannelBindingTlsServerEndPoint(final SSLSession sslSession)
|
||||||
|
throws SSLPeerUnverifiedException, CertificateEncodingException, NoSuchAlgorithmException {
|
||||||
|
final Certificate[] peerCertificates = sslSession.getPeerCertificates();
|
||||||
|
final Certificate certificate = peerCertificates[0];
|
||||||
|
final String certificateAlgorithm = certificate.getPublicKey().getAlgorithm();
|
||||||
|
|
||||||
|
// RFC 5929 § 4.1 hash function selection.
|
||||||
|
String algorithm;
|
||||||
|
switch (certificateAlgorithm) {
|
||||||
|
case "MD5":
|
||||||
|
case "SHA-1":
|
||||||
|
algorithm = "SHA-256";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
algorithm = certificateAlgorithm;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
|
||||||
|
final byte[] certificateDerEncoded = certificate.getEncoded();
|
||||||
|
messageDigest.update(certificateDerEncoded);
|
||||||
|
return messageDigest.digest();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates.
|
* A {@link X509TrustManager} that <b>doesn't validate</b> X.509 certificates.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014 Florian Schmaus
|
* Copyright © 2014-2016 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,15 +17,14 @@
|
||||||
package org.jivesoftware.smack.sasl;
|
package org.jivesoftware.smack.sasl;
|
||||||
|
|
||||||
import org.jivesoftware.smack.DummyConnection;
|
import org.jivesoftware.smack.DummyConnection;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
|
||||||
|
|
||||||
public class AbstractSaslTest {
|
public class AbstractSaslTest {
|
||||||
|
|
||||||
protected final XMPPConnection xmppConnection = new DummyConnection();
|
protected final DummyConnection xmppConnection = new DummyConnection();
|
||||||
protected final SASLMechanism saslMechanism;
|
protected final SASLMechanism saslMechanism;
|
||||||
|
|
||||||
protected AbstractSaslTest(SASLMechanism saslMechanism) {
|
protected AbstractSaslTest(SASLMechanism saslMechanism) {
|
||||||
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection);
|
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection, xmppConnection.getConfiguration());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class DigestMd5SaslTest extends AbstractSaslTest {
|
||||||
if (useAuthzid) {
|
if (useAuthzid) {
|
||||||
authzid = JidCreate.entityBareFrom("shazbat@xmpp.org");
|
authzid = JidCreate.entityBareFrom("shazbat@xmpp.org");
|
||||||
}
|
}
|
||||||
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret", authzid);
|
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret", authzid, null);
|
||||||
byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
|
byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
|
||||||
String responseString = new String(response);
|
String responseString = new String(response);
|
||||||
String[] responseParts = responseString.split(",");
|
String[] responseParts = responseString.split(",");
|
||||||
|
|
|
@ -48,9 +48,9 @@ public class SCRAMSHA1MechanismTest extends SmackTestSuite {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
mech.authenticate(USERNAME, "unusedFoo", JidTestUtil.DOMAIN_BARE_JID_1, PASSWORD, null);
|
mech.authenticate(USERNAME, "unusedFoo", JidTestUtil.DOMAIN_BARE_JID_1, PASSWORD, null, null);
|
||||||
AuthMechanism authMechanism = con.getSentPacket();
|
AuthMechanism authMechanism = con.getSentPacket();
|
||||||
assertEquals((new SCRAMSHA1Mechanism().getName()), authMechanism.getMechanism());
|
assertEquals(SCRAMSHA1Mechanism.NAME, authMechanism.getMechanism());
|
||||||
assertEquals(CLIENT_FIRST_MESSAGE, saslLayerString(authMechanism.getAuthenticationText()));
|
assertEquals(CLIENT_FIRST_MESSAGE, saslLayerString(authMechanism.getAuthenticationText()));
|
||||||
|
|
||||||
mech.challengeReceived(Base64.encode(SERVER_FIRST_MESSAGE), false);
|
mech.challengeReceived(Base64.encode(SERVER_FIRST_MESSAGE), false);
|
||||||
|
|
|
@ -91,6 +91,7 @@ import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
@ -160,7 +161,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
*/
|
*/
|
||||||
private boolean disconnectedButResumeable = false;
|
private boolean disconnectedButResumeable = false;
|
||||||
|
|
||||||
private boolean usingTLS = false;
|
private SSLSocket secureSocket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected access level because of unit test purposes
|
* Protected access level because of unit test purposes
|
||||||
|
@ -380,7 +381,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
protected synchronized void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
|
protected synchronized void loginInternal(String username, String password, Resourcepart resource) throws XMPPException,
|
||||||
SmackException, IOException, InterruptedException {
|
SmackException, IOException, InterruptedException {
|
||||||
// Authenticate using SASL
|
// Authenticate using SASL
|
||||||
saslAuthentication.authenticate(username, password, config.getAuthzid());
|
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
|
||||||
|
saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression. XEP-170
|
// If compression is enabled then request the server to use stream compression. XEP-170
|
||||||
// recommends to perform stream compression before resource binding.
|
// recommends to perform stream compression before resource binding.
|
||||||
|
@ -442,7 +444,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecureConnection() {
|
public boolean isSecureConnection() {
|
||||||
return usingTLS;
|
return secureSocket != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -519,7 +521,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
authenticated = false;
|
authenticated = false;
|
||||||
connected = false;
|
connected = false;
|
||||||
usingTLS = false;
|
secureSocket = null;
|
||||||
reader = null;
|
reader = null;
|
||||||
writer = null;
|
writer = null;
|
||||||
|
|
||||||
|
@ -801,7 +803,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set that TLS was successful
|
// Set that TLS was successful
|
||||||
usingTLS = true;
|
secureSocket = sslSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue