1package eu.siacs.conversations.crypto.sasl;
2
3import android.util.Log;
4import com.google.common.base.CaseFormat;
5import com.google.common.base.Predicates;
6import com.google.common.base.Strings;
7import com.google.common.collect.BiMap;
8import com.google.common.collect.Collections2;
9import com.google.common.collect.ImmutableBiMap;
10import eu.siacs.conversations.Config;
11import eu.siacs.conversations.utils.SSLSockets;
12import im.conversations.android.xmpp.model.cb.SaslChannelBinding;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16
17public enum ChannelBinding {
18 NONE,
19 TLS_EXPORTER,
20 TLS_SERVER_END_POINT,
21 TLS_UNIQUE;
22
23 public static final BiMap<ChannelBinding, String> SHORT_NAMES;
24
25 static {
26 final ImmutableBiMap.Builder<ChannelBinding, String> builder = ImmutableBiMap.builder();
27 for (final ChannelBinding cb : values()) {
28 builder.put(cb, shortName(cb));
29 }
30 SHORT_NAMES = builder.build();
31 }
32
33 public static Collection<ChannelBinding> of(final SaslChannelBinding channelBinding) {
34 if (channelBinding == null) {
35 return Collections.emptyList();
36 }
37 return Collections2.filter(
38 Collections2.transform(
39 channelBinding.getChannelBindings(), cb -> ChannelBinding.of(cb.getType())),
40 Predicates.notNull());
41 }
42
43 private static ChannelBinding of(final String type) {
44 if (type == null) {
45 return null;
46 }
47 try {
48 return valueOf(
49 CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_UNDERSCORE).convert(type));
50 } catch (final IllegalArgumentException e) {
51 Log.d(Config.LOGTAG, type + " is not a known channel binding");
52 return null;
53 }
54 }
55
56 public static ChannelBinding get(final String name) {
57 if (Strings.isNullOrEmpty(name)) {
58 return NONE;
59 }
60 try {
61 return valueOf(name);
62 } catch (final IllegalArgumentException e) {
63 return NONE;
64 }
65 }
66
67 public static ChannelBinding best(
68 final Collection<ChannelBinding> bindings, final SSLSockets.Version sslVersion) {
69 if (sslVersion == SSLSockets.Version.NONE) {
70 return NONE;
71 }
72 if (bindings.contains(TLS_EXPORTER) && sslVersion == SSLSockets.Version.TLS_1_3) {
73 return TLS_EXPORTER;
74 } else if (bindings.contains(TLS_UNIQUE)
75 && Arrays.asList(
76 SSLSockets.Version.TLS_1_0,
77 SSLSockets.Version.TLS_1_1,
78 SSLSockets.Version.TLS_1_2)
79 .contains(sslVersion)) {
80 return TLS_UNIQUE;
81 } else if (bindings.contains(TLS_SERVER_END_POINT)) {
82 return TLS_SERVER_END_POINT;
83 } else {
84 return NONE;
85 }
86 }
87
88 public static boolean isAvailable(
89 final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) {
90 return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion)
91 == channelBinding;
92 }
93
94 private static String shortName(final ChannelBinding channelBinding) {
95 return switch (channelBinding) {
96 case TLS_UNIQUE -> "UNIQ";
97 case TLS_EXPORTER -> "EXPR";
98 case TLS_SERVER_END_POINT -> "ENDP";
99 case NONE -> "NONE";
100 default -> throw new AssertionError("Missing short name for " + channelBinding);
101 };
102 }
103
104 public static int priority(final ChannelBinding channelBinding) {
105 if (Arrays.asList(TLS_EXPORTER, TLS_UNIQUE).contains(channelBinding)) {
106 return 2;
107 } else if (channelBinding == ChannelBinding.TLS_SERVER_END_POINT) {
108 return 1;
109 } else {
110 return 0;
111 }
112 }
113}