2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.security;
15 import static org.junit.jupiter.api.Assertions.*;
19 import java.util.Optional;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.junit.jupiter.api.Test;
23 import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
25 import tuwien.auto.calimero.GroupAddress;
26 import tuwien.auto.calimero.IndividualAddress;
27 import tuwien.auto.calimero.knxnetip.SecureConnection;
28 import tuwien.auto.calimero.secure.Keyring;
29 import tuwien.auto.calimero.secure.KnxSecureException;
30 import tuwien.auto.calimero.secure.Security;
34 * @author Holger Friedrich - initial contribution
38 public class KNXSecurityTest {
41 public void testCalimeroKeyring() {
42 @SuppressWarnings("null")
43 final String testFile = getClass().getClassLoader().getResource("misc" + File.separator + "openhab6.knxkeys")
45 final String passwordString = "habopen";
47 final char[] password = passwordString.toCharArray();
48 assertNotEquals("", testFile);
50 Keyring keys = Keyring.load(testFile);
52 // System.out.println(keys.devices().toString());
53 // System.out.println(keys.groups().toString());
54 // System.out.println(keys.interfaces().toString());
56 GroupAddress ga = new GroupAddress(8, 0, 0);
57 byte[] key800enc = keys.groups().get(ga);
58 assertNotNull(key800enc);
59 if (key800enc != null) {
60 assertNotEquals(0, key800enc.length);
62 byte[] key800dec = keys.decryptKey(key800enc, password);
63 assertEquals(16, key800dec.length);
65 IndividualAddress nopa = new IndividualAddress(2, 8, 20);
66 Keyring.Device nodev = keys.devices().get(nopa);
69 IndividualAddress pa = new IndividualAddress(1, 1, 42);
70 Keyring.Device dev = keys.devices().get(pa);
72 // cannot check this for dummy test file, needs real device to be included
73 // assertNotEquals(0, dev.sequenceNumber());
75 Security openhabSecurity = Security.newSecurity();
76 openhabSecurity.useKeyring(keys, password);
77 Map<GroupAddress, byte[]> groupKeys = openhabSecurity.groupKeys();
78 assertEquals(3, groupKeys.size());
80 assertEquals(2, groupKeys.size());
81 openhabSecurity.useKeyring(keys, password);
82 Map<GroupAddress, byte[]> groupKeys2 = openhabSecurity.groupKeys();
83 assertEquals(3, groupKeys2.size());
84 assertEquals(3, groupKeys.size());
85 ga = new GroupAddress(1, 0, 0);
86 groupKeys.put(ga, new byte[1]);
87 assertEquals(4, groupKeys2.size());
88 assertEquals(4, groupKeys.size());
89 openhabSecurity.useKeyring(keys, password);
90 assertEquals(4, groupKeys2.size());
91 assertEquals(4, groupKeys.size());
94 // check tunnel settings, this file does not contain any key
96 public void testSecurityHelperEmpty() {
97 @SuppressWarnings("null")
98 final String testFile = getClass().getClassLoader()
99 .getResource("misc" + File.separator + "openhab6-minimal-ipif.knxkeys").toString();
100 final String passwordString = "habopen";
102 final char[] password = passwordString.toCharArray();
103 assertNotEquals("", testFile);
105 Keyring keys = Keyring.load(testFile);
106 Security openhabSecurity = Security.newSecurity();
107 openhabSecurity.useKeyring(keys, password);
109 assertThrows(KnxSecureException.class, () -> {
110 KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
112 assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
115 // now check tunnel (expected to fail, not included)
116 IndividualAddress secureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
117 assertThrows(KnxSecureException.class, () -> {
118 KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
119 secureTunnelSourceAddr);
121 assertTrue(KNXBridgeBaseThingHandler
122 .secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
126 // check tunnel settings, this file does not contain any key
128 public void testSecurityHelperRouterKey() {
129 @SuppressWarnings("null")
130 final String testFile = getClass().getClassLoader()
131 .getResource("misc" + File.separator + "openhab6-minimal-sipr.knxkeys").toString();
132 final String passwordString = "habopen";
134 final char[] password = passwordString.toCharArray();
135 assertNotEquals("", testFile);
137 Keyring keys = Keyring.load(testFile);
138 Security openhabSecurity = Security.newSecurity();
139 openhabSecurity.useKeyring(keys, password);
141 assertThrows(KnxSecureException.class, () -> {
142 KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
144 assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
147 // now check tunnel (expected to fail, not included)
148 IndividualAddress secureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
149 assertThrows(KnxSecureException.class, () -> {
150 KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
151 secureTunnelSourceAddr);
153 assertTrue(KNXBridgeBaseThingHandler
154 .secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
158 // check tunnel settings, this file contains a secure interface, but no router password
160 public void testSecurityHelperTunnelKey() {
161 @SuppressWarnings("null")
162 final String testFile = getClass().getClassLoader()
163 .getResource("misc" + File.separator + "openhab6-minimal-sipif.knxkeys").toString();
164 final String passwordString = "habopen";
166 final char[] password = passwordString.toCharArray();
167 assertNotEquals("", testFile);
169 Keyring keys = Keyring.load(testFile);
170 Security openhabSecurity = Security.newSecurity();
171 openhabSecurity.useKeyring(keys, password);
173 assertThrows(KnxSecureException.class, () -> {
174 KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
176 assertTrue(KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys), passwordString)
180 IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
181 assertThrows(KnxSecureException.class, () -> {
182 KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
183 secureTunnelSourceAddr);
185 assertTrue(KNXBridgeBaseThingHandler
186 .secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr)
191 public void testSecurityHelpers() {
192 @SuppressWarnings("null")
193 final String testFile = getClass().getClassLoader().getResource("misc" + File.separator + "openhab6.knxkeys")
195 final String passwordString = "habopen";
197 final char[] password = passwordString.toCharArray();
198 assertNotEquals("", testFile);
200 Keyring keys = Keyring.load(testFile);
201 // this is done during load() in v2.5, but check it once....
202 assertTrue(keys.verifySignature(password));
204 Security openhabSecurity = Security.newSecurity();
205 openhabSecurity.useKeyring(keys, password);
207 // now check router settings:
208 assertThrows(KnxSecureException.class, () -> {
209 KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString);
211 String bbKeyHex = "D947B12DDECAD528B1D5A88FD347F284";
212 byte[] bbKeyParsedLower = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex.toLowerCase());
213 byte[] bbKeyParsedUpper = KNXBridgeBaseThingHandler.secHelperParseBackboneKey(bbKeyHex);
214 Optional<byte[]> bbKeyRead = KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.ofNullable(keys),
216 assertEquals(16, bbKeyParsedUpper.length);
217 assertArrayEquals(bbKeyParsedUpper, bbKeyParsedLower);
218 assertTrue(bbKeyRead.isPresent());
219 assertArrayEquals(bbKeyParsedUpper, bbKeyRead.get());
220 // System.out.print("Backbone key: \"");
221 // for (byte i : backboneGroupKey)
222 // System.out.print(String.format("%02X", i));
223 // System.out.println("\"");
225 // now check tunnel settings:
226 IndividualAddress secureTunnelSourceAddr = new IndividualAddress(1, 1, 2);
227 IndividualAddress noSecureTunnelSourceAddr = new IndividualAddress(2, 8, 20);
228 assertThrows(KnxSecureException.class, () -> {
229 KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.empty(), passwordString,
230 secureTunnelSourceAddr);
232 assertTrue(KNXBridgeBaseThingHandler
233 .secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, noSecureTunnelSourceAddr)
236 var config = KNXBridgeBaseThingHandler.secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString,
237 secureTunnelSourceAddr);
238 assertTrue(config.isPresent());
239 assertEquals(2, config.get().user);
241 assertArrayEquals(SecureConnection.hashUserPassword("mytunnel1".toCharArray()), config.get().userKey);
242 assertArrayEquals(SecureConnection.hashDeviceAuthenticationPassword("myauthcode".toCharArray()),
243 config.get().devKey);
245 // secure group addresses should contain at least one address marked as "surrogate"
246 final String secureAddresses = KNXBridgeBaseThingHandler.secHelperGetSecureGroupAddresses(openhabSecurity);
247 assertTrue(secureAddresses.contains("(S)"));
248 assertTrue(secureAddresses.contains("8/4/0"));