diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 812f6ae10..27d1f1097 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -60,7 +60,7 @@ public final class Config { public static final long CONTACT_SYNC_RETRY_INTERVAL = 1000L * 60 * 5; - public static final boolean QUICKSTART_ENABLED = true; + public static final boolean QUICKSTART_ENABLED = false; //Notification settings public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false; diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java index fb1255566..26f9c9da0 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java @@ -6,7 +6,9 @@ import com.google.common.base.CaseFormat; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.base.Strings; +import com.google.common.collect.BiMap; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableBiMap; import java.util.Arrays; import java.util.Collection; @@ -23,6 +25,16 @@ public enum ChannelBinding { TLS_SERVER_END_POINT, TLS_UNIQUE; + public static final BiMap SHORT_NAMES; + + static { + final ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + for (final ChannelBinding cb : values()) { + builder.put(cb, shortName(cb)); + } + SHORT_NAMES = builder.build(); + } + public static Collection of(final Element channelBinding) { Preconditions.checkArgument( channelBinding == null @@ -85,7 +97,24 @@ public enum ChannelBinding { } } - public static boolean ensureBest(final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) { - return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion) == channelBinding; + public static boolean ensureBest( + final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) { + return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion) + == channelBinding; + } + + private static String shortName(final ChannelBinding channelBinding) { + switch (channelBinding) { + case TLS_UNIQUE: + return "UNIQ"; + case TLS_EXPORTER: + return "EXPR"; + case TLS_SERVER_END_POINT: + return "ENDP"; + case NONE: + return "NONE"; + default: + throw new AssertionError("Missing short name for " + channelBinding); + } } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java new file mode 100644 index 000000000..f973c8377 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java @@ -0,0 +1,114 @@ +package eu.siacs.conversations.crypto.sasl; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.hash.HashFunction; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.SSLSocket; + +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.utils.SSLSockets; + +public abstract class HashedToken extends SaslMechanism { + + private static List HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256"); + + protected final ChannelBinding channelBinding; + + protected HashedToken(final Account account, final ChannelBinding channelBinding) { + super(account); + this.channelBinding = channelBinding; + } + + @Override + public int getPriority() { + throw new UnsupportedOperationException(); + } + + @Override + public String getClientFirstMessage() { + return null; // HMAC(token, "Initiator" || cb-data) + } + + @Override + public String getResponse(final String challenge, final SSLSocket socket) + throws AuthenticationException { + // todo verify that challenge matches HMAC(token, "Responder" || cb-data) + return null; + } + + protected abstract HashFunction getHashFunction(final byte[] key); + + public static final class Mechanism { + public final String hashFunction; + public final ChannelBinding channelBinding; + + public Mechanism(String hashFunction, ChannelBinding channelBinding) { + this.hashFunction = hashFunction; + this.channelBinding = channelBinding; + } + + public static Mechanism of(final String mechanism) { + final int first = mechanism.indexOf('-'); + final int last = mechanism.lastIndexOf('-'); + if (last <= first || mechanism.length() <= last) { + throw new IllegalArgumentException("Not a valid HashedToken name"); + } + if (mechanism.substring(0, first).equals("HT")) { + final String hashFunction = mechanism.substring(first + 1, last); + final String cbShortName = mechanism.substring(last + 1); + final ChannelBinding channelBinding = + ChannelBinding.SHORT_NAMES.inverse().get(cbShortName); + if (channelBinding == null) { + throw new IllegalArgumentException("Unknown channel binding " + cbShortName); + } + return new Mechanism(hashFunction, channelBinding); + } else { + throw new IllegalArgumentException("HashedToken name does not start with HT"); + } + } + + public static Multimap of(final Collection mechanisms) { + final ImmutableMultimap.Builder builder = + ImmutableMultimap.builder(); + for (final String name : mechanisms) { + try { + final Mechanism mechanism = Mechanism.of(name); + builder.put(mechanism.hashFunction, mechanism.channelBinding); + } catch (final IllegalArgumentException ignored) { + } + } + return builder.build(); + } + + public static Mechanism best( + final Collection mechanisms, final SSLSockets.Version sslVersion) { + final Multimap multimap = of(mechanisms); + for (final String hashFunction : HASH_FUNCTIONS) { + final Collection channelBindings = multimap.get(hashFunction); + if (channelBindings.isEmpty()) { + continue; + } + final ChannelBinding cb = ChannelBinding.best(channelBindings, sslVersion); + return new Mechanism(hashFunction, cb); + } + return null; + } + + @NotNull + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("hashFunction", hashFunction) + .add("channelBinding", channelBinding) + .toString(); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java new file mode 100644 index 000000000..fae756485 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java @@ -0,0 +1,24 @@ +package eu.siacs.conversations.crypto.sasl; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +import eu.siacs.conversations.entities.Account; + +public class HashedTokenSha256 extends HashedToken { + + public HashedTokenSha256(final Account account, final ChannelBinding channelBinding) { + super(account, channelBinding); + } + + @Override + protected HashFunction getHashFunction(final byte[] key) { + return Hashing.hmacSha256(key); + } + + @Override + public String getMechanism() { + final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding); + return String.format("HT-SHA-256-%s", cbShortName); + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index e693b2afa..f2b4c932a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -63,6 +63,7 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.XmppDomainVerifier; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.sasl.ChannelBinding; +import eu.siacs.conversations.crypto.sasl.HashedToken; import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Message; @@ -1344,7 +1345,7 @@ public class XmppConnection implements Runnable { final boolean sm = inline != null && inline.hasChild("sm", "urn:xmpp:sm:3"); final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST); final Collection fastMechanisms = SaslMechanism.mechanisms(fast); - Log.d(Config.LOGTAG,"fast mechanisms: "+fastMechanisms); + Log.d(Config.LOGTAG,"fast mechanism: "+ HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket))); final Collection bindFeatures = Bind2.features(inline); quickStartAvailable = sm