204
* Parse the specified authorization credentials, and return the
205
* associated Principal that these credentials authenticate (if any)
206
* from the specified Realm. If there is no such Principal, return
209
* @param request HTTP servlet request
210
* @param authorization Authorization credentials from this request
211
* @param realm Realm used to authenticate Principals
213
protected static Principal findPrincipal(Request request,
214
String authorization,
217
//System.out.println("Authorization token : " + authorization);
218
// Validate the authorization credentials format
219
if (authorization == null)
221
if (!authorization.startsWith("Digest "))
223
authorization = authorization.substring(7).trim();
225
// Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
226
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
228
String userName = null;
229
String realmName = null;
232
String cnonce = null;
235
String response = null;
236
String method = request.getMethod();
238
for (int i = 0; i < tokens.length; i++) {
239
String currentToken = tokens[i];
240
if (currentToken.length() == 0)
243
int equalSign = currentToken.indexOf('=');
246
String currentTokenName =
247
currentToken.substring(0, equalSign).trim();
248
String currentTokenValue =
249
currentToken.substring(equalSign + 1).trim();
250
if ("username".equals(currentTokenName))
251
userName = removeQuotes(currentTokenValue);
252
if ("realm".equals(currentTokenName))
253
realmName = removeQuotes(currentTokenValue, true);
254
if ("nonce".equals(currentTokenName))
255
nOnce = removeQuotes(currentTokenValue);
256
if ("nc".equals(currentTokenName))
257
nc = removeQuotes(currentTokenValue);
258
if ("cnonce".equals(currentTokenName))
259
cnonce = removeQuotes(currentTokenValue);
260
if ("qop".equals(currentTokenName))
261
qop = removeQuotes(currentTokenValue);
262
if ("uri".equals(currentTokenName))
263
uri = removeQuotes(currentTokenValue);
264
if ("response".equals(currentTokenName))
265
response = removeQuotes(currentTokenValue);
268
if ( (userName == null) || (realmName == null) || (nOnce == null)
269
|| (uri == null) || (response == null) )
272
// Second MD5 digest used to calculate the digest :
273
// MD5(Method + ":" + uri)
274
String a2 = method + ":" + uri;
275
//System.out.println("A2:" + a2);
277
byte[] buffer = null;
278
synchronized (md5Helper) {
279
buffer = md5Helper.digest(a2.getBytes());
281
String md5a2 = md5Encoder.encode(buffer);
283
return (realm.authenticate(userName, response, nOnce, nc, cnonce, qop,
290
300
* Parse the username from the specified authorization string. If none
291
301
* can be identified, return <code>null</code>
396
405
* @param response HTTP Servlet response
397
406
* @param config Login configuration describing how authentication
398
407
* should be performed
399
* @param nOnce nonce token
408
* @param nonce nonce token
401
410
protected void setAuthenticateHeader(Request request,
402
411
Response response,
403
412
LoginConfig config,
414
boolean isNonceStale) {
406
416
// Get the realm name
407
417
String realmName = config.getRealmName();
408
418
if (realmName == null)
409
419
realmName = REALM_NAME;
411
byte[] buffer = null;
412
synchronized (md5Helper) {
413
buffer = md5Helper.digest(nOnce.getBytes());
421
String authenticateHeader;
423
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
424
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
425
getOpaque() + "\", stale=true";
427
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
428
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
416
String authenticateHeader = "Digest realm=\"" + realmName + "\", "
417
+ "qop=\"auth\", nonce=\"" + nOnce + "\", " + "opaque=\""
418
+ md5Encoder.encode(buffer) + "\"";
419
432
response.setHeader("WWW-Authenticate", authenticateHeader);
437
// ------------------------------------------------------- Lifecycle Methods
440
public void start() throws LifecycleException {
443
// Generate a random secret key
444
if (getKey() == null) {
445
setKey(generateSessionId());
448
// Generate the opaque string the same way
449
if (getOpaque() == null) {
450
setOpaque(generateSessionId());
453
cnonces = new LinkedHashMap<String, DigestAuthenticator.NonceInfo>() {
455
private static final long serialVersionUID = 1L;
456
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
458
private long lastLog = 0;
461
protected boolean removeEldestEntry(
462
Map.Entry<String,NonceInfo> eldest) {
463
// This is called from a sync so keep it simple
464
long currentTime = System.currentTimeMillis();
465
if (size() > getCnonceCacheSize()) {
466
if (lastLog < currentTime &&
467
currentTime - eldest.getValue().getTimestamp() <
468
getNonceValidity()) {
469
// Replay attack is possible
470
log.warn(sm.getString(
471
"digestAuthenticator.cacheRemove"));
472
lastLog = currentTime + LOG_SUPPRESS_TIME;
481
private static class DigestInfo {
483
private String opaque;
484
private long nonceValidity;
486
private Map<String,NonceInfo> cnonces;
487
private boolean validateUri = true;
489
private String userName = null;
490
private String method = null;
491
private String uri = null;
492
private String response = null;
493
private String nonce = null;
494
private String nc = null;
495
private String cnonce = null;
496
private String realmName = null;
497
private String qop = null;
499
private boolean nonceStale = false;
502
public DigestInfo(String opaque, long nonceValidity, String key,
503
Map<String,NonceInfo> cnonces, boolean validateUri) {
504
this.opaque = opaque;
505
this.nonceValidity = nonceValidity;
507
this.cnonces = cnonces;
508
this.validateUri = validateUri;
511
public boolean validate(Request request, String authorization,
512
LoginConfig config) {
513
// Validate the authorization credentials format
514
if (authorization == null) {
517
if (!authorization.startsWith("Digest ")) {
520
authorization = authorization.substring(7).trim();
522
// Bugzilla 37132: http://issues.apache.org/bugzilla/show_bug.cgi?id=37132
523
String[] tokens = authorization.split(",(?=(?:[^\"]*\"[^\"]*\")+$)");
525
method = request.getMethod();
526
String opaque = null;
528
for (int i = 0; i < tokens.length; i++) {
529
String currentToken = tokens[i];
530
if (currentToken.length() == 0)
533
int equalSign = currentToken.indexOf('=');
537
String currentTokenName =
538
currentToken.substring(0, equalSign).trim();
539
String currentTokenValue =
540
currentToken.substring(equalSign + 1).trim();
541
if ("username".equals(currentTokenName))
542
userName = removeQuotes(currentTokenValue);
543
if ("realm".equals(currentTokenName))
544
realmName = removeQuotes(currentTokenValue, true);
545
if ("nonce".equals(currentTokenName))
546
nonce = removeQuotes(currentTokenValue);
547
if ("nc".equals(currentTokenName))
548
nc = removeQuotes(currentTokenValue);
549
if ("cnonce".equals(currentTokenName))
550
cnonce = removeQuotes(currentTokenValue);
551
if ("qop".equals(currentTokenName))
552
qop = removeQuotes(currentTokenValue);
553
if ("uri".equals(currentTokenName))
554
uri = removeQuotes(currentTokenValue);
555
if ("response".equals(currentTokenName))
556
response = removeQuotes(currentTokenValue);
557
if ("opaque".equals(currentTokenName))
558
opaque = removeQuotes(currentTokenValue);
561
if ( (userName == null) || (realmName == null) || (nonce == null)
562
|| (uri == null) || (response == null) ) {
566
// Validate the URI - should match the request line sent by client
569
String query = request.getQueryString();
571
uriQuery = request.getRequestURI();
573
uriQuery = request.getRequestURI() + "?" + query;
575
if (!uri.equals(uriQuery)) {
580
// Validate the Realm name
581
String lcRealm = config.getRealmName();
582
if (lcRealm == null) {
583
lcRealm = REALM_NAME;
585
if (!lcRealm.equals(realmName)) {
589
// Validate the opaque string
590
if (!this.opaque.equals(opaque)) {
595
int i = nonce.indexOf(":");
596
if (i < 0 || (i + 1) == nonce.length()) {
601
nonceTime = Long.parseLong(nonce.substring(0, i));
602
} catch (NumberFormatException nfe) {
605
String md5clientIpTimeKey = nonce.substring(i + 1);
606
long currentTime = System.currentTimeMillis();
607
if ((currentTime - nonceTime) > nonceValidity) {
611
String serverIpTimeKey =
612
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
613
byte[] buffer = null;
614
synchronized (md5Helper) {
615
buffer = md5Helper.digest(serverIpTimeKey.getBytes());
617
String md5ServerIpTimeKey = md5Encoder.encode(buffer);
618
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
623
if (qop != null && !QOP.equals(qop)) {
627
// Validate cnonce and nc
628
// Check if presence of nc and nonce is consistent with presence of qop
630
if (cnonce != null || nc != null) {
634
if (cnonce == null || nc == null) {
637
if (nc.length() != 8) {
642
count = Long.parseLong(nc, 16);
643
} catch (NumberFormatException nfe) {
647
synchronized (cnonces) {
648
info = cnonces.get(cnonce);
651
info = new NonceInfo();
653
if (count <= info.getCount()) {
657
info.setCount(count);
658
info.setTimestamp(currentTime);
659
synchronized (cnonces) {
660
cnonces.put(cnonce, info);
666
public boolean isNonceStale() {
670
public Principal authenticate(Realm realm) {
671
// Second MD5 digest used to calculate the digest :
672
// MD5(Method + ":" + uri)
673
String a2 = method + ":" + uri;
676
synchronized (md5Helper) {
677
buffer = md5Helper.digest(a2.getBytes());
679
String md5a2 = md5Encoder.encode(buffer);
681
return realm.authenticate(userName, response, nonce, nc, cnonce,
682
qop, realmName, md5a2);
687
private static class NonceInfo {
688
private volatile long count;
689
private volatile long timestamp;
691
public void setCount(long l) {
695
public long getCount() {
699
public void setTimestamp(long l) {
703
public long getTimestamp() {