/** * 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 if (chapPassword != null && getAuthenticator().length == 16) { // thanks to Guillaume Tartayre setAuthProtocol(AUTH_CHAP); this.chapPassword = chapPassword.getAttributeData(); this.chapChallenge = getAuthenticator(); } else throw new RadiusException( "Access-Request: User-Password or CHAP-Password/CHAP-Challenge missing"); }
/** * 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 == null ? 0 : 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); }
/** * 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)); } }
/** * 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; }