HashedToken.java

  1package eu.siacs.conversations.crypto.sasl;
  2
  3import com.google.common.base.MoreObjects;
  4import com.google.common.collect.ImmutableMultimap;
  5import com.google.common.collect.Multimap;
  6import com.google.common.hash.HashFunction;
  7
  8import org.jetbrains.annotations.NotNull;
  9
 10import java.util.Arrays;
 11import java.util.Collection;
 12import java.util.List;
 13
 14import javax.net.ssl.SSLSocket;
 15
 16import eu.siacs.conversations.entities.Account;
 17import eu.siacs.conversations.utils.SSLSockets;
 18
 19public abstract class HashedToken extends SaslMechanism {
 20
 21    private static List<String> HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256");
 22
 23    protected final ChannelBinding channelBinding;
 24
 25    protected HashedToken(final Account account, final ChannelBinding channelBinding) {
 26        super(account);
 27        this.channelBinding = channelBinding;
 28    }
 29
 30    @Override
 31    public int getPriority() {
 32        throw new UnsupportedOperationException();
 33    }
 34
 35    @Override
 36    public String getClientFirstMessage() {
 37        return null; // HMAC(token, "Initiator" || cb-data)
 38    }
 39
 40    @Override
 41    public String getResponse(final String challenge, final SSLSocket socket)
 42            throws AuthenticationException {
 43        // todo verify that challenge matches HMAC(token, "Responder" || cb-data)
 44        return null;
 45    }
 46
 47    protected abstract HashFunction getHashFunction(final byte[] key);
 48
 49    public static final class Mechanism {
 50        public final String hashFunction;
 51        public final ChannelBinding channelBinding;
 52
 53        public Mechanism(String hashFunction, ChannelBinding channelBinding) {
 54            this.hashFunction = hashFunction;
 55            this.channelBinding = channelBinding;
 56        }
 57
 58        public static Mechanism of(final String mechanism) {
 59            final int first = mechanism.indexOf('-');
 60            final int last = mechanism.lastIndexOf('-');
 61            if (last <= first || mechanism.length() <= last) {
 62                throw new IllegalArgumentException("Not a valid HashedToken name");
 63            }
 64            if (mechanism.substring(0, first).equals("HT")) {
 65                final String hashFunction = mechanism.substring(first + 1, last);
 66                final String cbShortName = mechanism.substring(last + 1);
 67                final ChannelBinding channelBinding =
 68                        ChannelBinding.SHORT_NAMES.inverse().get(cbShortName);
 69                if (channelBinding == null) {
 70                    throw new IllegalArgumentException("Unknown channel binding " + cbShortName);
 71                }
 72                return new Mechanism(hashFunction, channelBinding);
 73            } else {
 74                throw new IllegalArgumentException("HashedToken name does not start with HT");
 75            }
 76        }
 77
 78        public static Multimap<String, ChannelBinding> of(final Collection<String> mechanisms) {
 79            final ImmutableMultimap.Builder<String, ChannelBinding> builder =
 80                    ImmutableMultimap.builder();
 81            for (final String name : mechanisms) {
 82                try {
 83                    final Mechanism mechanism = Mechanism.of(name);
 84                    builder.put(mechanism.hashFunction, mechanism.channelBinding);
 85                } catch (final IllegalArgumentException ignored) {
 86                }
 87            }
 88            return builder.build();
 89        }
 90
 91        public static Mechanism best(
 92                final Collection<String> mechanisms, final SSLSockets.Version sslVersion) {
 93            final Multimap<String, ChannelBinding> multimap = of(mechanisms);
 94            for (final String hashFunction : HASH_FUNCTIONS) {
 95                final Collection<ChannelBinding> channelBindings = multimap.get(hashFunction);
 96                if (channelBindings.isEmpty()) {
 97                    continue;
 98                }
 99                final ChannelBinding cb = ChannelBinding.best(channelBindings, sslVersion);
100                return new Mechanism(hashFunction, cb);
101            }
102            return null;
103        }
104
105        @NotNull
106        @Override
107        public String toString() {
108            return MoreObjects.toStringHelper(this)
109                    .add("hashFunction", hashFunction)
110                    .add("channelBinding", channelBinding)
111                    .toString();
112        }
113    }
114}