From f219e3f8d518612626394af6180dd68c0f951a29 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sat, 1 Jul 2023 17:00:48 +0200 Subject: [PATCH 1/8] 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 --- bundles/org.openhab.binding.knx/pom.xml | 77 ++++ .../knx/internal/KNXBindingConstants.java | 1 + .../internal/client/AbstractKNXClient.java | 26 ++ .../internal/config/BridgeConfiguration.java | 32 ++ .../config/IPBridgeConfiguration.java | 4 +- .../internal/console/KNXCommandExtension.java | 46 ++- .../knx/internal/tpm/TpmInterface.java | 365 ++++++++++++++++++ .../binding/knx/internal/tpm/TpmTest.java | 111 ++++++ 8 files changed, 657 insertions(+), 5 deletions(-) create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java create mode 100644 bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java diff --git a/bundles/org.openhab.binding.knx/pom.xml b/bundles/org.openhab.binding.knx/pom.xml index 6455077f12b38..f965045d1a773 100644 --- a/bundles/org.openhab.binding.knx/pom.xml +++ b/bundles/org.openhab.binding.knx/pom.xml @@ -56,11 +56,87 @@ 0.8.11 test + + com.microsoft.azure + TSS.Java + 1.0.0 + provided + + + org.bouncycastle + + bcprov-jdk18on + 1.76 + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-tss + generate-sources + + unpack + + + "Unpacking TSS.Java" + + + com.microsoft.azure + TSS.Java + 1.0.0 + + ${project.build.directory}/classes + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + + fix-tss + process-sources + + run + + + + + "Fixing TSS.Java for OSGI" + + + + + + + + + + + + + biz.aQute.bnd bnd-maven-plugin @@ -74,6 +150,7 @@ Require-Capability: cardinality:=multiple SPI-Provider: tuwien.auto.calimero.serial.spi.SerialCom SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.serial.spi.SerialCom]) +-includeresource: "target/TSS.Java-fixed-1.0.0.jar";lib:=true ]]> diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java index 2905aeb75eddb..1604a02e61636 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java @@ -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"; diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java index fc2a4c1775d9f..02dbda3a1a48a 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java @@ -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(); } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java index 89af885de3556..13eebafaf5162 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java @@ -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 @@ -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; @@ -56,4 +65,27 @@ 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"); + if (tpmIf == null) { + tpmIf = new TpmInterface(); + logger.info("generating keys, this might take some time"); + } + TpmInterface tmpTpmIf = tpmIf; + if (tmpTpmIf != null) { + secret = tmpTpmIf.deserializeAndDectryptSecret( + secret.substring(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX.length())); + } else { + logger.error("Unable to decode stored password using TPM"); + } + } catch (SecurityException e) { + logger.error("Unable to decode stored password using TPM: {}", e.getMessage()); + // fall through + } + } + return secret; + } } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/IPBridgeConfiguration.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/IPBridgeConfiguration.java index 49749a3ca2705..ed6d0a78d4bc1 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/IPBridgeConfiguration.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/IPBridgeConfiguration.java @@ -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() { diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java index fe1b6d2590cbd..9c53a8fe8d141 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java @@ -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; @@ -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; @@ -60,14 +65,49 @@ 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"); + TpmInterface tpm = new TpmInterface(); + console.println("TPM version: " + tpm.getTpmVersion()); + console.println("TPM model: " + tpm.getTpmManufacturerShort() + " " + tpm.getTpmModel()); + console.println("TPM firmware: " + tpm.getTpmFirmwareVersion()); + console.println("TPM TCG Spec.: rev. " + tpm.getTpmTcgRevision() + " level " + 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"); + TpmInterface tpm = new TpmInterface(); + console.println("generating keys, this might take some time"); + String p = 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 = tpm.deserializeAndDectryptSecret(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 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 + " ", "Encrypt a password"), + buildCommandUsage(CMD_TPM_INFO, "Get information about available TPM")); } @Override diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java new file mode 100644 index 0000000000000..4389898cfad2b --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java @@ -0,0 +1,365 @@ +/** + * Copyright (c) 2010-2023 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.tpm; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.auth.SecurityException; +import org.openhab.core.id.InstanceUUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import tss.Helpers; +import tss.Tpm; +import tss.TpmException; +import tss.TpmFactory; +import tss.TpmHelpers; +import tss.Tss; +import tss.tpm.CreatePrimaryResponse; +import tss.tpm.StartAuthSessionResponse; +import tss.tpm.TPM2B_PUBLIC_KEY_RSA; +import tss.tpm.TPMA_OBJECT; +import tss.tpm.TPMS_NULL_ASYM_SCHEME; +import tss.tpm.TPMS_PCR_SELECTION; +import tss.tpm.TPMS_RSA_PARMS; +import tss.tpm.TPMS_SENSITIVE_CREATE; +import tss.tpm.TPMT_PUBLIC; +import tss.tpm.TPMT_SYM_DEF; +import tss.tpm.TPMT_SYM_DEF_OBJECT; +import tss.tpm.TPM_ALG_ID; +import tss.tpm.TPM_HANDLE; +import tss.tpm.TPM_PT; +import tss.tpm.TPM_RH; +import tss.tpm.TPM_SE; + +/** + * This class implements connection to a trusted platform module (TPM) + * which can be used as a secure storage for passwords. + * + * @author Holger Friedrich - Initial contribution + */ +@NonNullByDefault +public class TpmInterface { + private final Logger logger = LoggerFactory.getLogger(TpmInterface.class); + private static final byte[] STANDARD_EK_POLICY = Helpers + .fromHex("837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa"); + + private static final String OUTSIDE_INFO = "created by openHAB"; + private static final String USER_PWD = Objects.requireNonNullElse(InstanceUUID.get(), "habOpen"); + private static final TPMS_SENSITIVE_CREATE USER_AUTH = new TPMS_SENSITIVE_CREATE(new byte[0], USER_PWD.getBytes()); + + private @Nullable CreatePrimaryResponse rsaEk; + private @Nullable CreatePrimaryResponse rsaSrk; + private Tpm tpm; + + public record SecuredPassword(String secret, String encIdentity, String integrityHMAC) implements Serializable { + private static final long serialVersionUID = 238409238L; + } + + /** + * Create instance which interfaces a TPM. + * + * @throws SecurityException + */ + public TpmInterface() throws SecurityException { + try { + @Nullable + Tpm tmpTpm = TpmFactory.platformTpm(); + if (tmpTpm == null) { + throw new SecurityException("TPM cannot be accessed"); + } else { + tpm = tmpTpm; + } + } catch (TpmException e) { + throw new SecurityException("TPM cannot be accessed", e); + } + } + + /** + * Generate keys required for encryption and decryption. + * As TPM uses a key derivation function to derive the key from an + * internal seed set at production time, identical keys can be created. + * + * @throws SecurityException + */ + public void generateKeys() throws SecurityException { + try { + Instant start = Instant.now(); + TPMT_PUBLIC rsaEkTemplate = new TPMT_PUBLIC(TPM_ALG_ID.SHA256, + new TPMA_OBJECT(TPMA_OBJECT.fixedTPM, TPMA_OBJECT.fixedParent, TPMA_OBJECT.sensitiveDataOrigin, + TPMA_OBJECT.adminWithPolicy, TPMA_OBJECT.restricted, TPMA_OBJECT.decrypt), + STANDARD_EK_POLICY, new TPMS_RSA_PARMS(new TPMT_SYM_DEF_OBJECT(TPM_ALG_ID.AES, 128, TPM_ALG_ID.CFB), + new TPMS_NULL_ASYM_SCHEME(), 2048, 0), + new TPM2B_PUBLIC_KEY_RSA()); + + logger.info("TPM generates RSA based endorsement key, this takes time"); + rsaEk = tpm.CreatePrimary(TPM_HANDLE.from(TPM_RH.OWNER), + new TPMS_SENSITIVE_CREATE(new byte[0], new byte[0]), rsaEkTemplate, OUTSIDE_INFO.getBytes(), + new TPMS_PCR_SELECTION[0]); + Instant end = Instant.now(); + logger.debug("TPM based RSA endorsement key generated in {} seconds", + Duration.between(start, end).toSeconds()); + // logger.trace("TPM based RSA EK: {}", rsaEk.toString()); + + start = end; + // Now create an "SRK" in the owner hierarchy that we can activate + TPMT_PUBLIC srkTemplate = new TPMT_PUBLIC(TPM_ALG_ID.SHA256, + new TPMA_OBJECT(TPMA_OBJECT.fixedTPM, TPMA_OBJECT.fixedParent, TPMA_OBJECT.sensitiveDataOrigin, + TPMA_OBJECT.userWithAuth, TPMA_OBJECT.noDA, TPMA_OBJECT.restricted, TPMA_OBJECT.decrypt), + new byte[0], new TPMS_RSA_PARMS(new TPMT_SYM_DEF_OBJECT(TPM_ALG_ID.AES, 128, TPM_ALG_ID.CFB), + new TPMS_NULL_ASYM_SCHEME(), 2048, 0), + new TPM2B_PUBLIC_KEY_RSA()); + + logger.info("TPM generates RSA based storage key, this takes time"); + rsaSrk = tpm.CreatePrimary(TPM_HANDLE.from(TPM_RH.OWNER), USER_AUTH, srkTemplate, OUTSIDE_INFO.getBytes(), + new TPMS_PCR_SELECTION[0]); + // logger.trace("TPM based RSA Primary Key: {}", rsaSrk.toString()); + end = Instant.now(); + logger.debug("TPM based RSA storage key generated in {} seconds", Duration.between(start, end).toSeconds()); + + logger.info("TPM key genration complete"); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @param secret plain text representation of password + * @return offline representation enctypted and bound to this TPM + * @throws SecurityException + */ + public SecuredPassword encryptSecret(String secret) throws SecurityException { + try { + if ((rsaEk == null) || (rsaSrk == null)) { + generateKeys(); + } + + // create local copy to avoid null warnings + CreatePrimaryResponse tmpRsaEk = rsaEk; + CreatePrimaryResponse tmpRsaSrk = rsaSrk; + if ((tmpRsaEk == null) || (tmpRsaSrk == null) || (tmpRsaEk.outPublic == null)) { + throw new SecurityException("TPM keys could not be created"); + } + + // Use tss.java to create an activation credential. Note we use tss.java + // to get the name of the object based on the TPMT_PUBLIC. + Tss.ActivationCredential bundle = Tss.createActivationCredential(tmpRsaEk.outPublic, + tmpRsaSrk.outPublic.getName(), secret.getBytes()); + + return new SecuredPassword(Helpers.toHex(bundle.Secret, 0, bundle.Secret.length), + Helpers.toHex(bundle.CredentialBlob.encIdentity, 0, bundle.CredentialBlob.encIdentity.length), + Helpers.toHex(bundle.CredentialBlob.integrityHMAC, 0, bundle.CredentialBlob.integrityHMAC.length)); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + public String encryptAndSerializeSecret(String secret) throws SecurityException { + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + ObjectOutputStream serial = new ObjectOutputStream(stream); + serial.writeObject(encryptSecret(secret)); + byte[] array = stream.toByteArray(); + return Helpers.toHex(array, 0, array.length); + } catch (IOException e) { + throw new SecurityException("Serialization failed", e); + } + } + + /** + * @param secret offline repesentation of secret encrypted with this TPM. + * @return secret in clear text + * @throws SecurityException + */ + public String decryptSecret(SecuredPassword secret) throws SecurityException { + try { + if ((rsaEk == null) || (rsaSrk == null)) { + generateKeys(); + } + + CreatePrimaryResponse tmpRsaEk = rsaEk; + CreatePrimaryResponse tmpRsaSrk = rsaSrk; + if ((tmpRsaSrk == null) || (tmpRsaEk == null) || (tmpRsaEk.handle == null) || (tmpRsaSrk.handle == null)) { + throw new SecurityException("TPM keys could not be created"); + } + + // create credential from stored representation + Tss.ActivationCredential stored = new Tss.ActivationCredential(); + stored.Secret = Helpers.fromHex(secret.secret()); + stored.CredentialBlob.encIdentity = Helpers.fromHex(secret.encIdentity()); + stored.CredentialBlob.integrityHMAC = Helpers.fromHex(secret.integrityHMAC()); + + // policy session + byte[] nonceCaller = Helpers.RandomBytes(20); + StartAuthSessionResponse policySession = tpm.StartAuthSession(TPM_HANDLE.NULL, TPM_HANDLE.NULL, nonceCaller, + new byte[0], TPM_SE.POLICY, new TPMT_SYM_DEF(), TPM_ALG_ID.SHA256); + // password is used during creation of key handles, so it needs to be set + policySession.handle.AuthValue = USER_PWD.getBytes(); + tpm.PolicySecret(tpm._EndorsementHandle, policySession.handle, new byte[0], new byte[0], new byte[0], 0); + byte[] policyDigest = tpm.PolicyGetDigest(policySession.handle); + if (!Helpers.arraysAreEqual(policyDigest, STANDARD_EK_POLICY)) { + throw new SecurityException("TPM decryption failed"); + } + + tpm._withSessions(TPM_HANDLE.pwSession(new byte[0]), policySession.handle); + byte[] recoveredSecret = tpm.ActivateCredential(tmpRsaSrk.handle, tmpRsaEk.handle, stored.CredentialBlob, + stored.Secret); + + return new String(recoveredSecret); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + public String deserializeAndDectryptSecret(String encryptedSecret) throws SecurityException { + try { + byte[] array = Helpers.fromHex(encryptedSecret); + ByteArrayInputStream stream = new ByteArrayInputStream(array); + ObjectInputStream serial = new ObjectInputStream(stream); + SecuredPassword secret = (SecuredPassword) serial.readObject(); + if (secret == null) { + throw new SecurityException("Deserialization failed"); + } + return decryptSecret(secret); + } catch (IOException | ClassNotFoundException e) { + throw new SecurityException("Deserialization failed", e); + } + } + + /** + * Fetch random numbers from the hardware random number genrator of the TPM. + * + * @param bytesRequested + * @return array of random numbers + */ + byte[] getRandom(int bytesRequested) { + return tpm.GetRandom(bytesRequested); + } + + /** + * @return Return model of TPM chip + * @throws SecurityException + */ + public String getTpmFirmwareVersion() throws SecurityException { + try { + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.FIRMWARE_VERSION_1); + int major = ret >> 16; + int minor = ret & 0xffff; + return "" + major + "." + minor; + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @return Return manufacturer (abbreviation) of TPM chip + * @throws SecurityException + */ + public String getTpmManufacturerShort() throws SecurityException { + try { + StringBuilder sb = new StringBuilder(4); + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.MANUFACTURER); + for (int i = 3; i >= 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + return sb.toString().trim(); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @return Return model of TPM chip + * @throws SecurityException + */ + public String getTpmModel() throws SecurityException { + try { + StringBuilder sb = new StringBuilder(24); + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.VENDOR_STRING_1); + for (int i = 3; i >= 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.VENDOR_STRING_2); + for (int i = 3; i >= 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.VENDOR_STRING_3); + for (int i = 3; i > 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.VENDOR_STRING_4); + for (int i = 3; i > 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + return sb.toString().trim(); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @return Return level of TPM TCG standard, typically a number like "1.38" + * @see getTpmVersion() in case you want to differentiate TPM1.2, TPM2, etc. + * @throws SecurityException + */ + public String getTpmTcgLevel() throws SecurityException { + try { + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.LEVEL); + return "" + ret; + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @return Return revision of TPM TCG standard, typically a number like "1.38" + * @see getTpmVersion() in case you want to differentiate TPM1.2, TPM2, etc. + * @throws SecurityException + */ + public String getTpmTcgRevision() throws SecurityException { + try { + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.REVISION); + return "" + (ret / 100) + "." + (ret % 100); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } + + /** + * @return Return supported version of TPM standard is supported, typically + * "1.2" or "2.0". + * @throws SecurityException + */ + public String getTpmVersion() throws SecurityException { + try { + StringBuilder sb = new StringBuilder(4); + int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.FAMILY_INDICATOR); + for (int i = 3; i >= 0; i--) { + sb.append((char) ((ret >> (i * 8)) & 0xff)); + } + return sb.toString().trim(); + } catch (TpmException e) { + throw new SecurityException("TPM exception", e); + } + } +} diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java new file mode 100644 index 0000000000000..99d3e1c97c310 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2010-2023 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.tpm; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.condition.OS.*; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.openhab.binding.knx.internal.tpm.TpmInterface.SecuredPassword; +import org.openhab.core.auth.SecurityException; + +/** + * + * @author Holger Friedrich - Initial contribution + * + * This tests are intended to test the interface class for Trusted Platform Modules (TPM). + * The tests will succeed, in case no TPM is available during test execution. + * Some tests will be skipped on Windows, as some operations require admin access to the TPM. + * + */ +@NonNullByDefault +class TpmTest { + + @Test + void testTpmInfo() { + TpmInterface tpmIf = null; + + try { + tpmIf = new TpmInterface(); + } catch (SecurityException ignored) { + // TPM might not be availabe + } + + if (tpmIf != null) { + assertDoesNotThrow(tpmIf::getTpmManufacturerShort); + assertDoesNotThrow(tpmIf::getTpmModel); + assertDoesNotThrow(tpmIf::getTpmFirmwareVersion); + assertDoesNotThrow(tpmIf::getTpmTcgLevel); + assertDoesNotThrow(tpmIf::getTpmTcgRevision); + assertDoesNotThrow(tpmIf::getTpmVersion); + } + } + + /** + * Test encryption and decryption. Skipped on Windows as admin access seems to be required. + */ + @Test + @DisabledOnOs(WINDOWS) // this test fails on Windows as user + void testTpmEncDec() { + TpmInterface tpmIf = null; + SecuredPassword sPwd = null; + + try { + tpmIf = new TpmInterface(); + } catch (SecurityException ignored) { + // TPM might not be availabe + } + + if (tpmIf != null) { + try { + final String secret = "password"; + sPwd = tpmIf.encryptSecret(secret); + + TpmInterface tpmIf2 = null; + try { + tpmIf2 = new TpmInterface(); + } catch (SecurityException e) { + assertEquals("", e.toString()); + } + + assertNotEquals(null, tpmIf2); + + if (tpmIf2 != null) { // always true, avoid warning + assertEquals(secret, tpmIf2.decryptSecret(sPwd)); + } + } catch (SecurityException e) { + assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); + } + } + } + + @Test + void testTpmRandom() { + TpmInterface tpmIf = null; + + try { + tpmIf = new TpmInterface(); + } catch (SecurityException ignored) { + // TPM might not be availabe + } + + if (tpmIf != null) { + byte[] r = tpmIf.getRandom(20); + assertEquals(20, r.length); + } + } +} From de76eee322b293bc50f4db5cc17ce6fcf98b45ed Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 6 Aug 2023 22:23:28 +0200 Subject: [PATCH 2/8] single instance Signed-off-by: Holger Friedrich --- .../internal/config/BridgeConfiguration.java | 13 +- .../internal/console/KNXCommandExtension.java | 20 +-- .../knx/internal/tpm/TpmInterface.java | 135 +++++++++++++++--- .../binding/knx/internal/tpm/TpmTest.java | 64 +++------ 4 files changed, 148 insertions(+), 84 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java index 13eebafaf5162..6c54fa76037b4 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java @@ -70,17 +70,8 @@ protected String decrypt(String secret) { if (secret.startsWith(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX)) { try { logger.info("trying to access TPM module"); - if (tpmIf == null) { - tpmIf = new TpmInterface(); - logger.info("generating keys, this might take some time"); - } - TpmInterface tmpTpmIf = tpmIf; - if (tmpTpmIf != null) { - secret = tmpTpmIf.deserializeAndDectryptSecret( - secret.substring(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX.length())); - } else { - logger.error("Unable to decode stored password using TPM"); - } + return TpmInterface.TPM.deserializeAndDectryptSecret( + secret.substring(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX.length())); } catch (SecurityException e) { logger.error("Unable to decode stored password using TPM: {}", e.getMessage()); // fall through diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java index 9c53a8fe8d141..2bdd1bcab495f 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java @@ -68,11 +68,12 @@ public void execute(String[] args, Console console) { } else if (args.length == 1 && CMD_TPM_INFO.equalsIgnoreCase(args[0])) { try { console.println("trying to access TPM module"); - TpmInterface tpm = new TpmInterface(); - console.println("TPM version: " + tpm.getTpmVersion()); - console.println("TPM model: " + tpm.getTpmManufacturerShort() + " " + tpm.getTpmModel()); - console.println("TPM firmware: " + tpm.getTpmFirmwareVersion()); - console.println("TPM TCG Spec.: rev. " + tpm.getTpmTcgRevision() + " level " + tpm.getTpmTcgLevel()); + 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()); } @@ -80,14 +81,15 @@ public void execute(String[] args, Console console) { } else if (args.length == 2 && CMD_TPM_ENCRYPT.equalsIgnoreCase(args[0])) { try { console.println("trying to access TPM module"); - TpmInterface tpm = new TpmInterface(); - console.println("generating keys, this might take some time"); - String p = tpm.encryptAndSerializeSecret(args[1]); + 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 = tpm.deserializeAndDectryptSecret(p); + String decrypted = TpmInterface.TPM.deserializeAndDectryptSecret(p); if (args[1].equals(decrypted)) { console.println("Password successfully recovered from encrypted representation"); } else { diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java index 4389898cfad2b..6524d1c64e7ea 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java @@ -59,7 +59,9 @@ * @author Holger Friedrich - Initial contribution */ @NonNullByDefault -public class TpmInterface { +public enum TpmInterface { + TPM; + private final Logger logger = LoggerFactory.getLogger(TpmInterface.class); private static final byte[] STANDARD_EK_POLICY = Helpers .fromHex("837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa"); @@ -70,7 +72,8 @@ public class TpmInterface { private @Nullable CreatePrimaryResponse rsaEk; private @Nullable CreatePrimaryResponse rsaSrk; - private Tpm tpm; + private @Nullable Tpm tpm; + private @Nullable StartAuthSessionResponse policySession; public record SecuredPassword(String secret, String encIdentity, String integrityHMAC) implements Serializable { private static final long serialVersionUID = 238409238L; @@ -81,20 +84,49 @@ public record SecuredPassword(String secret, String encIdentity, String integrit * * @throws SecurityException */ - public TpmInterface() throws SecurityException { - try { - @Nullable - Tpm tmpTpm = TpmFactory.platformTpm(); - if (tmpTpm == null) { + private TpmInterface() { + } + + private void init() throws SecurityException { + if (tpm == null) { + initSynchronized(); + } + } + + private synchronized void initSynchronized() throws SecurityException { + if (tpm == null) { + try { + tpm = TpmFactory.platformTpm(); + } catch (TpmException e) { + throw new SecurityException("TPM cannot be accessed", e); + } + if (tpm == null) { throw new SecurityException("TPM cannot be accessed"); - } else { - tpm = tmpTpm; } - } catch (TpmException e) { - throw new SecurityException("TPM cannot be accessed", e); } } + public boolean isAvailable() { + if (tpm != null) { + return true; + } + try { + init(); + if (tpm != null) { + return true; + } + } catch (SecurityException e) { + logger.info("cannot open TPM"); + } + return false; + } + + public boolean isReady() { + CreatePrimaryResponse rsaEk = this.rsaEk; // to avoid warning + return (tpm != null) && (rsaEk != null) && (rsaSrk != null) && (rsaEk.outPublic != null) + && (policySession != null); + } + /** * Generate keys required for encryption and decryption. * As TPM uses a key derivation function to derive the key from an @@ -102,7 +134,15 @@ public TpmInterface() throws SecurityException { * * @throws SecurityException */ - public void generateKeys() throws SecurityException { + public synchronized void generateKeys() throws SecurityException { + if ((rsaEk != null) && (rsaSrk != null)) { + return; // keys already exist, re-creating will lead to same keys + } + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { Instant start = Instant.now(); TPMT_PUBLIC rsaEkTemplate = new TPMT_PUBLIC(TPM_ALG_ID.SHA256, @@ -137,7 +177,7 @@ STANDARD_EK_POLICY, new TPMS_RSA_PARMS(new TPMT_SYM_DEF_OBJECT(TPM_ALG_ID.AES, 1 end = Instant.now(); logger.debug("TPM based RSA storage key generated in {} seconds", Duration.between(start, end).toSeconds()); - logger.info("TPM key genration complete"); + logger.info("TPM key generation complete"); } catch (TpmException e) { throw new SecurityException("TPM exception", e); } @@ -149,6 +189,11 @@ STANDARD_EK_POLICY, new TPMS_RSA_PARMS(new TPMT_SYM_DEF_OBJECT(TPM_ALG_ID.AES, 1 * @throws SecurityException */ public SecuredPassword encryptSecret(String secret) throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { if ((rsaEk == null) || (rsaSrk == null)) { generateKeys(); @@ -175,6 +220,11 @@ public SecuredPassword encryptSecret(String secret) throws SecurityException { } public String encryptAndSerializeSecret(String secret) throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { ByteArrayOutputStream stream = new ByteArrayOutputStream(); ObjectOutputStream serial = new ObjectOutputStream(stream); @@ -192,6 +242,11 @@ public String encryptAndSerializeSecret(String secret) throws SecurityException * @throws SecurityException */ public String decryptSecret(SecuredPassword secret) throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { if ((rsaEk == null) || (rsaSrk == null)) { generateKeys(); @@ -211,14 +266,20 @@ public String decryptSecret(SecuredPassword secret) throws SecurityException { // policy session byte[] nonceCaller = Helpers.RandomBytes(20); - StartAuthSessionResponse policySession = tpm.StartAuthSession(TPM_HANDLE.NULL, TPM_HANDLE.NULL, nonceCaller, - new byte[0], TPM_SE.POLICY, new TPMT_SYM_DEF(), TPM_ALG_ID.SHA256); + if (policySession == null) { + policySession = tpm.StartAuthSession(TPM_HANDLE.NULL, TPM_HANDLE.NULL, nonceCaller, new byte[0], + TPM_SE.POLICY, new TPMT_SYM_DEF(), TPM_ALG_ID.SHA256); + } + StartAuthSessionResponse policySession = this.policySession; // local copy to avoid Null warnings + if (policySession == null) { + throw new SecurityException("TPM decryption failed, cannot create policy session"); + } // password is used during creation of key handles, so it needs to be set policySession.handle.AuthValue = USER_PWD.getBytes(); tpm.PolicySecret(tpm._EndorsementHandle, policySession.handle, new byte[0], new byte[0], new byte[0], 0); byte[] policyDigest = tpm.PolicyGetDigest(policySession.handle); if (!Helpers.arraysAreEqual(policyDigest, STANDARD_EK_POLICY)) { - throw new SecurityException("TPM decryption failed"); + throw new SecurityException("TPM decryption failed, policy mismatch"); } tpm._withSessions(TPM_HANDLE.pwSession(new byte[0]), policySession.handle); @@ -232,6 +293,11 @@ public String decryptSecret(SecuredPassword secret) throws SecurityException { } public String deserializeAndDectryptSecret(String encryptedSecret) throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { byte[] array = Helpers.fromHex(encryptedSecret); ByteArrayInputStream stream = new ByteArrayInputStream(array); @@ -252,7 +318,12 @@ public String deserializeAndDectryptSecret(String encryptedSecret) throws Securi * @param bytesRequested * @return array of random numbers */ - byte[] getRandom(int bytesRequested) { + byte[] getRandom(int bytesRequested) throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } return tpm.GetRandom(bytesRequested); } @@ -261,6 +332,11 @@ byte[] getRandom(int bytesRequested) { * @throws SecurityException */ public String getTpmFirmwareVersion() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.FIRMWARE_VERSION_1); int major = ret >> 16; @@ -276,6 +352,11 @@ public String getTpmFirmwareVersion() throws SecurityException { * @throws SecurityException */ public String getTpmManufacturerShort() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { StringBuilder sb = new StringBuilder(4); int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.MANUFACTURER); @@ -293,6 +374,11 @@ public String getTpmManufacturerShort() throws SecurityException { * @throws SecurityException */ public String getTpmModel() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { StringBuilder sb = new StringBuilder(24); int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.VENDOR_STRING_1); @@ -323,6 +409,11 @@ public String getTpmModel() throws SecurityException { * @throws SecurityException */ public String getTpmTcgLevel() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.LEVEL); return "" + ret; @@ -337,6 +428,11 @@ public String getTpmTcgLevel() throws SecurityException { * @throws SecurityException */ public String getTpmTcgRevision() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.REVISION); return "" + (ret / 100) + "." + (ret % 100); @@ -351,6 +447,11 @@ public String getTpmTcgRevision() throws SecurityException { * @throws SecurityException */ public String getTpmVersion() throws SecurityException { + init(); + Tpm tpm = this.tpm; // local copy to avoid Null warnings + if (tpm == null) { + throw new SecurityException("TPM cannot be opened"); + } try { StringBuilder sb = new StringBuilder(4); int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.FAMILY_INDICATOR); diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java index 99d3e1c97c310..7574a884e7dcc 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java @@ -37,21 +37,13 @@ class TpmTest { @Test void testTpmInfo() { - TpmInterface tpmIf = null; - - try { - tpmIf = new TpmInterface(); - } catch (SecurityException ignored) { - // TPM might not be availabe - } - - if (tpmIf != null) { - assertDoesNotThrow(tpmIf::getTpmManufacturerShort); - assertDoesNotThrow(tpmIf::getTpmModel); - assertDoesNotThrow(tpmIf::getTpmFirmwareVersion); - assertDoesNotThrow(tpmIf::getTpmTcgLevel); - assertDoesNotThrow(tpmIf::getTpmTcgRevision); - assertDoesNotThrow(tpmIf::getTpmVersion); + if (TpmInterface.TPM.isAvailable()) { + assertDoesNotThrow(TpmInterface.TPM::getTpmManufacturerShort); + assertDoesNotThrow(TpmInterface.TPM::getTpmModel); + assertDoesNotThrow(TpmInterface.TPM::getTpmFirmwareVersion); + assertDoesNotThrow(TpmInterface.TPM::getTpmTcgLevel); + assertDoesNotThrow(TpmInterface.TPM::getTpmTcgRevision); + assertDoesNotThrow(TpmInterface.TPM::getTpmVersion); } } @@ -61,32 +53,14 @@ void testTpmInfo() { @Test @DisabledOnOs(WINDOWS) // this test fails on Windows as user void testTpmEncDec() { - TpmInterface tpmIf = null; SecuredPassword sPwd = null; - try { - tpmIf = new TpmInterface(); - } catch (SecurityException ignored) { - // TPM might not be availabe - } - - if (tpmIf != null) { + if (TpmInterface.TPM.isAvailable()) { try { final String secret = "password"; - sPwd = tpmIf.encryptSecret(secret); - - TpmInterface tpmIf2 = null; - try { - tpmIf2 = new TpmInterface(); - } catch (SecurityException e) { - assertEquals("", e.toString()); - } - - assertNotEquals(null, tpmIf2); + sPwd = TpmInterface.TPM.encryptSecret(secret); - if (tpmIf2 != null) { // always true, avoid warning - assertEquals(secret, tpmIf2.decryptSecret(sPwd)); - } + assertEquals(secret, TpmInterface.TPM.decryptSecret(sPwd)); } catch (SecurityException e) { assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); } @@ -95,17 +69,13 @@ void testTpmEncDec() { @Test void testTpmRandom() { - TpmInterface tpmIf = null; - - try { - tpmIf = new TpmInterface(); - } catch (SecurityException ignored) { - // TPM might not be availabe - } - - if (tpmIf != null) { - byte[] r = tpmIf.getRandom(20); - assertEquals(20, r.length); + if (TpmInterface.TPM.isAvailable()) { + try { + byte[] r = TpmInterface.TPM.getRandom(20); + assertEquals(20, r.length); + } catch (SecurityException e) { + assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); + } } } } From 53de31d886506c15b819be966fe624ba05ba42ef Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Tue, 2 Jan 2024 19:43:36 +0100 Subject: [PATCH 3/8] 2024 headers Signed-off-by: Holger Friedrich --- .../java/org/openhab/binding/knx/internal/tpm/TpmInterface.java | 2 +- .../test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java index 6524d1c64e7ea..fc3a25267b9d6 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java index 7574a884e7dcc..7f83e567031e2 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. From 3dc1dab208f6366e7f617bf68d97ec8777c00a60 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 21 Jan 2024 22:34:05 +0100 Subject: [PATCH 4/8] upgrade bc to 1.77 Signed-off-by: Holger Friedrich --- bundles/org.openhab.binding.knx/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.knx/pom.xml b/bundles/org.openhab.binding.knx/pom.xml index f965045d1a773..9ecdc72ddcc8f 100644 --- a/bundles/org.openhab.binding.knx/pom.xml +++ b/bundles/org.openhab.binding.knx/pom.xml @@ -66,7 +66,7 @@ org.bouncycastle bcprov-jdk18on - 1.76 + 1.77 From e66976b7d28aa5face46242187b549a9880e5c58 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Wed, 14 Feb 2024 21:56:15 +0100 Subject: [PATCH 5/8] Drop transitive dependency on bcprov Signed-off-by: Holger Friedrich --- bundles/org.openhab.binding.knx/NOTICE | 5 +++++ bundles/org.openhab.binding.knx/pom.xml | 19 +++++-------------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/bundles/org.openhab.binding.knx/NOTICE b/bundles/org.openhab.binding.knx/NOTICE index 9da145223c82a..7198848bacd0a 100644 --- a/bundles/org.openhab.binding.knx/NOTICE +++ b/bundles/org.openhab.binding.knx/NOTICE @@ -18,3 +18,8 @@ calimero * License: GPL v2 License with CLASSPATH EXCEPTION * Project: http://calimero-project.github.io * Source: https://github.com/calimero-project + +TSS.MSR / TSS.java +* License: The MIT License (MIT) +* Project: https://github.com/microsoft/TSS.MSR +* Source: https://github.com/microsoft/TSS.MSR/tree/main/TSS.Java diff --git a/bundles/org.openhab.binding.knx/pom.xml b/bundles/org.openhab.binding.knx/pom.xml index 9ecdc72ddcc8f..60408296e551b 100644 --- a/bundles/org.openhab.binding.knx/pom.xml +++ b/bundles/org.openhab.binding.knx/pom.xml @@ -15,7 +15,7 @@ openHAB Add-ons :: Bundles :: KNX Binding - javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional" + javax.microedition.io.*;resolution:="optional",javax.usb.*;resolution:="optional",org.usb4java.*;resolution:="optional",org.bouncycastle.*;resolution:="optional" @@ -62,12 +62,6 @@ 1.0.0 provided - - org.bouncycastle - - bcprov-jdk18on - 1.77 - @@ -121,15 +115,13 @@ - "Fixing TSS.Java for OSGI" - + + + + - - - @@ -150,7 +142,6 @@ Require-Capability: cardinality:=multiple SPI-Provider: tuwien.auto.calimero.serial.spi.SerialCom SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero.serial.spi.SerialCom]) --includeresource: "target/TSS.Java-fixed-1.0.0.jar";lib:=true ]]> From 778e2616c0ba821a0617c6b3d0d98563d25e8040 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Fri, 26 Apr 2024 18:26:55 +0200 Subject: [PATCH 6/8] Exclude TSS from JaCoCo, resolve merge conflict Signed-off-by: Holger Friedrich --- bundles/org.openhab.binding.knx/pom.xml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.knx/pom.xml b/bundles/org.openhab.binding.knx/pom.xml index 60408296e551b..0485469d7011a 100644 --- a/bundles/org.openhab.binding.knx/pom.xml +++ b/bundles/org.openhab.binding.knx/pom.xml @@ -65,13 +65,6 @@ - - - org.apache.maven.plugins - maven-antrun-plugin - 3.1.0 - - @@ -149,6 +142,11 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero. + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + org.apache.maven.plugins maven-surefire-plugin @@ -164,6 +162,7 @@ SPI-Consumer: java.util.ServiceLoader#load(java.lang.Class[tuwien.auto.calimero. 0.8.11 + tss/**/* tuwien/auto/calimero/**/* io/calimero/**/* From 8278817464ac5a7fb597e42d52dbca46745dc993 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 15 Sep 2024 22:22:58 +0200 Subject: [PATCH 7/8] cleanup Signed-off-by: Holger Friedrich --- .../knx/internal/config/BridgeConfiguration.java | 2 +- .../knx/internal/console/KNXCommandExtension.java | 2 +- .../binding/knx/internal/tpm/TpmInterface.java | 14 +++++++------- .../openhab/binding/knx/internal/tpm/TpmTest.java | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java index 6c54fa76037b4..d933a8ae14948 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/config/BridgeConfiguration.java @@ -70,7 +70,7 @@ protected String decrypt(String secret) { if (secret.startsWith(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX)) { try { logger.info("trying to access TPM module"); - return TpmInterface.TPM.deserializeAndDectryptSecret( + 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()); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java index 2bdd1bcab495f..87cd9653b9ef6 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/console/KNXCommandExtension.java @@ -89,7 +89,7 @@ public void execute(String[] args, Console console) { console.println(KNXBindingConstants.ENCYRPTED_PASSWORD_SERIALIZATION_PREFIX + p); // check if TPM can decrypt - String decrypted = TpmInterface.TPM.deserializeAndDectryptSecret(p); + String decrypted = TpmInterface.TPM.deserializeAndDecryptSecret(p); if (args[1].equals(decrypted)) { console.println("Password successfully recovered from encrypted representation"); } else { diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java index fc3a25267b9d6..eea5e2b5558b7 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/tpm/TpmInterface.java @@ -185,7 +185,7 @@ STANDARD_EK_POLICY, new TPMS_RSA_PARMS(new TPMT_SYM_DEF_OBJECT(TPM_ALG_ID.AES, 1 /** * @param secret plain text representation of password - * @return offline representation enctypted and bound to this TPM + * @return offline representation encrypted and bound to this TPM * @throws SecurityException */ public SecuredPassword encryptSecret(String secret) throws SecurityException { @@ -237,7 +237,7 @@ public String encryptAndSerializeSecret(String secret) throws SecurityException } /** - * @param secret offline repesentation of secret encrypted with this TPM. + * @param secret offline representation of secret encrypted with this TPM. * @return secret in clear text * @throws SecurityException */ @@ -292,7 +292,7 @@ public String decryptSecret(SecuredPassword secret) throws SecurityException { } } - public String deserializeAndDectryptSecret(String encryptedSecret) throws SecurityException { + public String deserializeAndDecryptSecret(String encryptedSecret) throws SecurityException { init(); Tpm tpm = this.tpm; // local copy to avoid Null warnings if (tpm == null) { @@ -313,7 +313,7 @@ public String deserializeAndDectryptSecret(String encryptedSecret) throws Securi } /** - * Fetch random numbers from the hardware random number genrator of the TPM. + * Fetch random numbers from the hardware random number generator of the TPM. * * @param bytesRequested * @return array of random numbers @@ -341,7 +341,7 @@ public String getTpmFirmwareVersion() throws SecurityException { int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.FIRMWARE_VERSION_1); int major = ret >> 16; int minor = ret & 0xffff; - return "" + major + "." + minor; + return major + "." + minor; } catch (TpmException e) { throw new SecurityException("TPM exception", e); } @@ -435,7 +435,7 @@ public String getTpmTcgRevision() throws SecurityException { } try { int ret = TpmHelpers.getTpmProperty(tpm, TPM_PT.REVISION); - return "" + (ret / 100) + "." + (ret % 100); + return (ret / 100) + "." + (ret % 100); } catch (TpmException e) { throw new SecurityException("TPM exception", e); } @@ -444,7 +444,7 @@ public String getTpmTcgRevision() throws SecurityException { /** * @return Return supported version of TPM standard is supported, typically * "1.2" or "2.0". - * @throws SecurityException + * @throws SecurityException in case of a TPM error */ public String getTpmVersion() throws SecurityException { init(); diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java index 7f83e567031e2..1edf1ada61c64 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/tpm/TpmTest.java @@ -27,7 +27,7 @@ * * @author Holger Friedrich - Initial contribution * - * This tests are intended to test the interface class for Trusted Platform Modules (TPM). + * These tests are intended to test the interface class for Trusted Platform Modules (TPM). * The tests will succeed, in case no TPM is available during test execution. * Some tests will be skipped on Windows, as some operations require admin access to the TPM. * @@ -62,7 +62,7 @@ void testTpmEncDec() { assertEquals(secret, TpmInterface.TPM.decryptSecret(sPwd)); } catch (SecurityException e) { - assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); + assertEquals("", e + " " + Objects.toString(e.getCause(), "")); } } } @@ -74,7 +74,7 @@ void testTpmRandom() { byte[] r = TpmInterface.TPM.getRandom(20); assertEquals(20, r.length); } catch (SecurityException e) { - assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); + assertEquals("", e + " " + Objects.toString(e.getCause(), "")); } } } From 61038dde9130d0b085b3e779943c0220713d3139 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Tue, 17 Sep 2024 23:35:32 +0200 Subject: [PATCH 8/8] Rework Signed-off-by: Holger Friedrich --- .../handler/KNXBridgeBaseThingHandler.java | 4 +-- .../profiles/KNXContactControlProfile.java | 3 +-- .../internal/profiles/KNXProfileFactory.java | 10 +++---- .../binding/knx/internal/dpt/DPTTest.java | 26 ++++++++++--------- .../knx/internal/itests/Back2BackTest.java | 4 +-- .../internal/security/KNXSecurityTest.java | 11 +++----- 6 files changed, 27 insertions(+), 31 deletions(-) diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java index a2ab8573d4e5f..1b0803d8baa17 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/KNXBridgeBaseThingHandler.java @@ -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); } @@ -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 diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java index 7685d45488221..7c1b2fffc770a 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java @@ -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); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java index 42ff673842862..0a9de806fa380 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java @@ -86,12 +86,10 @@ public Collection 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; } diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java index e58a8e881a0a2..84f2ac5313bff 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/dpt/DPTTest.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.knx.internal.dpt; +import static java.lang.Double.*; import static org.junit.jupiter.api.Assertions.*; import java.text.DecimalFormat; @@ -148,7 +149,7 @@ void testToDPT8ValueFromQuantityType() { void testToDPT9ValueFromQuantityType() { assertEquals("23.1", ValueEncoder.encode(new QuantityType<>("23.1 °C"), "9.001")); assertEquals(5.0, - Double.parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("41 °F"), "9.001")))); + parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("41 °F"), "9.001")))); assertEquals("1", ValueEncoder.encode(new QuantityType<>("274.15 K"), "9.001")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 K"), "9.002")); assertEquals("1", ValueEncoder.encode(new QuantityType<>("1000 mK"), "9.002")); @@ -163,8 +164,8 @@ void testToDPT9ValueFromQuantityType() { assertEquals("1", ValueEncoder.encode(new QuantityType<>("1 m/s"), "9.005")); assertTrue(Objects.requireNonNullElse(ValueEncoder.encode(new QuantityType<>("1.94 kn"), "9.005"), "") .startsWith("0.99")); - assertEquals(1.0, Double - .parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("3.6 km/h"), "9.005")))); + assertEquals(1.0, + parseDouble(Objects.requireNonNull(ValueEncoder.encode(new QuantityType<>("3.6 km/h"), "9.005")))); assertEquals("456", ValueEncoder.encode(new QuantityType<>("456 Pa"), "9.006")); assertEquals("70", ValueEncoder.encode(new QuantityType<>("70 %"), "9.007")); assertEquals("8", ValueEncoder.encode(new QuantityType<>("8 ppm"), "9.008")); @@ -457,9 +458,9 @@ public void dpt251White() { String[] parts = enc.split(" "); assertEquals(5, parts.length); int[] rgb = ColorUtil.hsbToRgb(hsbType); - assertEquals(rgb[0] * 100d / 255, Double.valueOf(parts[0].replace(',', '.')), 1); - assertEquals(rgb[1] * 100d / 255, Double.valueOf(parts[1].replace(',', '.')), 1); - assertEquals(rgb[2] * 100d / 255, Double.valueOf(parts[2].replace(',', '.')), 1); + assertEquals(rgb[0] * 100d / 255, valueOf(parts[0].replace(',', '.')), 1); + assertEquals(rgb[1] * 100d / 255, valueOf(parts[1].replace(',', '.')), 1); + assertEquals(rgb[2] * 100d / 255, valueOf(parts[2].replace(',', '.')), 1); } @Test @@ -479,9 +480,9 @@ public void dpt251Value() { String[] parts = enc.split(" "); assertEquals(5, parts.length); int[] rgb = ColorUtil.hsbToRgb(hsbType); - assertEquals(rgb[0] * 100d / 255, Double.valueOf(parts[0].replace(',', '.')), 1); - assertEquals(rgb[1] * 100d / 255, Double.valueOf(parts[1].replace(',', '.')), 1); - assertEquals(rgb[2] * 100d / 255, Double.valueOf(parts[2].replace(',', '.')), 1); + assertEquals(rgb[0] * 100d / 255, valueOf(parts[0].replace(',', '.')), 1); + assertEquals(rgb[1] * 100d / 255, valueOf(parts[1].replace(',', '.')), 1); + assertEquals(rgb[2] * 100d / 255, valueOf(parts[2].replace(',', '.')), 1); } // This test checks all our overrides for units. It allows to detect unnecessary overrides when we @@ -594,6 +595,7 @@ public void dpt242BackToBackTest(byte[] value) { // encoding will return a String in notation defined by Calimero: "(x,xxxx y,yyyy) YY,Y %" String result = ValueEncoder.encode(hsb, dpt); + assertNotNull(result); // for back to back test, compare numerical values to allow tolerances double dx = (((value[0] & 0xff) << 8) | (value[1] & 0xff)) / 65535.0; @@ -608,9 +610,9 @@ public void dpt242BackToBackTest(byte[] value) { Assertions.assertNotNull(stringx); Assertions.assertNotNull(stringy); Assertions.assertNotNull(stringY); - double rx = Double.parseDouble(stringx.replace(',', '.')); - double ry = Double.parseDouble(stringy.replace(',', '.')); - double rY = Double.parseDouble(stringY.replace(',', '.')); + double rx = parseDouble(stringx.replace(',', '.')); + double ry = parseDouble(stringy.replace(',', '.')); + double rY = parseDouble(stringY.replace(',', '.')); final double tolerance = 0.001; if ((Math.abs(dx - rx) > tolerance) || (Math.abs(dy - ry) > tolerance) diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java index 033aaf756ba28..bbd4acc561a13 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/itests/Back2BackTest.java @@ -769,7 +769,7 @@ void testDpt18() { helper("18.001", new byte[] { 42 }, new DecimalType(42)); helper("18.001", new byte[] { 63 }, new DecimalType(63)); // scene, learn += 0x80 - helper("18.001", new byte[] { (byte) (0x80 + 0) }, new DecimalType(0x80)); + helper("18.001", new byte[] { (byte) (0x80) }, new DecimalType(0x80)); helper("18.001", new byte[] { (byte) (0x80 + 42) }, new DecimalType(0x80 + 42)); helper("18.001", new byte[] { (byte) (0x80 + 63) }, new DecimalType(0x80 + 63)); } @@ -786,7 +786,7 @@ void testDpt19() { new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }); helper("19.001", new byte[] { (byte) (2019 - 1900), 7, 15, 17, 30, 0, (byte) 0x24, (byte) 0x00 }, new DateTimeType("2019-07-15T17:30:00"), new byte[0], new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }); - // TODO add tests for incompletly filled frames (e.g. containing only date or time) + // TODO add tests for incompletely filled frames (e.g. containing only date or time) } @Test diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/security/KNXSecurityTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/security/KNXSecurityTest.java index f871bdb29b39b..c38d79bd02f67 100644 --- a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/security/KNXSecurityTest.java +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/security/KNXSecurityTest.java @@ -106,18 +106,15 @@ public void testSecurityHelperEmpty() { Security openhabSecurity = Security.newSecurity(); openhabSecurity.useKeyring(keys, password); - assertThrows(KnxSecureException.class, () -> { - KNXBridgeBaseThingHandler.secHelperReadBackboneKey(Optional.empty(), passwordString); - }); + 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); - }); + assertThrows(KnxSecureException.class, () -> KNXBridgeBaseThingHandler + .secHelperReadTunnelConfig(Optional.empty(), passwordString, secureTunnelSourceAddr)); assertTrue(KNXBridgeBaseThingHandler .secHelperReadTunnelConfig(Optional.ofNullable(keys), passwordString, secureTunnelSourceAddr) .isEmpty());