blob: e736deb9d57a2ddb80cbcd57df739c17eb9efb32 [file] [log] [blame]
Ari Saha89831742015-06-26 10:31:48 -07001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.aaa;
17
Ray Milkeyea366452015-09-30 10:56:43 -070018import java.nio.ByteBuffer;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.Set;
22
Ari Saha89831742015-06-26 10:31:48 -070023import org.junit.After;
24import org.junit.Before;
25import org.junit.Test;
Ray Milkeyea366452015-09-30 10:56:43 -070026import org.onlab.osgi.ComponentContextAdapter;
27import org.onlab.packet.Data;
28import org.onlab.packet.DeserializationException;
29import org.onlab.packet.EAP;
30import org.onlab.packet.EAPOL;
31import org.onlab.packet.EthType;
32import org.onlab.packet.Ethernet;
33import org.onlab.packet.IPv4;
34import org.onlab.packet.IpAddress;
35import org.onlab.packet.MacAddress;
36import org.onlab.packet.RADIUS;
37import org.onlab.packet.RADIUSAttribute;
38import org.onlab.packet.UDP;
39import org.onlab.packet.VlanId;
40import org.onosproject.cfg.ComponentConfigAdapter;
41import org.onosproject.core.CoreServiceAdapter;
42import org.onosproject.net.Annotations;
43import org.onosproject.net.Host;
44import org.onosproject.net.HostId;
45import org.onosproject.net.HostLocation;
46import org.onosproject.net.host.HostServiceAdapter;
47import org.onosproject.net.packet.DefaultInboundPacket;
48import org.onosproject.net.packet.DefaultPacketContext;
49import org.onosproject.net.packet.InboundPacket;
50import org.onosproject.net.packet.OutboundPacket;
51import org.onosproject.net.packet.PacketContext;
52import org.onosproject.net.packet.PacketProcessor;
53import org.onosproject.net.packet.PacketServiceAdapter;
54import org.onosproject.net.provider.ProviderId;
55
56import com.google.common.base.Charsets;
57import com.google.common.collect.ImmutableSet;
58
59import static org.hamcrest.Matchers.instanceOf;
60import static org.hamcrest.Matchers.is;
61import static org.hamcrest.Matchers.notNullValue;
62import static org.junit.Assert.assertThat;
63import static org.junit.Assert.fail;
64import static org.onosproject.net.NetTestTools.connectPoint;
Ari Saha89831742015-06-26 10:31:48 -070065
66/**
67 * Set of tests of the ONOS application component.
68 */
69public class AAATest {
70
Ray Milkeyea366452015-09-30 10:56:43 -070071 MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
72 MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a");
Ari Saha89831742015-06-26 10:31:48 -070073
Ray Milkeyea366452015-09-30 10:56:43 -070074 PacketProcessor packetProcessor;
75 private AAA aaa;
76 List<Ethernet> savedPackets = new LinkedList<>();
77
78 /**
79 * Saves the given packet onto the saved packets list.
80 *
81 * @param eth packet to save
82 */
83 private void savePacket(Ethernet eth) {
84 savedPackets.add(eth);
85 }
86
87 /**
88 * Keeps a reference to the PacketProcessor and saves the OutboundPackets.
89 */
90 private class MockPacketService extends PacketServiceAdapter {
91
92 @Override
93 public void addProcessor(PacketProcessor processor, int priority) {
94 packetProcessor = processor;
95 }
96
97 @Override
98 public void emit(OutboundPacket packet) {
99 try {
100 Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(),
101 0, packet.data().array().length);
102 savePacket(eth);
103 } catch (Exception e) {
104 fail(e.getMessage());
105 }
106 }
107 }
108
109 /**
110 * Mocks the DefaultPacketContext.
111 */
112 private final class TestPacketContext extends DefaultPacketContext {
113
114 private TestPacketContext(long time, InboundPacket inPkt,
115 OutboundPacket outPkt, boolean block) {
116 super(time, inPkt, outPkt, block);
117 }
118
119 @Override
120 public void send() {
121 // We don't send anything out.
122 }
123 }
124
125 /**
126 * Mocks a host to allow locating the Radius server.
127 */
128 private static final class MockHost implements Host {
129 @Override
130 public HostId id() {
131 return null;
132 }
133
134 @Override
135 public MacAddress mac() {
136 return null;
137 }
138
139 @Override
140 public VlanId vlan() {
141 return VlanId.vlanId(VlanId.UNTAGGED);
142 }
143
144 @Override
145 public Set<IpAddress> ipAddresses() {
146 return null;
147 }
148
149 @Override
150 public HostLocation location() {
151 return null;
152 }
153
154 @Override
155 public Annotations annotations() {
156 return null;
157 }
158
159 @Override
160 public ProviderId providerId() {
161 return null;
162 }
163 }
164
165 /**
166 * Mocks the Host service.
167 */
168 private static final class MockHostService extends HostServiceAdapter {
169 @Override
170 public Set<Host> getHostsByIp(IpAddress ip) {
171 return ImmutableSet.of(new MockHost());
172 }
173 }
174
175 /**
176 * Sends an Ethernet packet to the process method of the Packet Processor.
177 *
178 * @param reply Ethernet packet
179 */
180 private void sendPacket(Ethernet reply) {
181 final ByteBuffer byteBuffer = ByteBuffer.wrap(reply.serialize());
182 InboundPacket inPacket = new DefaultInboundPacket(connectPoint("1", 1),
183 reply,
184 byteBuffer);
185
186 PacketContext context = new TestPacketContext(127L, inPacket, null, false);
187 packetProcessor.process(context);
188 }
189
190 /**
191 * Constructs an Ethernet packet containing a EAPOL_START Payload.
192 *
193 * @return Ethernet packet
194 */
195 private Ethernet constructSupplicantStartPacket() {
196 Ethernet eth = new Ethernet();
197 eth.setDestinationMACAddress(clientMac.toBytes());
198 eth.setSourceMACAddress(serverMac.toBytes());
199 eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
200 eth.setVlanID((short) 2);
201
202 EAP eap = new EAP(EAPOL.EAPOL_START, (byte) 1, EAPOL.EAPOL_START, null);
203
204 //eapol header
205 EAPOL eapol = new EAPOL();
206 eapol.setEapolType(EAPOL.EAPOL_START);
207 eapol.setPacketLength(eap.getLength());
208
209 //eap part
210 eapol.setPayload(eap);
211
212 eth.setPayload(eapol);
213 eth.setPad(true);
214 return eth;
215 }
216
217 /**
218 * Constructs an Ethernet packet containing identification payload.
219 *
220 * @return Ethernet packet
221 */
222 private Ethernet constructSupplicantIdentifyPacket(byte type) {
223 Ethernet eth = new Ethernet();
224 eth.setDestinationMACAddress(clientMac.toBytes());
225 eth.setSourceMACAddress(serverMac.toBytes());
226 eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
227 eth.setVlanID((short) 2);
228
229 String username = "user";
230 EAP eap = new EAP(EAP.REQUEST, (byte) 1, type,
231 username.getBytes(Charsets.US_ASCII));
232 eap.setIdentifier((byte) 1);
233
234 // eapol header
235 EAPOL eapol = new EAPOL();
236 eapol.setEapolType(EAPOL.EAPOL_PACKET);
237 eapol.setPacketLength(eap.getLength());
238
239 // eap part
240 eapol.setPayload(eap);
241
242 eth.setPayload(eapol);
243 eth.setPad(true);
244 return eth;
245 }
246
247 /**
248 * Constructs an Ethernet packet containing a RADIUS challenge
249 * packet.
250 *
251 * @param challengeCode code to use in challenge packet
252 * @param challengeType type to use in challenge packet
253 * @return Ethernet packet
254 */
255 private Ethernet constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
256 Ethernet eth = new Ethernet();
257 eth.setDestinationMACAddress(clientMac.toBytes());
258 eth.setSourceMACAddress(serverMac.toBytes());
259 eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
260 eth.setVlanID((short) 2);
261
262 IPv4 ipv4 = new IPv4();
263 ipv4.setProtocol(IPv4.PROTOCOL_UDP);
264 ipv4.setSourceAddress("127.0.0.1");
265
266 String challenge = "1234";
267
268 EAP eap = new EAP(challengeType, (byte) 1, challengeType,
269 challenge.getBytes(Charsets.US_ASCII));
270 eap.setIdentifier((byte) 1);
271
272 RADIUS radius = new RADIUS();
273 radius.setCode(challengeCode);
274
275 radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
276 challenge.getBytes(Charsets.US_ASCII));
277
278 radius.setPayload(eap);
279 radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
280 eap.serialize());
281
282 UDP udp = new UDP();
283 udp.setPayload(radius);
284 ipv4.setPayload(udp);
285
286 eth.setPayload(ipv4);
287 eth.setPad(true);
288 return eth;
289 }
290
291 /**
292 * Sets up the services required by the AAA application.
293 */
Ari Saha89831742015-06-26 10:31:48 -0700294 @Before
295 public void setUp() {
Ray Milkeyea366452015-09-30 10:56:43 -0700296 aaa = new AAA();
297 aaa.cfgService = new ComponentConfigAdapter();
298 aaa.coreService = new CoreServiceAdapter();
299 aaa.packetService = new MockPacketService();
300 aaa.hostService = new MockHostService();
301 aaa.activate(new ComponentContextAdapter());
Ari Saha89831742015-06-26 10:31:48 -0700302 }
303
Ray Milkeyea366452015-09-30 10:56:43 -0700304 /**
305 * Tears down the AAA application.
306 */
Ari Saha89831742015-06-26 10:31:48 -0700307 @After
308 public void tearDown() {
Ray Milkeyea366452015-09-30 10:56:43 -0700309 aaa.deactivate();
Ari Saha89831742015-06-26 10:31:48 -0700310 }
311
Ray Milkeyea366452015-09-30 10:56:43 -0700312 /**
313 * Extracts the RADIUS packet from a packet sent by the supplicant.
314 *
315 * @param supplicantPacket packet sent by the supplicant
316 * @return RADIUS packet
317 * @throws DeserializationException if deserialization of the packet contents
318 * fails.
319 */
320 private RADIUS checkAndFetchRADIUSPacketFromSupplicant(Ethernet supplicantPacket)
321 throws DeserializationException {
322 assertThat(supplicantPacket, notNullValue());
323 assertThat(supplicantPacket.getVlanID(), is(VlanId.UNTAGGED));
324 assertThat(supplicantPacket.getSourceMAC().toString(), is(aaa.nasMacAddress));
325 assertThat(supplicantPacket.getDestinationMAC().toString(), is(aaa.radiusMacAddress));
326
327 assertThat(supplicantPacket.getPayload(), instanceOf(IPv4.class));
328 IPv4 ipv4 = (IPv4) supplicantPacket.getPayload();
329 assertThat(ipv4, notNullValue());
330 assertThat(IpAddress.valueOf(ipv4.getSourceAddress()).toString(),
331 is(aaa.nasIpAddress));
332 assertThat(IpAddress.valueOf(ipv4.getDestinationAddress()).toString(),
333 is(aaa.radiusIpAddress));
334
335 assertThat(ipv4.getPayload(), instanceOf(UDP.class));
336 UDP udp = (UDP) ipv4.getPayload();
337 assertThat(udp, notNullValue());
338
339 assertThat(udp.getPayload(), instanceOf(Data.class));
340 Data data = (Data) udp.getPayload();
341 RADIUS radius = RADIUS.deserializer()
342 .deserialize(data.getData(), 0, data.getData().length);
343 assertThat(radius, notNullValue());
344 return radius;
345 }
346
347 /**
348 * Checks the contents of a RADIUS packet being sent to the RADIUS server.
349 *
350 * @param radiusPacket packet to check
351 * @param code expected code
352 */
353 private void checkRadiusPacket(Ethernet radiusPacket, byte code) {
354 assertThat(radiusPacket.getVlanID(), is((short) 2));
355
356 // TODO: These address values seem wrong, but are produced by the current AAA implementation
357 assertThat(radiusPacket.getSourceMAC(), is(MacAddress.valueOf(1L)));
358 assertThat(radiusPacket.getDestinationMAC(), is(serverMac));
359
360 assertThat(radiusPacket.getPayload(), instanceOf(EAPOL.class));
361 EAPOL eapol = (EAPOL) radiusPacket.getPayload();
362 assertThat(eapol, notNullValue());
363
364 assertThat(eapol.getEapolType(), is(EAPOL.EAPOL_PACKET));
365 assertThat(eapol.getPayload(), instanceOf(EAP.class));
366 EAP eap = (EAP) eapol.getPayload();
367 assertThat(eap, notNullValue());
368 assertThat(eap.getCode(), is(code));
369 }
370
371 /**
372 * Fetches the sent packet at the given index. The requested packet
373 * must be the last packet on the list.
374 *
375 * @param index index into sent packets array
376 * @return packet
377 */
378 private Ethernet fetchPacket(int index) {
379 assertThat(savedPackets.size(), is(index + 1));
380 Ethernet eth = savedPackets.get(index);
381 assertThat(eth, notNullValue());
382 return eth;
383 }
384
385 /**
386 * Tests the authentication path through the AAA application.
387 *
388 * @throws DeserializationException if packed deserialization fails.
389 */
Ari Saha89831742015-06-26 10:31:48 -0700390 @Test
Ray Milkeyea366452015-09-30 10:56:43 -0700391 public void testAuthentication() throws DeserializationException {
Ari Saha89831742015-06-26 10:31:48 -0700392
Ray Milkeyea366452015-09-30 10:56:43 -0700393 // Our session id will be the device ID ("of:1") with the port ("1") concatenated
394 String sessionId = "of:11";
395
396 // (1) Supplicant start up
397
398 Ethernet startPacket = constructSupplicantStartPacket();
399 sendPacket(startPacket);
400
401 Ethernet responsePacket = fetchPacket(0);
402 checkRadiusPacket(responsePacket, EAP.ATTR_IDENTITY);
403
404 // (2) Supplicant identify
405
406 Ethernet identifyPacket = constructSupplicantIdentifyPacket(EAP.ATTR_IDENTITY);
407 sendPacket(identifyPacket);
408
409 Ethernet radiusIdentifyPacket = fetchPacket(1);
410
411 RADIUS radiusAccessRequest = checkAndFetchRADIUSPacketFromSupplicant(radiusIdentifyPacket);
412 assertThat(radiusAccessRequest, notNullValue());
413 assertThat(radiusAccessRequest.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
414 assertThat(new String(radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
415 is("user"));
416
417 IpAddress nasIp =
418 IpAddress.valueOf(IpAddress.Version.INET,
419 radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
420 .getValue());
421 assertThat(nasIp.toString(), is("127.0.0.1"));
422
423 // State machine should have been created by now
424
425 StateMachine stateMachine =
426 StateMachine.lookupStateMachineBySessionId(sessionId);
427 assertThat(stateMachine, notNullValue());
428 assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
429
430 // (3) RADIUS MD5 challenge
431
432 Ethernet radiusCodeAccessChallengePacket =
433 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
434 sendPacket(radiusCodeAccessChallengePacket);
435
436 Ethernet radiusChallengeMD5Packet = fetchPacket(2);
437 checkRadiusPacket(radiusChallengeMD5Packet, EAP.ATTR_MD5);
438
439 // (4) Supplicant MD5 response
440
441 Ethernet md5RadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_MD5);
442 sendPacket(md5RadiusPacket);
443 Ethernet supplicantMD5ResponsePacket = fetchPacket(3);
444 RADIUS responseMd5RadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantMD5ResponsePacket);
445 assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 1));
446 assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
447
448 // State machine should be in pending state
449
450 assertThat(stateMachine, notNullValue());
451 assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
452
453 // (5) RADIUS TLS Challenge
454
455 Ethernet radiusCodeAccessChallengeTLSPacket =
456 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_TLS);
457 sendPacket(radiusCodeAccessChallengeTLSPacket);
458
459 Ethernet radiusChallengeTLSPacket = fetchPacket(4);
460 checkRadiusPacket(radiusChallengeTLSPacket, EAP.ATTR_TLS);
461
462 // (6) Supplicant TLS response
463
464 Ethernet tlsRadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_TLS);
465 sendPacket(tlsRadiusPacket);
466 Ethernet supplicantTLSResponsePacket = fetchPacket(5);
467 RADIUS responseTLSRadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantTLSResponsePacket);
468 assertThat(responseTLSRadiusPacket.getIdentifier(), is((byte) 0));
469 assertThat(responseTLSRadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
470
471 // (7) RADIUS Success
472
473 Ethernet successPacket =
474 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
475 sendPacket(successPacket);
476 Ethernet supplicantSuccessPacket = fetchPacket(6);
477
478 checkRadiusPacket(supplicantSuccessPacket, EAP.SUCCESS);
479
480 // State machine should be in authorized state
481
482 assertThat(stateMachine, notNullValue());
483 assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
Ari Saha89831742015-06-26 10:31:48 -0700484 }
Ari Saha89831742015-06-26 10:31:48 -0700485}