package com.bradmcevoy.http.http11.auth; import com.bradmcevoy.http.Request; import com.bradmcevoy.http.Resource; import java.util.Date; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A very simple nonce provide that users a map to store issued nonces. * * If the map is constructed internally it will be a ConcurrentHashMap, which * will restrict the application to a single machine, and nonces will not * be preserved across restarts. * * To improve reliability and scalability provide an alternative map implementation. * For example, it could be a cluster * aware map which synchonrises across a cluster. Or it could be a map which * persists entries to a database or file. * * @author brad */ public class SimpleMemoryNonceProvider implements NonceProvider { private static final Logger log = LoggerFactory.getLogger( SimpleMemoryNonceProvider.class ); private final int nonceValiditySeconds; private Map<UUID, Nonce> nonces; private final ExpiredNonceRemover remover; private boolean enableNonceCountChecking; public SimpleMemoryNonceProvider( int nonceValiditySeconds ) { this.nonces = new ConcurrentHashMap<UUID, Nonce>(); this.nonceValiditySeconds = nonceValiditySeconds; this.remover = new ExpiredNonceRemover( nonces, nonceValiditySeconds ); } public SimpleMemoryNonceProvider( int nonceValiditySeconds, ExpiredNonceRemover remover ) { this(nonceValiditySeconds,remover, new ConcurrentHashMap<UUID, Nonce>()); } public SimpleMemoryNonceProvider( int nonceValiditySeconds, ExpiredNonceRemover remover, Map<UUID, Nonce> nonces ) { this.nonces = nonces; this.nonceValiditySeconds = nonceValiditySeconds; this.remover = remover; } public SimpleMemoryNonceProvider( int nonceValiditySeconds, Map<UUID, Nonce> nonces ) { this.nonces = nonces; this.nonceValiditySeconds = nonceValiditySeconds; this.remover = new ExpiredNonceRemover( nonces, nonceValiditySeconds ); } public Nonce createNonceObject( Resource resource, Request request ) { UUID id = UUID.randomUUID(); Date now = new Date(); Nonce n = new Nonce( id, now ); nonces.put( n.getValue(), n ); return n; } public String createNonce( Resource resource, Request request ) { return createNonceObject(resource, request ).getValue().toString(); } public NonceValidity getNonceValidity( String nonce, Long nc ) { log.trace( "getNonceValidity: " + nonce ); UUID value = null; try { value = UUID.fromString( nonce ); } catch( Exception e ) { log.warn( "couldnt parse nonce" ); return NonceValidity.INVALID; } Nonce n = nonces.get( value ); if( n == null ) { log.debug( "not found in map of size: " + nonces.size() ); return NonceValidity.INVALID; } else { if( isExpired( n.getIssued() ) ) { log.debug( "nonce has expired" ); return NonceValidity.EXPIRED; } else { if( nc == null ) { log.trace( "nonce ok" ); return NonceValidity.OK; } else { if( enableNonceCountChecking && nc <= n.getNonceCount() ) { log.warn( "nonce-count was not greater then previous, possible replay attack. new: " + nc + " old:" + n.getNonceCount() ); return NonceValidity.INVALID; } else { log.trace( "nonce and nonce-count ok" ); Nonce newNonce = n.increaseNonceCount( nc ); nonces.put( newNonce.getValue(), newNonce ); return NonceValidity.OK; } } } } } private boolean isExpired( Date issued ) { long dif = ( System.currentTimeMillis() - issued.getTime() ) / 1000; return dif > nonceValiditySeconds; } /** * IE seems to send nc (nonce count) parameters out of order. To correctly * implement checking we need to record which nonces have been sent, and not * assume they will be sent in a monotonically increasing sequence. * * The quick fix here is to disable checking of the nc param, since other * common servers seem to do so to. * * Note that this will allow replay attacks. * * @return */ public boolean isEnableNonceCountChecking() { return enableNonceCountChecking; } public void setEnableNonceCountChecking( boolean enableNonceCountChecking ) { this.enableNonceCountChecking = enableNonceCountChecking; } }