/*
* 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.wizards;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
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.PasswordProvider;
import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException;
import com.motorolamobility.studio.android.certmanager.i18n.CertificateManagerNLS;
import com.motorolamobility.studio.android.certmanager.ui.model.KeyStoreNode;
import com.motorolamobility.studio.android.certmanager.ui.model.SigningAndKeysModelManager;
public class CreateKeystorePage extends WizardPage
{
private static final String CREATE_KEYSTORE_HELP_ID = CertificateManagerActivator.PLUGIN_ID
+ ".new_keystore"; //$NON-NLS-1$
private Text keystoreFilenameText;
private ComboViewer keystoreTypeComboViewer;
private Text keystorePasswordText;
private Text keystoreConfirmPasswordText;
private String keystorePassword;
private boolean initialValidation = true;
private boolean userChangedPasswordConfirmation = false;
private boolean userChangedPassword = false;
SelectionListener selectionListener = new SelectionListener()
{
@Override
public void widgetSelected(SelectionEvent e)
{
validatePage();
}
@Override
public void widgetDefaultSelected(SelectionEvent e)
{
//nothing to do...
}
};
private Button savePassword;
private Button useTypeAsExtensionCheckBox;
protected boolean useTypeAsExtensionCheckBoxPreviousState = true;
/**
* @param pageName
*/
protected CreateKeystorePage(String pageName)
{
super(pageName);
}
/* (non-Javadoc)
* @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createControl(Composite parent)
{
Composite mainComposite = new Composite(parent, SWT.FILL);
mainComposite.setLayout(new GridLayout(3, false));
mainComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
setTitle(CertificateManagerNLS.CreateKeystorePage_CreateKeystore);
setMessage(CertificateManagerNLS.CreateKeystorePage_WizardDefaultMessage);
createFilenameSection(mainComposite);
createKeystoreTypeSection(mainComposite);
createFilenameExtensionSection(mainComposite);
setKeystoreFilenameExtension();
//LINE TO SEPARATE PASSWORD SECTION FROM KEYSTORE DETAILS SECTION
Label separator1 = new Label(mainComposite, SWT.SEPARATOR | SWT.HORIZONTAL);
separator1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
createKeystorePasswordSection(mainComposite);
createConfirmPasswordSection(mainComposite);
createSavePasswordSection(mainComposite);
validatePage();
setControl(mainComposite);
//set help id for this page
PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, CREATE_KEYSTORE_HELP_ID);
PlatformUI.getWorkbench().getHelpSystem().setHelp(mainComposite, CREATE_KEYSTORE_HELP_ID);
}
/**
* @param mainComposite
*/
private void createKeystoreTypeSection(Composite parent)
{
Label keystoreTypeLabel = new Label(parent, SWT.NONE);
keystoreTypeLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
keystoreTypeLabel.setText(CertificateManagerNLS.CreateKeystorePage_KeystoreType);
keystoreTypeComboViewer = new ComboViewer(parent, SWT.READ_ONLY);
keystoreTypeComboViewer.getCombo().setLayoutData(
new GridData(SWT.FILL, SWT.NONE, true, false, 1, 1));
keystoreTypeComboViewer.setContentProvider(new IStructuredContentProvider()
{
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
{
//do nothing
}
@Override
public void dispose()
{
//do nothing
}
@SuppressWarnings("unchecked")
@Override
public Object[] getElements(Object inputElement)
{
return ((List<String>) inputElement).toArray();
}
});
keystoreTypeComboViewer.setLabelProvider(new ILabelProvider()
{
@Override
public void removeListener(ILabelProviderListener listener)
{
//do nothing
}
@Override
public boolean isLabelProperty(Object element, String property)
{
return false;
}
@Override
public void dispose()
{
//do nothing
}
@Override
public void addListener(ILabelProviderListener listener)
{
//do nothing
}
@Override
public String getText(Object element)
{
return (String) element;
}
@Override
public Image getImage(Object element)
{
return null;
}
});
keystoreTypeComboViewer.setInput(KeyStoreManager.getInstance().getAvailableTypes());
keystoreTypeComboViewer.getCombo().addSelectionListener(new SelectionAdapter()
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e)
{
useTypeAsExtensionCheckBox.setEnabled(true);
useTypeAsExtensionCheckBox.setSelection(useTypeAsExtensionCheckBoxPreviousState);
if (useTypeAsExtensionCheckBox.getSelection())
{
setKeystoreFilenameExtension();
}
}
});
for (int i = 0; i < keystoreTypeComboViewer.getCombo().getItemCount(); i++)
{
if (keystoreTypeComboViewer.getCombo().getItem(i)
.compareToIgnoreCase(KeyStoreManager.getInstance().getDefaultType()) == 0)
{
keystoreTypeComboViewer.getCombo().select(i);
}
}
keystoreTypeComboViewer.getCombo().addModifyListener(new ModifyListener()
{
@Override
public void modifyText(ModifyEvent e)
{
if (useTypeAsExtensionCheckBox != null)
{
useTypeAsExtensionCheckBox.setEnabled(false);
useTypeAsExtensionCheckBox.setSelection(false);
}
}
});
//fill the third column with a blank label
Label separator2 = new Label(parent, SWT.NONE);
separator2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
}
/**
* Set the extension of the keystore based on selected keystore type and the user's choice to use it or not as the extension.
* If the user typed a custom keystore type, then the filename extension is set to ".keystore".
* */
protected void setKeystoreFilenameExtension()
{
String keystoreFilename = keystoreFilenameText.getText();
String keystoreType = keystoreTypeComboViewer.getCombo().getText();
List<String> availableTypes = KeyStoreManager.getInstance().getAvailableTypes();
availableTypes
.add(CertificateManagerNLS.CreateKeystorePage_DefaultKeystoreFilenameExtension);
for (String availableType : availableTypes)
{
String availableTypeExtension = "." + availableType.toLowerCase(); //$NON-NLS-1$
if (keystoreFilename.endsWith(availableTypeExtension))
{
keystoreFilename =
keystoreFilename.substring(0, keystoreFilename.length()
- availableTypeExtension.length());
break;
}
}
keystoreFilenameText.setText(keystoreFilename + "." + keystoreType.toLowerCase()); //$NON-NLS-1$
}
private void createFilenameExtensionSection(Composite mainComposite)
{
useTypeAsExtensionCheckBox = new Button(mainComposite, SWT.CHECK);
useTypeAsExtensionCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
3, 1));
useTypeAsExtensionCheckBox
.setText(CertificateManagerNLS.CreateKeystorePage_UseKeystoreTypeAsExtension);
useTypeAsExtensionCheckBox.setSelection(true);
useTypeAsExtensionCheckBox.addSelectionListener(new SelectionAdapter()
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e)
{
useTypeAsExtensionCheckBoxPreviousState = useTypeAsExtensionCheckBox.getSelection();
if (useTypeAsExtensionCheckBox.getSelection())
{
setKeystoreFilenameExtension();
}
}
});
}
/**
* @param mainComposite
*/
private void createSavePasswordSection(Composite mainComposite)
{
savePassword = new Button(mainComposite, SWT.CHECK);
savePassword.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
savePassword.setText(CertificateManagerNLS.CreateKeystorePage_SaveThisPassword);
savePassword.setSelection(false);
}
/**
* @param mainComposite
*/
private void createConfirmPasswordSection(Composite mainComposite)
{
Label keystoreConfirmPasswordLabel = new Label(mainComposite, SWT.NONE);
keystoreConfirmPasswordLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
1, 1));
keystoreConfirmPasswordLabel
.setText(CertificateManagerNLS.CreateKeystorePage_KeystoreConfirmPasswordLabel);
keystoreConfirmPasswordText =
new Text(mainComposite, SWT.SINGLE | SWT.BORDER | SWT.PASSWORD);
keystoreConfirmPasswordText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
1, 1));
keystoreConfirmPasswordText.addSelectionListener(selectionListener);
keystoreConfirmPasswordText.addModifyListener(new ModifyListener()
{
@Override
public void modifyText(ModifyEvent e)
{
userChangedPasswordConfirmation = true;
validatePage();
}
});
//fill the third column with a blank label
Label separator2 = new Label(mainComposite, SWT.NONE);
separator2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
}
/**
* @param mainComposite
*/
private void createKeystorePasswordSection(Composite mainComposite)
{
Label keystorePasswordLabel = new Label(mainComposite, SWT.NONE);
keystorePasswordLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
keystorePasswordLabel
.setText(CertificateManagerNLS.CreateKeystorePage_KeystorePasswordLabel);
keystorePasswordText = new Text(mainComposite, SWT.SINGLE | SWT.BORDER | SWT.PASSWORD);
keystorePasswordText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
keystorePasswordText.addSelectionListener(selectionListener);
keystorePasswordText.addModifyListener(new ModifyListener()
{
@Override
public void modifyText(ModifyEvent e)
{
keystorePassword = keystorePasswordText.getText();
userChangedPassword = true;
validatePage();
}
});
//fill the third column with a blank label
@SuppressWarnings("unused")
Label separator = new Label(mainComposite, SWT.NONE);
keystorePasswordLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
}
/**
* @param mainComposite
*/
private void createFilenameSection(Composite mainComposite)
{
Label keystoreFilenameLabel = new Label(mainComposite, SWT.NONE);
keystoreFilenameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1));
keystoreFilenameLabel
.setText(CertificateManagerNLS.CreateKeystorePage_KeystoreFilenameLabel);
keystoreFilenameText = new Text(mainComposite, SWT.SINGLE | SWT.BORDER);
keystoreFilenameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
keystoreFilenameText.setText(generateKeyStoreFilename());
keystoreFilenameText.addSelectionListener(selectionListener);
keystoreFilenameText.addModifyListener(new ModifyListener()
{
@Override
public void modifyText(ModifyEvent e)
{
validatePage();
}
});
Button chooseLocation = new Button(mainComposite, SWT.PUSH);
chooseLocation.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
chooseLocation.setText(CertificateManagerNLS.CreateKeystorePage_KeystoreFilenameBrowse);
chooseLocation.addSelectionListener(new SelectionAdapter()
{
/* (non-Javadoc)
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected(SelectionEvent e)
{
Shell shell = Display.getCurrent().getActiveShell();
FileDialog dialog = new FileDialog(shell, SWT.SAVE);
String keystoreFilenameStr = dialog.open();
if (keystoreFilenameStr != null)
{
keystoreFilenameText.setText(keystoreFilenameStr);
}
}
});
}
private void validatePage()
{
boolean pageComplete = true;
String errorMessage = null;
String message = CertificateManagerNLS.CreateKeystorePage_WizardDefaultMessage;
int messageType = IMessageProvider.NONE;
if (initialValidation == true)
{
//when the wizard opens, does not show any errors
pageComplete = false;
initialValidation = false;
}
else
{
//password text and confirmation password text must match
if (!keystorePasswordText.getText().equals(keystoreConfirmPasswordText.getText()))
{
//if the user hasn't started typing the confirmation password,
//then just show an info, instead of an error
if (userChangedPasswordConfirmation)
{
errorMessage = CertificateManagerNLS.CreateKeystorePage_PasswordDoesNotMatch;
pageComplete = false;
}
else
{
message = CertificateManagerNLS.CreateKeystorePage_ConfirmPasswordInfoMsg;
messageType = IMessageProvider.INFORMATION;
pageComplete = false;
}
}
//check password size according to keytool specification
if (keystorePasswordText.getText().length() < KeyStoreNode.KEYSTORE_PASSWORD_MIN_SIZE)
{
if (userChangedPassword)
{
errorMessage =
CertificateManagerNLS
.bind(CertificateManagerNLS.CreateKeystorePage_PasswordMinSizeMessage,
KeyStoreNode.KEYSTORE_PASSWORD_MIN_SIZE); //$NON-NLS-1$
pageComplete = false;
}
else
{
message = CertificateManagerNLS.CreateKeystorePage_SetPasswordInfoMsg;
messageType = IMessageProvider.INFORMATION;
pageComplete = false;
}
}
//check if store type is filled
if (keystoreTypeComboViewer.getCombo().getText().isEmpty())
{
errorMessage = CertificateManagerNLS.CreateKeystorePage_SetKeystoreType;
pageComplete = false;
}
//check if filename is valid
try
{
File keystoreFile = new File(keystoreFilenameText.getText().trim());
Path keystorePath = new Path(keystoreFilenameText.getText().trim());
if (!keystorePath.isValidPath(keystoreFile.getCanonicalPath()))
{
//throw the same exception as getCanonicalPath() in order to do not duplicate code
throw new IOException();
}
}
catch (IOException e)
{
errorMessage = CertificateManagerNLS.CreateKeystorePage_FilenameSyntaxError;
pageComplete = false;
}
if (keystoreFilenameText.getText().trim().isEmpty())
{
errorMessage = CertificateManagerNLS.ImportKeystorePage_FilenameCannotBeEmpty;
pageComplete = false;
}
}
setMessage(message, messageType);
setErrorMessage(errorMessage);
setPageComplete(pageComplete);
}
/**
* Generate a valid filename for a new keystore.
* The file must not exist, so a serial number is added to it as necessary.
* @return An standard keystore filename.
* */
private String generateKeyStoreFilename()
{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss_SSS"); //$NON-NLS-1$
String timestamp = dateFormat.format(Calendar.getInstance().getTime());
//initial keystore filename with timestamp
String keystoreFilenameStr =
System.getProperty("user.home") + System.getProperty("file.separator") //$NON-NLS-1$ //$NON-NLS-2$
+ CertificateManagerNLS.bind(
CertificateManagerNLS.CreateKeystorePage_DefaultKeystoreFilename,
timestamp);
File keystoreFile = new File(keystoreFilenameStr);
//while file already exists, generate a new one using a new timestamp
while (keystoreFile.exists())
{
timestamp = dateFormat.format(Calendar.getInstance().getTime());
keystoreFilenameStr =
System.getProperty("user.home") + System.getProperty("file.separator") //$NON-NLS-1$ //$NON-NLS-2$
+ CertificateManagerNLS
.bind(CertificateManagerNLS.CreateKeystorePage_DefaultKeystoreFilename,
timestamp);
keystoreFile = new File(keystoreFilenameStr);
}
return keystoreFilenameStr;
}
/**
* As this page works independently of other pages, it has its own version of performFinish().
* Wizards that use this page must call this method to effectively create the new keystore.
* @return {@code true} if the keystore were successfully created, {@code false} otherwise.
* */
public KeyStoreNode createKeyStore()
{
boolean successfullyCreated = true;
File keystoreFile = null;
KeyStoreNode keystoreNode = null;
try
{
keystoreFile = new File(keystoreFilenameText.getText().trim());
if (validateKeyStoreFile(keystoreFile))
{
keystoreNode =
(KeyStoreNode) KeyStoreManager.createKeyStore(keystoreFile,
keystoreTypeComboViewer.getCombo().getText(), keystorePasswordText
.getText().toCharArray());
SigningAndKeysModelManager.getInstance().mapKeyStore(keystoreNode);
}
else
{
//file already exist and will not be overwritten
successfullyCreated = false;
}
}
catch (KeyStoreManagerException e)
{
//in case of error, the keystore wasn't properly created and the file should not be left on file system
if (keystoreFile != null)
{
keystoreFile.delete();
}
EclipseUtils.showErrorDialog(
CertificateManagerNLS.CreateKeystorePage_ErrorCreatingKeystore, NLS.bind(
CertificateManagerNLS.CreateKeystorePage_ErrorOnKeyStoreFileCreation,
keystoreFilenameText.getText()));
successfullyCreated = false;
}
if (successfullyCreated && savePassword.getSelection())
{
savePassword(keystoreFile);
}
return successfullyCreated ? keystoreNode : null;
}
/**
* @param keystoreFile
*/
private void savePassword(File keystoreFile)
{
try
{
PasswordProvider passwordProvider = new PasswordProvider(keystoreFile);
passwordProvider.saveKeyStorePassword(keystorePasswordText.getText());
}
catch (KeyStoreManagerException e)
{
EclipseUtils.showWarningDialog(
CertificateManagerNLS.CreateKeystorePage_CouldNotSavePassword,
e.getLocalizedMessage());
}
}
/* If file exists and the user chooses to overwrite it, the key store file is valid and return value is true.
* If file exists and the user do not want to overwrite the file, then the keystore file is considered invalid and the return value is false.
* If file does not exist, then the file is valid and the return value is true.
* */
private boolean validateKeyStoreFile(File keystoreFile)
{
boolean result = true;
if (keystoreFile.exists())
{
Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
result =
MessageDialog.openQuestion(shell,
CertificateManagerNLS.CreateKeystorePage_ConfirmFileOverwrite,
NLS.bind(CertificateManagerNLS.CreateKeystorePage_ConfirmReplaceFile,
keystoreFile.getAbsolutePath()));
if (result)
{
//file will be recreated
keystoreFile.delete();
}
}
return result;
}
public String getPassword()
{
return keystorePassword;
}
}