1 16 package org.mortbay.http; 17 18 import java.io.IOException ; 19 import java.security.MessageDigest ; 20 import java.security.Principal ; 21 22 import org.apache.commons.logging.Log; 23 import org.mortbay.log.LogFactory; 24 import org.mortbay.util.B64Code; 25 import org.mortbay.util.Credential; 26 import org.mortbay.util.LogSupport; 27 import org.mortbay.util.QuotedStringTokenizer; 28 import org.mortbay.util.StringUtil; 29 import org.mortbay.util.TypeUtil; 30 31 32 37 public class DigestAuthenticator implements Authenticator 38 { 39 static Log log = LogFactory.getLog(DigestAuthenticator.class); 40 41 protected long maxNonceAge=0; 42 protected long nonceSecret=this.hashCode() ^ System.currentTimeMillis(); 43 protected boolean useStale=false; 44 45 46 47 53 public Principal authenticate(UserRealm realm, 54 String pathInContext, 55 HttpRequest request, 56 HttpResponse response) 57 throws IOException 58 { 59 boolean stale=false; 61 Principal user=null; 62 String credentials = request.getField(HttpFields.__Authorization); 63 64 if (credentials!=null ) 65 { 66 if(log.isDebugEnabled())log.debug("Credentials: "+credentials); 67 QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, 68 "=, ", 69 true, 70 false); 71 Digest digest=new Digest (request.getMethod()); 72 String last=null; 73 String name=null; 74 75 loop: 76 while (tokenizer.hasMoreTokens()) 77 { 78 String tok = tokenizer.nextToken(); 79 char c=(tok.length()==1)?tok.charAt(0):'\0'; 80 81 switch (c) 82 { 83 case '=': 84 name=last; 85 last=tok; 86 break; 87 case ',': 88 name=null; 89 case ' ': 90 break; 91 92 default: 93 last=tok; 94 if (name!=null) 95 { 96 if ("username".equalsIgnoreCase(name)) 97 digest.username=tok; 98 else if ("realm".equalsIgnoreCase(name)) 99 digest.realm=tok; 100 else if ("nonce".equalsIgnoreCase(name)) 101 digest.nonce=tok; 102 else if ("nc".equalsIgnoreCase(name)) 103 digest.nc=tok; 104 else if ("cnonce".equalsIgnoreCase(name)) 105 digest.cnonce=tok; 106 else if ("qop".equalsIgnoreCase(name)) 107 digest.qop=tok; 108 else if ("uri".equalsIgnoreCase(name)) 109 digest.uri=tok; 110 else if ("response".equalsIgnoreCase(name)) 111 digest.response=tok; 112 break; 113 } 114 } 115 } 116 117 int n=checkNonce(digest.nonce,request); 118 if (n>0) 119 user = realm.authenticate(digest.username,digest,request); 120 else if (n==0) 121 stale = true; 122 123 if (user==null) 124 log.warn("AUTH FAILURE: user "+digest.username); 125 else 126 { 127 request.setAuthType(SecurityConstraint.__DIGEST_AUTH); 128 request.setAuthUser(digest.username); 129 request.setUserPrincipal(user); 130 } 131 } 132 133 if (user==null && response!=null) 135 sendChallenge(realm,request,response,stale); 136 137 return user; 138 } 139 140 141 public String getAuthMethod() 142 { 143 return SecurityConstraint.__DIGEST_AUTH; 144 } 145 146 147 public void sendChallenge(UserRealm realm, 148 HttpRequest request, 149 HttpResponse response, 150 boolean stale) 151 throws IOException 152 { 153 response.setField(HttpFields.__WwwAuthenticate, 154 "Digest realm=\""+realm.getName()+ 155 "\", domain=\""+ 156 response.getHttpContext().getContextPath() + 157 "\", nonce=\""+newNonce(request)+ 158 "\", algorithm=MD5, qop=\"auth\"" + (useStale?(" stale="+stale):"") 159 ); 160 161 response.sendError(HttpResponse.__401_Unauthorized); 162 } 163 164 165 public String newNonce(HttpRequest request) 166 { 167 long ts=request.getTimeStamp(); 168 long sk=nonceSecret; 169 170 byte[] nounce = new byte[24]; 171 for (int i=0;i<8;i++) 172 { 173 nounce[i]=(byte)(ts&0xff); 174 ts=ts>>8; 175 nounce[8+i]=(byte)(sk&0xff); 176 sk=sk>>8; 177 } 178 179 byte[] hash=null; 180 try 181 { 182 MessageDigest md = MessageDigest.getInstance("MD5"); 183 md.reset(); 184 md.update(nounce,0,16); 185 hash = md.digest(); 186 } 187 catch(Exception e) 188 { 189 log.fatal(this,e); 190 } 191 192 for (int i=0;i<hash.length;i++) 193 { 194 nounce[8+i]=hash[i]; 195 if (i==23) 196 break; 197 } 198 199 return new String (B64Code.encode(nounce)); 200 } 201 202 207 208 public int checkNonce(String nonce, HttpRequest request) 209 { 210 try 211 { 212 byte[] n = B64Code.decode(nonce.toCharArray()); 213 if (n.length!=24) 214 return -1; 215 216 long ts=0; 217 long sk=nonceSecret; 218 byte[] n2 = new byte[16]; 219 for (int i=0;i<8;i++) 220 { 221 n2[i]=n[i]; 222 n2[8+i]=(byte)(sk&0xff); 223 sk=sk>>8; 224 ts=(ts<<8)+(0xff&(long)n[7-i]); 225 } 226 227 long age=request.getTimeStamp()-ts; 228 if (log.isDebugEnabled()) log.debug("age="+age); 229 230 byte[] hash=null; 231 try 232 { 233 MessageDigest md = MessageDigest.getInstance("MD5"); 234 md.reset(); 235 md.update(n2,0,16); 236 hash = md.digest(); 237 } 238 catch(Exception e) 239 { 240 log.fatal(this,e); 241 } 242 243 for (int i=0;i<16;i++) 244 if (n[i+8]!=hash[i]) 245 return -1; 246 247 if(maxNonceAge>0 && (age<0 || age>maxNonceAge)) 248 return 0; 250 return 1; 251 } 252 catch(Exception e) 253 { 254 log.debug("",e); 255 } 256 return -1; 257 } 258 259 260 261 262 private static class Digest extends Credential 263 { 264 String method=null; 265 String username = null; 266 String realm = null; 267 String nonce = null; 268 String nc = null; 269 String cnonce = null; 270 String qop = null; 271 String uri = null; 272 String response=null; 273 274 275 Digest(String m) 276 { 277 method=m; 278 } 279 280 281 public boolean check(Object credentials) 282 { 283 String password=(credentials instanceof String ) 284 ?(String )credentials 285 :credentials.toString(); 286 287 try{ 288 MessageDigest md = MessageDigest.getInstance("MD5"); 289 byte[] ha1; 290 if(credentials instanceof Credential.MD5) 291 { 292 ha1 = ((Credential.MD5)credentials).getDigest(); 296 } 297 else 298 { 299 md.update(username.getBytes(StringUtil.__ISO_8859_1)); 301 md.update((byte)':'); 302 md.update(realm.getBytes(StringUtil.__ISO_8859_1)); 303 md.update((byte)':'); 304 md.update(password.getBytes(StringUtil.__ISO_8859_1)); 305 ha1=md.digest(); 306 } 307 md.reset(); 309 md.update(method.getBytes(StringUtil.__ISO_8859_1)); 310 md.update((byte)':'); 311 md.update(uri.getBytes(StringUtil.__ISO_8859_1)); 312 byte[] ha2=md.digest(); 313 314 315 316 317 318 322 323 324 md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1)); 325 md.update((byte)':'); 326 md.update(nonce.getBytes(StringUtil.__ISO_8859_1)); 327 md.update((byte)':'); 328 md.update(nc.getBytes(StringUtil.__ISO_8859_1)); 329 md.update((byte)':'); 330 md.update(cnonce.getBytes(StringUtil.__ISO_8859_1)); 331 md.update((byte)':'); 332 md.update(qop.getBytes(StringUtil.__ISO_8859_1)); 333 md.update((byte)':'); 334 md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1)); 335 byte[] digest=md.digest(); 336 337 return (TypeUtil.toString(digest,16).equalsIgnoreCase(response)); 339 } 340 catch (Exception e) 341 {log.warn(LogSupport.EXCEPTION,e);} 342 343 return false; 344 } 345 346 public String toString() 347 { 348 return username+","+response; 349 } 350 351 } 352 355 public long getMaxNonceAge() 356 { 357 return maxNonceAge; 358 } 359 362 public void setMaxNonceAge(long maxNonceAge) 363 { 364 this.maxNonceAge = maxNonceAge; 365 } 366 369 public long getNonceSecret() 370 { 371 return nonceSecret; 372 } 373 376 public void setNonceSecret(long nonceSecret) 377 { 378 this.nonceSecret = nonceSecret; 379 } 380 381 public void setUseStale(boolean us) 382 { 383 this.useStale=us; 384 } 385 386 public boolean getUseStale() 387 { 388 return useStale; 389 } 390 } 391 392 | Popular Tags |