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 final String PREFIX = "HT";
 22
 23    private static final List<String> HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256");
 24
 25    protected final ChannelBinding channelBinding;
 26
 27    protected HashedToken(final Account account, final ChannelBinding channelBinding) {
 28        super(account);
 29        this.channelBinding = channelBinding;
 30    }
 31
 32    @Override
 33    public int getPriority() {
 34        throw new UnsupportedOperationException();
 35    }
 36
 37    @Override
 38    public String getClientFirstMessage() {
 39        return null; // HMAC(token, "Initiator" || cb-data)
 40    }
 41
 42    @Override
 43    public String getResponse(final String challenge, final SSLSocket socket)
 44            throws AuthenticationException {
 45        // todo verify that challenge matches HMAC(token, "Responder" || cb-data)
 46        return null;
 47    }
 48
 49    protected abstract HashFunction getHashFunction(final byte[] key);
 50
 51    public static final class Mechanism {
 52        public final String hashFunction;
 53        public final ChannelBinding channelBinding;
 54
 55        public Mechanism(String hashFunction, ChannelBinding channelBinding) {
 56            this.hashFunction = hashFunction;
 57            this.channelBinding = channelBinding;
 58        }
 59
 60        public static Mechanism of(final String mechanism) {
 61            final int first = mechanism.indexOf('-');
 62            final int last = mechanism.lastIndexOf('-');
 63            if (last <= first || mechanism.length() <= last) {
 64                throw new IllegalArgumentException("Not a valid HashedToken name");
 65            }
 66            if (mechanism.substring(0, first).equals(PREFIX)) {
 67                final String hashFunction = mechanism.substring(first + 1, last);
 68                final String cbShortName = mechanism.substring(last + 1);
 69                final ChannelBinding channelBinding =
 70                        ChannelBinding.SHORT_NAMES.inverse().get(cbShortName);
 71                if (channelBinding == null) {
 72                    throw new IllegalArgumentException("Unknown channel binding " + cbShortName);
 73                }
 74                return new Mechanism(hashFunction, channelBinding);
 75            } else {
 76                throw new IllegalArgumentException("HashedToken name does not start with HT");
 77            }
 78        }
 79
 80        public static Multimap<String, ChannelBinding> of(final Collection<String> mechanisms) {
 81            final ImmutableMultimap.Builder<String, ChannelBinding> builder =
 82                    ImmutableMultimap.builder();
 83            for (final String name : mechanisms) {
 84                try {
 85                    final Mechanism mechanism = Mechanism.of(name);
 86                    builder.put(mechanism.hashFunction, mechanism.channelBinding);
 87                } catch (final IllegalArgumentException ignored) {
 88                }
 89            }
 90            return builder.build();
 91        }
 92
 93        public static Mechanism best(
 94                final Collection<String> mechanisms, final SSLSockets.Version sslVersion) {
 95            final Multimap<String, ChannelBinding> multimap = of(mechanisms);
 96            for (final String hashFunction : HASH_FUNCTIONS) {
 97                final Collection<ChannelBinding> channelBindings = multimap.get(hashFunction);
 98                if (channelBindings.isEmpty()) {
 99                    continue;
100                }
101                final ChannelBinding cb = ChannelBinding.best(channelBindings, sslVersion);
102                return new Mechanism(hashFunction, cb);
103            }
104            return null;
105        }
106
107        @NotNull
108        @Override
109        public String toString() {
110            return MoreObjects.toStringHelper(this)
111                    .add("hashFunction", hashFunction)
112                    .add("channelBinding", channelBinding)
113                    .toString();
114        }
115
116        public String name() {
117            return String.format(
118                    "%s-%s-%s",
119                    PREFIX, hashFunction, ChannelBinding.SHORT_NAMES.get(channelBinding));
120        }
121    }
122}