DowngradeProtection.java

 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 = ',';
13    private static final char SEPARATOR_MECHANISM_AND_BINDING = '|';
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 asDString() {
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}