001: /*
002: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
003: * Reserved. Use is subject to license terms.
004: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License version
008: * 2 only, as published by the Free Software Foundation.
009: *
010: * This program is distributed in the hope that it will be useful, but
011: * WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * General Public License version 2 for more details (a copy is
014: * included at /legal/license.txt).
015: *
016: * You should have received a copy of the GNU General Public License
017: * version 2 along with this work; if not, write to the Free Software
018: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
022: * Clara, CA 95054 or visit www.sun.com if you need additional
023: * information or have any questions.
024: */
025: /*
026: * DigestClientAuthentication.java
027: *
028: * Created on January 7, 2003, 10:45 AM
029: */
030:
031: package gov.nist.siplite.stack.authentication;
032:
033: import java.util.Enumeration;
034: import java.util.Vector;
035:
036: import gov.nist.core.*;
037: import gov.nist.siplite.*;
038: import gov.nist.siplite.parser.Lexer;
039: import gov.nist.siplite.message.*;
040: import gov.nist.siplite.header.*;
041:
042: import com.sun.midp.log.Logging;
043: import com.sun.midp.log.LogChannels;
044:
045: /**
046: * Digest Client Authentication.
047: */
048: public class DigestClientAuthentication implements
049: AuthenticationListener {
050: /** MD5 value. */
051: private static final String MD5 = "MD5";
052: /** MD5-sess value. */
053: private static final String MD5_SESS = "MD5-sess";
054: /** Authorization category. */
055: private String realm;
056: /** Algorithm name. */
057: private String algorithm;
058: /** URI to be validated. */
059: private String uri;
060: /** Nonce. */
061: private String nonce;
062: /** Authorization method. */
063: private String method;
064: /** Client nonce. */
065: private String cnonce;
066: /** Qop. */
067: private String qop;
068: /** Cnonce counter value. */
069: private String nonceCountPar;
070: /** Credentials containing the keys. */
071: private Vector credentials;
072:
073: /**
074: * Constructor with initial credentials.
075: * @param credentials array of credentials
076: */
077: public DigestClientAuthentication(Vector credentials) {
078: this .credentials = credentials;
079: /*
080: * need revisit
081: try {
082: rs = RecordStore.openRecordStore("pass", true);
083: }
084: catch (Exception e) {
085: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
086: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
087: "DigestClientAuthentication, " +
088: "exception raised: " + e.getMessage());
089: }
090: }
091: */
092: }
093:
094: /**
095: * Creates a new ProxyAuthorizationHeader based on the newly supplied
096: * scheme value.
097: *
098: * @param scheme - the new string value of the scheme.
099: * @throws ParseException which signals that an error has been reached
100: * unexpectedly while parsing the scheme value.
101: * @return the newly created ProxyAuthorizationHeader object.
102: */
103: public ProxyAuthorizationHeader createProxyAuthorizationHeader(
104: String scheme) throws ParseException {
105: if (scheme == null) {
106: throw new NullPointerException("bad scheme arg");
107: }
108:
109: ProxyAuthorizationHeader p = new ProxyAuthorizationHeader();
110: p.setScheme(scheme);
111:
112: return p;
113: }
114:
115: /**
116: * Creates a new AuthorizationHeader based on the newly supplied
117: * scheme value.
118: *
119: * @param scheme - the new string value of the scheme.
120: * @throws ParseException which signals that an error has been reached
121: * unexpectedly while parsing the scheme value.
122: * @return the newly created AuthorizationHeader object.
123: */
124: public AuthorizationHeader createAuthorizationHeader(String scheme)
125: throws ParseException {
126: if (scheme == null) {
127: throw new NullPointerException("null arg scheme ");
128: }
129:
130: AuthorizationHeader auth = new AuthorizationHeader();
131: auth.setScheme(scheme);
132:
133: return auth;
134: }
135:
136: /**
137: * Hexadecimal conversion table.
138: */
139: private static final char[] toHex = { '0', '1', '2', '3', '4', '5',
140: '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
141:
142: /**
143: * Converts an array of bytes to an hexadecimal string.
144: * @return a string
145: * @param b bytes array to convert to a hexadecimal
146: * string
147: */
148: public static String toHexString(byte b[]) {
149: int pos = 0;
150: char[] c = new char[b.length * 2];
151: for (int i = 0; i < b.length; i++) {
152: c[pos++] = toHex[(b[i] >> 4) & 0x0F];
153: c[pos++] = toHex[b[i] & 0x0f];
154: }
155: return new String(c);
156: }
157:
158: /**
159: * Creates a new request.
160: * @param sipStack the curent SIP stack context
161: * @param originalRequest initiating request
162: * @param response reply to original request
163: * @param count number of request for nonce-count
164: * (please see RFC 2617, 3.2.2)
165: * @return the new request object with authentication
166: * headers
167: */
168: public Request createNewRequest(SipStack sipStack,
169: Request originalRequest, Response response, int count) {
170: Exception ex = null;
171: try {
172: Request newRequest = (Request) originalRequest.clone();
173: CSeqHeader cseqHeader = newRequest.getCSeqHeader();
174: cseqHeader
175: .setSequenceNumber(cseqHeader.getSequenceNumber() + 1);
176:
177: // Proxy-Authenticate header:
178: ProxyAuthenticateHeader proxyAuthHeader = (ProxyAuthenticateHeader) response
179: .getHeader(ProxyAuthenticateHeader.NAME);
180:
181: // WWWAuthenticate header:
182: WWWAuthenticateHeader wwwAuthenticateHeader = (WWWAuthenticateHeader) response
183: .getHeader(WWWAuthenticateHeader.NAME);
184:
185: // Cseq header:
186: cseqHeader = response.getCSeqHeader();
187: method = cseqHeader.getMethod();
188:
189: // RFC 2617, 3.2.2:
190: // digest-uri
191: // The URI from Request-URI of the Request-Line
192: uri = originalRequest.getRequestURI().toString();
193:
194: String opaque = null;
195:
196: if (proxyAuthHeader == null) {
197: if (wwwAuthenticateHeader == null) {
198: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
199: Logging
200: .report(
201: Logging.ERROR,
202: LogChannels.LC_JSR180,
203: "DigestClientAuthentication, "
204: + "ERROR: No ProxyAuthenticate header "
205: + "or WWWAuthenticateHeader "
206: + "in the response!");
207: }
208: return null;
209: }
210:
211: algorithm = wwwAuthenticateHeader.getAlgorithm();
212:
213: nonce = wwwAuthenticateHeader.getNonce();
214: realm = wwwAuthenticateHeader.getRealm();
215:
216: if (realm == null) {
217: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
218: Logging
219: .report(
220: Logging.ERROR,
221: LogChannels.LC_JSR180,
222: "DigestClientAuthentication, "
223: + "ERROR: the realm is not part "
224: + "of the 401 response!");
225: }
226: return null;
227: }
228:
229: qop = wwwAuthenticateHeader.getParameter("qop");
230: opaque = wwwAuthenticateHeader.getParameter("opaque");
231: } else {
232:
233: algorithm = proxyAuthHeader.getAlgorithm();
234: nonce = proxyAuthHeader.getNonce();
235: realm = proxyAuthHeader.getRealm();
236:
237: if (realm == null) {
238: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
239: Logging
240: .report(
241: Logging.ERROR,
242: LogChannels.LC_JSR180,
243: "DigestClientAuthentication, "
244: + "ERROR: the realm is not part "
245: + "of the 407 response!");
246: }
247: return null;
248: }
249:
250: qop = proxyAuthHeader.getParameter("qop");
251: opaque = proxyAuthHeader.getParameter("opaque");
252: }
253:
254: if (algorithm == null) {
255: algorithm = MD5; // default value
256: }
257:
258: if (!algorithm.equalsIgnoreCase(MD5)
259: && !algorithm.equalsIgnoreCase(MD5_SESS)) {
260: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
261: Logging.report(Logging.ERROR,
262: LogChannels.LC_JSR180,
263: "Algorithm parameter is wrong: "
264: + algorithm);
265: }
266: return null;
267: }
268:
269: Credentials credentials = getCredentials(realm);
270: if (credentials == null) {
271: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
272: Logging.report(Logging.ERROR,
273: LogChannels.LC_JSR180,
274: "DigestClientAuthentication, "
275: + "ERROR: unable to retrieve "
276: + "the credentials from RMS!");
277: }
278: return null;
279: }
280:
281: if (nonce == null) {
282: nonce = "";
283: }
284:
285: String digestEntityBody = null;
286: if (qop != null) {
287: cnonce = toHexString(Utils
288: .digest(("" + System.currentTimeMillis()
289: + ":ETag:" + credentials.getPassword())
290: .getBytes()));
291: if (qop.equalsIgnoreCase("auth-int")) {
292: String entityBody = new String(originalRequest
293: .getRawContent());
294: if (entityBody == null) {
295: entityBody = "";
296: }
297: digestEntityBody = toHexString(Utils
298: .digest(entityBody.getBytes()));
299: }
300: }
301:
302: AuthenticationHeader header = null;
303:
304: if (proxyAuthHeader == null) {
305: header = createAuthorizationHeader("Digest");
306: } else {
307: header = createProxyAuthorizationHeader("Digest");
308: }
309:
310: header.setParameter("username", credentials.getUserName());
311: header.setParameter("realm", realm);
312: header.setParameter("uri", uri);
313: header.setParameter("algorithm", algorithm);
314: header.setParameter("nonce", nonce);
315:
316: if (qop != null) {
317: // RFC 2617, 3.2.2
318: // qop contains a comma-separated list
319: // we should find "auth" or "auth-int"
320: Lexer qopLexer = new Lexer("qop", qop);
321: boolean foundAuth = false;
322: String currToken;
323: while (qopLexer.lookAhead(0) != '\0') {
324: currToken = qopLexer.byteStringNoComma()
325: .toLowerCase();
326: if (currToken.equals("auth")
327: || currToken.equals("auth-int")) {
328: foundAuth = true;
329: qop = currToken;
330: break;
331: }
332: }
333: if (!foundAuth) { // wrong qop value
334: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
335: Logging
336: .report(
337: Logging.WARNING,
338: LogChannels.LC_JSR180,
339: "DigestClientAuthentication, "
340: + "the digest response is null "
341: + "for the Authorization header!");
342: }
343: return null;
344: }
345: header.setParameter("qop", qop);
346: // RFC 2617, 3.2.2
347: header.setParameter("cnonce", cnonce);
348: // constructing 8LHEX
349: String nonceCount = Integer.toHexString(count);
350: nonceCountPar = "";
351: int lengthNonceCount = nonceCount.length();
352: if (lengthNonceCount < 8) {
353: for (int i = lengthNonceCount; i < 8; i++) {
354: nonceCountPar += "0";
355: }
356: }
357: nonceCountPar += nonceCount;
358: header.setParameter("nc", nonceCountPar);
359: }
360:
361: String digestResponse = generateResponse(credentials
362: .getUserName(), credentials.getPassword(),
363: digestEntityBody);
364:
365: if (digestResponse == null) {
366: if (Logging.REPORT_LEVEL <= Logging.WARNING) {
367: Logging.report(Logging.WARNING,
368: LogChannels.LC_JSR180,
369: "DigestClientAuthentication, "
370: + "the digest response is null "
371: + "for the Authorization header!");
372: }
373: return null;
374: }
375:
376: header.setParameter("response", digestResponse);
377:
378: if (opaque != null) {
379: header.setParameter("opaque", opaque);
380: }
381:
382: newRequest.setHeader(header);
383: return newRequest;
384: } catch (ParseException pe) {
385: ex = pe;
386: } catch (javax.microedition.sip.SipException se) {
387: ex = se;
388: }
389: if (ex != null) {
390: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
391: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
392: "DigestClientAuthentication, "
393: + "createNewRequest() "
394: + "exception raised: "
395: + ex.getMessage());
396: }
397: }
398: return null;
399: }
400:
401: /**
402: * Generates the response message.
403: * @param userName user name for authentication
404: * @param password password for authentication
405: * @param digestEntityBody MD5 value of body
406: * @return the new response message
407: */
408: private String generateResponse(String userName, String password,
409: String digestEntityBody) {
410: if (userName == null) {
411: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
412: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
413: "DigestClientAuthentication, "
414: + "generateResponse(): "
415: + "ERROR: no userName parameter");
416: }
417: return null;
418: }
419:
420: if (realm == null) {
421: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
422: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
423: "DigestClientAuthentication, "
424: + "generateResponse(): "
425: + "ERROR: no realm parameter");
426: }
427: return null;
428: }
429:
430: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
431: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
432: "DigestClientAuthentication, "
433: + "generateResponse(): "
434: + "Trying to generate a response "
435: + "for the user: " + userName + " , with "
436: + "the realm: " + realm);
437: }
438:
439: if (password == null) {
440: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
441: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
442: "DigestClientAuthentication, "
443: + "generateResponse(): "
444: + "ERROR: no password parameter");
445: }
446: return null;
447: }
448:
449: if (method == null) {
450: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
451: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
452: "DigestClientAuthentication, "
453: + "generateResponse(): "
454: + "ERROR: no method parameter");
455: }
456: return null;
457: }
458:
459: if (uri == null) {
460: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
461: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
462: "DigestClientAuthentication, "
463: + "generateResponse(): "
464: + "ERROR: no uri parameter");
465: }
466: return null;
467: }
468:
469: if (nonce == null) {
470: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
471: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
472: "DigestClientAuthentication, "
473: + "generateResponse(): "
474: + "ERROR: no nonce parameter");
475: }
476: return null;
477: }
478:
479: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
480: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
481: "DigestClientAuthentication, "
482: + "generateResponse(), userName: "
483: + userName + "!");
484: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
485: "DigestClientAuthentication, "
486: + "generateResponse(), realm: " + realm
487: + "!");
488: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
489: "DigestClientAuthentication, "
490: + "generateResponse(), password: "
491: + password + "!");
492: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
493: "DigestClientAuthentication, "
494: + "generateResponse(), uri: " + uri + "!");
495: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
496: "DigestClientAuthentication, "
497: + "generateResponse(), nonce: " + nonce
498: + "!");
499: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
500: "DigestClientAuthentication, "
501: + "generateResponse(), method: " + method
502: + "!");
503: }
504:
505: // RFC 2617, 3.2.2.2
506: String A1 = userName + ":" + realm + ":" + password;
507: if (algorithm.equalsIgnoreCase(MD5_SESS)) {
508: byte[] A1bytes = Utils.digest(A1.getBytes());
509: byte[] tmp = (":" + nonce + ":" + cnonce).getBytes();
510: byte[] join = new byte[A1bytes.length + tmp.length];
511: System.arraycopy(A1bytes, 0, join, 0, A1bytes.length);
512: System.arraycopy(tmp, 0, join, A1bytes.length, tmp.length);
513: A1 = new String(join);
514: }
515: String A2 = method.toUpperCase() + ":" + uri;
516:
517: // RFC 2617, 3.2.2.3 - body is empty
518: if (qop != null) {
519: if (qop.equalsIgnoreCase("auth-int")) {
520: A2 += ":" + digestEntityBody;
521: }
522: }
523:
524: byte mdbytes[] = Utils.digest(A1.getBytes());
525:
526: String HA1 = toHexString(mdbytes);
527:
528: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
529: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
530: "DigestClientAuthentication, "
531: + "generateResponse(), HA1:" + HA1 + "!");
532: }
533:
534: mdbytes = Utils.digest(A2.getBytes());
535:
536: String HA2 = toHexString(mdbytes);
537:
538: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
539: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
540: "DigestClientAuthentication, "
541: + "generateResponse(), HA2: " + HA2 + "!");
542: }
543:
544: String KD = HA1 + ":" + nonce;
545: if (qop != null) { // RFC 2617, 3.2.2.1
546: KD += ":" + nonceCountPar + ":" + cnonce + ":" + qop;
547: }
548: KD += ":" + HA2;
549:
550: mdbytes = Utils.digest(KD.getBytes());
551: String response = toHexString(mdbytes);
552:
553: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
554: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
555: "DigestClientAuthentication, "
556: + "generateResponse(): "
557: + "response generated: " + response);
558: }
559:
560: return response;
561: }
562:
563: /**
564: * Gets the credentials to use int the authentication request.
565: * @param realm the domain of the requested credentials
566: * @return the requested credentials
567: */
568: public Credentials getCredentials(String realm) {
569: Enumeration e = credentials.elements();
570:
571: while (e.hasMoreElements()) {
572: Credentials credentials = (Credentials) e.nextElement();
573: if (credentials.getRealm().equals(realm)) {
574: return credentials;
575: }
576: }
577:
578: return null;
579:
580: /*
581: * need revisit
582: try {
583: byte[] recData = new byte[200];
584: int len;
585:
586: for (int i = 1; i <= rs.getNumRecords(); i++) {
587: len = rs.getRecord(i, recData, 0);
588: String data = new String(recData, 0, len);
589:
590: if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
591: Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
592: "DigestClientAuthentication, " +
593: "data recovered from RMS: " + data);
594: }
595:
596: int realmIndex = data.indexOf(":");
597: String rmsRealm = data.substring(0, realmIndex);
598: int userNameIndex = data.indexOf(":", realmIndex+1);
599: String userName = data.substring(realmIndex+1, userNameIndex);
600:
601: if (realm.equals(rmsRealm.trim())) {
602: Credentials credentials =
603: new Credentials(realm, userName,
604: data.substring(userNameIndex + 1));
605:
606: return credentials;
607: }
608: }
609: return null;
610: }
611: catch (Exception e) {
612: if (Logging.REPORT_LEVEL <= Logging.ERROR) {
613: Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
614: "DigestClientAuthentication, getCredentials() " +
615: "exception raised:", e.getMessage());
616: }
617: return null;
618: }
619: */
620: }
621: }
|