blob: 214a83aada03d22b8e4357bc5d68ce76efc0df26 [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.packet.Data;
27import org.onlab.packet.DeserializationException;
28import org.onlab.packet.EAP;
29import org.onlab.packet.EAPOL;
30import org.onlab.packet.EthType;
31import org.onlab.packet.Ethernet;
32import org.onlab.packet.IPv4;
33import org.onlab.packet.IpAddress;
34import org.onlab.packet.MacAddress;
35import org.onlab.packet.RADIUS;
36import org.onlab.packet.RADIUSAttribute;
37import org.onlab.packet.UDP;
38import org.onlab.packet.VlanId;
Ray Milkeyea366452015-09-30 10:56:43 -070039import org.onosproject.core.CoreServiceAdapter;
40import org.onosproject.net.Annotations;
41import org.onosproject.net.Host;
42import org.onosproject.net.HostId;
43import org.onosproject.net.HostLocation;
Ray Milkeyfcb623d2015-10-01 16:48:18 -070044import org.onosproject.net.config.Config;
45import org.onosproject.net.config.NetworkConfigRegistryAdapter;
Ray Milkeyea366452015-09-30 10:56:43 -070046import 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 /**
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700176 * Mocks the network config registry.
177 */
178 @SuppressWarnings("unchecked")
179 private static final class TestNetworkConfigRegistry
180 extends NetworkConfigRegistryAdapter {
181 @Override
182 public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
183 return (C) new AAAConfig();
184 }
185 }
186
187 /**
Ray Milkeyea366452015-09-30 10:56:43 -0700188 * Sends an Ethernet packet to the process method of the Packet Processor.
189 *
190 * @param reply Ethernet packet
191 */
192 private void sendPacket(Ethernet reply) {
193 final ByteBuffer byteBuffer = ByteBuffer.wrap(reply.serialize());
194 InboundPacket inPacket = new DefaultInboundPacket(connectPoint("1", 1),
195 reply,
196 byteBuffer);
197
198 PacketContext context = new TestPacketContext(127L, inPacket, null, false);
199 packetProcessor.process(context);
200 }
201
202 /**
203 * Constructs an Ethernet packet containing a EAPOL_START Payload.
204 *
205 * @return Ethernet packet
206 */
207 private Ethernet constructSupplicantStartPacket() {
208 Ethernet eth = new Ethernet();
209 eth.setDestinationMACAddress(clientMac.toBytes());
210 eth.setSourceMACAddress(serverMac.toBytes());
211 eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
212 eth.setVlanID((short) 2);
213
214 EAP eap = new EAP(EAPOL.EAPOL_START, (byte) 1, EAPOL.EAPOL_START, null);
215
216 //eapol header
217 EAPOL eapol = new EAPOL();
218 eapol.setEapolType(EAPOL.EAPOL_START);
219 eapol.setPacketLength(eap.getLength());
220
221 //eap part
222 eapol.setPayload(eap);
223
224 eth.setPayload(eapol);
225 eth.setPad(true);
226 return eth;
227 }
228
229 /**
230 * Constructs an Ethernet packet containing identification payload.
231 *
232 * @return Ethernet packet
233 */
234 private Ethernet constructSupplicantIdentifyPacket(byte type) {
235 Ethernet eth = new Ethernet();
236 eth.setDestinationMACAddress(clientMac.toBytes());
237 eth.setSourceMACAddress(serverMac.toBytes());
238 eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
239 eth.setVlanID((short) 2);
240
241 String username = "user";
242 EAP eap = new EAP(EAP.REQUEST, (byte) 1, type,
243 username.getBytes(Charsets.US_ASCII));
244 eap.setIdentifier((byte) 1);
245
246 // eapol header
247 EAPOL eapol = new EAPOL();
248 eapol.setEapolType(EAPOL.EAPOL_PACKET);
249 eapol.setPacketLength(eap.getLength());
250
251 // eap part
252 eapol.setPayload(eap);
253
254 eth.setPayload(eapol);
255 eth.setPad(true);
256 return eth;
257 }
258
259 /**
260 * Constructs an Ethernet packet containing a RADIUS challenge
261 * packet.
262 *
263 * @param challengeCode code to use in challenge packet
264 * @param challengeType type to use in challenge packet
265 * @return Ethernet packet
266 */
267 private Ethernet constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
268 Ethernet eth = new Ethernet();
269 eth.setDestinationMACAddress(clientMac.toBytes());
270 eth.setSourceMACAddress(serverMac.toBytes());
271 eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
272 eth.setVlanID((short) 2);
273
274 IPv4 ipv4 = new IPv4();
275 ipv4.setProtocol(IPv4.PROTOCOL_UDP);
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700276 ipv4.setSourceAddress(aaa.radiusIpAddress.getHostAddress());
Ray Milkeyea366452015-09-30 10:56:43 -0700277
278 String challenge = "1234";
279
280 EAP eap = new EAP(challengeType, (byte) 1, challengeType,
281 challenge.getBytes(Charsets.US_ASCII));
282 eap.setIdentifier((byte) 1);
283
284 RADIUS radius = new RADIUS();
285 radius.setCode(challengeCode);
286
287 radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
288 challenge.getBytes(Charsets.US_ASCII));
289
290 radius.setPayload(eap);
291 radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
292 eap.serialize());
293
294 UDP udp = new UDP();
295 udp.setPayload(radius);
296 ipv4.setPayload(udp);
297
298 eth.setPayload(ipv4);
299 eth.setPad(true);
300 return eth;
301 }
302
303 /**
304 * Sets up the services required by the AAA application.
305 */
Ari Saha89831742015-06-26 10:31:48 -0700306 @Before
307 public void setUp() {
Ray Milkeyea366452015-09-30 10:56:43 -0700308 aaa = new AAA();
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700309 aaa.netCfgService = new TestNetworkConfigRegistry();
Ray Milkeyea366452015-09-30 10:56:43 -0700310 aaa.coreService = new CoreServiceAdapter();
311 aaa.packetService = new MockPacketService();
312 aaa.hostService = new MockHostService();
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700313 aaa.activate();
Ari Saha89831742015-06-26 10:31:48 -0700314 }
315
Ray Milkeyea366452015-09-30 10:56:43 -0700316 /**
317 * Tears down the AAA application.
318 */
Ari Saha89831742015-06-26 10:31:48 -0700319 @After
320 public void tearDown() {
Ray Milkeyea366452015-09-30 10:56:43 -0700321 aaa.deactivate();
Ari Saha89831742015-06-26 10:31:48 -0700322 }
323
Ray Milkeyea366452015-09-30 10:56:43 -0700324 /**
325 * Extracts the RADIUS packet from a packet sent by the supplicant.
326 *
327 * @param supplicantPacket packet sent by the supplicant
328 * @return RADIUS packet
329 * @throws DeserializationException if deserialization of the packet contents
330 * fails.
331 */
332 private RADIUS checkAndFetchRADIUSPacketFromSupplicant(Ethernet supplicantPacket)
333 throws DeserializationException {
334 assertThat(supplicantPacket, notNullValue());
335 assertThat(supplicantPacket.getVlanID(), is(VlanId.UNTAGGED));
336 assertThat(supplicantPacket.getSourceMAC().toString(), is(aaa.nasMacAddress));
337 assertThat(supplicantPacket.getDestinationMAC().toString(), is(aaa.radiusMacAddress));
338
339 assertThat(supplicantPacket.getPayload(), instanceOf(IPv4.class));
340 IPv4 ipv4 = (IPv4) supplicantPacket.getPayload();
341 assertThat(ipv4, notNullValue());
342 assertThat(IpAddress.valueOf(ipv4.getSourceAddress()).toString(),
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700343 is(aaa.nasIpAddress.getHostAddress()));
Ray Milkeyea366452015-09-30 10:56:43 -0700344 assertThat(IpAddress.valueOf(ipv4.getDestinationAddress()).toString(),
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700345 is(aaa.radiusIpAddress.getHostAddress()));
Ray Milkeyea366452015-09-30 10:56:43 -0700346
347 assertThat(ipv4.getPayload(), instanceOf(UDP.class));
348 UDP udp = (UDP) ipv4.getPayload();
349 assertThat(udp, notNullValue());
350
351 assertThat(udp.getPayload(), instanceOf(Data.class));
352 Data data = (Data) udp.getPayload();
353 RADIUS radius = RADIUS.deserializer()
354 .deserialize(data.getData(), 0, data.getData().length);
355 assertThat(radius, notNullValue());
356 return radius;
357 }
358
359 /**
360 * Checks the contents of a RADIUS packet being sent to the RADIUS server.
361 *
362 * @param radiusPacket packet to check
363 * @param code expected code
364 */
365 private void checkRadiusPacket(Ethernet radiusPacket, byte code) {
366 assertThat(radiusPacket.getVlanID(), is((short) 2));
367
368 // TODO: These address values seem wrong, but are produced by the current AAA implementation
369 assertThat(radiusPacket.getSourceMAC(), is(MacAddress.valueOf(1L)));
370 assertThat(radiusPacket.getDestinationMAC(), is(serverMac));
371
372 assertThat(radiusPacket.getPayload(), instanceOf(EAPOL.class));
373 EAPOL eapol = (EAPOL) radiusPacket.getPayload();
374 assertThat(eapol, notNullValue());
375
376 assertThat(eapol.getEapolType(), is(EAPOL.EAPOL_PACKET));
377 assertThat(eapol.getPayload(), instanceOf(EAP.class));
378 EAP eap = (EAP) eapol.getPayload();
379 assertThat(eap, notNullValue());
380 assertThat(eap.getCode(), is(code));
381 }
382
383 /**
384 * Fetches the sent packet at the given index. The requested packet
385 * must be the last packet on the list.
386 *
387 * @param index index into sent packets array
388 * @return packet
389 */
390 private Ethernet fetchPacket(int index) {
391 assertThat(savedPackets.size(), is(index + 1));
392 Ethernet eth = savedPackets.get(index);
393 assertThat(eth, notNullValue());
394 return eth;
395 }
396
397 /**
398 * Tests the authentication path through the AAA application.
399 *
400 * @throws DeserializationException if packed deserialization fails.
401 */
Ari Saha89831742015-06-26 10:31:48 -0700402 @Test
Ray Milkeyea366452015-09-30 10:56:43 -0700403 public void testAuthentication() throws DeserializationException {
Ari Saha89831742015-06-26 10:31:48 -0700404
Ray Milkeyea366452015-09-30 10:56:43 -0700405 // Our session id will be the device ID ("of:1") with the port ("1") concatenated
406 String sessionId = "of:11";
407
408 // (1) Supplicant start up
409
410 Ethernet startPacket = constructSupplicantStartPacket();
411 sendPacket(startPacket);
412
413 Ethernet responsePacket = fetchPacket(0);
414 checkRadiusPacket(responsePacket, EAP.ATTR_IDENTITY);
415
416 // (2) Supplicant identify
417
418 Ethernet identifyPacket = constructSupplicantIdentifyPacket(EAP.ATTR_IDENTITY);
419 sendPacket(identifyPacket);
420
421 Ethernet radiusIdentifyPacket = fetchPacket(1);
422
423 RADIUS radiusAccessRequest = checkAndFetchRADIUSPacketFromSupplicant(radiusIdentifyPacket);
424 assertThat(radiusAccessRequest, notNullValue());
425 assertThat(radiusAccessRequest.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
426 assertThat(new String(radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
427 is("user"));
428
429 IpAddress nasIp =
430 IpAddress.valueOf(IpAddress.Version.INET,
431 radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
432 .getValue());
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700433 assertThat(nasIp.toString(), is(aaa.nasIpAddress.getHostAddress()));
Ray Milkeyea366452015-09-30 10:56:43 -0700434
435 // State machine should have been created by now
436
437 StateMachine stateMachine =
438 StateMachine.lookupStateMachineBySessionId(sessionId);
439 assertThat(stateMachine, notNullValue());
440 assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
441
442 // (3) RADIUS MD5 challenge
443
444 Ethernet radiusCodeAccessChallengePacket =
445 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
446 sendPacket(radiusCodeAccessChallengePacket);
447
448 Ethernet radiusChallengeMD5Packet = fetchPacket(2);
449 checkRadiusPacket(radiusChallengeMD5Packet, EAP.ATTR_MD5);
450
451 // (4) Supplicant MD5 response
452
453 Ethernet md5RadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_MD5);
454 sendPacket(md5RadiusPacket);
455 Ethernet supplicantMD5ResponsePacket = fetchPacket(3);
456 RADIUS responseMd5RadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantMD5ResponsePacket);
457 assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 1));
458 assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
459
460 // State machine should be in pending state
461
462 assertThat(stateMachine, notNullValue());
463 assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
464
465 // (5) RADIUS TLS Challenge
466
467 Ethernet radiusCodeAccessChallengeTLSPacket =
468 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_TLS);
469 sendPacket(radiusCodeAccessChallengeTLSPacket);
470
471 Ethernet radiusChallengeTLSPacket = fetchPacket(4);
472 checkRadiusPacket(radiusChallengeTLSPacket, EAP.ATTR_TLS);
473
474 // (6) Supplicant TLS response
475
476 Ethernet tlsRadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_TLS);
477 sendPacket(tlsRadiusPacket);
478 Ethernet supplicantTLSResponsePacket = fetchPacket(5);
479 RADIUS responseTLSRadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantTLSResponsePacket);
480 assertThat(responseTLSRadiusPacket.getIdentifier(), is((byte) 0));
481 assertThat(responseTLSRadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
482
483 // (7) RADIUS Success
484
485 Ethernet successPacket =
486 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
487 sendPacket(successPacket);
488 Ethernet supplicantSuccessPacket = fetchPacket(6);
489
490 checkRadiusPacket(supplicantSuccessPacket, EAP.SUCCESS);
491
492 // State machine should be in authorized state
493
494 assertThat(stateMachine, notNullValue());
495 assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
Ari Saha89831742015-06-26 10:31:48 -0700496 }
Ray Milkeyfcb623d2015-10-01 16:48:18 -0700497
498
499 private static final String RADIUS_SECRET = "radiusSecret";
500 private static final String RADIUS_SWITCH = "radiusSwitch";
501 private static final String RADIUS_PORT = "radiusPort";
502
503 /**
504 * Tests the default configuration.
505 */
506 @Test
507 public void testConfig() {
508 assertThat(aaa.nasIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_NAS_IP));
509 assertThat(aaa.nasMacAddress, is(AAAConfig.DEFAULT_NAS_MAC));
510 assertThat(aaa.radiusIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_RADIUS_IP));
511 assertThat(aaa.radiusMacAddress, is(AAAConfig.DEFAULT_RADIUS_MAC));
512 }
Ari Saha89831742015-06-26 10:31:48 -0700513}