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}