diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 27d1f1097..812f6ae10 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 = false; + public static final boolean QUICKSTART_ENABLED = true; //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 26f9c9da0..216f3d7f8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java @@ -97,7 +97,7 @@ public enum ChannelBinding { } } - public static boolean ensureBest( + public static boolean isAvailable( final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) { return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion) == channelBinding; diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java new file mode 100644 index 000000000..d4e34ba59 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java @@ -0,0 +1,6 @@ +package eu.siacs.conversations.crypto.sasl; + +public interface ChannelBindingMechanism { + + ChannelBinding getChannelBinding(); +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java index b54864fea..1d8aeac69 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java @@ -1,12 +1,17 @@ package eu.siacs.conversations.crypto.sasl; +import android.util.Base64; + import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.hash.HashFunction; +import com.google.common.primitives.Bytes; import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -16,11 +21,13 @@ import javax.net.ssl.SSLSocket; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.utils.SSLSockets; -public abstract class HashedToken extends SaslMechanism { +public abstract class HashedToken extends SaslMechanism implements ChannelBindingMechanism { private static final String PREFIX = "HT"; private static final List HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256"); + private static final byte[] INITIATOR = "Initiator".getBytes(StandardCharsets.UTF_8); + private static final byte[] RESPONDER = "Responder".getBytes(StandardCharsets.UTF_8); protected final ChannelBinding channelBinding; @@ -36,18 +43,48 @@ public abstract class HashedToken extends SaslMechanism { @Override public String getClientFirstMessage() { - return null; // HMAC(token, "Initiator" || cb-data) + final String token = Strings.nullToEmpty(this.account.getFastToken()); + final HashFunction hashing = getHashFunction(token.getBytes(StandardCharsets.UTF_8)); + final byte[] cbData = new byte[0]; + final byte[] initiatorHashedToken = + hashing.hashBytes(Bytes.concat(INITIATOR, cbData)).asBytes(); + final byte[] firstMessage = + Bytes.concat( + account.getUsername().getBytes(StandardCharsets.UTF_8), + new byte[] {0x00}, + initiatorHashedToken); + return Base64.encodeToString(firstMessage, Base64.NO_WRAP); } @Override public String getResponse(final String challenge, final SSLSocket socket) throws AuthenticationException { - // todo verify that challenge matches HMAC(token, "Responder" || cb-data) - return null; + final byte[] responderMessage; + try { + responderMessage = Base64.decode(challenge, Base64.NO_WRAP); + } catch (final Exception e) { + throw new AuthenticationException("Unable to decode responder message", e); + } + final String token = Strings.nullToEmpty(this.account.getFastToken()); + final HashFunction hashing = getHashFunction(token.getBytes(StandardCharsets.UTF_8)); + final byte[] cbData = new byte[0]; + final byte[] expectedResponderMessage = + hashing.hashBytes(Bytes.concat(RESPONDER, cbData)).asBytes(); + if (Arrays.equals(responderMessage, expectedResponderMessage)) { + return null; + } + throw new AuthenticationException("Responder message did not match"); } protected abstract HashFunction getHashFunction(final byte[] key); + public abstract Mechanism getTokenMechanism(); + + @Override + public String getMechanism() { + return getTokenMechanism().name(); + } + public static final class Mechanism { public final String hashFunction; public final ChannelBinding channelBinding; @@ -77,6 +114,14 @@ public abstract class HashedToken extends SaslMechanism { } } + public static Mechanism ofOrNull(final String mechanism) { + try { + return mechanism == null ? null : of(mechanism); + } catch (final IllegalArgumentException e) { + return null; + } + } + public static Multimap of(final Collection mechanisms) { final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); @@ -119,4 +164,8 @@ public abstract class HashedToken extends SaslMechanism { PREFIX, hashFunction, ChannelBinding.SHORT_NAMES.get(channelBinding)); } } + + public ChannelBinding getChannelBinding() { + return this.channelBinding; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java index fae756485..aef19d72a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha256.java @@ -17,8 +17,7 @@ public class HashedTokenSha256 extends HashedToken { } @Override - public String getMechanism() { - final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding); - return String.format("HT-SHA-256-%s", cbShortName); + public Mechanism getTokenMechanism() { + return new Mechanism("SHA-256", channelBinding); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha512.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha512.java index fd1b7be51..6f48b5444 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha512.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedTokenSha512.java @@ -17,8 +17,7 @@ public class HashedTokenSha512 extends HashedToken { } @Override - public String getMechanism() { - final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding); - return String.format("HT-SHA-512-%s", cbShortName); + public Mechanism getTokenMechanism() { + return new Mechanism("SHA-512", this.channelBinding); } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java index e0df3a2d4..48835f9df 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java @@ -166,9 +166,9 @@ public abstract class SaslMechanism { public static SaslMechanism ensureAvailable( final SaslMechanism mechanism, final SSLSockets.Version sslVersion) { - if (mechanism instanceof ScramPlusMechanism) { - final ChannelBinding cb = ((ScramPlusMechanism) mechanism).getChannelBinding(); - if (ChannelBinding.ensureBest(cb, sslVersion)) { + if (mechanism instanceof ChannelBindingMechanism) { + final ChannelBinding cb = ((ChannelBindingMechanism) mechanism).getChannelBinding(); + if (ChannelBinding.isAvailable(cb, sslVersion)) { return mechanism; } else { Log.d( diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramPlusMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramPlusMechanism.java index 707883d73..c6a63ddbd 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramPlusMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramPlusMechanism.java @@ -16,7 +16,7 @@ import javax.net.ssl.SSLSocket; import eu.siacs.conversations.entities.Account; -public abstract class ScramPlusMechanism extends ScramMechanism { +public abstract class ScramPlusMechanism extends ScramMechanism implements ChannelBindingMechanism { private static final String EXPORTER_LABEL = "EXPORTER-Channel-Binding"; @@ -103,6 +103,7 @@ public abstract class ScramPlusMechanism extends ScramMechanism { return messageDigest.digest(); } + @Override public ChannelBinding getChannelBinding() { return this.channelBinding; } diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 1817c24bb..d570cbec3 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -26,6 +26,9 @@ import eu.siacs.conversations.crypto.PgpDecryptionService; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.crypto.sasl.ChannelBinding; +import eu.siacs.conversations.crypto.sasl.HashedToken; +import eu.siacs.conversations.crypto.sasl.HashedTokenSha256; +import eu.siacs.conversations.crypto.sasl.HashedTokenSha512; import eu.siacs.conversations.crypto.sasl.SaslMechanism; import eu.siacs.conversations.crypto.sasl.ScramPlusMechanism; import eu.siacs.conversations.services.AvatarService; @@ -55,7 +58,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public static final String RESOURCE = "resource"; public static final String PINNED_MECHANISM = "pinned_mechanism"; public static final String PINNED_CHANNEL_BINDING = "pinned_channel_binding"; - + public static final String FAST_MECHANISM = "fast_mechanism"; + public static final String FAST_TOKEN = "fast_token"; public static final int OPTION_DISABLED = 1; public static final int OPTION_REGISTER = 2; @@ -72,7 +76,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable private static final String KEY_PINNED_MECHANISM = "pinned_mechanism"; public static final String KEY_PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration"; - protected final JSONObject keys; private final Roster roster = new Roster(this); private final Collection blocklist = new CopyOnWriteArraySet<>(); @@ -101,16 +104,46 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable private String presenceStatusMessage; private String pinnedMechanism; private String pinnedChannelBinding; + private String fastMechanism; + private String fastToken; public Account(final Jid jid, final String password) { - this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null, null, null); + this( + java.util.UUID.randomUUID().toString(), + jid, + password, + 0, + null, + "", + null, + null, + null, + 5222, + Presence.Status.ONLINE, + null, + null, + null, + null, + null); } - private Account(final String uuid, final Jid jid, - final String password, final int options, final String rosterVersion, final String keys, - final String avatar, String displayName, String hostname, int port, - final Presence.Status status, String statusMessage, final String pinnedMechanism, final String pinnedChannelBinding) { + private Account( + final String uuid, + final Jid jid, + final String password, + final int options, + final String rosterVersion, + final String keys, + final String avatar, + String displayName, + String hostname, + int port, + final Presence.Status status, + String statusMessage, + final String pinnedMechanism, + final String pinnedChannelBinding, + final String fastMechanism, + final String fastToken) { this.uuid = uuid; this.jid = jid; this.password = password; @@ -131,21 +164,29 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable this.presenceStatusMessage = statusMessage; this.pinnedMechanism = pinnedMechanism; this.pinnedChannelBinding = pinnedChannelBinding; + this.fastMechanism = fastMechanism; + this.fastToken = fastToken; } public static Account fromCursor(final Cursor cursor) { final Jid jid; try { final String resource = cursor.getString(cursor.getColumnIndexOrThrow(RESOURCE)); - jid = Jid.of( - cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)), - cursor.getString(cursor.getColumnIndexOrThrow(SERVER)), - resource == null || resource.trim().isEmpty() ? null : resource); + jid = + Jid.of( + cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)), + cursor.getString(cursor.getColumnIndexOrThrow(SERVER)), + resource == null || resource.trim().isEmpty() ? null : resource); } catch (final IllegalArgumentException e) { - Log.d(Config.LOGTAG, cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)) + "@" + cursor.getString(cursor.getColumnIndexOrThrow(SERVER))); + Log.d( + Config.LOGTAG, + cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)) + + "@" + + cursor.getString(cursor.getColumnIndexOrThrow(SERVER))); throw new AssertionError(e); } - return new Account(cursor.getString(cursor.getColumnIndexOrThrow(UUID)), + return new Account( + cursor.getString(cursor.getColumnIndexOrThrow(UUID)), jid, cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD)), cursor.getInt(cursor.getColumnIndexOrThrow(OPTIONS)), @@ -155,10 +196,13 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable cursor.getString(cursor.getColumnIndexOrThrow(DISPLAY_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)), cursor.getInt(cursor.getColumnIndexOrThrow(PORT)), - Presence.Status.fromShowString(cursor.getString(cursor.getColumnIndexOrThrow(STATUS))), + Presence.Status.fromShowString( + cursor.getString(cursor.getColumnIndexOrThrow(STATUS))), cursor.getString(cursor.getColumnIndexOrThrow(STATUS_MESSAGE)), cursor.getString(cursor.getColumnIndexOrThrow(PINNED_MECHANISM)), - cursor.getString(cursor.getColumnIndexOrThrow(PINNED_CHANNEL_BINDING))); + cursor.getString(cursor.getColumnIndexOrThrow(PINNED_CHANNEL_BINDING)), + cursor.getString(cursor.getColumnIndexOrThrow(FAST_MECHANISM)), + cursor.getString(cursor.getColumnIndexOrThrow(FAST_TOKEN))); } public boolean httpUploadAvailable(long size) { @@ -305,10 +349,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public void setPinnedMechanism(final SaslMechanism mechanism) { this.pinnedMechanism = mechanism.getMechanism(); if (mechanism instanceof ScramPlusMechanism) { - this.pinnedChannelBinding = ((ScramPlusMechanism) mechanism).getChannelBinding().toString(); + this.pinnedChannelBinding = + ((ScramPlusMechanism) mechanism).getChannelBinding().toString(); + } else { + this.pinnedChannelBinding = null; } } + public void setFastToken(final HashedToken.Mechanism mechanism, final String token) { + this.fastMechanism = mechanism.name(); + this.fastToken = token; + } + public void resetPinnedMechanism() { this.pinnedMechanism = null; this.pinnedChannelBinding = null; @@ -328,12 +380,39 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } } - public SaslMechanism getPinnedMechanism() { + private SaslMechanism getPinnedMechanism() { final String mechanism = Strings.nullToEmpty(this.pinnedMechanism); final ChannelBinding channelBinding = ChannelBinding.get(this.pinnedChannelBinding); return new SaslMechanism.Factory(this).of(mechanism, channelBinding); } + private HashedToken getFastMechanism() { + final HashedToken.Mechanism fastMechanism = HashedToken.Mechanism.ofOrNull(this.fastMechanism); + final String token = this.fastToken; + if (fastMechanism == null || Strings.isNullOrEmpty(token)) { + return null; + } + if (fastMechanism.hashFunction.equals("SHA-256")) { + return new HashedTokenSha256(this, fastMechanism.channelBinding); + } else if (fastMechanism.hashFunction.equals("SHA-512")) { + return new HashedTokenSha512(this, fastMechanism.channelBinding); + } else { + return null; + } + } + + public SaslMechanism getQuickStartMechanism() { + final HashedToken hashedTokenMechanism = getFastMechanism(); + if (hashedTokenMechanism != null) { + return hashedTokenMechanism; + } + return getPinnedMechanism(); + } + + public String getFastToken() { + return this.fastToken; + } + public State getTrueStatus() { return this.status; } @@ -435,6 +514,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable values.put(RESOURCE, jid.getResource()); values.put(PINNED_MECHANISM, pinnedMechanism); values.put(PINNED_CHANNEL_BINDING, pinnedChannelBinding); + values.put(FAST_MECHANISM, this.fastMechanism); + values.put(FAST_TOKEN, this.fastToken); return values; } @@ -480,7 +561,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public int activeDevicesWithRtpCapability() { int i = 0; - for(Presence presence : getSelfContact().getPresences().getPresences()) { + for (Presence presence : getSelfContact().getPresences().getPresences()) { if (RtpCapability.check(presence) != RtpCapability.Capability.NONE) { i++; } @@ -617,7 +698,9 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public String getShareableLink() { List fingerprints = this.getFingerprints(); - String uri = "https://conversations.im/i/" + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); + String uri = + "https://conversations.im/i/" + + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); if (fingerprints.size() > 0) { return XmppUri.getFingerprintUri(uri, fingerprints, '&'); } else { @@ -630,10 +713,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable if (axolotlService == null) { return fingerprints; } - fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO, axolotlService.getOwnFingerprint().substring(2), axolotlService.getOwnDeviceId())); + fingerprints.add( + new XmppUri.Fingerprint( + XmppUri.FingerprintType.OMEMO, + axolotlService.getOwnFingerprint().substring(2), + axolotlService.getOwnDeviceId())); for (XmppAxolotlSession session : axolotlService.findOwnSessions()) { if (session.getTrust().isVerified() && session.getTrust().isActive()) { - fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OMEMO, session.getFingerprint().substring(2).replaceAll("\\s", ""), session.getRemoteAddress().getDeviceId())); + fingerprints.add( + new XmppUri.Fingerprint( + XmppUri.FingerprintType.OMEMO, + session.getFingerprint().substring(2).replaceAll("\\s", ""), + session.getRemoteAddress().getDeviceId())); } } return fingerprints; @@ -641,7 +732,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable public boolean isBlocked(final ListItem contact) { final Jid jid = contact.getJid(); - return jid != null && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain())); + return jid != null + && (blocklist.contains(jid.asBareJid()) || blocklist.contains(jid.getDomain())); } public boolean isBlocked(final Jid jid) { @@ -685,7 +777,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable REGISTRATION_CONFLICT(true, false), REGISTRATION_NOT_SUPPORTED(true, false), REGISTRATION_PLEASE_WAIT(true, false), - REGISTRATION_INVALID_TOKEN(true,false), + REGISTRATION_INVALID_TOKEN(true, false), REGISTRATION_PASSWORD_TOO_WEAK(true, false), TLS_ERROR, TLS_ERROR_DOMAIN, diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 49de553eb..3eb48a1c9 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -64,7 +64,7 @@ import eu.siacs.conversations.xmpp.mam.MamReference; public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 50; + private static final int DATABASE_VERSION = 51; private static boolean requiresMessageIndexRebuild = false; private static DatabaseBackend instance = null; @@ -232,6 +232,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Account.RESOURCE + " TEXT," + Account.PINNED_MECHANISM + " TEXT," + Account.PINNED_CHANNEL_BINDING + " TEXT," + + Account.FAST_MECHANISM + " TEXT," + + Account.FAST_TOKEN + " TEXT," + Account.PORT + " NUMBER DEFAULT 5222)"); db.execSQL("create table " + Conversation.TABLENAME + " (" + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME @@ -594,7 +596,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 50 && newVersion >= 50) { db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PINNED_MECHANISM + " TEXT"); db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PINNED_CHANNEL_BINDING + " TEXT"); - + } + if (oldVersion < 51 && newVersion >= 51) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_MECHANISM + " TEXT"); + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_TOKEN + " TEXT"); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 2fa8d6df3..2c60534dd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -757,7 +757,6 @@ public class XmppConnection implements Runnable { Config.LOGTAG, account.getJid().asBareJid() + ": jid changed during SASL 2.0. updating database"); - mXmppConnectionService.databaseBackend.updateAccount(account); } final Element bound = success.findChild("bound", Namespace.BIND2); final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3"); @@ -798,11 +797,21 @@ public class XmppConnection implements Runnable { } sendPostBindInitialization(waitForDisco, carbonsEnabled != null); } - //TODO figure out name either by the existence of hashTokenRequest or if scramMechanism is of instance HashedToken - if (this.hashTokenRequest != null && !Strings.isNullOrEmpty(token)) { - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": storing hashed token "+this.hashTokenRequest.name()+ " "+token); + final HashedToken.Mechanism tokenMechanism; + final SaslMechanism currentMechanism = this.saslMechanism; + if (currentMechanism instanceof HashedToken) { + tokenMechanism = ((HashedToken) currentMechanism).getTokenMechanism(); + } else if (this.hashTokenRequest != null) { + tokenMechanism = this.hashTokenRequest; + } else { + tokenMechanism = null; + } + if (tokenMechanism != null && !Strings.isNullOrEmpty(token)) { + this.account.setFastToken(tokenMechanism,token); + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": storing hashed token "+tokenMechanism); } } + mXmppConnectionService.databaseBackend.updateAccount(account); this.quickStartInProgress = false; if (version == SaslMechanism.Version.SASL) { tagReader.reset(); @@ -826,6 +835,7 @@ public class XmppConnection implements Runnable { } catch (final IllegalArgumentException e) { throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); } + Log.d(Config.LOGTAG,failure.toString()); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": login failure " + version); if (failure.hasChild("temporary-auth-failure")) { throw new StateChangingException(Account.State.TEMPORARY_AUTH_FAILURE); @@ -1340,6 +1350,7 @@ public class XmppConnection implements Runnable { } final boolean quickStartAvailable; final String firstMessage = saslMechanism.getClientFirstMessage(); + final boolean usingFast = saslMechanism instanceof HashedToken; final Element authenticate; if (version == SaslMechanism.Version.SASL) { authenticate = new Element("auth", Namespace.SASL); @@ -1350,9 +1361,15 @@ public class XmppConnection implements Runnable { } else if (version == SaslMechanism.Version.SASL_2) { final Element inline = authElement.findChild("inline", Namespace.SASL_2); 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); - final HashedToken.Mechanism hashTokenRequest = HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); + final HashedToken.Mechanism hashTokenRequest; + if (usingFast) { + hashTokenRequest = null; + } else { + final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST); + final Collection fastMechanisms = SaslMechanism.mechanisms(fast); + hashTokenRequest = + HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); + } final Collection bindFeatures = Bind2.features(inline); quickStartAvailable = sm @@ -1370,7 +1387,7 @@ public class XmppConnection implements Runnable { } } this.hashTokenRequest = hashTokenRequest; - authenticate = generateAuthenticationRequest(firstMessage, hashTokenRequest, bindFeatures, sm); + authenticate = generateAuthenticationRequest(firstMessage, usingFast, hashTokenRequest, bindFeatures, sm); } else { throw new AssertionError("Missing implementation for " + version); } @@ -1390,12 +1407,13 @@ public class XmppConnection implements Runnable { tagWriter.writeElement(authenticate); } - private Element generateAuthenticationRequest(final String firstMessage) { - return generateAuthenticationRequest(firstMessage, null, Bind2.QUICKSTART_FEATURES, true); + private Element generateAuthenticationRequest(final String firstMessage, final boolean usingFast) { + return generateAuthenticationRequest(firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true); } private Element generateAuthenticationRequest( final String firstMessage, + final boolean usingFast, final HashedToken.Mechanism hashedTokenRequest, final Collection bind, final boolean inlineStreamManagement) { @@ -1423,7 +1441,12 @@ public class XmppConnection implements Runnable { authenticate.addChild(resume); } if (hashedTokenRequest != null) { - authenticate.addChild("request-token", Namespace.FAST).setAttribute("mechanism", hashedTokenRequest.name()); + authenticate + .addChild("request-token", Namespace.FAST) + .setAttribute("mechanism", hashedTokenRequest.name()); + } + if (usingFast) { + authenticate.addChild("fast", Namespace.FAST); } return authenticate; } @@ -2059,25 +2082,26 @@ public class XmppConnection implements Runnable { private boolean establishStream(final SSLSockets.Version sslVersion) throws IOException, InterruptedException { - final SaslMechanism pinnedMechanism = - SaslMechanism.ensureAvailable(account.getPinnedMechanism(), sslVersion); + final SaslMechanism quickStartMechanism = + SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion); final boolean secureConnection = sslVersion != SSLSockets.Version.NONE; if (secureConnection && Config.QUICKSTART_ENABLED - && pinnedMechanism != null + && quickStartMechanism != null && account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) { mXmppConnectionService.restoredFromDatabaseLatch.await(); - this.saslMechanism = pinnedMechanism; + this.saslMechanism = quickStartMechanism; + final boolean usingFast = quickStartMechanism instanceof HashedToken; final Element authenticate = - generateAuthenticationRequest(pinnedMechanism.getClientFirstMessage()); - authenticate.setAttribute("mechanism", pinnedMechanism.getMechanism()); + generateAuthenticationRequest(quickStartMechanism.getClientFirstMessage(), usingFast); + authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism()); sendStartStream(true, false); tagWriter.writeElement(authenticate); Log.d( Config.LOGTAG, account.getJid().toString() + ": quick start with " - + pinnedMechanism.getMechanism()); + + quickStartMechanism.getMechanism()); return true; } else { sendStartStream(secureConnection, true);