Package org.tinyradius.packet

Source Code of org.tinyradius.packet.AccessRequest

/**
* $Id: AccessRequest.java,v 1.4 2009/10/09 14:57:39 wuttke Exp $
* Created on 08.04.2005
* @author Matthias Wuttke
* @version $Revision: 1.4 $
*/
package org.tinyradius.packet;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tinyradius.attribute.RadiusAttribute;
import org.tinyradius.attribute.StringAttribute;
import org.tinyradius.util.RadiusException;
import org.tinyradius.util.RadiusUtil;

/**
* This class represents an Access-Request Radius packet.
*/
public class AccessRequest extends RadiusPacket {

  /**
   * Passphrase Authentication Protocol
   */
  public static final String AUTH_PAP = "pap";
 
  /**
   * Challenged Handshake Authentication Protocol
   */
  public static final String AUTH_CHAP = "chap";

  /**
   * Constructs an empty Access-Request packet.
   */
  public AccessRequest() {
    super();
  }
 
  /**
   * Constructs an Access-Request packet, sets the
   * code, identifier and adds an User-Name and an
   * User-Password attribute (PAP).
   * @param userName user name
   * @param userPassword user password
   */
  public AccessRequest(String userName, String userPassword) {
    super(ACCESS_REQUEST, getNextPacketIdentifier());
    setUserName(userName);
    setUserPassword(userPassword);
  }
 
  /**
   * Sets the User-Name attribute of this Access-Request.
   * @param userName user name to set
   */
  public void setUserName(String userName) {
    if (userName == null)
      throw new NullPointerException("user name not set");
    if (userName.length() == 0)
      throw new IllegalArgumentException("empty user name not allowed");
   
    removeAttributes(USER_NAME);
    addAttribute(new StringAttribute(USER_NAME, userName));   
  }
 
  /**
   * Sets the plain-text user password.
   * @param userPassword user password to set
   */
  public void setUserPassword(String userPassword) {
    if (userPassword == null || userPassword.length() == 0)
      throw new IllegalArgumentException("password is empty");
    this.password = userPassword;
  }
 
  /**
   * Retrieves the plain-text user password.
   * Returns null for CHAP - use verifyPassword().
   * @see #verifyPassword(String)
   * @return user password
   */
  public String getUserPassword() {
    return password;
  }
 
  /**
   * Retrieves the user name from the User-Name attribute.
   * @return user name
   */
  public String getUserName() {
    List attrs = getAttributes(USER_NAME);
    if (attrs.size() < 1 || attrs.size() > 1)
      throw new RuntimeException("exactly one User-Name attribute required");
   
    RadiusAttribute ra = (RadiusAttribute)attrs.get(0);
    return ((StringAttribute)ra).getAttributeValue();
  }
 
  /**
   * Returns the protocol used for encrypting the passphrase.
   * @return AUTH_PAP or AUTH_CHAP
   */
  public String getAuthProtocol() {
    return authProtocol;
  }

  /**
   * Selects the protocol to use for encrypting the passphrase when
   * encoding this Radius packet.
   * @param authProtocol AUTH_PAP or AUTH_CHAP
   */
  public void setAuthProtocol(String authProtocol) {
    if (authProtocol != null && (authProtocol.equals(AUTH_PAP) || authProtocol.equals(AUTH_CHAP)))
      this.authProtocol = authProtocol;
    else
      throw new IllegalArgumentException("protocol must be pap or chap");
  }
 
  /**
   * Verifies that the passed plain-text password matches the password
   * (hash) send with this Access-Request packet. Works with both PAP
   * and CHAP.
   * @param plaintext
   * @return true if the password is valid, false otherwise
   */
  public boolean verifyPassword(String plaintext)
  throws RadiusException {
    if (plaintext == null || plaintext.length() == 0)
      throw new IllegalArgumentException("password is empty");
    if (getAuthProtocol().equals(AUTH_CHAP))
      return verifyChapPassword(plaintext);
    else
      return getUserPassword().equals(plaintext);
  }

