[jboss-cvs] jboss-seam/src/main/org/jboss/seam/security/digest ...

Shane Bryzak sbryzak at redhat.com
Tue May 22 23:44:32 EDT 2007


  User: sbryzak2
  Date: 07/05/22 23:44:32

  Added:       src/main/org/jboss/seam/security/digest    
                        DigestAuthenticator.java DigestRequest.java
                        DigestUtils.java DigestValidationException.java
  Log:
  JBSEAM-743 still todo: testing, documentation, config
  
  Revision  Changes    Path
  1.1      date: 2007/05/23 03:44:32;  author: sbryzak2;  state: Exp;jboss-seam/src/main/org/jboss/seam/security/digest/DigestAuthenticator.java
  
  Index: DigestAuthenticator.java
  ===================================================================
  package org.jboss.seam.security.digest;
  
  import javax.security.auth.login.LoginException;
  
  import org.jboss.seam.contexts.Context;
  import org.jboss.seam.contexts.Contexts;
  import org.jboss.seam.security.Identity;
  
  /**
   * This class provides methods for performing Digest (RFC 2617) authentication
   * and is intended to be extended by a concrete Authenticator implementation. 
   *  
   * @author Shane Bryzak
   */
  public abstract class DigestAuthenticator
  {
     protected void checkPassword(String password)
        throws LoginException
     {
        Context ctx = Contexts.getSessionContext();
        
        DigestRequest digestRequest = (DigestRequest) ctx.get(DigestRequest.DIGEST_REQUEST);
        if (digestRequest == null)
        {
           throw new LoginException("No digest request found in session scope");
        }
        
        // Remove the digest request from the session now
        ctx.remove(DigestRequest.DIGEST_REQUEST);
        
        // Calculate the expected digest
        String serverDigestMd5 = DigestUtils.generateDigest(
                 digestRequest.isPasswordAlreadyEncoded(), 
                 Identity.instance().getUsername(), digestRequest.getRealm(), 
                 password, digestRequest.getHttpMethod(), 
                 digestRequest.getUri(), digestRequest.getQop(), 
                 digestRequest.getNonce(), digestRequest.getNonceCount(), 
                 digestRequest.getClientNonce());
  
        // If digest is incorrect, try refreshing from backend and recomputing
        if (!serverDigestMd5.equals(digestRequest.getClientDigest()))
        {
           throw new LoginException("Digest authentication failed - incorrect response");
        }
     }  
  }
  
  
  
  1.1      date: 2007/05/23 03:44:32;  author: sbryzak2;  state: Exp;jboss-seam/src/main/org/jboss/seam/security/digest/DigestRequest.java
  
  Index: DigestRequest.java
  ===================================================================
  package org.jboss.seam.security.digest;
  
  import org.jboss.seam.util.Base64;
  
  public class DigestRequest
  {
     public static final String DIGEST_REQUEST = "org.jboss.seam.security.digestRequest";   
     
     private boolean passwordAlreadyEncoded;
     private String systemRealm;
     private String realm;
     private String key;
     private String password;
     private String uri;
     
     /**
      * quality of protection, defined by RFC 2617
      */
     private String qop;
     private String nonce;
     private String nonceCount;
     private String clientNonce;
     private String httpMethod;
     
     /**
      * The digest that the client responds with
      */
     private String clientDigest;
     
     public String getClientNonce()
     {
        return clientNonce;
     }
     
     public void setClientNonce(String clientNonce)
     {
        this.clientNonce = clientNonce;
     }
     
     public String getNonce()
     {
        return nonce;
     }
     
     public void setNonce(String nonce)
     {
        this.nonce = nonce;
     }
     
     public String getNonceCount()
     {
        return nonceCount;
     }
     
     public void setNonceCount(String nonceCount)
     {
        this.nonceCount = nonceCount;
     }
     
     public String getPassword()
     {
        return password;
     }
     
     public void setPassword(String password)
     {
        this.password = password;
     }
     
     public boolean isPasswordAlreadyEncoded()
     {
        return passwordAlreadyEncoded;
     }
     
     public void setPasswordAlreadyEncoded(boolean passwordAlreadyEncoded)
     {
        this.passwordAlreadyEncoded = passwordAlreadyEncoded;
     }
     
     public String getQop()
     {
        return qop;
     }
     
     public void setQop(String qop)
     {
        this.qop = qop;
     }
     
     public String getRealm()
     {
        return realm;
     }
     
     public String getSystemRealm()
     {
        return systemRealm;
     }
     
     public void setSystemRealm(String systemRealm)
     {
        this.systemRealm = systemRealm;
     }
     
     public void setRealm(String realm)
     {
        this.realm = realm;
     }
     
     public String getKey()
     {
        return key;
     }
     
     public void setKey(String key)
     {
        this.key = key;
     }
     
     public String getUri()
     {
        return uri;
     }
     
     public void setUri(String uri)
     {
        this.uri = uri;
     } 
     
     public String getHttpMethod()
     {
        return httpMethod;
     }
     
     public void setHttpMethod(String httpMethod)
     {
        this.httpMethod = httpMethod;
     }
     
     public String getClientDigest()
     {
        return clientDigest;
     }
     
     public void setClientDigest(String clientDigest)
     {
        this.clientDigest = clientDigest;
     }
     
     public void validate()
        throws DigestValidationException
     {
        // Check all required parameters were supplied (ie RFC 2069)
        if (realm == null) throw new DigestValidationException("Mandatory field 'realm' not specified");
        if (nonce == null) throw new DigestValidationException("Mandatory field 'nonce' not specified");
        if (uri == null) throw new DigestValidationException("Mandatory field 'uri' not specified");
        if (clientDigest == null) throw new DigestValidationException("Mandatory field 'response' not specified");
        
        // Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
        if ("auth".equals(qop)) 
        {
           if (nonceCount == null) 
           {
              throw new DigestValidationException("Mandatory field 'nc' not specified");
           }
           
           if (clientNonce == null) 
           {
              throw new DigestValidationException("Mandatory field 'cnonce' not specified");
           }
        }         
        
  
        String nonceAsText = new String(Base64.decode(nonce));         
        if (nonceAsText == null) 
        {
           throw new DigestValidationException("Nonce is not Base64 encoded - nonce received: " + nonce);
        }           
        
        String[] nonceTokens = nonceAsText.split(":");
        if (nonceTokens.length != 2) 
        {
           throw new DigestValidationException("Nonce should provide two tokens - nonce received: " + nonce);
        }
        
        // Check realm name equals what we expected
        if (!systemRealm.equals(realm)) 
        {
           throw new DigestValidationException("Realm name [" + realm + 
                    "] does not match system realm name [" + systemRealm + "]");
        }            
        
        long nonceExpiry = 0;      
        try 
        {
           nonceExpiry = new Long(nonceTokens[0]).longValue();
        } 
        catch (NumberFormatException nfe) 
        {
           throw new DigestValidationException("First nonce token should be numeric, but was: " + nonceTokens[0]);
        }
        
  
        // To get this far, the digest must have been valid
        // Check the nonce has not expired
        // We do this last so we can direct the user agent its nonce is stale
        // but the request was otherwise appearing to be valid
        if (nonceExpiry < System.currentTimeMillis()) 
        {
           throw new DigestValidationException("Nonce has expired", true);
        }        
        
        String expectedNonceSignature = DigestUtils.md5Hex(nonceExpiry + ":" + key);
        
        if (!expectedNonceSignature.equals(nonceTokens[1])) 
        {
           throw new DigestValidationException("Nonce token invalid: " + nonceAsText);
        }       
     }
  }
  
  
  
  1.1      date: 2007/05/23 03:44:32;  author: sbryzak2;  state: Exp;jboss-seam/src/main/org/jboss/seam/security/digest/DigestUtils.java
  
  Index: DigestUtils.java
  ===================================================================
  package org.jboss.seam.security.digest;
  
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  
  import org.apache.commons.codec.binary.Hex;
  
  /**
   * Digest-related utility methods, adapted from Acegi and Apache Commons.
   *  
   * @author Shane Bryzak
   */
  public class DigestUtils
  {   
     public static String generateDigest(boolean passwordAlreadyEncoded, String username,
              String realm, String password, String httpMethod, String uri, String qop, String nonce,
              String nc, String cnonce) throws IllegalArgumentException
     {
        String a1Md5 = null;
        String a2 = httpMethod + ":" + uri;
        String a2Md5 = new String(DigestUtils.md5Hex(a2));
  
        if (passwordAlreadyEncoded)
        {
           a1Md5 = password;
        }
        else
        {
           a1Md5 = encodePasswordInA1Format(username, realm, password);
        }
  
        String digest;
  
        if (qop == null)
        {
           // as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
           digest = a1Md5 + ":" + nonce + ":" + a2Md5;
        }
        else if ("auth".equals(qop))
        {
           // As per RFC 2617 compliant clients
           digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
        }
        else
        {
           throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
        }
  
        String digestMd5 = new String(DigestUtils.md5Hex(digest));
  
        return digestMd5;
     }
  
     public static String encodePasswordInA1Format(String username, String realm, String password)
     {
        String a1 = username + ":" + realm + ":" + password;
        String a1Md5 = new String(DigestUtils.md5Hex(a1));
  
        return a1Md5;
     } 
     
     public static String md5Hex(String value)
     {
        try
        {
           MessageDigest md = MessageDigest.getInstance("MD5");
           return new String(Hex.encodeHex(md.digest(value.getBytes())));
        }
        catch (NoSuchAlgorithmException ex)
        {
           throw new RuntimeException("Invalid algorithm");
        }
     }
  }
  
  
  
  1.1      date: 2007/05/23 03:44:32;  author: sbryzak2;  state: Exp;jboss-seam/src/main/org/jboss/seam/security/digest/DigestValidationException.java
  
  Index: DigestValidationException.java
  ===================================================================
  package org.jboss.seam.security.digest;
  
  /**
   * Thrown when a DigestRequest fails validation.
   * 
   * @author Shane Bryzak
   */
  public class DigestValidationException extends Exception
  {
     private boolean nonceExpired = false;
     
     public DigestValidationException(String message)
     {
        super(message);
     }
     
     public DigestValidationException(String message, boolean nonceExpired)
     {
        super(message);
        this.nonceExpired = nonceExpired;
     }
     
     public boolean isNonceExpired()
     {
        return nonceExpired;
     }
  }
  
  
  



More information about the jboss-cvs-commits mailing list