mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[knx] Allow decoding of KNX Data Secure frames (#12434)
* [knx] Allow decoding of KNX Data Secure frames Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
This commit is contained in:
parent
104f32892e
commit
dbc607abcf
@ -50,35 +50,40 @@ as multicast traffic is typically not forwarded.
|
|||||||
The IP Gateway is the most commonly used way to connect to the KNX bus.
|
The IP Gateway is the most commonly used way to connect to the KNX bus.
|
||||||
At its base, the _ip_ bridge accepts the following configuration parameters:
|
At its base, the _ip_ bridge accepts the following configuration parameters:
|
||||||
|
|
||||||
| Name | Required | Description | Default value |
|
| Name | Required | Description | Default value |
|
||||||
|---------------------|--------------|--------------------------------------------------------------------------------------------------------------|------------------------------------------------------|
|
|---------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
|
||||||
| type | Yes | The IP connection type for connecting to the KNX bus (`TUNNEL`, `ROUTER`, `SECURETUNNEL` or `SECUREROUTER`) | - |
|
| type | Yes | The IP connection type for connecting to the KNX bus (`TUNNEL`, `ROUTER`, `SECURETUNNEL` or `SECUREROUTER`) | - |
|
||||||
| ipAddress | for `TUNNEL` | Network address of the KNX/IP gateway. If type `ROUTER` is set, the IPv4 Multicast Address can be set. | for `TUNNEL`: \<nothing\>, for `ROUTER`: 224.0.23.12 |
|
| ipAddress | for `TUNNEL` | Network address of the KNX/IP gateway. If type `ROUTER` is set, the IPv4 Multicast Address can be set. | for `TUNNEL`: \<nothing\>, for `ROUTER`: 224.0.23.12 |
|
||||||
| portNumber | for `TUNNEL` | Port number of the KNX/IP gateway | 3671 |
|
| portNumber | for `TUNNEL` | Port number of the KNX/IP gateway | 3671 |
|
||||||
| localIp | No | Network address of the local host to be used to set up the connection to the KNX/IP gateway | the system-wide configured primary interface address |
|
| localIp | No | Network address of the local host to be used to set up the connection to the KNX/IP gateway | the system-wide configured primary interface address |
|
||||||
| localSourceAddr | No | The (virtual) individual address for identification of this openHAB Thing within the KNX bus <br/><br/>Note: Use a free address, not the one of the interface. Or leave it at `0.0.0` and let openHAB decide which address to use. When using knxd, make sure _not to use_ one of the addresses reserved for tunneling clients. | 0.0.0 |
|
| localSourceAddr | No | The (virtual) individual address for identification of this openHAB Thing within the KNX bus <br/><br/>Note: Use a free address, not the one of the interface. Or leave it at `0.0.0` and let openHAB decide which address to use.<br/>When using knxd, make sure _not to use_ one of the addresses reserved for tunneling clients. | 0.0.0 |
|
||||||
| useNAT | No | Whether there is network address translation between the server and the gateway | false |
|
| useNAT | No | Whether there is network address translation between the server and the gateway | false |
|
||||||
| readingPause | No | Time in milliseconds of how long should be paused between two read requests to the bus during initialization | 50 |
|
| readingPause | No | Time in milliseconds of how long should be paused between two read requests to the bus during initialization | 50 |
|
||||||
| responseTimeout | No | Timeout in seconds to wait for a response from the KNX bus | 10 |
|
| responseTimeout | No | Timeout in seconds to wait for a response from the KNX bus | 10 |
|
||||||
| readRetriesLimit | No | Limits the read retries while initialization from the KNX bus | 3 |
|
| readRetriesLimit | No | Limits the read retries while initialization from the KNX bus | 3 |
|
||||||
| autoReconnectPeriod | No | Seconds between connect retries when KNX link has been lost (0 means never). | 0 |
|
| autoReconnectPeriod | No | Seconds between connect retries when KNX link has been lost (0 means never). | 0 |
|
||||||
| routerBackboneKey | No | KNX secure: Backbone key for secure router mode | - |
|
| routerBackboneKey | No | KNX secure: Backbone key for secure router mode | - |
|
||||||
| tunnelUserId | No | KNX secure: Tunnel user id for secure tunnel mode (if specified, it must be a number >0) | - |
|
| tunnelUserId | No | KNX secure: Tunnel user id for secure tunnel mode (if specified, it must be a number >0) | - |
|
||||||
| tunnelUserPassword | No | KNX secure: Tunnel user key for secure tunnel mode | - |
|
| tunnelUserPassword | No | KNX secure: Tunnel user key for secure tunnel mode | - |
|
||||||
| tunnelDeviceAuthentication | No | KNX secure: Tunnel device authentication for secure tunnel mode | - |
|
| tunnelDeviceAuthentication | No | KNX secure: Tunnel device authentication for secure tunnel mode | - |
|
||||||
|
| keyringFile | No | KNX secure: Keyring file exported from ETS and placed in openHAB config/misc folder. Mandatory to decode secure group addresses. | - |
|
||||||
|
| keyringPassword | No | KNX secure: Keyring file password (set during export from ETS) | - |
|
||||||
|
| tunnelSourceAddress | No | KNX secure: Physical KNX address of tunnel in secure mode to identify tunnel. If given, openHAB will read tunnelUserId, tunnelUserPassword, tunnelDeviceAuthentication from keyring. | - |
|
||||||
|
|
||||||
### Serial Gateway
|
### Serial Gateway
|
||||||
|
|
||||||
The _serial_ bridge accepts the following configuration parameters:
|
The _serial_ bridge accepts the following configuration parameters:
|
||||||
|
|
||||||
| Name | Required | Description | Default value |
|
| Name | Required | Description | Default value |
|
||||||
|---------------------|----------|--------------------------------------------------------------------------------------------------------------|---------------|
|
|---------------------|----------|----------------------------------------------------------------------------------------------------------------------------------|---------------|
|
||||||
| serialPort | Y | The serial port to use for connecting to the KNX bus | - |
|
| serialPort | Y | The serial port to use for connecting to the KNX bus | - |
|
||||||
| readingPause | N | Time in milliseconds of how long should be paused between two read requests to the bus during initialization | 50 |
|
| readingPause | N | Time in milliseconds of how long should be paused between two read requests to the bus during initialization | 50 |
|
||||||
| responseTimeout | N | Timeout in seconds to wait for a response from the KNX bus | 10 |
|
| responseTimeout | N | Timeout in seconds to wait for a response from the KNX bus | 10 |
|
||||||
| readRetriesLimit | N | Limits the read retries while initialization from the KNX bus | 3 |
|
| readRetriesLimit | N | Limits the read retries while initialization from the KNX bus | 3 |
|
||||||
| autoReconnectPeriod | N | Seconds between connect retries when KNX link has been lost, 0 means never retry | 0 |
|
| autoReconnectPeriod | N | Seconds between connect retries when KNX link has been lost, 0 means never retry | 0 |
|
||||||
| useCemi | N | Use newer CEMI message format, useful for newer devices like KNX RF sticks, kBerry, etc. | false |
|
| useCemi | N | Use newer CEMI message format, useful for newer devices like KNX RF sticks, kBerry, etc. | false |
|
||||||
|
| keyringFile | N | KNX secure: Keyring file exported from ETS and placed in openHAB config/misc folder. Mandatory to decode secure group addresses. | - |
|
||||||
|
| keyringPassword | N | KNX secure: Keyring file password (set during export from ETS) | - |
|
||||||
|
|
||||||
## Things
|
## Things
|
||||||
|
|
||||||
@ -452,16 +457,22 @@ It **requires a KNX Secure Router or a Secure IP Interface** and a KNX installat
|
|||||||
|
|
||||||
For _Secure routing_ mode, the so-called `backbone key` needs to be configured in openHAB.
|
For _Secure routing_ mode, the so-called `backbone key` needs to be configured in openHAB.
|
||||||
It is created by the ETS tool and cannot be changed via the ETS user interface.
|
It is created by the ETS tool and cannot be changed via the ETS user interface.
|
||||||
|
There are two possible ways to provide the key to openHAB:
|
||||||
|
|
||||||
- The backbone key can be extracted from Security report (ETS, Reports, Security, look for a 32-digit key) and specified in parameter `routerBackboneKey`.
|
- The backbone key can be extracted from Security report (ETS, Reports, Security, look for a 32-digit key) and specified in parameter `routerBackboneKey`.
|
||||||
|
- The backbone key is included in ETS keyring export (ETS, project settings, export keyring). Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and also requires `keyringPassword`.
|
||||||
|
|
||||||
For _Secure tunneling_ with a Secure IP Interface (or a router in tunneling mode), more parameters are required.
|
For _Secure tunneling_ with a Secure IP Interface (or a router in tunneling mode), more parameters are required.
|
||||||
A unique device authentication key, and a specific tunnel identifier and password need to be available.
|
A unique device authentication key, and a specific tunnel identifier and password need to be available.
|
||||||
|
It can be provided to openHAB in two different ways:
|
||||||
|
|
||||||
- All information can be looked up in ETS and provided separately: `tunnelDeviceAuthentication`, `tunnelUserPassword`.
|
- All information can be looked up in ETS and provided separately: `tunnelDeviceAuthentication`, `tunnelUserPassword`.
|
||||||
`tunnelUserId` is a number that is not directly visible in ETS, but can be looked up in keyring export or deduced (typically 2 for the first tunnel of a device, 3 for the second one, ...).
|
`tunnelUserId` is a number that is not directly visible in ETS, but can be looked up in keyring export or deduced (typically 2 for the first tunnel of a device, 3 for the second one, ...).
|
||||||
`tunnelUserPasswort` is set in ETS in the properties of the tunnel (below the IP interface, you will see the different tunnels listed) and denoted as "Password".
|
`tunnelUserPasswort` is set in ETS in the properties of the tunnel (below the IP interface, you will see the different tunnels listed) and denoted as "Password".
|
||||||
`tunnelDeviceAuthentication` is set in the properties of the IP interface itself; check for the tab "IP" and the description "Authentication Code".
|
`tunnelDeviceAuthentication` is set in the properties of the IP interface itself; check for the tab "IP" and the description "Authentication Code".
|
||||||
|
- All necessary information is included in ETS keyring export (ETS, project settings, export keyring).
|
||||||
|
Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and `keyringPassword`.
|
||||||
|
In addition, `tunnelSourceAddress` needs to be set to uniquely identify the tunnel in use.
|
||||||
|
|
||||||
### KNX Data Secure
|
### KNX Data Secure
|
||||||
|
|
||||||
@ -469,7 +480,14 @@ KNX Data Secure protects the content of messages on the KNX bus.
|
|||||||
In a KNX installation, both classic and secure group addresses can coexist.
|
In a KNX installation, both classic and secure group addresses can coexist.
|
||||||
Data Secure does _not_ necessarily require a KNX Secure Router or a Secure IP Interface, but a KNX installation with newer KNX devices that support Data Secure and with **security features enabled in the ETS tool**.
|
Data Secure does _not_ necessarily require a KNX Secure Router or a Secure IP Interface, but a KNX installation with newer KNX devices that support Data Secure and with **security features enabled in the ETS tool**.
|
||||||
|
|
||||||
> NOTE: **openHAB currently ignores messages with secure group addresses.**
|
**openHAB ignores messages with secure group addresses, unless data secure is configured.**
|
||||||
|
|
||||||
|
> NOTE: openHAB currently does fully support passive (listening) access to secure group addresses.
|
||||||
|
Write access to secure group addresses is currently disabled in openHAB.
|
||||||
|
Initial/periodic read will fail, avoid automatic read (< in thing definition).
|
||||||
|
|
||||||
|
All necessary information to decode secure group addresses is included in ETS keyring export (ETS, project settings, export keyring).
|
||||||
|
Keyring file is configured using `keyringFile` (put it in `config\misc` folder of the openHAB installation) and also requires `keyringPassword`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -63,10 +63,13 @@ public class KNXBindingConstants {
|
|||||||
public static final String PORT_NUMBER = "portNumber";
|
public static final String PORT_NUMBER = "portNumber";
|
||||||
public static final String SERIAL_PORT = "serialPort";
|
public static final String SERIAL_PORT = "serialPort";
|
||||||
public static final String USE_CEMI = "useCemi";
|
public static final String USE_CEMI = "useCemi";
|
||||||
|
public static final String KEYRING_FILE = "keyringFile";
|
||||||
|
public static final String KEYRING_PASSWORD = "keyringPassword";
|
||||||
public static final String ROUTER_BACKBONE_GROUP_KEY = "routerBackboneGroupKey";
|
public static final String ROUTER_BACKBONE_GROUP_KEY = "routerBackboneGroupKey";
|
||||||
public static final String TUNNEL_USER_ID = "tunnelUserId";
|
public static final String TUNNEL_USER_ID = "tunnelUserId";
|
||||||
public static final String TUNNEL_USER_PASSWORD = "tunnelUserPassword";
|
public static final String TUNNEL_USER_PASSWORD = "tunnelUserPassword";
|
||||||
public static final String TUNNEL_DEVICE_AUTHENTICATION = "tunnelDeviceAuthentication";
|
public static final String TUNNEL_DEVICE_AUTHENTICATION = "tunnelDeviceAuthentication";
|
||||||
|
public static final String TUNNEL_SOURCE_ADDRESS = "tunnelSourceAddress";
|
||||||
|
|
||||||
// The default multicast ip address (see <a
|
// The default multicast ip address (see <a
|
||||||
// href="http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml">iana</a> EIBnet/IP
|
// href="http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml">iana</a> EIBnet/IP
|
||||||
|
@ -28,6 +28,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.knx.internal.dpt.ValueEncoder;
|
import org.openhab.binding.knx.internal.dpt.ValueEncoder;
|
||||||
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
|
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
|
||||||
|
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
|
||||||
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
|
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
|
||||||
import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
|
import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
@ -51,20 +52,18 @@ import tuwien.auto.calimero.link.KNXNetworkLink;
|
|||||||
import tuwien.auto.calimero.link.NetworkLinkListener;
|
import tuwien.auto.calimero.link.NetworkLinkListener;
|
||||||
import tuwien.auto.calimero.mgmt.Destination;
|
import tuwien.auto.calimero.mgmt.Destination;
|
||||||
import tuwien.auto.calimero.mgmt.ManagementClient;
|
import tuwien.auto.calimero.mgmt.ManagementClient;
|
||||||
import tuwien.auto.calimero.mgmt.ManagementClientImpl;
|
|
||||||
import tuwien.auto.calimero.mgmt.ManagementProcedures;
|
import tuwien.auto.calimero.mgmt.ManagementProcedures;
|
||||||
import tuwien.auto.calimero.mgmt.ManagementProceduresImpl;
|
import tuwien.auto.calimero.mgmt.TransportLayerImpl;
|
||||||
import tuwien.auto.calimero.process.ProcessCommunication;
|
import tuwien.auto.calimero.process.ProcessCommunication;
|
||||||
import tuwien.auto.calimero.process.ProcessCommunicator;
|
import tuwien.auto.calimero.process.ProcessCommunicator;
|
||||||
import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
|
import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
|
||||||
import tuwien.auto.calimero.process.ProcessEvent;
|
import tuwien.auto.calimero.process.ProcessEvent;
|
||||||
import tuwien.auto.calimero.process.ProcessListener;
|
import tuwien.auto.calimero.process.ProcessListener;
|
||||||
import tuwien.auto.calimero.secure.KnxSecureException;
|
import tuwien.auto.calimero.secure.KnxSecureException;
|
||||||
import tuwien.auto.calimero.secure.SecureApplicationLayer;
|
|
||||||
import tuwien.auto.calimero.secure.Security;
|
import tuwien.auto.calimero.secure.Security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KNX Client which encapsulates the communication with the KNX bus via the calimero libary.
|
* KNX Client which encapsulates the communication with the KNX bus via the calimero library.
|
||||||
*
|
*
|
||||||
* @author Simon Kaufmann - initial contribution and API.
|
* @author Simon Kaufmann - initial contribution and API.
|
||||||
*
|
*
|
||||||
@ -92,6 +91,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
private final StatusUpdateCallback statusUpdateCallback;
|
private final StatusUpdateCallback statusUpdateCallback;
|
||||||
private final ScheduledExecutorService knxScheduler;
|
private final ScheduledExecutorService knxScheduler;
|
||||||
private final CommandExtensionData commandExtensionData;
|
private final CommandExtensionData commandExtensionData;
|
||||||
|
protected final Security openhabSecurity;
|
||||||
|
|
||||||
private @Nullable ProcessCommunicator processCommunicator;
|
private @Nullable ProcessCommunicator processCommunicator;
|
||||||
private @Nullable ProcessCommunicationResponder responseCommunicator;
|
private @Nullable ProcessCommunicationResponder responseCommunicator;
|
||||||
@ -139,7 +139,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
|
|
||||||
public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
|
public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
|
||||||
int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
|
int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
|
||||||
StatusUpdateCallback statusUpdateCallback) {
|
Security openhabSecurity, StatusUpdateCallback statusUpdateCallback) {
|
||||||
this.autoReconnectPeriod = autoReconnectPeriod;
|
this.autoReconnectPeriod = autoReconnectPeriod;
|
||||||
this.thingUID = thingUID;
|
this.thingUID = thingUID;
|
||||||
this.responseTimeout = responseTimeout;
|
this.responseTimeout = responseTimeout;
|
||||||
@ -148,6 +148,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
this.knxScheduler = knxScheduler;
|
this.knxScheduler = knxScheduler;
|
||||||
this.statusUpdateCallback = statusUpdateCallback;
|
this.statusUpdateCallback = statusUpdateCallback;
|
||||||
this.commandExtensionData = commandExtensionData;
|
this.commandExtensionData = commandExtensionData;
|
||||||
|
this.openhabSecurity = openhabSecurity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@ -206,38 +207,46 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
KNXNetworkLink link = establishConnection();
|
KNXNetworkLink link = establishConnection();
|
||||||
this.link = link;
|
this.link = link;
|
||||||
|
|
||||||
// ManagementProcedures provided by Calimero: allow managing other KNX devices, e.g. check if an address is
|
// one transport layer implementation, to be shared by all following classes
|
||||||
// reachable.
|
TransportLayerImpl tl = new TransportLayerImpl(link);
|
||||||
// Note for KNX Secure: ManagmentProcedueresImpl currently does not provide a ctor with external SAL,
|
|
||||||
// it internally creates an instance of ManagementClientImpl, which uses
|
// new SecureManagement / SecureApplicationLayer, based on the keyring (if any)
|
||||||
// Security.defaultInstallation().deviceToolKeys()
|
// SecureManagement does not offer a public ctor which can use a given TL.
|
||||||
// Protected ctor using given ManagementClientImpl is avalable (custom class to be inherited)
|
// Protected ctor using given TransportLayerImpl is available (custom class to be inherited)
|
||||||
managementProcedures = new ManagementProceduresImpl(link);
|
// which also copies the relevant content of the supplied SAL to a new SAL instance created
|
||||||
|
// by SecureManagement ctor.
|
||||||
|
CustomSecureManagement sal = new CustomSecureManagement(tl, openhabSecurity);
|
||||||
|
|
||||||
|
logger.debug("GAs: {} Send: {}, S={}", sal.security().groupKeys().size(),
|
||||||
|
sal.security().groupSenders().size(),
|
||||||
|
KNXBridgeBaseThingHandler.secHelperGetSecureGroupAddresses(sal.security()));
|
||||||
|
|
||||||
// ManagementClient provided by Calimero: allow reading device info, etc.
|
// ManagementClient provided by Calimero: allow reading device info, etc.
|
||||||
// Note for KNX Secure: ManagementClientImpl does not provide a ctor with external SAL in Calimero 2.5,
|
// Note for KNX Secure: ManagementClientImpl does not provide a ctor with external SAL in Calimero 2.5.
|
||||||
// is uses global Security.defaultInstalltion().deviceToolKeys()
|
// Protected ctor using given ManagementClientImpl is available in >2.5 (custom class to be inherited)
|
||||||
// Current main branch includes a protected ctor (custom class to be inherited)
|
ManagementClient managementClient = new CustomManagementClientImpl(link, sal);
|
||||||
// TODO Calimero>2.5: check if there is a new way to provide security info, there is a new protected ctor
|
|
||||||
// TODO check if we can avoid creating another ManagementClient and re-use this from ManagemntProcedures
|
|
||||||
ManagementClient managementClient = new ManagementClientImpl(link);
|
|
||||||
managementClient.responseTimeout(Duration.ofSeconds(responseTimeout));
|
managementClient.responseTimeout(Duration.ofSeconds(responseTimeout));
|
||||||
this.managementClient = managementClient;
|
this.managementClient = managementClient;
|
||||||
|
|
||||||
|
// ManagementProcedures provided by Calimero: allow managing other KNX devices, e.g. check if an address is
|
||||||
|
// reachable.
|
||||||
|
// Note for KNX Secure: ManagementProceduresImpl currently does not provide a public ctor with external SAL.
|
||||||
|
// Protected ctor using given ManagementClientImpl is available (custom class to be inherited)
|
||||||
|
managementProcedures = new CustomManagementProceduresImpl(managementClient, tl);
|
||||||
|
|
||||||
// OH helper for reading device info, based on managementClient above
|
// OH helper for reading device info, based on managementClient above
|
||||||
deviceInfoClient = new DeviceInfoClientImpl(managementClient);
|
deviceInfoClient = new DeviceInfoClientImpl(managementClient);
|
||||||
|
|
||||||
// ProcessCommunicator provides main KNX communication (Calimero).
|
// ProcessCommunicator provides main KNX communication (Calimero).
|
||||||
// Note for KNX Secure: SAL to be provided
|
final boolean useGoDiagnostics = true;
|
||||||
ProcessCommunicator processCommunicator = new ProcessCommunicatorImpl(link);
|
ProcessCommunicator processCommunicator = new ProcessCommunicatorImpl(link, sal, useGoDiagnostics);
|
||||||
processCommunicator.responseTimeout(Duration.ofSeconds(responseTimeout));
|
processCommunicator.responseTimeout(Duration.ofSeconds(responseTimeout));
|
||||||
processCommunicator.addProcessListener(processListener);
|
processCommunicator.addProcessListener(processListener);
|
||||||
this.processCommunicator = processCommunicator;
|
this.processCommunicator = processCommunicator;
|
||||||
|
|
||||||
// ProcessCommunicationResponder provides responses to requests from KNX bus (Calimero).
|
// ProcessCommunicationResponder provides responses to requests from KNX bus (Calimero).
|
||||||
// Note for KNX Secure: SAL to be provided
|
ProcessCommunicationResponder responseCommunicator = new ProcessCommunicationResponder(link, sal);
|
||||||
this.responseCommunicator = new ProcessCommunicationResponder(link,
|
this.responseCommunicator = responseCommunicator;
|
||||||
new SecureApplicationLayer(link, Security.defaultInstallation()));
|
|
||||||
|
|
||||||
// register this class, callbacks will be triggered
|
// register this class, callbacks will be triggered
|
||||||
link.addLinkListener(this);
|
link.addLinkListener(this);
|
||||||
@ -277,7 +286,7 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect(@Nullable Exception e) {
|
private synchronized void disconnect(@Nullable Exception e) {
|
||||||
disconnect(e, null);
|
disconnect(e, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,23 +303,23 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
|
|
||||||
protected void releaseConnection() {
|
protected void releaseConnection() {
|
||||||
logger.debug("Bridge {} is disconnecting from KNX bus", thingUID);
|
logger.debug("Bridge {} is disconnecting from KNX bus", thingUID);
|
||||||
var tmplink = link;
|
var tmpLink = link;
|
||||||
if (tmplink != null) {
|
if (tmpLink != null) {
|
||||||
tmplink.removeLinkListener(this);
|
tmpLink.removeLinkListener(this);
|
||||||
}
|
}
|
||||||
busJob = nullify(busJob, j -> j.cancel(true));
|
|
||||||
readDatapoints.clear();
|
readDatapoints.clear();
|
||||||
responseCommunicator = nullify(responseCommunicator, rc -> {
|
busJob = nullify(busJob, j -> j.cancel(true));
|
||||||
rc.removeProcessListener(processListener);
|
deviceInfoClient = null;
|
||||||
rc.detach();
|
managementProcedures = nullify(managementProcedures, ManagementProcedures::detach);
|
||||||
});
|
managementClient = nullify(managementClient, ManagementClient::detach);
|
||||||
processCommunicator = nullify(processCommunicator, pc -> {
|
processCommunicator = nullify(processCommunicator, pc -> {
|
||||||
pc.removeProcessListener(processListener);
|
pc.removeProcessListener(processListener);
|
||||||
pc.detach();
|
pc.detach();
|
||||||
});
|
});
|
||||||
deviceInfoClient = null;
|
responseCommunicator = nullify(responseCommunicator, rc -> {
|
||||||
managementClient = nullify(managementClient, ManagementClient::detach);
|
rc.removeProcessListener(processListener);
|
||||||
managementProcedures = nullify(managementProcedures, ManagementProcedures::detach);
|
rc.detach();
|
||||||
|
});
|
||||||
link = nullify(link, KNXNetworkLink::close);
|
link = nullify(link, KNXNetworkLink::close);
|
||||||
logger.trace("Bridge {} disconnected from KNX bus", thingUID);
|
logger.trace("Bridge {} disconnected from KNX bus", thingUID);
|
||||||
}
|
}
|
||||||
@ -361,13 +370,20 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
}
|
}
|
||||||
ReadDatapoint datapoint = readDatapoints.poll();
|
ReadDatapoint datapoint = readDatapoints.poll();
|
||||||
if (datapoint != null) {
|
if (datapoint != null) {
|
||||||
|
// TODO #8872: allow write access, currently only listening mode
|
||||||
|
if (openhabSecurity.groupKeys().containsKey(datapoint.getDatapoint().getMainAddress())) {
|
||||||
|
logger.debug("outgoing secure communication not implemented, explicit read from GA '{}' skipped",
|
||||||
|
datapoint.getDatapoint().getMainAddress());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
datapoint.incrementRetries();
|
datapoint.incrementRetries();
|
||||||
try {
|
try {
|
||||||
logger.trace("Sending a Group Read Request telegram for {}", datapoint.getDatapoint().getMainAddress());
|
logger.trace("Sending a Group Read Request telegram for {}", datapoint.getDatapoint().getMainAddress());
|
||||||
processCommunicator.read(datapoint.getDatapoint());
|
processCommunicator.read(datapoint.getDatapoint());
|
||||||
} catch (KNXException e) {
|
} catch (KNXException e) {
|
||||||
// Note: KnxException does not cover KnxRuntimeException and subclasses KnxSecureException,
|
// Note: KnxException does not cover KnxRuntimeException and subclasses KnxSecureException,
|
||||||
// KnxIllegArgumentException
|
// KnxIllegalArgumentException
|
||||||
if (datapoint.getRetries() < datapoint.getLimit()) {
|
if (datapoint.getRetries() < datapoint.getLimit()) {
|
||||||
readDatapoints.add(datapoint);
|
readDatapoints.add(datapoint);
|
||||||
logger.debug("Could not read value for datapoint {}: {}. Going to retry.",
|
logger.debug("Could not read value for datapoint {}: {}. Going to retry.",
|
||||||
@ -532,6 +548,12 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO #8872: allow write access, currently only listening mode
|
||||||
|
if (openhabSecurity.groupKeys().containsKey(groupAddress)) {
|
||||||
|
logger.debug("outgoing secure communication not implemented, write to GA '{}' skipped", groupAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Datapoint datapoint = new CommandDP(groupAddress, thingUID.toString(), 0,
|
Datapoint datapoint = new CommandDP(groupAddress, thingUID.toString(), 0,
|
||||||
NORMALIZED_DPT.getOrDefault(dpt, dpt));
|
NORMALIZED_DPT.getOrDefault(dpt, dpt));
|
||||||
String mappedValue = ValueEncoder.encode(type, dpt);
|
String mappedValue = ValueEncoder.encode(type, dpt);
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.knx.internal.client;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.link.KNXLinkClosedException;
|
||||||
|
import tuwien.auto.calimero.link.KNXNetworkLink;
|
||||||
|
import tuwien.auto.calimero.mgmt.ManagementClientImpl;
|
||||||
|
import tuwien.auto.calimero.mgmt.SecureManagement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is to provide access to protected constructors in the Calimero library.
|
||||||
|
* Reason is to provide custom KNX keyring data.
|
||||||
|
*
|
||||||
|
* @author Holger Friedrich - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CustomManagementClientImpl extends ManagementClientImpl {
|
||||||
|
public CustomManagementClientImpl(final KNXNetworkLink link, final SecureManagement secureManagement)
|
||||||
|
throws KNXLinkClosedException {
|
||||||
|
// super(link, secureManagement) is available since Calimero 2.5.1
|
||||||
|
super(link, secureManagement);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.knx.internal.client;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.link.KNXLinkClosedException;
|
||||||
|
import tuwien.auto.calimero.mgmt.ManagementClient;
|
||||||
|
import tuwien.auto.calimero.mgmt.ManagementProceduresImpl;
|
||||||
|
import tuwien.auto.calimero.mgmt.TransportLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is to provide access to protected constructors in the Calimero library.
|
||||||
|
* Reason is to provide custom KNX keyring data.
|
||||||
|
*
|
||||||
|
* @author Holger Friedrich - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CustomManagementProceduresImpl extends ManagementProceduresImpl {
|
||||||
|
public CustomManagementProceduresImpl(final ManagementClient mgmtClient, final TransportLayer transportLayer)
|
||||||
|
throws KNXLinkClosedException {
|
||||||
|
// super(mgmtClient, transportLayer) is protected
|
||||||
|
super(mgmtClient, transportLayer);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.knx.internal.client;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.SerialNumber;
|
||||||
|
import tuwien.auto.calimero.link.KNXLinkClosedException;
|
||||||
|
import tuwien.auto.calimero.mgmt.SecureManagement;
|
||||||
|
import tuwien.auto.calimero.mgmt.TransportLayer;
|
||||||
|
import tuwien.auto.calimero.mgmt.TransportLayerImpl;
|
||||||
|
import tuwien.auto.calimero.secure.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is to provide access to protected constructors in the Calimero library.
|
||||||
|
* Reason is to provide custom KNX keyring data.
|
||||||
|
*
|
||||||
|
* @author Holger Friedrich - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CustomSecureManagement extends SecureManagement {
|
||||||
|
public CustomSecureManagement(final TransportLayer transportLayer, final Security security)
|
||||||
|
throws KNXLinkClosedException {
|
||||||
|
// super(link, secureManagement) is not yet available in Calimero 2.5.1
|
||||||
|
super((TransportLayerImpl) transportLayer, SerialNumber.Zero, 0, security.deviceToolKeys());
|
||||||
|
|
||||||
|
// instance of Security has been created in ctor of SecureManagement, but only using deviceToolKeys
|
||||||
|
// no need to clear, just copy (otherwise SAL would lack the group keys)
|
||||||
|
final Security sal = security();
|
||||||
|
sal.groupKeys().putAll(security.groupKeys());
|
||||||
|
sal.groupSenders().putAll(security.groupSenders());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final Security security() {
|
||||||
|
return super.security();
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ import tuwien.auto.calimero.link.KNXNetworkLink;
|
|||||||
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
|
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
|
||||||
import tuwien.auto.calimero.link.medium.KNXMediumSettings;
|
import tuwien.auto.calimero.link.medium.KNXMediumSettings;
|
||||||
import tuwien.auto.calimero.link.medium.TPSettings;
|
import tuwien.auto.calimero.link.medium.TPSettings;
|
||||||
|
import tuwien.auto.calimero.secure.Security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP specific {@link AbstractKNXClient} implementation.
|
* IP specific {@link AbstractKNXClient} implementation.
|
||||||
@ -88,9 +89,9 @@ public class IPClient extends AbstractKNXClient {
|
|||||||
byte[] secureRoutingBackboneGroupKey, long secureRoutingLatencyToleranceMs, byte[] secureTunnelDevKey,
|
byte[] secureRoutingBackboneGroupKey, long secureRoutingLatencyToleranceMs, byte[] secureTunnelDevKey,
|
||||||
int secureTunnelUser, byte[] secureTunnelUserKey, ThingUID thingUID, int responseTimeout, int readingPause,
|
int secureTunnelUser, byte[] secureTunnelUserKey, ThingUID thingUID, int responseTimeout, int readingPause,
|
||||||
int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
|
int readRetriesLimit, ScheduledExecutorService knxScheduler, CommandExtensionData commandExtensionData,
|
||||||
StatusUpdateCallback statusUpdateCallback) {
|
Security openhabSecurity, StatusUpdateCallback statusUpdateCallback) {
|
||||||
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
|
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
|
||||||
commandExtensionData, statusUpdateCallback);
|
commandExtensionData, openhabSecurity, statusUpdateCallback);
|
||||||
this.ipConnectionType = ipConnectionType;
|
this.ipConnectionType = ipConnectionType;
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
this.localSource = localSource;
|
this.localSource = localSource;
|
||||||
|
@ -32,6 +32,7 @@ import tuwien.auto.calimero.KNXException;
|
|||||||
import tuwien.auto.calimero.link.KNXNetworkLink;
|
import tuwien.auto.calimero.link.KNXNetworkLink;
|
||||||
import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
|
import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
|
||||||
import tuwien.auto.calimero.link.medium.TPSettings;
|
import tuwien.auto.calimero.link.medium.TPSettings;
|
||||||
|
import tuwien.auto.calimero.secure.Security;
|
||||||
import tuwien.auto.calimero.serial.FT12Connection;
|
import tuwien.auto.calimero.serial.FT12Connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,10 +54,10 @@ public class SerialClient extends AbstractKNXClient {
|
|||||||
|
|
||||||
public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
|
public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
|
||||||
int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
|
int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
|
||||||
SerialPortManager serialPortManager, CommandExtensionData commandExtensionData,
|
SerialPortManager serialPortManager, CommandExtensionData commandExtensionData, Security openhabSecurity,
|
||||||
StatusUpdateCallback statusUpdateCallback) {
|
StatusUpdateCallback statusUpdateCallback) {
|
||||||
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
|
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
|
||||||
commandExtensionData, statusUpdateCallback);
|
commandExtensionData, openhabSecurity, statusUpdateCallback);
|
||||||
this.serialPortManager = serialPortManager;
|
this.serialPortManager = serialPortManager;
|
||||||
this.serialPort = serialPort;
|
this.serialPort = serialPort;
|
||||||
this.useCemi = useCemi;
|
this.useCemi = useCemi;
|
||||||
|
@ -26,6 +26,8 @@ public class BridgeConfiguration {
|
|||||||
private int readingPause = 0;
|
private int readingPause = 0;
|
||||||
private int readRetriesLimit = 0;
|
private int readRetriesLimit = 0;
|
||||||
private int responseTimeout = 0;
|
private int responseTimeout = 0;
|
||||||
|
private String keyringFile = "";
|
||||||
|
private String keyringPassword = "";
|
||||||
|
|
||||||
public int getAutoReconnectPeriod() {
|
public int getAutoReconnectPeriod() {
|
||||||
return autoReconnectPeriod;
|
return autoReconnectPeriod;
|
||||||
@ -46,4 +48,12 @@ public class BridgeConfiguration {
|
|||||||
public void setAutoReconnectPeriod(int period) {
|
public void setAutoReconnectPeriod(int period) {
|
||||||
autoReconnectPeriod = period;
|
autoReconnectPeriod = period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getKeyringFile() {
|
||||||
|
return keyringFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyringPassword() {
|
||||||
|
return keyringPassword;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ public class IPBridgeConfiguration extends BridgeConfiguration {
|
|||||||
private String tunnelUserId = "";
|
private String tunnelUserId = "";
|
||||||
private String tunnelUserPassword = "";
|
private String tunnelUserPassword = "";
|
||||||
private String tunnelDeviceAuthentication = "";
|
private String tunnelDeviceAuthentication = "";
|
||||||
|
private String tunnelSourceAddress = "";
|
||||||
|
|
||||||
public Boolean getUseNAT() {
|
public Boolean getUseNAT() {
|
||||||
return useNAT;
|
return useNAT;
|
||||||
@ -73,4 +74,8 @@ public class IPBridgeConfiguration extends BridgeConfiguration {
|
|||||||
public String getTunnelDeviceAuthentication() {
|
public String getTunnelDeviceAuthentication() {
|
||||||
return tunnelDeviceAuthentication;
|
return tunnelDeviceAuthentication;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTunnelSourceAddress() {
|
||||||
|
return tunnelSourceAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,14 +72,25 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
IPBridgeConfiguration config = getConfigAs(IPBridgeConfiguration.class);
|
IPBridgeConfiguration config = getConfigAs(IPBridgeConfiguration.class);
|
||||||
boolean securityAvailable = false;
|
boolean securityAvailable = false;
|
||||||
try {
|
try {
|
||||||
securityAvailable = initializeSecurity(config.getRouterBackboneKey(),
|
securityAvailable = initializeSecurity(config.getKeyringFile(), config.getKeyringPassword(),
|
||||||
config.getTunnelDeviceAuthentication(), config.getTunnelUserId(), config.getTunnelUserPassword());
|
config.getRouterBackboneKey(), config.getTunnelDeviceAuthentication(), config.getTunnelUserId(),
|
||||||
|
config.getTunnelUserPassword(), config.getTunnelSourceAddress());
|
||||||
if (securityAvailable) {
|
if (securityAvailable) {
|
||||||
logger.debug("KNX secure: router backboneGroupKey is {} set",
|
logger.debug("KNX secure: router backboneGroupKey is {} set",
|
||||||
((secureRouting.backboneGroupKey.length == 16) ? "properly" : "not"));
|
((secureRouting.backboneGroupKey.length == 16) ? "properly" : "not"));
|
||||||
boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
|
boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16)
|
||||||
&& (secureTunnel.userKey.length == 16));
|
&& (secureTunnel.userKey.length == 16));
|
||||||
logger.debug("KNX secure: tunnel keys are {} set", (tunnelOk ? "properly" : "not"));
|
logger.debug("KNX secure: tunnel keys are {} set", (tunnelOk ? "properly" : "not"));
|
||||||
|
|
||||||
|
if (keyring.isPresent()) {
|
||||||
|
logger.debug("KNX secure available for {} devices, {} group addresses",
|
||||||
|
openhabSecurity.deviceToolKeys().size(), openhabSecurity.groupKeys().size());
|
||||||
|
|
||||||
|
logger.debug("Secure group addresses and associated devices: {}",
|
||||||
|
secHelperGetSecureGroupAddresses(openhabSecurity));
|
||||||
|
} else {
|
||||||
|
logger.debug("KNX secure: keyring is not available");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("KNX security not configured");
|
logger.debug("KNX security not configured");
|
||||||
}
|
}
|
||||||
@ -154,7 +165,7 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (secureRouting.backboneGroupKey.length != 16) {
|
if (secureRouting.backboneGroupKey.length != 16) {
|
||||||
// failed to read shared backbone group key from config
|
// failed to read shared backbone group key from config or keyring
|
||||||
logger.warn("Bridge {} invalid security configuration for secure routing", thing.getUID());
|
logger.warn("Bridge {} invalid security configuration for secure routing", thing.getUID());
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"@text/error.knx-secure-routing-backbonegroupkey-invalid");
|
"@text/error.knx-secure-routing-backbonegroupkey-invalid");
|
||||||
@ -186,7 +197,7 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
|
secureRouting.backboneGroupKey, secureRouting.latencyToleranceMs, secureTunnel.devKey,
|
||||||
secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout(),
|
secureTunnel.user, secureTunnel.userKey, thing.getUID(), config.getResponseTimeout(),
|
||||||
config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), getCommandExtensionData(),
|
config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), getCommandExtensionData(),
|
||||||
this);
|
openhabSecurity, this);
|
||||||
|
|
||||||
IPClient tmpClient = client;
|
IPClient tmpClient = client;
|
||||||
if (tmpClient != null) {
|
if (tmpClient != null) {
|
||||||
@ -200,7 +211,7 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
public void dispose() {
|
public void dispose() {
|
||||||
Future<?> tmpInitJob = initJob;
|
Future<?> tmpInitJob = initJob;
|
||||||
if (tmpInitJob != null) {
|
if (tmpInitJob != null) {
|
||||||
while (!tmpInitJob.isDone()) {
|
if (!tmpInitJob.isDone()) {
|
||||||
logger.trace("Bridge {}, shutdown during init, trying to cancel", thing.getUID());
|
logger.trace("Bridge {}, shutdown during init, trying to cancel", thing.getUID());
|
||||||
tmpInitJob.cancel(true);
|
tmpInitJob.cancel(true);
|
||||||
try {
|
try {
|
||||||
|
@ -12,7 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.knx.internal.handler;
|
package org.openhab.binding.knx.internal.handler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@ -21,6 +29,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.knx.internal.client.KNXClient;
|
import org.openhab.binding.knx.internal.client.KNXClient;
|
||||||
import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
|
import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
|
||||||
|
import org.openhab.core.OpenHAB;
|
||||||
import org.openhab.core.common.ThreadPoolManager;
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
@ -29,8 +38,16 @@ import org.openhab.core.thing.ThingStatusDetail;
|
|||||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.GroupAddress;
|
||||||
|
import tuwien.auto.calimero.IndividualAddress;
|
||||||
|
import tuwien.auto.calimero.KNXFormatException;
|
||||||
import tuwien.auto.calimero.knxnetip.SecureConnection;
|
import tuwien.auto.calimero.knxnetip.SecureConnection;
|
||||||
|
import tuwien.auto.calimero.secure.Keyring;
|
||||||
|
import tuwien.auto.calimero.secure.Keyring.Backbone;
|
||||||
|
import tuwien.auto.calimero.secure.Keyring.Interface;
|
||||||
import tuwien.auto.calimero.secure.KnxSecureException;
|
import tuwien.auto.calimero.secure.KnxSecureException;
|
||||||
|
import tuwien.auto.calimero.secure.Security;
|
||||||
|
import tuwien.auto.calimero.xml.KNXMLException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
|
* The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
|
||||||
@ -73,12 +90,20 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
|
|||||||
|
|
||||||
private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
|
private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
|
||||||
private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
|
private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
protected Optional<Keyring> keyring;
|
||||||
|
// password used to protect content of the keyring
|
||||||
|
private String keyringPassword = "";
|
||||||
|
// backbone key (shared password used for secure router mode)
|
||||||
|
|
||||||
|
protected Security openhabSecurity;
|
||||||
protected SecureRoutingConfig secureRouting;
|
protected SecureRoutingConfig secureRouting;
|
||||||
protected SecureTunnelConfig secureTunnel;
|
protected SecureTunnelConfig secureTunnel;
|
||||||
private CommandExtensionData commandExtensionData;
|
private CommandExtensionData commandExtensionData;
|
||||||
|
|
||||||
public KNXBridgeBaseThingHandler(Bridge bridge) {
|
public KNXBridgeBaseThingHandler(Bridge bridge) {
|
||||||
super(bridge);
|
super(bridge);
|
||||||
|
keyring = Optional.empty();
|
||||||
|
openhabSecurity = Security.newSecurity();
|
||||||
secureRouting = new SecureRoutingConfig();
|
secureRouting = new SecureRoutingConfig();
|
||||||
secureTunnel = new SecureTunnelConfig();
|
secureTunnel = new SecureTunnelConfig();
|
||||||
commandExtensionData = new CommandExtensionData(new TreeMap<>());
|
commandExtensionData = new CommandExtensionData(new TreeMap<>());
|
||||||
@ -91,24 +116,47 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Initialize KNX secure if configured (full interface)
|
* Initialize KNX secure if configured (simple interface)
|
||||||
*
|
*
|
||||||
* @param cRouterBackboneGroupKey shared key for secure router mode.
|
* @param cKeyringFile keyring file, exported from ETS tool
|
||||||
* @param cTunnelDevAuth device password for IP interface in tunnel mode.
|
* @param cKeyringPassword keyring password, set during export from ETS tool
|
||||||
* @param cTunnelUser user id for tunnel mode. Must be an integer >0.
|
|
||||||
* @param cTunnelPassword user password for tunnel mode.
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected boolean initializeSecurity(String cRouterBackboneGroupKey, String cTunnelDevAuth, String cTunnelUser,
|
protected boolean initializeSecurity(String cKeyringFile, String cKeyringPassword) throws KnxSecureException {
|
||||||
String cTunnelPassword) throws KnxSecureException {
|
return initializeSecurity(cKeyringFile, cKeyringPassword, "", "", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Initialize KNX secure if configured (full interface)
|
||||||
|
*
|
||||||
|
* @param cKeyringFile keyring file, exported from ETS tool
|
||||||
|
* @param cKeyringPassword keyring password, set during export from ETS tool
|
||||||
|
* @param cRouterBackboneGroupKey shared key for secure router mode. If not given, it will be read from keyring.
|
||||||
|
* @param cTunnelDevAuth device password for IP interface in tunnel mode. If not given it will be read from keyring
|
||||||
|
* if cTunnelSourceAddr is configured.
|
||||||
|
* @param cTunnelUser user id for tunnel mode. Must be an integer >0. If not given it will be read from keyring if
|
||||||
|
* cTunnelSourceAddr is configured.
|
||||||
|
* @param cTunnelPassword user password for tunnel mode. If not given it will be read from keyring if
|
||||||
|
* cTunnelSourceAddr is configured.
|
||||||
|
* @param cTunnelSourceAddr specify the KNX address to uniquely identify a tunnel connection in secure tunneling
|
||||||
|
* mode. Not required if cTunnelDevAuth, cTunnelUser, and cTunnelPassword are given.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean initializeSecurity(String cKeyringFile, String cKeyringPassword, String cRouterBackboneGroupKey,
|
||||||
|
String cTunnelDevAuth, String cTunnelUser, String cTunnelPassword, String cTunnelSourceAddr)
|
||||||
|
throws KnxSecureException {
|
||||||
|
keyring = Optional.empty();
|
||||||
|
keyringPassword = "";
|
||||||
|
IndividualAddress secureTunnelSourceAddr = null;
|
||||||
secureRouting = new SecureRoutingConfig();
|
secureRouting = new SecureRoutingConfig();
|
||||||
secureTunnel = new SecureTunnelConfig();
|
secureTunnel = new SecureTunnelConfig();
|
||||||
|
|
||||||
boolean securityInitialized = false;
|
boolean securityInitialized = false;
|
||||||
|
|
||||||
// step 1: secure routing, backbone group key manually specified in OH config
|
// step 1: secure routing, backbone group key manually specified in OH config (typically it is read from
|
||||||
|
// keyring)
|
||||||
if (!cRouterBackboneGroupKey.isBlank()) {
|
if (!cRouterBackboneGroupKey.isBlank()) {
|
||||||
// provided in config
|
// provided in config, this will override whatever is read from keyring
|
||||||
String key = cRouterBackboneGroupKey.trim().replaceFirst("^0x", "").trim().replace(" ", "");
|
String key = cRouterBackboneGroupKey.trim().replaceFirst("^0x", "").trim().replace(" ", "");
|
||||||
if (!key.isEmpty()) {
|
if (!key.isEmpty()) {
|
||||||
// helper may throw KnxSecureException
|
// helper may throw KnxSecureException
|
||||||
@ -118,6 +166,14 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// step 2: check if valid tunnel parameters are specified in config
|
// step 2: check if valid tunnel parameters are specified in config
|
||||||
|
if (!cTunnelSourceAddr.isBlank()) {
|
||||||
|
try {
|
||||||
|
secureTunnelSourceAddr = new IndividualAddress(cTunnelSourceAddr.trim());
|
||||||
|
securityInitialized = true;
|
||||||
|
} catch (KNXFormatException e) {
|
||||||
|
throw new KnxSecureException("tunnel source address cannot be parsed, valid format is x.y.z");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!cTunnelDevAuth.isBlank()) {
|
if (!cTunnelDevAuth.isBlank()) {
|
||||||
secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(cTunnelDevAuth.toCharArray());
|
secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(cTunnelDevAuth.toCharArray());
|
||||||
securityInitialized = true;
|
securityInitialized = true;
|
||||||
@ -139,29 +195,107 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
|
|||||||
securityInitialized = true;
|
securityInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// step 3: keyring
|
||||||
|
if (!cKeyringFile.isBlank()) {
|
||||||
|
// filename defined in config, start parsing
|
||||||
|
try {
|
||||||
|
// load keyring file from config dir, folder misc
|
||||||
|
String keyringUri = OpenHAB.getConfigFolder() + File.separator + "misc" + File.separator + cKeyringFile;
|
||||||
|
try {
|
||||||
|
keyring = Optional.ofNullable(Keyring.load(keyringUri));
|
||||||
|
} catch (KNXMLException e) {
|
||||||
|
throw new KnxSecureException("keyring file configured, but loading failed: ", e);
|
||||||
|
}
|
||||||
|
if (!keyring.isPresent()) {
|
||||||
|
throw new KnxSecureException("keyring file configured, but loading failed: " + keyringUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loading was successful, check signatures
|
||||||
|
// -> disabled, as Calimero v2.5 does this within the load() function
|
||||||
|
// if (!keyring.verifySignature(cKeyringPassword.toCharArray()))
|
||||||
|
// throw new KnxSecureException(
|
||||||
|
// "signature verification failed, please check keyring file: " + keyringUri);
|
||||||
|
keyringPassword = cKeyringPassword;
|
||||||
|
|
||||||
|
// We use a specific Security instance instead of default Calimero static instance
|
||||||
|
// Security.defaultInstallation().
|
||||||
|
// This necessary as it seems there is no possibility to clear the global instance on config changes.
|
||||||
|
openhabSecurity.useKeyring(keyring.get(), keyringPassword.toCharArray());
|
||||||
|
|
||||||
|
securityInitialized = true;
|
||||||
|
} catch (KnxSecureException e) {
|
||||||
|
keyring = Optional.empty();
|
||||||
|
keyringPassword = "";
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
// load() may throw KnxSecureException or other undeclared exceptions, e.g. UncheckedIOException when
|
||||||
|
// file is not found
|
||||||
|
keyring = Optional.empty();
|
||||||
|
keyringPassword = "";
|
||||||
|
throw new KnxSecureException("keyring file configured, but loading failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4: router: load backboneGroupKey from keyring if not manually specified
|
||||||
|
if ((secureRouting.backboneGroupKey.length == 0) && (keyring.isPresent())) {
|
||||||
|
// backbone group key is only available if secure routers are present
|
||||||
|
final Optional<byte[]> key = secHelperReadBackboneKey(keyring, keyringPassword);
|
||||||
|
if (key.isPresent()) {
|
||||||
|
secureRouting.backboneGroupKey = key.get();
|
||||||
|
securityInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// step 5: router: load latencyTolerance
|
// step 5: router: load latencyTolerance
|
||||||
// default to 2000ms
|
// default to 2000ms
|
||||||
// this parameter is currently not exposed in config, it may later be set by using the keyring
|
// this parameter is currently not exposed in config, in case it must be set by using the keyring
|
||||||
secureRouting.latencyToleranceMs = 2000;
|
secureRouting.latencyToleranceMs = 2000;
|
||||||
|
if (keyring.isPresent()) {
|
||||||
|
// backbone latency is only relevant if secure routers are present
|
||||||
|
final Optional<Backbone> bb = keyring.get().backbone();
|
||||||
|
if (bb.isPresent()) {
|
||||||
|
final long toleranceMs = bb.get().latencyTolerance().toMillis();
|
||||||
|
secureRouting.latencyToleranceMs = toleranceMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 6: tunnel: load data from keyring
|
||||||
|
if (secureTunnelSourceAddr != null) {
|
||||||
|
// requires a valid keyring
|
||||||
|
if (!keyring.isPresent()) {
|
||||||
|
throw new KnxSecureException("valid keyring specification required for secure tunnel mode");
|
||||||
|
}
|
||||||
|
// other parameters will not be accepted, since all is read from keyring in this case
|
||||||
|
if ((secureTunnel.userKey.length > 0) || secureTunnel.user != 0 || (secureTunnel.devKey.length > 0)) {
|
||||||
|
throw new KnxSecureException(
|
||||||
|
"tunnelSourceAddr is configured, please do not specify other parameters of secure tunnel");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<SecureTunnelConfig> config = secHelperReadTunnelConfig(keyring, keyringPassword,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
if (config.isEmpty()) {
|
||||||
|
throw new KnxSecureException("tunnel definition cannot be read from keyring");
|
||||||
|
}
|
||||||
|
secureTunnel = config.get();
|
||||||
|
}
|
||||||
return securityInitialized;
|
return securityInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* converts hex string (32 characters) to byte[16]
|
* converts hex string (32 characters) to byte[16]
|
||||||
*
|
*
|
||||||
* @param hexstring 32 characters hex
|
* @param hexString 32 characters hex
|
||||||
* @return key in byte array format
|
* @return key in byte array format
|
||||||
*/
|
*/
|
||||||
public static byte[] secHelperParseBackboneKey(String hexstring) throws KnxSecureException {
|
public static byte[] secHelperParseBackboneKey(String hexString) throws KnxSecureException {
|
||||||
if (hexstring.length() != 32) {
|
if (hexString.length() != 32) {
|
||||||
throw new KnxSecureException("backbone key must be 32 characters (16 byte hex notation)");
|
throw new KnxSecureException("backbone key must be 32 characters (16 byte hex notation)");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] parsed = new byte[16];
|
byte[] parsed = new byte[16];
|
||||||
try {
|
try {
|
||||||
for (byte i = 0; i < 16; i++) {
|
for (byte i = 0; i < 16; i++) {
|
||||||
parsed[i] = (byte) Integer.parseInt(hexstring.substring(2 * i, 2 * i + 2), 16);
|
parsed[i] = (byte) Integer.parseInt(hexString.substring(2 * i, 2 * i + 2), 16);
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
|
throw new KnxSecureException("backbone key configured, cannot parse hex string, illegal character", e);
|
||||||
@ -169,6 +303,97 @@ public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implem
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Optional<byte[]> secHelperReadBackboneKey(Optional<Keyring> keyring, String keyringPassword) {
|
||||||
|
if (keyring.isEmpty()) {
|
||||||
|
throw new KnxSecureException("keyring not available, cannot read backbone key");
|
||||||
|
}
|
||||||
|
final Optional<Backbone> bb = keyring.get().backbone();
|
||||||
|
if (bb.isPresent()) {
|
||||||
|
final Optional<byte[]> gk = bb.get().groupKey();
|
||||||
|
if (gk.isPresent()) {
|
||||||
|
byte[] secureRoutingBackboneGroupKey = keyring.get().decryptKey(gk.get(),
|
||||||
|
keyringPassword.toCharArray());
|
||||||
|
if (secureRoutingBackboneGroupKey.length != 16) {
|
||||||
|
throw new KnxSecureException("backbone key found, unexpected length != 16");
|
||||||
|
}
|
||||||
|
return Optional.of(secureRoutingBackboneGroupKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<SecureTunnelConfig> secHelperReadTunnelConfig(Optional<Keyring> keyring,
|
||||||
|
String keyringPassword, IndividualAddress secureTunnelSourceAddr) {
|
||||||
|
if (keyring.isEmpty()) {
|
||||||
|
throw new KnxSecureException("keyring not available, cannot read tunnel config");
|
||||||
|
}
|
||||||
|
// iterate all interfaces to find matching secureTunnelSourceAddr
|
||||||
|
SecureTunnelConfig secureTunnel = new SecureTunnelConfig();
|
||||||
|
Iterator<List<Interface>> itInterface = keyring.get().interfaces().values().iterator();
|
||||||
|
boolean complete = false;
|
||||||
|
while (!complete && itInterface.hasNext()) {
|
||||||
|
List<Interface> eInterface = itInterface.next();
|
||||||
|
// tunnels are nested
|
||||||
|
Iterator<Interface> itTunnel = eInterface.iterator();
|
||||||
|
while (!complete && itTunnel.hasNext()) {
|
||||||
|
Interface eTunnel = itTunnel.next();
|
||||||
|
|
||||||
|
if (secureTunnelSourceAddr.equals(eTunnel.address())) {
|
||||||
|
String pw = "";
|
||||||
|
final Optional<byte[]> pwBytes = eTunnel.password();
|
||||||
|
if (pwBytes.isPresent()) {
|
||||||
|
pw = new String(keyring.get().decryptPassword(pwBytes.get(), keyringPassword.toCharArray()));
|
||||||
|
secureTunnel.userKey = SecureConnection.hashUserPassword(pw.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
String au = "";
|
||||||
|
final Optional<byte[]> auBytes = eTunnel.authentication();
|
||||||
|
if (auBytes.isPresent()) {
|
||||||
|
au = new String(keyring.get().decryptPassword(auBytes.get(), keyringPassword.toCharArray()));
|
||||||
|
secureTunnel.devKey = SecureConnection.hashDeviceAuthenticationPassword(au.toCharArray())
|
||||||
|
.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set user, 0=fail
|
||||||
|
secureTunnel.user = eTunnel.user();
|
||||||
|
|
||||||
|
return Optional.of(secureTunnel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Show all secure group addresses and surrogates. A surrogate is the device which is asked to carry out an indirect
|
||||||
|
* read/write request.
|
||||||
|
* Simpler approach w/o surrogates: Security.defaultInstallation().groupSenders().toString());
|
||||||
|
*/
|
||||||
|
public static String secHelperGetSecureGroupAddresses(final Security openhabSecurity) {
|
||||||
|
Map<GroupAddress, Set<String>> groupSendersWithSurrogate = new HashMap<GroupAddress, Set<String>>();
|
||||||
|
final Map<GroupAddress, Set<IndividualAddress>> senders = openhabSecurity.groupSenders();
|
||||||
|
for (var entry : senders.entrySet()) {
|
||||||
|
final GroupAddress ga = entry.getKey();
|
||||||
|
// the following approach is uses by Calimero to deduce the surrogate for GA diagnostics
|
||||||
|
// see calimero-core security/SecureApplicationLayer.java, surrogate(...)
|
||||||
|
IndividualAddress surrogate = null;
|
||||||
|
try {
|
||||||
|
surrogate = senders.getOrDefault(ga, Set.of()).stream().findAny().get();
|
||||||
|
} catch (NoSuchElementException e) {
|
||||||
|
}
|
||||||
|
Set<String> devices = new HashSet<String>();
|
||||||
|
for (var device : entry.getValue()) {
|
||||||
|
if (device.equals(surrogate)) {
|
||||||
|
devices.add(device.toString() + " (S)");
|
||||||
|
} else {
|
||||||
|
devices.add(device.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groupSendersWithSurrogate.put(ga, devices);
|
||||||
|
}
|
||||||
|
return groupSendersWithSurrogate.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
|
@ -23,9 +23,12 @@ import org.openhab.binding.knx.internal.config.SerialBridgeConfiguration;
|
|||||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.secure.KnxSecureException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link IPBridgeThingHandler} is responsible for handling commands, which are
|
* The {@link IPBridgeThingHandler} is responsible for handling commands, which are
|
||||||
* sent to one of the channels. It implements a KNX Serial/USB Gateway, that either acts as a
|
* sent to one of the channels. It implements a KNX Serial/USB Gateway, that either acts as a
|
||||||
@ -57,7 +60,7 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
|
SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
|
||||||
client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(), config.getResponseTimeout(),
|
client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(), config.getResponseTimeout(),
|
||||||
config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), config.getSerialPort(),
|
config.getReadingPause(), config.getReadRetriesLimit(), getScheduler(), config.getSerialPort(),
|
||||||
config.useCemi(), serialPortManager, getCommandExtensionData(), this);
|
config.useCemi(), serialPortManager, getCommandExtensionData(), openhabSecurity, this);
|
||||||
|
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
// delay actual initialization, allow for longer runtime of actual initialization
|
// delay actual initialization, allow for longer runtime of actual initialization
|
||||||
@ -65,6 +68,32 @@ public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void initializeLater() {
|
public void initializeLater() {
|
||||||
|
SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
|
||||||
|
try {
|
||||||
|
if (initializeSecurity(config.getKeyringFile(), config.getKeyringPassword())) {
|
||||||
|
if (keyring.isPresent()) {
|
||||||
|
logger.info("KNX secure available for {} devices, {} group addresses",
|
||||||
|
openhabSecurity.deviceToolKeys().size(), openhabSecurity.groupKeys().size());
|
||||||
|
|
||||||
|
logger.debug("Secure group addresses and associated devices: {}",
|
||||||
|
secHelperGetSecureGroupAddresses(openhabSecurity));
|
||||||
|
} else {
|
||||||
|
logger.debug("KNX secure: keyring is not available");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("KNX security not configured");
|
||||||
|
}
|
||||||
|
} catch (KnxSecureException e) {
|
||||||
|
logger.debug("{}, {}", thing.getUID(), e.toString());
|
||||||
|
|
||||||
|
String message = e.getLocalizedMessage();
|
||||||
|
if (message == null) {
|
||||||
|
message = e.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "KNX security: " + message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SerialClient tmpClient = client;
|
SerialClient tmpClient = client;
|
||||||
if (tmpClient != null) {
|
if (tmpClient != null) {
|
||||||
tmpClient.initialize();
|
tmpClient.initialize();
|
||||||
|
@ -33,6 +33,10 @@ thing-type.config.knx.ip.group.knxsecure.label = KNX secure
|
|||||||
thing-type.config.knx.ip.group.knxsecure.description = Settings for KNX secure. Optional. Requires KNX secure features to be active in KNX installation.
|
thing-type.config.knx.ip.group.knxsecure.description = Settings for KNX secure. Optional. Requires KNX secure features to be active in KNX installation.
|
||||||
thing-type.config.knx.ip.ipAddress.label = Network Address
|
thing-type.config.knx.ip.ipAddress.label = Network Address
|
||||||
thing-type.config.knx.ip.ipAddress.description = Network address of the KNX/IP gateway
|
thing-type.config.knx.ip.ipAddress.description = Network address of the KNX/IP gateway
|
||||||
|
thing-type.config.knx.ip.keyringFile.label = Keyring file
|
||||||
|
thing-type.config.knx.ip.keyringFile.description = Keyring file exported from ETS and placed in openHAB config/misc folder, e.g. knx.knxkeys. This file is mandatory to decode secure group addresses. It can provide settings and credentials for IP Secure if not configured separately.
|
||||||
|
thing-type.config.knx.ip.keyringPassword.label = Keyring password
|
||||||
|
thing-type.config.knx.ip.keyringPassword.description = Keyring file password (set during export from ETS).
|
||||||
thing-type.config.knx.ip.localIp.label = Local Network Address
|
thing-type.config.knx.ip.localIp.label = Local Network Address
|
||||||
thing-type.config.knx.ip.localIp.description = Network address of the local host to be used to set up the connection to the KNX/IP gateway
|
thing-type.config.knx.ip.localIp.description = Network address of the local host to be used to set up the connection to the KNX/IP gateway
|
||||||
thing-type.config.knx.ip.localSourceAddr.label = Local Device Address
|
thing-type.config.knx.ip.localSourceAddr.label = Local Device Address
|
||||||
@ -46,13 +50,15 @@ thing-type.config.knx.ip.readingPause.description = Time in milliseconds of how
|
|||||||
thing-type.config.knx.ip.responseTimeout.label = Response Timeout
|
thing-type.config.knx.ip.responseTimeout.label = Response Timeout
|
||||||
thing-type.config.knx.ip.responseTimeout.description = Seconds to wait for a response from the KNX bus
|
thing-type.config.knx.ip.responseTimeout.description = Seconds to wait for a response from the KNX bus
|
||||||
thing-type.config.knx.ip.routerBackboneKey.label = Router backbone key
|
thing-type.config.knx.ip.routerBackboneKey.label = Router backbone key
|
||||||
thing-type.config.knx.ip.routerBackboneKey.description = Backbone key for secure router mode. 16 bytes in hex notation. Can also be found in ETS security report.
|
thing-type.config.knx.ip.routerBackboneKey.description = Backbone key for secure router mode. 16 bytes in hex notation. Can also be found in ETS security report. Optional, can be read from keyring file if it is configured.
|
||||||
thing-type.config.knx.ip.tunnelDeviceAuthentication.label = Tunnel device authentication
|
thing-type.config.knx.ip.tunnelDeviceAuthentication.label = Tunnel device authentication
|
||||||
thing-type.config.knx.ip.tunnelDeviceAuthentication.description = Tunnel device authentication for secure tunnel mode.
|
thing-type.config.knx.ip.tunnelDeviceAuthentication.description = Tunnel device authentication for secure tunnel mode. Optional, can be read from keyring file if tunnelSourceAddr is configured.
|
||||||
|
thing-type.config.knx.ip.tunnelSourceAddress.label = Tunnel source address
|
||||||
|
thing-type.config.knx.ip.tunnelSourceAddress.description = Physical KNX address of tunnel in secure mode. Optional, only used in combination with keyring file to uniquely identify a tunnel. If given, openHAB will try to read user id, user password and device authentication from keyring.
|
||||||
thing-type.config.knx.ip.tunnelUserId.label = Tunnel user id
|
thing-type.config.knx.ip.tunnelUserId.label = Tunnel user id
|
||||||
thing-type.config.knx.ip.tunnelUserId.description = Tunnel user id for secure tunnel mode.
|
thing-type.config.knx.ip.tunnelUserId.description = Tunnel user id for secure tunnel mode. Optional, can be read from keyring file if tunnelSourceAddr is configured.
|
||||||
thing-type.config.knx.ip.tunnelUserPassword.label = Tunnel user password
|
thing-type.config.knx.ip.tunnelUserPassword.label = Tunnel user password
|
||||||
thing-type.config.knx.ip.tunnelUserPassword.description = Tunnel user key for secure tunnel mode.
|
thing-type.config.knx.ip.tunnelUserPassword.description = Tunnel user key for secure tunnel mode. Optional, can be read from keyring file if tunnelSourceAddr is configured.
|
||||||
thing-type.config.knx.ip.type.label = IP Connection Type
|
thing-type.config.knx.ip.type.label = IP Connection Type
|
||||||
thing-type.config.knx.ip.type.description = The IP connection type for connecting to the KNX bus. Could be either TUNNEL, ROUTER, SECURETUNNEL, or SECUREROUTER
|
thing-type.config.knx.ip.type.description = The IP connection type for connecting to the KNX bus. Could be either TUNNEL, ROUTER, SECURETUNNEL, or SECUREROUTER
|
||||||
thing-type.config.knx.ip.type.option.TUNNEL = Tunnel
|
thing-type.config.knx.ip.type.option.TUNNEL = Tunnel
|
||||||
@ -63,6 +69,12 @@ thing-type.config.knx.ip.useNAT.label = Use NAT
|
|||||||
thing-type.config.knx.ip.useNAT.description = Set to "true" when having network address translation between this server and the gateway
|
thing-type.config.knx.ip.useNAT.description = Set to "true" when having network address translation between this server and the gateway
|
||||||
thing-type.config.knx.serial.autoReconnectPeriod.label = Auto Reconnect Period
|
thing-type.config.knx.serial.autoReconnectPeriod.label = Auto Reconnect Period
|
||||||
thing-type.config.knx.serial.autoReconnectPeriod.description = Seconds between connect retries when KNX link has been lost, 0 means never retry
|
thing-type.config.knx.serial.autoReconnectPeriod.description = Seconds between connect retries when KNX link has been lost, 0 means never retry
|
||||||
|
thing-type.config.knx.serial.group.knxsecure.label = KNX secure
|
||||||
|
thing-type.config.knx.serial.group.knxsecure.description = Settings for KNX secure. Optional. Requires KNX secure features to be active in KNX installation.
|
||||||
|
thing-type.config.knx.serial.keyringFile.label = Keyring file
|
||||||
|
thing-type.config.knx.serial.keyringFile.description = Keyring file exported from ETS and placed in openHAB config/misc folder, e.g. knx.knxkeys. This file is mandatory to decode secure group addresses.
|
||||||
|
thing-type.config.knx.serial.keyringPassword.label = Keyring password
|
||||||
|
thing-type.config.knx.serial.keyringPassword.description = Keyring file password (set during export from ETS).
|
||||||
thing-type.config.knx.serial.readRetriesLimit.label = Read Retries Limit
|
thing-type.config.knx.serial.readRetriesLimit.label = Read Retries Limit
|
||||||
thing-type.config.knx.serial.readRetriesLimit.description = Limits the read retries while initialization from the KNX bus
|
thing-type.config.knx.serial.readRetriesLimit.description = Limits the read retries while initialization from the KNX bus
|
||||||
thing-type.config.knx.serial.readingPause.label = Reading Pause
|
thing-type.config.knx.serial.readingPause.label = Reading Pause
|
||||||
@ -152,11 +164,6 @@ channel-type.config.knx.rollershutter.upDown.description = The group address (GA
|
|||||||
channel-type.config.knx.single.ga.label = Address
|
channel-type.config.knx.single.ga.label = Address
|
||||||
channel-type.config.knx.single.ga.description = The group address (GA) for this channel. Additional listening (status) GAs can be added by concatenating GAs with a "+" (e.g. "1/7/18+2/9/15"). The DPT can be defined by prepending it to the first GA, separated by ":" (e.g. "2.001:1/4/17"). GAs prepended with a "<" (e.g. "<5/1/125") are read from the bus on startup.
|
channel-type.config.knx.single.ga.description = The group address (GA) for this channel. Additional listening (status) GAs can be added by concatenating GAs with a "+" (e.g. "1/7/18+2/9/15"). The DPT can be defined by prepending it to the first GA, separated by ":" (e.g. "2.001:1/4/17"). GAs prepended with a "<" (e.g. "<5/1/125") are read from the bus on startup.
|
||||||
|
|
||||||
# thing types config
|
|
||||||
|
|
||||||
thing-type.config.knx.serial.group.knxsecure.label = KNX secure
|
|
||||||
thing-type.config.knx.serial.group.knxsecure.description = Settings for KNX secure. Requires KNX secure features to be active in KNX installation.
|
|
||||||
|
|
||||||
# add-on specific strings
|
# add-on specific strings
|
||||||
|
|
||||||
error.knx-secure-routing-backbonegroupkey-invalid = backbonegroupkey invalid, please check if it is specified correctly
|
error.knx-secure-routing-backbonegroupkey-invalid = backbonegroupkey invalid, please check if it is specified correctly
|
||||||
|
@ -73,29 +73,61 @@
|
|||||||
<description>Seconds between connection retries when KNX link has been lost, 0 means never retry, minimum 30s</description>
|
<description>Seconds between connection retries when KNX link has been lost, 0 means never retry, minimum 30s</description>
|
||||||
<default>60</default>
|
<default>60</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="keyringFile" type="text" groupName="knxsecure">
|
||||||
|
<label>Keyring file</label>
|
||||||
|
<description>Keyring file exported from ETS and placed in openHAB config/misc folder, e.g.
|
||||||
|
knx.knxkeys. This file is
|
||||||
|
mandatory to decode secure group addresses. It can provide settings
|
||||||
|
and credentials for IP Secure if not configured
|
||||||
|
separately.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="keyringPassword" type="text" groupName="knxsecure">
|
||||||
|
<context>password</context>
|
||||||
|
<label>Keyring password</label>
|
||||||
|
<description>Keyring file password (set during export from ETS).</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
<parameter name="routerBackboneKey" type="text" groupName="knxsecure">
|
<parameter name="routerBackboneKey" type="text" groupName="knxsecure">
|
||||||
<context>password</context>
|
<context>password</context>
|
||||||
<label>Router backbone key</label>
|
<label>Router backbone key</label>
|
||||||
<description>Backbone key for secure router mode. 16 bytes
|
<description>Backbone key for secure router mode. 16 bytes
|
||||||
in hex notation. Can also be found
|
in hex notation. Can also be found
|
||||||
in ETS security report.</description>
|
in ETS security report.
|
||||||
|
Optional, can be read from
|
||||||
|
keyring file if it is configured.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="tunnelSourceAddress" type="text" groupName="knxsecure">
|
||||||
|
<label>Tunnel source address</label>
|
||||||
|
<description>Physical KNX address of tunnel in secure mode. Optional, only used in combination
|
||||||
|
with keyring file to
|
||||||
|
uniquely identify a tunnel. If given, openHAB will try to read user id, user
|
||||||
|
password and device authentication from
|
||||||
|
keyring.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="tunnelUserId" type="text" groupName="knxsecure">
|
<parameter name="tunnelUserId" type="text" groupName="knxsecure">
|
||||||
<label>Tunnel user id</label>
|
<label>Tunnel user id</label>
|
||||||
<description>Tunnel user id for secure tunnel mode.</description>
|
<description>Tunnel user id for secure tunnel mode. Optional, can be read from
|
||||||
|
keyring file if tunnelSourceAddr is
|
||||||
|
configured.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="tunnelUserPassword" type="text" groupName="knxsecure">
|
<parameter name="tunnelUserPassword" type="text" groupName="knxsecure">
|
||||||
<context>password</context>
|
<context>password</context>
|
||||||
<label>Tunnel user password</label>
|
<label>Tunnel user password</label>
|
||||||
<description>Tunnel user key for secure tunnel mode.</description>
|
<description>Tunnel user key for secure tunnel mode. Optional, can be read from
|
||||||
|
keyring file if tunnelSourceAddr is
|
||||||
|
configured.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="tunnelDeviceAuthentication" type="text" groupName="knxsecure">
|
<parameter name="tunnelDeviceAuthentication" type="text" groupName="knxsecure">
|
||||||
<context>password</context>
|
<context>password</context>
|
||||||
<label>Tunnel device authentication</label>
|
<label>Tunnel device authentication</label>
|
||||||
<description>Tunnel device authentication for secure tunnel mode.</description>
|
<description>Tunnel device authentication for secure tunnel mode. Optional, can be read from
|
||||||
|
keyring file if
|
||||||
|
tunnelSourceAddr is configured.</description>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
@ -8,6 +8,10 @@
|
|||||||
<label>KNX FT1.2 Interface</label>
|
<label>KNX FT1.2 Interface</label>
|
||||||
<description>This is a serial interface for accessing the KNX bus</description>
|
<description>This is a serial interface for accessing the KNX bus</description>
|
||||||
<config-description>
|
<config-description>
|
||||||
|
<parameter-group name="knxsecure">
|
||||||
|
<label>KNX secure</label>
|
||||||
|
<description>Settings for KNX secure. Optional. Requires KNX secure features to be active in KNX installation.</description>
|
||||||
|
</parameter-group>
|
||||||
<parameter name="serialPort" type="text" required="true">
|
<parameter name="serialPort" type="text" required="true">
|
||||||
<context>serial-port </context>
|
<context>serial-port </context>
|
||||||
<limitToOptions>false</limitToOptions>
|
<limitToOptions>false</limitToOptions>
|
||||||
@ -42,6 +46,19 @@
|
|||||||
<description>Seconds between connect retries when KNX link has been lost, 0 means never retry</description>
|
<description>Seconds between connect retries when KNX link has been lost, 0 means never retry</description>
|
||||||
<default>0</default>
|
<default>0</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="keyringFile" type="text" groupName="knxsecure">
|
||||||
|
<label>Keyring file</label>
|
||||||
|
<description>Keyring file exported from ETS and placed in openHAB config/misc folder, e.g.
|
||||||
|
knx.knxkeys. This file is
|
||||||
|
mandatory to decode secure group addresses.</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="keyringPassword" type="text" groupName="knxsecure">
|
||||||
|
<context>password</context>
|
||||||
|
<label>Keyring password</label>
|
||||||
|
<description>Keyring file password (set during export from ETS).</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public class DummyClient extends AbstractKNXClient {
|
|||||||
|
|
||||||
public DummyClient() {
|
public DummyClient() {
|
||||||
super(0, new ThingUID("dummy connection"), 0, 0, 0, null, new CommandExtensionData(Collections.emptyMap()),
|
super(0, new ThingUID("dummy connection"), 0, 0, 0, null, new CommandExtensionData(Collections.emptyMap()),
|
||||||
null);
|
null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,8 +15,13 @@ package org.openhab.binding.knx.internal.handler;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.core.OpenHAB;
|
||||||
import org.openhab.core.net.NetworkAddressService;
|
import org.openhab.core.net.NetworkAddressService;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
|
|
||||||
@ -48,28 +53,53 @@ class KNXBridgeBaseThingHandlerTest {
|
|||||||
IPBridgeThingHandler handler = new IPBridgeThingHandler(bridge, nas);
|
IPBridgeThingHandler handler = new IPBridgeThingHandler(bridge, nas);
|
||||||
|
|
||||||
// no config given
|
// no config given
|
||||||
assertFalse(handler.initializeSecurity("", "", "", ""));
|
assertFalse(handler.initializeSecurity("", "", "", "", "", "", ""));
|
||||||
|
|
||||||
// router password configured, length must be 16 bytes in hex notation
|
// router password configured, length must be 16 bytes in hex notation
|
||||||
assertTrue(handler.initializeSecurity("D947B12DDECAD528B1D5A88FD347F284", "", "", ""));
|
assertTrue(handler.initializeSecurity("", "", "D947B12DDECAD528B1D5A88FD347F284", "", "", "", ""));
|
||||||
assertTrue(handler.initializeSecurity("0xD947B12DDECAD528B1D5A88FD347F284", "", "", ""));
|
assertTrue(handler.initializeSecurity("", "", "0xD947B12DDECAD528B1D5A88FD347F284", "", "", "", ""));
|
||||||
assertThrows(KnxSecureException.class, () -> {
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
handler.initializeSecurity("wrongLength", "", "", "");
|
handler.initializeSecurity("", "", "wrongLength", "", "", "", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// tunnel configuration
|
// tunnel configuration
|
||||||
assertTrue(handler.initializeSecurity("", "da", "1", "pw"));
|
assertTrue(handler.initializeSecurity("", "", "", "da", "1", "pw", ""));
|
||||||
// cTunnelUser is restricted to a number >0
|
// cTunnelUser is restricted to a number >0
|
||||||
assertThrows(KnxSecureException.class, () -> {
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
handler.initializeSecurity("", "da", "0", "pw");
|
handler.initializeSecurity("", "", "", "da", "0", "pw", "");
|
||||||
});
|
});
|
||||||
assertThrows(KnxSecureException.class, () -> {
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
handler.initializeSecurity("", "da", "eins", "pw");
|
handler.initializeSecurity("", "", "", "da", "eins", "pw", "");
|
||||||
});
|
});
|
||||||
// at least one setting for tunnel is given, count as try to configure secure tunnel
|
// at least one setting for tunnel is given, count as try to configure secure tunnel
|
||||||
// plausibility is checked during initialize()
|
// plausibility is checked during initialize()
|
||||||
assertTrue(handler.initializeSecurity("", "da", "", ""));
|
assertTrue(handler.initializeSecurity("", "", "", "da", "", "", ""));
|
||||||
assertTrue(handler.initializeSecurity("", "", "1", ""));
|
assertTrue(handler.initializeSecurity("", "", "", "", "1", "", ""));
|
||||||
assertTrue(handler.initializeSecurity("", "", "", "pw"));
|
assertTrue(handler.initializeSecurity("", "", "", "", "", "pw", ""));
|
||||||
|
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
handler.initializeSecurity("nonExistingFile.xml", "", "", "", "", "", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
Properties pBackup = new Properties(System.getProperties());
|
||||||
|
try {
|
||||||
|
final File testFile = new File(
|
||||||
|
getClass().getClassLoader().getResource("misc" + File.separator + "openhab6.knxkeys").toURI());
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
Properties p = new Properties(System.getProperties());
|
||||||
|
p.put(OpenHAB.CONFIG_DIR_PROG_ARGUMENT, testFile.getParent().replaceAll("misc$", ""));
|
||||||
|
System.setProperties(p);
|
||||||
|
|
||||||
|
assertTrue(handler.initializeSecurity(testFile.getName().toString(), passwordString, "", "", "", "pw", ""));
|
||||||
|
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
assertTrue(handler.initializeSecurity(testFile.getName().toString(), "wrong", "", "", "", "pw", ""));
|
||||||
|
});
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
} finally {
|
||||||
|
// properties are not persistent, but change may interference with other tests -> restore
|
||||||
|
System.setProperties(pBackup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 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.knx.internal.security;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
|
||||||
|
|
||||||
|
import tuwien.auto.calimero.GroupAddress;
|
||||||
|
import tuwien.auto.calimero.IndividualAddress;
|
||||||
|
import tuwien.auto.calimero.knxnetip.SecureConnection;
|
||||||
|
import tuwien.auto.calimero.secure.Keyring;
|
||||||
|
import tuwien.auto.calimero.secure.KnxSecureException;
|
||||||
|
import tuwien.auto.calimero.secure.Security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Holger Friedrich - initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class KNXSecurityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalimeroKeyring() {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
final String testFile = getClass().getClassLoader().getResource("misc" + File.separator + "openhab6.knxkeys")
|
||||||
|
.toString();
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
final char[] password = passwordString.toCharArray();
|
||||||
|
assertNotEquals("", testFile);
|
||||||
|
|
||||||
|
Keyring keys = Keyring.load(testFile);
|
||||||
|
|
||||||
|
// System.out.println(keys.devices().toString());
|
||||||
|
// System.out.println(keys.groups().toString());
|
||||||
|
// System.out.println(keys.interfaces().toString());
|
||||||
|
|
||||||
|
GroupAddress ga = new GroupAddress(8, 0, 0);
|
||||||
|
byte[] key800enc = keys.groups().get(ga);
|
||||||
|
assertNotNull(key800enc);
|
||||||
|
if (key800enc != null) {
|
||||||
|
assertNotEquals(0, key800enc.length);
|
||||||
|
}
|
||||||
|
byte[] key800dec = keys.decryptKey(key800enc, password);
|
||||||
|
assertEquals(16, key800dec.length);
|
||||||
|
|
||||||
|
IndividualAddress nopa = new IndividualAddress(2, 8, 20);
|
||||||
|
Keyring.Device nodev = keys.devices().get(nopa);
|
||||||
|
assertNull(nodev);
|
||||||
|
|
||||||
|
IndividualAddress pa = new IndividualAddress(1, 1, 42);
|
||||||
|
Keyring.Device dev = keys.devices().get(pa);
|
||||||
|
assertNotNull(dev);
|
||||||
|
// cannot check this for dummy test file, needs real device to be included
|
||||||
|
// assertNotEquals(0, dev.sequenceNumber());
|
||||||
|
|
||||||
|
Security openhabSecurity = Security.newSecurity();
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
Map<GroupAddress, byte[]> groupKeys = openhabSecurity.groupKeys();
|
||||||
|
assertEquals(3, groupKeys.size());
|
||||||
|
groupKeys.remove(ga);
|
||||||
|
assertEquals(2, groupKeys.size());
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
Map<GroupAddress, byte[]> groupKeys2 = openhabSecurity.groupKeys();
|
||||||
|
assertEquals(3, groupKeys2.size());
|
||||||
|
assertEquals(3, groupKeys.size());
|
||||||
|
ga = new GroupAddress(1, 0, 0);
|
||||||
|
groupKeys.put(ga, new byte[1]);
|
||||||
|
assertEquals(4, groupKeys2.size());
|
||||||
|
assertEquals(4, groupKeys.size());
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
assertEquals(4, groupKeys2.size());
|
||||||
|
assertEquals(4, groupKeys.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tunnel settings, this file does not contain any key
|
||||||
|
@Test
|
||||||
|
public void testSecurityHelperEmpty() {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
final String testFile = getClass().getClassLoader()
|
||||||
|
.getResource("misc" + File.separator + "openhab6-minimal-ipif.knxkeys").toString();
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
final char[] password = passwordString.toCharArray();
|
||||||
|
assertNotEquals("", testFile);
|
||||||
|
|
||||||
|
Keyring keys = Keyring.load(testFile);
|
||||||
|
Security openhabSecurity = Security.newSecurity();
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
|
||||||
|
.isEmpty());
|
||||||
|
|
||||||
|
// now check tunnel (expected to fail, not included)
|
||||||
|
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler
|
||||||
|
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
|
||||||
|
.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tunnel settings, this file does not contain any key
|
||||||
|
@Test
|
||||||
|
public void testSecurityHelperRouterKey() {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
final String testFile = getClass().getClassLoader()
|
||||||
|
.getResource("misc" + File.separator + "openhab6-minimal-sipr.knxkeys").toString();
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
final char[] password = passwordString.toCharArray();
|
||||||
|
assertNotEquals("", testFile);
|
||||||
|
|
||||||
|
Keyring keys = Keyring.load(testFile);
|
||||||
|
Security openhabSecurity = Security.newSecurity();
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
|
||||||
|
.isPresent());
|
||||||
|
|
||||||
|
// now check tunnel (expected to fail, not included)
|
||||||
|
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler
|
||||||
|
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
|
||||||
|
.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tunnel settings, this file contains a secure interface, but no router password
|
||||||
|
@Test
|
||||||
|
public void testSecurityHelperTunnelKey() {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
final String testFile = getClass().getClassLoader()
|
||||||
|
.getResource("misc" + File.separator + "openhab6-minimal-sipif.knxkeys").toString();
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
final char[] password = passwordString.toCharArray();
|
||||||
|
assertNotEquals("", testFile);
|
||||||
|
|
||||||
|
Keyring keys = Keyring.load(testFile);
|
||||||
|
Security openhabSecurity = Security.newSecurity();
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
|
||||||
|
.isEmpty());
|
||||||
|
|
||||||
|
// now check tunnel
|
||||||
|
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler
|
||||||
|
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
|
||||||
|
.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityHelpers() {
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
final String testFile = getClass().getClassLoader().getResource("misc" + File.separator + "openhab6.knxkeys")
|
||||||
|
.toString();
|
||||||
|
final String passwordString = "habopen";
|
||||||
|
|
||||||
|
final char[] password = passwordString.toCharArray();
|
||||||
|
assertNotEquals("", testFile);
|
||||||
|
|
||||||
|
Keyring keys = Keyring.load(testFile);
|
||||||
|
// this is done during load() in v2.5, but check it once....
|
||||||
|
assertTrue(keys.verifySignature(password));
|
||||||
|
|
||||||
|
Security openhabSecurity = Security.newSecurity();
|
||||||
|
openhabSecurity.useKeyring(keys, password);
|
||||||
|
|
||||||
|
// now check router settings:
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
|
||||||
|
});
|
||||||
|
String bbKeyHex = "D947B12DDECAD528B1D5A88FD347F284";
|
||||||
|
byte[] bbKeyParsedLower = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex.toLowerCase());
|
||||||
|
byte[] bbKeyParsedUpper = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex);
|
||||||
|
Optional<byte[]> bbKeyRead = KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys),
|
||||||
|
passwordString);
|
||||||
|
assertEquals(16, bbKeyParsedUpper.length);
|
||||||
|
assertArrayEquals(bbKeyParsedUpper, bbKeyParsedLower);
|
||||||
|
assertTrue(bbKeyRead.isPresent());
|
||||||
|
assertArrayEquals(bbKeyParsedUpper, bbKeyRead.get());
|
||||||
|
// System.out.print("Backbone key: \"");
|
||||||
|
// for (byte i : backboneGroupKey)
|
||||||
|
// System.out.print(String.format("%02X", i));
|
||||||
|
// System.out.println("\"");
|
||||||
|
|
||||||
|
// now check tunnel settings:
|
||||||
|
IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
|
||||||
|
IndividualAddress noSecureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
|
||||||
|
assertThrows(KnxSecureException.class, () -> {
|
||||||
|
KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
});
|
||||||
|
assertTrue(KNXBridgeBaseThingHandler
|
||||||
|
.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, noSecureTunnelSourceAddr)
|
||||||
|
.isEmpty());
|
||||||
|
|
||||||
|
var config = KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString,
|
||||||
|
secureTunnelSourceAddr);
|
||||||
|
assertTrue(config.isPresent());
|
||||||
|
assertEquals(2, config.get().user);
|
||||||
|
|
||||||
|
assertArrayEquals(SecureConnection.hashUserPassword("mytunnel1".toCharArray()), config.get().userKey);
|
||||||
|
assertArrayEquals(SecureConnection.hashDeviceAuthenticationPassword("myauthcode".toCharArray()),
|
||||||
|
config.get().devKey);
|
||||||
|
|
||||||
|
// secure group addresses should contain at least one address marked as "surrogate"
|
||||||
|
final String secureAddresses = KNXBridgeBaseThingHandler.secHelperGetSecureGroupAddresses(openhabSecurity);
|
||||||
|
assertTrue(secureAddresses.contains("(S)"));
|
||||||
|
assertTrue(secureAddresses.contains("8/4/0"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Keyring Project="openhab6-minimal-ipif" CreatedBy="6.0.2" Created="2022-03-02T17:40:53" Signature="ZNKRogTN6X8uSviKxnb8FQ==" xmlns="http://knx.org/xml/keyring/1">
|
||||||
|
<Backbone MulticastAddress="224.0.23.12" />
|
||||||
|
<Devices>
|
||||||
|
<Device IndividualAddress="1.1.3" ToolKey="4TAbUJiKVy8Fb51bO8fKJw==" />
|
||||||
|
</Devices>
|
||||||
|
</Keyring>
|
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Keyring Project="openhab6-minimal-sipif" CreatedBy="6.0.2" Created="2022-03-02T20:47:32" Signature="ITyO/PaDzSA2Lb6J2FyXgA==" xmlns="http://knx.org/xml/keyring/1">
|
||||||
|
<Backbone MulticastAddress="224.0.23.12" />
|
||||||
|
<Interface IndividualAddress="1.1.2" Type="Tunneling" Host="1.1.1" UserID="2" Password="7vI8IYOA6TG8aRDiBMb3KIfoHLPKxp4jG3+sO7uwuw4=" Authentication="AVQ2dMT+52vspyStmZ9+8g6Oskjt70o4wnqm0tsPkI0=" />
|
||||||
|
<Interface IndividualAddress="1.1.4" Type="Tunneling" Host="1.1.1" UserID="3" Password="LsurQzd7MV6bWI+KxlpFGf17gW3lagaeRGenm9riJG4=" Authentication="qk597AJwqZ/Qe0Erc2hCJCG/6mv1hGQKRINfN+7bZPU=" />
|
||||||
|
<Interface IndividualAddress="1.1.5" Type="Tunneling" Host="1.1.1" UserID="4" Password="DHdsQEFqRWlAu0lSCOyz9HoKr2hbsMCOk1VpE6jESsA=" Authentication="KEwPKUS/0fVnlPcJAA3aYzbRCKE/QcXVdeCnZJed+P0=" />
|
||||||
|
<Interface IndividualAddress="1.1.6" Type="Tunneling" Host="1.1.1" UserID="5" Password="NRQekIgTZDRXs9HNHCbaPyUeL9H02ET9oCzwYxe+HA0=" Authentication="mgSxsc6tGJfwtoSZifdghTcgqUI4XwV5QITjH0R7cNA=" />
|
||||||
|
<Interface IndividualAddress="1.1.7" Type="Tunneling" Host="1.1.1" UserID="6" Password="cvP1SzE4PWkUNv+evNCLdPUMZcZJnJxzp1bDwFT2emA=" Authentication="KyPx37deTYrh3ObQYRwmALdDXeBG4u9TT8VTEOBhTqE=" />
|
||||||
|
<Devices>
|
||||||
|
<Device IndividualAddress="1.1.1" ToolKey="MH9tidHty0OwjaWKIGH5Zg==" ManagementPassword="hqygOoW7gJyKzRwoWzZbVxKHa1TMRm5MPRmWdhMROtQ=" Authentication="2GBT98qKw96qNS573gXrExG5NIL3QMvba/h89thsaLI=" />
|
||||||
|
<Device IndividualAddress="1.1.3" ToolKey="RMumSDwjPH8quCkcp+EJxA==" />
|
||||||
|
</Devices>
|
||||||
|
</Keyring>
|
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Keyring Project="openhab6-minimal-sipr" CreatedBy="6.0.2" Created="2022-03-02T20:21:31" Signature="Mv4HUIhAEdN0j3eHK8kvhw==" xmlns="http://knx.org/xml/keyring/1">
|
||||||
|
<Backbone MulticastAddress="224.0.23.12" Latency="2000" Key="WTq688uTJdyF+mDUyV7h9Q==" />
|
||||||
|
<Interface IndividualAddress="1.1.1" Type="Tunneling" Host="1.1.0" UserID="2" Password="Rr4I9shKloT2z4H0SDrCxvLckph2XRqQCUaBAbYnDRY=" Authentication="fTg6DhcrcYePIl/hbtTd+Munz5z1Gjf5uQIaOybsFsE=" />
|
||||||
|
<Interface IndividualAddress="1.1.2" Type="Tunneling" Host="1.1.0" UserID="3" Password="BVGPxxeXIG/9vTUbKjCcOHcBVzPDwtGtXxIV9DK80qU=" Authentication="VFw6piwppnbwsn67KnzxmoUT6cG95N8k4tor+mpBlJc=" />
|
||||||
|
<Interface IndividualAddress="1.1.4" Type="Tunneling" Host="1.1.0" UserID="4" Password="tEYKV5oCCUca6Brj4kqJ5ykWTzuXRZGVp2eXniloIz8=" Authentication="g27EgH8nAtmIDA898c1UJT3P9EZodBZd+AzsBrErRpc=" />
|
||||||
|
<Interface IndividualAddress="1.1.5" Type="Tunneling" Host="1.1.0" UserID="5" Password="AoP9ObiwOtatsqyaLkNNQVd5zTk5ZPjwXDF2YhgnQxY=" Authentication="HMZs3QDHclVlzZ5JQln6ZutxPaCLwkpV/8419SHJHM4=" />
|
||||||
|
<Interface IndividualAddress="1.1.6" Type="Tunneling" Host="1.1.0" UserID="6" Password="ImqRByvtwVOKePgyK5Gscn0a6eKPG8PEn6PZigJuUK8=" Authentication="dHvCcwhP8XrUuey7Hg6eMzRV9GBLUm/SUK4ymi1ZYKs=" />
|
||||||
|
<Devices>
|
||||||
|
<Device IndividualAddress="1.1.0" ToolKey="DPgL63v9oOT2lPTrl0znNw==" ManagementPassword="nH9hF0hEeLJB5lCd+sx+P0BcyKlfkrEZqVyBZcXYXb8=" Authentication="Coqlcx76csPb+36QQS1GvtCxWgGJTwrllzIGHW22sTQ=" />
|
||||||
|
<Device IndividualAddress="1.1.3" ToolKey="18/4WzFOCyNrlob2B4ey3g==" />
|
||||||
|
</Devices>
|
||||||
|
</Keyring>
|
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Keyring Project="openHAB test" CreatedBy="6.0.2" Created="2022-02-16T20:14:21" Signature="7DCcy3pjn9UL/62Cd4JRNw==" xmlns="http://knx.org/xml/keyring/1">
|
||||||
|
<Backbone MulticastAddress="224.0.23.12" Latency="2000" Key="jqQQPxOaT9yQmGntjzmmcA==" />
|
||||||
|
<Interface IndividualAddress="1.1.2" Type="Tunneling" Host="1.1.1" UserID="2" Password="I/mTxaNvfOBksLSm+DpvJEOByNJ2CdFstHUApyc7vYg=" Authentication="pywl6qpSlAHikvSZ95qa8Tpz1ybtBPbLsNWr11Y+rCc=" />
|
||||||
|
<Interface IndividualAddress="1.1.3" Type="Tunneling" Host="1.1.1" UserID="3" Password="VS5jc4CYCkn3fLjXOfucRX/IyWV5vFWCVOf+o7Z2WkQ=" Authentication="Is2o/DJtci597gNs8kk3xa2PnvR73CREFS4CSsxvs4E=" />
|
||||||
|
<Interface IndividualAddress="1.1.4" Type="Tunneling" Host="1.1.1" UserID="4" Password="TEPR9kJnMBfR4ZjknYk/LVpFa9NecnMS9eVYzQ9nDtU=" Authentication="HCSeleEDD2ZUIP9Necvt8bWb7N1Lmb/rmZZ2MWKGxto=" />
|
||||||
|
<Interface IndividualAddress="1.1.5" Type="Tunneling" Host="1.1.1" UserID="5" Password="ik4IhpmfEca7E1fs1tAOJ+dw2t3VIAmNtwbcUcjV6l8=" Authentication="jZRam0aOS8pFF5jNGTWsp8dlAYSBvtFtby3zLXdGqJ4=" />
|
||||||
|
<Interface IndividualAddress="1.1.6" Type="Tunneling" Host="1.1.1" UserID="6" Password="ZLaimyAb6WK4W24vKWx+9/p7F7PsmxXlu53i1xgl/3c=" Authentication="mTAGaAAv310sfwb8150/kG+qEl4SNJEpPexGly/gEj8=" />
|
||||||
|
<Interface IndividualAddress="1.1.7" Type="Tunneling" Host="1.1.1" UserID="7" Password="iShH6XxU8ynPd2bxk9e07lOPHqfvk+pyoNhirjwmseM=" Authentication="pPg4npj9AkGr7e9i4zTTHgdIjq8pPNB/t/ThVofm1I0=" />
|
||||||
|
<Interface IndividualAddress="1.1.8" Type="Tunneling" Host="1.1.1" UserID="8" Password="Qsoe9Uql3HRm0Acm3g1MNBh4aWI9e6Z7+Bdaxr4HgBg=" Authentication="+0DEjJHPWZBXNcOz7kvK+hcYwrlj5Ep/cb/EdQB5s1o=" />
|
||||||
|
<Interface IndividualAddress="1.1.9" Type="Tunneling" Host="1.1.1" UserID="9" Password="u35VfR3rgsZo9iMbhudqMd79a1ymcXJA1nFqUqUIrOg=" Authentication="2516mjVwl1+e0uF2gMYUsv9+OkMqpnmMlDUJJtLqY54=" />
|
||||||
|
<Interface IndividualAddress="1.1.10" Type="Tunneling" Host="1.1.0" UserID="2" Password="mVhL48YOaihWHFtXa+wmzI9JckF3ABbdvlbZCJGFiSo=" Authentication="xl7KByFbKpHIVX1HwP6coo8tN9Kn7v7FODYQ9YrYvLQ=" />
|
||||||
|
<Interface IndividualAddress="1.1.11" Type="Tunneling" Host="1.1.0" UserID="3" Password="aVxzF1AAwa0Fe6VCkPIpBnYlQQNVUAESgEev0fTNez8=" Authentication="R82NqLPaZsBqseWcWWYkpb+lojfwTvXnjJJIrgD/GgM=" />
|
||||||
|
<Interface IndividualAddress="1.1.12" Type="Tunneling" Host="1.1.0" UserID="4" Password="ZfpsaaR1Z1f3xOrdl3lMB3ECEaJHGQ67hYYJtS8kJvk=" Authentication="7W0oAfAWRM4AvCgDqJaAK2D+syyaz62Z6L9JyCc5Pe8=" />
|
||||||
|
<Interface IndividualAddress="1.1.13" Type="Tunneling" Host="1.1.0" UserID="5" Password="RFHe5Rf6dOnBXFc6I5Ib/gyPHdnVrqVnA0miEg8Hf1U=" Authentication="dy10MHltqGJI5HVVPVzKFVz21NJBv+d4riTrsjgkgfk=" />
|
||||||
|
<Interface IndividualAddress="1.1.14" Type="Tunneling" Host="1.1.0" UserID="6" Password="Es9do2yHUNMKNqqqXsqPhWQHmqzpABd0bllosyHBl4M=" Authentication="FPvvbkyXtztAy/vg4p+yb/duUiR2ynD9dDbfD3ceFWI=" />
|
||||||
|
<Interface IndividualAddress="1.1.15" Type="Tunneling" Host="1.1.0" UserID="7" Password="hVHfIe+089Gsl7ONAcFnVGWwtb8H7PEgts+M5mI8ssw=" Authentication="I/NINRXOpKypAHMn41GQKtZL/YkehxGnUnPP2Mlnv4c=" />
|
||||||
|
<Interface IndividualAddress="1.1.16" Type="Tunneling" Host="1.1.0" UserID="8" Password="hIEQVBFFg7N5d4pGDUZwZljIH3k4OiCmc3cNHmwP7Ko=" Authentication="WM31k8coJR+fe/1NC15iFinZUWhrg8wSqPhnmQPVF/c=" />
|
||||||
|
<Interface IndividualAddress="1.1.17" Type="Tunneling" Host="1.1.0" UserID="9" Password="LYzsUD6qXY5fJYMyglcoYF57ImWQ9R343DcVqIpGCos=" Authentication="MJQmHxu2dS9JIs4K8vUhvcbmt1flKwPJEk/53EzRENI=" />
|
||||||
|
<Interface IndividualAddress="1.0.128" Type="Backbone">
|
||||||
|
<Group Address="16384" Senders="1.1.42" />
|
||||||
|
<Group Address="17408" Senders="1.1.42" />
|
||||||
|
</Interface>
|
||||||
|
<GroupAddresses>
|
||||||
|
<Group Address="16384" Key="aet9NVDLVrBozo0fjAZsfw==" />
|
||||||
|
<Group Address="17408" Key="JNPEYPcwjqvNVMGEqfLYFg==" />
|
||||||
|
<Group Address="17409" Key="hSZjJizZHAaEJDCGWBG02g==" />
|
||||||
|
</GroupAddresses>
|
||||||
|
<Devices>
|
||||||
|
<Device IndividualAddress="1.0.128" ToolKey="y6tTTgkjYVHzadcO3DsaYA==" ManagementPassword="eh9/MLvXQp5YI+rHKRt+eg9X4oHCiHcsx2sZllE1UAo=" Authentication="bSNdyKdQK9K36wnSF5p+FaoOnFu7vtppdYE+wgoMAHw=" />
|
||||||
|
<Device IndividualAddress="1.1.0" ToolKey="YQn06Qzc5YKOtzLH4qAasg==" ManagementPassword="hOvb9C7GWTx0kyc043QJouMCC1xKV5sq0zyf6qIri8c=" Authentication="fIvCX/e7mlRGBZ6g8tforCijY3+OZYXmAwC1DiDcBkQ=" />
|
||||||
|
<Device IndividualAddress="1.1.1" ToolKey="/dXTk0oJstzyIfOVyAAZOg==" ManagementPassword="DwfYrNbvxKev6Lk+T+vJ5Gw9TW3mfsvmvK6Eh6Slrrs=" Authentication="/Ioy/hSd4D9GSRgwrt8zP4bV8tIeews4650OKxl4AEg=" />
|
||||||
|
<Device IndividualAddress="1.1.42" ToolKey="c+Qmg96GNf8SB3idRFei6A==" />
|
||||||
|
</Devices>
|
||||||
|
</Keyring>
|
Loading…
Reference in New Issue
Block a user