/*
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package jlibs.nio.http.filters;
import jlibs.core.lang.ImpossibleException;
import jlibs.nio.http.FilterType;
import jlibs.nio.http.ServerExchange;
import jlibs.nio.http.util.Credentials;
import jlibs.nio.http.util.DigestChallenge;
import jlibs.nio.http.util.DigestCredentials;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.concurrent.ThreadLocalRandom;
import static javax.xml.bind.DatatypeConverter.*;
import static jlibs.core.io.IOUtil.UTF_8;
/**
* @author Santhosh Kumar Tekuri
*
* Server Request Filter
*/
public class CheckDigestAuthentication extends CheckAuthentication{
private final String realm;
public int nonceValiditySeconds = 300;
private final String key;
public CheckDigestAuthentication(Authenticator authenticator, String realm, boolean proxy){
super(authenticator, proxy);
this.realm = realm;
try{
byte bytes[] = new byte[10];
ThreadLocalRandom.current().nextBytes(bytes);
MessageDigest md5 = MessageDigest.getInstance("MD5");
key = printHexBinary(md5.digest(bytes)).toLowerCase();
}catch(NoSuchAlgorithmException ex){
throw new ImpossibleException(ex);
}
}
@Override
public boolean filter(ServerExchange exchange, FilterType type) throws Exception{
assert type==FilterType.REQUEST;
Credentials credentials = getCredentials(exchange);
MessageDigest md5 = MessageDigest.getInstance("MD5");
boolean stale = false;
if(credentials instanceof DigestCredentials){
DigestCredentials digestCredentials = (DigestCredentials)credentials;
long expiryTime = getExpiryTime(md5, digestCredentials.nonce);
if(expiryTime!=-1){
long beginTime = -1;
//beginTime = exchange.getBeginTime(); //todo
if(beginTime==-1)
beginTime = System.currentTimeMillis();
if(expiryTime<beginTime)
stale = true;
else if("auth".equals(digestCredentials.qop)
&& realm.equals(digestCredentials.realm)
&& digestCredentials.nc!=null
&& digestCredentials.cnonce!=null){
String password = authenticator.getPassword(digestCredentials.username);
if(password!=null){
String a1 = digestCredentials.username+':'+realm+':'+password;
String ha1 = printHexBinary(md5.digest(a1.getBytes(UTF_8))).toLowerCase();
md5.reset();
md5.update((exchange.getRequest().method+":"+digestCredentials.uri).getBytes(UTF_8));
String ha2 = printHexBinary(md5.digest()).toLowerCase();
md5.reset();
String respString = ha1+':'+digestCredentials.nonce;
respString += ':'+digestCredentials.nc+':'+digestCredentials.cnonce+':'+digestCredentials.qop;
respString += ':'+ha2;
String response = printHexBinary(md5.digest(respString.getBytes(UTF_8))).toLowerCase();
md5.reset();
if(response.equals(digestCredentials.response)){
authorized(exchange, digestCredentials.username);
return true;
}
}
}
}
}
DigestChallenge challenge = new DigestChallenge();
challenge.realm = realm;
challenge.qops = Collections.singletonList("auth");
challenge.stale = stale;
challenge.nonce = createNonce(md5);
throw unauthorized(exchange, challenge);
}
private String createNonce(MessageDigest md5){
long expiryTime = System.currentTimeMillis()+nonceValiditySeconds*1000;
String signature = expiryTime+":"+key;
signature = printHexBinary(md5.digest(signature.getBytes(UTF_8))).toLowerCase();
md5.reset();
String nonce = expiryTime+":"+signature;
return printBase64Binary(nonce.getBytes(UTF_8));
}
private long getExpiryTime(MessageDigest md5, String nonce){
nonce = new String(parseBase64Binary(nonce), UTF_8);
int colon = nonce.indexOf(':');
if(colon!=-1){
String signature = nonce.substring(0, colon+1)+key;
signature = printHexBinary(md5.digest(signature.getBytes(UTF_8))).toLowerCase();
md5.reset();
if(signature.equals(nonce.substring(colon+1)))
return Long.parseLong(nonce.substring(0, colon));
}
return -1;
}
}