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}