From 1f1bc236fd6650fba2f9e59e2aa03969b0700402 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 20 Nov 2016 19:32:26 +0100 Subject: [PATCH] Ads support for SCRAM-SHA-1-PLUS Related to SMACK-398. --- .../smack/bosh/XMPPBOSHConnection.java | 2 +- .../smack/ConnectionConfiguration.java | 4 +- .../smack/SASLAuthentication.java | 20 +++--- .../smack/SmackInitialization.java | 4 +- .../smack/sasl/SASLMechanism.java | 57 ++++++++--------- .../smack/sasl/core/SCRAMSHA1Mechanism.java | 15 ++++- .../smack/sasl/core/ScramMechanism.java | 43 ++++++++++++- .../smack/sasl/core/ScramPlusMechanism.java | 61 +++++++++++++++++++ .../sasl/core/ScramSha1PlusMechanism.java | 43 +++++++++++++ .../org/jivesoftware/smack/util/TLSUtils.java | 41 ++++++++++++- .../smack/sasl/AbstractSaslTest.java | 7 +-- .../smack/sasl/DigestMd5SaslTest.java | 2 +- .../sasl/core/SCRAMSHA1MechanismTest.java | 4 +- .../smack/tcp/XMPPTCPConnection.java | 12 ++-- 14 files changed, 252 insertions(+), 63 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramSha1PlusMechanism.java diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java index 0473359c6..4d85da2f7 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java @@ -214,7 +214,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { protected void loginInternal(String username, String password, Resourcepart resource) throws XMPPException, SmackException, IOException, InterruptedException { // Authenticate using SASL - saslAuthentication.authenticate(username, password, config.getAuthzid()); + saslAuthentication.authenticate(username, password, config.getAuthzid(), null); bindResourceAndEstablishSession(resource); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index ff6207dc9..b41ab46b0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -462,9 +462,9 @@ public abstract class ConnectionConfiguration { * @return true if the given SASL mechanism is enabled, false otherwise. */ 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) { - return true; + return !SASLAuthentication.getBlacklistedSASLMechanisms().contains(saslMechanism); } return enabledSaslMechanisms.contains(saslMechanism); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java index faf90a291..ca11f21a1 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SASLAuthentication.java @@ -22,11 +22,13 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.Mechanisms; import org.jivesoftware.smack.sasl.SASLErrorException; 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.Success; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; +import javax.net.ssl.SSLSession; import javax.security.auth.callback.CallbackHandler; import java.io.IOException; @@ -65,6 +67,11 @@ public final class SASLAuthentication { private static final Set BLACKLISTED_MECHANISMS = new HashSet(); + static { + // Blacklist SCRAM-SHA-1-PLUS for now. + blacklistSASLMechanism(ScramSha1PlusMechanism.NAME); + } + /** * Registers a new SASL mechanism. * @@ -137,9 +144,7 @@ public final class SASLAuthentication { } public static Set getBlacklistedSASLMechanisms() { - synchronized(BLACKLISTED_MECHANISMS) { - return new HashSet(BLACKLISTED_MECHANISMS); - } + return Collections.unmodifiableSet(BLACKLISTED_MECHANISMS); } private final AbstractXMPPConnection connection; @@ -173,13 +178,14 @@ public final class SASLAuthentication { * @param username the username that is authenticating with the server. * @param password the password to send to the server. * @param authzid the authorization identifier (typically null). + * @param sslSession the optional SSL/TLS session (if one was established) * @throws XMPPErrorException * @throws SASLErrorException * @throws IOException * @throws SmackException * @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, SmackException, InterruptedException { currentMechanism = selectMechanism(authzid); @@ -189,10 +195,10 @@ public final class SASLAuthentication { synchronized (this) { if (callbackHandler != null) { - currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid); + currentMechanism.authenticate(host, xmppServiceDomain, callbackHandler, authzid, sslSession); } else { - currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid); + currentMechanism.authenticate(username, host, xmppServiceDomain, password, authzid, sslSession); } final long deadline = System.currentTimeMillis() + connection.getPacketReplyTimeout(); while (!authenticationSuccessful && saslException == null) { @@ -340,7 +346,7 @@ public final class SASLAuthentication { } if (serverMechanisms.contains(mechanismName)) { // Create a new instance of the SASLMechanism for every authentication attempt. - return mechanism.instanceForAuthentication(connection); + return mechanism.instanceForAuthentication(connection, configuration); } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java index a674c3629..74f8d49fb 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java @@ -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"); * 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.SASLXOauth2Mechanism; import org.jivesoftware.smack.sasl.core.SCRAMSHA1Mechanism; +import org.jivesoftware.smack.sasl.core.ScramSha1PlusMechanism; import org.jivesoftware.smack.util.FileUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -137,6 +138,7 @@ public final class SmackInitialization { } SASLAuthentication.registerSASLMechanism(new SCRAMSHA1Mechanism()); + SASLAuthentication.registerSASLMechanism(new ScramSha1PlusMechanism()); SASLAuthentication.registerSASLMechanism(new SASLXOauth2Mechanism()); SASLAuthentication.registerSASLMechanism(new SASLAnonymous()); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java index 19f7e9988..dce630b8c 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/SASLMechanism.java @@ -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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ */ package org.jivesoftware.smack.sasl; +import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; 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.EntityBareJid; +import javax.net.ssl.SSLSession; import javax.security.auth.callback.CallbackHandler; /** - * Base class for SASL mechanisms. Subclasses must implement these methods: - *
    - *
  • {@link #getName()} -- returns the common name of the SASL mechanism.
  • - *
+ * Base class for SASL mechanisms. * Subclasses will likely want to implement their own versions of these methods: - *
  • {@link #authenticate(String, String, DomainBareJid, String, EntityBareJid)} -- Initiate authentication stanza using the + *
  • {@link #authenticate(String, String, DomainBareJid, String, EntityBareJid, SSLSession)} -- Initiate authentication stanza using the * deprecated method.
  • - *
  • {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid)} -- Initiate authentication stanza + *
  • {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} -- Initiate authentication stanza * using the CallbackHandler method.
  • *
  • {@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.
  • * - * - * Basic XMPP SASL authentication steps: - * 1. Client authentication initialization, stanza sent to the server (Base64 encoded): - * - * 2. Server sends back to the client the challenge response (Base64 encoded) - * sample: - * realm=,nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess - * 3. The client responds back to the server (Base 64 encoded): - * sample: - * username=,realm=,nonce="OA6MG9tEQGm2hh", - * cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth, - * digest-uri=, - * response=d388dad90d4bbd760a152321f2143af7, - * charset=utf-8, - * authzid= - * 4. The server evaluates if the user is present and contained in the REALM - * if successful it sends: (Base64 encoded) - * if not successful it sends: - * sample: - * - * cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA== - * * * @author Jay Kline + * @author Florian Schmaus */ public abstract class SASLMechanism implements Comparable { @@ -92,6 +70,8 @@ public abstract class SASLMechanism implements Comparable { 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 * "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 { protected String password; protected String host; + /** + * The used SSL/TLS session (if any). + */ + protected SSLSession sslSession; + /** * Builds and sends the auth stanza to the server. Note that this method of * 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: * @@ -165,17 +150,20 @@ public abstract class SASLMechanism implements Comparable { * serviceName format is: host [ "/" serv-name ] as per RFC-2831 * @param password the password for this account. * @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 NotConnectedException * @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 { this.authenticationId = username; this.host = host; this.serviceName = serviceName; this.password = password; this.authorizationId = authzid; + this.sslSession = sslSession; assert(authorizationId == null || authzidSupported()); authenticateInternal(); authenticate(); @@ -195,15 +183,17 @@ public abstract class SASLMechanism implements Comparable { * @param serviceName the xmpp service location * @param cbh the CallbackHandler to obtain user information. * @param authzid the optional authorization identity. + * @param sslSession the optional SSL/TLS session (if one was established) * @throws SmackException * @throws NotConnectedException * @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 { this.host = host; this.serviceName = serviceName; this.authorizationId = authzid; + this.sslSession = sslSession; assert(authorizationId == null || authzidSupported()); authenticateInternal(cbh); authenticate(); @@ -290,9 +280,10 @@ public abstract class SASLMechanism implements Comparable { public abstract void checkIfSuccessfulOrThrow() throws SmackException; - public SASLMechanism instanceForAuthentication(XMPPConnection connection) { + public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) { SASLMechanism saslMechansim = newInstance(); saslMechansim.connection = connection; + saslMechansim.connectionConfiguration = connectionConfiguration; return saslMechansim; } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1Mechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1Mechanism.java index e92b9c469..6573fa4a5 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1Mechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1Mechanism.java @@ -23,8 +23,8 @@ import org.jivesoftware.smack.util.MAC; public class SCRAMSHA1Mechanism extends ScramMechanism { - public SCRAMSHA1Mechanism() { - super(new ScramHmac() { + static { + SHA_1_SCRAM_HMAC = new ScramHmac() { @Override public String getHmacName() { return "SHA-1"; @@ -33,7 +33,16 @@ public class SCRAMSHA1Mechanism extends ScramMechanism { public byte[] hmac(byte[] key, byte[] str) throws InvalidKeyException { 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 diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java index 74381c6d7..ef7fbc4ef 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java @@ -28,6 +28,7 @@ import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.util.ByteUtils; import org.jivesoftware.smack.util.SHA1; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jxmpp.util.cache.Cache; 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 // 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 + "," + // 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 … // 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; Keys keys = CACHE.get(cacheKey); if (keys == null) { @@ -211,7 +215,40 @@ public abstract class ScramMechanism extends SASLMechanism { if (authorizationId != null) { 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 parseAttributes(String string) throws SmackException { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java new file mode 100644 index 000000000..be5d5a862 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java @@ -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; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramSha1PlusMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramSha1PlusMechanism.java new file mode 100644 index 000000000..05312c9e4 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramSha1PlusMechanism.java @@ -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(); + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java index 633064066..be3d3a46f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/TLSUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014 Florian Schmaus + * Copyright 2014-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. @@ -17,6 +17,7 @@ package org.jivesoftware.smack.util; import java.security.KeyManagementException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.cert.CertificateException; @@ -27,10 +28,13 @@ import java.util.Set; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import org.jivesoftware.smack.ConnectionConfiguration; 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 RFC 5929 § 4. + */ + 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 doesn't validate X.509 certificates. *

    diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java index b22a858bb..c4b402027 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/AbstractSaslTest.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014 Florian Schmaus + * Copyright © 2014-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. @@ -17,15 +17,14 @@ package org.jivesoftware.smack.sasl; import org.jivesoftware.smack.DummyConnection; -import org.jivesoftware.smack.XMPPConnection; public class AbstractSaslTest { - protected final XMPPConnection xmppConnection = new DummyConnection(); + protected final DummyConnection xmppConnection = new DummyConnection(); protected final SASLMechanism saslMechanism; protected AbstractSaslTest(SASLMechanism saslMechanism) { - this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection); + this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection, xmppConnection.getConfiguration()); } } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java index 6cfe83e51..5855aba7f 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/DigestMd5SaslTest.java @@ -43,7 +43,7 @@ public class DigestMd5SaslTest extends AbstractSaslTest { if (useAuthzid) { 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); String responseString = new String(response); String[] responseParts = responseString.split(","); diff --git a/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java b/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java index 7057a3b75..9fe177bd7 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/sasl/core/SCRAMSHA1MechanismTest.java @@ -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(); - assertEquals((new SCRAMSHA1Mechanism().getName()), authMechanism.getMechanism()); + assertEquals(SCRAMSHA1Mechanism.NAME, authMechanism.getMechanism()); assertEquals(CLIENT_FIRST_MESSAGE, saslLayerString(authMechanism.getAuthenticationText())); mech.challengeReceived(Base64.encode(SERVER_FIRST_MESSAGE), false); diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index b9444a8ed..ca39e73f5 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -91,6 +91,7 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -160,7 +161,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { */ private boolean disconnectedButResumeable = false; - private boolean usingTLS = false; + private SSLSocket secureSocket; /** * 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, SmackException, IOException, InterruptedException { // 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 // recommends to perform stream compression before resource binding. @@ -442,7 +444,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { @Override public boolean isSecureConnection() { - return usingTLS; + return secureSocket != null; } /** @@ -519,7 +521,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } authenticated = false; connected = false; - usingTLS = false; + secureSocket = null; reader = null; writer = null; @@ -801,7 +803,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } // Set that TLS was successful - usingTLS = true; + secureSocket = sslSocket; } /**