001/** 002 * 003 * Copyright 2003-2007 Jive Software, 2014-2016 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.sasl; 018 019import javax.net.ssl.SSLSession; 020import javax.security.auth.callback.CallbackHandler; 021 022import org.jivesoftware.smack.ConnectionConfiguration; 023import org.jivesoftware.smack.SmackException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.XMPPConnection; 026import org.jivesoftware.smack.sasl.packet.SaslStreamElements.AuthMechanism; 027import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Response; 028import org.jivesoftware.smack.util.StringTransformer; 029import org.jivesoftware.smack.util.StringUtils; 030import org.jivesoftware.smack.util.stringencoder.Base64; 031 032import org.jxmpp.jid.DomainBareJid; 033import org.jxmpp.jid.EntityBareJid; 034 035/** 036 * Base class for SASL mechanisms. 037 * Subclasses will likely want to implement their own versions of these methods: 038 * <ul> 039 * <li>{@link #authenticate(String, String, DomainBareJid, String, EntityBareJid, SSLSession)} -- Initiate authentication stanza using the 040 * deprecated method.</li> 041 * <li>{@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} -- Initiate authentication stanza 042 * using the CallbackHandler method.</li> 043 * <li>{@link #challengeReceived(String, boolean)} -- Handle a challenge from the server.</li> 044 * </ul> 045 * 046 * @author Jay Kline 047 * @author Florian Schmaus 048 */ 049public abstract class SASLMechanism implements Comparable<SASLMechanism> { 050 051 public static final String CRAMMD5 = "CRAM-MD5"; 052 public static final String DIGESTMD5 = "DIGEST-MD5"; 053 public static final String EXTERNAL = "EXTERNAL"; 054 public static final String GSSAPI = "GSSAPI"; 055 public static final String PLAIN = "PLAIN"; 056 057 // TODO Remove once Smack's min Android API is 9, where java.text.Normalizer is available 058 private static StringTransformer saslPrepTransformer; 059 060 /** 061 * Set the SASLPrep StringTransformer. 062 * <p> 063 * A simple SASLPrep StringTransformer would be for example: <code>java.text.Normalizer.normalize(string, Form.NFKC);</code> 064 * </p> 065 * 066 * @param stringTransformer set StringTransformer to use for SASLPrep. 067 * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a> 068 */ 069 public static void setSaslPrepTransformer(StringTransformer stringTransformer) { 070 saslPrepTransformer = stringTransformer; 071 } 072 073 protected XMPPConnection connection; 074 075 protected ConnectionConfiguration connectionConfiguration; 076 077 /** 078 * Then authentication identity (authcid). RFC 6120 § 6.3.7 informs us that some SASL mechanisms use this as a 079 * "simple user name". But the exact form is a matter of the mechanism and that it does not necessarily map to an 080 * localpart. But it usually is the localpart of the client JID, although sometimes other formats are used (e.g. the 081 * full JID). 082 * <p> 083 * Not to be confused with the authzid (see RFC 6120 § 6.3.8). 084 * </p> 085 */ 086 protected String authenticationId; 087 088 /** 089 * The authorization identifier (authzid). 090 * This is always a bare Jid, but can be null. 091 */ 092 protected EntityBareJid authorizationId; 093 094 /** 095 * The name of the XMPP service 096 */ 097 protected DomainBareJid serviceName; 098 099 /** 100 * The users password 101 */ 102 protected String password; 103 protected String host; 104 105 /** 106 * The used SSL/TLS session (if any). 107 */ 108 protected SSLSession sslSession; 109 110 /** 111 * Builds and sends the <tt>auth</tt> stanza to the server. Note that this method of 112 * authentication is not recommended, since it is very inflexible. Use 113 * {@link #authenticate(String, DomainBareJid, CallbackHandler, EntityBareJid, SSLSession)} whenever possible. 114 * 115 * Explanation of auth stanza: 116 * 117 * The client authentication stanza needs to include the digest-uri of the form: xmpp/serviceName 118 * From RFC-2831: 119 * digest-uri = "digest-uri" "=" digest-uri-value 120 * digest-uri-value = serv-type "/" host [ "/" serv-name ] 121 * 122 * digest-uri: 123 * Indicates the principal name of the service with which the client 124 * wishes to connect, formed from the serv-type, host, and serv-name. 125 * For example, the FTP service 126 * on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP 127 * server from the example above would have a "digest-uri" value of 128 * "smtp/mail3.example.com/example.com". 129 * 130 * host: 131 * The DNS host name or IP address for the service requested. The DNS host name 132 * must be the fully-qualified canonical name of the host. The DNS host name is the 133 * preferred form; see notes on server processing of the digest-uri. 134 * 135 * serv-name: 136 * Indicates the name of the service if it is replicated. The service is 137 * considered to be replicated if the client's service-location process involves resolution 138 * using standard DNS lookup operations, and if these operations involve DNS records (such 139 * as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case, 140 * the initial name used by the client is the "serv-name", and the final name is the "host" 141 * component. For example, the incoming mail service for "example.com" may be replicated 142 * through the use of MX records stored in the DNS, one of which points at an SMTP server 143 * called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be 144 * "mail3.example.com". If the service is not replicated, or the serv-name is identical to 145 * the host, then the serv-name component MUST be omitted 146 * 147 * digest-uri verification is needed for ejabberd 2.0.3 and higher 148 * 149 * @param username the username of the user being authenticated. 150 * @param host the hostname where the user account resides. 151 * @param serviceName the xmpp service location - used by the SASL client in digest-uri creation 152 * serviceName format is: host [ "/" serv-name ] as per RFC-2831 153 * @param password the password for this account. 154 * @param authzid the optional authorization identity. 155 * @param sslSession the optional SSL/TLS session (if one was established) 156 * @throws SmackException If a network error occurs while authenticating. 157 * @throws NotConnectedException 158 * @throws InterruptedException 159 */ 160 public final void authenticate(String username, String host, DomainBareJid serviceName, String password, 161 EntityBareJid authzid, SSLSession sslSession) 162 throws SmackException, NotConnectedException, InterruptedException { 163 this.authenticationId = username; 164 this.host = host; 165 this.serviceName = serviceName; 166 this.password = password; 167 this.authorizationId = authzid; 168 this.sslSession = sslSession; 169 assert (authorizationId == null || authzidSupported()); 170 authenticateInternal(); 171 authenticate(); 172 } 173 174 /** 175 * @throws SmackException 176 */ 177 protected void authenticateInternal() throws SmackException { 178 } 179 180 /** 181 * Builds and sends the <tt>auth</tt> stanza to the server. The callback handler will handle 182 * any additional information, such as the authentication ID or realm, if it is needed. 183 * 184 * @param host the hostname where the user account resides. 185 * @param serviceName the xmpp service location 186 * @param cbh the CallbackHandler to obtain user information. 187 * @param authzid the optional authorization identity. 188 * @param sslSession the optional SSL/TLS session (if one was established) 189 * @throws SmackException 190 * @throws NotConnectedException 191 * @throws InterruptedException 192 */ 193 public void authenticate(String host, DomainBareJid serviceName, CallbackHandler cbh, EntityBareJid authzid, SSLSession sslSession) 194 throws SmackException, NotConnectedException, InterruptedException { 195 this.host = host; 196 this.serviceName = serviceName; 197 this.authorizationId = authzid; 198 this.sslSession = sslSession; 199 assert (authorizationId == null || authzidSupported()); 200 authenticateInternal(cbh); 201 authenticate(); 202 } 203 204 protected abstract void authenticateInternal(CallbackHandler cbh) throws SmackException; 205 206 private final void authenticate() throws SmackException, NotConnectedException, InterruptedException { 207 byte[] authenticationBytes = getAuthenticationText(); 208 String authenticationText; 209 // Some SASL mechanisms do return an empty array (e.g. EXTERNAL from javax), so check that 210 // the array is not-empty. Mechanisms are allowed to return either 'null' or an empty array 211 // if there is no authentication text. 212 if (authenticationBytes != null && authenticationBytes.length > 0) { 213 authenticationText = Base64.encodeToString(authenticationBytes); 214 } else { 215 // RFC6120 6.4.2 "If the initiating entity needs to send a zero-length initial response, 216 // it MUST transmit the response as a single equals sign character ("="), which 217 // indicates that the response is present but contains no data." 218 authenticationText = "="; 219 } 220 // Send the authentication to the server 221 connection.sendNonza(new AuthMechanism(getName(), authenticationText)); 222 } 223 224 /** 225 * Should return the initial response of the SASL mechanism. The returned byte array will be 226 * send base64 encoded to the server. SASL mechanism are free to return <code>null</code> or an 227 * empty array here. 228 * 229 * @return the initial response or null 230 * @throws SmackException 231 */ 232 protected abstract byte[] getAuthenticationText() throws SmackException; 233 234 /** 235 * The server is challenging the SASL mechanism for the stanza he just sent. Send a 236 * response to the server's challenge. 237 * 238 * @param challengeString a base64 encoded string representing the challenge. 239 * @param finalChallenge true if this is the last challenge send by the server within the success stanza 240 * @throws SmackException exception 241 * @throws InterruptedException if the connection is interrupted 242 */ 243 public final void challengeReceived(String challengeString, boolean finalChallenge) throws SmackException, InterruptedException { 244 byte[] challenge = Base64.decode((challengeString != null && challengeString.equals("=")) ? "" : challengeString); 245 byte[] response = evaluateChallenge(challenge); 246 if (finalChallenge) { 247 return; 248 } 249 250 Response responseStanza; 251 if (response == null) { 252 responseStanza = new Response(); 253 } 254 else { 255 responseStanza = new Response(Base64.encodeToString(response)); 256 } 257 258 // Send the authentication to the server 259 connection.sendNonza(responseStanza); 260 } 261 262 /** 263 * Evaluate the SASL challenge. 264 * 265 * @param challenge challenge to evaluate. 266 * 267 * @return null. 268 * @throws SmackException in case of an error. 269 */ 270 protected byte[] evaluateChallenge(byte[] challenge) throws SmackException { 271 return null; 272 } 273 274 @Override 275 public final int compareTo(SASLMechanism other) { 276 // Switch to Integer.compare(int, int) once Smack is on Android 19 or higher. 277 Integer ourPriority = getPriority(); 278 return ourPriority.compareTo(other.getPriority()); 279 } 280 281 /** 282 * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI. 283 * 284 * @return the common name of the SASL mechanism. 285 */ 286 public abstract String getName(); 287 288 /** 289 * Get the priority of this SASL mechanism. Lower values mean higher priority. 290 * 291 * @return the priority of this SASL mechanism. 292 */ 293 public abstract int getPriority(); 294 295 public abstract void checkIfSuccessfulOrThrow() throws SmackException; 296 297 public SASLMechanism instanceForAuthentication(XMPPConnection connection, ConnectionConfiguration connectionConfiguration) { 298 SASLMechanism saslMechansim = newInstance(); 299 saslMechansim.connection = connection; 300 saslMechansim.connectionConfiguration = connectionConfiguration; 301 return saslMechansim; 302 } 303 304 public boolean authzidSupported() { 305 return false; 306 } 307 308 protected abstract SASLMechanism newInstance(); 309 310 protected static byte[] toBytes(String string) { 311 return StringUtils.toBytes(string); 312 } 313 314 /** 315 * SASLprep the given String. The resulting String is in UTF-8. 316 * 317 * @param string the String to sasl prep. 318 * @return the given String SASL preped 319 * @see <a href="http://tools.ietf.org/html/rfc4013">RFC 4013 - SASLprep: Stringprep Profile for User Names and Passwords</a> 320 */ 321 protected static String saslPrep(String string) { 322 StringTransformer stringTransformer = saslPrepTransformer; 323 if (stringTransformer != null) { 324 return stringTransformer.transform(string); 325 } 326 return string; 327 } 328 329 @Override 330 public final String toString() { 331 return "SASL Mech: " + getName() + ", Prio: " + getPriority(); 332 } 333}