1 /* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package javax.obex; 34 35 import java.io.IOException; 36 37 import android.util.Log; 38 39 /** 40 * The <code>ObexSession</code> interface characterizes the term 41 * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which 42 * could be the server-side view of an OBEX connection, or the client-side view 43 * of the same connection, which is established by server's accepting of a 44 * client issued "CONNECT". 45 * <P> 46 * This interface serves as the common super class for 47 * <CODE>ClientSession</CODE> and <CODE>ServerSession</CODE>. 48 * @hide 49 */ 50 public class ObexSession { 51 52 private static final String TAG = "ObexSession"; 53 private static final boolean V = ObexHelper.VDBG; 54 55 protected Authenticator mAuthenticator; 56 57 protected byte[] mChallengeDigest; 58 59 /** 60 * Called when the server received an authentication challenge header. This 61 * will cause the authenticator to handle the authentication challenge. 62 * @param header the header with the authentication challenge 63 * @return <code>true</code> if the last request should be resent; 64 * <code>false</code> if the last request should not be resent 65 * @throws IOException 66 */ 67 public boolean handleAuthChall(HeaderSet header) throws IOException { 68 if (mAuthenticator == null) { 69 return false; 70 } 71 72 /* 73 * An authentication challenge is made up of one required and two 74 * optional tag length value triplets. The tag 0x00 is required to be in 75 * the authentication challenge and it represents the challenge digest 76 * that was received. The tag 0x01 is the options tag. This tag tracks 77 * if user ID is required and if full access will be granted. The tag 78 * 0x02 is the realm, which provides a description of which user name 79 * and password to use. 80 */ 81 byte[] challenge = ObexHelper.getTagValue((byte)0x00, header.mAuthChall); 82 byte[] option = ObexHelper.getTagValue((byte)0x01, header.mAuthChall); 83 byte[] description = ObexHelper.getTagValue((byte)0x02, header.mAuthChall); 84 85 String realm = null; 86 if (description != null) { 87 byte[] realmString = new byte[description.length - 1]; 88 System.arraycopy(description, 1, realmString, 0, realmString.length); 89 90 switch (description[0] & 0xFF) { 91 92 case ObexHelper.OBEX_AUTH_REALM_CHARSET_ASCII: 93 // ASCII encoding 94 // Fall through 95 case ObexHelper.OBEX_AUTH_REALM_CHARSET_ISO_8859_1: 96 // ISO-8859-1 encoding 97 try { 98 realm = new String(realmString, "ISO8859_1"); 99 } catch (Exception e) { 100 throw new IOException("Unsupported Encoding Scheme"); 101 } 102 break; 103 104 case ObexHelper.OBEX_AUTH_REALM_CHARSET_UNICODE: 105 // UNICODE Encoding 106 realm = ObexHelper.convertToUnicode(realmString, false); 107 break; 108 109 default: 110 throw new IOException("Unsupported Encoding Scheme"); 111 } 112 } 113 114 boolean isUserIDRequired = false; 115 boolean isFullAccess = true; 116 if (option != null) { 117 if ((option[0] & 0x01) != 0) { 118 isUserIDRequired = true; 119 } 120 121 if ((option[0] & 0x02) != 0) { 122 isFullAccess = false; 123 } 124 } 125 126 PasswordAuthentication result = null; 127 header.mAuthChall = null; 128 129 try { 130 result = mAuthenticator 131 .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); 132 } catch (Exception e) { 133 if (V) Log.d(TAG, "Exception occured - returning false", e); 134 return false; 135 } 136 137 /* 138 * If no password is provided then we not resent the request 139 */ 140 if (result == null) { 141 return false; 142 } 143 144 byte[] password = result.getPassword(); 145 if (password == null) { 146 return false; 147 } 148 149 byte[] userName = result.getUserName(); 150 151 /* 152 * Create the authentication response header. It includes 1 required and 153 * 2 option tag length value triples. The required triple has a tag of 154 * 0x00 and is the response digest. The first optional tag is 0x01 and 155 * represents the user ID. If no user ID is provided, then no user ID 156 * will be sent. The second optional tag is 0x02 and is the challenge 157 * that was received. This will always be sent 158 */ 159 if (userName != null) { 160 header.mAuthResp = new byte[38 + userName.length]; 161 header.mAuthResp[36] = (byte)0x01; 162 header.mAuthResp[37] = (byte)userName.length; 163 System.arraycopy(userName, 0, header.mAuthResp, 38, userName.length); 164 } else { 165 header.mAuthResp = new byte[36]; 166 } 167 168 // Create the secret String 169 byte[] digest = new byte[challenge.length + password.length + 1]; 170 System.arraycopy(challenge, 0, digest, 0, challenge.length); 171 // Insert colon between challenge and password 172 digest[challenge.length] = (byte)0x3A; 173 System.arraycopy(password, 0, digest, challenge.length + 1, password.length); 174 175 // Add the Response Digest 176 header.mAuthResp[0] = (byte)0x00; 177 header.mAuthResp[1] = (byte)0x10; 178 179 System.arraycopy(ObexHelper.computeMd5Hash(digest), 0, header.mAuthResp, 2, 16); 180 181 // Add the challenge 182 header.mAuthResp[18] = (byte)0x02; 183 header.mAuthResp[19] = (byte)0x10; 184 System.arraycopy(challenge, 0, header.mAuthResp, 20, 16); 185 186 return true; 187 } 188 189 /** 190 * Called when the server received an authentication response header. This 191 * will cause the authenticator to handle the authentication response. 192 * @param authResp the authentication response 193 * @return <code>true</code> if the response passed; <code>false</code> if 194 * the response failed 195 */ 196 public boolean handleAuthResp(byte[] authResp) { 197 if (mAuthenticator == null) { 198 return false; 199 } 200 // get the correct password from the application 201 byte[] correctPassword = mAuthenticator.onAuthenticationResponse(ObexHelper.getTagValue( 202 (byte)0x01, authResp)); 203 if (correctPassword == null) { 204 return false; 205 } 206 207 byte[] temp = new byte[correctPassword.length + 16]; 208 209 System.arraycopy(mChallengeDigest, 0, temp, 0, 16); 210 System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); 211 212 byte[] correctResponse = ObexHelper.computeMd5Hash(temp); 213 byte[] actualResponse = ObexHelper.getTagValue((byte)0x00, authResp); 214 215 // compare the MD5 hash array . 216 for (int i = 0; i < 16; i++) { 217 if (correctResponse[i] != actualResponse[i]) { 218 return false; 219 } 220 } 221 222 return true; 223 } 224 } 225