Skip to content

Commit

Permalink
feat(nm): Backend implementation to support EAP-TLS + Minor WebUI fix…
Browse files Browse the repository at this point in the history
…es (#4872)

* feat: initial implimentation of tls backend

* fix: weird formatter oddity

* fix: removed loggers

* refactor: ifPresent if's changed to lamdas

* tests: update for new tls changes

* fix: revert un-needed change

* feat: added support for multiple keystores

* tests: removed not needed logging

* fix: improved backend stability, and fixed regex

* refactor: changed the way the keystore var is passed

* feat: added more type security in webUI

* refactor: fix comments enable -> unable

* refactor: removed to string

* refactor: certificate replacement method

* refactor: removed not needed Exception

* refactor: decryptAndConvertCertificates method

* refactor: removed extra newline

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>

* refactor: error message

* refactor: added more if checks to improve reliability when applying from webUI

* refactor: created a hard copy of the modifiedMap so it is not passed to the configuration service and applied

* refactor: removed extra 802-1x in String

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>

* refactor: add specificity to to isCertificate method

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>

* refactor: updated String for better readability

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>

* refactor: add extra safety checks in getTrustedCertificateFromKeystoreMethod

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>

* lint: fix whitespace issues

* fix: changed 802-1x -> 802.1x

* refactor: removed unnecessary cast to String

* refactor: removed extra curly braces in lamdas

* refactor: removed type in <>

* tests: added basic enterprise test coverage

* test: added method for mocking keystore

* fix: added static variable for NM_SECRET_FLAGS_NOT_REQUIRED

* fix: removed generic exception logging, and changed exceptions

---------

Co-authored-by: Mattia Dal Ben <mattdibi@users.noreply.github.com>
  • Loading branch information
2 people authored and pierantoniomerlino committed Oct 20, 2023
1 parent ce3b698 commit 8fbf6f9
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 87 deletions.
2 changes: 2 additions & 0 deletions kura/org.eclipse.kura.nm/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Import-Package: org.apache.commons.io;version="2.4.0",
org.eclipse.kura.core.net.modem;version="[1.0,2.0)",
org.eclipse.kura.core.net.util;version="[1.0,2.0)",
org.eclipse.kura.core.util;version="[1.0,2.0)",
org.eclipse.kura.core.keystore.util;version="[1.0,2.0)",
org.eclipse.kura.crypto;version="[1.1,2.0)",
org.eclipse.kura.executor;version="[1.0,2.0)",
org.eclipse.kura.internal.linux.net.dns;version="[1.0,2.0)",
Expand All @@ -35,6 +36,7 @@ Import-Package: org.apache.commons.io;version="2.4.0",
org.eclipse.kura.net.status.vlan;version="[1.0,2.0)",
org.eclipse.kura.net.wifi;version="[2.4,3.0]",
org.eclipse.kura.usb;version="[1.0,2.0)",
org.eclipse.kura.security.keystore;version="[1.0,2.0)",
org.osgi.framework;version="1.5.0",
org.osgi.service.component;version="1.2.0",
org.osgi.service.event;version="1.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
<reference bind="setExecutorService" cardinality="1..1" interface="org.eclipse.kura.executor.PrivilegedExecutorService" name="PrivilegedExecutorService" policy="static" unbind="unsetExecutorService"/>
<reference bind="setDnsServerService" cardinality="1..1" interface="org.eclipse.kura.internal.linux.net.dns.DnsServerService" name="DNSService" policy="static" />
<reference name="CryptoService" interface="org.eclipse.kura.crypto.CryptoService" bind="setCryptoService" unbind="unsetCryptoService" cardinality="1..1" policy="static"/>
<reference name="KeystoreService" interface="org.eclipse.kura.security.keystore.KeystoreService" bind="setKeystoreService" unbind="unsetKeystoreService" cardinality="0..n" policy="dynamic"/>
</scr:component>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
package org.eclipse.kura.nm.configuration;

import java.net.UnknownHostException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -25,9 +29,10 @@
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.eclipse.kura.KuraErrorCode;
import org.eclipse.kura.KuraException;
import org.eclipse.kura.configuration.ComponentConfiguration;
import org.eclipse.kura.configuration.ConfigurationService;
import org.eclipse.kura.configuration.Password;
import org.eclipse.kura.configuration.SelfConfiguringComponent;
import org.eclipse.kura.crypto.CryptoService;
Expand All @@ -45,6 +50,7 @@
import org.eclipse.kura.nm.configuration.monitor.DnsServerMonitor;
import org.eclipse.kura.nm.configuration.writer.DhcpServerConfigWriter;
import org.eclipse.kura.nm.configuration.writer.FirewallNatConfigWriter;
import org.eclipse.kura.security.keystore.KeystoreService;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.osgi.service.component.ComponentContext;
Expand All @@ -61,19 +67,20 @@ public class NMConfigurationServiceImpl implements SelfConfiguringComponent {
private static final String MODIFIED_INTERFACE_NAMES = "modified.interface.names";
private static final String MODEM_PORT_REGEX = "^\\d+-\\d+";
private static final Pattern PPP_INTERFACE = Pattern.compile("ppp\\d+");

private static final List<NetInterfaceType> SUPPORTED_NAT_INTERFACE_TYPES = Arrays.asList(
NetInterfaceType.ETHERNET, NetInterfaceType.WIFI, NetInterfaceType.MODEM,
NetInterfaceType.VLAN);
private static final List<NetInterfaceType> SUPPORTED_DHCP_SERVER_INTERFACE_TYPES = Arrays.asList(
NetInterfaceType.ETHERNET, NetInterfaceType.WIFI, NetInterfaceType.VLAN);

private static final List<NetInterfaceType> SUPPORTED_NAT_INTERFACE_TYPES = Arrays.asList(NetInterfaceType.ETHERNET,
NetInterfaceType.WIFI, NetInterfaceType.MODEM, NetInterfaceType.VLAN);
private static final List<NetInterfaceType> SUPPORTED_DHCP_SERVER_INTERFACE_TYPES = Arrays
.asList(NetInterfaceType.ETHERNET, NetInterfaceType.WIFI, NetInterfaceType.VLAN);

private NetworkService networkService;
private DnsServerService dnsServer;
private EventAdmin eventAdmin;
private CommandExecutorService commandExecutorService;
private CryptoService cryptoService;

private Map<String, KeystoreService> keystoreServices = new HashMap<>();

private DhcpServerMonitor dhcpServerMonitor;
private DnsServerMonitor dnsServerMonitor;

Expand Down Expand Up @@ -127,6 +134,16 @@ public void unsetCryptoService(CryptoService cryptoService) {
}
}

public void setKeystoreService(KeystoreService keystoreService, Map<String, Object> properties) {
this.keystoreServices.put((String) properties.get(ConfigurationService.KURA_SERVICE_PID), keystoreService);
}

public void unsetKeystoreService(KeystoreService keystoreService, Map<String, Object> properties) {
if (this.keystoreServices.containsValue(keystoreService)) {
this.keystoreServices.remove(properties.get(ConfigurationService.KURA_SERVICE_PID));
}
}

public void setDnsServerService(DnsServerService dnsServer) {
this.dnsServer = dnsServer;
}
Expand Down Expand Up @@ -213,13 +230,16 @@ public synchronized void update(Map<String, Object> receivedProperties) {
}
if (NetInterfaceType.MODEM.equals(interfaceTypeProperty.get())) {
setModemPppNumber(modifiedProps, interfaceName);
}
}
}

