package com.mobilesorcery.sdk.builder.java; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.preference.IPreferenceStore; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.IPropertyOwner; import com.mobilesorcery.sdk.core.ISecurePropertyOwner; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.ParameterResolver; import com.mobilesorcery.sdk.core.ParameterResolverException; import com.mobilesorcery.sdk.core.PreferenceStorePropertyOwner; import com.mobilesorcery.sdk.core.PropertyUtil; import com.mobilesorcery.sdk.core.SecurePropertyException; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.ui.DefaultMessageProvider; /** * <p> * A utility class for handling certificates stored in a key store. * </p> * * @author Mattias Bybro, mattias.bybro@purplescout.se * */ public class KeystoreCertificateInfo { public static final String KEYSTORE_LOCATION_SUFFIX = ".location"; public static final String ALIAS_SUFFIX = ".alias"; public static final String KEYSTORE_PWD_SUFFIX = ".store.pwd"; public static final String KEY_PWD_SUFFIX = ".key.pwd"; public static final String PASSWORDS_IN_CLEARTEXT = ".enc.pwd"; private String keystoreLocation; private String alias; private String keystorePassword; private String keyPassword; private boolean shouldEncryptPasswords = true; private SecurePropertyException exception; public KeystoreCertificateInfo() { } public KeystoreCertificateInfo(String keystoreLocation, String alias, String keystorePassword, String keyPassword, boolean shouldEncryptPasswords) { this.keystoreLocation = keystoreLocation; this.alias = alias; this.keystorePassword = keystorePassword; this.keyPassword = keyPassword; this.shouldEncryptPasswords = shouldEncryptPasswords; } public String getKeystoreLocation() { return keystoreLocation; } public void setKeystoreLocation(String keystoreLocation) { this.keystoreLocation = keystoreLocation; } public String getAlias() { return alias; } public void setAlias(String alias) { this.alias = alias; } public String getKeystorePassword() { return keystorePassword; } public void setKeystorePassword(String keystorePassword) { this.keystorePassword = keystorePassword; } public String getKeyPassword() { return keyPassword; } public void setKeyPassword(String keyPassword) { this.keyPassword = keyPassword; } public boolean shouldEncryptPasswords() { return shouldEncryptPasswords; } public IMessageProvider validate(boolean strict) { return validate(strict, null); } public IMessageProvider validate(boolean strict, ParameterResolver resolver) { String msg = null; int type = IMessageProvider.NONE; File keystoreFile = null; try { keystoreFile = new File(Util.replace(keystoreLocation, resolver)); } catch (ParameterResolverException e) { msg = e.getMessage(); } if (exception != null) { msg = "Could not read encrypted passwords (this happens for shared projects, for security reasons)."; type = IMessageProvider.WARNING; } else if (Util.isEmpty(keyPassword) || Util.isEmpty(keystorePassword)) { msg = "Empty passwords are not allowed (shared projects do not share passwords for security reasons)"; type = IMessageProvider.WARNING; } else if (Util.isEmpty(alias)) { msg = "Empty aliases are not allowed"; type = IMessageProvider.WARNING; } else if (Util.isEmpty(keystoreLocation)) { msg = "Keystore location must be provided"; type = IMessageProvider.WARNING; } else if (resolver != null && keystoreFile != null && !keystoreFile.exists()) { msg = MessageFormat.format("Keystore {0} does not exist", keystoreLocation); type = IMessageProvider.WARNING; } else if (strict && CoreMoSyncPlugin.getDefault().usesEclipseSecureStorage()) { msg = "Un-encrypted passwords, see Preferences > MoSync Tool > Security"; type = IMessageProvider.WARNING; } return new DefaultMessageProvider(msg, type); } /** * <p> * Loads this {@link KeystoreCertificateInfo} with information from * non-encrypted and encrypted storages (passwords are encrypted). * </p> * <p> * If decryption failed (for example due to no wrong password), the * {@link #validate()} method will return a non-<code>null</code> error * message. * </p> * * @param baseKey * the key that is used as a base to store the information; the * properties actually used will be <code>baseKey</code> + a * suffix for each property + an ordinal marker. * @param storage * @param secureStorage */ public static List<KeystoreCertificateInfo> load(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage) { // First, check for old format List<KeystoreCertificateInfo> result = tryToLoadOldFormat(baseKey, storage); if (result == null) { int ordinal = 0; boolean mayHaveMore = true; result = new ArrayList<KeystoreCertificateInfo>(); while (mayHaveMore) { KeystoreCertificateInfo info = new KeystoreCertificateInfo(); mayHaveMore = info.load(baseKey, storage, secureStorage, ordinal); if (mayHaveMore) { result.add(info); } ordinal++; } } return result; } public static KeystoreCertificateInfo loadOne(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage) { List<KeystoreCertificateInfo> result = load(baseKey, storage, secureStorage); return result.size() > 0 ? result.get(0) : null; } /** * Utility method to loads a {@link KeystoreCertificateInfo} for a project. It also supports loading a workspace default * certificate if the value of the project specific property defined by {@code projectSpecificKey} is set to {@code true}. * @param baseKey * @param projectSpecificKey {@code null} if this should be ignored * @param project * @return */ public static KeystoreCertificateInfo loadOne(String baseKey, String projectSpecificKey, MoSyncProject project, IPreferenceStore store) { if (projectSpecificKey == null || PropertyUtil.getBoolean(project, projectSpecificKey)) { return loadOne(baseKey, project, project.getSecurePropertyOwner()); } else { return loadOne(baseKey, new PreferenceStorePropertyOwner(store), CoreMoSyncPlugin.getDefault().getSecureProperties()); } } private boolean load(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage, int ordinal) { keystoreLocation = storage.getProperty(baseKey + KEYSTORE_LOCATION_SUFFIX + "." + ordinal); if (Util.isEmpty(keystoreLocation)) { return false; } alias = storage.getProperty(baseKey + ALIAS_SUFFIX + "." + ordinal); shouldEncryptPasswords = !PropertyUtil.getBoolean(storage, baseKey + PASSWORDS_IN_CLEARTEXT + "." + ordinal); if (shouldEncryptPasswords && secureStorage != null) { try { keystorePassword = secureStorage.getSecureProperty(baseKey + KEYSTORE_PWD_SUFFIX + "." + ordinal); keyPassword = secureStorage.getSecureProperty(baseKey + KEY_PWD_SUFFIX + "." + ordinal); } catch (SecurePropertyException e) { exception = e; } } else { keystorePassword = storage.getProperty(baseKey + KEYSTORE_PWD_SUFFIX + "." + ordinal); keyPassword = storage.getProperty(baseKey + KEY_PWD_SUFFIX + "." + ordinal); } return true; // return !(Util.isEmpty(keystoreLocation) && Util.isEmpty(alias) && Util.isEmpty(keyPassword) && Util.isEmpty(keystorePassword)); } public static void store(List<KeystoreCertificateInfo> infoList, String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage) throws SecurePropertyException { List<KeystoreCertificateInfo> oldies = load(baseKey, storage, secureStorage); int ordinal = 0; for (KeystoreCertificateInfo info : infoList) { info.store(baseKey, storage, secureStorage, ordinal); ordinal++; } int oldCount = oldies.size(); for (int i = ordinal; i < oldCount; i++) { clear(baseKey, storage, secureStorage, i, oldies.get(i)); } } private static void clear(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage, int ordinal, KeystoreCertificateInfo info) throws SecurePropertyException { info.alias = null; info.keyPassword = null; info.keystoreLocation = null; info.keystorePassword = null; // But we keep the 'shouldEncryptPasswords' attribute info.store(baseKey, storage, secureStorage, ordinal); } public void store(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage) throws SecurePropertyException { store(baseKey, storage, secureStorage, 0); } private void store(String baseKey, IPropertyOwner storage, ISecurePropertyOwner secureStorage, int ordinal) throws SecurePropertyException { PropertyUtil.setBoolean(storage, baseKey + PASSWORDS_IN_CLEARTEXT + "." + ordinal, !shouldEncryptPasswords); storage.setProperty(baseKey + KEYSTORE_LOCATION_SUFFIX + "." + ordinal, keystoreLocation); storage.setProperty(baseKey + ALIAS_SUFFIX + "." + ordinal, alias); if (shouldEncryptPasswords) { secureStorage.setSecureProperty(baseKey + KEYSTORE_PWD_SUFFIX + "." + ordinal, keystorePassword); secureStorage.setSecureProperty(baseKey + KEY_PWD_SUFFIX + "." + ordinal, keyPassword); } else { storage.setProperty(baseKey + KEYSTORE_PWD_SUFFIX + "." + ordinal, keystorePassword); storage.setProperty(baseKey + KEY_PWD_SUFFIX + "." + ordinal, keyPassword); } } public static KeystoreCertificateInfo createDefault() { String defaultKeystore = MoSyncTool.getDefault().getMoSyncHome().append("etc/mosync.keystore").toOSString(); KeystoreCertificateInfo defaultInfo = new KeystoreCertificateInfo(defaultKeystore, "mosync.keystore", "default", "default", false); return defaultInfo; } private static List<KeystoreCertificateInfo> tryToLoadOldFormat(String key, IPropertyOwner storage) { String input = storage.getProperty(key); if (!Util.isEmpty(input)) { List<KeystoreCertificateInfo> resultList = new ArrayList<KeystoreCertificateInfo>(); JSONParser parser = new JSONParser(); try { JSONArray keystoreCertificateInfos = (JSONArray) parser.parse(input); for (int i = 0; i < keystoreCertificateInfos.size(); i++) { JSONObject keystoreCertificateInfo = (JSONObject) keystoreCertificateInfos.get(i); String keystore = (String) keystoreCertificateInfo.get("keystore"); String alias = (String) keystoreCertificateInfo.get("alias"); String keystorePass = (String) keystoreCertificateInfo.get("keystorePass"); String keyPass = (String) keystoreCertificateInfo.get("keyPass"); KeystoreCertificateInfo result = new KeystoreCertificateInfo(keystore, alias, keystorePass, keyPass, false); resultList.add(result); } return resultList; } catch (Exception e) { CoreMoSyncPlugin.getDefault().log(e); } } return null; } }