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