Skip to content

Commit

Permalink
#55 replaced the password with a fixed one
Browse files Browse the repository at this point in the history
The changeable SHC system password for the keystore is replaced by a static string in the code.
The keyStore name is now based on SHC ipAddress to support multiple SmartHomeControllers.

Signed-off-by: Gerd Zanker <gerd.zanker@web.de>
  • Loading branch information
GerdZanker committed Jan 16, 2021
1 parent ab17362 commit 0136cf0
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,12 @@ public boolean doPairing() throws InterruptedException {
ContentResponse contentResponse;
try {
String publicCert = getCertFromSslContextFactory();
logger.trace("Pairing this Client '{}' with SHC {}", BoschSslUtil.getBoschSHCId(), ipAddress);
logger.trace("Pairing with SHC {}", ipAddress);

// JSON Rest content
Map<String, String> items = new HashMap<>();
items.put("@type", "client");
items.put("id", BoschSslUtil.getBoschSHCId()); // Client Id contains the unique OpenHab instance Id
items.put("id", BoschSslUtil.getBoschShcClientId()); // Client Id contains the unique OpenHab instance Id
items.put("name", "oss_OpenHAB_Binding"); // Client name according to
// https://github.com/BoschSmartHome/bosch-shc-api-docs#terms-and-conditions
items.put("primaryRole", "ROLE_RESTRICTED_CLIENT");
Expand Down Expand Up @@ -240,7 +240,8 @@ public <TContent> TContent sendRequest(Request request, Class<TContent> response
}

private String getCertFromSslContextFactory() throws KeyStoreException, CertificateEncodingException {
Certificate cert = this.getSslContextFactory().getKeyStore().getCertificate(BoschSslUtil.getBoschSHCId());
Certificate cert = this.getSslContextFactory().getKeyStore()
.getCertificate(BoschSslUtil.getBoschShcServerId(ipAddress));
return Base64.getEncoder().encodeToString(cert.getEncoded());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void initialize() {
SslContextFactory factory;
try {
// prepare SSL key and certificates
factory = new BoschSslUtil(config.password).getSslContextFactory();
factory = new BoschSslUtil(config.ipAddress).getSslContextFactory();
} catch (PairingFailedException e) {
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"@text/offline.conf-error-ssl");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,50 @@
public class BoschSslUtil {

private static final String OSS_OPENHAB_BINDING = "oss_openhab_binding";
private static final String KEYSTORE_PASSWORD = "openhab";

private final Logger logger = LoggerFactory.getLogger(BoschSslUtil.class);

private final String boschShcServerID;
private final String keystorePath;
private final String keystorePassword;

public static String getBoschSHCId() {
/**
* Returns unique ID for this Bosch SmartHomeController client.
*
* @return unique string containing the openhab UUID.
*/
public static String getBoschShcClientId() {
return OSS_OPENHAB_BINDING + "_" + InstanceUUID.get();
}

public static String getKeystorePath() {
return Paths.get(OpenHAB.getUserDataFolder(), "etc", getBoschSHCId() + ".jks").toString();
/**
* Returns ID for passed Bosch SmartHomeController server.
*
* @param shcServerID the ip address of the SHC server
* @return unique string containing the server id
*/
public static String getBoschShcServerId(String shcServerID) {
return OSS_OPENHAB_BINDING + "_" + shcServerID;
}

public BoschSslUtil(String keystorePassword) {
/**
* Constructor
*
* @param boschShcServerID the ip address of the SHC server
*/
public BoschSslUtil(String boschShcServerID) {
this.boschShcServerID = boschShcServerID;
this.keystorePath = getKeystorePath();
this.keystorePassword = keystorePassword;
}

/// Returns unique ID for Bosch SmartHomeController server.
public String getBoschShcServerId() {
return BoschSslUtil.getBoschShcServerId(boschShcServerID);
}

/// Returns the unique keystore for each Bosch Smart Home Controller server.
public String getKeystorePath() {
return Paths.get(OpenHAB.getUserDataFolder(), "etc", getBoschShcServerId() + ".jks").toString();
}

public SslContextFactory getSslContextFactory() throws PairingFailedException {
Expand All @@ -87,7 +114,7 @@ public SslContextFactory getSslContextFactory() throws PairingFailedException {
// Keystore for managing the keys that have been used to pair with the SHC
// https://www.eclipse.org/jetty/javadoc/9.4.12.v20180830/org/eclipse/jetty/util/ssl/SslContextFactory.html
sslContextFactory.setKeyStorePath(keystorePath);
sslContextFactory.setKeyStorePassword(keystorePassword);
sslContextFactory.setKeyStorePassword(KEYSTORE_PASSWORD);

// Bosch is using a self signed certificate
sslContextFactory.setTrustAll(true);
Expand All @@ -104,12 +131,12 @@ public KeyStore getKeyStoreAndCreateIfNecessary() throws PairingFailedException
if (!file.exists()) {
// create new keystore
logger.info("Creating new keystore {} because it doesn't exist.", keystorePath);
return createKeyStore(keystorePath, keystorePassword);
return createKeyStore(keystorePath);
} else {
// load keystore as a first check
KeyStore keyStore = KeyStore.getInstance("JKS");
try (FileInputStream streamKeystore = new FileInputStream(file)) {
keyStore.load(streamKeystore, keystorePassword.toCharArray());
try (FileInputStream keystoreStream = new FileInputStream(file)) {
keyStore.load(keystoreStream, KEYSTORE_PASSWORD.toCharArray());
}
logger.debug("Using existing keystore {}", keystorePath);
return keyStore;
Expand All @@ -123,7 +150,7 @@ public KeyStore getKeyStoreAndCreateIfNecessary() throws PairingFailedException

private X509Certificate generateClientCertificate(KeyPair keyPair)
throws GeneralSecurityException, OperatorCreationException {
final String dirName = "CN=" + getBoschSHCId() + ", O=openHAB, L=None, ST=None, C=None";
final String dirName = "CN=" + getBoschShcClientId() + ", O=openHAB, L=None, ST=None, C=None";
logger.debug("Creating a new self signed certificate: {}", dirName);
final Instant now = Instant.now();
final Date notBefore = Date.from(now);
Expand All @@ -141,7 +168,7 @@ private X509Certificate generateClientCertificate(KeyPair keyPair)
.getCertificate(certificateBuilder.build(contentSigner));
}

private KeyStore createKeyStore(String keystore, String keystorePassword)
private KeyStore createKeyStore(String keystore)
throws IOException, OperatorCreationException, GeneralSecurityException {
// create a new keystore
KeyStore keyStore = KeyStore.getInstance("JKS");
Expand All @@ -161,8 +188,8 @@ private KeyStore createKeyStore(String keystore, String keystorePassword)

X509Certificate cert = generateClientCertificate(keyPair);

logger.debug("Adding keypair and self signed certificate to keystore");
keyStore.setKeyEntry(getBoschSHCId(), keyPair.getPrivate(), keystorePassword.toCharArray(),
logger.debug("Adding keyEntry '{}' with self signed certificate to keystore", getBoschShcServerId());
keyStore.setKeyEntry(getBoschShcServerId(), keyPair.getPrivate(), KEYSTORE_PASSWORD.toCharArray(),
new Certificate[] { cert });

// add Bosch Certs
Expand All @@ -183,8 +210,8 @@ private KeyStore createKeyStore(String keystore, String keystorePassword)
}

logger.debug("Storing keystore to file {}", keystore);
try (FileOutputStream streamKeystore = new FileOutputStream(keystore)) {
keyStore.store(streamKeystore, keystorePassword.toCharArray());
try (FileOutputStream keystoreStream = new FileOutputStream(keystore)) {
keyStore.store(keystoreStream, KEYSTORE_PASSWORD.toCharArray());
}

return keyStore;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import static org.junit.jupiter.api.Assertions.*;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.util.ssl.SslContextFactory;
Expand All @@ -31,6 +33,7 @@
@NonNullByDefault
class BoschHttpClientTest {

@Nullable
private BoschHttpClient httpClient;

@BeforeAll
Expand All @@ -40,8 +43,9 @@ static void beforeAll() {

@BeforeEach
void beforeEach() throws PairingFailedException {
SslContextFactory sslFactory = new BoschSslUtil("dummy").getSslContextFactory();
SslContextFactory sslFactory = new BoschSslUtil("127.0.0.1").getSslContextFactory();
httpClient = new BoschHttpClient("127.0.0.1", "dummy", sslFactory);
assertNotNull(httpClient);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,44 +50,53 @@ public static void prepareTempFolderForKeyStore() {
}

@Test
void getBoschSHCId() {
void getBoschShcClientId() {
// OpenSource Bosch SHC clients needs start with oss
assertTrue(BoschSslUtil.getBoschSHCId().startsWith("oss"));
assertTrue(BoschSslUtil.getBoschShcClientId().startsWith("oss"));
}

@Test
void getBoschShcServerId() {
// OpenSource Bosch SHC clients needs start with oss
assertTrue(BoschSslUtil.getBoschShcServerId("localhost").startsWith("oss"));
assertTrue(BoschSslUtil.getBoschShcServerId("localhost").contains("localhost"));
}

@Test
void getKeystorePath() {
assertTrue(BoschSslUtil.getKeystorePath().endsWith(".jks"));
BoschSslUtil sslUtil = new BoschSslUtil("123.45.67.89");
assertTrue(sslUtil.getKeystorePath().endsWith(".jks"));
}

/**
* Test if the keyStore can be created if it doesn't exist.
*/
@Test
void keyStoreAndFactory() throws PairingFailedException {
BoschSslUtil sslUtil1 = new BoschSslUtil("127.0.0.1");

// remote old, existing jks
File keyStoreFile = new File(BoschSslUtil.getKeystorePath());
File keyStoreFile = new File(sslUtil1.getKeystorePath());
keyStoreFile.deleteOnExit();
if (keyStoreFile.exists()) {
assertTrue(keyStoreFile.delete());
}

assertFalse(keyStoreFile.exists());

BoschSslUtil sslUtil = new BoschSslUtil("pwd");
BoschSslUtil sslUtil2 = new BoschSslUtil("127.0.0.1");
// fist call where keystore is created
KeyStore keyStore = sslUtil.getKeyStoreAndCreateIfNecessary();
KeyStore keyStore = sslUtil2.getKeyStoreAndCreateIfNecessary();
assertNotNull(keyStore);

assertTrue(keyStoreFile.exists());

// second call where keystore is reopened
KeyStore keyStore2 = sslUtil.getKeyStoreAndCreateIfNecessary();
KeyStore keyStore2 = sslUtil2.getKeyStoreAndCreateIfNecessary();
assertNotNull(keyStore2);

// basic test if a SSL factory instance can be created
SslContextFactory factory = sslUtil.getSslContextFactory();
SslContextFactory factory = sslUtil2.getSslContextFactory();
assertNotNull(factory);
}
}

0 comments on commit 0136cf0

Please sign in to comment.