Skip to content

Commit 954358e

Browse files
[knx] Add support for using hardware TPM modules
Add TpmInterface, a class built on top of Tss.Java. This lib is to be included in a special way due to inconsistencies in package creation which makes it incompatible to OSGI. Signed-off-by: Holger Friedrich <mail@holger-friedrich.de>
1 parent e009276 commit 954358e

15 files changed

Lines changed: 744 additions & 38 deletions

File tree

bundles/org.openhab.binding.knx/NOTICE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ calimero
1818
* License: GPL v2 License with CLASSPATH EXCEPTION
1919
* Project: http://calimero-project.github.io
2020
* Source: https://github.com/calimero-project
21+
22+
TSS.MSR / TSS.java
23+
* License: The MIT License (MIT)
24+
* Project: https://github.com/microsoft/TSS.MSR
25+
* Source: https://github.com/microsoft/TSS.MSR/tree/main/TSS.Java

bundles/org.openhab.binding.knx/pom.xml

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<name>openHAB Add-ons :: Bundles :: KNX Binding</name>
1616

1717
<properties>
18-
<bnd.importpackage>javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional"</bnd.importpackage>
18+
<bnd.importpackage>javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional",org.bouncycastle.*;resolution:="optional"</bnd.importpackage>
1919
</properties>
2020

2121
<dependencies>
@@ -56,11 +56,69 @@
5656
<classifier>runtime</classifier>
5757
<scope>test</scope>
5858
</dependency>
59+
<dependency>
60+
<groupId>com.microsoft.azure</groupId>
61+
<artifactId>TSS.Java</artifactId>
62+
<version>1.0.0</version>
63+
<scope>provided</scope>
64+
</dependency>
5965
</dependencies>
6066

6167
<build>
6268
<pluginManagement>
6369
<plugins>
70+
<plugin>
71+
<groupId>org.apache.maven.plugins</groupId>
72+
<artifactId>maven-dependency-plugin</artifactId>
73+
<executions>
74+
<execution>
75+
<id>unpack-tss</id>
76+
<goals>
77+
<goal>unpack</goal>
78+
</goals>
79+
<phase>generate-sources</phase>
80+
<configuration>
81+
<echo>"Unpacking TSS.Java"</echo>
82+
<artifactItems>
83+
<artifactItem>
84+
<groupId>com.microsoft.azure</groupId>
85+
<artifactId>TSS.Java</artifactId>
86+
<version>1.0.0</version>
87+
<outputDirectory>${project.build.directory}/classes</outputDirectory>
88+
</artifactItem>
89+
</artifactItems>
90+
</configuration>
91+
</execution>
92+
</executions>
93+
</plugin>
94+
95+
<plugin>
96+
<groupId>org.apache.maven.plugins</groupId>
97+
<artifactId>maven-antrun-plugin</artifactId>
98+
<executions>
99+
100+
<execution>
101+
<!-- Remove classes from the root package and re jar -->
102+
<id>fix-tss</id>
103+
<goals>
104+
<goal>run</goal>
105+
</goals>
106+
<phase>process-sources</phase>
107+
<configuration>
108+
<target>
109+
<echo>"Fixing TSS.Java for OSGI"</echo>
110+
<delete>
111+
<fileset dir="${project.build.directory}/classes" includes="*"/>
112+
</delete>
113+
<delete failonerror="false" includeemptydirs="true">
114+
<fileset dir="${project.build.directory}/classes/samples"/>
115+
</delete>
116+
</target>
117+
</configuration>
118+
</execution>
119+
</executions>
120+
</plugin>
121+
64122
<plugin>
65123
<groupId>biz.aQute.bnd</groupId>
66124
<artifactId>bnd-maven-plugin</artifactId>
@@ -80,6 +138,11 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.
80138
</plugins>
81139
</pluginManagement>
82140
<plugins>
141+
<plugin>
142+
<groupId>org.apache.maven.plugins</groupId>
143+
<artifactId>maven-antrun-plugin</artifactId>
144+
<version>3.1.0</version>
145+
</plugin>
83146
<plugin>
84147
<groupId>org.apache.maven.plugins</groupId>
85148
<artifactId>maven-surefire-plugin</artifactId>
@@ -95,6 +158,7 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.
95158
<version>0.8.14</version>
96159
<configuration>
97160
<excludes>
161+
<exclude>tss/**/*</exclude>
98162
<exclude>tuwien/auto/calimero/**/*</exclude>
99163
<exclude>io/calimero/**/*</exclude>
100164
</excludes>

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
public class KNXBindingConstants {
3434

3535
public static final String BINDING_ID = "knx";
36+
public static final String ENCRYPTED_PASSWORD_SERIALIZATION_PREFIX = "TpM2-pRoTeCteD-";
3637

3738
// Global config
3839
public static final String CONFIG_DISABLE_UOM = "disableUoM";

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,32 @@ public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int respons
157157
}
158158

