Continue security implementation

This commit is contained in:
Admin
2017-03-22 16:35:19 -05:00
parent a2b652907f
commit b000215b26
12 changed files with 310 additions and 83 deletions

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>4.3.1Secure-a</version>
<version>4.3.1Secure-b</version>
<packaging>jar</packaging>
<name>HA Bridge</name>

View File

@@ -3,6 +3,7 @@ package com.bwssystems.HABridge;
public class BridgeControlDescriptor {
private boolean reinit;
private boolean stop;
private boolean linkButton;
public BridgeControlDescriptor() {
super();
@@ -22,4 +23,12 @@ public class BridgeControlDescriptor {
public void setStop(boolean stop) {
this.stop = stop;
}
public boolean isLinkButton() {
return linkButton;
}
public void setLinkButton(boolean linkButton) {
this.linkButton = linkButton;
}
}

View File

@@ -0,0 +1,121 @@
package com.bwssystems.HABridge;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
public class BridgeSecurity {
private static final Logger log = LoggerFactory.getLogger(BridgeSecurity.class);
private char[] habridgeKey;
private static final byte[] SALT = {
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
};
private BridgeSecurityDescriptor securityDescriptor;
public BridgeSecurity(char[] theKey, String theData) {
habridgeKey = theKey;
securityDescriptor = null;
String anError = null;
if(theData != null && !theData.isEmpty()) {
try {
securityDescriptor = new Gson().fromJson(decrypt(theData), BridgeSecurityDescriptor.class);
} catch (JsonSyntaxException e) {
anError = e.getMessage();
} catch (GeneralSecurityException e) {
anError = e.getMessage();
} catch (IOException e) {
anError = e.getMessage();
}
log.warn("Cound not get security data, using default security (none): " + anError);
}
if(theData == null || anError != null) {
securityDescriptor = new BridgeSecurityDescriptor();
}
}
public String getSecurityDescriptorData() throws UnsupportedEncodingException, GeneralSecurityException {
return encrypt(new Gson().toJson(securityDescriptor));
}
public boolean isUseLinkButton() {
return securityDescriptor.isUseLinkButton();
}
public void setPassword(String aPassword) throws IOException {
if(aPassword != null) {
securityDescriptor.setUiPassword(String.valueOf(base64Decode(aPassword)));
securityDescriptor.setPasswordSet(true);
} else {
securityDescriptor.setUiPassword(null);
securityDescriptor.setPasswordSet(false);
}
securityDescriptor.setSettingsChanged(true);
}
public void setExecGarden(String theGarden) {
securityDescriptor.setExecGarden(theGarden);
securityDescriptor.setSettingsChanged(true);
}
public String getExecGarden() {
return securityDescriptor.getExecGarden();
}
public void setUseLinkButton(boolean useThis) {
securityDescriptor.setUseLinkButton(useThis);
securityDescriptor.setSettingsChanged(true);
}
public boolean validatePassword(String targetPassword) throws IOException {
if(securityDescriptor.isPasswordSet()) {
if(securityDescriptor.getUiPassword().equals(String.valueOf(base64Decode(targetPassword))))
return true;
} else {
log.warn("validating password when password is not set....");
return true;
}
return false;
}
public boolean isSecure() {
return securityDescriptor.isPasswordSet();
}
private String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
}
private static String base64Encode(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
private String decrypt(String property) throws GeneralSecurityException, IOException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(habridgeKey));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
}
private static byte[] base64Decode(String property) throws IOException {
return Base64.getDecoder().decode(property);
}
}

View File

@@ -1,19 +1,17 @@
package com.bwssystems.HABridge;
public class BridgeSecurityDescriptor {
private boolean linkButton;
private String uiPassword;
private boolean passwordSet;
private boolean useLinkButton;
private String execGarden;
private boolean settingsChanged;
public BridgeSecurityDescriptor() {
super();
}
public boolean isLinkButton() {
return linkButton;
}
public void setLinkButton(boolean linkButton) {
this.linkButton = linkButton;
this.setUiPassword(null);
this.setPasswordSet(false);
this.setUseLinkButton(false);
}
public String getUiPassword() {
@@ -23,4 +21,36 @@ public class BridgeSecurityDescriptor {
public void setUiPassword(String uiPassword) {
this.uiPassword = uiPassword;
}
public boolean isPasswordSet() {
return passwordSet;
}
public void setPasswordSet(boolean passwordSet) {
this.passwordSet = passwordSet;
}
public boolean isUseLinkButton() {
return useLinkButton;
}
public void setUseLinkButton(boolean useLinkButton) {
this.useLinkButton = useLinkButton;
}
public String getExecGarden() {
return execGarden;
}
public void setExecGarden(String execGarden) {
this.execGarden = execGarden;
}
public boolean isSettingsChanged() {
return settingsChanged;
}
public void setSettingsChanged(boolean settingsChanged) {
this.settingsChanged = settingsChanged;
}
}

View File

@@ -26,14 +26,14 @@ public class BridgeSettings extends BackupHandler {
private static final Logger log = LoggerFactory.getLogger(BridgeSettings.class);
private BridgeSettingsDescriptor theBridgeSettings;
private BridgeControlDescriptor bridgeControl;
private BridgeSecurityDescriptor bridgeSecurity;
private BridgeSecurity bridgeSecurity;
public BridgeSettings() {
super();
bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor();
bridgeSecurity = new BridgeSecurityDescriptor();
String ipV6Stack = System.getProperty("ipV6Stack");
bridgeSecurity = null;
String ipV6Stack = System.getProperty("ipV6Stack");
if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) {
System.setProperty("java.net.preferIPv4Stack" , "true");
}
@@ -45,7 +45,7 @@ public class BridgeSettings extends BackupHandler {
public BridgeSettingsDescriptor getBridgeSettingsDescriptor() {
return theBridgeSettings;
}
public BridgeSecurityDescriptor getBridgeSecurity() {
public BridgeSecurity getBridgeSecurity() {
return bridgeSecurity;
}
public void buildSettings() {
@@ -180,6 +180,11 @@ public class BridgeSettings extends BackupHandler {
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
setupInternalTestUser();
String theKey = System.getProperty("security.key");
if(theKey == null)
theKey = "";
bridgeSecurity = new BridgeSecurity(theKey.toCharArray(), theBridgeSettings.getSecurityData());
}
public void loadConfig() {

View File

@@ -51,8 +51,8 @@ public class BridgeSettingsDescriptor {
private boolean domoticzconfigured;
private IpList somfyaddress;
private boolean somfyconfigured;
private boolean lifxconfigured;
private String securityData;
public BridgeSettingsDescriptor() {
super();
@@ -295,6 +295,12 @@ public class BridgeSettingsDescriptor {
public void setLifxconfigured(boolean lifxconfigured) {
this.lifxconfigured = lifxconfigured;
}
public String getSecurityData() {
return securityData;
}
public void setSecurityData(String securityData) {
this.securityData = securityData;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;

View File

@@ -12,6 +12,7 @@ import java.net.MulticastSocket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Timer;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
@@ -121,6 +122,9 @@ public class SystemControl {
// http://ip_address:port/system/presslinkbutton which sets the link button for device registration
put(SYSTEM_CONTEXT + "/presslinkbutton", "application/json", (request, response) -> {
log.info("Link button pressed....");
bridgeSettings.getBridgeControl().setLinkButton(true);
Timer theTimer = new Timer();
theTimer.schedule(new LinkButtonPressed(bridgeSettings.getBridgeControl(), theTimer), 30000);
return null;
}, new JsonTransformer());

View File

@@ -576,7 +576,7 @@ public class HueMulator {
private String basicListHandler(String type, String userId, String requestIp) {
log.debug("hue " + type + " list requested: " + userId + " from " + requestIp);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors != null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
@@ -590,7 +590,7 @@ public class HueMulator {
log.debug("hue group list requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
Map<String, GroupResponse> groupResponseMap = null;
theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
@@ -607,7 +607,7 @@ public class HueMulator {
private Object groupsIdHandler(String groupId, String userId, String requestIp) {
log.debug("hue group id: <" + groupId + "> requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
@@ -632,7 +632,7 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue lights list requested: " + userId + " from " + requestIp);
log.debug("hue lights list requested: " + userId + " from " + requestIp);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
@@ -642,28 +642,30 @@ public class HueMulator {
deviceResponseMap = new HashMap<String, DeviceResponse>();
for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = null;
if (device.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
CallItem[] callItems = null;
try {
if(device.getOnUrl() != null)
callItems = aGsonHandler.fromJson(device.getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
log.warn("Could not decode Json for url items to get Hue state for device: " + device.getName());
callItems = null;
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if((callItems[i].getType() != null && callItems[i].getType().equals(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) ||
(callItems[i].getItem().getAsString().contains("hueName"))) {
deviceResponse = myHueHome.getHueDeviceInfo(callItems[i], device);
i = callItems.length;
if(!device.isInactive()) {
if (device.containsType(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) {
CallItem[] callItems = null;
try {
if(device.getOnUrl() != null)
callItems = aGsonHandler.fromJson(device.getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
log.warn("Could not decode Json for url items to get Hue state for device: " + device.getName());
callItems = null;
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if((callItems[i].getType() != null && callItems[i].getType().equals(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) ||
(callItems[i].getItem().getAsString().contains("hueName"))) {
deviceResponse = myHueHome.getHueDeviceInfo(callItems[i], device);
i = callItems.length;
}
}
}
if (deviceResponse == null)
deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse);
}
if (deviceResponse == null)
deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse);
}
}
@@ -677,43 +679,51 @@ public class HueMulator {
UserCreateRequest aNewUser = null;
String newUser = null;
String aDeviceType = null;
boolean toContinue = false;
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested: " + body + " from " + ipAddress);
log.debug("hue api user create requested: " + body + " from " + ipAddress);
if (body != null && !body.isEmpty()) {
try {
aNewUser = aGsonHandler.fromJson(body, UserCreateRequest.class);
} catch (Exception e) {
log.warn("Could not add user. Request garbled: " + body);
return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/",
"Could not add user.", null, null, null).getTheErrors(), HueError[].class);
}
newUser = aNewUser.getUsername();
aDeviceType = aNewUser.getDevicetype();
}
if (aDeviceType == null)
aDeviceType = "<not given>";
if (newUser == null) {
newUser = bridgeSettings.createWhitelistUser(aDeviceType);
}
else {
bridgeSettings.validateWhitelistUser(newUser, aDeviceType, false);
}
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : ""));
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser + (followingSlash ? " /api/ called" : ""));
return "[{\"success\":{\"username\":\"" + newUser + "\"}}]";
if(bridgeSettingMaster.getBridgeSecurity().isUseLinkButton() && bridgeSettingMaster.getBridgeControl().isLinkButton())
toContinue = true;
else if(!bridgeSettingMaster.getBridgeSecurity().isUseLinkButton())
toContinue = true;
if(toContinue) {
log.debug("hue api user create requested: " + body + " from " + ipAddress);
if (body != null && !body.isEmpty()) {
try {
aNewUser = aGsonHandler.fromJson(body, UserCreateRequest.class);
} catch (Exception e) {
log.warn("Could not add user. Request garbled: " + body);
return aGsonHandler.toJson(HueErrorResponse.createResponse("2", "/",
"Could not add user.", null, null, null).getTheErrors(), HueError[].class);
}
newUser = aNewUser.getUsername();
aDeviceType = aNewUser.getDevicetype();
}
if (aDeviceType == null)
aDeviceType = "<not given>";
if (newUser == null) {
newUser = bridgeSettings.createWhitelistUser(aDeviceType);
}
else {
bridgeSettings.validateWhitelistUser(newUser, aDeviceType, false);
}
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : ""));
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser + (followingSlash ? " /api/ called" : ""));
return "[{\"success\":{\"username\":\"" + newUser + "\"}}]";
}
return aGsonHandler.toJson(HueErrorResponse.createResponse("1", "/api/", "unauthorized user", null, null, null).getTheErrors());
}
private Object getConfig(String userId, String ipAddress) {

View File

@@ -18,16 +18,20 @@ import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class TCPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(TCPHome.class);
private byte[] sendData;
private Map<String, Socket> theSockets;
private Gson aGsonHandler;
public TCPHome(BridgeSettingsDescriptor bridgeSettings) {
@@ -41,8 +45,12 @@ public class TCPHome implements Home {
Socket dataSendSocket = null;
log.debug("executing HUE api request to TCP: " + anItem.getItem().getAsString());
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.startsWith("tcp://")) {
String intermediate = theUrl.substring(theUrl.indexOf("://") + 3);
if(theUrl != null && !theUrl.isEmpty () && theUrl.contains("tcp://")) {
if(!theUrl.startsWith("{\"tcpDevice\""))
theUrl = "{\"tcpDevice\":\"" + theUrl + "\"}";
TcpDevice theDevice = aGsonHandler.fromJson(theUrl, TcpDevice.class);
String intermediate = theDevice.getTcpDevice().substring(theDevice.getTcpDevice().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
@@ -58,14 +66,15 @@ public class TCPHome implements Home {
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
// noop
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot connect, Unknown Host", null, "/lights/" + device.getId(), null).getTheErrors());
}
try {
dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
theSockets.put(hostPortion, dataSendSocket);
if(theDevice.isPersistent())
theSockets.put(hostPortion, dataSendSocket);
} catch (Exception e) {
// noop
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot connect, Socket Creation issue", null, "/lights/" + device.getId(), null).getTheErrors());
}
}
@@ -80,18 +89,32 @@ public class TCPHome implements Home {
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes();
}
try {
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
} catch (Exception e) {
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
} catch (IOException e) {
log.warn("Could not send data to TCP socket <<<" + e.getMessage() + ">>>, closing socket: " + theUrl);
try {
dataSendSocket.close();
} catch (IOException e1) {
// noop
}
theSockets.remove(hostPortion);
dataSendSocket = null;
if(theDevice.isPersistent())
theSockets.remove(hostPortion);
return aGsonHandler.toJson(HueErrorResponse.createResponse("901", null, "Cannot send data", null, "/lights/" + device.getId(), null).getTheErrors());
}
if(!theDevice.isPersistent()) {
try {
if(dataSendSocket != null)
dataSendSocket.close();
} catch (IOException e1) {
// noop
}
dataSendSocket = null;
}
} else
log.warn("Tcp Call to be presented as tcp://<ip_address>:<port>/payload, format of request unknown: " + theUrl);
@@ -102,6 +125,7 @@ public class TCPHome implements Home {
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
log.info("TCP Home created.");
theSockets = new HashMap<String, Socket>();
aGsonHandler = new GsonBuilder().create();
return this;
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.plugins.tcp;
public class TcpDevice {
private String tcpDevice;
private boolean persistent;
public String getTcpDevice() {
return tcpDevice;
}
public void setTcpDevice(String tcpDevice) {
this.tcpDevice = tcpDevice;
}
public boolean isPersistent() {
return persistent;
}
public void setPersistent(boolean persistent) {
this.persistent = persistent;
}
}

View File

@@ -1,4 +1,4 @@
package com.bwssystems.hass.test;
package com.bwssystems.domoticz.test;
import java.util.Iterator;

View File

@@ -1,4 +1,4 @@
package com.bwssystems.hass.test;
package com.bwssystems.domoticz.test;
import org.junit.Assert;
import org.junit.Test;