do quick start with HT-SHA-256-NONE
This commit is contained in:
parent
c13787873c
commit
24badda4c9
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package eu.siacs.conversations.crypto.sasl;
|
||||||
|
|
||||||
|
public interface ChannelBindingMechanism {
|
||||||
|
|
||||||
|
ChannelBinding getChannelBinding();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
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;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 =
|
||||||
|
Jid.of(
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)),
|
cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)),
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(SERVER)),
|
cursor.getString(cursor.getColumnIndexOrThrow(SERVER)),
|
||||||
resource == null || resource.trim().isEmpty() ? null : resource);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 HashedToken.Mechanism hashTokenRequest;
|
||||||
|
if (usingFast) {
|
||||||
|
hashTokenRequest = null;
|
||||||
|
} else {
|
||||||
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST);
|
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST);
|
||||||
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
|
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
|
||||||
final HashedToken.Mechanism hashTokenRequest = HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
|
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);
|
||||||
|
|
Loading…
Reference in a new issue