package com.bradmcevoy.http.http11.auth; import com.bradmcevoy.http.Auth; import com.bradmcevoy.http.Request.Method; import com.bradmcevoy.http.http11.auth.NonceProvider.NonceValidity; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * */ public class DigestHelper { private static final Logger log = LoggerFactory.getLogger( DigestHelper.class ); private final NonceProvider nonceProvider; public DigestHelper(NonceProvider nonceProvider) { this.nonceProvider = nonceProvider; } public DigestResponse calculateResponse( Auth auth, String expectedRealm, Method method ) { // Check all required parameters were supplied (ie RFC 2069) if( ( auth.getUser() == null ) || ( auth.getRealm() == null ) || ( auth.getNonce() == null ) || ( auth.getUri() == null ) ) { log.warn( "missing params" ); return null; } // Check all required parameters for an "auth" qop were supplied (ie RFC 2617) Long nc; if( "auth".equals( auth.getQop() ) ) { if( ( auth.getNc() == null ) || ( auth.getCnonce() == null ) ) { log.warn( "missing params: nc and/or cnonce" ); return null; } nc = Long.parseLong( auth.getNc(), 16); // the nonce-count. hex value, must always increase } else { nc = null; } // Check realm name equals what we expected if( expectedRealm == null ) throw new IllegalStateException( "realm is null"); if( !expectedRealm.equals( auth.getRealm() ) ) { log.warn( "incorrect realm: resource: " + expectedRealm + " given: " + auth.getRealm() ); return null; } // Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint) if( !Base64.isArrayByteBase64( auth.getNonce().getBytes() ) ) { log.warn( "nonce not base64 encoded" ); return null; } log.debug( "nc: " + auth.getNc()); // Decode nonce from Base64 // format of nonce is // base64(expirationTime + "" + md5Hex(expirationTime + "" + key)) String plainTextNonce = new String( Base64.decodeBase64( auth.getNonce().getBytes() ) ); NonceValidity validity = nonceProvider.getNonceValidity( plainTextNonce, nc ); if( NonceValidity.INVALID.equals( validity ) ) { log.debug( "invalid nonce: " + plainTextNonce ); return null; } else if( NonceValidity.EXPIRED.equals( validity ) ) { log.debug( "expired nonce: " + plainTextNonce ); // make this known so that we can add stale field to challenge auth.setNonceStale( true ); return null; } DigestResponse resp = toDigestResponse( auth, method ); return resp; } public String getChallenge( String nonceValue, Auth auth, String actualRealm ) { String nonceValueBase64 = new String( Base64.encodeBase64( nonceValue.getBytes() ) ); // qop is quality of protection, as defined by RFC 2617. // we do not use opaque due to IE violation of RFC 2617 in not // representing opaque on subsequent requests in same session. String authenticateHeader = "Digest realm=\"" + actualRealm + "\", " + "qop=\"auth\", nonce=\"" + nonceValueBase64 + "\""; if( auth != null ) { if( auth.isNonceStale() ) { authenticateHeader = authenticateHeader + ", stale=\"true\""; } } return authenticateHeader; } private DigestResponse toDigestResponse( Auth auth, Method m ) { DigestResponse dr = new DigestResponse( m, auth.getUser(), auth.getRealm(), auth.getNonce(), auth.getUri(), auth.getResponseDigest(), auth.getQop(), auth.getNc(), auth.getCnonce() ); return dr; } }