diff options
Diffstat (limited to 'framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java')
-rw-r--r-- | framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java new file mode 100644 index 00000000..297fee7c --- /dev/null +++ b/framework/src/onos/utils/misc/src/main/java/org/onlab/packet/RADIUS.java @@ -0,0 +1,423 @@ +/* + * + * * Copyright 2015 AT&T Foundry + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.onlab.packet; + +import org.slf4j.Logger; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.onlab.packet.PacketUtils.checkHeaderLength; +import static org.onlab.packet.PacketUtils.checkInput; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * RADIUS packet. + */ +public class RADIUS extends BasePacket { + protected byte code; + protected byte identifier; + protected short length = RADIUS_MIN_LENGTH; + protected byte[] authenticator = new byte[16]; + protected List<RADIUSAttribute> attributes = new ArrayList<>(); + + // RADIUS parameters + public static final short RADIUS_MIN_LENGTH = 20; + public static final short MAX_ATTR_VALUE_LENGTH = 253; + public static final short RADIUS_MAX_LENGTH = 4096; + + // RADIUS packet types + public static final byte RADIUS_CODE_ACCESS_REQUEST = 0x01; + public static final byte RADIUS_CODE_ACCESS_ACCEPT = 0x02; + public static final byte RADIUS_CODE_ACCESS_REJECT = 0x03; + public static final byte RADIUS_CODE_ACCOUNTING_REQUEST = 0x04; + public static final byte RADIUS_CODE_ACCOUNTING_RESPONSE = 0x05; + public static final byte RADIUS_CODE_ACCESS_CHALLENGE = 0x0b; + + private final Logger log = getLogger(getClass()); + + /** + * Default constructor. + */ + public RADIUS() { + } + + /** + * Constructs a RADIUS packet with the given code and identifier. + * + * @param code code + * @param identifier identifier + */ + public RADIUS(byte code, byte identifier) { + this.code = code; + this.identifier = identifier; + } + + /** + * Gets the code. + * + * @return code + */ + public byte getCode() { + return this.code; + } + + /** + * Sets the code. + * + * @param code code + */ + public void setCode(byte code) { + this.code = code; + } + + /** + * Gets the identifier. + * + * @return identifier + */ + public byte getIdentifier() { + return this.identifier; + } + + /** + * Sets the identifier. + * + * @param identifier identifier + */ + public void setIdentifier(byte identifier) { + this.identifier = identifier; + } + + /** + * Gets the authenticator. + * + * @return authenticator + */ + public byte[] getAuthenticator() { + return this.authenticator; + } + + /** + * Sets the authenticator. + * + * @param authenticator authenticator + */ + public void setAuthenticator(byte[] authenticator) { + this.authenticator = authenticator; + } + + /** + * Generates an authenticator code. + * + * @return the authenticator + */ + public byte[] generateAuthCode() { + new SecureRandom().nextBytes(this.authenticator); + return this.authenticator; + } + + /** + * Checks if the packet's code field is valid. + * + * @return whether the code is valid + */ + public boolean isValidCode() { + return this.code == RADIUS_CODE_ACCESS_REQUEST || + this.code == RADIUS_CODE_ACCESS_ACCEPT || + this.code == RADIUS_CODE_ACCESS_REJECT || + this.code == RADIUS_CODE_ACCOUNTING_REQUEST || + this.code == RADIUS_CODE_ACCOUNTING_RESPONSE || + this.code == RADIUS_CODE_ACCESS_CHALLENGE; + } + + /** + * Adds a message authenticator to the packet based on the given key. + * + * @param key key to generate message authenticator + * @return the messgae authenticator RADIUS attribute + */ + public RADIUSAttribute addMessageAuthenticator(String key) { + // Message-Authenticator = HMAC-MD5 (Type, Identifier, Length, + // Request Authenticator, Attributes) + // When the message integrity check is calculated the signature string + // should be considered to be sixteen octets of zero. + byte[] hashOutput = new byte[16]; + Arrays.fill(hashOutput, (byte) 0); + + RADIUSAttribute authAttribute = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH); + if (authAttribute != null) { + // If Message-Authenticator was already present, override it + this.log.warn("Attempted to add duplicate Message-Authenticator"); + authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } else { + // Else generate a new attribute padded with zeroes + authAttribute = this.setAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } + // Calculate the MD5 HMAC based on the message + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5"); + Mac mac = Mac.getInstance("HmacMD5"); + mac.init(keySpec); + hashOutput = mac.doFinal(this.serialize()); + // Update HMAC in Message-Authenticator + authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput); + } catch (Exception e) { + this.log.error("Failed to generate message authenticator: {}", e.getMessage()); + } + + return authAttribute; + } + + /** + * Checks the message authenticator in the packet with one generated from + * the given key. + * + * @param key key to generate message authenticator + * @return whether the message authenticators match or not + */ + public boolean checkMessageAuthenticator(String key) { + byte[] newHash = new byte[16]; + Arrays.fill(newHash, (byte) 0); + byte[] messageAuthenticator = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH).getValue(); + this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, newHash); + // Calculate the MD5 HMAC based on the message + try { + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5"); + Mac mac = Mac.getInstance("HmacMD5"); + mac.init(keySpec); + newHash = mac.doFinal(this.serialize()); + } catch (Exception e) { + log.error("Failed to generate message authenticator: {}", e.getMessage()); + } + this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, messageAuthenticator); + // Compare the calculated Message-Authenticator with the one in the message + return Arrays.equals(newHash, messageAuthenticator); + } + + /** + * Encapsulates an EAP packet in this RADIUS packet. + * + * @param message EAP message object to be embedded in the RADIUS + * EAP-Message attributed + */ + public void encapsulateMessage(EAP message) { + if (message.length <= MAX_ATTR_VALUE_LENGTH) { + // Use the regular serialization method as it fits into one EAP-Message attribute + this.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE, + message.serialize()); + } else { + // Segment the message into chucks and embed them in several EAP-Message attributes + short remainingLength = message.length; + byte[] messageBuffer = message.serialize(); + final ByteBuffer bb = ByteBuffer.wrap(messageBuffer); + while (bb.hasRemaining()) { + byte[] messageAttributeData; + if (remainingLength > MAX_ATTR_VALUE_LENGTH) { + // The remaining data is still too long to fit into one attribute, keep going + messageAttributeData = new byte[MAX_ATTR_VALUE_LENGTH]; + bb.get(messageAttributeData, 0, MAX_ATTR_VALUE_LENGTH); + remainingLength -= MAX_ATTR_VALUE_LENGTH; + } else { + // The remaining data fits, this will be the last chunk + messageAttributeData = new byte[remainingLength]; + bb.get(messageAttributeData, 0, remainingLength); + } + this.attributes.add(new RADIUSAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE, + (byte) (messageAttributeData.length + 2), messageAttributeData)); + + // Adding the size of the data to the total RADIUS length + this.length += (short) (messageAttributeData.length & 0xFF); + // Adding the size of the overhead attribute type and length + this.length += 2; + } + } + } + + /** + * Decapsulates an EAP packet from the RADIUS packet. + * + * @return An EAP object containing the reassembled EAP message + */ + public EAP decapsulateMessage() { + EAP message = new EAP(); + ByteArrayOutputStream messageStream = new ByteArrayOutputStream(); + // Iterating through EAP-Message attributes to concatenate their value + for (RADIUSAttribute ra : this.getAttributeList(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE)) { + try { + messageStream.write(ra.getValue()); + } catch (IOException e) { + log.error("Error while reassembling EAP message: {}", e.getMessage()); + } + } + // Assembling EAP object from the concatenated stream + message.deserialize(messageStream.toByteArray(), 0, messageStream.size()); + return message; + } + + /** + * Gets a list of attributes from the RADIUS packet. + * + * @param attrType the type field of the required attributes + * @return List of the attributes that matches the type or an empty list if there is none + */ + public ArrayList<RADIUSAttribute> getAttributeList(byte attrType) { + ArrayList<RADIUSAttribute> attrList = new ArrayList<>(); + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + attrList.add(this.attributes.get(i)); + } + } + return attrList; + } + + /** + * Gets an attribute from the RADIUS packet. + * + * @param attrType the type field of the required attribute + * @return the first attribute that matches the type or null if does not exist + */ + public RADIUSAttribute getAttribute(byte attrType) { + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + return this.attributes.get(i); + } + } + return null; + } + + /** + * Sets an attribute in the RADIUS packet. + * + * @param attrType the type field of the attribute to set + * @param value value to be set + * @return reference to the attribute object + */ + public RADIUSAttribute setAttribute(byte attrType, byte[] value) { + byte attrLength = (byte) (value.length + 2); + RADIUSAttribute newAttribute = new RADIUSAttribute(attrType, attrLength, value); + this.attributes.add(newAttribute); + this.length += (short) (attrLength & 0xFF); + return newAttribute; + } + + /** + * Updates an attribute in the RADIUS packet. + * + * @param attrType the type field of the attribute to update + * @param value the value to update to + * @return reference to the attribute object + */ + public RADIUSAttribute updateAttribute(byte attrType, byte[] value) { + for (int i = 0; i < this.attributes.size(); i++) { + if (this.attributes.get(i).getType() == attrType) { + this.length -= (short) (this.attributes.get(i).getLength() & 0xFF); + RADIUSAttribute newAttr = new RADIUSAttribute(attrType, (byte) (value.length + 2), value); + this.attributes.set(i, newAttr); + this.length += (short) (newAttr.getLength() & 0xFF); + return newAttr; + } + } + return null; + } + + /** + * Deserializer for RADIUS packets. + * + * @return deserializer + */ + public static Deserializer<RADIUS> deserializer() { + return (data, offset, length) -> { + checkInput(data, offset, length, RADIUS_MIN_LENGTH); + + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + RADIUS radius = new RADIUS(); + radius.code = bb.get(); + radius.identifier = bb.get(); + radius.length = bb.getShort(); + bb.get(radius.authenticator, 0, 16); + + checkHeaderLength(length, radius.length); + + int remainingLength = radius.length - RADIUS_MIN_LENGTH; + while (remainingLength > 0 && bb.hasRemaining()) { + + RADIUSAttribute attr = new RADIUSAttribute(); + attr.setType(bb.get()); + attr.setLength(bb.get()); + short attrLength = (short) (attr.length & 0xff); + attr.value = new byte[attrLength - 2]; + bb.get(attr.value, 0, attrLength - 2); + radius.attributes.add(attr); + remainingLength -= attr.length; + } + return radius; + }; + } + + @Override + public byte[] serialize() { + final byte[] data = new byte[this.length]; + final ByteBuffer bb = ByteBuffer.wrap(data); + + bb.put(this.code); + bb.put(this.identifier); + bb.putShort(this.length); + bb.put(this.authenticator); + for (int i = 0; i < this.attributes.size(); i++) { + RADIUSAttribute attr = this.attributes.get(i); + bb.put(attr.getType()); + bb.put(attr.getLength()); + bb.put(attr.getValue()); + } + + return data; + } + + @Override + public IPacket deserialize(final byte[] data, final int offset, + final int length) { + final ByteBuffer bb = ByteBuffer.wrap(data, offset, length); + this.code = bb.get(); + this.identifier = bb.get(); + this.length = bb.getShort(); + bb.get(this.authenticator, 0, 16); + + int remainingLength = this.length - RADIUS_MIN_LENGTH; + while (remainingLength > 0 && bb.hasRemaining()) { + RADIUSAttribute attr = new RADIUSAttribute(); + attr.setType(bb.get()); + attr.setLength(bb.get()); + short attrLength = (short) (attr.length & 0xff); + attr.value = new byte[attrLength - 2]; + bb.get(attr.value, 0, attrLength - 2); + this.attributes.add(attr); + remainingLength -= attr.length; + } + return this; + } + +} |