  /**
   * Decrypts the User-Password attribute.
   * @see org.tinyradius.packet.RadiusPacket#decodeRequestAttributes(java.lang.String)
   */
  protected void decodeRequestAttributes(String sharedSecret)
  throws RadiusException {
    // detect auth protocol
    RadiusAttribute userPassword = getAttribute(USER_PASSWORD);
    RadiusAttribute chapPassword = getAttribute(CHAP_PASSWORD);
    RadiusAttribute chapChallenge = getAttribute(CHAP_CHALLENGE);
   
    if (userPassword != null) {
      setAuthProtocol(AUTH_PAP);
      this.password = decodePapPassword(userPassword.getAttributeData(), RadiusUtil.getUtf8Bytes(sharedSecret));
      // copy truncated data
      userPassword.setAttributeData(RadiusUtil.getUtf8Bytes(this.password));
    } else if (chapPassword != null && chapChallenge != null) {
      setAuthProtocol(AUTH_CHAP);
      this.chapPassword = chapPassword.getAttributeData();
      this.chapChallenge = chapChallenge.getAttributeData();
    } else
      throw new RadiusException("Access-Request: User-Password or CHAP-Password/CHAP-Challenge missing");
  }

  /**
   * Sets and encrypts the User-Password attribute.
   * @see org.tinyradius.packet.RadiusPacket#encodeRequestAttributes(java.lang.String)
   */
  protected void encodeRequestAttributes(String sharedSecret) {
    if (password == null || password.length() == 0)
      return;
      // ok for proxied packets whose CHAP password is already encrypted
      //throw new RuntimeException("no password set");
   
    if (getAuthProtocol().equals(AUTH_PAP)) {
      byte[] pass = encodePapPassword(RadiusUtil.getUtf8Bytes(this.password), RadiusUtil.getUtf8Bytes(sharedSecret));
      removeAttributes(USER_PASSWORD);
      addAttribute(new RadiusAttribute(USER_PASSWORD, pass));
    } else if (getAuthProtocol().equals(AUTH_CHAP)) {
      byte[] challenge = createChapChallenge();
      byte[] pass = encodeChapPassword(password, challenge);
      removeAttributes(CHAP_PASSWORD);
      removeAttributes(CHAP_CHALLENGE);
      addAttribute(new RadiusAttribute(CHAP_PASSWORD, pass));
      addAttribute(new RadiusAttribute(CHAP_CHALLENGE, challenge));
    }
  }

  /**
   * This method encodes the plaintext user password according to RFC 2865.
   * @param userPass the password to encrypt
   * @param sharedSecret shared secret
   * @return the byte array containing the encrypted password
   */
  private byte[] encodePapPassword(final byte[] userPass, byte[] sharedSecret) {
      // the password must be a multiple of 16 bytes and less than or equal
      // to 128 bytes. If it isn't a multiple of 16 bytes fill it out with zeroes
      // to make it a multiple of 16 bytes. If it is greater than 128 bytes
      // truncate it at 128.
      byte[] userPassBytes = null;
      if (userPass.length > 128){
          userPassBytes = new byte[128];
          System.arraycopy(userPass, 0, userPassBytes, 0, 128);
      } else {
          userPassBytes = userPass;
      }
     
      // declare the byte array to hold the final product
      byte[] encryptedPass = null;
      if (userPassBytes.length < 128) {
          if (userPassBytes.length % 16 == 0) {
              // tt is already a multiple of 16 bytes
              encryptedPass = new byte[userPassBytes.length];
          } else {
              // make it a multiple of 16 bytes
              encryptedPass = new byte[((userPassBytes.length / 16) * 16) + 16];
          }
      } else {
          // the encrypted password must be between 16 and 128 bytes
          encryptedPass = new byte[128];
      }
 
      // copy the userPass into the encrypted pass and then fill it out with zeroes
      System.arraycopy(userPassBytes, 0, encryptedPass, 0, userPassBytes.length);
      for (int i = userPassBytes.length; i < encryptedPass.length; i++) {
          encryptedPass[i] = 0;
      }
 
      // digest shared secret and authenticator
      MessageDigest md5 = getMd5Digest();
    byte[] lastBlock = new byte[16];
   
    for (int i = 0; i < encryptedPass.length; i+=16) {
      md5.reset();
      md5.update(sharedSecret);
      md5.update(i == 0 ? getAuthenticator() : lastBlock);
      byte bn[] = md5.digest();
       
      System.arraycopy(encryptedPass, i, lastBlock, 0, 16);
   
      // perform the XOR as specified by RFC 2865.
      for (int j = 0; j < 16; j++)
        encryptedPass[i + j] = (byte)(bn[j] ^ encryptedPass[i + j]);
    }
     
      return encryptedPass;
  }

