/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.motorolamobility.studio.android.certmanager.ui.model; import java.io.File; import java.security.KeyStore; import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.osgi.util.NLS; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.common.preferences.DialogWithToggleUtils; import com.motorola.studio.android.common.utilities.EclipseUtils; import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator; import com.motorolamobility.studio.android.certmanager.core.KeyStoreManager; import com.motorolamobility.studio.android.certmanager.core.KeyStoreUtils; import com.motorolamobility.studio.android.certmanager.core.PasswordProvider; import com.motorolamobility.studio.android.certmanager.event.KeyStoreModelEvent.EventType; import com.motorolamobility.studio.android.certmanager.event.KeyStoreModelEventManager; import com.motorolamobility.studio.android.certmanager.exception.InvalidPasswordException; import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException; import com.motorolamobility.studio.android.certmanager.i18n.CertificateManagerNLS; import com.motorolamobility.studio.android.certmanager.views.KeystoreManagerView; /** * Represents a keystore visual item for the {@link KeystoreManagerView}. * * @author gdpr78 * */ public class KeyStoreNode extends AbstractTreeNode implements IKeyStore { public static final String WARN_ABOUT_UNSUPPORTED_ENTRIES_PREFERENCE = CertificateManagerActivator.PLUGIN_ID + ".warnAboutUnsupportedEntries"; //$NON-NLS-1$ private static final String DUMMY_NODE = "DUMMY_NODE"; //$NON-NLS-1$ private final File keyStoreFile; private KeyStore keyStore; private Date lastBackupDate; private String type; /** * Alias to {@link EntryNode} */ private final Map<String, ITreeNode> entries = new LinkedHashMap<String, ITreeNode>(); private final String KEYSTORE_NONSAVED_PASSWORD_ICON_PATH = "icons/keystore.png"; //$NON-NLS-1$ private final String KEYSTORE_SAVED_PASSWORD_ICON_PATH = "icons/keystore_saved_password.png"; //$NON-NLS-1$ private static final String WRONG_KEYSTORE_TYPE_ICON_PATH = "icons/keystore_incorrect_type.png"; private final PasswordProvider passwordProvider; private boolean ignoreRefresh; private boolean quiet; private boolean skipNextReload = false; private boolean typeVerified; public KeyStoreNode(File path) { this.keyStoreFile = path; passwordProvider = new PasswordProvider(keyStoreFile); updateStatus(); } public KeyStoreNode(File path, String type) { this.keyStoreFile = path; this.type = type; passwordProvider = new PasswordProvider(keyStoreFile); updateStatus(); } public KeyStoreNode(File keyStoreFile, KeyStore keyStore) { this(keyStoreFile); this.keyStore = keyStore; this.type = keyStore.getType(); } @Override public PasswordProvider getPasswordProvider() { return passwordProvider; } @Override public String getKeyStorePassword(boolean promptPassword) { String password = null; boolean keepTrying = true; //keep asking password until user either enter the correct password or cancel the operation while (keepTrying) { try { try { keepTrying = false; password = getPasswordProvider().getKeyStorePassword(promptPassword); if (password != null) { isPasswordValid(password); } } catch (InvalidPasswordException e) { getPasswordProvider().deleteKeyStoreSavedPasswordNode(); password = null; keepTrying = true; } } catch (KeyStoreManagerException e) { password = null; keepTrying = false; StudioLogger.info( this.getClass(), CertificateManagerNLS.KeyStoreNode_CouldNotGetKeyStorePassword + e.getLocalizedMessage()); } } return password; } /** * @return the path */ @Override public File getFile() { return keyStoreFile; } @Override public KeyStore getKeyStore() throws KeyStoreManagerException { return getKeyStore(true); } /* * (non-Javadoc) * * @see com.motorolamobility.studio.android.certmanager.ui.model.IKeyStore# * getKeyStore() */ public KeyStore getKeyStore(boolean load) throws KeyStoreManagerException { if (keyStore == null) { boolean tryAgain = false; boolean useSavedPass = true; String password = null; do { if (tryAgain) { useSavedPass = false; } password = passwordProvider.getKeyStorePassword(true, useSavedPass); tryAgain = false; if (password != null) { try { keyStore = loadKeystore(password.toCharArray()); setTooltip(null); if (load) { loadEntries(); } } catch (InvalidPasswordException e) { tryAgain = true; } } else { setTooltip(CertificateManagerNLS.KeyStoreNode_CouldNotLoadKeystore_Tooltip); } } while (tryAgain); } return keyStore; } public KeyStore getKeyStore(String password) throws KeyStoreManagerException, InvalidPasswordException { if ((keyStore == null) && (password != null)) { keyStore = loadKeystore(password.toCharArray()); loadEntries(); } else { //just check if given password is valid for this keystore isPasswordValid(password); } return keyStore; } @Override public boolean isPasswordValid(String password) throws KeyStoreManagerException, InvalidPasswordException { KeyStore myKeyStore = null; if (password != null) { myKeyStore = loadKeystore(password.toCharArray()); } else { throw new InvalidPasswordException(CertificateManagerNLS.KeyStoreNode_Password_NotNull); } return myKeyStore != null; } protected KeyStore loadKeystore(char[] password) throws KeyStoreManagerException, InvalidPasswordException { KeyStore keyStore = null; setNodeStatus(Status.OK_STATUS); setTooltip(null); try { if (!typeVerified && type.equalsIgnoreCase("jceks")) //$NON-NLS-1$ { //Try to load this as JKS. keyStore = KeyStoreUtils.loadKeystore(keyStoreFile, password, "JKS"); //$NON-NLS-1$ if (keyStore != null) { //Keystore type is actually wrong, it's a jks keystore. EclipseUtils.showWarningDialog( CertificateManagerNLS.KeyStoreNode_Wrong_KeystoreType_Title, NLS.bind( CertificateManagerNLS.KeyStoreNode_Wrong_KeystoreType_Message, getName())); setType("JKS"); //$NON-NLS-1$ typeVerified = true; } } } catch (KeyStoreManagerException keyStoreManagerException) { //Do nothing, let's try with the correct type. } catch (InvalidPasswordException invalidPasswordException) { setNodeStatus(new Status(IStatus.ERROR, CertificateManagerActivator.PLUGIN_ID, CertificateManagerNLS.KeyStoreNode_InvalidPassword)); throw invalidPasswordException; } try { keyStore = KeyStoreUtils.loadKeystore(keyStoreFile, password, type); setNodeStatus(Status.OK_STATUS); } catch (KeyStoreManagerException keyStoreManagerException) { setNodeStatus(new Status(IStatus.ERROR, CertificateManagerActivator.PLUGIN_ID, IKeyStore.WRONG_KEYSTORE_TYPE_ERROR_CODE, CertificateManagerNLS.KeyStoreNode_KeystoreTypeWrong_NodeStatus, null)); throw keyStoreManagerException; } catch (InvalidPasswordException invalidPasswordException) { setNodeStatus(new Status(IStatus.ERROR, CertificateManagerActivator.PLUGIN_ID, CertificateManagerNLS.KeyStoreNode_InvalidPassword)); throw invalidPasswordException; } return keyStore; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = (prime * result) + ((keyStoreFile == null) ? 0 : keyStoreFile.hashCode()); return result; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof KeyStoreNode)) { return false; } KeyStoreNode other = (KeyStoreNode) obj; if (keyStoreFile == null) { if (other.keyStoreFile != null) { return false; } } else if (!keyStoreFile.equals(other.keyStoreFile)) { return false; } return true; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return getName() + " - ( " + getId() + " )"; //$NON-NLS-1$ //$NON-NLS-2$ } @Override public void refresh() throws KeyStoreManagerException { if (!ignoreRefresh) { if (!skipNextReload) { keyStore = null; skipNextReload = false; } entries.clear(); updateStatus(); if (getNodeStatus().isOK()) { quiet = true; loadEntries(); quiet = false; passwordProvider.cleanModel(new ArrayList<String>(entries.keySet())); } } else { setIgnoreRefresh(false); } } private void setIgnoreRefresh(boolean ignoreRefresh) { this.ignoreRefresh = ignoreRefresh; } @Override public String getId() { return keyStoreFile.getAbsolutePath(); } @Override public String getName() { return keyStoreFile.getName(); } @Override public ImageDescriptor getIcon() { ImageDescriptor descr = null; if (!isStoreTypeCorrect()) { //wrong keystore type descr = CertificateManagerActivator.imageDescriptorFromPlugin( CertificateManagerActivator.PLUGIN_ID, WRONG_KEYSTORE_TYPE_ICON_PATH); } else if (isPasswordSaved()) { //saved password descr = CertificateManagerActivator.imageDescriptorFromPlugin( CertificateManagerActivator.PLUGIN_ID, KEYSTORE_SAVED_PASSWORD_ICON_PATH); } else { //non saved password descr = CertificateManagerActivator.imageDescriptorFromPlugin( CertificateManagerActivator.PLUGIN_ID, KEYSTORE_NONSAVED_PASSWORD_ICON_PATH); } return descr; } @Override public boolean isLeaf() { return false; } @Override public List<ITreeNode> getChildren() throws KeyStoreManagerException { ArrayList<ITreeNode> children = new ArrayList<ITreeNode>(entries.values()); return children; } private void loadEntries() throws KeyStoreManagerException { if (entries.size() == 1) { ITreeNode entryNode = entries.get(DUMMY_NODE); //$NON-NLS-1$ if (entryNode != null) { entries.remove(DUMMY_NODE); //$NON-NLS-1$ KeyStoreModelEventManager.getInstance().fireEvent(entryNode, EventType.REMOVE); } } entries.clear(); KeyStore keyStore = getKeyStore(false); if (keyStore != null) { Enumeration<String> aliases; try { aliases = keyStore.aliases(); } catch (KeyStoreException e) { throw new KeyStoreManagerException(CertificateManagerNLS.bind( CertificateManagerNLS.KeyStoreModel_Error_GettingAliasesFromKeystore, getName()), e); } List<String> keyPairEntries = new ArrayList<String>(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); EntryNode keyStoreEntry = new EntryNode(this, alias); if (!keyStoreEntry.isKeyPairEntry()) { //we will not support key pairs entries.put(alias, keyStoreEntry); } else { //is key pair keyPairEntries.add(alias); String msg = CertificateManagerNLS.bind( CertificateManagerNLS.KeyStoreNode_KeyPairNotMapped_LogMessage, alias); StudioLogger.debug(msg); } } if ((keyPairEntries != null) && !keyPairEntries.isEmpty()) { //found key pairs DialogWithToggleUtils.showInformation(WARN_ABOUT_UNSUPPORTED_ENTRIES_PREFERENCE, CertificateManagerNLS.KeyStoreNode_KeyPairNotMapped_Title, CertificateManagerNLS.KeyStoreNode_KeyPairNotMapped_Message); } if (entries.isEmpty()) { entries.put(DUMMY_NODE, new EntryDummyNode(this)); //$NON-NLS-1$ } } else { setNodeStatus(new Status(IStatus.ERROR, CertificateManagerActivator.PLUGIN_ID, CertificateManagerNLS.KeyStoreNode_UseRefresh_StatusNode)); } } private void updateStatus() { setNodeStatus(Status.OK_STATUS); if (!keyStoreFile.exists()) { setNodeStatus(new Status(IStatus.ERROR, CertificateManagerActivator.PLUGIN_ID, CertificateManagerNLS.KeyStoreNode_KeystoreFileNotFound)); } } @Override public void addChild(ITreeNode newChild) { if (entries.size() == 1) { ITreeNode entryNode = entries.get(DUMMY_NODE); //$NON-NLS-1$ if (entryNode != null) { entries.remove(DUMMY_NODE); //$NON-NLS-1$ KeyStoreModelEventManager.getInstance().fireEvent(entryNode, EventType.REMOVE); } } if ((newChild instanceof IKeyStoreEntry) || (newChild instanceof EntryDummyNode)) { EntryNode entryNode = (EntryNode) newChild; String alias = entryNode.getAlias(); entries.put(alias, entryNode); if (!quiet && !(newChild instanceof EntryDummyNode)) { KeyStoreModelEventManager.getInstance().fireEvent(newChild, EventType.ADD); } } } /** * @return the lastBackupDate */ @Override public Date getLastBackupDate() { return lastBackupDate; } /** * @param lastBackupDate * the lastBackupDate to set */ @Override public void setLastBackupDate(Date lastBackupDate) { this.lastBackupDate = lastBackupDate; try { KeyStoreManager.getInstance().setBackupDate(this, lastBackupDate); } catch (KeyStoreManagerException e) { StudioLogger.error("Could not set backup date for keystore"); } KeyStoreModelEventManager.getInstance().fireEvent(this, EventType.UPDATE); } /** * @return the type */ @Override public String getType() { return type != null ? type : KeyStore.getDefaultType().toUpperCase(); } /** * @param type * the type to set * @throws KeyStoreManagerException */ @Override public void setType(String type) throws KeyStoreManagerException { this.type = type; KeyStoreManager.getInstance().updateKeyStoreType(this); } @Override public List<IKeyStoreEntry> getEntries(String password) throws KeyStoreManagerException, InvalidPasswordException { getKeyStore(password); ArrayList<IKeyStoreEntry> children = new ArrayList<IKeyStoreEntry>(entries.size()); for (ITreeNode treeNode : entries.values()) { if (treeNode instanceof IKeyStoreEntry) { children.add((IKeyStoreEntry) treeNode); } } return children; } @Override public IKeyStoreEntry getEntry(String alias, String keystorePassword) throws KeyStoreManagerException, InvalidPasswordException { IKeyStoreEntry result = null; for (IKeyStoreEntry entry : getEntries(keystorePassword)) { if (entry.getAlias().equalsIgnoreCase(alias)) { result = entry; } } return result; } @Override public List<String> getAliases(String password) throws KeyStoreManagerException, InvalidPasswordException { getKeyStore(password); ArrayList<String> children = new ArrayList<String>(entries.size()); for (ITreeNode treeNode : entries.values()) { if (treeNode instanceof IKeyStoreEntry) { children.add(((IKeyStoreEntry) treeNode).getAlias()); } } return children; } @Override public void removeKey(String alias) throws KeyStoreManagerException { String password = passwordProvider.getKeyStorePassword(true, true); if (password != null) { KeyStoreUtils.deleteEntry(keyStore, password.toCharArray(), keyStoreFile, alias); try { forceReload(password.toCharArray(), false); } catch (InvalidPasswordException e) { //Should never happen. StudioLogger.debug("Could reload ks after removing entry, invalid password"); //$NON-NLS-1$ } ITreeNode entryNode = entries.remove(alias); KeyStoreModelEventManager.getInstance().fireEvent(entryNode, EventType.REMOVE); if (entries.isEmpty()) { EntryDummyNode entryDummyNode = new EntryDummyNode(this); entries.put(DUMMY_NODE, entryDummyNode); //$NON-NLS-1$ KeyStoreModelEventManager.getInstance().fireEvent(entryDummyNode, EventType.ADD); } } else { // password not found throw new KeyStoreManagerException( CertificateManagerNLS.KeyStoreNode_NotFoundOrIncorrectPasswordToDeleteEntry + alias); } } @Override public void removeKeys(List<String> aliases) throws KeyStoreManagerException { String password = passwordProvider.getKeyStorePassword(true, true); if (password != null) { for (String alias : aliases) { KeyStoreUtils.deleteEntry(keyStore, password.toCharArray(), keyStoreFile, alias); ITreeNode entryNode = entries.remove(alias); KeyStoreModelEventManager.getInstance().fireEvent(entryNode, EventType.REMOVE); } try { forceReload(password.toCharArray(), false); } catch (InvalidPasswordException e) { //Should never happen. StudioLogger.debug("Could reload ks after removing entry, invalid password"); //$NON-NLS-1$ } if (entries.isEmpty()) { EntryDummyNode entryDummyNode = new EntryDummyNode(this); entries.put(DUMMY_NODE, entryDummyNode); //$NON-NLS-1$ KeyStoreModelEventManager.getInstance().fireEvent(entryDummyNode, EventType.ADD); } } else { // password not found throw new KeyStoreManagerException( CertificateManagerNLS.KeyStoreNode_IncorrectPasswordToDeleteEntries_Error); } } @Override public boolean testAttribute(Object target, String name, String value) { boolean result = super.testAttribute(target, name, value); if (name.equals(PROP_NAME_NODE_STATUS)) { if (value.equals(PROP_VALUE_NODE_STATUS_ERROR)) { if (!isStoreTypeCorrect()) { //when store type is incorrect the icon is changed, not decorated. result = false; } else if (!keyStoreFile.exists()) { // keystore not found result = true; setTooltip(CertificateManagerNLS.KeyStoreNode_ErrorKeystoreNotFound); } } else if (value.equals(PROP_VALUE_NODE_STATUS_KEYSTORE_TYPE_OK)) { result = isStoreTypeCorrect(); } } return result; } @Override public void forceReload(char[] password, boolean updateUi) throws KeyStoreManagerException, InvalidPasswordException { keyStore = loadKeystore(password); if (updateUi) { skipNextReload = true; KeyStoreModelEventManager.getInstance().fireEvent(this, EventType.REFRESH); } } @Override protected boolean isPasswordSaved() { PasswordProvider pp = new PasswordProvider(getFile()); return pp.isPasswordSaved(); } protected boolean isStoreTypeCorrect() { return getNodeStatus().getCode() != WRONG_KEYSTORE_TYPE_ERROR_CODE; } }