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