com.disney.http.auth.server.digest.DigestVerifierImpl Maven / Gradle / Ivy
/*******************************************************************************
* © 2018 Disney | ABC Television Group
*
* Licensed under the Apache License, Version 2.0 (the "Apache License")
* with the following modification; you may not use this file except in
* compliance with the Apache License and the following modification to it:
* Section 6. Trademarks. is deleted and replaced with:
*
* 6. Trademarks. This License does not grant permission to use the trade
* names, trademarks, service marks, or product names of the Licensor
* and its affiliates, except as required to comply with Section 4(c) of
* the License and to reproduce the content of the NOTICE file.
*
* You may obtain a copy of the Apache License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Apache License with the above modification is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the Apache License for the specific
* language governing permissions and limitations under the Apache License.
*******************************************************************************/
package com.disney.http.auth.server.digest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import com.disney.http.auth.AuthConstants;
import com.disney.http.auth.DigestAuthorization;
import com.disney.http.auth.server.AbstractVerifier;
import com.disney.http.auth.server.AuthenticatedPrincipal;
import com.disney.http.auth.server.ServerAuthorizationRequest;
import com.disney.http.auth.server.VerifierResult;
/**
* Digest verifier implementation performs Digest authentication according to RFC 2617, including the "auth" level of QOP
*
* This implementation relies on one or more PasswordDigesters to provide the necessary credential validation
*
* @author Alex Vigdor
*/
public class DigestVerifierImpl extends AbstractVerifier implements AuthConstants{
private List passwordDigesters;
private String nonceSecret = "HttpAuthNonce";
private long maxNonceAge = 120000;
private String domain = "/";
@Override
protected VerifierResult doVerifyInternal(ServerAuthorizationRequest request) throws NoSuchAlgorithmException, IOException {
VerifierResult result = new VerifierResult();
List headers = request.getHeaders(AUTHORIZATION_HEADER);
String authHeader = null;
for(String header: headers){
if(header.startsWith(DIGEST)){
authHeader = header;
break;
}
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
if(authHeader==null){
challenge(request, md5,result,ERROR_MISSING_CREDENTIALS,false);
return result;
}
DigestAuthorization authd;
try{
authd = new DigestAuthorization(authHeader);
}
catch(Exception e){
challenge(request, md5,result,e.getMessage(),false);
return result;
}
//validate URI
if(!request.getURI().equals(authd.getUri())){
challenge(request, md5,result,ERROR_INCORRECT_URI,false);
return result;
}
String nonce = authd.getNonce();
byte[] nonceBytes = DatatypeConverter.parseBase64Binary(nonce);
//validate timestamp
long timestamp = toLong(nonceBytes);
//validate nonce
if(!nonce.equals(makeNonce(md5, timestamp))){
challenge(request, md5,result,ERROR_INVALID_NONCE,false);
return result;
}
//validate digest
byte[] ha2 = DatatypeConverter.printHexBinary(md5.digest((request.getMethod()+":"+authd.getUri()).getBytes("UTF-8"))).toLowerCase().getBytes();
for(int i=0;i maxNonceAge){
challenge(request, md5,result,ERROR_STALE_NONCE,true);
}
else{
result.setAuthenticated(true);
result.setPrincipal(new AuthenticatedPrincipal(authd.getUsername()));
}
return result;
}
}
}
challenge(request, md5,result,ERROR_UNKNOWN_CREDENTIALS,false);
return result;
}
private void challenge(ServerAuthorizationRequest request, MessageDigest digester, VerifierResult result, String message, boolean stale) throws UnsupportedEncodingException{
StringBuilder authn = new StringBuilder(DIGEST);
authn.append(" ").append(REALM).append("=\"").append(getRealm()).append("\", qop=\"auth\", nonce=\"");
long curTime = System.currentTimeMillis();
authn.append(makeNonce(digester,curTime)).append("\", opaque=\"");
authn.append(DatatypeConverter.printBase64Binary(toBytes(curTime))).append("\"");
if(domain!=null){
authn.append(", domain=\"").append(domain).append("\"");
}
if(stale){
authn.append(", stale=\"true\"");
}
//System.out.println("digest challenge: "+message+"; "+authn.toString());
result.setChallenge(authn.toString());
result.setAuthenticated(false);
result.setMessage(message);
}
private String makeNonce(MessageDigest digest, long timestamp) throws UnsupportedEncodingException{
digest.reset();
String nonceSource = String.valueOf(timestamp)+":"+nonceSecret;
byte[] nonceHash = digest.digest(nonceSource.getBytes("UTF-8"));
byte[] nonce = new byte[nonceHash.length+8];
toBytes(timestamp,nonce);
System.arraycopy(nonceHash, 0, nonce, 8, nonceHash.length);
return DatatypeConverter.printBase64Binary(nonce);
}
public static byte[] toBytes(long val) {
byte [] b = new byte[8];
for (int i = 7; i > 0; i--) {
b[i] = (byte) val;
val >>>= 8;
}
b[0] = (byte) val;
return b;
}
public static void toBytes(long val, byte[] b) {
for (int i = 7; i > 0; i--) {
b[i] = (byte) val;
val >>>= 8;
}
b[0] = (byte) val;
}
public static long toLong(byte[] bytes){
long l = 0;
for(int i = 0; i < 8; i++) {
l <<= 8;
l ^= bytes[i] & 0xFF;
}
return l;
}
public List getPasswordDigesters() {
return passwordDigesters;
}
public void setPasswordDigesters(List passwordDigesters) {
this.passwordDigesters = passwordDigesters;
}
public String getNonceSecret() {
return nonceSecret;
}
public void setNonceSecret(String nonceSecret) {
this.nonceSecret = nonceSecret;
}
public long getMaxNonceAge() {
return maxNonceAge;
}
public void setMaxNonceAge(long maxNonceAge) {
this.maxNonceAge = maxNonceAge;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}