blob: 3f3b517591bf7222e05928378d0ff6a3c69ef564 [file] [log] [blame]
Ari Saha79d7c252015-06-26 10:31:48 -07001/*
2 *
3 * * Copyright 2015 AT&T Foundry
4 * *
5 * * Licensed under the Apache License, Version 2.0 (the "License");
6 * * you may not use this file except in compliance with the License.
7 * * You may obtain a copy of the License at
8 * *
9 * * http://www.apache.org/licenses/LICENSE-2.0
10 * *
11 * * Unless required by applicable law or agreed to in writing, software
12 * * distributed under the License is distributed on an "AS IS" BASIS,
13 * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * * See the License for the specific language governing permissions and
15 * * limitations under the License.
16 *
17 */
18
Jonathan Hart4a60bb32015-06-30 15:31:20 -070019package org.onlab.packet;
Ari Saha79d7c252015-06-26 10:31:48 -070020
Ari Saha79d7c252015-06-26 10:31:48 -070021import org.slf4j.Logger;
22
23import javax.crypto.Mac;
24import javax.crypto.spec.SecretKeySpec;
25import java.io.ByteArrayOutputStream;
26import java.io.IOException;
27import java.nio.ByteBuffer;
28import java.security.SecureRandom;
29import java.util.ArrayList;
30import java.util.Arrays;
Jonathan Hart4a60bb32015-06-30 15:31:20 -070031import java.util.List;
Ari Saha79d7c252015-06-26 10:31:48 -070032
Jian Li5fc14292015-12-04 11:30:46 -080033import static com.google.common.base.MoreObjects.toStringHelper;
Jonathan Hart4a60bb32015-06-30 15:31:20 -070034import static org.onlab.packet.PacketUtils.checkHeaderLength;
35import static org.onlab.packet.PacketUtils.checkInput;
Ari Saha79d7c252015-06-26 10:31:48 -070036import static org.slf4j.LoggerFactory.getLogger;
37
38/**
Jonathan Hart4a60bb32015-06-30 15:31:20 -070039 * RADIUS packet.
Ari Saha79d7c252015-06-26 10:31:48 -070040 */
41public class RADIUS extends BasePacket {
42 protected byte code;
43 protected byte identifier;
44 protected short length = RADIUS_MIN_LENGTH;
45 protected byte[] authenticator = new byte[16];
Jonathan Hart4a60bb32015-06-30 15:31:20 -070046 protected List<RADIUSAttribute> attributes = new ArrayList<>();
Ari Saha79d7c252015-06-26 10:31:48 -070047
Jonathan Hart4a60bb32015-06-30 15:31:20 -070048 // RADIUS parameters
Ari Saha79d7c252015-06-26 10:31:48 -070049 public static final short RADIUS_MIN_LENGTH = 20;
50 public static final short MAX_ATTR_VALUE_LENGTH = 253;
51 public static final short RADIUS_MAX_LENGTH = 4096;
52
Jonathan Hart4a60bb32015-06-30 15:31:20 -070053 // RADIUS packet types
Ari Saha79d7c252015-06-26 10:31:48 -070054 public static final byte RADIUS_CODE_ACCESS_REQUEST = 0x01;
55 public static final byte RADIUS_CODE_ACCESS_ACCEPT = 0x02;
56 public static final byte RADIUS_CODE_ACCESS_REJECT = 0x03;
57 public static final byte RADIUS_CODE_ACCOUNTING_REQUEST = 0x04;
58 public static final byte RADIUS_CODE_ACCOUNTING_RESPONSE = 0x05;
59 public static final byte RADIUS_CODE_ACCESS_CHALLENGE = 0x0b;
60
61 private final Logger log = getLogger(getClass());
62
Jonathan Hart4a60bb32015-06-30 15:31:20 -070063 /**
64 * Default constructor.
65 */
Ari Saha79d7c252015-06-26 10:31:48 -070066 public RADIUS() {
67 }
68
Jonathan Hart4a60bb32015-06-30 15:31:20 -070069 /**
70 * Constructs a RADIUS packet with the given code and identifier.
71 *
Jian Li5fc14292015-12-04 11:30:46 -080072 * @param code code
Jonathan Hart4a60bb32015-06-30 15:31:20 -070073 * @param identifier identifier
74 */
Ari Saha79d7c252015-06-26 10:31:48 -070075 public RADIUS(byte code, byte identifier) {
76 this.code = code;
77 this.identifier = identifier;
78 }
79
Jonathan Hart4a60bb32015-06-30 15:31:20 -070080 /**
81 * Gets the code.
82 *
83 * @return code
84 */
Ari Saha79d7c252015-06-26 10:31:48 -070085 public byte getCode() {
86 return this.code;
87 }
88
Jonathan Hart4a60bb32015-06-30 15:31:20 -070089 /**
90 * Sets the code.
91 *
92 * @param code code
93 */
Ari Saha79d7c252015-06-26 10:31:48 -070094 public void setCode(byte code) {
95 this.code = code;
96 }
97
Jonathan Hart4a60bb32015-06-30 15:31:20 -070098 /**
99 * Gets the identifier.
100 *
101 * @return identifier
102 */
Ari Saha79d7c252015-06-26 10:31:48 -0700103 public byte getIdentifier() {
104 return this.identifier;
105 }
106
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700107 /**
108 * Sets the identifier.
109 *
110 * @param identifier identifier
111 */
Ari Saha79d7c252015-06-26 10:31:48 -0700112 public void setIdentifier(byte identifier) {
113 this.identifier = identifier;
114 }
115
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700116 /**
117 * Gets the authenticator.
118 *
119 * @return authenticator
120 */
Ari Saha79d7c252015-06-26 10:31:48 -0700121 public byte[] getAuthenticator() {
122 return this.authenticator;
123 }
124
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700125 /**
126 * Sets the authenticator.
127 *
128 * @param authenticator authenticator
129 */
130 public void setAuthenticator(byte[] authenticator) {
131 this.authenticator = authenticator;
Ari Saha79d7c252015-06-26 10:31:48 -0700132 }
133
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700134 /**
135 * Generates an authenticator code.
136 *
137 * @return the authenticator
138 */
Ari Saha79d7c252015-06-26 10:31:48 -0700139 public byte[] generateAuthCode() {
140 new SecureRandom().nextBytes(this.authenticator);
141 return this.authenticator;
142 }
143
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700144 /**
145 * Checks if the packet's code field is valid.
146 *
147 * @return whether the code is valid
148 */
Ari Saha79d7c252015-06-26 10:31:48 -0700149 public boolean isValidCode() {
150 return this.code == RADIUS_CODE_ACCESS_REQUEST ||
151 this.code == RADIUS_CODE_ACCESS_ACCEPT ||
152 this.code == RADIUS_CODE_ACCESS_REJECT ||
153 this.code == RADIUS_CODE_ACCOUNTING_REQUEST ||
154 this.code == RADIUS_CODE_ACCOUNTING_RESPONSE ||
155 this.code == RADIUS_CODE_ACCESS_CHALLENGE;
156 }
157
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700158 /**
159 * Adds a message authenticator to the packet based on the given key.
160 *
161 * @param key key to generate message authenticator
162 * @return the messgae authenticator RADIUS attribute
163 */
Ari Saha79d7c252015-06-26 10:31:48 -0700164 public RADIUSAttribute addMessageAuthenticator(String key) {
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700165 // Message-Authenticator = HMAC-MD5 (Type, Identifier, Length,
166 // Request Authenticator, Attributes)
167 // When the message integrity check is calculated the signature string
168 // should be considered to be sixteen octets of zero.
Ari Saha79d7c252015-06-26 10:31:48 -0700169 byte[] hashOutput = new byte[16];
170 Arrays.fill(hashOutput, (byte) 0);
171
172 RADIUSAttribute authAttribute = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH);
173 if (authAttribute != null) {
174 // If Message-Authenticator was already present, override it
175 this.log.warn("Attempted to add duplicate Message-Authenticator");
176 authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput);
177 } else {
178 // Else generate a new attribute padded with zeroes
179 authAttribute = this.setAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput);
180 }
181 // Calculate the MD5 HMAC based on the message
182 try {
183 SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5");
184 Mac mac = Mac.getInstance("HmacMD5");
185 mac.init(keySpec);
186 hashOutput = mac.doFinal(this.serialize());
187 // Update HMAC in Message-Authenticator
188 authAttribute = this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, hashOutput);
189 } catch (Exception e) {
190 this.log.error("Failed to generate message authenticator: {}", e.getMessage());
191 }
192
193 return authAttribute;
194 }
195
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700196 /**
197 * Checks the message authenticator in the packet with one generated from
198 * the given key.
199 *
200 * @param key key to generate message authenticator
201 * @return whether the message authenticators match or not
202 */
Ari Saha79d7c252015-06-26 10:31:48 -0700203 public boolean checkMessageAuthenticator(String key) {
204 byte[] newHash = new byte[16];
205 Arrays.fill(newHash, (byte) 0);
206 byte[] messageAuthenticator = this.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH).getValue();
207 this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, newHash);
208 // Calculate the MD5 HMAC based on the message
209 try {
210 SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacMD5");
211 Mac mac = Mac.getInstance("HmacMD5");
212 mac.init(keySpec);
213 newHash = mac.doFinal(this.serialize());
214 } catch (Exception e) {
215 log.error("Failed to generate message authenticator: {}", e.getMessage());
216 }
217 this.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, messageAuthenticator);
218 // Compare the calculated Message-Authenticator with the one in the message
219 return Arrays.equals(newHash, messageAuthenticator);
220 }
221
222 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700223 * Encapsulates an EAP packet in this RADIUS packet.
224 *
225 * @param message EAP message object to be embedded in the RADIUS
226 * EAP-Message attributed
Ari Saha79d7c252015-06-26 10:31:48 -0700227 */
228 public void encapsulateMessage(EAP message) {
229 if (message.length <= MAX_ATTR_VALUE_LENGTH) {
230 // Use the regular serialization method as it fits into one EAP-Message attribute
231 this.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
232 message.serialize());
233 } else {
234 // Segment the message into chucks and embed them in several EAP-Message attributes
235 short remainingLength = message.length;
236 byte[] messageBuffer = message.serialize();
237 final ByteBuffer bb = ByteBuffer.wrap(messageBuffer);
238 while (bb.hasRemaining()) {
239 byte[] messageAttributeData;
240 if (remainingLength > MAX_ATTR_VALUE_LENGTH) {
241 // The remaining data is still too long to fit into one attribute, keep going
242 messageAttributeData = new byte[MAX_ATTR_VALUE_LENGTH];
243 bb.get(messageAttributeData, 0, MAX_ATTR_VALUE_LENGTH);
244 remainingLength -= MAX_ATTR_VALUE_LENGTH;
245 } else {
246 // The remaining data fits, this will be the last chunk
247 messageAttributeData = new byte[remainingLength];
248 bb.get(messageAttributeData, 0, remainingLength);
249 }
250 this.attributes.add(new RADIUSAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
251 (byte) (messageAttributeData.length + 2), messageAttributeData));
252
253 // Adding the size of the data to the total RADIUS length
254 this.length += (short) (messageAttributeData.length & 0xFF);
255 // Adding the size of the overhead attribute type and length
256 this.length += 2;
257 }
258 }
259 }
260
261 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700262 * Decapsulates an EAP packet from the RADIUS packet.
263 *
Ari Saha79d7c252015-06-26 10:31:48 -0700264 * @return An EAP object containing the reassembled EAP message
Terje Mikal Mjelde34cc8e02018-03-09 08:31:14 +0100265 * @throws DeserializationException if packet deserialization fails
Ari Saha79d7c252015-06-26 10:31:48 -0700266 */
Terje Mikal Mjelde34cc8e02018-03-09 08:31:14 +0100267 public EAP decapsulateMessage() throws DeserializationException {
Ari Saha79d7c252015-06-26 10:31:48 -0700268 EAP message = new EAP();
269 ByteArrayOutputStream messageStream = new ByteArrayOutputStream();
270 // Iterating through EAP-Message attributes to concatenate their value
271 for (RADIUSAttribute ra : this.getAttributeList(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE)) {
272 try {
273 messageStream.write(ra.getValue());
274 } catch (IOException e) {
275 log.error("Error while reassembling EAP message: {}", e.getMessage());
276 }
277 }
278 // Assembling EAP object from the concatenated stream
Terje Mikal Mjelde34cc8e02018-03-09 08:31:14 +0100279 message = EAP.deserializer().deserialize(messageStream.toByteArray(), 0, messageStream.size());
Ari Saha79d7c252015-06-26 10:31:48 -0700280 return message;
281 }
282
283 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700284 * Gets a list of attributes from the RADIUS packet.
285 *
286 * @param attrType the type field of the required attributes
Ari Saha79d7c252015-06-26 10:31:48 -0700287 * @return List of the attributes that matches the type or an empty list if there is none
288 */
289 public ArrayList<RADIUSAttribute> getAttributeList(byte attrType) {
290 ArrayList<RADIUSAttribute> attrList = new ArrayList<>();
291 for (int i = 0; i < this.attributes.size(); i++) {
292 if (this.attributes.get(i).getType() == attrType) {
293 attrList.add(this.attributes.get(i));
294 }
295 }
296 return attrList;
297 }
298
299 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700300 * Gets an attribute from the RADIUS packet.
301 *
302 * @param attrType the type field of the required attribute
Ari Saha79d7c252015-06-26 10:31:48 -0700303 * @return the first attribute that matches the type or null if does not exist
304 */
305 public RADIUSAttribute getAttribute(byte attrType) {
306 for (int i = 0; i < this.attributes.size(); i++) {
307 if (this.attributes.get(i).getType() == attrType) {
308 return this.attributes.get(i);
309 }
310 }
311 return null;
312 }
313
314 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700315 * Sets an attribute in the RADIUS packet.
316 *
317 * @param attrType the type field of the attribute to set
Jian Li5fc14292015-12-04 11:30:46 -0800318 * @param value value to be set
Ari Saha79d7c252015-06-26 10:31:48 -0700319 * @return reference to the attribute object
320 */
321 public RADIUSAttribute setAttribute(byte attrType, byte[] value) {
322 byte attrLength = (byte) (value.length + 2);
323 RADIUSAttribute newAttribute = new RADIUSAttribute(attrType, attrLength, value);
324 this.attributes.add(newAttribute);
325 this.length += (short) (attrLength & 0xFF);
326 return newAttribute;
327 }
328
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700329 /**
330 * Updates an attribute in the RADIUS packet.
331 *
332 * @param attrType the type field of the attribute to update
Jian Li5fc14292015-12-04 11:30:46 -0800333 * @param value the value to update to
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700334 * @return reference to the attribute object
335 */
Ari Saha79d7c252015-06-26 10:31:48 -0700336 public RADIUSAttribute updateAttribute(byte attrType, byte[] value) {
337 for (int i = 0; i < this.attributes.size(); i++) {
338 if (this.attributes.get(i).getType() == attrType) {
339 this.length -= (short) (this.attributes.get(i).getLength() & 0xFF);
340 RADIUSAttribute newAttr = new RADIUSAttribute(attrType, (byte) (value.length + 2), value);
341 this.attributes.set(i, newAttr);
342 this.length += (short) (newAttr.getLength() & 0xFF);
343 return newAttr;
344 }
345 }
346 return null;
347 }
348
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700349 /**
350 * Deserializer for RADIUS packets.
351 *
352 * @return deserializer
353 */
354 public static Deserializer<RADIUS> deserializer() {
355 return (data, offset, length) -> {
356 checkInput(data, offset, length, RADIUS_MIN_LENGTH);
357
358 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
359 RADIUS radius = new RADIUS();
360 radius.code = bb.get();
361 radius.identifier = bb.get();
362 radius.length = bb.getShort();
363 bb.get(radius.authenticator, 0, 16);
364
365 checkHeaderLength(length, radius.length);
366
367 int remainingLength = radius.length - RADIUS_MIN_LENGTH;
368 while (remainingLength > 0 && bb.hasRemaining()) {
369
370 RADIUSAttribute attr = new RADIUSAttribute();
371 attr.setType(bb.get());
372 attr.setLength(bb.get());
373 short attrLength = (short) (attr.length & 0xff);
374 attr.value = new byte[attrLength - 2];
375 bb.get(attr.value, 0, attrLength - 2);
376 radius.attributes.add(attr);
Qianqian Hu33836df2015-12-23 20:44:48 +0800377 remainingLength -= attrLength;
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700378 }
379 return radius;
380 };
381 }
382
Ari Saha79d7c252015-06-26 10:31:48 -0700383 @Override
384 public byte[] serialize() {
385 final byte[] data = new byte[this.length];
386 final ByteBuffer bb = ByteBuffer.wrap(data);
387
388 bb.put(this.code);
389 bb.put(this.identifier);
390 bb.putShort(this.length);
391 bb.put(this.authenticator);
392 for (int i = 0; i < this.attributes.size(); i++) {
393 RADIUSAttribute attr = this.attributes.get(i);
394 bb.put(attr.getType());
395 bb.put(attr.getLength());
396 bb.put(attr.getValue());
397 }
398
399 return data;
400 }
401
Ari Saha79d7c252015-06-26 10:31:48 -0700402
Jian Li5fc14292015-12-04 11:30:46 -0800403 @Override
404 public String toString() {
405 return toStringHelper(getClass())
406 .add("code", Byte.toString(code))
407 .add("identifier", Byte.toString(identifier))
408 .add("length", Short.toString(length))
409 .add("authenticator", Arrays.toString(authenticator))
410 .toString();
411
412 // TODO: need to handle attributes
413 }
Ari Saha79d7c252015-06-26 10:31:48 -0700414}