1package eu.siacs.conversations.crypto.sasl;
2
3import com.google.common.base.CharMatcher;
4import com.google.common.base.Joiner;
5import com.google.common.base.Strings;
6import com.google.common.collect.ImmutableList;
7import com.google.common.collect.Ordering;
8import java.util.Collection;
9
10public class DowngradeProtection {
11
12 private static final char SEPARATOR = 0x1E;
13 private static final char SEPARATOR_MECHANISM_AND_BINDING = 0x1F;
14
15 public final ImmutableList<String> mechanisms;
16 public final ImmutableList<String> channelBindings;
17
18 public DowngradeProtection(
19 final Collection<String> mechanisms, final Collection<String> channelBindings) {
20 this.mechanisms = Ordering.natural().immutableSortedCopy(mechanisms);
21 this.channelBindings = Ordering.natural().immutableSortedCopy(channelBindings);
22 }
23
24 public DowngradeProtection(final Collection<String> mechanisms) {
25 this.mechanisms = Ordering.natural().immutableSortedCopy(mechanisms);
26 this.channelBindings = null;
27 }
28
29 public String asHString() {
30 ensureSaslMechanismFormat(this.mechanisms);
31 ensureNoSeparators(this.mechanisms);
32 if (this.channelBindings != null) {
33 ensureNoSeparators(this.channelBindings);
34 ensureBindingFormat(this.channelBindings);
35 final var builder = new StringBuilder();
36 Joiner.on(SEPARATOR).appendTo(builder, mechanisms);
37 builder.append(SEPARATOR_MECHANISM_AND_BINDING);
38 Joiner.on(SEPARATOR).appendTo(builder, channelBindings);
39 return builder.toString();
40 } else {
41 return Joiner.on(SEPARATOR).join(mechanisms);
42 }
43 }
44
45 private static void ensureNoSeparators(final Iterable<String> list) {
46 for (final String item : list) {
47 if (item.indexOf(SEPARATOR) >= 0
48 || item.indexOf(SEPARATOR_MECHANISM_AND_BINDING) >= 0) {
49 throw new SecurityException("illegal chars found in list");
50 }
51 }
52 }
53
54 private static void ensureSaslMechanismFormat(final Iterable<String> names) {
55 for (final String name : names) {
56 ensureSaslMechanismFormat(name);
57 }
58 }
59
60 private static void ensureSaslMechanismFormat(final String name) {
61 if (Strings.isNullOrEmpty(name)) {
62 throw new SecurityException("Empty sasl mechanism names are not permitted");
63 }
64 // https://www.rfc-editor.org/rfc/rfc4422.html#section-3.1
65 if (name.length() <= 20
66 && CharMatcher.inRange('A', 'Z')
67 .or(CharMatcher.inRange('0', '9'))
68 .or(CharMatcher.is('-'))
69 .or(CharMatcher.is('_'))
70 .matchesAllOf(name)
71 && !Character.isDigit(name.charAt(0))) {
72 return;
73 }
74 throw new SecurityException("Encountered illegal sasl name");
75 }
76
77 private static void ensureBindingFormat(final Iterable<String> names) {
78 for (final String name : names) {
79 ensureBindingFormat(name);
80 }
81 }
82
83 private static void ensureBindingFormat(final String name) {
84 if (Strings.isNullOrEmpty(name)) {
85 throw new SecurityException("Empty binding names are not permitted");
86 }
87 // https://www.rfc-editor.org/rfc/rfc5056.html#section-7d
88 if (CharMatcher.inRange('A', 'Z')
89 .or(CharMatcher.inRange('a', 'z'))
90 .or(CharMatcher.inRange('0', '9'))
91 .or(CharMatcher.is('.'))
92 .or(CharMatcher.is('-'))
93 .matchesAllOf(name)) {
94 return;
95 }
96 throw new SecurityException("Encountered illegal binding name");
97 }
98}