WIP : IEEE 802.1x KaY support
Change-Id: I16ac0af06e33950b1585728bdd41e9091853e413
diff --git a/pom.xml b/pom.xml
index 515bd24..339e615 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,8 +22,8 @@
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onos-dependencies</artifactId>
- <version>1.10.9</version>
- <relativePath></relativePath>
+ <version>1.12.1-SNAPSHOT</version>
+ <relativePath>../onos/lib/pom.xml</relativePath>
</parent>
<groupId>org.opencord</groupId>
@@ -35,7 +35,7 @@
<properties>
<onos.app.name>org.opencord.aaa</onos.app.name>
- <onos.version>1.10.9</onos.version>
+ <onos.version>1.12.1-SNAPSHOT</onos.version>
<onos.app.title>AAA App</onos.app.title>
<onos.app.category>Security</onos.app.category>
<onos.app.url>http://opencord.org</onos.app.url>
@@ -44,8 +44,8 @@
org.onosproject.olt,
org.opencord.sadis
</onos.app.requires>
- <sadis.api.version>2.0.0</sadis.api.version>
- <olt.api.version>1.3.1</olt.api.version>
+ <sadis.api.version>2.0.1-SNAPSHOT</sadis.api.version>
+ <olt.api.version>1.3.2-SNAPSHOT</olt.api.version>
</properties>
<dependencies>
@@ -86,6 +86,28 @@
<artifactId>onlab-junit</artifactId>
<version>${onos.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-protocols-netconf-api</artifactId>
+ <version>1.8.5-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-protocols-netconf-ctl</artifactId>
+ <version>1.8.5-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-common</artifactId>
+ <version>${onos.version}</version>
+ </dependency>
</dependencies>
diff --git a/src/main/java/org/opencord/aaa/AaaConfig.java b/src/main/java/org/opencord/aaa/AaaConfig.java
index 8ffdf00..99d1589 100644
--- a/src/main/java/org/opencord/aaa/AaaConfig.java
+++ b/src/main/java/org/opencord/aaa/AaaConfig.java
@@ -15,9 +15,6 @@
*/
package org.opencord.aaa;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-
import com.google.common.collect.ImmutableSet;
import org.onosproject.core.ApplicationId;
@@ -54,7 +51,8 @@
private static final String PACKET_CUSTOMIZER = "packetCustomizer";
// RADIUS server IP address
- protected static final String DEFAULT_RADIUS_IP = "10.128.10.4";
+ //protected static final String DEFAULT_RADIUS_IP = "10.128.10.4";
+ protected static final String DEFAULT_RADIUS_IP = "192.168.1.14";
// RADIUS MAC address
protected static final String DEFAULT_RADIUS_MAC = "00:00:00:00:01:10";
@@ -66,7 +64,8 @@
protected static final String DEFAULT_NAS_MAC = "00:00:00:00:10:01";
// RADIUS server shared secret
- protected static final String DEFAULT_RADIUS_SECRET = "ONOSecret";
+ //protected static final String DEFAULT_RADIUS_SECRET = "ONOSecret";
+ protected static final String DEFAULT_RADIUS_SECRET = "karaf";
// Radius Server UDP Port Number
protected static final String DEFAULT_RADIUS_SERVER_PORT = "1812";
@@ -83,7 +82,6 @@
// Packet Customizer Default value
protected static final String DEFAULT_PACKET_CUSTOMIZER = "default";
-
/**
* Gets the value of a string property, protecting for an empty
* JSON object.
@@ -271,7 +269,7 @@
if (!object.has(RADIUS_SERVER_CONNECTPOINTS)) {
return ImmutableSet.of();
}
-
+ /* Following portion to be rewritten with out Guava ImmutableSet...
ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
ArrayNode arrayNode = (ArrayNode) object.path(RADIUS_SERVER_CONNECTPOINTS);
for (JsonNode jsonNode : arrayNode) {
@@ -285,6 +283,7 @@
return null;
}
}
- return builder.build();
+ return builder.build();*/
+ return new HashSet<ConnectPoint>();
}
}
diff --git a/src/main/java/org/opencord/aaa/AaaManager.java b/src/main/java/org/opencord/aaa/AaaManager.java
index 799064a..972a072 100755
--- a/src/main/java/org/opencord/aaa/AaaManager.java
+++ b/src/main/java/org/opencord/aaa/AaaManager.java
@@ -19,8 +19,10 @@
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.DeserializationException;
import org.onlab.packet.EAP;
import org.onlab.packet.EAPOL;
+import org.onlab.packet.EAPOL_MKPDU;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
@@ -33,6 +35,7 @@
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
@@ -43,6 +46,8 @@
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.intf.InterfaceService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
@@ -57,11 +62,18 @@
import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.AbstractMap;
import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
+import org.onlab.packet.EAPOL_MKPDU_BasicParameterSet.SCI;
+
/**
* AAA application for ONOS.
*/
@@ -97,6 +109,9 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected AccessDeviceService accessDeviceService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
private final DeviceListener deviceListener = new InternalDeviceListener();
// NAS IP address
@@ -143,20 +158,51 @@
// latest configuration
AaaConfig newCfg;
+ //MACSec verion
+ private String version;
+
+ //MACSec capability
+ private String capability;
+
+ //Secure Connectivity Association Key NameK
+ private String mkaCkn;
+
+ //Secure Connectivity Association Key
+ private String mkaCak;
+
+ //Default Netconf Device ID
+ private DeviceId defaultNetconfDeviceId;
+
// Configuration properties factory
private final ConfigFactory factory =
new ConfigFactory<ApplicationId, AaaConfig>(APP_SUBJECT_FACTORY,
- AaaConfig.class,
- "AAA") {
+ AaaConfig.class,
+ "AAA") {
@Override
public AaaConfig createConfig() {
return new AaaConfig();
}
};
+ // Configuration properties factory with MACSec
+ private final ConfigFactory factoryWithMacSec =
+ new ConfigFactory<ApplicationId, MacSecConfig>(APP_SUBJECT_FACTORY,
+ MacSecConfig.class,
+ "MACSEC") {
+ @Override
+ public MacSecConfig createConfig() {
+ return new MacSecConfig();
+ }
+ };
+
// Listener for config changes
private final InternalConfigListener cfgListener = new InternalConfigListener();
+ // MACSec KaY store and SecY drivers
+ private Map<DeviceId, Map.Entry<MacSecKaY, MacSecSecY>>
+ mkaStore = new ConcurrentHashMap<>();
+
+
/**
* Builds an EAPOL packet based on the given parameters.
*
@@ -197,6 +243,12 @@
appId = coreService.registerApplication(APP_NAME);
customInfo = new CustomizationInfo(subsService, deviceService);
cfgListener.reconfigureNetwork(netCfgService.getConfig(appId, AaaConfig.class));
+
+ /* Enable MACSec configuration Support */
+ netCfgService.registerConfigFactory(factoryWithMacSec);
+ cfgListener.reconfigureNetworkWithMacSec(netCfgService.getConfig(appId, MacSecConfig.class));
+ deviceService.addListener(new InternalDeviceListener());
+
configureRadiusCommunication();
// register our event handler
@@ -227,6 +279,7 @@
StateMachine.destroyMaps();
impl.deactivate();
deviceService.removeListener(deviceListener);
+ macSecThreadPool.shutdown();
log.info("Stopped");
}
@@ -280,57 +333,72 @@
switch (radiusPacket.getCode()) {
case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
- RADIUSAttribute radiusAttrState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE);
- byte[] challengeState = null;
- if (radiusAttrState != null) {
- challengeState = radiusAttrState.getValue();
+ try {
+ RADIUSAttribute radiusAttrState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE);
+ byte[] challengeState = null;
+ if (radiusAttrState != null) {
+ challengeState = radiusAttrState.getValue();
+ }
+
+ eapPayload = radiusPacket.decapsulateMessage();
+
+ stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
+ eth = buildEapolResponse(stateMachine.supplicantAddress(),
+ MacAddress.valueOf(nasMacAddress),
+ stateMachine.vlanId(),
+ EAPOL.EAPOL_PACKET,
+ eapPayload, stateMachine.priorityCode());
+ sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+ } catch (DeserializationException e) {
+ e.printStackTrace();
}
- eapPayload = radiusPacket.decapsulateMessage();
- stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
- eth = buildEapolResponse(stateMachine.supplicantAddress(),
- MacAddress.valueOf(nasMacAddress),
- stateMachine.vlanId(),
- EAPOL.EAPOL_PACKET,
- eapPayload, stateMachine.priorityCode());
- sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
break;
case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
- //send an EAPOL - Success to the supplicant.
- byte[] eapMessageSuccess =
- radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue();
- eapPayload = new EAP();
- eapPayload = (EAP) eapPayload.deserialize(eapMessageSuccess, 0, eapMessageSuccess.length);
- eth = buildEapolResponse(stateMachine.supplicantAddress(),
- MacAddress.valueOf(nasMacAddress),
- stateMachine.vlanId(),
- EAPOL.EAPOL_PACKET,
- eapPayload, stateMachine.priorityCode());
- sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+ try {
+ //send an EAPOL - Success to the supplicant.
+ byte[] eapMessageSuccess =
+ radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue();
+ eapPayload = new EAP();
+ //eapPayload = (EAP) eapPayload.deserialize(eapMessageSuccess, 0, eapMessageSuccess.length);
+ eapPayload = eapPayload.deserializer().deserialize(eapMessageSuccess, 0, eapMessageSuccess.length);
+ eth = buildEapolResponse(stateMachine.supplicantAddress(),
+ MacAddress.valueOf(nasMacAddress),
+ stateMachine.vlanId(),
+ EAPOL.EAPOL_PACKET,
+ eapPayload, stateMachine.priorityCode());
+ sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
- stateMachine.authorizeAccess();
-
+ stateMachine.authorizeAccess();
+ } catch (DeserializationException e) {
+ log.error("Error in processing RADIUS message {}", e);
+ }
break;
case RADIUS.RADIUS_CODE_ACCESS_REJECT:
- //send an EAPOL - Failure to the supplicant.
- byte[] eapMessageFailure;
- eapPayload = new EAP();
- RADIUSAttribute radiusAttrEap = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE);
- if (radiusAttrEap == null) {
- eapPayload.setCode(EAP.FAILURE);
- eapPayload.setIdentifier(stateMachine.challengeIdentifier());
- eapPayload.setLength(EAP.EAP_HDR_LEN_SUC_FAIL);
- } else {
- eapMessageFailure = radiusAttrEap.getValue();
- eapPayload = (EAP) eapPayload.deserialize(eapMessageFailure, 0, eapMessageFailure.length);
+ try {
+ //send an EAPOL - Failure to the supplicant.
+ byte[] eapMessageFailure;
+ eapPayload = new EAP();
+ RADIUSAttribute radiusAttrEap = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE);
+ if (radiusAttrEap == null) {
+ eapPayload.setCode(EAP.FAILURE);
+ eapPayload.setIdentifier(stateMachine.challengeIdentifier());
+ eapPayload.setLength(EAP.EAP_HDR_LEN_SUC_FAIL);
+ } else {
+ eapMessageFailure = radiusAttrEap.getValue();
+ //eapPayload = (EAP) eapPayload.deserialize(eapMessageFailure, 0, eapMessageFailure.length);
+ eapPayload = (EAP) eapPayload.deserializer()
+ .deserialize(eapMessageFailure, 0, eapMessageFailure.length);
+ }
+ eth = buildEapolResponse(stateMachine.supplicantAddress(),
+ MacAddress.valueOf(nasMacAddress),
+ stateMachine.vlanId(),
+ EAPOL.EAPOL_PACKET,
+ eapPayload, stateMachine.priorityCode());
+ sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+ stateMachine.denyAccess();
+ } catch (DeserializationException e) {
+ log.error("Error in processing RADIUS REJECT {}", e);
}
- eth = buildEapolResponse(stateMachine.supplicantAddress(),
- MacAddress.valueOf(nasMacAddress),
- stateMachine.vlanId(),
- EAPOL.EAPOL_PACKET,
- eapPayload, stateMachine.priorityCode());
- sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
- stateMachine.denyAccess();
-
break;
default:
log.warn("Unknown RADIUS message received with code: {}", radiusPacket.getCode());
@@ -346,7 +414,7 @@
private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
- treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
+ treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
packetService.emit(packet);
}
@@ -385,20 +453,20 @@
* Creates and initializes common fields of a RADIUS packet.
*
* @param stateMachine state machine for the request
- * @param eapPacket EAP packet
+ * @param eapPacket EAP packet
* @return RADIUS packet
*/
private RADIUS getRadiusPayload(StateMachine stateMachine, byte identifier, EAP eapPacket) {
RADIUS radiusPayload =
new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
- eapPacket.getIdentifier());
+ eapPacket.getIdentifier());
// set Request Authenticator in StateMachine
stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
radiusPayload.setIdentifier(identifier);
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
- stateMachine.username());
+ stateMachine.username());
radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
AaaManager.this.nasIpAddress.getAddress());
@@ -450,11 +518,11 @@
//send an EAP Request/Identify to the supplicant
EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null);
if (ethPkt.getVlanID() != Ethernet.VLAN_UNTAGGED) {
- stateMachine.setPriorityCode(ethPkt.getPriorityCode());
+ stateMachine.setPriorityCode(ethPkt.getPriorityCode());
}
Ethernet eth = buildEapolResponse(srcMac, MacAddress.valueOf(nasMacAddress),
- ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
- eapPayload, stateMachine.priorityCode());
+ ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
+ eapPayload, stateMachine.priorityCode());
stateMachine.setSupplicantAddress(srcMac);
stateMachine.setVlanId(ethPkt.getVlanID());
@@ -496,8 +564,8 @@
//send the RADIUS challenge response
radiusPayload =
getRadiusPayload(stateMachine,
- stateMachine.identifier(),
- eapPacket);
+ stateMachine.identifier(),
+ eapPacket);
radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
if (stateMachine.challengeState() != null) {
@@ -531,6 +599,78 @@
return;
}
break;
+ case EAPOL.EAPOL_MKA:
+
+ // Received MACSec PDU from supplicant.
+
+ /* For version 1.0 Logon Process (IEEE802.1X 2010 Clause 12.5) starts here.
+ * Currently only authenticated & secure connections are supported.
+ * */
+
+ /* TODO : Ensure AAA authentication is success. */
+
+ /* Perform Sanity Check */
+ EAPOL_MKPDU mkpdu = (EAPOL_MKPDU) eapol.getPayload();
+
+ /* TODO : Sanity checking
+ if(!MKPDUSanityCheck(mkpdu)) {
+ log.warn("Sanity failed for MKPDU from {}, dropping it.", srcMac);
+ return;
+ }*/
+
+ // Load participant details(key <device><port>) and let it process packet.
+ Map.Entry<MacSecKaY, MacSecSecY> mka = mkaStore.getOrDefault(deviceId, null);
+ if (mka == null) {
+ log.error("EAPOL-MKA received from undetected device {}. Ignoring.", deviceId);
+ return;
+ }
+ MacSecKaY kaY = mka.getKey();
+ MkaParticipant participant = kaY.lookupParticipantByID(sessionId);
+ if (participant != null) {
+ participant.receive(mkpdu);
+ } else {
+ // New MKA actor; prepare default resources.
+
+ /* TODO : Locate CAK, CKN for <device>:<port>.
+ * Currently global CAK & CKN. */
+ byte[] cak = MkaKeyUtils.hexStringToByteArray(mkaCak);
+ byte[] ckn = MkaKeyUtils.hexStringToByteArray(mkaCkn);
+
+ // Initialize participant details
+ Port port = deviceService.getPort(deviceId, portNumber);
+ MacAddress portMac = null;
+ Optional<MacAddress> macAddress = interfaceService.getInterfacesByPort(inPacket.receivedFrom())
+ .stream()
+ .map(Interface::mac).findFirst();
+ if (macAddress.isPresent()) {
+ portMac = macAddress.get();
+ } else {
+ log.warn("Fetching EAPOL-MKA ingress port MAC from annotations.");
+ portMac = MacAddress.valueOf(port.annotations().value("portMac"));
+ }
+ participant = new MkaParticipant(stateMachine, packetService,
+ macSecThreadPool,
+ kaY, mka.getValue(), cak, ckn,
+ new SCI(portMac.toBytes(), (short) (port.number().toLong())),
+ deviceId, port);
+
+ // Initialize CP state machine.
+ CpStateMachine cpSM = new CpStateMachine(participant, mka.getValue(),
+ kaY.defaultCipherSuit(), participant.latestKI(),
+ participant.latestAN(), macSecThreadPool);
+
+ // Update per port details in store
+ kaY.addPortDetails(stateMachine.sessionId(), participant, cpSM);
+
+ // Handover EAPOL_MKPDU to participant to handle it.
+ participant.receive(mkpdu);
+
+ // Start participant Hello timer & CP state machine.
+ participant.startTicking();
+ cpSM.startStateMachine();
+ }
+
+ break;
default:
log.trace("Skipping EAPOL message {}", eapol.getEapolType());
}
@@ -595,6 +735,37 @@
}
}
+ /**
+ * Reconfigures the AAA application according to the
+ * configuration parameters passed.
+ *
+ * @param cfg configuration object
+ **/
+ private void reconfigureNetworkWithMacSec(MacSecConfig cfg) {
+ MacSecConfig newCfg;
+ if (cfg == null) {
+ newCfg = new MacSecConfig();
+ } else {
+ newCfg = cfg;
+ }
+ if (newCfg.version() != null) {
+ version = newCfg.version();
+ }
+ if (newCfg.capability() != null) {
+ capability = newCfg.capability();
+ }
+ if (newCfg.ckn() != null) {
+ mkaCkn = newCfg.ckn();
+ }
+ if (newCfg.cak() != null) {
+ mkaCak = newCfg.cak();
+ }
+ if (newCfg.netConfDeviceId() != null) {
+ defaultNetconfDeviceId = newCfg.netConfDeviceId();
+ }
+ }
+
+
@Override
public void event(NetworkConfigEvent event) {
@@ -604,6 +775,9 @@
AaaConfig cfg = netCfgService.getConfig(appId, AaaConfig.class);
reconfigureNetwork(cfg);
+ //Include MACSec
+ MacSecConfig cfgMacSec = netCfgService.getConfig(appId, MacSecConfig.class);
+ reconfigureNetworkWithMacSec(cfgMacSec);
log.info("Reconfigured");
}
@@ -613,10 +787,9 @@
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
-
+ DeviceId devId = event.subject().id();
switch (event.type()) {
case PORT_REMOVED:
- DeviceId devId = event.subject().id();
PortNumber portNumber = event.port().number();
String sessionId = devId.toString() + portNumber.toString();
@@ -626,10 +799,42 @@
StateMachine.deleteStateMachineMapping(removed);
}
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ // Derive paired netconf device id from openflow device id
+ DeviceId netconfId = getNetconfPairId(devId);
+ if (netconfId == null) {
+ netconfId = defaultNetconfDeviceId;
+ // netconfId = DeviceId.deviceId("netconf:192.168.1.2:2022");
+ }
+
+ // Update store with KaY and SecY device details.
+ MacSecSecY secY = new MacSecSecY(netconfId);
+ MacSecKaY kaY = new MacSecKaY(secY);
+ mkaStore.put(devId,
+ new AbstractMap.SimpleEntry<MacSecKaY, MacSecSecY>(kaY, secY));
break;
+
+ case DEVICE_REMOVED:
+ mkaStore.remove(devId);
+ break;
+
default:
return;
}
}
+
+ // Openflow device to netconf device mapping logic for MACSec SecY switches
+ private DeviceId getNetconfPairId(DeviceId deviceID) {
+ /* TODO : Finalize and implement logic */
+ return null;
+ }
}
+
+ // Device listener for MACSec capability loading.
+ // MACSec Thread Pool. TODO: Pool Size Fixing.
+ private static final int MACSEC_THREAD_POOL_SIZE = 10;
+ private ScheduledExecutorService macSecThreadPool =
+ Executors.newScheduledThreadPool(MACSEC_THREAD_POOL_SIZE);
+
}
diff --git a/src/main/java/org/opencord/aaa/AescMac.java b/src/main/java/org/opencord/aaa/AescMac.java
new file mode 100644
index 0000000..65a5dda
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AescMac.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2018-present. * Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.ByteArrayOutputStream;
+
+/**
+ * AES-CMAC Algorithm. RFC 4493.
+ * Cipher-based Message Authentication Code(CMAC),
+ * a keyed hash function based on a symmetric key block cipher: Advanced Encryption Standard(AES).
+ * Reference: https://github.com/E3V3A/androsmex/blob/master/src/de/tsenger/androsmex/tools/AESCMac.java
+ */
+public class AescMac {
+
+ private static byte[] constZero = MkaKeyUtils.hexStringToByteArray("00000000000000000000000000000000");
+ private static byte[] constRb = MkaKeyUtils.hexStringToByteArray("00000000000000000000000000000087");
+ private byte[] key;
+ private ByteArrayOutputStream catInput;
+
+ /**
+ * AES 128-bit encryption using JDK built-in classes.
+ */
+ private static class AesCrypto {
+
+ static Cipher aesEcb = null;
+
+ static {
+ try {
+ aesEcb = Cipher.getInstance("AES/ECB/NOPADDING");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Encrypt AES 128 bit key.
+ * @param zeroConst - Zero-constant
+ * @param key - Input key value
+ * @return Encrypted Key
+ */
+ private static byte[] encryptAES128(byte[] zeroConst, byte[] key) {
+ SecretKey aesKey = new SecretKeySpec(key, 0, 16, "AES");
+ try {
+ synchronized (aesEcb) {
+ aesEcb.init(Cipher.ENCRYPT_MODE, aesKey);
+ return aesEcb.doFinal(zeroConst);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ static Cipher aesKeyWrapper = null;
+
+ static {
+ try {
+ aesKeyWrapper = Cipher.getInstance("AESWrap");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * AES Key Wrapper.
+ * @param message - Message to be wrapped
+ * @param key - Key-Encryption Key
+ * @return Wrapped key
+ */
+ private static byte[] keyWrap(byte[] key, byte[] message) {
+ SecretKey aesKey = new SecretKeySpec(key, 0, 16, "AES");
+ SecretKey messageKey = new SecretKeySpec(message, 0, 16, "AES");
+ try {
+ synchronized (aesKeyWrapper) {
+ aesKeyWrapper.init(Cipher.WRAP_MODE, aesKey);
+ byte[] wrappedKey = aesKeyWrapper.wrap(messageKey);
+
+ /* Unwrap the key */
+ aesKeyWrapper.init(Cipher.UNWRAP_MODE, aesKey);
+ messageKey = (SecretKey) aesKeyWrapper.unwrap(wrappedKey, "AES/CTR/NOPADDING", Cipher.SECRET_KEY);
+
+ return wrappedKey;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ }
+
+ public AescMac() {
+
+ }
+
+ /**
+ * Wrap a message.
+ * @param message - Message to be wrapped
+ * @param key - Key-Encryption Key
+ * @return Wrapped key
+ */
+ public byte[] wrap(byte[] key, byte[] message) {
+ return AesCrypto.keyWrap(key, message);
+ }
+
+ /**
+ * Generate AES message authentication code.
+ * @param key - Key derivation key
+ * @param m - Message to be authenticated
+ * @param len - Length of message in octets
+ * @return Message Authentication Code
+ */
+ public byte[] generateAescMac(byte[] key, byte[] m, int len) {
+
+ initialize(key);
+ update(m, 0, len);
+ return generateMac();
+ }
+
+ /**
+ * Initialize data members.
+ * @param key Key derivation key
+ */
+ private void initialize(byte[] key) {
+ this.key = key;
+ catInput = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Update the message byte array with offset and length.
+ * @param msg - Message to be authenticated
+ * @param offset - Offset to be added
+ * @param len - Length of the message
+ */
+ private void update(byte[] msg, int offset, int len) {
+ catInput.write(msg, offset, len);
+ }
+
+ /**
+ * Generate MAC.
+ * @return Message authentication code
+ */
+ private byte[] generateMac() {
+ // Step 1
+ Object[] keys = generateSubKey(key);
+ byte[] k1 = (byte[]) keys[0];
+ byte[] k2 = (byte[]) keys[1];
+ byte[] input = catInput.toByteArray();
+
+ // Step 2
+ int numberOfRounds = (input.length + 15) / 16;
+ boolean lastBlockComplete;
+
+ // Step 3
+ if (numberOfRounds == 0) {
+ numberOfRounds = 1;
+ lastBlockComplete = false;
+ } else {
+ lastBlockComplete = (input.length % 16 == 0);
+ }
+
+ byte[] mLast;
+ int srcPos = 16 * (numberOfRounds - 1);
+
+ // Step 4
+ if (lastBlockComplete) {
+ byte[] partInput = new byte[16];
+
+ System.arraycopy(input, srcPos, partInput, 0, 16);
+ mLast = xor128(partInput, k1);
+ } else {
+ byte[] partInput = new byte[input.length % 16];
+
+ System.arraycopy(input, srcPos, partInput, 0, input.length % 16);
+ byte[] padded = padTo16(partInput);
+ mLast = xor128(padded, k2);
+ }
+
+ // Step 5
+ byte[] x = constZero.clone();
+ byte[] partInput = new byte[16];
+ byte[] y;
+
+ // Step 6
+ for (int i = 0; i < numberOfRounds - 1; i++) {
+ srcPos = 16 * i;
+ System.arraycopy(input, srcPos, partInput, 0, 16);
+
+ y = xor128(partInput, x); // Y := Mi (+) X
+ x = AesCrypto.encryptAES128(y, key);
+ }
+
+ y = xor128(x, mLast);
+ x = AesCrypto.encryptAES128(y, key);
+
+ // Step 7
+ return x;
+ }
+
+ /**
+ * Pad to 16 bytes.
+ * @param input - Value to be padded
+ * @return Padded byte array
+ */
+ private byte[] padTo16(byte[] input) {
+ byte[] padded = new byte[16];
+
+ for (int j = 0; j < 16; j++) {
+ if (j < input.length) {
+ padded[j] = input[j];
+ } else if (j == input.length) {
+ padded[j] = (byte) 0x80;
+ } else {
+ padded[j] = (byte) 0x00;
+ }
+ }
+ return padded;
+ }
+
+ /**
+ * Generate Subkey.
+ * @param key - Input key to generate the subkeys
+ * @return Array of two subkeys
+ */
+ private static Object[] generateSubKey(byte[] key) {
+ // Step 1
+ byte[] l = AesCrypto.encryptAES128(constZero, key);
+
+ // Step 2
+ byte[] k1;
+ if ((l[0] & 0x80) == 0) { // If MSB(L) = 0, then K1 = L << 1
+ k1 = leftShiftBit(l);
+ } else { //Else K1 = ( L << 1 ) (+) Rb
+ byte[] tmp = leftShiftBit(l);
+ k1 = xor128(tmp, constRb);
+ }
+
+ // Step 3
+ byte[] k2;
+ if ((k1[0] & 0x80) == 0) {
+ k2 = leftShiftBit(k1);
+ } else {
+ byte[] tmp = leftShiftBit(k1);
+ k2 = xor128(tmp, constRb);
+ }
+
+ // Step 4
+ Object[] result = new Object[2];
+ result[0] = k1;
+ result[1] = k2;
+ return result;
+ }
+
+ /**
+ * XOR two values.
+ * @param input1 - java.util.byte[]
+ * @param input2 - java.util.byte[]
+ * @return XORed byte array
+ */
+ private static byte[] xor128(byte[] input1, byte[] input2) {
+ byte[] output = new byte[input1.length];
+ for (int i = 0; i < input1.length; i++) {
+ output[i] = (byte) (((int) input1[i] ^ (int) input2[i]) & 0xFF);
+ }
+ return output;
+ }
+
+ /**
+ * Shift left by one bit.
+ * @param input - Value to be shifted
+ * @return Shifted result
+ */
+ private static byte[] leftShiftBit(byte[] input) {
+ byte[] output = new byte[input.length];
+ byte overflow = 0;
+ for (int i = (input.length - 1); i >= 0; i--) {
+ output[i] = (byte) ((int) input[i] << 1 & 0xFF);
+ output[i] |= overflow;
+ overflow = ((input[i] & 0x80) != 0) ? (byte) 1 : (byte) 0;
+ }
+
+ return output;
+ }
+}
+
diff --git a/src/main/java/org/opencord/aaa/CpStateMachine.java b/src/main/java/org/opencord/aaa/CpStateMachine.java
new file mode 100644
index 0000000..8b1ea10
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/CpStateMachine.java
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2018-present. * Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CpStateMachine {
+ private final Logger log = getLogger(getClass());
+ /* State, Transition details. IEEE802.1X-2010. Figure 12-2 */
+ private boolean portValid, newSak;
+ private boolean lrx, ltx, orx, otx;
+ private boolean chgdServer, controlledPortEnabled, chgdCipher;
+ private boolean usingReceiveSAs;
+ private boolean serverTransmitting, usingTransmitSA;
+ private boolean electedSelf, allReceiving, newInfo;
+ private byte[] lki, oki, distributedKI;
+ private byte distributedAN, lan, oan;
+ private long transmitWhen, transmitDelay, retireWhen, retireDelay;
+ /* MACSec management & control */
+ private boolean protectFrames;
+ private boolean replayProtect;
+ private int replayProtectWindow;
+
+ enum MacSecValidate {
+ Disabled, Checked, Strict
+ }
+
+ private MacSecValidate validateFrames;
+ private final int defaultReplayProtectWindow = 10;
+ private final int defaultTransmitDelay = 6000;
+ private final int defaultSakRetireTime = 5000;
+ /* Ciphersuit management & control */
+ private MacSecCipherSuit cipherSuit;
+ enum ConnectType { PENDING, UNAUTHENTICATED, AUTHENTICATED, SECURE }
+ ConnectType connectType;
+ enum StateIndex {
+ BEGIN, INIT, CHANGE, ALLOWED, AUTHENTICATED, SECURED,
+ RECEIVE, RECEIVING, READY, TRANSMIT, TRANSMITTING, ABANDON, RETIRE
+ }
+
+ protected boolean changedConnect() {
+ return (connectType != ConnectType.SECURE) || chgdServer || chgdCipher;
+ }
+
+ abstract class State {
+ StateIndex index;
+ Logger logger;
+ State(StateIndex index, Logger logger) {
+ this.index = index;
+ this.logger = logger;
+ }
+ /**
+ * State Machine state based action function.
+ */
+ public void enter() {
+ logger.trace("Entering state : {} ", index.name());
+ }
+ /**
+ * State Machine state stepping function.
+ * Assumes that before calling "step" atleast once "enter" of the state should be called.
+ * @return null
+ */
+ public StateIndex step() {
+ logger.trace("Stepping from state : {}", index.name());
+ return null;
+ }
+ }
+ class Begin extends State {
+ Begin(Logger logger) {
+ super(StateIndex.BEGIN, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ return StateIndex.INIT;
+ }
+ }
+ class Init extends State {
+ Init(Logger logger) {
+ super(StateIndex.INIT, logger);
+ }
+ @Override
+ public void enter() {
+ controlledPortEnabled = false;
+ /* TODO : */
+ // secY.enablePort(portName, controlledPortEnabled);
+ portValid = false;
+ lki = null;
+ oki = null;
+ lrx = false;
+ ltx = false;
+ orx = false;
+ otx = false;
+ }
+ @Override
+ public StateIndex step() {
+ return StateIndex.CHANGE;
+ }
+ }
+
+ class Change extends State {
+ Change(Logger logger) {
+ super(StateIndex.CHANGE, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ portValid = false;
+ controlledPortEnabled = false;
+ secY.enablePort(portName, controlledPortEnabled);
+ /* TODO : Delete SAs associated with keys. */
+ if (lki != null) {
+ participant.deleteSAs(lki);
+ }
+ if (oki != null) {
+ participant.deleteSAs(oki);
+ }
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex index = StateIndex.CHANGE;
+ switch (connectType) {
+ case UNAUTHENTICATED:
+ index = StateIndex.ALLOWED;
+ break;
+ case AUTHENTICATED:
+ index = StateIndex.AUTHENTICATED;
+ break;
+ case SECURE:
+ index = StateIndex.SECURED;
+ default:
+ break;
+ }
+ return index;
+ }
+ }
+ class Allowed extends State {
+ Allowed(Logger logger) {
+ super(StateIndex.ALLOWED, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ protectFrames = false;
+ replayProtect = false;
+ validateFrames = MacSecValidate.Checked;
+ portValid = false;
+ controlledPortEnabled = true;
+ /* Update SecY */
+ secY.enablePort(portName, controlledPortEnabled);
+ secY.protectFrames(portName, protectFrames);
+ secY.validateFrames(portName, validateFrames);
+ secY.replayProtect(portName, replayProtect, replayProtectWindow);
+ }
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.ALLOWED;
+ if (connectType != ConnectType.UNAUTHENTICATED) {
+ nextState = StateIndex.CHANGE;
+ }
+ return nextState;
+ }
+ }
+ class Authenticated extends State {
+ Authenticated(Logger logger) {
+ super(StateIndex.AUTHENTICATED, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ protectFrames = false;
+ replayProtect = false;
+ validateFrames = MacSecValidate.Checked;
+ portValid = false;
+ controlledPortEnabled = true;
+ /* Update SecY */
+ secY.enablePort(portName, controlledPortEnabled);
+ secY.protectFrames(portName, protectFrames);
+ secY.validateFrames(portName, validateFrames);
+ secY.replayProtect(portName, replayProtect, replayProtectWindow);
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.AUTHENTICATED;
+ if (connectType != ConnectType.AUTHENTICATED) {
+ nextState = StateIndex.CHANGE;
+ }
+ return nextState;
+ }
+ }
+ class Secured extends State {
+ Secured(Logger logger) {
+ super(StateIndex.SECURED, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ chgdServer = false;
+ /* TODO : Load from per port configuration. */
+ protectFrames = true;
+ validateFrames = MacSecValidate.Strict;
+ replayProtect = true;
+ replayProtectWindow = defaultReplayProtectWindow;
+ portValid = true;
+ /* Update SecY */
+ secY.setCurrentCipherSuit(portName, cipherSuit.id());
+ /* TODO: Update confidentiality offset from ciphersuit. */
+ secY.protectFrames(portName, protectFrames);
+ secY.validateFrames(portName, validateFrames);
+ secY.replayProtect(portName, replayProtect, replayProtectWindow);
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.SECURED;
+ if (changedConnect()) {
+ nextState = StateIndex.CHANGE;
+ } else if (newSak) {
+ nextState = StateIndex.RECEIVE;
+ }
+ return nextState;
+ }
+ }
+ /**
+ * Receive state slighlty differ from IEEE 802.1X 2010 Figure 12-2.
+ * Refer : wpa_supplicant-2.6/src/pae/ieee802_1x_cp.c
+ */
+ class Receive extends State {
+ Receive(Logger logger) {
+ super(StateIndex.RECEIVE, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ oki = lki;
+ orx = lrx;
+ otx = ltx;
+ oan = lan;
+ lki = distributedKI;
+ lan = distributedAN;
+ newSak = false;
+ /* Update SecY */
+ participant.setOldSAAttributes(oki, oan, orx, otx);
+ participant.setLatestSAAttributes(lki, lan, ltx, lrx);
+ participant.createSAs(lki);
+ participant.enableReceiveSAs(lki);
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.RECEIVE;
+ if (usingReceiveSAs) {
+ nextState = StateIndex.RECEIVING;
+ }
+ return nextState;
+ }
+ }
+ class Receiving extends State {
+ Receiving(Logger logger) {
+ super(StateIndex.RECEIVING, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ lrx = true;
+ participant.setLatestSAAttributes(lki, lan, ltx, lrx);
+ transmitWhen = transmitDelay;
+ /* TODO : CP Timout reset. */
+ usingReceiveSAs = false;
+ serverTransmitting = false;
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.RECEIVING;
+ if (newSak || changedConnect()) {
+ nextState = StateIndex.ABANDON;
+ }
+ if (!electedSelf) {
+ nextState = StateIndex.READY;
+ }
+ /* IEEE 802.1X-2010 Figure 12-2 deviation. !controlPortEnabled removed from check. */
+ if (electedSelf && (allReceiving || (transmitWhen == 0))) {
+ nextState = StateIndex.TRANSMIT;
+ }
+ return nextState;
+ }
+ }
+ class Ready extends State {
+ Ready(Logger logger) {
+ super(StateIndex.READY, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ newInfo = true;
+ // TODO : participant.enableNewInfo();
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.READY;
+ if (newSak || changedConnect()) {
+ nextState = StateIndex.ABANDON;
+ }
+ if (serverTransmitting) {
+ nextState = StateIndex.TRANSMIT;
+ }
+ return nextState;
+ }
+ }
+ class Transmit extends State {
+ Transmit(Logger logger) {
+ super(StateIndex.TRANSMIT, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ controlledPortEnabled = true;
+ secY.enablePort(portName, controlledPortEnabled);
+ ltx = true;
+ participant.setLatestSAAttributes(lki, lan, ltx, lrx);
+ participant.enableTransmitSA(lki);
+ allReceiving = false;
+ serverTransmitting = false;
+ }
+ @Override
+ public StateIndex step() {
+ super.enter();
+ StateIndex nextState = StateIndex.TRANSMIT;
+ if (usingTransmitSA) {
+ nextState = StateIndex.TRANSMITTING;
+ }
+ return nextState;
+ }
+ }
+ class Transmitting extends State {
+ Transmitting(Logger logger) {
+ super(StateIndex.TRANSMITTING, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ retireWhen = orx ? retireDelay : 0;
+ otx = false;
+ participant.setOldSAAttributes(oki, oan, otx, orx);
+ newInfo = true;
+ // TODO: participant.enableNewInfo();
+ /* TODO : Reset retire when timeout. */
+ usingTransmitSA = false;
+ }
+ @Override
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.TRANSMITTING;
+ if ((retireWhen == 0) || changedConnect()) {
+ nextState = StateIndex.RETIRE;
+ }
+ return nextState;
+ }
+ }
+ class Abandon extends State {
+ Abandon(Logger logger) {
+ super(StateIndex.ABANDON, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ lrx = false;
+ participant.setLatestSAAttributes(lki, lan, ltx, lrx);
+ participant.deleteSAs(lki);
+ lki = null;
+ participant.setLatestSAAttributes(lki, lan, ltx, lrx);
+ newSak = false;
+ }
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.ABANDON;
+ if (changedConnect()) {
+ nextState = StateIndex.RETIRE;
+ } else if (newSak) {
+ nextState = StateIndex.RECEIVE;
+ }
+ return nextState;
+ }
+ }
+ class Retire extends State {
+ Retire(Logger logger) {
+ super(StateIndex.RETIRE, logger);
+ }
+ @Override
+ public void enter() {
+ super.enter();
+ /* IEEE 802.1X-2010 Figure 12-2 deviation. Only clearing old keys. */
+ oki = null;
+ orx = false;
+ otx = false;
+ participant.setOldSAAttributes(oki, oan, otx, orx);
+ /* TODO : deleteSA(oki) ? */
+ }
+ public StateIndex step() {
+ super.step();
+ StateIndex nextState = StateIndex.RETIRE;
+ if (changedConnect()) {
+ nextState = StateIndex.CHANGE;
+ } else if (newSak) {
+ nextState = StateIndex.RECEIVE;
+ }
+ return nextState;
+ }
+ }
+ private final Logger logger = getLogger(getClass());
+ Map<StateIndex, State> states = new HashMap<StateIndex, State>() {{
+ put(StateIndex.BEGIN, new Begin(logger));
+ put(StateIndex.INIT, new Init(logger));
+ put(StateIndex.CHANGE, new Change(logger));
+ put(StateIndex.ALLOWED, new Allowed(logger));
+ put(StateIndex.AUTHENTICATED, new Authenticated(logger));
+ put(StateIndex.SECURED, new Secured(logger));
+ put(StateIndex.RECEIVE, new Receive(logger));
+ put(StateIndex.RECEIVING, new Receiving(logger));
+ put(StateIndex.READY, new Ready(logger));
+ put(StateIndex.TRANSMIT, new Transmit(logger));
+ put(StateIndex.TRANSMITTING, new Transmitting(logger));
+ put(StateIndex.ABANDON, new Abandon(logger));
+ put(StateIndex.RETIRE, new Retire(logger));
+ }};
+ StateIndex currentStateIndex;
+ /* Real worker for State Machine. */
+ private class Worker implements Runnable {
+ public int maxSmIterCount = 100;
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ /* Maximum maxSmIterCount state changes per turn. */
+ int count = maxSmIterCount;
+ StateIndex previousState = null;
+ State currentState = null;
+ while (count-- > 0) {
+ previousState = currentStateIndex;
+ currentState = states.getOrDefault(currentStateIndex, null);
+ if (currentState != null && (currentState.step() != previousState)) {
+ currentState.enter();
+ currentStateIndex = currentState.step();
+ } else {
+ break;
+ }
+ }
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ log.trace("Interruption in CP SM scheduling. {}", e.getMessage());
+ }
+ }
+ }
+ }
+ Worker worker = new Worker();
+ /* State Machine thread pool related components. */
+ ScheduledExecutorService executor;
+ ScheduledFuture<?> future;
+ /* KaY, SecY connections. */
+ MkaParticipant participant;
+ MacSecSecY secY;
+ String portName;
+ /**
+ * Needs participant created properly.
+ *
+ * @param participant details.
+ * @param secY SecY interface details.
+ * @param cipherSuit details.
+ * @param distributedKI KeyIdentifier
+ * @param distributedAN association number
+ * @param executor scheduled service
+ */
+ public CpStateMachine(MkaParticipant participant, MacSecSecY secY,
+ MacSecCipherSuit cipherSuit,
+ byte[] distributedKI,
+ byte distributedAN,
+ ScheduledExecutorService executor) {
+ this.secY = secY;
+ this.participant = participant;
+ portName = participant.port().number().name();
+ this.executor = executor;
+ /* Generation default settings. */
+ protectFrames = true;
+ connectType = ConnectType.SECURE;
+ newSak = true;
+ usingReceiveSAs = true;
+ usingTransmitSA = true;
+ /* Validation default settings. */
+ validateFrames = MacSecValidate.Strict;
+ replayProtect = false;
+ replayProtectWindow = defaultReplayProtectWindow;
+ /* Other settings */
+ lrx = true;
+ ltx = true;
+ lki = null;
+ orx = false;
+ otx = false;
+ oki = null;
+ controlledPortEnabled = false;
+ chgdServer = false;
+ this.distributedAN = distributedAN;
+ this.distributedKI = distributedKI;
+ electedSelf = true;
+ /* Ciphersuit */
+ this.cipherSuit = cipherSuit;
+ /* Timers */
+ // TODO : Needs timeout on transmitDelay & retireDelay
+ // transmitDelay = defaultTransmitDelay;
+ transmitDelay = 0;
+ retireDelay = defaultSakRetireTime;
+ currentStateIndex = StateIndex.BEGIN;
+ /* Really apply SecY settings. */
+ secY.setCurrentCipherSuit(portName, cipherSuit.id());
+ secY.protectFrames(portName, protectFrames);
+ secY.validateFrames(portName, validateFrames);
+ secY.replayProtect(portName, replayProtect, replayProtectWindow);
+ secY.enablePort(portName, controlledPortEnabled);
+ }
+ public void startStateMachine() {
+ future = executor.scheduleWithFixedDelay(worker, 1, 1, TimeUnit.MICROSECONDS);
+ }
+ public void stepStateMachine() {
+ future.cancel(false);
+ future = executor.scheduleWithFixedDelay(worker, 1, 1, TimeUnit.MICROSECONDS);
+ }
+ public void setDistributedKI(byte[] ki) {
+ distributedKI = ki;
+ }
+ public void setDistributedAN(byte an) {
+ distributedAN = an;
+ }
+ public void setUsingReceiveSAs(boolean status) {
+ usingReceiveSAs = status;
+ }
+ public void setUsingTransmitSA(boolean status) {
+ usingTransmitSA = status;
+ }
+ public MacSecCipherSuit cipherSuit() {
+ return cipherSuit;
+ }
+ public boolean macSecReplayProtect() {
+ return replayProtect;
+ }
+ public boolean macSecProtect() {
+ return protectFrames;
+ }
+ public MacSecValidate macSecValidate() {
+ return validateFrames; } }
diff --git a/src/main/java/org/opencord/aaa/CustomizationInfo.java b/src/main/java/org/opencord/aaa/CustomizationInfo.java
index d0709f2..1183e67 100755
--- a/src/main/java/org/opencord/aaa/CustomizationInfo.java
+++ b/src/main/java/org/opencord/aaa/CustomizationInfo.java
@@ -19,8 +19,6 @@
import org.onosproject.net.device.DeviceService;
import org.opencord.sadis.SubscriberAndDeviceInformationService;
-//import java.util.Map;
-
/**
* Info required to do customization to packets.
*/
diff --git a/src/main/java/org/opencord/aaa/MacSecAlgorithmAgility.java b/src/main/java/org/opencord/aaa/MacSecAlgorithmAgility.java
new file mode 100644
index 0000000..df04f42
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecAlgorithmAgility.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present. * Open Networking Foundation .
+ *
+ * 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.opencord.aaa;
+
+/**
+ * Algorithm Agility.
+ */
+public interface MacSecAlgorithmAgility {
+
+ /* Misc information */
+ public byte[] getParameter();
+
+ /* Various Key Lengths */
+ public int getCakLength();
+ public int getKekLength();
+ public int getIckLength();
+ public int getIcvLength();
+ public int getSakLength();
+
+ /* Transformation Functions */
+ public byte[] generateCak(byte[] msk, byte[] mac1, byte[] mac2);
+ public byte[] generateCkn(byte[] msk, byte[] mac1, byte[] mac2, byte[] sid);
+ public byte[] generateKek(byte[] cak, byte[] ckn);
+ public byte[] generateIck(byte[] cak, byte[] ckn);
+ public byte[] generateIcv(byte[] ick, byte[] msg);
+
+ public byte[] generateSak(byte[] cak, byte[] ksNonce, byte[] memberIDs, int keyNumber);
+
+}
diff --git a/src/main/java/org/opencord/aaa/MacSecCapabilities.java b/src/main/java/org/opencord/aaa/MacSecCapabilities.java
new file mode 100644
index 0000000..8ecbb09
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecCapabilities.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present. * Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+/*
+ * MACSec Capabalities. IEEE 802.1x; Table 11-6
+ */
+public enum MacSecCapabilities {
+ /* Not implemented */
+ NOT_IMPLEMENTED,
+
+ /* 'Integrity without confidentiality' */
+ INTEGRITY,
+
+ /* 'Integrity without confidentiality' and
+ 'Integrity and confidentiality' with a confidentiality offset of 0*/
+ INTEGRITY_AND_CONFIDENTIALITY,
+
+ /* 'Integrity without confidentiality' and
+ 'Integrity and confidentiality' with a confidentiality offset of 0, 30, 50 */
+ INTEGRITY_AND_CONFIDENTIALITY_0_30_50
+}
diff --git a/src/main/java/org/opencord/aaa/MacSecCipherSuit.java b/src/main/java/org/opencord/aaa/MacSecCipherSuit.java
new file mode 100644
index 0000000..747a36e
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecCipherSuit.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017-present. * Open Networking Foundation .
+ *
+ * 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.opencord.aaa;
+
+/*
+ * Cipher Suit representation. IEEE 802.1AE 2006; Clause 14
+ */
+class MacSecCipherSuit {
+ private Long id;
+ private String name;
+ private MacSecCapabilities capabilities;
+ private MacSecConfidentialityOffset confidentialityOffset;
+ private int keyLength;
+
+ /*
+ * MACSec Capabalities. IEEE 802.1x; Table 11-6
+ */
+ public enum MacSecCapabilities {
+ /* Not implemented */
+ NOT_IMPLEMENTED,
+
+ /* 'Integrity without confidentiality' */
+ INTEGRITY,
+
+ /* 'Integrity without confidentiality' and
+ 'Integrity and confidentiality' with a confidentiality offset of 0*/
+ INTEGRITY_AND_CONFIDENTIALITY,
+
+ /* 'Integrity without confidentiality' and
+ 'Integrity and confidentiality' with a confidentiality offset of 0, 30, 50 */
+ INTEGRITY_AND_CONFIDENTIALITY_0_30_50
+ }
+
+ /* Confidentiality Offset. */
+ public enum MacSecConfidentialityOffset {
+ /* No confidentiality. */
+ CONFIDENTIALITY_NONE,
+ CONFIDENTIALITY_OFFSET_0,
+ CONFIDENTIALITY_OFFSET_30,
+ CONFIDENTIALITY_OFFSET_50,
+ }
+
+ MacSecCipherSuit() {
+
+ /* Default Cipher Suit. GCM-AES-128. Clause 14.4 */
+ this.id = Long.parseLong("800200"); //Long.parseLong("0080020001000001");
+ this.name = "GCM-AES-128";
+ this.capabilities = MacSecCapabilities.INTEGRITY_AND_CONFIDENTIALITY_0_30_50;
+ keyLength = 16; /* Unit: bytes. ie. 128bits = 16 bytes */
+ confidentialityOffset = MacSecConfidentialityOffset.CONFIDENTIALITY_OFFSET_0;
+ }
+
+ MacSecCipherSuit(Long id, String name, MacSecCapabilities capabilities,
+ MacSecConfidentialityOffset confidentialityOffset, int keyLength) {
+ this.id = id;
+ this.name = name;
+ this.capabilities = capabilities;
+ this.keyLength = keyLength;
+ this.confidentialityOffset = confidentialityOffset;
+ }
+
+ public int getKeyLength() {
+ return keyLength;
+ }
+
+ public MacSecConfidentialityOffset getConfidentialityOffset() {
+ return confidentialityOffset;
+ }
+
+ public long id() {
+ return id.longValue();
+ }
+}
diff --git a/src/main/java/org/opencord/aaa/MacSecConfig.java b/src/main/java/org/opencord/aaa/MacSecConfig.java
new file mode 100644
index 0000000..47f578b
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecConfig.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2018-present. * Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.basics.BasicElementConfig;
+
+/**
+ * MACSec config for the AAA app.
+ */
+public class MacSecConfig extends Config<ApplicationId> {
+
+ private static final String VERSION = "version";
+ private static final String CAPABILITY = "capability";
+ private static final String CKN = "ckn";
+ private static final String CAK = "cak";
+ private static final String DEFAULT_NETCONF_DEVICE_ID = "netconfDeviceId";
+
+ //MACSec verion
+ protected static final String DEFAULT_VERSION = "1";
+
+ //MACSec capability
+ protected static final String DEFAULT_CAPABILITY = "0";
+
+ //secure Connectivity Association Key Name
+ protected static final String DEFAULT_CKN = "96437a93ccf10d9dfe347846cce52c7d";
+
+ //secure Connectivity Association Key
+ protected static final String DEFAULT_CAK = "135bd758b0ee5c11c55ff6ab19fdb199";
+
+ //Netconf Device Default value
+ protected static final String DEFAULT_NETCONF_DEVICE = "netconf:192.168.1.2:2022";
+
+ /**
+ * Gets the value of a string property, protecting for an empty JSON object.
+ *
+ * @param name name of the property
+ * @param defaultValue default value if none has been specified
+ * @return String value if one os found, default value otherwise
+ */
+ private String getStringProperty(String name, String defaultValue) {
+ if (object == null) {
+ return defaultValue;
+ }
+ return get(name, defaultValue);
+ }
+
+ /**
+ * Version of MACSEC.
+ * @return MACSec version or null if not set.
+ */
+ public String version() {
+ return getStringProperty(VERSION, DEFAULT_VERSION);
+ }
+
+ /**
+ * Sets the MACSec version.
+ *
+ * @param ver New version; null to clear
+ * @return self
+ */
+ public BasicElementConfig version(String ver) {
+ return (BasicElementConfig) setOrClear(VERSION, ver);
+ }
+
+ /**
+ * Capability of MACSEC.
+ *
+ * @return MACSec capability or null if not set
+ */
+ public String capability() {
+ return getStringProperty(CAPABILITY, DEFAULT_CAPABILITY);
+ }
+
+ /**
+ * Sets the MACSec capability.
+ *
+ * @param cap New capability; null to clear
+ * @return self
+ */
+ public BasicElementConfig capability(String cap) {
+ return (BasicElementConfig) setOrClear(CAPABILITY, cap);
+ }
+
+ /**
+ * CKN.
+ *
+ * @return ckn or null if not set
+ */
+ public String ckn() {
+ return getStringProperty(CKN, DEFAULT_CKN);
+ }
+
+ /**
+ * Sets the CKN.
+ *
+ * @param keyName New CKN; null to clear
+ * @return self
+ */
+ public BasicElementConfig ckn(String keyName) {
+ return (BasicElementConfig) setOrClear(CKN, keyName);
+ }
+
+ /**
+ * CAK.
+ *
+ * @return cak or null if not set
+ */
+ public String cak() {
+ return getStringProperty(CAK, DEFAULT_CAK);
+ }
+
+ /**
+ * Sets the CAK.
+ *
+ * @param key New CAK; null to clear
+ * @return self
+ */
+ public BasicElementConfig cak(String key) {
+ return (BasicElementConfig) setOrClear(CAK, key);
+ }
+
+ /*
+ * Returns default Netconf device Id for SecY interface.
+ * @return Device ID
+ * */
+ public DeviceId netConfDeviceId() {
+ return DeviceId.deviceId(getStringProperty(DEFAULT_NETCONF_DEVICE_ID, DEFAULT_NETCONF_DEVICE));
+ }
+}
+
diff --git a/src/main/java/org/opencord/aaa/MacSecDefaultAlgorithmAgility.java b/src/main/java/org/opencord/aaa/MacSecDefaultAlgorithmAgility.java
new file mode 100644
index 0000000..bff4599
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecDefaultAlgorithmAgility.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017-present .* Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+import java.util.Arrays;
+import java.nio.ByteBuffer;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+
+/**
+ * Default algorithm agility implementation. IEEE Std. 802.1X-2009
+ */
+public class MacSecDefaultAlgorithmAgility implements MacSecAlgorithmAgility {
+
+ /* IEEE 802.1X, Table 9-1 */
+ private byte[] parameter = {(byte) 0x00, (byte) 0x80, (byte) 0xC2, (byte) 0x01};
+
+ /* 128 bits for CAK, KEK, ICK, ICV */
+ private int defaultKeyLength = 16;
+
+ /* KDF Parameters */
+ private int kdfContextLength = 16;
+
+ /* Default agility initialization. */
+ MacSecDefaultAlgorithmAgility() {
+ }
+
+ @Override
+ public byte[] getParameter() {
+ return parameter;
+ }
+
+ @Override
+ public int getCakLength() {
+ return defaultKeyLength * 8;
+ }
+
+ @Override
+ public int getKekLength() {
+ return defaultKeyLength * 8;
+ }
+
+ @Override
+ public int getIckLength() {
+ return defaultKeyLength * 8;
+ }
+
+ @Override
+ public int getIcvLength() {
+ return defaultKeyLength * 8;
+ }
+
+ @Override
+ public int getSakLength() {
+ return defaultKeyLength * 8;
+ }
+
+ @Override
+ public byte[] generateCak(byte[] msk, byte[] mac1, byte[] mac2) {
+ /* IEEE 802.1X 2010, Clause 6.2.2. */
+ byte[] cak = null;
+ byte[] label = "IEEE8021 EAP CAK".getBytes();
+ byte[] context = new byte[mac1.length + mac2.length];
+ ByteBuffer contextBuffer = ByteBuffer.wrap(context);
+ contextBuffer.put(mac1);
+ contextBuffer.put(mac2);
+ cak = deriveKey(msk, label, context, (short) getCakLength());
+ return cak;
+ }
+
+ @Override
+ public byte[] generateCkn(byte[] msk, byte[] mac1, byte[] mac2, byte[] sid) {
+ /* IEEE 802.1X 2010, Clause 6.2.2. */
+ byte[] ckn = null;
+ byte[] label = "IEEE8021 EAP CKN".getBytes();
+ byte[] context = new byte[sid.length + mac1.length + mac2.length];
+ ByteBuffer contextBuffer = ByteBuffer.wrap(context);
+ contextBuffer.put(sid);
+ contextBuffer.put(mac1);
+ contextBuffer.put(mac2);
+ ckn = deriveKey(msk, label, context, (short) (defaultKeyLength * 8));
+ return ckn;
+ }
+
+ @Override
+ public byte[] generateKek(byte[] cak, byte[] ckn) {
+ /* IEEE 802.1X 2010, Clause 9.3.3. */
+ byte[] kek = null;
+ byte[] label = "IEEE8021 KEK".getBytes();
+ byte[] keyID = Arrays.copyOfRange(ckn, 0, kdfContextLength);
+ kek = deriveKey(cak, label, keyID, (short) getKekLength());
+ return kek;
+ }
+
+ @Override
+ public byte[] generateIck(byte[] cak, byte[] ckn) {
+ /* IEEE 802.1X 2010, Clause 9.3.3. */
+ byte[] ick = null;
+ byte[] label = "IEEE8021 ICK".getBytes();
+ byte[] keyID = Arrays.copyOfRange(ckn, 0, kdfContextLength);
+ ick = deriveKey(cak, label, keyID, (short) getIckLength());
+ return ick;
+ }
+
+ @Override
+ public byte[] generateIcv(byte[] ick, byte[] msg) {
+ /* IEEE 802.1X-2010 9.4.1
+ * ICV = AES-CMAC(ICK, M, 128) */
+ byte[] icv = null;
+ // icv = prf(ick, msg, 128);
+ icv = prf(ick, msg, msg.length);
+ return icv;
+
+ //return MkaKeyUtils.hexStringToByteArray("045205925831ae59c14550ed59cc003d");
+ }
+
+ @Override
+ public byte[] generateSak(byte[] cak, byte[] ksNonce, byte[] memberIDs, int keyNumber) {
+ byte[] sak = null;
+ byte[] label = "IEEE8021 SAK".getBytes();
+ byte[] kn = ByteBuffer.allocate(4).putInt(keyNumber).array();
+ byte[] context = new byte[ksNonce.length + memberIDs.length + kn.length];
+ ByteBuffer contextBuffer = ByteBuffer.wrap(context);
+ contextBuffer.put(ksNonce);
+ contextBuffer.put(memberIDs);
+ contextBuffer.put(kn);
+ sak = deriveKey(cak, label, context, (short) getSakLength());
+ return sak;
+ }
+
+ /**
+ * Key Derivation Function(KDF). IEEE 802.1X-2010, 6.2.1
+ *
+ * @param key - Key derivation key either 128 or 256 bits
+ * @param label - String identifying the purpose of keys derived using KDF
+ * @param context - Bit string that provides context to identify derived key
+ * @param keyLength - Length of output key in bits encoded in two octets with the most significant octet first
+ * @return Derived Key
+ */
+ private byte[] deriveKey(byte[] key, byte[] label, byte[] context, short keyLength) {
+
+ byte[] length = ByteBuffer.allocate(2).putShort(keyLength).array();
+ int iterations, h;
+ double r = 32;
+
+ // Convert key length into bytes
+ h = key.length;
+
+ // Number of iterations to perform
+ iterations = (key.length * 8 + (h - 1)) / h;
+
+ // Check if iterations exceeds limit of integer capacity
+ if (iterations > (Math.pow(2, r) - 1)) {
+ return null;
+ }
+
+ // Perform key generation
+ ByteArrayOutputStream message = new ByteArrayOutputStream();
+ ByteArrayOutputStream catStream = new ByteArrayOutputStream();
+ try {
+ for (short i = 1; i < iterations; i++) {
+ message.write(i);
+ message.write(label);
+ message.write(0x00);
+ message.write(context);
+ message.write(length);
+ byte[] cat = message.toByteArray();
+ catStream.write(prf(key, cat, cat.length));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ byte[] result = catStream.toByteArray();
+
+ /* Perform shift operation to get the first Length-bits of the result */
+ int bitsToShift = (result.length * 8) - key.length * 8;
+ BigInteger shiftInt = new BigInteger(result).shiftRight(bitsToShift);
+ return shiftInt.toByteArray();
+ }
+
+
+ /**
+ * Pseudo Random Function - Currently AES_CMAC.
+ *
+ * @param key - Input key.
+ * @param message - Message to be hashed/authenticated.
+ * @param length - Length of message.
+ * @return Message Authentication Code (MAC).
+ */
+ private static byte[] prf(byte[] key, byte[] message, int length) {
+ return (new AescMac()).generateAescMac(key, message, length);
+ }
+
+}
diff --git a/src/main/java/org/opencord/aaa/MacSecKaY.java b/src/main/java/org/opencord/aaa/MacSecKaY.java
new file mode 100644
index 0000000..b519233
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecKaY.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017-present.* Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * MKA KaY( MAC Key Agreement Protocol Entity) per device information.
+ * IEEE 802.1X-2010 Clause 9.16, Figure 12-3.
+ */
+public class MacSecKaY {
+ /* KaY activation */
+ boolean enable;
+
+ /* MKA Version */
+ private byte mkaVersion = 0x01;
+
+ /* SecY interface */
+ MacSecSecY secY = null;
+
+ /* Algorithm Agility. IEEE 802.1X 2010; Table 9.1.
+ * Currently standard supports only one. List is used considering future additions. */
+ private List<MacSecAlgorithmAgility> agilities = new ArrayList<>();
+ private static int defaultAlgorithmAgility = 0;
+
+ /* CipherSuits supported by device. */
+ private List<MacSecCipherSuit> cipherSuits = new ArrayList<>();
+ private static int defaultCipherSuitOffset = 0;
+
+ /* Participants & CP state machine store. */
+ private Map<String, Map.Entry<MkaParticipant, CpStateMachine>> portStoreMap =
+ new ConcurrentHashMap<>();
+
+ /* Load all SecY supporting CipherSuits */
+ private void loadCipherSuits() {
+ /* TODO : Load CipherSuits using SecY. */
+ }
+
+ MacSecKaY(MacSecSecY secY) {
+ /* Initialize default algorithm agility. IEEE 802.1X; Table 9.1*/
+ agilities.add(defaultAlgorithmAgility, new MacSecDefaultAlgorithmAgility());
+
+ /* Initialize CipherSuits.
+ * All devices should support default CipherSuit GCM–AES–128. IEEE 802.1AE 14.5
+ */
+ this.secY = secY;
+ cipherSuits.add(defaultCipherSuitOffset, new MacSecCipherSuit());
+ loadCipherSuits();
+ }
+
+ /**
+ * Adding participants & CP state machine to KaY.
+ *
+ * @param id value in <Device ID><Port ID> format
+ * @param participant details
+ * @param cpStateMachine details
+ */
+ public void addPortDetails(String id, MkaParticipant participant, CpStateMachine cpStateMachine) {
+ portStoreMap.put(id, new AbstractMap.SimpleEntry<>(participant, cpStateMachine));
+ }
+
+ /* Retrieve Participant using CAK */
+ public MkaParticipant participant(byte[] ckn) {
+ Optional<Map.Entry<String, Map.Entry<MkaParticipant, CpStateMachine>>> participant =
+ portStoreMap.entrySet().stream()
+ .filter(entry -> entry.getValue().getKey().cak().equals(ckn))
+ .findFirst();
+ return participant.isPresent() ? participant.get().getValue().getKey() : null;
+ }
+
+ /* Participant by ID. */
+ public MkaParticipant lookupParticipantByID(String id) {
+ Map.Entry<MkaParticipant, CpStateMachine> portDetails = portStoreMap.getOrDefault(id, null);
+ return (portDetails != null) ? portDetails.getKey() : null;
+ }
+
+ /* CP state machine by ID */
+ public CpStateMachine lookupCPStateMachineByID(String id) {
+ Map.Entry<MkaParticipant, CpStateMachine> portDetails = portStoreMap.getOrDefault(id, null);
+ return (portDetails != null) ? portDetails.getValue() : null;
+ }
+
+ /* Default algorithm agility. */
+ public MacSecAlgorithmAgility defaultAlgorithmAgility() {
+ return agilities.get(defaultAlgorithmAgility);
+ }
+
+ /* Default cipher suit. */
+ public MacSecCipherSuit defaultCipherSuit() {
+ return cipherSuits.get(defaultCipherSuitOffset);
+ }
+
+ /* Version */
+ public byte mkaVersion() {
+ return mkaVersion;
+ }
+ }
diff --git a/src/main/java/org/opencord/aaa/MacSecSecY.java b/src/main/java/org/opencord/aaa/MacSecSecY.java
new file mode 100644
index 0000000..d7792ec
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MacSecSecY.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017-present. * Open Networking Foundation .
+ *
+ * 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.opencord.aaa;
+
+import org.slf4j.Logger;
+
+import org.onlab.packet.EAPOL_MKPDU_BasicParameterSet.SCI;
+
+import org.onosproject.net.DeviceId;
+
+import org.opencord.aaa.CpStateMachine.MacSecValidate;
+
+import java.util.List;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class MacSecSecY {
+
+ private final Logger log = getLogger(getClass());
+ DeviceId deviceId;
+
+ MacSecSecY(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ /* Verification */
+ public boolean validateFrames(String portName, MacSecValidate validate) {
+ log.trace("Request for Validate Frames: {} {}", portName, validate);
+ return true;
+ }
+
+ /* Generation */
+ public boolean protectFrames(String portName, boolean status) {
+ log.trace("Request for enabling/disabling Protect Frames : {} {}", portName, status);
+ return true;
+ }
+
+ /* Transmit & Receive SCs & SAs. */
+ public boolean createTransmitSC(String portName, SCI sci) {
+ log.trace("Transmit SC creation request : {}, {}", portName, sci);
+ return true;
+ }
+
+ public boolean deleteTransmitSC(String portName, SCI sci) {
+ log.trace("Transmit SC delete request : [{}, {}]", portName, sci);
+ return true;
+ }
+
+ public boolean createTransmitSA(String portName, SCI sci, int an, long pn) {
+ log.trace("Transmit SA creation request : [{}, {}, {}, {}]", portName, sci, an, pn);
+ return true;
+ }
+
+ public boolean deleteTransmitSA(String portName, SCI sci, int an) {
+ log.trace("Transmit SA deletion request : [{},{},{},{}]", portName, sci, an);
+ return true;
+ }
+
+ public boolean enableTransmitSA(String portName, SCI sci, int an) {
+ log.trace("Transmit SA enable request : [{},{},{},{}]", portName, sci, an);
+ return true;
+ }
+
+ public boolean createReceiveSC(String portName, SCI sci) {
+ log.trace("Receive SC creation request : [{}, {}]", portName, sci);
+ return true;
+ }
+
+ public boolean deleteReceiveSC(String portName, SCI sci) {
+ log.trace("Receive SC deletion request : [{}, {}]", portName, sci);
+ return true;
+ }
+
+ public boolean createReceiveSA(String portName, SCI sci, int an, long pn, long lowestPn) {
+ log.trace("Receive SA creation request : [{}, {}, {}, {}, {}]", portName, sci, an, pn,
+ lowestPn);
+ return true;
+ }
+
+ public boolean deleteReceiveSA(String portName, SCI sci, int an) {
+ log.trace("Receive SA deletion request : [{},{},{},{}]", portName, sci, an);
+ return true;
+ }
+
+ public boolean enableReceiveSA(String portName, SCI sci, int an) {
+ log.trace("Receive SA enable request : [{},{},{},{}]", portName, sci, an);
+ return true;
+ }
+
+ /* SAK Keys. */
+ public boolean installKey(String portName, int index, byte[] key, byte[] keyIdentifier,
+ boolean transmits, boolean receives) {
+ log.trace("Received Key Installation request : {} {} {} {} {} {}", portName, index, key,
+ keyIdentifier, transmits, receives);
+ return true;
+ }
+
+ public boolean removeKey(String portName, int index) {
+ log.trace("Received Key Removal request : {} {}", portName, index);
+ return true;
+ }
+
+ /* CipherSuit configuration. */
+ public boolean addCipherSuite(int cipherSuitID, boolean enable,
+ boolean confidentiality) {
+ return true;
+ }
+
+ public boolean setCurrentCipherSuit(String portName, long cipherSuitID) {
+ log.trace("Request for setting current Cipher Suit : {} {}", portName, cipherSuitID);
+ return true;
+ }
+
+ public List<MacSecCipherSuit> getCipherSuites() {
+ return null;
+ }
+
+ /* Port enable/disable*/
+ public boolean enablePort(String portName, boolean status) {
+ log.trace("Enable port request : {} {} {}", portName, status);
+ return true;
+ }
+
+ public boolean replayProtect(String portName, boolean status, int window) {
+ log.trace("Replay protection request : {} {} {}", portName, status, window);
+ return true;
+ }
+}
+
diff --git a/src/main/java/org/opencord/aaa/MkaKeyUtils.java b/src/main/java/org/opencord/aaa/MkaKeyUtils.java
new file mode 100644
index 0000000..3aa7a1e
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MkaKeyUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018-present. * Open Networking Foundation.
+ *
+ * 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.opencord.aaa;
+
+/**
+ * Misc. MKA key utility.
+ */
+public final class MkaKeyUtils {
+
+ static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+ private MkaKeyUtils(){
+
+ }
+ /**
+ * Convert hex string to byte array.
+ *
+ * @param s - Hex string to be converted to byte array
+ * @return byte array formed from the string
+ */
+ public static byte[] hexStringToByteArray(String s) {
+ int len;
+ byte[] data;
+ if (s == null) {
+ return null;
+ }
+
+ len = s.length();
+ data = new byte[len / 2]; /* Two characters forms one byte. */
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
+ /**
+ * Utility to convert byte to hex string.
+ *
+ * @param data - Byte array to be converted to hex string
+ * @return - String representing ASCII hex string of given data.
+ */
+ public static String byteArrayToHexString(byte[] data) {
+ final char[] hexString = new char[data.length << 1];
+ if (data == null) {
+ return null;
+ }
+
+ for (int i = 0, j = 0; i < data.length; i++) {
+ hexString[j++] = HEX_DIGITS[(data[i] & 0xF0) >>> 4];
+ hexString[j++] = HEX_DIGITS[data[i] & 0x0F];
+ }
+ return new String(hexString);
+ }
+
+}
diff --git a/src/main/java/org/opencord/aaa/MkaParticipant.java b/src/main/java/org/opencord/aaa/MkaParticipant.java
new file mode 100644
index 0000000..63fa6a6
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/MkaParticipant.java
@@ -0,0 +1,1340 @@
+/*
+ * Copyright 2017-present. * Open Networking Foundation .
+ *
+ * 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.opencord.aaa;
+
+// import com.sun.xml.internal.bind.v2.runtime.reflect.Lister;
+
+
+import org.onlab.packet.EAPOL;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.EAPOL_MKPDU;
+import org.onlab.packet.EAPOL_MKPDU_ParameterSet;
+import org.onlab.packet.EAPOL_MKPDU_BasicParameterSet;
+import org.onlab.packet.EAPOL_MKPDU_BasicParameterSet.SCI;
+import org.onlab.packet.EAPOL_MKPDU_PeerListParameterSet;
+import org.onlab.packet.EAPOL_MKPDU_PeerListParameterSet.MemberDetails;
+import org.onlab.packet.EAPOL_MKPDU_MACSecUseParameterSet;
+import org.onlab.packet.EAPOL_MKPDU_DistributedSAKParameterSet;
+import org.onlab.packet.EAPOL_MKPDU_ICVIndicatorParameterSet;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketService;
+
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * MKA KaY( MAC Key Agreement Protocol Entity) participants coming under one CA.
+ * IEEE 802.1X-2010 Clause 9.16, Figure 12-3
+ */
+public class MkaParticipant {
+
+ /* Logger */
+ private final Logger log = getLogger(getClass());
+
+ /* KaY(parent), SecY interfaces*/
+ MacSecKaY kay;
+ MacSecSecY secY;
+
+ /* SCI */
+ private SCI actorSci;
+
+ /* Key Details */
+ private byte[] cak;
+ private byte[] ckn;
+ private byte[] kek;
+ private byte[] ick;
+ private byte[] sak;
+
+ /* Actor Details. ie. myself as Actor. */
+ private byte actorPriority;
+ private byte[] mi;
+ private int mn;
+
+ /* Port specific management, monitor & control */
+ private boolean active;
+
+ public enum ActivateType {
+ DEFAULT,
+ DISABLED,
+ ON_OPER_UP,
+ ALWAYS
+ }
+
+ private ActivateType activate;
+ private boolean principal;
+
+ /* MACSec specific management, monitor & control. */
+ /* MACSec Capabalities. IEEE 802.1x; Table 11-6 */
+ private MacSecCipherSuit.MacSecCapabilities macSecCapability;
+ private boolean macSecDesired;
+
+ private List<MkaPeer> livePeers = new ArrayList<>();
+ private List<MkaPeer> potentialPeers = new ArrayList<>();
+
+ /* Latest Key Server Details. ie. Details of Key Server for current SAK. */
+ private byte[] latestKI;
+ private int latestKN;
+ private boolean latestTX;
+ private boolean latestRX;
+ private byte latestAN;
+
+ /* Old Key Server Details. ie. Details of Key Server for previous SAK. */
+ private byte[] oldKI;
+ private int oldKN;
+ private boolean oldTX;
+ private boolean oldRX;
+ private byte oldAN;
+
+ private final byte[] emptyKI = new byte[EAPOL_MKPDU_ParameterSet.FIELD_MI_LENGTH];
+
+ /* AAA State-Machine; for Device, Supplicant access */
+ private StateMachine sm;
+
+ /* Packet Service for packet transmission. */
+ private PacketService ps;
+ private DeviceId deviceId;
+ private Port port;
+
+ /* Key Store */
+ public class KeyStore {
+ class Key {
+ int keyIndex;
+ ByteBuffer keyIdentifier;
+ ByteBuffer key;
+ long createdTime;
+ boolean transmits, receives;
+
+ Key(int keyIndex, byte[] keyIdentifier, byte[] key) {
+ this.keyIndex = keyIndex;
+ this.keyIdentifier = ByteBuffer.wrap(keyIdentifier);
+ this.key = ByteBuffer.wrap(key);
+ createdTime = System.currentTimeMillis();
+ transmits = true;
+ receives = true;
+ }
+
+ public byte[] key() {
+ return key.array();
+ }
+
+ public ByteBuffer keyBuffer() {
+ return key;
+ }
+
+ public byte[] keyIdentifier() {
+ return keyIdentifier.array();
+ }
+
+ public int keyIndex() {
+ return keyIndex;
+ }
+
+ public ByteBuffer keyIdentifierBuffer() {
+ return keyIdentifier;
+ }
+
+
+ /* @Override
+ public boolean equals(Object object) {
+
+ if ((object instanceof Key) && ((Key) object).keyBuffer().equals(key))
+ return true;
+ else if ((object instanceof ByteBuffer) && ((ByteBuffer) object).equals(keyIdentifier))
+ return true;
+ return false;
+ }*/
+
+ @Override
+ public int hashCode() {
+ return keyIdentifier().hashCode();
+ }
+
+ public void setTransmits(boolean status) {
+ transmits = status;
+ }
+
+ public boolean isTransmits() {
+ return transmits;
+ }
+
+ public void setReceives(boolean status) {
+ receives = status;
+ }
+
+ public boolean isReceives() {
+ return receives;
+ }
+ }
+
+ class Store {
+ Map<Key, List<SecureAssociation>> store = new LinkedHashMap<>();
+
+ public void put(Key key, List<SecureAssociation> value) {
+ store.put(key, value);
+ }
+
+ public List<SecureAssociation> get(ByteBuffer keyID) {
+ Optional<Key> key = store.keySet().stream()
+ .filter(k -> k.keyIdentifierBuffer().equals(keyID))
+ .findFirst();
+ List<SecureAssociation> x = key.isPresent() ? store.get(key.get()) : null;
+ return x;
+// return (key != null) ? store.get(key) : null;
+ }
+
+ public Set<Key> keySet() {
+ return store.keySet();
+ }
+
+ }
+
+ Store store = null;
+ AtomicInteger indexSequence = null;
+
+ KeyStore() {
+ store = new Store();
+ indexSequence = new AtomicInteger();
+ }
+
+ public void storeSA(byte[] keyIdentifier, SecureAssociation sa) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ List<SecureAssociation> saList = store.get(keyIDBuffer);
+ if (saList != null) {
+ saList.add(sa);
+ }
+ }
+
+ public List<SecureAssociation> retrieveSAs(byte[] keyIdentifier) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ return store.get(keyIDBuffer);
+
+ //return store.get(keyIDBuffer);
+ }
+
+ public void transmits(byte[] keyIdentifier, boolean status) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ Optional<Key> keyEntry = store.keySet().stream()
+ .filter(k -> k.equals(keyIDBuffer))
+ .findFirst();
+ keyEntry.ifPresent(k -> k.setTransmits(status));
+ }
+
+ public boolean isTransmits(byte[] keyIdentifier) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ Key keyEntry = store.keySet().stream()
+ .filter(k -> k.equals(keyIDBuffer))
+ .findFirst()
+ .get();
+ return (keyEntry != null) ? keyEntry.isTransmits() : false;
+ }
+
+ public void receives(byte[] keyIdentifier, boolean status) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ Optional<Key> keyEntry = store.keySet().stream()
+ .filter(k -> k.equals(keyIDBuffer))
+ .findFirst();
+ keyEntry.ifPresent(k -> k.setReceives(status));
+ }
+
+ public boolean isReceives(byte[] keyIdentifier) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ Key keyEntry = store.keySet().stream()
+ .filter(k -> k.equals(keyIDBuffer))
+ .findFirst()
+ .get();
+ return (keyEntry != null) ? keyEntry.isReceives() : false;
+ }
+
+ public void createKey(byte[] keyIdentifier, byte[] key) {
+ ByteBuffer keyIDBuffer = ByteBuffer.wrap(keyIdentifier);
+ List<SecureAssociation> saList = store.get(keyIDBuffer);
+ if (saList == null) {
+ saList = new LinkedList<>();
+ Key index = new Key(indexSequence.getAndIncrement() % Byte.MAX_VALUE,
+ keyIdentifier, key);
+ store.put(index, saList);
+ secY.installKey(port.number().name(), index.keyIndex(), index.key(),
+ index.keyIdentifier(), index.isTransmits(), index.isReceives());
+ }
+ }
+ }
+
+ KeyStore keyStore = null;
+
+ /* General Secure Connection & Secure Associations. IEEE 802.1AE SecY YANG model. */
+ enum SCType {
+ GENERATION, VERIFICATION
+ }
+
+ abstract class SecureConnection {
+ SCI sci = null;
+ long createdTime;
+ SCType type;
+ AtomicInteger anIndex = new AtomicInteger();
+ List<SecureAssociation> sas = new LinkedList<>();
+
+ SecureConnection(SCI sci, SCType type) {
+ this.sci = sci;
+ this.type = type;
+ createdTime = System.currentTimeMillis();
+ }
+
+ public void attachSA(SecureAssociation sa) {
+ sas.add(sa);
+ }
+
+ public void detachSA(SecureAssociation sa) {
+ sas.removeIf(s -> s.equals(sa));
+ }
+
+ public byte getNextAvailableAN() {
+ return (byte) (anIndex.getAndIncrement() % Byte.MAX_VALUE);
+ }
+
+ public SCI sci() {
+ return sci;
+ }
+
+ public SCType type() {
+ return type;
+ }
+ }
+
+ abstract class SecureAssociation {
+ SecureConnection parent;
+ byte an;
+ long createdTime;
+ boolean inUse;
+
+ SecureAssociation(SecureConnection sc, byte an) {
+ parent = sc;
+ this.an = an;
+ createdTime = System.currentTimeMillis();
+ inUse = false;
+ }
+
+ public SecureConnection parent() {
+ return parent;
+ }
+
+ public byte an() {
+ return an;
+ }
+
+ public void setInUse(boolean status) {
+ inUse = status;
+ }
+ }
+
+ /* Transmit SC. */
+ class TransmitSC extends SecureConnection {
+ byte encodingSA;
+ boolean transmitting;
+
+ /* Transmit SAs */
+ class TransmitSA extends SecureAssociation {
+ long nextPN;
+ boolean confidentiality;
+
+ TransmitSA(TransmitSC sc, byte an, long nextPN) {
+ super(sc, an);
+ this.nextPN = nextPN;
+
+ /* TODO : Confidentiality determine from offset. */
+ /* confidentiality = ? */
+ }
+
+ public long nextPN() {
+ return nextPN;
+ }
+ }
+
+ TransmitSC(SCI sci) {
+ super(sci, SCType.GENERATION);
+ encodingSA = (byte) 0x00;
+ transmitting = false;
+ }
+
+ public SecureAssociation createSA(byte an, long nextPN) {
+ TransmitSA sa = new TransmitSA(this, an, nextPN);
+ attachSA(sa);
+ return sa;
+ }
+ }
+
+ TransmitSC txSC = null;
+
+ /* Receive SC. IEEE 802.1AE SecY YANG model. */
+ class ReceiveSC extends SecureConnection {
+
+ boolean receiving;
+
+ ReceiveSC(SCI sci) {
+ super(sci, SCType.VERIFICATION);
+ receiving = false;
+ }
+
+ class ReceiveSA extends SecureAssociation {
+ long nextPN, lowestPN;
+
+ ReceiveSA(ReceiveSC sc, byte an, long pn, long lpn) {
+ super(sc, an);
+ nextPN = pn;
+ lowestPN = lpn;
+ }
+
+ public long nextPN() {
+ return nextPN;
+ }
+
+ public long lowestPN() {
+ return lowestPN;
+ }
+
+ }
+
+ public SecureAssociation createSA(byte an, long pn, long lpn) {
+ ReceiveSA sa = new ReceiveSA(this, an, pn, lpn);
+ attachSA(sa);
+ return sa;
+ }
+
+ }
+
+ LinkedList<ReceiveSC> rxSCList = new LinkedList<>();
+
+ /* EAPOL-MKPDU serialization/de-serialization supporters.
+ * ie. ParameterSet Creators & Processors */
+ abstract class ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ public ParameterSetCreator(MacSecKaY kaY, MkaParticipant participant) {
+ this.kaY = kaY;
+ this.participant = participant;
+ }
+
+ /* Building parameter set. */
+ abstract EAPOL_MKPDU_ParameterSet buildParameterSet();
+
+ /* Identifying type of parameter set supporting. */
+ abstract int parameterSetType();
+ }
+
+ /* Basic Parameter Set Creator */
+ class BasicParameterSetCreator extends ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ BasicParameterSetCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ EAPOL_MKPDU_BasicParameterSet bps = new EAPOL_MKPDU_BasicParameterSet();
+ bps.setMkaVersion(kay.mkaVersion());
+ bps.setKeyServerPriority(participant.actorPriority());
+ /* TODO: KeyServer Election not done.
+ * So setting myself as infrastructure key server.
+ */
+ bps.setKeyServer(true);
+ bps.setMacSecDesired(participant.macSecDesired);
+ bps.setMacSecCapability((byte) participant.macSecCapability.ordinal());
+ bps.setSci(participant.actorSci());
+ bps.setActorMI(participant.actorMI());
+ bps.setActorMN(participant.retrieveAndUpdateMN());
+ bps.setAlgAgility(kay.defaultAlgorithmAgility().getParameter());
+ bps.setCKN(participant.ckn());
+ return (EAPOL_MKPDU_ParameterSet) bps;
+ }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_BASIC;
+ }
+ }
+
+ /* Live Peer List Parameter Set Creator */
+ class LivePeerListCreator extends ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ LivePeerListCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ /* Ensure Live Peers are existing. */
+ List<MkaPeer> peers = participant.allLivePeers();
+ if (peers.isEmpty()) {
+ return null;
+ }
+ /* Populate Peer list. */
+ EAPOL_MKPDU_PeerListParameterSet lplps =
+ new EAPOL_MKPDU_PeerListParameterSet();
+ lplps.setPeerListType(EAPOL_MKPDU_PeerListParameterSet.PEERLIST_TYPE_LIVE);
+ peers.forEach((peer) -> {
+ lplps.addMember(peer.mi(), peer.mn());
+ });
+ return (EAPOL_MKPDU_ParameterSet) lplps;
+ }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_PeerListParameterSet.PEERLIST_TYPE_LIVE;
+ }
+ }
+
+ /* Potential Parameter Set Creator */
+ class PotentialPeerListCreator extends ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ PotentialPeerListCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ /* Ensure Potential Peers are existing. */
+ List<MkaPeer> peers = participant.allPotentialPeers();
+ if (peers.isEmpty()) {
+ return null;
+ }
+ /* Populate Peer list. */
+ EAPOL_MKPDU_PeerListParameterSet pplps =
+ new EAPOL_MKPDU_PeerListParameterSet();
+ pplps.setPeerListType(EAPOL_MKPDU_PeerListParameterSet.PEERLIST_TYPE_POTENTIAL);
+ peers.forEach((i) -> {
+ pplps.addMember(i.mi(), i.mn());
+ });
+ return (EAPOL_MKPDU_ParameterSet) pplps;
+ }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_PeerListParameterSet.PEERLIST_TYPE_POTENTIAL;
+ }
+ }
+
+ /* MACSec Use Parameter Set Creator */
+ class MacSecUseCreator extends ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ MacSecUseCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ EAPOL_MKPDU_MACSecUseParameterSet ups =
+ new EAPOL_MKPDU_MACSecUseParameterSet();
+ CpStateMachine cpSM = kay.lookupCPStateMachineByID(sm.sessionId());
+ ups.setDelayProtect(cpSM.macSecReplayProtect());
+ ups.setPlainTX(!cpSM.macSecProtect());
+ ups.setPlainRX((cpSM.macSecValidate() == CpStateMachine.MacSecValidate.Disabled) ? true : false);
+
+ // Latest Key Server Details
+ ups.setLatestKI(participant.latestKI());
+ ups.setLatestKN(participant.latestKN());
+ ups.setLatestTX(participant.latestTX());
+ ups.setLatestRX(participant.latestRX());
+ ups.setLatestAN(participant.latestAN());
+
+ /* TODO: Filling Latest Lowest Acceptable PN. */
+ // Retrieve PN for Key Server.
+ // int pn = DEVICE-Abstract-Layer.getPN(participant.latestKI())
+ // ups.setLatestLAPN(pn)
+
+ // Old Key Server Details.
+ /* TODO: Needs rethinking for newly joined actors. */
+ ups.setOldKI(participant.oldKI());
+ ups.setOldKN(participant.oldKN());
+ ups.setOldTX(participant.oldTX());
+ ups.setOldRX(participant.oldRX());
+ ups.setOldAN(participant.oldAN());
+
+ /* TODO: Filling Old Lowest Acceptable PN. */
+ // Retrieve PN for Key Server.
+ // int opn = DEVICE-Abstract-Layer.getPN(participant.oldKI())
+ // ups.setLatestLAPN(opn)
+ return (EAPOL_MKPDU_ParameterSet) ups; }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_MACSEC_SAK_USE;
+ }
+ }
+
+ /* Distributed SAK Parameter Set Creator */
+ class DistributedSakCreator extends ParameterSetCreator {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ DistributedSakCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ EAPOL_MKPDU_DistributedSAKParameterSet dsps =
+ new EAPOL_MKPDU_DistributedSAKParameterSet();
+ dsps.setDistributedAN(participant.latestAN());
+ CpStateMachine cpSM = kay.lookupCPStateMachineByID(sm.sessionId());
+ dsps.setConfidentialityOffset((byte) cpSM.cipherSuit()
+ .getConfidentialityOffset().ordinal());
+ dsps.setKeyNumber(participant.latestKN());
+ dsps.setSAK(participant.sak());
+ dsps.setKeyWrapper((byte[] key) -> {
+ byte[] wrappedKey = new AescMac().wrap(participant.kek(), key);
+ return wrappedKey;
+ });
+ return (EAPOL_MKPDU_ParameterSet) dsps;
+ }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_DISTRIBUTED_SAK;
+ }
+ }
+
+ /* ICV Parameter Set Creator */
+ class IcvCreator extends ParameterSetCreator {
+
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ IcvCreator(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ EAPOL_MKPDU_ParameterSet buildParameterSet() {
+ EAPOL_MKPDU_ICVIndicatorParameterSet icvps = new EAPOL_MKPDU_ICVIndicatorParameterSet();
+ /* Populate PS fields. */
+// icvps.setKey(participant.ick());
+// class AESCMAC_128_HashCreator implements EAPOL_MKPDU_ICVIndicatorParameterSet.HashGenerator<byte[]> {
+// @Override
+// public byte[] generateHash(byte[] key, byte[] msg) {
+// return participant.kay.defaultAlgorithmAgility().generateICV(key, msg);
+// }
+// }
+// icvps.setHashCreator(new AESCMAC_128_HashCreator());
+// icvps.setHashCreator((byte[] key, byte[] msg)
+// -> (kay.defaultAlgorithmAgility().generateICV(key, msg)));
+ return (EAPOL_MKPDU_ParameterSet) icvps;
+ }
+ @Override
+ int parameterSetType() {
+ return EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_ICV_INDICATOR;
+ }
+ }
+
+ Map<Byte, ParameterSetCreator> psCreators = new LinkedHashMap<>();
+
+ /* Parameter Set Processors */
+ abstract class ParameterSetProcessor {
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ public ParameterSetProcessor(MacSecKaY kaY, MkaParticipant participant) {
+ this.kaY = kaY;
+ this.participant = participant;
+ }
+
+ /* Process Parameter Set */
+ abstract boolean process(EAPOL_MKPDU mkpdu, EAPOL_MKPDU_ParameterSet parameterSet);
+ }
+
+ /* Basic Parameter Set Processor */
+ class BasicParameterSetProcessor extends ParameterSetProcessor {
+
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ BasicParameterSetProcessor(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ public boolean process(EAPOL_MKPDU mkpdu, EAPOL_MKPDU_ParameterSet parameterSet) {
+ EAPOL_MKPDU_BasicParameterSet bps = (EAPOL_MKPDU_BasicParameterSet) parameterSet;
+ byte[] mi = bps.getActorMI();
+ int mn = bps.getActorMN();
+
+ /* MKPDU received from new participant then populate in Potential Peer List */
+ MkaPeer livePeer = participant.isLivePeer(mi);
+ MkaPeer potentialPeer = participant.isPotentialPeer(mi);
+ if ((livePeer == null) && (potentialPeer == null)) {
+ MkaPeer peer = new MkaPeer(bps.getSci(), mi, mn);
+ peer.setKeyServer(bps.getKeyServer());
+ peer.setKeyServerPriority(bps.getKeyServerPriority());
+ peer.setMacSecDesired(bps.getMacSecDesired());
+ peer.setMacSecCapability(bps.getMacSecCapacity());
+ peer.setTimestamp(System.currentTimeMillis());
+ participant.addPotentialPeer(peer);
+ } else {
+ /* Already exiting actor, update details.*/
+ MkaPeer currentPeer = (livePeer != null) ? livePeer : potentialPeer;
+ if (currentPeer.mn() < mn) {
+ currentPeer.setMN(mn);
+ currentPeer.setKeyServer(bps.getKeyServer());
+ currentPeer.setKeyServerPriority(bps.getKeyServerPriority());
+ currentPeer.setMacSecDesired(bps.getMacSecDesired());
+ currentPeer.setMacSecCapability(bps.getMacSecCapacity());
+ currentPeer.setTimestamp(System.currentTimeMillis());
+ /* (livePeer != null) ? participant.updateLivePeer(currentPeer)
+ : participant.updatePotentialPeer(currentPeer); */
+ }
+ }
+ /* Myself in peer's peer list and peer is in Potential Peer List */
+ // if ((potentialPeer != null) &&
+ // (participant.myselfPeerOfRemotePeer(eapolMkpdu.getPeerListParameterSet()) == true)) {
+ // /* Update Live Peer list : */
+ // participant.addLivePeer(participant.removePotentialPeer(mi));
+ // }
+ return true;
+ }
+ }
+
+ /* Live Peer List Parameter Set Processor. */
+ class LivePeerListProcessor extends ParameterSetProcessor {
+
+ MacSecKaY kaY;
+ MkaParticipant participant;
+
+ LivePeerListProcessor(MacSecKaY kaY, MkaParticipant participant) {
+ super(kaY, participant);
+ this.kaY = kay;
+ this.participant = participant;
+ }
+
+ @Override
+ boolean process(EAPOL_MKPDU mkpdu, EAPOL_MKPDU_ParameterSet parameterSet) {
+ /* Actor basic details. */
+ EAPOL_MKPDU_BasicParameterSet bps = mkpdu.getBasicParameterSet();
+ final byte[] actorMI = bps.getActorMI();
+ int actorMN = bps.getActorMN();
+ /* Process each peer details in Live Parameter Set */
+ EAPOL_MKPDU_PeerListParameterSet lplPS = (EAPOL_MKPDU_PeerListParameterSet) parameterSet;
+ List<MemberDetails> lplMembers = lplPS.getMembers();
+ lplMembers.forEach(m -> {
+ byte[] mID = m.getMemberID();
+ int mn = m.getMessageNo();
+ /* Live peer is myself. */
+ if (Arrays.equals(mID, participant.mi)) {
+ /* Check peer is in Potential Peer list, if so update to Live Peers list. */
+ if (participant.isPotentialPeer(actorMI) != null) {
+ MkaPeer peer = participant.removePotentialPeer(actorMI);
+ participant.addLivePeer(peer);
+ participant.createReceiveSC(peer.getSci());
+ secY.createReceiveSA(port.number().name(), peer.getSci(), txSC.getNextAvailableAN(),
+ 1, 1);
+ }
+ return;
+ }
+ /* Update if peer is in live list, otherwise add new in potential list. */
+ MkaPeer peer = participant.isLivePeer(mID);
+ if (peer != null) {
+ peer.setMN(mn);
+ peer.setTimestamp(System.currentTimeMillis());
+ } else {
+ MkaPeer potentialPeer = new MkaPeer(peer.getSci(), mID, mn);
+ potentialPeer.setTimestamp(System.currentTimeMillis());
+ participant.addPotentialPeer(potentialPeer);
+ }
+ });
+ return false;
+ }
+ }
+
+ Map<Byte, ParameterSetProcessor> psProcessors = new LinkedHashMap<>();
+
+ /* Hello Timer Executor */
+ ScheduledExecutorService executorService;
+
+ /* PAE Group Address. */
+ public static final byte[] HELLO_MKPDU_SEND_ADDRESS = {(byte) 0x01, (byte) 0x80, (byte) 0xC2,
+ (byte) 0x00, (byte) 0x00, (byte) 0x03};
+
+ /* Constructor using Key & KeyName */
+ MkaParticipant(StateMachine sm, PacketService ps,
+ ScheduledExecutorService executerService, MacSecKaY kay, MacSecSecY secY, byte[] key,
+ byte[] keyName, SCI actorSci, DeviceId deviceId, Port port) {
+
+ /* Sanitize. Evaluate CAK & CKN */
+ if ((key == null) || (keyName == null)) {
+ /* TODO : */
+ }
+ if ((kay.defaultAlgorithmAgility().getCakLength() != key.length)) {
+ /* TODO : */
+ }
+
+ /* Initialize KaY, SM, Packet Service etc. */
+ this.kay = kay;
+ this.secY = secY;
+ this.sm = sm;
+ this.ps = ps;
+ this.executorService = executerService;
+
+ /* Default MACSec RX/TX Protection Features. */
+ macSecCapability = MacSecCipherSuit.MacSecCapabilities.INTEGRITY_AND_CONFIDENTIALITY;
+ macSecDesired = true;
+
+ /* KeyStore, SC & SA details */
+ keyStore = new KeyStore();
+ this.actorSci = actorSci;
+ this.port = port;
+ txSC = new TransmitSC(actorSci);
+ secY.createTransmitSC(port.number().name(), actorSci);
+
+ /* Key details update. */
+ cak = key;
+ ckn = keyName;
+
+ /* Clear Member ID */
+ resetMI();
+
+ /* Initialize Key Server Details. */
+ this.deviceId = deviceId;
+ latestKI = mi;
+ latestKN = 1; /* TODO: Starting KeyNumber Configurable. */
+ latestTX = true;
+ latestRX = true;
+ latestAN = 0x01; /* TODO: Association Number should be initialized using standard method. */
+ oldKI = emptyKI; /* Cleared old Member ID. */
+ oldKN = 0;
+ oldTX = latestTX;
+ oldRX = latestRX;
+ oldAN = 0X00; /* Cleared old AN. */
+
+ /* Derive KEK, ICK from CAK, CKN */
+ MacSecAlgorithmAgility algorithmAgility = kay.defaultAlgorithmAgility();
+ kek = algorithmAgility.generateKek(cak, ckn);
+ ick = algorithmAgility.generateIck(cak, ckn);
+
+ /* Derive SAK from CAK */
+ byte[] ksNonce = new byte[16];
+ SecureRandom randomGenerator = new SecureRandom();
+ randomGenerator.nextBytes(ksNonce);
+ sak = algorithmAgility.generateSak(cak, ksNonce, mi, 1);
+ keyStore.createKey(latestKI, sak);
+
+ /* Initialize Parameter Creators. */
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_BASIC,
+ new BasicParameterSetCreator(kay, this));
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_LIVE_PEER_LIST,
+ new LivePeerListCreator(kay, this));
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_POTENTIAL_PEER_LIST,
+ new PotentialPeerListCreator(kay, this));
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_MACSEC_SAK_USE,
+ new MacSecUseCreator(kay, this));
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_DISTRIBUTED_SAK,
+ new DistributedSakCreator(kay, this));
+ /* TODO: Other parameter sets filling. */
+ psCreators.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_ICV_INDICATOR,
+ new IcvCreator(kay, this));
+
+ /* Initialize Parameter Processors. */
+ psProcessors.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_BASIC,
+ new BasicParameterSetProcessor(kay, this));
+ psProcessors.put(EAPOL_MKPDU_ParameterSet.PARAMETERSET_TYPE_LIVE_PEER_LIST,
+ new LivePeerListProcessor(kay, this));
+ /* TODO : Other parameter set processing. */
+ }
+
+ /* Actor SCI */
+ public SCI actorSci() {
+ return actorSci;
+ }
+ public void setActorSci(SCI sci) {
+ this.actorSci = sci;
+ }
+
+ /* Outbound device details for packet transmission. */
+ public DeviceId deviceId() {
+ return deviceId;
+ }
+ public void setDeviceId(DeviceId deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ /* Outbound port details for packet transmission. */
+ public Port port() {
+ return port;
+ }
+ public void setPort(Port port) {
+ this.port = port;
+ }
+
+ /* Process received EAPOL-MKPDU */
+ public void receive(EAPOL_MKPDU mkpdu) {
+ /* Process Various Parameter Sets */
+ ((LinkedHashMap<Byte, ParameterSetProcessor>) psProcessors).forEach((key, psp) -> {
+ EAPOL_MKPDU_ParameterSet ps = mkpdu.getParameterSet(key);
+ if ((ps != null) && (psp != null)) {
+ psp.process(mkpdu, ps);
+ }
+ });
+ return;
+ }
+
+ /* Start ticking heartbeat. ie enables Timers. */
+ public void startTicking() {
+ /* Initialize Hello Timer. */
+ class WorkerThread implements Runnable {
+ MkaParticipant participant;
+ WorkerThread(MkaParticipant participant) {
+ this.participant = participant;
+ }
+ public void run() {
+ /* TODO: Make sure my lifespan exists. */
+ /* TODO: Clear expired Live Peers. */
+ /* TODO : Clear expired Potential Peers. */
+ /* TODO: Regenerate SAK if needed snd send it. */
+ /* Build & Send MKPDU */
+ log.info("Hello-Timer : Transmitting EAPOL-MKPDU....");
+ participant.transmitMkpdu();
+ log.info("Done.");
+ }
+ }
+ executorService.scheduleAtFixedRate(new WorkerThread(this), 5, 5, TimeUnit.SECONDS);
+ }
+
+ /* Building & Sending EAPOL-MKPDU. Hello Timer's routine. */
+ public void transmitMkpdu() {
+
+ /* EAPOL-MKA packet building & populating Parameter Sets*/
+ EAPOL_MKPDU mkpdu = new EAPOL_MKPDU();
+ for (Map.Entry<Byte, ParameterSetCreator> entry : psCreators.entrySet()) {
+ ParameterSetCreator c = entry.getValue();
+ mkpdu.addParameterSet(entry.getKey(), c.buildParameterSet());
+ }
+
+ /* EAPOL Header Filling */
+ EAPOL eapol = new EAPOL();
+ eapol.setEapolType(EAPOL.EAPOL_MKA);
+ eapol.setPacketLength(mkpdu.packetLength());
+ eapol.setPayload(mkpdu);
+
+ /* Ethernet Header Filling. */
+ Ethernet ethernet = new Ethernet();
+ ethernet.setDestinationMACAddress(HELLO_MKPDU_SEND_ADDRESS);
+ ethernet.setSourceMACAddress(this.actorSci().address());
+ ethernet.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
+ ethernet.setVlanID(Ethernet.VLAN_UNTAGGED);
+ ethernet.setPayload(eapol);
+ ethernet.setPad(false);
+
+ /* ICV Calculation and Populating */
+ ByteBuffer frame = ByteBuffer.wrap(ethernet.serialize());
+ byte[] frameWoIcv = new byte[frame.capacity() -
+ kay.defaultAlgorithmAgility().getIcvLength() / 8];
+ frame.get(frameWoIcv);
+ byte[] icv = kay.defaultAlgorithmAgility().generateIcv(ick, frameWoIcv);
+ frame.position(frame.capacity() -
+ kay.defaultAlgorithmAgility().getIcvLength() / 8);
+ frame.put(icv);
+
+ /* Send packet to supplicant. */
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(port.number()).build();
+ OutboundPacket packet = new DefaultOutboundPacket(deviceId, treatment, frame);
+ ps.emit(packet);
+ log.info("Flushed out Hello-Timer EAPOL-MKA PDU.");
+ }
+
+ /* CP state machine KaY interactions. IEEE 802.1X 2010 Figure 12-2. */
+ public void createReceiveSC(SCI sci) {
+ if (sci == null) {
+ log.info("Invalid SCI provided for SC creation.");
+ return;
+ }
+ rxSCList.add(new ReceiveSC(sci));
+ secY.createReceiveSC(port.number().name(), sci);
+ }
+
+ /* Creating Receive & Transmit SAs /*/
+ public void createSAs(byte[] keyIdentifier) {
+ List<SecureAssociation> sas = keyStore.retrieveSAs(keyIdentifier);
+ if (sas != null && sas.size() > 0) {
+ log.error("Improper keystore stage : {}", keyIdentifier);
+ return;
+ }
+
+ /* Create Transmit SAs */
+ SecureAssociation sa = txSC.createSA(txSC.getNextAvailableAN(), 1);
+ keyStore.storeSA(keyIdentifier, sa);
+ secY.createTransmitSA(port.number().name(), txSC.sci(), sa.an(), 1);
+ keyStore.transmits(keyIdentifier, true);
+
+ /* Create Receive SAs */
+ rxSCList.stream().forEach(sc -> {
+ SecureAssociation rxSA = sc.createSA(sc.getNextAvailableAN(), 1, 1);
+ keyStore.storeSA(keyIdentifier, rxSA);
+ secY.createReceiveSA(port.number().name(), sc.sci(), rxSA.an(), 1, 1);
+ });
+ keyStore.receives(keyIdentifier, true);
+ }
+
+ /* Deleting SAs associated with certain key. */
+ public void deleteSAs(byte[] keyIdentifier) {
+ List<SecureAssociation> saList = keyStore.retrieveSAs(keyIdentifier);
+ if (saList == null) {
+ log.error("Failed looking up key store. Key ID : {}", keyIdentifier);
+ return;
+ }
+ saList.stream().forEach(sa -> {
+ SecureConnection sc = sa.parent();
+ if (sc.type() == SCType.GENERATION) {
+ secY.deleteTransmitSA(port.number().name(), sc.sci(), sa.an());
+ } else {
+ secY.deleteReceiveSA(port.number().name(), sc.sci(), sa.an());
+ }
+ sc.detachSA(sa);
+ });
+ keyStore.transmits(keyIdentifier, false);
+ keyStore.receives(keyIdentifier, false);
+ }
+
+ public void enableReceiveSAs(byte[] keyIdentifier) {
+ List<SecureAssociation> saList = keyStore.retrieveSAs(keyIdentifier);
+ if (saList == null) {
+ log.error("Failed looking up key store. Key ID : {}", keyIdentifier);
+ return;
+ }
+
+ // Iterate by filtering receive SAs in store and enable it
+ CpStateMachine cpSM = kay.lookupCPStateMachineByID(sm.sessionId());
+ saList.stream().filter(sa -> sa.parent().type() == SCType.GENERATION)
+ .forEach(sa -> {
+ SecureConnection sc = sa.parent();
+ sa.setInUse(true);
+ secY.enableReceiveSA(port.number().name(), sc.sci(), sa.an());
+ cpSM.setUsingReceiveSAs(true);
+ cpSM.stepStateMachine();
+ });
+ return;
+ }
+
+ public void enableTransmitSA(byte[] keyIdentifier) {
+ List<SecureAssociation> saList = keyStore.retrieveSAs(keyIdentifier);
+ if (saList == null) {
+ log.error("Failed looking up key store. Key ID : {}", keyIdentifier);
+ return;
+ }
+
+ // Iterate by filtering receive SAs in store and enable it
+ CpStateMachine cpSM = kay.lookupCPStateMachineByID(sm.sessionId());
+ saList.stream().filter(sa -> sa.parent().type() == SCType.GENERATION)
+ .forEach(sa -> {
+ SecureConnection sc = sa.parent();
+ sa.setInUse(true);
+ secY.enableTransmitSA(port.number().name(), sc.sci(), sa.an());
+ cpSM.setUsingTransmitSA(true);
+ cpSM.stepStateMachine();
+ });
+ return;
+ }
+
+ public void setOldSAAttributes(byte[] ki, byte an, boolean tx, boolean rx) {
+ oldKI = (ki == null) ? emptyKI : ki;
+ oldAN = an; oldTX = tx; oldRX = rx;
+ }
+
+ public void setLatestSAAttributes(byte[] ki, byte an, boolean tx, boolean rx) {
+ latestKI = (ki == null) ? emptyKI : ki;
+ latestAN = an; latestTX = tx; latestRX = rx;
+ }
+
+ /* Peer details storing. */
+ protected class MkaPeer {
+ /* Basic Parameter Set details.*/
+ SCI sci;
+ private byte[] mi;
+ private int mn;
+ private boolean isKeyServer;
+ private byte keyServerPriority;
+ private boolean macSecDesired;
+ private byte macSecCapbility;
+ /* Expire time details */
+ private long expireTimestamp;
+ MkaPeer(SCI sci, byte[] mi, int mn) {
+ this.sci = sci;
+ this.mi = mi;
+ this.mn = mn;
+ }
+ /* Member Identifier */
+ public byte[] mi() {
+ return mi;
+ }
+ /* Message Number */
+ public int mn() {
+ return mn;
+ }
+ public void setMN(int mn) {
+ this.mn = mn;
+ }
+ /* Key Server Status */
+ public boolean keyServer() {
+ return isKeyServer;
+ }
+ public void setKeyServer(boolean keyServer) {
+ this.isKeyServer = keyServer;
+ }
+ /* Key Server Priority */
+ public byte keyServerPriority() {
+ return keyServerPriority;
+ }
+ public void setKeyServerPriority(byte keyServerPriority) {
+ this.keyServerPriority = keyServerPriority;
+ }
+ /* MACSec desirability */
+ public boolean macSecDesired() {
+ return macSecDesired;
+ }
+ public void setMacSecDesired(boolean macSecDesired) {
+ this.macSecDesired = macSecDesired;
+ }
+ /* MACSec capability */
+ public byte macSecCapability() {
+ return macSecCapbility;
+ }
+ public void setMacSecCapability(byte macSecCapbility) {
+ this.macSecCapbility = macSecCapbility;
+ }
+ /* Expire time details. */
+ public void setTimestamp(long timestamp) {
+ expireTimestamp = timestamp;
+ }
+ public long getTimestamp() {
+ return expireTimestamp;
+ }
+ public SCI getSci() {
+ return sci;
+ }
+ }
+
+ /* Live peer or not ? */
+ protected MkaPeer isLivePeer(byte[] mi) {
+ MkaPeer p = livePeers.stream()
+ .filter((q) -> Arrays.equals(q.mi(), mi))
+ .findAny()
+ .orElse(null);
+ return p;
+ }
+
+ /* Live peers at a time. */
+ protected List<MkaPeer> allLivePeers() {
+ return livePeers;
+ }
+
+ /* Potential peer or not ? */
+ protected MkaPeer isPotentialPeer(byte[] mi) {
+ MkaPeer p = potentialPeers.stream()
+ .filter((q) -> Arrays.equals(q.mi(), mi))
+ .findAny()
+ .orElse(null);
+ // if( p != null ) {
+ // /* Update MN if new. */
+ // if (p.getMN() < mn) p.setMN(mn);
+ // }
+ return p;
+ }
+
+ /* All Potential Peers at a time. */
+ protected List<MkaPeer> allPotentialPeers() {
+ return potentialPeers;
+ }
+
+ /* Populate Potential Peer */
+ protected void addPotentialPeer(MkaPeer p) {
+ if (p != null && (isPotentialPeer(p.mi()) == null)) {
+ potentialPeers.add(p);
+ }
+ return;
+ }
+
+ /* Remove Potential Peer */
+ protected MkaPeer removePotentialPeer(byte[] mi) {
+ MkaPeer p = potentialPeers.stream()
+ .filter((q) -> Arrays.equals(q.mi(), (mi)))
+ .findAny()
+ .orElse(null);
+ if (p != null) {
+ potentialPeers.remove(p);
+ }
+ return p;
+ }
+
+ /* Populate Live Peer */
+ protected void addLivePeer(MkaPeer p) {
+ if (p != null && (isLivePeer(p.mi()) == null)) {
+ livePeers.add(p);
+ }
+ return;
+ }
+
+ /* Check myself present in remote peer's peer list */
+ protected boolean myselfPeerOfRemotePeer(EAPOL_MKPDU_PeerListParameterSet peerList) {
+ /* Handle Non-Peer List cases. */
+ if (peerList == null) {
+ return false;
+ }
+ if (peerList.getParameterSetType() != EAPOL_MKPDU_PeerListParameterSet.PEERLIST_TYPE_LIVE) {
+ return false;
+ }
+ return peerList.memberExists(mi);
+ }
+
+ /* Resets MI & MN */
+ protected void resetMI() {
+ SecureRandom random = new SecureRandom();
+ mi = new byte[EAPOL_MKPDU_ParameterSet.FIELD_MI_LENGTH];
+ random.nextBytes(mi);
+ /* TODO : Check IEEE 802.1X and finalize. */
+ mn = 1;
+ }
+
+ /* Key Server Priority */
+ protected byte actorPriority() {
+ return actorPriority;
+ }
+
+ /* Key Server MI */
+ protected byte[] actorMI() {
+ return mi;
+ }
+
+ /* Key Server MN */
+ protected int actorMN() {
+ return mn;
+ }
+
+ /* Retrieve and update Key Server MN */
+ protected int retrieveAndUpdateMN() {
+ int currentMN = mn;
+ /* Update MN for each Tx. */
+ mn += 1;
+ return currentMN;
+ }
+
+ /* Various Getters & Setters. */
+
+ /* Various Keys. */
+ protected byte[] cak() {
+ return cak;
+ }
+
+ protected byte[] ckn() {
+ return ckn;
+ }
+
+ protected byte[] sak() {
+ return sak;
+ }
+
+ protected byte[] ick() {
+ return ick;
+ }
+
+ protected byte[] kek() {
+ return kek;
+ }
+
+ /* Current Key Server Key Number */
+ protected int latestKN() {
+ return latestKN;
+ }
+
+ /* Current Key Server Identifier */
+ protected byte[] latestKI() {
+ return latestKI;
+ }
+
+ /* Current Key Server Association Number */
+ protected byte latestAN() {
+ return latestAN;
+ }
+
+ /* Current Key Server used for TX protection ? */
+ protected boolean latestTX() {
+ return latestTX;
+ }
+
+ /* Current Key Server used for RX protection ? */
+ protected boolean latestRX() {
+ return latestRX;
+ }
+
+ /* Old Key Server Identifier */
+ protected byte[] oldKI() {
+ return oldKI;
+ }
+
+ /* Old Key Server Key Number */
+ protected int oldKN() {
+ return oldKN;
+ }
+
+ /* Old Key Server Association Number */
+ protected byte oldAN() {
+ return oldAN;
+ }
+
+ /* Old Key Server used for TX protection ? */
+ protected boolean oldTX() {
+ return oldTX;
+ }
+
+ /* Old Key Server used for RX protection ? */
+ protected boolean oldRX() {
+ return oldRX;
+ }
+}
diff --git a/src/main/java/org/opencord/aaa/PortBasedRadiusCommunicator.java b/src/main/java/org/opencord/aaa/PortBasedRadiusCommunicator.java
index cf53b68..9210ad0 100755
--- a/src/main/java/org/opencord/aaa/PortBasedRadiusCommunicator.java
+++ b/src/main/java/org/opencord/aaa/PortBasedRadiusCommunicator.java
@@ -49,8 +49,6 @@
import org.slf4j.Logger;
-import com.google.common.collect.Maps;
-
import static org.onosproject.net.packet.PacketPriority.CONTROL;
import static org.slf4j.LoggerFactory.getLogger;
@@ -58,6 +56,7 @@
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Handles communication with the RADIUS server through ports
@@ -125,7 +124,8 @@
this.pktCustomizer = pktCustomizer;
this.aaaManager = aaaManager;
- ipToSnMap = Maps.newConcurrentMap();
+ //ipToSnMap = Maps.newConcurrentMap();
+ ipToSnMap = new ConcurrentHashMap<>();
mastershipService.addListener(changeListener);
deviceService.addListener(deviceListener);
diff --git a/src/main/java/org/opencord/aaa/StateMachine.java b/src/main/java/org/opencord/aaa/StateMachine.java
index 6795c43..d4c7904 100644
--- a/src/main/java/org/opencord/aaa/StateMachine.java
+++ b/src/main/java/org/opencord/aaa/StateMachine.java
@@ -18,6 +18,7 @@
package org.opencord.aaa;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
@@ -27,8 +28,6 @@
import org.slf4j.Logger;
-import com.google.common.collect.Maps;
-
import static org.slf4j.LoggerFactory.getLogger;
/**
@@ -118,8 +117,10 @@
private static Map<Integer, StateMachine> identifierMap;
public static void initializeMaps() {
- sessionIdMap = Maps.newConcurrentMap();
- identifierMap = Maps.newConcurrentMap();
+ //sessionIdMap = Maps.newConcurrentMap();
+ sessionIdMap = new ConcurrentHashMap<>();
+ //identifierMap = Maps.newConcurrentMap();
+ identifierMap = new ConcurrentHashMap<>();
identifier = -1;
}