mergeNetworkConfigurationProperties(modifiedProps, this.networkProperties.getProperties());

this.networkProperties = new NetworkProperties(
discardModifiedNetworkInterfaces(new HashMap<>(modifiedProps)));

decryptAndConvertPasswordProperties(modifiedProps);
this.networkProperties = new NetworkProperties(discardModifiedNetworkInterfaces(modifiedProps));
decryptAndConvertCertificatesProperties(modifiedProps, interfaces);

writeNetworkConfigurationSettings(modifiedProps);
writeFirewallNatRules(interfaces, modifiedProps);
Expand Down Expand Up @@ -252,7 +272,7 @@ protected void setModemPppNumber(Map<String, Object> modifiedProps, String inter
Integer pppNum = Integer.valueOf(this.networkService.getModemPppInterfaceName(interfaceName).substring(3));
modifiedProps.put(String.format(PREFIX + "%s.config.pppNum", interfaceName), pppNum);
}

protected void setInterfaceType(Map<String, Object> modifiedProps, String interfaceName, NetInterfaceType type) {
modifiedProps.put(String.format(PREFIX + "%s.type", interfaceName), type.toString());
}
Expand Down Expand Up @@ -295,6 +315,86 @@ private void decryptAndConvertPasswordProperties(Map<String, Object> modifiedPro
}
}

