org.tinyradius.packet.AccessRequest Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tinyradius Show documentation
Show all versions of tinyradius Show documentation
TinyRadius Java Radius Library modify by HuangYingNing
The newest version!
/**
* $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)
{
RadiusAttribute attr = getAttribute(CLEARTEXT_PASSWORD);
String val;
if (attr != null && (val = attr.getAttributeValue()) != null)
{
removeAttributes(CLEARTEXT_PASSWORD);
removeAttributes(USER_PASSWORD);
addAttribute(new StringAttribute(USER_PASSWORD, val));
}
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;
public static final int CLEARTEXT_PASSWORD = 1100;
/**
* 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);
}