[mynice] Enable legacy TLS protocols (#20205)

* Enabling legacy TLS protocols

Signed-off-by: gael@lhopital.org <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital
2026-02-14 14:19:53 +01:00
committed by GitHub
parent c910d6994a
commit d1ce992d4e
4 changed files with 173 additions and 21 deletions
@@ -14,4 +14,29 @@
<name>openHAB Add-ons :: Bundles :: MyNice Binding</name>
<properties>
<bouncycastle.version>1.83</bouncycastle.version>
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -14,27 +14,24 @@ package org.openhab.binding.mynice.internal;
import static org.openhab.binding.mynice.internal.MyNiceBindingConstants.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mynice.internal.handler.GateHandler;
import org.openhab.binding.mynice.internal.handler.It4WifiHandler;
import org.openhab.core.io.net.http.TrustAllTrustManager;
import org.openhab.binding.mynice.internal.ssl.BcJsseSocketFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
/**
* The {@link MyNiceHandlerFactory} is responsible for creating thing handlers.
@@ -46,18 +43,7 @@ import org.osgi.service.component.annotations.Component;
public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_TYPE_IT4WIFI, THING_TYPE_SWING,
THING_TYPE_SLIDING);
private final SSLSocketFactory socketFactory;
@Activate
public MyNiceHandlerFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { TrustAllTrustManager.getInstance() }, null);
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IllegalArgumentException(e);
}
}
private @Nullable SSLSocketFactory socketFactory;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -69,7 +55,7 @@ public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (BRIDGE_TYPE_IT4WIFI.equals(thingTypeUID)) {
return new It4WifiHandler((Bridge) thing, socketFactory);
return new It4WifiHandler((Bridge) thing, getSocketFactory());
} else if (THING_TYPE_SWING.equals(thingTypeUID)) {
return new GateHandler(thing);
} else if (THING_TYPE_SLIDING.equals(thingTypeUID)) {
@@ -78,4 +64,16 @@ public class MyNiceHandlerFactory extends BaseThingHandlerFactory {
return null;
}
private SSLSocketFactory getSocketFactory() {
if (socketFactory == null) {
socketFactory = BcJsseSocketFactory.get();
}
return Objects.requireNonNull(socketFactory);
}
@Deactivate
public void dispose() {
BcJsseSocketFactory.dispose();
}
}
@@ -18,6 +18,7 @@ import static org.openhab.core.types.RefreshType.REFRESH;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -59,6 +60,8 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class It4WifiHandler extends BaseBridgeHandler {
private static final String NEEDED_PROTOCOL = "TLSv1.2";
private static final String NEEDED_CIPHER = "TLS_RSA_WITH_AES_256_CBC_SHA256";
private static final int SERVER_PORT = 443;
private static final int MAX_HANDSHAKE_ATTEMPTS = 3;
private static final int KEEPALIVE_DELAY_S = 235; // Timeout seems to be at 6 min
@@ -133,12 +136,38 @@ public class It4WifiHandler extends BaseBridgeHandler {
private void startConnector() {
It4WifiConfiguration config = getConfigAs(It4WifiConfiguration.class);
freeKeepAlive();
SSLSocket localSocket = null;
try {
logger.debug("Initiating connection to IT4Wifi {} on port {}...", config.hostname, SERVER_PORT);
SSLSocket localSocket = (SSLSocket) socketFactory.createSocket(config.hostname, SERVER_PORT);
sslSocket = Optional.of(localSocket);
localSocket = (SSLSocket) socketFactory.createSocket(config.hostname, SERVER_PORT);
// We require an older TLS protocol and a static RSA cipher suite to avoid the problematic
// DHE exchange that causes 'insufficient_security' errors. If these are not available on
// the current JVM, we abort the connection.
String[] enabledLegacyProtocols = Arrays.stream(localSocket.getSupportedProtocols())
.filter(NEEDED_PROTOCOL::equals).toArray(String[]::new);
String[] legacyCiphers = Arrays.stream(localSocket.getSupportedCipherSuites())
.filter(suite -> NEEDED_CIPHER.equals(suite)).toArray(String[]::new);
if (enabledLegacyProtocols.length > 0) {
localSocket.setEnabledProtocols(enabledLegacyProtocols);
} else {
throw new IOException(
"Required TLS protocol " + NEEDED_PROTOCOL + " is not supported by this JVM for IT4Wifi");
}
if (legacyCiphers.length > 0) {
localSocket.setEnabledCipherSuites(legacyCiphers);
} else {
throw new IOException(
"Required TLS cipher suite " + NEEDED_CIPHER + " is not supported by this JVM for IT4Wifi");
}
localSocket.startHandshake();
logger.debug("Handshake successful. Protocol: {}, Cipher: {}", localSocket.getSession().getProtocol(),
localSocket.getSession().getCipherSuite());
sslSocket = Optional.of(localSocket);
It4WifiConnector localConnector = new It4WifiConnector(this, localSocket);
connector = Optional.of(localConnector);
@@ -149,7 +178,16 @@ public class It4WifiHandler extends BaseBridgeHandler {
} catch (UnknownHostException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-hostname");
} catch (IOException e) {
if (localSocket != null) {
try {
localSocket.close();
} catch (IOException closeException) {
logger.debug("Error closing socket after failed handshake", closeException);
}
}
sslSocket = Optional.empty();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error-handshake-init");
logger.warn("Error in IT4Wifi handshake: {}", e.getMessage());
}
}
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mynice.internal.ssl;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.io.net.http.TrustAllTrustManager;
/**
* Factory to create SSLSocketFactory.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class BcJsseSocketFactory {
private static volatile boolean bcAdded = false;
private static volatile boolean bcJsseAdded = false;
private static volatile boolean initialized = false;
private BcJsseSocketFactory() {
}
public static SSLSocketFactory get() {
initializeOnce();
try {
SSLContext context = SSLContext.getInstance("TLS", "BCJSSE");
context.init(null, new TrustManager[] { TrustAllTrustManager.getInstance() }, new SecureRandom());
return context.getSocketFactory();
} catch (NoSuchAlgorithmException | NoSuchProviderException | KeyManagementException e) {
throw new IllegalStateException("Unable to initialize BCJSSE SSLContext", e);
}
}
public static void dispose() {
if (bcJsseAdded) {
Security.removeProvider("BCJSSE");
bcJsseAdded = false;
}
if (bcAdded) {
Security.removeProvider("BC");
bcAdded = false;
}
}
private static void initializeOnce() {
if (initialized) {
return;
}
synchronized (BcJsseSocketFactory.class) {
if (initialized) {
return;
}
// Get the existing BC provider from the platform (provided by bcprov bundle)
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
bcAdded = true;
}
// Get or register the BCJSSE provider
if (Security.getProvider("BCJSSE") == null) {
// Initialize BCJSSE in non-FIPS mode. It will find the existing "BC" provider.
Security.addProvider(new BouncyCastleJsseProvider(false));
bcJsseAdded = true;
}
initialized = true;
}
}
}