package com.bwssystems.HABridge; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.util.Base64; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.UUID; import java.util.Map.Entry; 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.bwssystems.HABridge.api.hue.HueError; import com.bwssystems.HABridge.api.hue.HueErrorResponse; import com.bwssystems.HABridge.api.hue.WhitelistEntry; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import spark.Request; public class BridgeSecurity { private static final Logger log = LoggerFactory.getLogger(BridgeSecurity.class); private static final String USER_SESSION_ID = "user"; private static final String DEPRACATED_INTERNAL_USER = "thehabridgeuser"; private static final String TEST_USER_TYPE = "test_ha_bridge"; private static final byte[] SALT = { (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12, (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12, }; private char[] habridgeKey; private String execGarden; private BridgeSecurityDescriptor securityDescriptor; private boolean settingsChanged; public BridgeSecurity(char[] theKey, String theExecGarden) { habridgeKey = theKey; execGarden = theExecGarden; securityDescriptor = null; settingsChanged = false; } public void setSecurityData(String theData) { 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(); } if(anError != null) 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 String setPassword(User aUser) throws IOException { String error = null; if(aUser != null) { error = aUser.validate(); if(error == null) { if(securityDescriptor.getUsers() != null) { User theUser = securityDescriptor.getUsers().get(aUser.getUsername()); if(theUser != null) { theUser.setPassword(aUser.getPassword()); theUser.setPassword2(null); settingsChanged = true; } else error = "User not found"; } else error = "User not found"; } } else error = "invalid user object given"; return error; } public String addUser(User aUser) throws IOException { String error = null; if(aUser != null) { error = aUser.validate(); if(error == null) { if(securityDescriptor.getUsers() == null) securityDescriptor.setUsers(new HashMap<String, User>()); if(securityDescriptor.getUsers().get(aUser.getUsername()) == null) { securityDescriptor.getUsers().put(aUser.getUsername(), aUser); settingsChanged = true; } else error = "Invalid request"; } } else error = "invalid user object given"; return error; } public String delUser(User aUser) throws IOException { String error = null; if(aUser != null) { if(securityDescriptor.getUsers() != null) { if(securityDescriptor.getUsers().get(aUser.getUsername()) != null) { securityDescriptor.getUsers().remove(aUser.getUsername()); settingsChanged = true; } else error = "User not found"; } } else error = "invalid user object given"; return error; } public String getExecGarden() { return execGarden; } public void setUseLinkButton(boolean useThis) { securityDescriptor.setUseLinkButton(useThis); settingsChanged = true; } public boolean isSecureHueApi() { return securityDescriptor.isSecureHueApi(); } public void setSecureHueApi(boolean theState) { securityDescriptor.setSecureHueApi(theState); } public SecurityInfo getSecurityInfo() { SecurityInfo theInfo = new SecurityInfo(); theInfo.setUseLinkButton(isUseLinkButton()); theInfo.setSecureHueApi(isSecureHueApi()); theInfo.setSecure(isSecure()); return theInfo; } public LoginResult validatePassword(User targetUser) throws IOException { LoginResult result = new LoginResult(); if(targetUser != null && targetUser.getUsername() != null) { if(securityDescriptor.getUsers() != null && securityDescriptor.getUsers().get(targetUser.getUsername()) != null) { User theUser = securityDescriptor.getUsers().get(targetUser.getUsername()); if(theUser.getPassword() != null) { theUser.setPassword2(targetUser.getPassword()); if(theUser.validatePassword()) { theUser.setPassword2(null); result.setUser(targetUser); } else result.setError("user or password not correct"); } else { result.setError("input password is not set...."); } } else result.setError("user or password not correct"); } else result.setError("input user not given"); return result; } public boolean isSecure() { return securityDescriptor.isSecure(); } public boolean isSettingsChanged() { return settingsChanged; } public void setSettingsChanged(boolean settingsChanged) { this.settingsChanged = settingsChanged; } public HueError[] validateWhitelistUser(String aUser, String userDescription, boolean strict) { String validUser = null; boolean found = false; if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null") && !aUser.equalsIgnoreCase("")) { if (securityDescriptor.getWhitelist() != null) { Set<String> theUserIds = securityDescriptor.getWhitelist().keySet(); Iterator<String> userIterator = theUserIds.iterator(); while (userIterator.hasNext()) { validUser = userIterator.next(); if (validUser.equals(aUser)) found = true; } } } if(!found && !strict) { newWhitelistUser(aUser, userDescription); found = true; } if (!found) { return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors(); } Object anUser = securityDescriptor.getWhitelist().remove(DEPRACATED_INTERNAL_USER); if(anUser != null) setSettingsChanged(true); return null; } public void newWhitelistUser(String aUser, String userDescription) { if(aUser.equals(DEPRACATED_INTERNAL_USER)) return; if (securityDescriptor.getWhitelist() == null) { securityDescriptor.setWhitelist(new HashMap<>()); } if(userDescription == null) userDescription = "auto insert user"; securityDescriptor.getWhitelist().put(aUser, WhitelistEntry.createEntry(userDescription)); setSettingsChanged(true); } public String createWhitelistUser(String userDescription) { String aUser = getNewUserID(); newWhitelistUser(aUser, userDescription); return aUser; } public void convertWhitelist(Map<String, WhitelistEntry> whitelist) { securityDescriptor.setWhitelist(whitelist); } private String getNewUserID() { UUID uid = UUID.randomUUID(); StringTokenizer st = new StringTokenizer(uid.toString(), "-"); String newUser = ""; while (st.hasMoreTokens()) { newUser = newUser + st.nextToken(); } return newUser; } public void removeTestUsers() { if (securityDescriptor.getWhitelist() != null) { Object anUser = securityDescriptor.getWhitelist().remove(DEPRACATED_INTERNAL_USER); if(anUser != null) setSettingsChanged(true); Iterator<Entry<String, WhitelistEntry>> it = securityDescriptor.getWhitelist().entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, WhitelistEntry> pair = it.next(); it.remove(); // avoids a ConcurrentModificationException if(pair.getValue().getName().equals(TEST_USER_TYPE)) { securityDescriptor.getWhitelist().remove(pair.getKey()); setSettingsChanged(true); } } } } 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); } public void addAuthenticatedUser(Request request, User u) { request.session().attribute(USER_SESSION_ID, u); } public void removeAuthenticatedUser(Request request) { request.session().removeAttribute(USER_SESSION_ID); } public User getAuthenticatedUser(Request request) { User theUser = request.session().attribute(USER_SESSION_ID); if(theUser == null) { String authHeader = request.headers("Authorization"); if(authHeader != null) { byte[] authData; try { authData = base64Decode(authHeader.substring(6)); } catch (IOException e1) { // TODO Auto-generated catch block return theUser; } String[] credentials = new String(authData).split(":"); String username = credentials[0]; String password = credentials[1]; theUser = new User(); theUser.setUsername(username); theUser.setPassword(password); LoginResult theResult = null; try { theResult = validatePassword(theUser); } catch (IOException e) { // TODO Auto-generated catch block return null; } if(theResult != null && theResult.getError() == null) { addAuthenticatedUser(request, theUser); } } } return theUser; } }