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 com.android.obex; 34 35 import android.util.Log; 36 37 import java.io.IOException; 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 */ 49 public class ObexSession { 50 51 private static final String TAG = "ObexSession"; 52 private static final boolean V = ObexHelper.VDBG; 53 54 protected Authenticator mAuthenticator; 55 56 protected byte[] mChallengeDigest; 57 58 /** 59 * Called when the server received an authentication challenge header. This 60 * will cause the authenticator to handle the authentication challenge. 61 * @param header the header with the authentication challenge 62 * @return <code>true</code> if the last request should be resent; 63 * <code>false</code> if the last request should not be resent 64 * @throws IOException 65 */ handleAuthChall(HeaderSet header)66 public boolean handleAuthChall(HeaderSet header) throws IOException { 67 if (mAuthenticator == null) { 68 return false; 69 } 70 71 /* 72 * An authentication challenge is made up of one required and two 73 * optional tag length value triplets. The tag 0x00 is required to be in 74 * the authentication challenge and it represents the challenge digest 75 * that was received. The tag 0x01 is the options tag. This tag tracks 76 * if user ID is required and if full access will be granted. The tag 77 * 0x02 is the realm, which provides a description of which user name 78 * and password to use. 79 */ 80 byte[] challenge = ObexHelper.getTagValue((byte)0x00, header.mAuthChall); 81 byte[] option = ObexHelper.getTagValue((byte)0x01, header.mAuthChall); 82 byte[] description = ObexHelper.getTagValue((byte)0x02, header.mAuthChall); 83 84 String realm = null; 85 if (description != null) { 86 byte[] realmString = new byte[description.length - 1]; 87 System.arraycopy(description, 1, realmString, 0, realmString.length); 88 89 switch (description[0] & 0xFF) { 90 91 case ObexHelper.OBEX_AUTH_REALM_CHARSET_ASCII: 92 // ASCII encoding 93 // Fall through 94 case ObexHelper.OBEX_AUTH_REALM_CHARSET_ISO_8859_1: 95 // ISO-8859-1 encoding 96 try { 97 realm = new String(realmString, "ISO8859_1"); 98 } catch (Exception e) { 99 throw new IOException("Unsupported Encoding Scheme"); 100 } 101 break; 102 103 case ObexHelper.OBEX_AUTH_REALM_CHARSET_UNICODE: 104 // UNICODE Encoding 105 realm = ObexHelper.convertToUnicode(realmString, false); 106 break; 107 108 default: 109 throw new IOException("Unsupported Encoding Scheme"); 110 } 111 } 112 113 boolean isUserIDRequired = false; 114 boolean isFullAccess = true; 115 if (option != null) { 116 if ((option[0] & 0x01) != 0) { 117 isUserIDRequired = true; 118 } 119 120 if ((option[0] & 0x02) != 0) { 121 isFullAccess = false; 122 } 123 } 124 125 PasswordAuthentication result = null; 126 header.mAuthChall = null; 127 128 try { 129 result = mAuthenticator 130 .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); 131 } catch (Exception e) { 132 if (V) Log.d(TAG, "Exception occurred - returning false", e); 133 return false; 134 } 135 136 /* 137 * If no password is provided then we not resent the request 138 */ 139 if (result == null) { 140 return false; 141 } 142 143 byte[] password = result.getPassword(); 144 if (password == null) { 145 return false; 146 } 147 148 byte[] userName = result.getUserName(); 149 150 /* 151 * Create the authentication response header. It includes 1 required and 152 * 2 option tag length value triples. The required triple has a tag of 153 * 0x00 and is the response digest. The first optional tag is 0x01 and 154 * represents the user ID. If no user ID is provided, then no user ID 155 * will be sent. The second optional tag is 0x02 and is the challenge 156 * that was received. This will always be sent 157 */ 158 if (userName != null) { 159 header.mAuthResp = new byte[38 + userName.length]; 160 header.mAuthResp[36] = (byte)0x01; 161 header.mAuthResp[37] = (byte)userName.length; 162 System.arraycopy(userName, 0, header.mAuthResp, 38, userName.length); 163 } else { 164 header.mAuthResp = new byte[36]; 165 } 166 167 // Create the secret String 168 byte[] digest = new byte[challenge.length + password.length + 1]; 169 System.arraycopy(challenge, 0, digest, 0, challenge.length); 170 // Insert colon between challenge and password 171 digest[challenge.length] = (byte)0x3A; 172 System.arraycopy(password, 0, digest, challenge.length + 1, password.length); 173 174 // Add the Response Digest 175 header.mAuthResp[0] = (byte)0x00; 176 header.mAuthResp[1] = (byte)0x10; 177 178 System.arraycopy(ObexHelper.computeMd5Hash(digest), 0, header.mAuthResp, 2, 16); 179 180 // Add the challenge 181 header.mAuthResp[18] = (byte)0x02; 182 header.mAuthResp[19] = (byte)0x10; 183 System.arraycopy(challenge, 0, header.mAuthResp, 20, 16); 184 185 return true; 186 } 187 188 /** 189 * Called when the server received an authentication response header. This 190 * will cause the authenticator to handle the authentication response. 191 * @param authResp the authentication response 192 * @return <code>true</code> if the response passed; <code>false</code> if 193 * the response failed 194 */ handleAuthResp(byte[] authResp)195 public boolean handleAuthResp(byte[] authResp) { 196 if (mAuthenticator == null) { 197 return false; 198 } 199 // get the correct password from the application 200 byte[] correctPassword = mAuthenticator.onAuthenticationResponse(ObexHelper.getTagValue( 201 (byte)0x01, authResp)); 202 if (correctPassword == null) { 203 return false; 204 } 205 206 byte[] temp = new byte[correctPassword.length + 16]; 207 208 System.arraycopy(mChallengeDigest, 0, temp, 0, 16); 209 System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); 210 211 byte[] correctResponse = ObexHelper.computeMd5Hash(temp); 212 byte[] actualResponse = ObexHelper.getTagValue((byte)0x00, authResp); 213 214 // compare the MD5 hash array . 215 for (int i = 0; i < 16; i++) { 216 if (correctResponse[i] != actualResponse[i]) { 217 return false; 218 } 219 } 220 221 return true; 222 } 223 } 224