Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] [knx] Add support for using hardware TPM modules #15326

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions bundles/org.openhab.binding.knx/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ calimero
* License: GPL v2 License with CLASSPATH EXCEPTION
* Project: http://calimero-project.github.io
* Source: https:/calimero-project

TSS.MSR / TSS.java
* License: The MIT License (MIT)
* Project: https:/microsoft/TSS.MSR
* Source: https:/microsoft/TSS.MSR/tree/main/TSS.Java
69 changes: 68 additions & 1 deletion bundles/org.openhab.binding.knx/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<name>openHAB Add-ons :: Bundles :: KNX Binding</name>

<properties>
<bnd.importpackage>javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional"</bnd.importpackage>
<bnd.importpackage>javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional",org.bouncycastle.*;resolution:="optional"</bnd.importpackage>
</properties>

<dependencies>
Expand Down Expand Up @@ -56,11 +56,72 @@
<version>0.8.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>TSS.Java</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-tss</id>
<phase>generate-sources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<echo>"Unpacking TSS.Java"</echo>
<artifactItems>
<artifactItem>
<groupId>com.microsoft.azure</groupId>
<artifactId>TSS.Java</artifactId>
<version>1.0.0</version>
<outputDirectory>
${project.build.directory}/classes
</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>

<execution>
<!-- Remove classes from the root package and re jar -->
<id>fix-tss</id>
<phase>process-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>"Fixing TSS.Java for OSGI"</echo>
<delete>
<fileset dir="${project.build.directory}/classes" includes="*"/>
</delete>
<delete includeemptydirs="true" failonerror="false">
<fileset dir="${project.build.directory}/classes/samples"/>
</delete>
</target>
</configuration>
</execution>
</executions>
</plugin>


<plugin>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd-maven-plugin</artifactId>
Expand All @@ -81,6 +142,11 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
Expand All @@ -96,6 +162,7 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.
<version>0.8.11</version>
<configuration>
<excludes>
<exclude>tss/**/*</exclude>
<exclude>tuwien/auto/calimero/**/*</exclude>
<exclude>io/calimero/**/*</exclude>
</excludes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
public class KNXBindingConstants {

public static final String BINDING_ID = "knx";
public static final String ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX = "TpM2-pRoTeCteD-";

// Global config
public static final String CONFIG_DISABLE_UOM = "disableUoM";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,32 @@ public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int respons
}

