diff --git a/src/main/java/io/mapsmessaging/config/NetworkManagerConfig.java b/src/main/java/io/mapsmessaging/config/NetworkManagerConfig.java index f0d26e0ee..76fa66db2 100644 --- a/src/main/java/io/mapsmessaging/config/NetworkManagerConfig.java +++ b/src/main/java/io/mapsmessaging/config/NetworkManagerConfig.java @@ -101,7 +101,7 @@ private boolean updateEndPointServerConfig(EndPointServerConfigDTO endPointServe } if(name.equals(endPointServerConfig.getName()) && endPointServerConfigDTO instanceof EndPointServerConfig) { - return ((EndPointServerConfig) endPointServerConfig).update(endPointServerConfigDTO); + return ((EndPointServerConfig) endPointServerConfigDTO).update(endPointServerConfig); } } return false; diff --git a/src/main/java/io/mapsmessaging/config/network/KeyStoreConfig.java b/src/main/java/io/mapsmessaging/config/network/KeyStoreConfig.java index cca242e63..187bd2057 100644 --- a/src/main/java/io/mapsmessaging/config/network/KeyStoreConfig.java +++ b/src/main/java/io/mapsmessaging/config/network/KeyStoreConfig.java @@ -23,6 +23,7 @@ import io.mapsmessaging.configuration.ConfigurationProperties; import io.mapsmessaging.dto.rest.config.BaseConfigDTO; import io.mapsmessaging.dto.rest.config.network.KeyStoreConfigDTO; +import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -45,31 +46,31 @@ public boolean update(BaseConfigDTO config) { boolean hasChanged = false; if (config instanceof KeyStoreConfigDTO) { KeyStoreConfigDTO newConfig = (KeyStoreConfigDTO) config; - if (!this.alias.equals(newConfig.getAlias())) { + if (!Objects.equals(this.alias, newConfig.getAlias())) { this.alias = newConfig.getAlias(); hasChanged = true; } - if (!this.type.equals(newConfig.getType())) { + if (!Objects.equals(this.type, newConfig.getType())) { this.type = newConfig.getType(); hasChanged = true; } - if (!this.providerName.equals(newConfig.getProviderName())) { + if (!Objects.equals(this.providerName, newConfig.getProviderName())) { this.providerName = newConfig.getProviderName(); hasChanged = true; } - if (!this.managerFactory.equals(newConfig.getManagerFactory())) { + if (!Objects.equals(this.managerFactory, newConfig.getManagerFactory())) { this.managerFactory = newConfig.getManagerFactory(); hasChanged = true; } - if (!this.path.equals(newConfig.getPath())) { + if (!Objects.equals(this.path, newConfig.getPath())) { this.path = newConfig.getPath(); hasChanged = true; } - if (!this.passphrase.equals(newConfig.getPassphrase())) { + if (!Objects.equals(this.passphrase, newConfig.getPassphrase())) { this.passphrase = newConfig.getPassphrase(); hasChanged = true; } - if (!this.provider.equals(newConfig.getProvider())) { + if (!Objects.equals(this.provider, newConfig.getProvider())) { this.provider = newConfig.getProvider(); hasChanged = true; } diff --git a/src/main/java/io/mapsmessaging/config/network/SslConfig.java b/src/main/java/io/mapsmessaging/config/network/SslConfig.java index e0cd845ee..eccf51076 100644 --- a/src/main/java/io/mapsmessaging/config/network/SslConfig.java +++ b/src/main/java/io/mapsmessaging/config/network/SslConfig.java @@ -23,77 +23,97 @@ import io.mapsmessaging.configuration.ConfigurationProperties; import io.mapsmessaging.dto.rest.config.BaseConfigDTO; import io.mapsmessaging.dto.rest.config.network.SslConfigDTO; +import java.util.Objects; import lombok.EqualsAndHashCode; import lombok.ToString; @EqualsAndHashCode(callSuper = true) @ToString -public class SslConfig extends SslConfigDTO implements Config { +public class SslConfig extends SslConfigDTO implements Config { public SslConfig(ConfigurationProperties config) { - ConfigurationProperties securityProps = locateConfig(config); - this.context = securityProps.getProperty("context", "tls"); - this.clientCertificateRequired = config.getBooleanProperty("clientCertificateRequired", false); - this.clientCertificateWanted = config.getBooleanProperty("clientCertificateWanted", false); - this.crlUrl = config.getProperty("crlUrl", null); - this.crlInterval = config.getLongProperty("crlInterval", 0); + this(config, "tls"); + } + + public SslConfig(ConfigurationProperties config, String transport) { + ConfigurationProperties securityProps = locateConfig(config, transport); + if (securityProps == null) { + throw new IllegalArgumentException("Missing security." + transport + " configuration"); + } + String defaultContext = "dtls".equalsIgnoreCase(transport) ? "DTLSv1.2" : "TLS"; + this.context = securityProps.getProperty("context", defaultContext); + this.clientCertificateRequired = securityProps.getBooleanProperty("clientCertificateRequired", false); + this.clientCertificateWanted = securityProps.getBooleanProperty("clientCertificateWanted", false); + this.hostnameVerificationEnabled = securityProps.getBooleanProperty("hostnameVerificationEnabled", true); + this.crlUrl = securityProps.getProperty("crlUrl", null); + this.crlInterval = securityProps.getLongProperty("crlInterval", 3600000L); this.keyStore = new KeyStoreConfig((ConfigurationProperties) securityProps.get("keyStore")); this.trustStore = new KeyStoreConfig((ConfigurationProperties) securityProps.get("trustStore")); } - private ConfigurationProperties locateConfig(ConfigurationProperties config) { - if (config.containsKey("clientCertificateRequired")) { + private ConfigurationProperties locateConfig(ConfigurationProperties config, String transport) { + if (config.containsKey("keyStore") || config.containsKey("trustStore") || config.containsKey("clientCertificateRequired")) { return config; } - ConfigurationProperties security = (ConfigurationProperties) config.get("security"); - if (security != null) { - security = (ConfigurationProperties) security.get("tls"); - } ConfigurationProperties endPoint = (ConfigurationProperties) config.get("endPoint"); if (endPoint != null) { - return locateConfig(endPoint); + ConfigurationProperties endPointSecurity = locateConfig(endPoint, transport); + if (endPointSecurity != null) { + return endPointSecurity; + } } - return security; + ConfigurationProperties security = (ConfigurationProperties) config.get("security"); + return security == null ? null : (ConfigurationProperties) security.get(transport); } public boolean update(BaseConfigDTO config) { - boolean hasChanged = false; - if (config instanceof SslConfigDTO) { - SslConfigDTO newConfig = (SslConfigDTO) config; + if (!(config instanceof SslConfigDTO newConfig)) { + return false; + } - if (this.clientCertificateRequired != newConfig.isClientCertificateRequired()) { - this.clientCertificateRequired = newConfig.isClientCertificateRequired(); - hasChanged = true; - } - if (this.clientCertificateWanted != newConfig.isClientCertificateWanted()) { - this.clientCertificateWanted = newConfig.isClientCertificateWanted(); - hasChanged = true; - } - if (!this.crlUrl.equals(newConfig.getCrlUrl())) { - this.crlUrl = newConfig.getCrlUrl(); - hasChanged = true; - } - if (this.crlInterval != newConfig.getCrlInterval()) { - this.crlInterval = newConfig.getCrlInterval(); - hasChanged = true; - } - if (((KeyStoreConfig)this.keyStore).update(newConfig.getKeyStore())) { - hasChanged = true; - } - if (((KeyStoreConfig)this.trustStore).update(newConfig.getTrustStore())) { - hasChanged = true; - } + boolean hasChanged = false; + if (this.clientCertificateRequired != newConfig.isClientCertificateRequired()) { + this.clientCertificateRequired = newConfig.isClientCertificateRequired(); + hasChanged = true; + } + if (this.clientCertificateWanted != newConfig.isClientCertificateWanted()) { + this.clientCertificateWanted = newConfig.isClientCertificateWanted(); + hasChanged = true; + } + if (this.hostnameVerificationEnabled != newConfig.isHostnameVerificationEnabled()) { + this.hostnameVerificationEnabled = newConfig.isHostnameVerificationEnabled(); + hasChanged = true; + } + if (!Objects.equals(this.context, newConfig.getContext())) { + this.context = newConfig.getContext(); + hasChanged = true; + } + if (!Objects.equals(this.crlUrl, newConfig.getCrlUrl())) { + this.crlUrl = newConfig.getCrlUrl(); + hasChanged = true; + } + if (this.crlInterval != newConfig.getCrlInterval()) { + this.crlInterval = newConfig.getCrlInterval(); + hasChanged = true; + } + if (newConfig.getKeyStore() != null && ((KeyStoreConfig) this.keyStore).update(newConfig.getKeyStore())) { + hasChanged = true; + } + if (newConfig.getTrustStore() != null && ((KeyStoreConfig) this.trustStore).update(newConfig.getTrustStore())) { + hasChanged = true; } return hasChanged; } public ConfigurationProperties toConfigurationProperties() { ConfigurationProperties config = new ConfigurationProperties(); + config.put("context", this.context); config.put("clientCertificateRequired", this.clientCertificateRequired); config.put("clientCertificateWanted", this.clientCertificateWanted); + config.put("hostnameVerificationEnabled", this.hostnameVerificationEnabled); config.put("crlUrl", this.crlUrl); config.put("crlInterval", this.crlInterval); config.put("keyStore", ((KeyStoreConfig) keyStore).toConfigurationProperties()); diff --git a/src/main/java/io/mapsmessaging/config/network/impl/DtlsConfig.java b/src/main/java/io/mapsmessaging/config/network/impl/DtlsConfig.java index 2982557fb..e943da9eb 100644 --- a/src/main/java/io/mapsmessaging/config/network/impl/DtlsConfig.java +++ b/src/main/java/io/mapsmessaging/config/network/impl/DtlsConfig.java @@ -38,19 +38,20 @@ public class DtlsConfig extends DtlsConfigDTO implements Config { public DtlsConfig(ConfigurationProperties config) { setType("dtls"); NetworkConfigFactory.unpack(config, this); - sslConfig = new SslConfig(config); + sslConfig = new SslConfig(config, "dtls"); if(!sslConfig.getContext().toLowerCase().startsWith("dtls")){ sslConfig.setContext("DTLSv1.2"); } } public boolean update(BaseConfigDTO update) { - boolean hasChanged = false; - if (update instanceof DtlsConfigDTO) { - hasChanged = NetworkConfigFactory.update(this, (DtlsConfigDTO) update); - if(((SslConfig)sslConfig).update(update)){ - hasChanged = true; - } + if (!(update instanceof DtlsConfigDTO newConfig)) { + return false; + } + + boolean hasChanged = NetworkConfigFactory.update(this, newConfig); + if (newConfig.getSslConfig() != null && ((SslConfig) sslConfig).update(newConfig.getSslConfig())) { + hasChanged = true; } return hasChanged; } @@ -62,4 +63,5 @@ public ConfigurationProperties toConfigurationProperties() { security.put("dtls", ((SslConfig)sslConfig).toConfigurationProperties()); config.put("security", security); return config; - }} + } +} diff --git a/src/main/java/io/mapsmessaging/config/network/impl/TlsConfig.java b/src/main/java/io/mapsmessaging/config/network/impl/TlsConfig.java index bce8af281..e3b97cacb 100644 --- a/src/main/java/io/mapsmessaging/config/network/impl/TlsConfig.java +++ b/src/main/java/io/mapsmessaging/config/network/impl/TlsConfig.java @@ -23,7 +23,6 @@ import io.mapsmessaging.config.network.SslConfig; import io.mapsmessaging.configuration.ConfigurationProperties; import io.mapsmessaging.dto.rest.config.BaseConfigDTO; -import io.mapsmessaging.dto.rest.config.network.impl.TcpConfigDTO; import io.mapsmessaging.dto.rest.config.network.impl.TlsConfigDTO; import lombok.Data; import lombok.EqualsAndHashCode; @@ -38,21 +37,21 @@ public class TlsConfig extends TlsConfigDTO implements Config { public TlsConfig(ConfigurationProperties config) { NetworkConfigFactory.unpack(config, this); - sslConfig = new SslConfig(config); + sslConfig = new SslConfig(config, "tls"); if(sslConfig.getContext() == null || sslConfig.getContext().isEmpty()) { sslConfig.setContext("TLSv1.3"); } } public boolean update(BaseConfigDTO update) { - boolean hasChanged = false; - if (update instanceof TcpConfigDTO) { - hasChanged = NetworkConfigFactory.update(this, (TcpConfigDTO) update); - if(((SslConfig)sslConfig).update(update)){ - hasChanged = true; - } + if (!(update instanceof TlsConfigDTO newConfig)) { + return false; } + boolean hasChanged = NetworkConfigFactory.update(this, newConfig); + if (newConfig.getSslConfig() != null && ((SslConfig) sslConfig).update(newConfig.getSslConfig())) { + hasChanged = true; + } return hasChanged; } diff --git a/src/main/java/io/mapsmessaging/dto/rest/config/network/SslConfigDTO.java b/src/main/java/io/mapsmessaging/dto/rest/config/network/SslConfigDTO.java index c4e57764c..6fae3f351 100644 --- a/src/main/java/io/mapsmessaging/dto/rest/config/network/SslConfigDTO.java +++ b/src/main/java/io/mapsmessaging/dto/rest/config/network/SslConfigDTO.java @@ -50,6 +50,15 @@ public class SslConfigDTO extends BaseConfigDTO { ) protected boolean clientCertificateWanted = false; + @Schema( + description = "Whether outbound TLS connections verify the server certificate hostname.", + example = "true", + defaultValue = "true", + requiredMode = Schema.RequiredMode.REQUIRED, + nullable = false + ) + protected boolean hostnameVerificationEnabled = true; + @Schema( description = "URL for the Certificate Revocation List (CRL). " + "If not set, CRL checking is disabled.", @@ -72,10 +81,10 @@ public class SslConfigDTO extends BaseConfigDTO { protected long crlInterval = 3600000L; @Schema( - description = "SSL context identifier or protocol profile to use (for example: TLS, TLSv1.2, TLSv1.3).", + description = "SSL context identifier or protocol profile to use (for example: TLS, TLSv1.3, DTLS, DTLSv1.2).", example = "TLS", defaultValue = "TLS", - pattern = "^TLS(?:v1\\.(?:2|3))?$", + pattern = "^(?:TLS(?:v1\\.(?:2|3))?|DTLS(?:v1\\.(?:0|2))?)$", requiredMode = Schema.RequiredMode.REQUIRED, nullable = false ) diff --git a/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSEndPoint.java b/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSEndPoint.java index 16edbd82f..20b770370 100644 --- a/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSEndPoint.java +++ b/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSEndPoint.java @@ -21,6 +21,7 @@ import io.mapsmessaging.logging.Logger; import io.mapsmessaging.logging.LoggerFactory; +import io.mapsmessaging.logging.ServerLogMessages; import io.mapsmessaging.network.admin.EndPointJMX; import io.mapsmessaging.network.admin.EndPointManagerJMX; import io.mapsmessaging.network.io.*; @@ -33,7 +34,10 @@ import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; +import java.security.Principal; import java.util.concurrent.FutureTask; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; public class DTLSEndPoint extends EndPoint implements StateChangeListener, Timeoutable { @@ -128,6 +132,24 @@ public void handshakeComplete() { } } + @Override + public Principal getEndPointPrincipal() { + SSLEngine sslEngine = stateEngine.getSslEngine(); + if (sslEngine.getNeedClientAuth() || sslEngine.getWantClientAuth()) { + try { + return sslEngine.getSession().getPeerPrincipal(); + } catch (SSLPeerUnverifiedException e) { + logger.log(ServerLogMessages.SSL_ENGINE_CLIENT_AUTH); + } + } + return null; + } + + @Override + public boolean isSSL() { + return true; + } + @Override public boolean isUDP() { return true; diff --git a/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSSessionManager.java b/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSSessionManager.java index 56fbc5b41..29415aa34 100644 --- a/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSSessionManager.java +++ b/src/main/java/io/mapsmessaging/network/io/impl/dtls/DTLSSessionManager.java @@ -90,12 +90,7 @@ public boolean processPacket(@NonNull @NotNull Packet packet) throws IOException if (state == null) { StateEngine stateEngine; SSLEngine sslEngine = SslHelper.createSSLEngine(sslContext, ((Config)((DtlsConfig)udpEndPoint.getConfig().getEndPointConfig()).getSslConfig()).toConfigurationProperties()); - SSLParameters paras = sslEngine.getSSLParameters(); - int mtu = 8192; - paras.setMaximumPacketSize(mtu); - paras.setEnableRetransmissions(true); - paras.setNeedClientAuth(false); - sslEngine.setSSLParameters(paras); + configureEngine(sslEngine); stateEngine = new StateEngine(packet.getFromAddress(), sslEngine, this); endPoint = new DTLSEndPoint(this, uniqueId.incrementAndGet(), packet.getFromAddress(), server, stateEngine, managerMBean); sessionMapping.addState(packet.getFromAddress(), new UDPSessionState<>(endPoint)); @@ -112,6 +107,13 @@ public boolean processPacket(@NonNull @NotNull Packet packet) throws IOException return true; } + static void configureEngine(SSLEngine sslEngine) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setMaximumPacketSize(8192); + sslParameters.setEnableRetransmissions(true); + sslEngine.setSSLParameters(sslParameters); + } + public void close(SocketAddress clientId) { UDPSessionState state = sessionMapping.getState(clientId); if (state != null && state.getContext() != null) { diff --git a/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPoint.java b/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPoint.java index bf11cef6e..8ca014611 100644 --- a/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPoint.java +++ b/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPoint.java @@ -231,7 +231,7 @@ private SSLEngineResult handleSSLEngineResult(SSLEngineResult result) throws IOE @Override public Principal getEndPointPrincipal() { - if (sslEngine.getNeedClientAuth()) { + if (sslEngine.getNeedClientAuth() || sslEngine.getWantClientAuth()) { try { return sslEngine.getSession().getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { diff --git a/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPointConnectionFactory.java b/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPointConnectionFactory.java index f9ff0abf6..fb3d6008e 100644 --- a/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPointConnectionFactory.java +++ b/src/main/java/io/mapsmessaging/network/io/impl/ssl/SSLEndPointConnectionFactory.java @@ -33,6 +33,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; @@ -49,8 +50,11 @@ public EndPoint connect(EndPointURL url, SelectorLoadManager selector, EndPointC throws IOException { TlsConfig securityProps = (TlsConfig) endPointServerStatus.getConfig().getEndPointConfig(); SSLContext context = SslHelper.createContext(securityProps.getSslConfig().getContext(),((Config) securityProps.getSslConfig()).toConfigurationProperties(), logger); - SSLEngine engine = SslHelper.createSSLEngine(context, ((Config)securityProps.getSslConfig()).toConfigurationProperties()); - engine.setUseClientMode(true); + SSLEngine engine = createClientEngine( + context, + url.getHost(), + url.getPort(), + securityProps.getSslConfig().isHostnameVerificationEnabled()); SocketChannel channel = SocketChannel.open(); InetSocketAddress address = new InetSocketAddress(url.getHost(), url.getPort()); channel.configureBlocking(true); @@ -59,6 +63,17 @@ public EndPoint connect(EndPointURL url, SelectorLoadManager selector, EndPointC return new SSLEndPoint(generateID(), engine, channel, selector.allocate(), callback, endPointServerStatus, jmxPath); } + static SSLEngine createClientEngine(SSLContext context, String host, int port, boolean hostnameVerificationEnabled) { + SSLEngine engine = context.createSSLEngine(host, port); + engine.setUseClientMode(true); + if (hostnameVerificationEnabled) { + SSLParameters sslParameters = engine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + engine.setSSLParameters(sslParameters); + } + return engine; + } + @Override public String getName() { return "ssl"; diff --git a/src/main/resources/NetworkManager.yaml b/src/main/resources/NetworkManager.yaml index cf84be409..f165acee2 100644 --- a/src/main/resources/NetworkManager.yaml +++ b/src/main/resources/NetworkManager.yaml @@ -87,10 +87,11 @@ NetworkManager: tls: clientCertificateRequired: false clientCertificateWanted: false + hostnameVerificationEnabled: true #----------------------------------- # CRL Specific # crlUrl: - # crlInterval: