From 80e64730f06324f0e550bc7d7bd19424fb6aadb6 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 6 Aug 2023 22:23:28 +0200 Subject: [PATCH] single instance Signed-off-by: Holger Friedrich --- .../internal/config/BridgeConfiguration.java | 13 +- .../internal/console/KNXCommandExtension.java | 20 +-- .../knx/internal/tpm/TpmInterface.java | 139 +++++++++++++++--- .../binding/knx/internal/tpm/TpmTest.java | 64 +++----- 4 files changed, 150 insertions(+), 86 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 67418ca70ae..b156a16422c 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 @@ public class BridgeConfiguration { 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 a76c3bd8b48..89a14984f52 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 class KNXCommandExtension extends AbstractConsoleCommandExtension impleme } 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 class KNXCommandExtension extends AbstractConsoleCommandExtension impleme } 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 4389898cfad..6524d1c64e7 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 @@ import tss.tpm.TPM_SE; * @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 class TpmInterface { * * @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); + 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"); + } + } + } + + 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 class TpmInterface { * * @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 @@ public class TpmInterface { 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 @@ public class TpmInterface { * @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 class TpmInterface { } 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 class TpmInterface { * @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 class TpmInterface { // 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 class TpmInterface { } 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 class TpmInterface { * @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 @@ public class TpmInterface { * @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 class TpmInterface { * @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 class TpmInterface { * @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 class TpmInterface { * @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 class TpmInterface { * @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 class TpmInterface { * @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 99d3e1c97c3..7574a884e7d 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 @@ class TpmTest { @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); + sPwd = TpmInterface.TPM.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)); - } + assertEquals(secret, TpmInterface.TPM.decryptSecret(sPwd)); } catch (SecurityException e) { assertEquals("", e.toString() + " " + Objects.toString(e.getCause(), "")); } @@ -95,17 +69,13 @@ class TpmTest { @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(), "")); + } } } }