package org.zoolu.sip.authentication; import org.zoolu.sip.header.AuthenticationHeader; import org.zoolu.sip.header.AuthorizationHeader; import org.zoolu.sip.header.ProxyAuthorizationHeader; import org.zoolu.sip.header.WwwAuthenticateHeader; import org.zoolu.tools.MD5; /** The HTTP Digest Authentication as defined in RFC2617. * It can be used to i) calculate an authentication response * from an authentication request, or ii) validate an authentication response. * <br/> in the former case the DigestAuthentication is created based on * a WwwAuthenticationHeader (or ProxyAuthenticationHeader), * while in the latter case it is created based on an AuthorizationHeader * (or ProxyAuthorizationHeader). */ public class DigestAuthentication { protected String method; protected String username; protected String passwd; protected String realm; protected String nonce; // e.g. base 64 encoding of time-stamp H(time-stamp ":" ETag ":" private-key) //protected String[] domain; protected String opaque; //protected boolean stale; // "true" | "false" protected String algorithm; // "MD5" | "MD5-sess" | token protected String qop; // "auth" | "auth-int" | token protected String uri; protected String cnonce; protected String nc; protected String response; protected String body; /** Costructs a new DigestAuthentication. */ protected DigestAuthentication() { } /** Costructs a new DigestAuthentication. */ public DigestAuthentication(String method, AuthorizationHeader ah, String body, String passwd) { init(method,ah,body,passwd); } /** Costructs a new DigestAuthentication. */ public DigestAuthentication(String method, String uri, WwwAuthenticateHeader ah, String qop, String body, String username, String passwd) { init(method,ah,body,passwd); this.uri=uri; this.qop=qop; this.username=username; } /** Costructs a new DigestAuthentication. */ private void init(String method, AuthenticationHeader ah, String body, String passwd) { this.method=method; this.username=ah.getUsernameParam(); this.passwd=passwd; this.realm=ah.getRealmParam(); this.opaque=ah.getOpaqueParam(); this.nonce=ah.getNonceParam(); this.algorithm=ah.getAlgorithParam(); this.qop=ah.getQopParam(); this.uri=ah.getUriParam(); this.cnonce=ah.getCnonceParam(); this.nc=ah.getNcParam(); this.response=ah.getResponseParam(); this.body=body; } /** Gets a String representation of the object. */ public String toString() { StringBuffer sb=new StringBuffer(); sb.append("method=").append(method).append("\n"); sb.append("username=").append(username).append("\n"); sb.append("passwd=").append(passwd).append("\n"); sb.append("realm=").append(realm).append("\n"); sb.append("nonce=").append(nonce).append("\n"); sb.append("opaque=").append(opaque).append("\n"); sb.append("algorithm=").append(algorithm).append("\n"); sb.append("qop=").append(qop).append("\n"); sb.append("uri=").append(uri).append("\n"); sb.append("cnonce=").append(cnonce).append("\n"); sb.append("nc=").append(nc).append("\n"); sb.append("response=").append(response).append("\n"); sb.append("body=").append(body).append("\n"); return sb.toString(); } /** Whether the digest-response in the 'response' parameter in correct. */ public boolean checkResponse() { if (response==null) return false; else return response.equals(getResponse()); } /** Gets a new AuthorizationHeader based on current authentication attributes. */ public AuthorizationHeader getAuthorizationHeader() { AuthorizationHeader ah=new AuthorizationHeader("Digest"); ah.addUsernameParam(username); ah.addRealmParam(realm); ah.addNonceParam(nonce); ah.addUriParam(uri); if (algorithm!=null) ah.addAlgorithParam(algorithm); if (opaque!=null) ah.addOpaqueParam(opaque); /* if (qop!=null) ah.addQopParam(qop); if (nc!=null) ah.addNcParam(nc); */ if (qop!=null) { ah.addQopParam(qop); if (qop.equalsIgnoreCase("auth-int") || qop.equalsIgnoreCase("auth")) { // qop requires cnonce and nc as per rfc2617 cnonce=HEX(MD5(Long.toString(System.currentTimeMillis()))); // unique hopefully ah.addCnonceParam(cnonce); nc="00000001"; // always 1 since cnonce should be unique - avoids having to have a static counter ah.addNcParam(nc); } } String response=getResponse(); ah.addResponseParam(response); return ah; } /** Gets a new ProxyAuthorizationHeader based on current authentication attributes. */ public ProxyAuthorizationHeader getProxyAuthorizationHeader() { return new ProxyAuthorizationHeader(getAuthorizationHeader().getValue()); } /** Calculates the digest-response. * <p> If the "qop" value is "auth" or "auth-int": * <br> KD ( H(A1), unq(nonce) ":" nc ":" unq(cnonce) ":" unq(qop) ":" H(A2) ) * * <p> If the "qop" directive is not present: * <br> KD ( H(A1), unq(nonce) ":" H(A2) ) */ public String getResponse() { String secret=HEX(MD5(A1())); StringBuffer sb=new StringBuffer(); if (nonce!=null) sb.append(nonce); sb.append(":"); if (qop!=null) { if (nc!=null) sb.append(nc); sb.append(":"); if (cnonce!=null) sb.append(cnonce); sb.append(":"); sb.append(qop); sb.append(":"); } sb.append(HEX(MD5(A2()))); String data=sb.toString(); return HEX(KD(secret,data)); } /** Calculates KD() value. * <p> KD(secret, data) = H(concat(secret, ":", data)) */ private byte[] KD(String secret, String data) { StringBuffer sb=new StringBuffer(); sb.append(secret).append(":").append(data); return MD5(sb.toString()); } /** Calculates A1 value. * <p> If the "algorithm" directive's value is "MD5" or is unspecified: * <br> A1 = unq(username) ":" unq(realm) ":" passwd * * <p> If the "algorithm" directive's value is "MD5-sess": * <br> A1 = H( unq(username) ":" unq(realm) ":" passwd ) ":" unq(nonce) ":" unq(cnonce) */ private byte[] A1() { StringBuffer sb=new StringBuffer(); if (username!=null) sb.append(username); sb.append(":"); if (realm!=null) sb.append(realm); sb.append(":"); if (passwd!=null) sb.append(passwd); if (algorithm==null || !algorithm.equalsIgnoreCase("MD5-sess")) { return sb.toString().getBytes(); } else { StringBuffer sb2=new StringBuffer(); sb2.append(":"); if (nonce!=null) sb2.append(nonce); sb2.append(":"); if (cnonce!=null) sb2.append(cnonce); return cat(MD5(sb.toString()),sb2.toString().getBytes()); } } /** Calculates A2 value. * <p> If the "qop" directive's value is "auth" or is unspecified: * <br> A2 = Method ":" digest-uri * * <p> If the "qop" value is "auth-int": * <br> A2 = Method ":" digest-uri ":" H(entity-body) */ private String A2() { StringBuffer sb=new StringBuffer(); sb.append(method); sb.append(":"); if (uri!=null) sb.append(uri); if (qop!=null && qop.equalsIgnoreCase("auth-int")) { sb.append(":"); if (body==null) sb.append(HEX(MD5(""))); else sb.append(HEX(MD5(body))); } return sb.toString(); } /** Concatenates two arrays of bytes. */ private static byte[] cat(byte[] a, byte[] b) { int len=a.length+b.length; byte[ ] c=new byte[len]; for (int i=0; i<a.length; i++) c[i]=a[i]; for (int i=0; i<b.length; i++) c[i+a.length]=b[i]; return c; } /** Calculates the MD5 of a String. */ private static byte[] MD5(String str) { return MD5.digest(str); } /** Calculates the MD5 of an array of bytes. */ private static byte[] MD5(byte[] bb) { return MD5.digest(bb); } /** Calculates the HEX of an array of bytes. */ private static String HEX(byte[] bb) { return MD5.asHex(bb); } /** Main method. * It tests DigestAuthentication with the example provided in the RFC2617. */ public static void main(String[] args) { /* Authorization: Digest username="Mufasa", realm="testrealm@host.com", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", qop=auth, nc=00000001, cnonce="0a4f113b", response="6629fae49393a05397450978507c4ef1", opaque="5ccc069c403ebaf9f0171e9517f40e41" */ DigestAuthentication a=new DigestAuthentication(); a.method="GET"; a.passwd="Circle Of Life"; a.realm="testrealm@host.com"; a.nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"; a.uri="/dir/index.html"; a.qop="auth"; a.nc="00000001"; a.cnonce="0a4f113b"; a.username="Mufasa"; String response1=a.getResponse(); String response2="6629fae49393a05397450978507c4ef1"; System.out.println(response1); System.out.println(response2); System.out.println(" "); String ah_str="Digest username=\"Mufasa\", realm=\"testrealm@host.com\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", uri=\"/dir/index.html\", qop=auth, nc=00000001, cnonce=\"0a4f113b\", response=\"6629fae49393a05397450978507c4ef1\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"\n"; AuthorizationHeader ah=new AuthorizationHeader(ah_str); a=new DigestAuthentication("GET",ah,null,"Circle Of Life"); response1=a.getResponse(); response2="6629fae49393a05397450978507c4ef1"; System.out.println(response1); System.out.println(response2); System.out.println(a.checkResponse()); } }