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