do quick start with HT-SHA-256-NONE

This commit is contained in:
Daniel Gultsch 2022-10-15 18:56:31 +02:00
parent c13787873c
commit 24badda4c9
11 changed files with 235 additions and 60 deletions

View file

@ -60,7 +60,7 @@ public final class Config {
public static final long CONTACT_SYNC_RETRY_INTERVAL = 1000L * 60 * 5; 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 //Notification settings
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false; public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;

View file

@ -97,7 +97,7 @@ public enum ChannelBinding {
} }
} }
public static boolean ensureBest( public static boolean isAvailable(
final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) { final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) {
return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion) return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion)
== channelBinding; == channelBinding;

View file

@ -0,0 +1,6 @@
package eu.siacs.conversations.crypto.sasl;
public interface ChannelBindingMechanism {
ChannelBinding getChannelBinding();
}

View file

@ -1,12 +1,17 @@
package eu.siacs.conversations.crypto.sasl; package eu.siacs.conversations.crypto.sasl;
import android.util.Base64;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.hash.HashFunction; import com.google.common.hash.HashFunction;
import com.google.common.primitives.Bytes;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -16,11 +21,13 @@ import javax.net.ssl.SSLSocket;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.SSLSockets; 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 String PREFIX = "HT";
private static final List<String> HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256"); private static final List<String> 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; protected final ChannelBinding channelBinding;
@ -36,18 +43,48 @@ public abstract class HashedToken extends SaslMechanism {
@Override @Override
public String getClientFirstMessage() { 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 @Override
public String getResponse(final String challenge, final SSLSocket socket) public String getResponse(final String challenge, final SSLSocket socket)
throws AuthenticationException { throws AuthenticationException {
// todo verify that challenge matches HMAC(token, "Responder" || cb-data) final byte[] responderMessage;
return null; 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); protected abstract HashFunction getHashFunction(final byte[] key);
public abstract Mechanism getTokenMechanism();
@Override
public String getMechanism() {
return getTokenMechanism().name();
}
public static final class Mechanism { public static final class Mechanism {
public final String hashFunction; public final String hashFunction;
public final ChannelBinding channelBinding; 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<String, ChannelBinding> of(final Collection<String> mechanisms) { public static Multimap<String, ChannelBinding> of(final Collection<String> mechanisms) {
final ImmutableMultimap.Builder<String, ChannelBinding> builder = final ImmutableMultimap.Builder<String, ChannelBinding> builder =
ImmutableMultimap.builder(); ImmutableMultimap.builder();
@ -119,4 +164,8 @@ public abstract class HashedToken extends SaslMechanism {
PREFIX, hashFunction, ChannelBinding.SHORT_NAMES.get(channelBinding)); PREFIX, hashFunction, ChannelBinding.SHORT_NAMES.get(channelBinding));
} }
} }
public ChannelBinding getChannelBinding() {
return this.channelBinding;
}
} }

View file

@ -17,8 +17,7 @@ public class HashedTokenSha256 extends HashedToken {
} }
@Override @Override
public String getMechanism() { public Mechanism getTokenMechanism() {
final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding); return new Mechanism("SHA-256", channelBinding);
return String.format("HT-SHA-256-%s", cbShortName);
} }
} }

View file

@ -17,8 +17,7 @@ public class HashedTokenSha512 extends HashedToken {
} }
@Override @Override
public String getMechanism() { public Mechanism getTokenMechanism() {
final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding); return new Mechanism("SHA-512", this.channelBinding);
return String.format("HT-SHA-512-%s", cbShortName);
} }
} }

View file

