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