private void decryptAndConvertCertificatesProperties(Map<String, Object> modifiedProps, Set<String> interfaces) {

interfaces.forEach(interfaceName -> {
String key = String.format("net.interface.%s.config.802-1x.keystore.pid", interfaceName);
if (modifiedProps.containsKey(key)) {

Object prop = modifiedProps.get(key);

if (prop instanceof String) {
String keystorePid = (String) prop;

findAndDecodeCertificatesForInterface(interfaceName, modifiedProps,
this.keystoreServices.get(keystorePid));
}
}
});
}

private void findAndDecodeCertificatesForInterface(String interfaceName, Map<String, Object> modifiedProps,
KeystoreService keystoreService) {

if (keystoreService == null) {
logger.error("Cannot find keystore service for interface {}", interfaceName);
return;
}

final String clientCertString = String.format("net.interface.%s.config.802-1x.client-cert-name", interfaceName);
final String caCertString = String.format("net.interface.%s.config.802-1x.ca-cert-name", interfaceName);
final String privateKeyString = String.format("net.interface.%s.config.802-1x.private-key-name", interfaceName);
final List<String> keyCertStrings = Arrays.asList(clientCertString, caCertString, privateKeyString);

for (String key : keyCertStrings) {
if (!modifiedProps.containsKey(key)) {
continue;
}

Object value = modifiedProps.get(key);
try {
String valueString = value.toString();
if (isCertificate(key)) {
modifiedProps.put(key, getTrustedCertificateFromKeystore(valueString, keystoreService));
} else {
modifiedProps.put(key, getTrustedPrivateKeyFromKeystore(valueString, keystoreService));
}
} catch (KuraException e) {
logger.error("Unable to decode key/certificate {} from keystore.", key, e);
modifiedProps.put(key, value);
}
}
}

private boolean isCertificate(String key) {
return key.contains("802-1x.client-cert-name") || key.contains("802-1x.ca-cert-name");
}

private Certificate getTrustedCertificateFromKeystore(String certificateName, KeystoreService keystoreService)
throws KuraException {
if (keystoreService.getEntry(certificateName) instanceof TrustedCertificateEntry) {
TrustedCertificateEntry cert = (TrustedCertificateEntry) keystoreService.getEntry(certificateName);
return cert.getTrustedCertificate();
} else if (keystoreService.getEntry(certificateName) instanceof PrivateKeyEntry) {
PrivateKeyEntry cert = (PrivateKeyEntry) keystoreService.getEntry(certificateName);
return cert.getCertificate();
} else {
throw new KuraException(KuraErrorCode.CONFIGURATION_ERROR,
String.format("Certificate \"%s\" is not of the expected key type or not found.", certificateName));
}
}

private PrivateKey getTrustedPrivateKeyFromKeystore(String privateKeyName, KeystoreService keystoreService)
throws KuraException {
if (!(keystoreService.getEntry(privateKeyName) instanceof PrivateKeyEntry)) {
throw new KuraException(KuraErrorCode.CONFIGURATION_ERROR,
String.format("Private key \"%s\" is not of the expected key type or not found.", privateKeyName));
}

PrivateKeyEntry key = (PrivateKeyEntry) keystoreService.getEntry(privateKeyName);
return key.getPrivateKey();
}