public void initialize() {
/*
* TpmInterface.SecuredPassword passKey = new TpmInterface.SecuredPassword("", "", "");
* try {
* TpmInterface tpmIf = new TpmInterface();
* String tpmRev = tpmIf.getTpmVersion();
* String tpmModel = "unknown";
* try {
* tpmModel = tpmIf.getTpmModel();
* } catch (KNXException ignored) {
* }
* logger.info("TPM rev. {} detected, based on {}", tpmRev, tpmModel);
*
* passKey = tpmIf.encryptSecret("habOpen");
* logger.warn("{}", passKey);
* } catch (KNXException e) {
* logger.warn("TPM exception", e);
* }
* try {
* TpmInterface tpmIf = new TpmInterface();
* String pass = tpmIf.decryptSecret(passKey);
* logger.warn("TPM decoded: {}", pass);
* } catch (KNXException e) {
* logger.warn("TPM exception", e);
* }
*/

connect();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
package org.openhab.binding.knx.internal.config;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.KNXBindingConstants;
import org.openhab.binding.knx.internal.tpm.TpmInterface;
import org.openhab.core.auth.SecurityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* {@link org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler} configuration
Expand All @@ -22,6 +28,9 @@
*/
@NonNullByDefault
public class BridgeConfiguration {
private final Logger logger = LoggerFactory.getLogger(BridgeConfiguration.class);
@Nullable
TpmInterface tpmIf = null;
private int autoReconnectPeriod = 0;
private int readingPause = 0;
private int readRetriesLimit = 0;
Expand Down Expand Up @@ -56,4 +65,18 @@ public String getKeyringFile() {
public String getKeyringPassword() {
return keyringPassword;
}

protected String decrypt(String secret) {
if (secret.startsWith(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX)) {
try {
logger.info("trying to access TPM module");
return TpmInterface.TPM.deserializeAndDecryptSecret(
secret.substring(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX.length()));
} catch (SecurityException e) {
logger.error("Unable to decode stored password using TPM: {}", e.getMessage());
// fall through
}
}
return secret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ public String getTunnelUserId() {
}

public String getTunnelUserPassword() {
return tunnelUserPassword;
return decrypt(tunnelUserPassword);
}

public String getTunnelDeviceAuthentication() {
return tunnelDeviceAuthentication;
return decrypt(tunnelDeviceAuthentication);
}

public String getTunnelSourceAddress() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.openhab.binding.knx.internal.KNXBindingConstants;
import org.openhab.binding.knx.internal.factory.KNXHandlerFactory;
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
import org.openhab.binding.knx.internal.tpm.TpmInterface;
import org.openhab.core.auth.SecurityException;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.ConsoleCommandCompleter;
import org.openhab.core.io.console.StringsCompleter;
Expand All @@ -39,7 +41,10 @@
public class KNXCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {

private static final String CMD_LIST_UNKNOWN_GA = "list-unknown-ga";
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(CMD_LIST_UNKNOWN_GA), false);
private static final String CMD_TPM_INFO = "tpm-info";
private static final String CMD_TPM_ENCRYPT = "tpm-encrypt";
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(
List.of(CMD_LIST_UNKNOWN_GA, CMD_TPM_INFO, CMD_TPM_ENCRYPT), false);

private final KNXHandlerFactory knxHandlerFactory;

Expand All @@ -60,14 +65,51 @@ public void execute(String[] args, Console console) {
}
}
return;
} else if (args.length == 1 && CMD_TPM_INFO.equalsIgnoreCase(args[0])) {
try {
console.println("trying to access TPM module");
console.println("TPM version: " + TpmInterface.TPM.getTpmVersion());
console.println("TPM model: " + TpmInterface.TPM.getTpmManufacturerShort() + " "
+ TpmInterface.TPM.getTpmModel());
console.println("TPM firmware: " + TpmInterface.TPM.getTpmFirmwareVersion());
console.println("TPM TCG Spec.: rev. " + TpmInterface.TPM.getTpmTcgRevision() + " level "
+ TpmInterface.TPM.getTpmTcgLevel());
} catch (SecurityException e) {
console.print("error: " + e.getMessage());
}
return;
} else if (args.length == 2 && CMD_TPM_ENCRYPT.equalsIgnoreCase(args[0])) {
try {
console.println("trying to access TPM module");
if (!TpmInterface.TPM.isReady()) {
console.println("generating keys, this might take some time");
}
String p = TpmInterface.TPM.encryptAndSerializeSecret(args[1]);
console.println("encrypted representation of password");
console.println(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX + p);

// check if TPM can decrypt
String decrypted = TpmInterface.TPM.deserializeAndDecryptSecret(p);
if (args[1].equals(decrypted)) {
console.println("Password successfully recovered from encrypted representation");
} else {
console.println("WARNING: could not decrypt");
}

} catch (SecurityException e) {
console.print("error: " + e.getMessage());
}
return;
}
printUsage(console);
}

@Override
public List<String> getUsages() {
return List
.of(buildCommandUsage(CMD_LIST_UNKNOWN_GA, "list group addresses which are not configured in openHAB"));
return List.of(
buildCommandUsage(CMD_LIST_UNKNOWN_GA, "list group addresses which are not configured in openHAB"),
buildCommandUsage(CMD_TPM_ENCRYPT + " <password>", "Encrypt a password"),
buildCommandUsage(CMD_TPM_INFO, "Get information about available TPM"));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ protected boolean initializeSecurity(String cKeyringFile, String cKeyringPasswor
} catch (KNXMLException e) {
throw new KnxSecureException("keyring file configured, but loading failed: ", e);
}
if (!keyring.isPresent()) {
if (keyring.isEmpty()) {
throw new KnxSecureException("keyring file configured, but loading failed: " + keyringUri);
}

Expand Down Expand Up @@ -262,7 +262,7 @@ protected boolean initializeSecurity(String cKeyringFile, String cKeyringPasswor
// step 6: tunnel: load data from keyring
if (secureTunnelSourceAddr != null) {
// requires a valid keyring
if (!keyring.isPresent()) {
if (keyring.isEmpty()) {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ public void onStateUpdateFromItem(State state) {
ChannelUID linkedChannelUID = callback.getItemChannelLink().getLinkedUID();
logger.trace("onStateUpdateFromItem({}) to {}", state.toString(), linkedChannelUID);

if (!(state instanceof Command)) {
if (!(state instanceof Command command)) {
logger.debug("The given state {} could not be transformed to a command", state);
return;
}
Command command = (Command) state;

// this does not have effect for contact items
// callback.handleCommand(command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,10 @@ public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
private @Nullable ProfileTypeUID getSuggestedProfileTypeUID(@Nullable ChannelTypeUID channelTypeUID,
@Nullable String itemType) {
if (KNXBindingConstants.CHANNEL_CONTACT_CONTROL_UID.equals(channelTypeUID) && itemType != null) {
switch (itemType) {
case CoreItemFactory.CONTACT:
return UID_CONTACT_CONTROL;
default:
return null;
}
return switch (itemType) {
case CoreItemFactory.CONTACT -> UID_CONTACT_CONTROL;
default -> null;
};
}
return null;
}
Expand Down
Loading