parse hash token names

This commit is contained in:
Daniel Gultsch 2022-10-15 00:09:29 +02:00
parent 0cd416298d
commit 3378447f60
5 changed files with 172 additions and 4 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 boolean QUICKSTART_ENABLED = true;
public static final boolean QUICKSTART_ENABLED = false;
//Notification settings
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;

View file

@ -6,7 +6,9 @@ import com.google.common.base.CaseFormat;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableBiMap;
import java.util.Arrays;
import java.util.Collection;
@ -23,6 +25,16 @@ public enum ChannelBinding {
TLS_SERVER_END_POINT,
TLS_UNIQUE;
public static final BiMap<ChannelBinding, String> SHORT_NAMES;
static {
final ImmutableBiMap.Builder<ChannelBinding, String> builder = ImmutableBiMap.builder();
for (final ChannelBinding cb : values()) {
builder.put(cb, shortName(cb));
}
SHORT_NAMES = builder.build();
}
public static Collection<ChannelBinding> of(final Element channelBinding) {
Preconditions.checkArgument(
channelBinding == null
@ -85,7 +97,24 @@ public enum ChannelBinding {
}
}
public static boolean ensureBest(final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) {
return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion) == channelBinding;
public static boolean ensureBest(
final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) {
return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion)
== channelBinding;
}
private static String shortName(final ChannelBinding channelBinding) {
switch (channelBinding) {
case TLS_UNIQUE:
return "UNIQ";
case TLS_EXPORTER:
return "EXPR";
case TLS_SERVER_END_POINT:
return "ENDP";
case NONE:
return "NONE";
default:
throw new AssertionError("Missing short name for " + channelBinding);
}
}
}

View file

@ -0,0 +1,114 @@
package eu.siacs.conversations.crypto.sasl;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.hash.HashFunction;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.net.ssl.SSLSocket;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.SSLSockets;
public abstract class HashedToken extends SaslMechanism {
private static List<String> HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256");
protected final ChannelBinding channelBinding;
protected HashedToken(final Account account, final ChannelBinding channelBinding) {
super(account);
this.channelBinding = channelBinding;
}
@Override
public int getPriority() {
throw new UnsupportedOperationException();
}
@Override
public String getClientFirstMessage() {
return null; // HMAC(token, "Initiator" || cb-data)
}
@Override
public String getResponse(final String challenge, final SSLSocket socket)
throws AuthenticationException {
// todo verify that challenge matches HMAC(token, "Responder" || cb-data)
return null;
}
protected abstract HashFunction getHashFunction(final byte[] key);
public static final class Mechanism {
public final String hashFunction;
public final ChannelBinding channelBinding;
public Mechanism(String hashFunction, ChannelBinding channelBinding) {
this.hashFunction = hashFunction;
this.channelBinding = channelBinding;
}
public static Mechanism of(final String mechanism) {
final int first = mechanism.indexOf('-');
final int last = mechanism.lastIndexOf('-');
if (last <= first || mechanism.length() <= last) {
throw new IllegalArgumentException("Not a valid HashedToken name");
}
if (mechanism.substring(0, first).equals("HT")) {
final String hashFunction = mechanism.substring(first + 1, last);
final String cbShortName = mechanism.substring(last + 1);
final ChannelBinding channelBinding =
ChannelBinding.SHORT_NAMES.inverse().get(cbShortName);
if (channelBinding == null) {
throw new IllegalArgumentException("Unknown channel binding " + cbShortName);
}
return new Mechanism(hashFunction, channelBinding);
} else {
throw new IllegalArgumentException("HashedToken name does not start with HT");
}
}
public static Multimap<String, ChannelBinding> of(final Collection<String> mechanisms) {
final ImmutableMultimap.Builder<String, ChannelBinding> builder =
ImmutableMultimap.builder();
for (final String name : mechanisms) {
try {
final Mechanism mechanism = Mechanism.of(name);
builder.put(mechanism.hashFunction, mechanism.channelBinding);
} catch (final IllegalArgumentException ignored) {
}
}
return builder.build();
}
public static Mechanism best(
final Collection<String> mechanisms, final SSLSockets.Version sslVersion) {
final Multimap<String, ChannelBinding> multimap = of(mechanisms);
for (final String hashFunction : HASH_FUNCTIONS) {
final Collection<ChannelBinding> channelBindings = multimap.get(hashFunction);
if (channelBindings.isEmpty()) {
continue;
}
final ChannelBinding cb = ChannelBinding.best(channelBindings, sslVersion);
return new Mechanism(hashFunction, cb);
}
return null;
}
@NotNull
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("hashFunction", hashFunction)
.add("channelBinding", channelBinding)
.toString();
}
}
}

View file

@ -0,0 +1,24 @@
package eu.siacs.conversations.crypto.sasl;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import eu.siacs.conversations.entities.Account;
public class HashedTokenSha256 extends HashedToken {
public HashedTokenSha256(final Account account, final ChannelBinding channelBinding) {
super(account, channelBinding);
}
@Override
protected HashFunction getHashFunction(final byte[] key) {
return Hashing.hmacSha256(key);
}
@Override
public String getMechanism() {
final String cbShortName = ChannelBinding.SHORT_NAMES.get(this.channelBinding);
return String.format("HT-SHA-256-%s", cbShortName);
}
}

View file

@ -63,6 +63,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.sasl.ChannelBinding;
import eu.siacs.conversations.crypto.sasl.HashedToken;
import eu.siacs.conversations.crypto.sasl.SaslMechanism;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
@ -1344,7 +1345,7 @@ public class XmppConnection implements Runnable {
final boolean sm = inline != null && inline.hasChild("sm", "urn:xmpp:sm:3");
final Element fast = inline == null ? null : inline.findChild("fast", Namespace.FAST);
final Collection<String> fastMechanisms = SaslMechanism.mechanisms(fast);
Log.d(Config.LOGTAG,"fast mechanisms: "+fastMechanisms);
Log.d(Config.LOGTAG,"fast mechanism: "+ HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket)));
final Collection<String> bindFeatures = Bind2.features(inline);
quickStartAvailable =
sm