blob: 0a2b220bbc12da49125e7b1b0e9b85a0c238f6da [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
265 */
266 public EAP decapsulateMessage() {
267 EAP message = new EAP();
268 ByteArrayOutputStream messageStream = new ByteArrayOutputStream();
269 // Iterating through EAP-Message attributes to concatenate their value
270 for (RADIUSAttribute ra : this.getAttributeList(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE)) {
271 try {
272 messageStream.write(ra.getValue());
273 } catch (IOException e) {
274 log.error("Error while reassembling EAP message: {}", e.getMessage());
275 }
276 }
277 // Assembling EAP object from the concatenated stream
278 message.deserialize(messageStream.toByteArray(), 0, messageStream.size());
279 return message;
280 }
281
282 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700283 * Gets a list of attributes from the RADIUS packet.
284 *
285 * @param attrType the type field of the required attributes
Ari Saha79d7c252015-06-26 10:31:48 -0700286 * @return List of the attributes that matches the type or an empty list if there is none
287 */
288 public ArrayList<RADIUSAttribute> getAttributeList(byte attrType) {
289 ArrayList<RADIUSAttribute> attrList = new ArrayList<>();
290 for (int i = 0; i < this.attributes.size(); i++) {
291 if (this.attributes.get(i).getType() == attrType) {
292 attrList.add(this.attributes.get(i));
293 }
294 }
295 return attrList;
296 }
297
298 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700299 * Gets an attribute from the RADIUS packet.
300 *
301 * @param attrType the type field of the required attribute
Ari Saha79d7c252015-06-26 10:31:48 -0700302 * @return the first attribute that matches the type or null if does not exist
303 */
304 public RADIUSAttribute getAttribute(byte attrType) {
305 for (int i = 0; i < this.attributes.size(); i++) {
306 if (this.attributes.get(i).getType() == attrType) {
307 return this.attributes.get(i);
308 }
309 }
310 return null;
311 }
312
313 /**
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700314 * Sets an attribute in the RADIUS packet.
315 *
316 * @param attrType the type field of the attribute to set
Jian Li5fc14292015-12-04 11:30:46 -0800317 * @param value value to be set
Ari Saha79d7c252015-06-26 10:31:48 -0700318 * @return reference to the attribute object
319 */
320 public RADIUSAttribute setAttribute(byte attrType, byte[] value) {
321 byte attrLength = (byte) (value.length + 2);
322 RADIUSAttribute newAttribute = new RADIUSAttribute(attrType, attrLength, value);
323 this.attributes.add(newAttribute);
324 this.length += (short) (attrLength & 0xFF);
325 return newAttribute;
326 }
327
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700328 /**
329 * Updates an attribute in the RADIUS packet.
330 *
331 * @param attrType the type field of the attribute to update
Jian Li5fc14292015-12-04 11:30:46 -0800332 * @param value the value to update to
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700333 * @return reference to the attribute object
334 */
Ari Saha79d7c252015-06-26 10:31:48 -0700335 public RADIUSAttribute updateAttribute(byte attrType, byte[] value) {
336 for (int i = 0; i < this.attributes.size(); i++) {
337 if (this.attributes.get(i).getType() == attrType) {
338 this.length -= (short) (this.attributes.get(i).getLength() & 0xFF);
339 RADIUSAttribute newAttr = new RADIUSAttribute(attrType, (byte) (value.length + 2), value);
340 this.attributes.set(i, newAttr);
341 this.length += (short) (newAttr.getLength() & 0xFF);
342 return newAttr;
343 }
344 }
345 return null;
346 }
347
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700348 /**
349 * Deserializer for RADIUS packets.
350 *
351 * @return deserializer
352 */
353 public static Deserializer<RADIUS> deserializer() {
354 return (data, offset, length) -> {
355 checkInput(data, offset, length, RADIUS_MIN_LENGTH);
356
357 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
358 RADIUS radius = new RADIUS();
359 radius.code = bb.get();
360 radius.identifier = bb.get();
361 radius.length = bb.getShort();
362 bb.get(radius.authenticator, 0, 16);
363
364 checkHeaderLength(length, radius.length);
365
366 int remainingLength = radius.length - RADIUS_MIN_LENGTH;
367 while (remainingLength > 0 && bb.hasRemaining()) {
368
369 RADIUSAttribute attr = new RADIUSAttribute();
370 attr.setType(bb.get());
371 attr.setLength(bb.get());
372 short attrLength = (short) (attr.length & 0xff);
373 attr.value = new byte[attrLength - 2];
374 bb.get(attr.value, 0, attrLength - 2);
375 radius.attributes.add(attr);
Qianqian Hu33836df2015-12-23 20:44:48 +0800376 remainingLength -= attrLength;
Jonathan Hart4a60bb32015-06-30 15:31:20 -0700377 }
378 return radius;
379 };
380 }
381
Ari Saha79d7c252015-06-26 10:31:48 -0700382 @Override
383 public byte[] serialize() {
384 final byte[] data = new byte[this.length];
385 final ByteBuffer bb = ByteBuffer.wrap(data);
386
387 bb.put(this.code);
388 bb.put(this.identifier);
389 bb.putShort(this.length);
390 bb.put(this.authenticator);
391 for (int i = 0; i < this.attributes.size(); i++) {
392 RADIUSAttribute attr = this.attributes.get(i);
393 bb.put(attr.getType());
394 bb.put(attr.getLength());
395 bb.put(attr.getValue());
396 }
397
398 return data;
399 }
400
401 @Override
402 public IPacket deserialize(final byte[] data, final int offset,
403 final int length) {
404 final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
405 this.code = bb.get();
406 this.identifier = bb.get();
407 this.length = bb.getShort();
408 bb.get(this.authenticator, 0, 16);
409
410 int remainingLength = this.length - RADIUS_MIN_LENGTH;
411 while (remainingLength > 0 && bb.hasRemaining()) {
412 RADIUSAttribute attr = new RADIUSAttribute();
413 attr.setType(bb.get());
414 attr.setLength(bb.get());
415 short attrLength = (short) (attr.length & 0xff);
416 attr.value = new byte[attrLength - 2];
417 bb.get(attr.value, 0, attrLength - 2);
418 this.attributes.add(attr);
419 remainingLength -= attr.length;
420 }
421 return this;
422 }
423
Jian Li5fc14292015-12-04 11:30:46 -0800424 @Override
425 public String toString() {
426 return toStringHelper(getClass())
427 .add("code", Byte.toString(code))
428 .add("identifier", Byte.toString(identifier))
429 .add("length", Short.toString(length))
430 .add("authenticator", Arrays.toString(authenticator))
431 .toString();
432
433 // TODO: need to handle attributes
434 }
Ari Saha79d7c252015-06-26 10:31:48 -0700435}