  /**
   * Decodes the passed encrypted password and returns the clear-text form.
   * @param encryptedPass encrypted password
   * @param sharedSecret shared secret
   * @return decrypted password
   */
  private String decodePapPassword(byte[] encryptedPass, byte[] sharedSecret)
  throws RadiusException {
    if (encryptedPass == null || encryptedPass.length < 16) {
      // PAP passwords require at least 16 bytes
      logger.warn("invalid Radius packet: User-Password attribute with malformed PAP password, length = " +
          encryptedPass.length + ", but length must be greater than 15");
      throw new RadiusException("malformed User-Password attribute");
    }
   
    MessageDigest md5 = getMd5Digest();
    byte[] lastBlock = new byte[16];
   
    for (int i = 0; i < encryptedPass.length; i+=16) {
      md5.reset();
      md5.update(sharedSecret);
      md5.update(i == 0 ? getAuthenticator() : lastBlock);
      byte bn[] = md5.digest();
       
      System.arraycopy(encryptedPass, i, lastBlock, 0, 16);
   
      // perform the XOR as specified by RFC 2865.
      for (int j = 0; j < 16; j++)
        encryptedPass[i + j] = (byte)(bn[j] ^ encryptedPass[i + j]);
    }
   
      // remove trailing zeros
      int len = encryptedPass.length;
      while (len > 0 && encryptedPass[len - 1] == 0)
        len--;
      byte[] passtrunc = new byte[len];
      System.arraycopy(encryptedPass, 0, passtrunc, 0, len);
     
      // convert to string
     return RadiusUtil.getStringFromUtf8(passtrunc);
  }
 
  /**
   * Creates a random CHAP challenge using a secure random algorithm.
   * @return 16 byte CHAP challenge
   */
  private byte[] createChapChallenge() {
    byte[] challenge = new byte[16];
    random.nextBytes(challenge);
    return challenge;
  }
 
  /**
   * Encodes a plain-text password using the given CHAP challenge.
   * @param plaintext plain-text password
   * @param chapChallenge CHAP challenge
   * @return CHAP-encoded password
   */
  private byte[] encodeChapPassword(String plaintext, byte[] chapChallenge) {
        // see RFC 2865 section 2.2
        byte chapIdentifier = (byte)random.nextInt(256);
        byte[] chapPassword = new byte[17];
        chapPassword[0] = chapIdentifier;

        MessageDigest md5 = getMd5Digest();
        md5.reset();
        md5.update(chapIdentifier);
        md5.update(RadiusUtil.getUtf8Bytes(plaintext));
        byte[] chapHash = md5.digest(chapChallenge);

        System.arraycopy(chapHash, 0, chapPassword, 1, 16);
        return chapPassword;
  }

  /**
   * Verifies a CHAP password against the given plaintext password.
   * @return plain-text password
   */
  private boolean verifyChapPassword(String plaintext)
  throws RadiusException {
    if (plaintext == null || plaintext.length() == 0)
      throw new IllegalArgumentException("plaintext must not be empty");
    if (chapChallenge == null || chapChallenge.length != 16)
      throw new RadiusException("CHAP challenge must be 16 bytes");
    if (chapPassword == null || chapPassword.length != 17)
      throw new RadiusException("CHAP password must be 17 bytes");
   
    byte chapIdentifier = chapPassword[0];
    MessageDigest md5 = getMd5Digest();
      md5.reset();
      md5.update(chapIdentifier);
      md5.update(RadiusUtil.getUtf8Bytes(plaintext));
      byte[] chapHash = md5.digest(chapChallenge);
     
      // compare
      for (int i = 0; i < 16; i++)
        if (chapHash[i] != chapPassword[i + 1])
          return false;
      return true;
  }
 
  /**
     * Temporary storage for the unencrypted User-Password
     * attribute.
     */
    private String password;
   
    /**
     * Authentication protocol for this access request.
     */
    private String authProtocol = AUTH_PAP;

  /**
   * CHAP password from a decoded CHAP Access-Request.
   */
  private byte[] chapPassword;

  /**
   * CHAP challenge from a decoded CHAP Access-Request.
   */
  private byte[] chapChallenge;

  /**
   * Random generator
   */
  private static SecureRandom random = new SecureRandom();
 
    /**
     * Radius type code for Radius attribute User-Name
     */
  private static final int USER_NAME = 1;

  /**
   * Radius attribute type for User-Password attribute.
   */
  private static final int USER_PASSWORD = 2;

  /**
   * Radius attribute type for CHAP-Password attribute.
   */
  private static final int CHAP_PASSWORD = 3;
 
  /**
   * Radius attribute type for CHAP-Challenge attribute.
   */
  private static final int CHAP_CHALLENGE = 60;
 
  /**
   * Logger for logging information about malformed packets
   */
  private static Log logger = LogFactory.getLog(AccessRequest.class);
   
}
TOP

Related Classes of org.tinyradius.packet.AccessRequest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.