159159
public void initialize() {
160+
/*
161+
* TpmInterface.SecuredPassword passKey = new TpmInterface.SecuredPassword("", "", "");
162+
* try {
163+
* TpmInterface tpmIf = new TpmInterface();
164+
* String tpmRev = tpmIf.getTpmVersion();
165+
* String tpmModel = "unknown";
166+
* try {
167+
* tpmModel = tpmIf.getTpmModel();
168+
* } catch (KNXException ignored) {
169+
* }
170+
* logger.info("TPM rev. {} detected, based on {}", tpmRev, tpmModel);
171+
*
172+
* passKey = tpmIf.encryptSecret("habOpen");
173+
* logger.warn("{}", passKey);
174+
* } catch (KNXException e) {
175+
* logger.warn("TPM exception", e);
176+
* }
177+
* try {
178+
* TpmInterface tpmIf = new TpmInterface();
179+
* String pass = tpmIf.decryptSecret(passKey);
180+
* logger.warn("TPM decoded: {}", pass);
181+
* } catch (KNXException e) {
182+
* logger.warn("TPM exception", e);
183+
* }
184+
*/
185+
160186
connect();
161187
}
162188

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
package org.openhab.binding.knx.internal.config;
1414

1515
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.eclipse.jdt.annotation.Nullable;
17+
import org.openhab.binding.knx.internal.KNXBindingConstants;
18+
import org.openhab.binding.knx.internal.tpm.TpmInterface;
19+
import org.openhab.core.auth.SecurityException;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
1622

1723
/**
1824
* {@link org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler} configuration
@@ -22,6 +28,9 @@
2228
*/
2329
@NonNullByDefault
2430
public class BridgeConfiguration {
31+
private final Logger logger = LoggerFactory.getLogger(BridgeConfiguration.class);
32+
@Nullable
33+
TpmInterface tpmIf = null;
2534
private int autoReconnectPeriod = 0;
2635
private int readingPause = 0;
2736
private int readRetriesLimit = 0;
@@ -54,6 +63,20 @@ public String getKeyringFile() {
5463
}
5564

5665
public String getKeyringPassword() {
57-
return keyringPassword;
66+
return decrypt(keyringPassword);
67+
}
68+
69+
protected String decrypt(String secret) {
70+
if (secret.startsWith(KNXBindingConstants.ENCRYPTED_PASSWORD_SERIALIZATION_PREFIX)) {
71+
try {
72+
logger.info("trying to access TPM module");
73+
return TpmInterface.TPM.deserializeAndDecryptSecret(
74+
secret.substring(KNXBindingConstants.ENCRYPTED_PASSWORD_SERIALIZATION_PREFIX.length()));
75+
} catch (SecurityException e) {
76+
logger.error("Unable to decode stored password using TPM: {}", e.getMessage());
77+
// fall through
78+
}
79+
}
80+
return secret;
5881
}
5982
}

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/IPBridgeConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ public String getTunnelUserId() {
6868
}
6969

7070
public String getTunnelUserPassword() {
71-
return tunnelUserPassword;
71+
return decrypt(tunnelUserPassword);
7272
}
7373

7474
public String getTunnelDeviceAuthentication() {
75-
return tunnelDeviceAuthentication;
75+
return decrypt(tunnelDeviceAuthentication);
7676
}
7777

7878
public String getTunnelSourceAddress() {

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.openhab.binding.knx.internal.KNXBindingConstants;
2121
import org.openhab.binding.knx.internal.factory.KNXHandlerFactory;
2222
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
23+
import org.openhab.binding.knx.internal.tpm.TpmInterface;
24+
import org.openhab.core.auth.SecurityException;
2325
import org.openhab.core.io.console.Console;
2426
import org.openhab.core.io.console.ConsoleCommandCompleter;
2527
import org.openhab.core.io.console.StringsCompleter;
@@ -39,7 +41,10 @@
3941
public class KNXCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
4042

4143
private static final String CMD_LIST_UNKNOWN_GA = "list-unknown-ga";
42-
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_LIST_UNKNOWN_GA), false);
44+
private static final String CMD_TPM_INFO = "tpm-info";
45+
private static final String CMD_TPM_ENCRYPT = "tpm-encrypt";
46+
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(
47+
List.of(CMD_LIST_UNKNOWN_GA, CMD_TPM_INFO, CMD_TPM_ENCRYPT), false);
4348

4449
private final KNXHandlerFactory knxHandlerFactory;
4550

