001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.kvem.jsr082.obex;
027:
028: import java.io.IOException;
029: import java.io.UnsupportedEncodingException;
030: import java.util.Vector;
031: import javax.obex.Authenticator;
032: import javax.obex.PasswordAuthentication;
033:
034: /**
035: * Obex protocol authentication functinality.
036: */
037: class ObexAuth {
038:
039: /** Debug information, should be false for RR. */
040: private static final boolean DEBUG = false;
041:
042: private static byte[] column = { (byte) ':' };
043:
044: String realm;
045: boolean userID;
046: boolean access;
047: byte[] nonce;
048: private static int counter = 0;
049:
050: // used in prepareChallenge, addChallenge
051: private byte[] realm_array;
052: private int challengeLength;
053:
054: static ObexAuth createChallenge(String realm, boolean userID,
055: boolean access) {
056: return new ObexAuth(realm, null, userID, access);
057: }
058:
059: private ObexAuth(String realm, byte[] nonce, boolean userID,
060: boolean access) {
061: this .realm = realm;
062: this .nonce = nonce;
063: this .userID = userID;
064: this .access = access;
065: }
066:
067: /**
068: * Prepare challenge before adding to packet.
069: * @return length of challenge
070: */
071: int prepareChallenge() {
072: if (challengeLength != 0) {
073: return challengeLength;
074: }
075: try {
076: int len = 24;
077: realm_array = null;
078: if (realm != null) {
079: realm_array = realm.getBytes("UTF-16BE");
080: len += 3 + realm_array.length;
081: }
082: challengeLength = len;
083: } catch (UnsupportedEncodingException e) {
084: if (DEBUG) {
085: System.out
086: .println("prepareChallenge(): ERROR, no encoding");
087: }
088: return 0;
089: }
090: return challengeLength;
091: }
092:
093: int addChallenge(byte[] packet, int offset) throws IOException {
094: int len = prepareChallenge();
095:
096: packet[offset] = (byte) ObexPacketStream.HEADER_AUTH_CHALLENGE;
097: packet[offset + 1] = (byte) (len >> 8);
098: packet[offset + 2] = (byte) (len & 255);
099: packet[offset + 3] = (byte) 0; // nonce tag (0x0)
100: packet[offset + 4] = (byte) 16; // nonce len (16) (1 bytes)
101: nonce = makeNonce(); // 16 byte of nonce
102: if (DEBUG) {
103: print("addChallenge: nonce", nonce);
104: }
105: System.arraycopy(nonce, 0, packet, offset + 5, 16);
106: packet[offset + 21] = (byte) 1; // options tag (0x1)
107: packet[offset + 22] = (byte) 1; // options length (1)
108: packet[offset + 23] = (byte) ((userID ? 1 : 0) + (access ? 0
109: : 2));
110: if (realm != null) {
111: int realm_len = realm_array.length;
112: packet[offset + 24] = (byte) 2; // realm tag (0x2)
113:
114: // realm length including encoding (1 byte)
115: packet[offset + 25] = (byte) (realm_len + 1);
116: packet[offset + 26] = (byte) 0xFF; // realm encoding UNICODE
117: System.arraycopy(realm_array, 0, packet, offset + 27,
118: realm_len);
119: }
120: return len;
121: }
122:
123: private static byte[] makeNonce() throws IOException {
124: SSLWrapper md5 = new SSLWrapper();
125: byte[] timestamp = createTimestamp();
126: md5.update(timestamp, 0, timestamp.length);
127: md5.update(column, 0, 1);
128: byte[] privateKey = getPrivateKey();
129: byte[] nonce = new byte[16];
130: md5.doFinal(privateKey, 0, privateKey.length, nonce, 0);
131: return nonce;
132: }
133:
134: /**
135: * Creates timestamp.
136: * No strict specification for timestamp generation in OBEX 1.2
137: * @return timestamp value
138: */
139: private static byte[] createTimestamp() {
140: long time = System.currentTimeMillis();
141: byte[] timestamp = new byte[9];
142: timestamp[0] = (byte) (time >> 56);
143: timestamp[1] = (byte) (time >> 48);
144: timestamp[2] = (byte) (time >> 40);
145: timestamp[3] = (byte) (time >> 32);
146: timestamp[4] = (byte) (time >> 24);
147: timestamp[5] = (byte) (time >> 16);
148: timestamp[6] = (byte) (time >> 8);
149: timestamp[7] = (byte) (time);
150:
151: synchronized (ObexAuth.class) {
152: timestamp[8] = (byte) (counter++);
153: }
154: return timestamp;
155: }
156:
157: private static byte[] privateKey = null;
158:
159: /**
160: * Create and return private key.
161: * Weak security scheme. Should be rewritten for more secure
162: * implementation if used outside emulator.
163: */
164: private synchronized static byte[] getPrivateKey()
165: throws IOException {
166: if (privateKey != null) {
167: return privateKey;
168: }
169: SSLWrapper md5 = new SSLWrapper();
170: byte[] keyData = null;
171:
172: try {
173: keyData = "timestamp = ".getBytes("ISO-8859-1");
174: } catch (UnsupportedEncodingException e) {
175: e.printStackTrace();
176: }
177: md5.update(keyData, 0, keyData.length);
178: byte[] timestamp = createTimestamp();
179: privateKey = new byte[16];
180: md5.doFinal(timestamp, 0, timestamp.length, privateKey, 0);
181: return privateKey;
182: }
183:
184: static void makeDigest(byte[] buffer, int offset, byte[] nonce,
185: byte[] password) throws IOException {
186: SSLWrapper md5 = new SSLWrapper();
187: md5.update(nonce, 0, 16);
188: md5.update(column, 0, 1);
189: md5.doFinal(password, 0, password.length, buffer, offset);
190: }
191:
192: static ObexAuth parseAuthChallenge(byte[] buffer, int packetOffset,
193: int length) throws IOException {
194: if (DEBUG) {
195: System.out.println("ObexAuth.parseAuthChallenge()");
196: }
197:
198: // default values
199: boolean readonly = false;
200: boolean needUserid = false;
201: byte[] nonce = null;
202: String realm = null;
203:
204: // skiping header type and length
205: int offset = packetOffset + 3;
206: length += packetOffset;
207:
208: // decoding data in buffer
209: while (offset < length) {
210: int tag = buffer[offset] & 0xFF;
211: int len = buffer[offset + 1] & 0xFF;
212: offset += 2;
213:
214: switch (tag) {
215: case 0x0: // nonce
216: if (len != 16 || nonce != null) {
217: throw new IOException("protocol error");
218: }
219: nonce = new byte[16];
220: System.arraycopy(buffer, offset, nonce, 0, 16);
221: if (DEBUG) {
222: print("got challenge: nonce", nonce);
223: }
224: break;
225: case 0x1: // options
226: if (len != 1) {
227: throw new IOException("protocol error");
228: }
229: int options = buffer[offset];
230: readonly = ((options & 2) != 0);
231: needUserid = ((options & 1) != 0);
232: break;
233: case 0x2: // realm
234: try {
235: int encodingID = buffer[offset] & 0xFF;
236: String encoding = null;
237: if (encodingID == 255)
238: encoding = "UTF-16BE";
239: else if (encodingID == 0)
240: encoding = "US-ASCII";
241: else if (encodingID < 10)
242: encoding = "ISO-8859-" + encoding;
243: else
244: throw new UnsupportedEncodingException();
245:
246: realm = new String(buffer, offset + 1, len - 1,
247: encoding);
248: } catch (UnsupportedEncodingException e) {
249: // already: realm = null;
250: }
251: }
252: offset += len;
253: }
254: if (offset != length) {
255: throw new IOException("protocol error");
256: }
257: return new ObexAuth(realm, nonce, needUserid, !readonly);
258: }
259:
260: int replyAuthChallenge(byte[] buffer, int packetOffset,
261: Authenticator authenticator) throws IOException {
262:
263: if (DEBUG) {
264: System.out.println("ObexAuth.replyAuthChallenge()");
265: }
266:
267: if (realm == null) {
268: realm = "";
269: }
270:
271: byte[] password = null;
272: byte[] username = null;
273:
274: try {
275: PasswordAuthentication pass = authenticator
276: .onAuthenticationChallenge(realm, userID, access);
277:
278: password = pass.getPassword();
279: int uidLen = 0;
280: // userid subheader length with subheader <tag> and <len>
281:
282: username = pass.getUserName();
283: if (userID || username != null) {
284:
285: // username is required but not provided
286: if (userID && username.length == 0) {
287: if (DEBUG) {
288: System.out
289: .println("ObexAuth.replyAuthChallenge():"
290: + " required username not provided");
291: }
292: throw new Exception();
293: }
294: uidLen = 2 + username.length;
295:
296: // maximum supported username length = 20
297: if (uidLen > 22)
298: uidLen = 22;
299: }
300:
301: int len = 39 + uidLen;
302: // byte[] response = new byte[len];
303: buffer[packetOffset + 0] = (byte) ObexPacketStream.HEADER_AUTH_RESPONSE;
304: buffer[packetOffset + 1] = (byte) (len >> 8);
305: buffer[packetOffset + 2] = (byte) (len & 255);
306: buffer[packetOffset + 3] = 0x0; // tag (Request-Digest)
307: buffer[packetOffset + 4] = 16; // digest len (16)
308: makeDigest(buffer, packetOffset + 5, nonce, password);
309: buffer[packetOffset + 21] = 0x02; // tag nonce
310: buffer[packetOffset + 22] = 16; // nonce len (16)
311: System.arraycopy(nonce, 0, buffer, packetOffset + 23, 16);
312: if (DEBUG) {
313: print("send response: nonce", nonce);
314: }
315: if (uidLen > 2) {
316: buffer[packetOffset + 39] = 0x01; // tag userid
317: buffer[packetOffset + 40] = (byte) (uidLen - 2); // userid len
318: System.arraycopy(username, 0, buffer,
319: packetOffset + 41, uidLen - 2);
320: }
321:
322: if (DEBUG) {
323: System.out.println("ObexAuth.replyAuthChallenge():"
324: + " response generated");
325: }
326: return len;
327:
328: // need to create authentication response
329: // we should resend previous packet with the authentication response
330:
331: } catch (Throwable t) {
332: if (DEBUG) {
333: System.out
334: .println("ObexAuth.replyAuthChallenge(): exception");
335: t.printStackTrace();
336: }
337: // will caught NullPointerException if authenticator
338: // was not provided
339: // will caught exceptions in handler
340: // will caught NullPointerException exception
341: // if username == null and needUserid
342: //
343: // wrong response from client application,
344: // ignoring authentication challenge
345: // client will receive UNAUTHORIZED
346: }
347: return 0;
348: }
349:
350: private static void print(String msg, byte[] array) {
351: if (DEBUG) {
352: System.out.println(msg);
353: if (array == null) {
354: System.out.println("[0] = NULL");
355: return;
356: }
357: System.out.print("[" + array.length + "]");
358: for (int i = 0; i < array.length; i++) {
359: System.out.print(" "
360: + Integer.toHexString(array[i] & 0xFF));
361: }
362: System.out.println("");
363: }
364: }
365:
366: private static boolean compare(byte[] src1, byte[] src2) {
367: for (int i = 0; i < 16; i++) {
368: if (src1[i] != src2[i])
369: return false;
370: }
371: return true;
372: }
373:
374: static boolean checkAuthResponse(byte[] buffer, int packetOffset,
375: int length, ObexPacketStream stream, Vector challenges)
376: throws IOException {
377:
378: if (DEBUG) {
379: System.out.println("ObexAuth.parseAuthResponse()");
380: }
381:
382: // skiping header type and length
383: int offset = packetOffset + 3;
384: length += packetOffset;
385:
386: byte[] digest = null;
387: byte[] username = null;
388: byte[] nonce = null;
389:
390: // decoding data in buffer
391: while (offset < length) {
392: int tag = buffer[offset] & 0xFF;
393: int len = buffer[offset + 1] & 0xFF;
394: offset += 2;
395:
396: switch (tag) {
397: case 0x0: // digest
398: if (DEBUG) {
399: System.out.println("got digest");
400: }
401: if (len != 16 || digest != null) {
402: throw new IOException("protocol error (1)");
403: }
404: digest = new byte[16];
405: System.arraycopy(buffer, offset, digest, 0, 16);
406: if (DEBUG) {
407: print("got response: digest", digest);
408: }
409: break;
410: case 0x1: // username
411: if (DEBUG) {
412: System.out.println("got username");
413: }
414: if (len > 20 || len == 0 || username != null) {
415: throw new IOException("protocol error (2)");
416: }
417: username = new byte[len];
418: System.arraycopy(buffer, offset, username, 0, len);
419: break;
420: case 0x2: // nonce
421: if (DEBUG) {
422: System.out.println("got nonce");
423: }
424: if (len != 16 || nonce != null) {
425: throw new IOException("protocol error (3)");
426: }
427: nonce = new byte[16];
428: System.arraycopy(buffer, offset, nonce, 0, 16);
429: if (DEBUG) {
430: print("got response: nonce", nonce);
431: }
432: break;
433: default:
434: if (DEBUG) {
435: System.out.println("unknown tag = " + tag);
436: }
437: }
438: offset += len;
439: }
440:
441: if (offset != length) {
442: throw new IOException("protocol error (4)");
443: }
444:
445: // check nonce and select auth object
446: ObexAuth auth = null;
447:
448: if (nonce == null) {
449: if (DEBUG) {
450: System.out
451: .println("no nonce received, using first auth");
452: }
453: if (challenges.size() == 0) {
454: return false;
455: }
456: auth = (ObexAuth) challenges.elementAt(0);
457: nonce = auth.nonce;
458: challenges.removeElementAt(0);
459: } else {
460: if (DEBUG) {
461: System.out
462: .println("nonce provided, searching for auth");
463: System.out.println("challenges = " + challenges.size());
464: }
465: for (int i = 0; i < challenges.size(); i++) {
466: ObexAuth a = (ObexAuth) challenges.elementAt(i);
467: if (compare(nonce, a.nonce)) {
468: if (DEBUG) {
469: System.out.println("nonce is in " + i
470: + " challenge");
471: }
472: auth = a;
473: challenges.removeElementAt(i);
474: break;
475: }
476: }
477: if (DEBUG) {
478: System.out.println("auth = " + auth);
479: }
480: if (auth == null)
481: return false;
482: }
483:
484: // check username existance
485: if (auth.userID && username == null) {
486: if (DEBUG) {
487: System.out.println("need username!");
488: }
489: // NOTE: may be too strict
490: stream.onAuthenticationFailure(username);
491: return false;
492: }
493:
494: // ask password from authenticator and check digest
495: try {
496: if (DEBUG) {
497: System.out
498: .println("running onAuthenticationResponse()...");
499: }
500: byte[] password = stream.authenticator
501: .onAuthenticationResponse(username);
502: byte[] localDigest = new byte[16];
503: makeDigest(localDigest, 0, nonce, password);
504: if (DEBUG) {
505: System.out.println("digest created");
506: }
507: boolean res = compare(localDigest, digest);
508:
509: if (res == false) {
510: if (DEBUG) {
511: System.out
512: .println("Calling onAuthenticationFailure()..");
513: }
514: stream.onAuthenticationFailure(username);
515: }
516: return res;
517: } catch (Throwable t) {
518: // catch localDigest = null, crypto and user code exception
519: if (DEBUG) {
520: System.out.println("exception");
521: }
522: stream.onAuthenticationFailure(username);
523: return false;
524: }
525: }
526: }
|