1package eu.siacs.conversations.crypto.sasl;
2
3import com.google.common.base.Strings;
4
5import java.util.Collection;
6
7import javax.net.ssl.SSLSocket;
8
9import eu.siacs.conversations.entities.Account;
10import eu.siacs.conversations.xml.Element;
11import eu.siacs.conversations.xml.Namespace;
12
13public abstract class SaslMechanism {
14
15 protected final Account account;
16
17 protected SaslMechanism(final Account account) {
18 this.account = account;
19 }
20
21 public static String namespace(final Version version) {
22 if (version == Version.SASL) {
23 return Namespace.SASL;
24 } else {
25 return Namespace.SASL_2;
26 }
27 }
28
29 /**
30 * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be
31 * retried with another mechanism of the same priority, but MUST NOT be tried with a mechanism
32 * of lower priority (to prevent downgrade attacks).
33 *
34 * @return An arbitrary int representing the priority
35 */
36 public abstract int getPriority();
37
38 public abstract String getMechanism();
39
40 public String getClientFirstMessage() {
41 return "";
42 }
43
44 public String getResponse(final String challenge, final SSLSocket sslSocket)
45 throws AuthenticationException {
46 return "";
47 }
48
49 protected enum State {
50 INITIAL,
51 AUTH_TEXT_SENT,
52 RESPONSE_SENT,
53 VALID_SERVER_RESPONSE,
54 }
55
56 public enum Version {
57 SASL,
58 SASL_2;
59
60 public static Version of(final Element element) {
61 switch (Strings.nullToEmpty(element.getNamespace())) {
62 case Namespace.SASL:
63 return SASL;
64 case Namespace.SASL_2:
65 return SASL_2;
66 default:
67 throw new IllegalArgumentException("Unrecognized SASL namespace");
68 }
69 }
70 }
71
72 public static class AuthenticationException extends Exception {
73 public AuthenticationException(final String message) {
74 super(message);
75 }
76
77 public AuthenticationException(final Exception inner) {
78 super(inner);
79 }
80
81 public AuthenticationException(final String message, final Exception exception) {
82 super(message, exception);
83 }
84 }
85
86 public static class InvalidStateException extends AuthenticationException {
87 public InvalidStateException(final String message) {
88 super(message);
89 }
90
91 public InvalidStateException(final State state) {
92 this("Invalid state: " + state.toString());
93 }
94 }
95
96 public static final class Factory {
97
98 private final Account account;
99
100 public Factory(final Account account) {
101 this.account = account;
102 }
103
104 public SaslMechanism of(
105 final Collection<String> mechanisms, final Collection<ChannelBinding> bindings) {
106 final ChannelBinding channelBinding = ChannelBinding.best(bindings);
107 if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
108 return new External(account);
109 } else if (mechanisms.contains(ScramSha512Plus.MECHANISM) && channelBinding != null) {
110 return new ScramSha512Plus(account, channelBinding);
111 } else if (mechanisms.contains(ScramSha256Plus.MECHANISM) && channelBinding != null) {
112 return new ScramSha256Plus(account, channelBinding);
113 } else if (mechanisms.contains(ScramSha1Plus.MECHANISM) && channelBinding != null) {
114 return new ScramSha1Plus(account, channelBinding);
115 } else if (mechanisms.contains(ScramSha512.MECHANISM)) {
116 return new ScramSha512(account);
117 } else if (mechanisms.contains(ScramSha256.MECHANISM)) {
118 return new ScramSha256(account);
119 } else if (mechanisms.contains(ScramSha1.MECHANISM)) {
120 return new ScramSha1(account);
121 } else if (mechanisms.contains(Plain.MECHANISM)
122 && !account.getServer().equals("nimbuzz.com")) {
123 return new Plain(account);
124 } else if (mechanisms.contains(DigestMd5.MECHANISM)) {
125 return new DigestMd5(account);
126 } else if (mechanisms.contains(Anonymous.MECHANISM)) {
127 return new Anonymous(account);
128 } else {
129 return null;
130 }
131 }
132 }
133}