@@ -60,14 +65,51 @@ public void execute(String[] args, Console console) {
6065
}
6166
}
6267
return;
68+
} else if (args.length == 1 && CMD_TPM_INFO.equalsIgnoreCase(args[0])) {
69+
try {
70+
console.println("trying to access TPM module");
71+
console.println("TPM version: " + TpmInterface.TPM.getTpmVersion());
72+
console.println("TPM model: " + TpmInterface.TPM.getTpmManufacturerShort() + " "
73+
+ TpmInterface.TPM.getTpmModel());
74+
console.println("TPM firmware: " + TpmInterface.TPM.getTpmFirmwareVersion());
75+
console.println("TPM TCG Spec.: rev. " + TpmInterface.TPM.getTpmTcgRevision() + " level "
76+
+ TpmInterface.TPM.getTpmTcgLevel());
77+
} catch (SecurityException e) {
78+
console.print("error: " + e.getMessage());
79+
}
80+
return;
81+
} else if (args.length == 2 && CMD_TPM_ENCRYPT.equalsIgnoreCase(args[0])) {
82+
try {
83+
console.println("trying to access TPM module");
84+
if (!TpmInterface.TPM.isReady()) {
85+
console.println("generating keys, this might take some time");
86+
}
87+
String p = TpmInterface.TPM.encryptAndSerializeSecret(args[1]);
88+
console.println("encrypted representation of password");
89+
console.println(KNXBindingConstants.ENCRYPTED_PASSWORD_SERIALIZATION_PREFIX + p);
90+
91+
// check if TPM can decrypt
92+
String decrypted = TpmInterface.TPM.deserializeAndDecryptSecret(p);
93+
if (args[1].equals(decrypted)) {
94+
console.println("Password successfully recovered from encrypted representation");
95+
} else {
96+
console.println("WARNING: could not decrypt");
97+
}
98+
99+
} catch (SecurityException e) {
100+
console.print("error: " + e.getMessage());
101+
}
102+
return;
63103
}
64104
printUsage(console);
65105
}
66106

67107
@Override
68108
public List<String> getUsages() {
69-
return List
70-
.of(buildCommandUsage(CMD_LIST_UNKNOWN_GA, "list group addresses which are not configured in openHAB"));
109+
return List.of(
110+
buildCommandUsage(CMD_LIST_UNKNOWN_GA, "list group addresses which are not configured in openHAB"),
111+
buildCommandUsage(CMD_TPM_ENCRYPT + " <password>", "Encrypt a password"),
112+
buildCommandUsage(CMD_TPM_INFO, "Get information about available TPM"));
71113
}
72114

73115
@Override

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ protected boolean initializeSecurity(String cKeyringFile, String cKeyringPasswor
207207
} catch (KNXMLException e) {
208208
throw new KnxSecureException("keyring file configured, but loading failed: ", e);
209209
}
210-
if (!keyring.isPresent()) {
210+
if (keyring.isEmpty()) {
211211
throw new KnxSecureException("keyring file configured, but loading failed: " + keyringUri);
212212
}
213213

@@ -263,7 +263,7 @@ protected boolean initializeSecurity(String cKeyringFile, String cKeyringPasswor
263263
// step 6: tunnel: load data from keyring
264264
if (secureTunnelSourceAddr != null) {
265265
// requires a valid keyring
266-
if (!keyring.isPresent()) {
266+
if (keyring.isEmpty()) {
267267
throw new KnxSecureException("valid keyring specification required for secure tunnel mode");
268268
}
269269
// other parameters will not be accepted, since all is read from keyring in this case

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,10 @@ public void onStateUpdateFromItem(State state) {
5656
ChannelUID linkedChannelUID = callback.getItemChannelLink().getLinkedUID();
5757
logger.trace("onStateUpdateFromItem({}) to {}", state.toString(), linkedChannelUID);
5858

59-
if (!(state instanceof Command)) {
59+
if (!(state instanceof Command command)) {
6060
logger.debug("The given state {} could not be transformed to a command", state);
6161
return;
6262
}
63-
Command command = (Command) state;
6463

6564
// this does not have effect for contact items
6665
// callback.handleCommand(command);

bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,10 @@ public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
8686
private @Nullable ProfileTypeUID getSuggestedProfileTypeUID(@Nullable ChannelTypeUID channelTypeUID,
8787
@Nullable String itemType) {
8888
if (KNXBindingConstants.CHANNEL_CONTACT_CONTROL_UID.equals(channelTypeUID) && itemType != null) {
89-
switch (itemType) {
90-
case CoreItemFactory.CONTACT:
91-
return UID_CONTACT_CONTROL;
92-
default:
93-
return null;
94-
}
89+
return switch (itemType) {
90+
case CoreItemFactory.CONTACT -> UID_CONTACT_CONTROL;
91+
default -> null;
92+
};
9593
}
9694
return null;
9795
}

0 commit comments

Comments
 (0)