@Override
@SuppressWarnings("restriction")
public synchronized ComponentConfiguration getConfiguration() throws KuraException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -64,6 +67,8 @@ public class NMSettingsConverter {
private static final String KURA_PROPS_KEY_WIFI_MODE = "net.interface.%s.config.wifi.mode";
private static final String KURA_PROPS_KEY_WIFI_SECURITY_TYPE = "net.interface.%s.config.wifi.%s.securityType";

static final UInt32 NM_SECRET_FLAGS_NOT_REQUIRED = new UInt32(4);

private NMSettingsConverter() {
throw new IllegalStateException("Utility class");
}
Expand Down Expand Up @@ -171,36 +176,51 @@ private static void create8021xTls(NetworkProperties props, String deviceId, Map
String identity = props.get(String.class, "net.interface.%s.config.802-1x.identity", deviceId);
settings.put("identity", new Variant<>(identity));

String clientCert = props.get(String.class, "net.interface.%s.config.802-1x.client-cert", deviceId);
settings.put("client-cert", new Variant<>(clientCert.getBytes(StandardCharsets.UTF_8)));
try {
Certificate clientCert = props.get(Certificate.class, "net.interface.%s.config.802-1x.client-cert-name",
deviceId);
settings.put("client-cert", new Variant<>(clientCert.getEncoded()));
} catch (CertificateEncodingException e) {
logger.error("Unable to find or decode Client Certificate");
}

PrivateKey privateKey = props.get(PrivateKey.class, "net.interface.%s.config.802-1x.private-key-name",
deviceId);

if (privateKey.getEncoded() != null) {
settings.put("private-key", new Variant<>(privateKey.getEncoded()));
} else {
logger.error("Unable to find or decode Private Key");
}

Optional<Password> privateKeyPassword = props.getOpt(Password.class,
"net.interface.%s.config.802-1x.private-key-password", deviceId);

String privateKey = props.get(String.class, "net.interface.%s.config.802-1x.private-key", deviceId);
settings.put("private-key", new Variant<>(privateKey.getBytes(StandardCharsets.UTF_8)));
privateKeyPassword.ifPresent(value -> settings.put("private-key-password", new Variant<>(value.toString())));

String privateKeyPassword = props
.get(Password.class, "net.interface.%s.config.802-1x.private-key-password", deviceId).toString();
settings.put("private-key-password", new Variant<>(privateKeyPassword));
settings.put("private-key-password-flags", new Variant<>(NM_SECRET_FLAGS_NOT_REQUIRED));

}

private static void create8021xOptionalCaCertAndAnonIdentity(NetworkProperties props, String deviceId,
Map<String, Variant<?>> settings) {

Optional<String> anonymousIdentity = props.getOpt(String.class,
"net.interface.%s.config.802-1x.anonymous-identity", deviceId);
if (anonymousIdentity.isPresent()) {
settings.put("anonymous-identity", new Variant<>(anonymousIdentity.get()));
}

Optional<String> caCert = props.getOpt(String.class, "net.interface.%s.config.802-1x.ca-cert", deviceId);
if (caCert.isPresent()) {
settings.put("ca-cert", new Variant<>(caCert.get().getBytes(StandardCharsets.UTF_8)));
anonymousIdentity.ifPresent(value -> settings.put("anonymous-identity", new Variant<>(value)));

try {
Certificate caCert = props.get(Certificate.class, "net.interface.%s.config.802-1x.ca-cert-name", deviceId);
settings.put("ca-cert", new Variant<>(caCert.getEncoded()));
} catch (Exception e) {
logger.error(String.format("Unable to find or decode CA Certificate for interface %s", deviceId));
}

Optional<Password> caCertPassword = props.getOpt(Password.class,
"net.interface.%s.config.802-1x.ca-cert-password", deviceId);
if (caCertPassword.isPresent()) {
settings.put("ca-cert-password", new Variant<>(caCertPassword.get().toString()));
}

caCertPassword.ifPresent(value -> settings.put("ca-cert-password", new Variant<>(value.toString())));
}

private static void create8021xMschapV2(NetworkProperties props, String deviceId,
Expand Down Expand Up @@ -512,7 +532,7 @@ public static Map<String, Variant<?>> buildPPPSettings(NetworkProperties props,

return settings;
}

public static Map<String, Variant<?>> buildVlanSettings(NetworkProperties props, String deviceId) {
Map<String, Variant<?>> settings = new HashMap<>();
settings.put("interface-name", new Variant<>(deviceId));
Expand All @@ -524,11 +544,9 @@ public static Map<String, Variant<?>> buildVlanSettings(NetworkProperties props,
settings.put("flags", new Variant<>(new UInt32(vlanFlags.orElse(1))));
DBusListType listType = new DBusListType(String.class);
Optional<List<String>> ingressMap = props.getOptStringList("net.interface.%s.config.vlan.ingress", deviceId);
settings.put("ingress-priority-map", new Variant<>(ingressMap
.orElse(new ArrayList<String>()), listType));
settings.put("ingress-priority-map", new Variant<>(ingressMap.orElse(new ArrayList<>()), listType));
Optional<List<String>> egressMap = props.getOptStringList("net.interface.%s.config.vlan.egress", deviceId);
settings.put("egress-priority-map", new Variant<>(egressMap
.orElse(new ArrayList<String>()), listType));
settings.put("egress-priority-map", new Variant<>(egressMap.orElse(new ArrayList<>()), listType));
return settings;
}

Expand All @@ -552,7 +570,7 @@ public static Map<String, Variant<?>> buildConnectionSettings(Optional<Connectio

return connectionMap;
}

private static Map<String, Variant<?>> createConnectionSettings(String iface) {
Map<String, Variant<?>> connectionMap = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private void monitor() {
stopDhcpServer(interfaceName);
}
} catch (KuraException e) {
logger.warn("Failed to chech DHCP server status for the interface " + interfaceName, e);
logger.warn("Failed to check DHCP server status for the interface " + interfaceName, e);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,16 +327,21 @@ public void setDirty(boolean dirty) {
public void getUpdatedNetInterface(GwtNetInterfaceConfig updatedNetIf) {
Gwt8021xConfig updated8021xConfig = new Gwt8021xConfig();

if (this.username.isEnabled()) {
if (!this.username.getText().isEmpty() && this.username.getText() != null) {
updated8021xConfig.setIdentity(this.username.getText());
}

if (this.password.isEnabled()) {
if (!this.password.getText().isEmpty() && this.password.getText() != null) {
updated8021xConfig.setPassword(this.password.getText());
}

updated8021xConfig.setEap(Gwt8021xEap.valueOf(this.eap.getSelectedValue()));
updated8021xConfig.setInnerAuthEnum(Gwt8021xInnerAuth.valueOf(this.innerAuth.getSelectedValue()));
if (!this.eap.getSelectedValue().isEmpty() && this.eap.getSelectedValue() != null) {
updated8021xConfig.setEap(Gwt8021xEap.valueOf(this.eap.getSelectedValue()));
}

if (!this.innerAuth.getSelectedValue().isEmpty() && this.innerAuth.getSelectedValue() != null) {
updated8021xConfig.setInnerAuthEnum(Gwt8021xInnerAuth.valueOf(this.innerAuth.getSelectedValue()));
}

updatedNetIf.setEnterpriseConfig(updated8021xConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,7 @@ private void refreshForm() {
this.dns.setEnabled(false);
}
this.renew.setEnabled(false);

this.configure.setSelectedIndex(this.configure.getItemText(0).equals(IPV4_MODE_DHCP_MESSAGE) ? 0 : 1);
} else if (this.selectedNetIfConfig != null
&& this.selectedNetIfConfig.getHwTypeEnum() == GwtNetIfType.LOOPBACK) {
Expand Down Expand Up @@ -867,4 +868,4 @@ private void initModal() {
this.multipleWanWarnText.setText(MSGS.netStatusWarning());
this.wanModal.addHideHandler(evt -> this.setDirty(true));
}
}
}
Loading

0 comments on commit 8fbf6f9

Please sign in to comment.