@ -166,9 +166,9 @@ public abstract class SaslMechanism {
public static SaslMechanism ensureAvailable( public static SaslMechanism ensureAvailable(
final SaslMechanism mechanism, final SSLSockets.Version sslVersion) { final SaslMechanism mechanism, final SSLSockets.Version sslVersion) {
if (mechanism instanceof ScramPlusMechanism) { if (mechanism instanceof ChannelBindingMechanism) {
final ChannelBinding cb = ((ScramPlusMechanism) mechanism).getChannelBinding(); final ChannelBinding cb = ((ChannelBindingMechanism) mechanism).getChannelBinding();
if (ChannelBinding.ensureBest(cb, sslVersion)) { if (ChannelBinding.isAvailable(cb, sslVersion)) {
return mechanism; return mechanism;
} else { } else {
Log.d( Log.d(

View file

@ -16,7 +16,7 @@ import javax.net.ssl.SSLSocket;
import eu.siacs.conversations.entities.Account; 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"; private static final String EXPORTER_LABEL = "EXPORTER-Channel-Binding";
@ -103,6 +103,7 @@ public abstract class ScramPlusMechanism extends ScramMechanism {
return messageDigest.digest(); return messageDigest.digest();
} }
@Override
public ChannelBinding getChannelBinding() { public ChannelBinding getChannelBinding() {
return this.channelBinding; return this.channelBinding;
} }

View file

@ -26,6 +26,9 @@ import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.crypto.sasl.ChannelBinding; 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.SaslMechanism;
import eu.siacs.conversations.crypto.sasl.ScramPlusMechanism; import eu.siacs.conversations.crypto.sasl.ScramPlusMechanism;
import eu.siacs.conversations.services.AvatarService; 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 RESOURCE = "resource";
public static final String PINNED_MECHANISM = "pinned_mechanism"; public static final String PINNED_MECHANISM = "pinned_mechanism";
public static final String PINNED_CHANNEL_BINDING = "pinned_channel_binding"; 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_DISABLED = 1;
public static final int OPTION_REGISTER = 2; 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"; private static final String KEY_PINNED_MECHANISM = "pinned_mechanism";
public static final String KEY_PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration"; public static final String KEY_PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";
protected final JSONObject keys; protected final JSONObject keys;
private final Roster roster = new Roster(this); private final Roster roster = new Roster(this);
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
@ -101,16 +104,46 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
private String presenceStatusMessage; private String presenceStatusMessage;
private String pinnedMechanism; private String pinnedMechanism;
private String pinnedChannelBinding; private String pinnedChannelBinding;
private String fastMechanism;
private String fastToken;
public Account(final Jid jid, final String password) { public Account(final Jid jid, final String password) {
this(java.util.UUID.randomUUID().toString(), jid, this(
password, 0, null, "", null, null, null, 5222, Presence.Status.ONLINE, null, null, null); 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, private Account(
final String password, final int options, final String rosterVersion, final String keys, final String uuid,
final String avatar, String displayName, String hostname, int port, final Jid jid,
final Presence.Status status, String statusMessage, final String pinnedMechanism, final String pinnedChannelBinding) { 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.uuid = uuid;
this.jid = jid; this.jid = jid;
this.password = password; this.password = password;
@ -131,21 +164,29 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
this.presenceStatusMessage = statusMessage; this.presenceStatusMessage = statusMessage;
this.pinnedMechanism = pinnedMechanism; this.pinnedMechanism = pinnedMechanism;
this.pinnedChannelBinding = pinnedChannelBinding; this.pinnedChannelBinding = pinnedChannelBinding;
this.fastMechanism = fastMechanism;
this.fastToken = fastToken;
} }
public static Account fromCursor(final Cursor cursor) { public static Account fromCursor(final Cursor cursor) {
final Jid jid; final Jid jid;
try { try {
final String resource = cursor.getString(cursor.getColumnIndexOrThrow(RESOURCE)); final String resource = cursor.getString(cursor.getColumnIndexOrThrow(RESOURCE));
jid = Jid.of( jid =
cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)), Jid.of(
cursor.getString(cursor.getColumnIndexOrThrow(SERVER)), cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)),
resource == null || resource.trim().isEmpty() ? null : resource); cursor.getString(cursor.getColumnIndexOrThrow(SERVER)),
resource == null || resource.trim().isEmpty() ? null : resource);
} catch (final IllegalArgumentException e) { } 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); throw new AssertionError(e);
} }
return new Account(cursor.getString(cursor.getColumnIndexOrThrow(UUID)), return new Account(
cursor.getString(cursor.getColumnIndexOrThrow(UUID)),
jid, jid,
cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD)), cursor.getString(cursor.getColumnIndexOrThrow(PASSWORD)),
cursor.getInt(cursor.getColumnIndexOrThrow(OPTIONS)), 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(DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)), cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME)),
cursor.getInt(cursor.getColumnIndexOrThrow(PORT)), 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(STATUS_MESSAGE)),
cursor.getString(cursor.getColumnIndexOrThrow(PINNED_MECHANISM)), 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) { public boolean httpUploadAvailable(long size) {
@ -305,10 +349,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public void setPinnedMechanism(final SaslMechanism mechanism) { public void setPinnedMechanism(final SaslMechanism mechanism) {
this.pinnedMechanism = mechanism.getMechanism(); this.pinnedMechanism = mechanism.getMechanism();
if (mechanism instanceof ScramPlusMechanism) { 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() { public void resetPinnedMechanism() {
this.pinnedMechanism = null; this.pinnedMechanism = null;
this.pinnedChannelBinding = 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 String mechanism = Strings.nullToEmpty(this.pinnedMechanism);
final ChannelBinding channelBinding = ChannelBinding.get(this.pinnedChannelBinding); final ChannelBinding channelBinding = ChannelBinding.get(this.pinnedChannelBinding);
return new SaslMechanism.Factory(this).of(mechanism, channelBinding); 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() { public State getTrueStatus() {
return this.status; return this.status;
} }
@ -435,6 +514,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
values.put(RESOURCE, jid.getResource()); values.put(RESOURCE, jid.getResource());
values.put(PINNED_MECHANISM, pinnedMechanism); values.put(PINNED_MECHANISM, pinnedMechanism);
values.put(PINNED_CHANNEL_BINDING, pinnedChannelBinding); values.put(PINNED_CHANNEL_BINDING, pinnedChannelBinding);
values.put(FAST_MECHANISM, this.fastMechanism);
values.put(FAST_TOKEN, this.fastToken);
return values; return values;
} }
@ -480,7 +561,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public int activeDevicesWithRtpCapability() { public int activeDevicesWithRtpCapability() {
int i = 0; int i = 0;
for(Presence presence : getSelfContact().getPresences().getPresences()) { for (Presence presence : getSelfContact().getPresences().getPresences()) {
if (RtpCapability.check(presence) != RtpCapability.Capability.NONE) { if (RtpCapability.check(presence) != RtpCapability.Capability.NONE) {
i++; i++;
} }
@ -617,7 +698,9 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public String getShareableLink() { public String getShareableLink() {
List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); List<XmppUri.Fingerprint> 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) { if (fingerprints.size() > 0) {
return XmppUri.getFingerprintUri(uri, fingerprints, '&'); return XmppUri.getFingerprintUri(uri, fingerprints, '&');
} else { } else {
@ -630,10 +713,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
if (axolotlService == null) { if (axolotlService == null) {
return fingerprints; 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()) { for (XmppAxolotlSession session : axolotlService.findOwnSessions()) {
if (session.getTrust().isVerified() && session.getTrust().isActive()) { 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; return fingerprints;
@ -641,7 +732,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public boolean isBlocked(final ListItem contact) { public boolean isBlocked(final ListItem contact) {
final Jid jid = contact.getJid(); 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) { public boolean isBlocked(final Jid jid) {
@ -685,7 +777,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
REGISTRATION_CONFLICT(true, false), REGISTRATION_CONFLICT(true, false),
REGISTRATION_NOT_SUPPORTED(true, false), REGISTRATION_NOT_SUPPORTED(true, false),
REGISTRATION_PLEASE_WAIT(true, false), REGISTRATION_PLEASE_WAIT(true, false),
REGISTRATION_INVALID_TOKEN(true,false), REGISTRATION_INVALID_TOKEN(true, false),
REGISTRATION_PASSWORD_TOO_WEAK(true, false), REGISTRATION_PASSWORD_TOO_WEAK(true, false),
TLS_ERROR, TLS_ERROR,
TLS_ERROR_DOMAIN, TLS_ERROR_DOMAIN,

View file

@ -64,7 +64,7 @@ import eu.siacs.conversations.xmpp.mam.MamReference;
public class DatabaseBackend extends SQLiteOpenHelper { public class DatabaseBackend extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "history"; 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 boolean requiresMessageIndexRebuild = false;
private static DatabaseBackend instance = null; private static DatabaseBackend instance = null;
@ -232,6 +232,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Account.RESOURCE + " TEXT," + Account.RESOURCE + " TEXT,"
+ Account.PINNED_MECHANISM + " TEXT," + Account.PINNED_MECHANISM + " TEXT,"
+ Account.PINNED_CHANNEL_BINDING + " TEXT," + Account.PINNED_CHANNEL_BINDING + " TEXT,"
+ Account.FAST_MECHANISM + " TEXT,"
+ Account.FAST_TOKEN + " TEXT,"
+ Account.PORT + " NUMBER DEFAULT 5222)"); + Account.PORT + " NUMBER DEFAULT 5222)");
db.execSQL("create table " + Conversation.TABLENAME + " (" db.execSQL("create table " + Conversation.TABLENAME + " ("
+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
@ -594,7 +596,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if (oldVersion < 50 && newVersion >= 50) { 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_MECHANISM + " TEXT");
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PINNED_CHANNEL_BINDING + " 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");
} }
} }

View file

@ -757,7 +757,6 @@ public class XmppConnection implements Runnable {
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() account.getJid().asBareJid()
+ ": jid changed during SASL 2.0. updating database"); + ": jid changed during SASL 2.0. updating database");
mXmppConnectionService.databaseBackend.updateAccount(account);
} }
final Element bound = success.findChild("bound", Namespace.BIND2); final Element bound = success.findChild("bound", Namespace.BIND2);
final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3"); final Element resumed = success.findChild("resumed", "urn:xmpp:sm:3");
@ -798,11 +797,21 @@ public class XmppConnection implements Runnable {
} }
sendPostBindInitialization(waitForDisco, carbonsEnabled != null); sendPostBindInitialization(waitForDisco, carbonsEnabled != null);
} }
//TODO figure out name either by the existence of hashTokenRequest or if scramMechanism is of instance HashedToken final HashedToken.Mechanism tokenMechanism;
if (this.hashTokenRequest != null && !Strings.isNullOrEmpty(token)) { final SaslMechanism currentMechanism = this.saslMechanism;
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": storing hashed token "+this.hashTokenRequest.name()+ " "+token); 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; this.quickStartInProgress = false;
if (version == SaslMechanism.Version.SASL) { if (version == SaslMechanism.Version.SASL) {
tagReader.reset(); tagReader.reset();
@ -826,6 +835,7 @@ public class XmppConnection implements Runnable {
} catch (final IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER); throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);
} }
Log.d(Config.LOGTAG,failure.toString());
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": login failure " + version); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": login failure " + version);
if (failure.hasChild("temporary-auth-failure")) { if (failure.hasChild("temporary-auth-failure")) {
throw new StateChangingException(Account.State.TEMPORARY_AUTH_FAILURE); throw new StateChangingException(Account.State.TEMPORARY_AUTH_FAILURE);
@ -1340,6 +1350,7 @@ public class XmppConnection implements Runnable {
} }
final boolean quickStartAvailable; final boolean quickStartAvailable;
final String firstMessage = saslMechanism.getClientFirstMessage(); final String firstMessage = saslMechanism.getClientFirstMessage();
final boolean usingFast = saslMechanism instanceof HashedToken;
final Element authenticate; final Element authenticate;
if (version == SaslMechanism.Version.SASL) { if (version == SaslMechanism.Version.SASL) {
authenticate = new Element("auth", Namespace.SASL); authenticate = new Element("auth", Namespace.SASL);
@ -1350,9 +1361,15 @@ public class XmppConnection implements Runnable {
} else if (version == SaslMechanism.Version.SASL_2) { } else if (version == SaslMechanism.Version.SASL_2) {
final Element inline = authElement.findChild("inline", Namespace.SASL_2); final Element inline = authElement.findChild("inline", Namespace.SASL_2);
final boolean sm = inline != null && inline.hasChild("sm", "urn:xmpp:sm:3"); final boolean sm = inline != null && inline.hasChild("sm", "urn:xmpp:sm:3");
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST); final HashedToken.Mechanism hashTokenRequest;
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast); if (usingFast) {
final HashedToken.Mechanism hashTokenRequest = HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)); hashTokenRequest = null;
} else {
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST);
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
hashTokenRequest =
HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
}
final Collection<String> bindFeatures = Bind2.features(inline); final Collection<String> bindFeatures = Bind2.features(inline);
quickStartAvailable = quickStartAvailable =
sm sm
@ -1370,7 +1387,7 @@ public class XmppConnection implements Runnable {
} }
} }
this.hashTokenRequest = hashTokenRequest; this.hashTokenRequest = hashTokenRequest;
authenticate = generateAuthenticationRequest(firstMessage, hashTokenRequest, bindFeatures, sm); authenticate = generateAuthenticationRequest(firstMessage, usingFast, hashTokenRequest, bindFeatures, sm);
} else { } else {
throw new AssertionError("Missing implementation for " + version); throw new AssertionError("Missing implementation for " + version);
} }
@ -1390,12 +1407,13 @@ public class XmppConnection implements Runnable {
tagWriter.writeElement(authenticate); tagWriter.writeElement(authenticate);
} }
private Element generateAuthenticationRequest(final String firstMessage) { private Element generateAuthenticationRequest(final String firstMessage, final boolean usingFast) {
return generateAuthenticationRequest(firstMessage, null, Bind2.QUICKSTART_FEATURES, true); return generateAuthenticationRequest(firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
} }
private Element generateAuthenticationRequest( private Element generateAuthenticationRequest(
final String firstMessage, final String firstMessage,
final boolean usingFast,
final HashedToken.Mechanism hashedTokenRequest, final HashedToken.Mechanism hashedTokenRequest,
final Collection<String> bind, final Collection<String> bind,
final boolean inlineStreamManagement) { final boolean inlineStreamManagement) {
@ -1423,7 +1441,12 @@ public class XmppConnection implements Runnable {
authenticate.addChild(resume); authenticate.addChild(resume);
} }
if (hashedTokenRequest != null) { 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; return authenticate;
} }
@ -2059,25 +2082,26 @@ public class XmppConnection implements Runnable {
private boolean establishStream(final SSLSockets.Version sslVersion) private boolean establishStream(final SSLSockets.Version sslVersion)
throws IOException, InterruptedException { throws IOException, InterruptedException {
final SaslMechanism pinnedMechanism = final SaslMechanism quickStartMechanism =
SaslMechanism.ensureAvailable(account.getPinnedMechanism(), sslVersion); SaslMechanism.ensureAvailable(account.getQuickStartMechanism(), sslVersion);
final boolean secureConnection = sslVersion != SSLSockets.Version.NONE; final boolean secureConnection = sslVersion != SSLSockets.Version.NONE;
if (secureConnection if (secureConnection
&& Config.QUICKSTART_ENABLED && Config.QUICKSTART_ENABLED
&& pinnedMechanism != null && quickStartMechanism != null
&& account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) { && account.isOptionSet(Account.OPTION_QUICKSTART_AVAILABLE)) {
mXmppConnectionService.restoredFromDatabaseLatch.await(); mXmppConnectionService.restoredFromDatabaseLatch.await();
this.saslMechanism = pinnedMechanism; this.saslMechanism = quickStartMechanism;
final boolean usingFast = quickStartMechanism instanceof HashedToken;
final Element authenticate = final Element authenticate =
generateAuthenticationRequest(pinnedMechanism.getClientFirstMessage()); generateAuthenticationRequest(quickStartMechanism.getClientFirstMessage(), usingFast);
authenticate.setAttribute("mechanism", pinnedMechanism.getMechanism()); authenticate.setAttribute("mechanism", quickStartMechanism.getMechanism());
sendStartStream(true, false); sendStartStream(true, false);
tagWriter.writeElement(authenticate); tagWriter.writeElement(authenticate);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.getJid().toString() account.getJid().toString()
+ ": quick start with " + ": quick start with "
+ pinnedMechanism.getMechanism()); + quickStartMechanism.getMechanism());
return true; return true;
} else { } else {
sendStartStream(secureConnection, true); sendStartStream(secureConnection, true);