/* * Copyright (C) 2010 Marc A. Paradise * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.bbssh.ui.screens; import java.io.IOException; import net.rim.device.api.applicationcontrol.ApplicationPermissions; import net.rim.device.api.crypto.CryptoTokenException; import net.rim.device.api.crypto.CryptoUnsupportedOperationException; import net.rim.device.api.crypto.DSACryptoSystem; import net.rim.device.api.crypto.DSAKeyPair; import net.rim.device.api.crypto.DSAPrivateKey; import net.rim.device.api.i18n.ResourceBundle; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.FieldChangeListener; import net.rim.device.api.ui.UiApplication; import net.rim.device.api.ui.component.BasicEditField; import org.bbssh.BBSSHApp; import org.bbssh.ui.components.ClickableButtonField; import net.rim.device.api.ui.component.Dialog; import net.rim.device.api.ui.component.RadioButtonField; import net.rim.device.api.ui.component.RadioButtonGroup; import net.rim.device.api.ui.component.SeparatorField; import net.rim.device.api.ui.component.Status; import net.rim.device.api.ui.container.HorizontalFieldManager; import net.rim.device.api.ui.container.PopupScreen; import net.rim.device.api.ui.container.VerticalFieldManager; import org.bbssh.crypto.TypesWriter; import org.bbssh.i18n.BBSSHResource; import org.bbssh.model.Key; import org.bbssh.model.KeyManager; import org.bbssh.patterns.UpdatingBackgroundTask; import org.bbssh.platform.PlatformServicesProvider; import org.bbssh.ui.components.FileSelectorPopupScreen; import org.bbssh.ui.components.PasswordControl; import org.bbssh.ui.components.PleaseWaitTaskMonitorScreen; import org.bbssh.util.Logger; import ch.ethz.ssh2.crypto.PEMDecoder; import ch.ethz.ssh2.crypto.PEMStructure; /** * Used for importing a key and configuring it, or creating a new key. */ public final class ImportKeyScreen extends PopupScreen implements BBSSHResource, FieldChangeListener { ResourceBundle res = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME); BasicEditField friendlyName; BasicEditField location; RadioButtonGroup importType = new RadioButtonGroup(); RadioButtonField importFromFile; RadioButtonField importFromURL; PasswordControl passwordControl; ClickableButtonField loadFile; ClickableButtonField importButton; Key key; boolean generateNew = false; private ClickableButtonField cancelButton; private String fileName; /** * Instantiates a new import key screen. * * @param generateNew the generate new */ public ImportKeyScreen(boolean generateNew) { super(new VerticalFieldManager(VERTICAL_SCROLL | VERTICAL_SCROLLBAR), DEFAULT_CLOSE); int actionLabel; this.generateNew = generateNew; // @todo this same form shoudl support editing? friendlyName = new BasicEditField(res.getString(IMPORT_KEY_LBL_FRIENDLY_NAME), "", 32, BasicEditField.NO_NEWLINE | BasicEditField.NON_SPELLCHECKABLE); add(friendlyName); if (generateNew) { actionLabel = KEY_MGR_LABEL_GENERATE; // nothing else to add here. } else { actionLabel = MENU_IMPORT; // we'll default to displaying the URL import, but radio button will allow // user to choose "from file". location = new BasicEditField(res.getString(IMPORT_KEY_LBL_LOCATION), "http://", 1024, BasicEditField.NO_NEWLINE | BasicEditField.NON_SPELLCHECKABLE | BasicEditField.FILTER_URL); importFromFile = new RadioButtonField(res.getString(IMPORT_KEY_LBL_AS_FILE), importType, false); importFromURL = new RadioButtonField(res.getString(IMPORT_KEY_LBL_AS_URL), importType, true); loadFile = new ClickableButtonField(res.getString(IMPORT_KEY_LBL_CHOOSE_FILE)); loadFile.setChangeListener(this); add(importFromFile); add(importFromURL); add(location); importType.setChangeListener(this); importType.setNotifyReselected(false); passwordControl = new PasswordControl(false, true); add(passwordControl); add(new SeparatorField()); } HorizontalFieldManager hfm = new HorizontalFieldManager(HorizontalFieldManager.FIELD_HCENTER); importButton = new ClickableButtonField(res.getString(actionLabel)); importButton.setChangeListener(this); hfm.add(importButton); cancelButton = new ClickableButtonField(res.getString(GENERAL_LBL_CANCEL)); cancelButton.setChangeListener(this); hfm.add(cancelButton); add(hfm); } /* * (non-Javadoc) * * @see net.rim.device.api.ui.Screen#isDirty() */ public boolean isDirty() { return true; } /* * (non-Javadoc) * * @see net.rim.device.api.ui.Screen#onSavePrompt() */ protected boolean onSavePrompt() { return true; } /** * Do shared validation. * * @return true, if successful */ private boolean doSharedValidation() { if (friendlyName.getTextLength() == 0) { Status.show(res.getString(MSG_KEYPAIR_NAME_REQUIRED)); friendlyName.setFocus(); return false; } if (!passwordControl.isDataValid()) { passwordControl.setFocus(); return false; } return true; } private void doGenerate() { try { DSACryptoSystem dcs = new DSACryptoSystem(); DSAKeyPair pair = dcs.createDSAKeyPair(); DSAPrivateKey pk = pair.getDSAPrivateKey(); TypesWriter writer = new TypesWriter(); writer.writeString(dcs.getP()); writer.writeString(dcs.getQ()); writer.writeString(dcs.getG()); writer.writeString(pk.getPublicKeyData()); writer.writeString(pk.getPrivateKeyData()); key = new Key(friendlyName.getText(), writer.getBytes()); Status.show(res.getString(MSG_KEYPAIR_GENERATE_SUCCESS)); close(); } catch (CryptoTokenException e) { Dialog.ask(Dialog.OK, res.getString(MSG_KEYPAIR_ERROR_OCCURRED) + " (" + e.toString() + ")"); } catch (CryptoUnsupportedOperationException e) { Dialog.ask(Dialog.OK, res.getString(MSG_KEYPAIR_ERROR_OCCURRED) + " (" + e.toString() + ")"); } } private String url; private String failError; /** * Import a key from a file (http or SD card) */ private void doImport() { if (!doSharedValidation()) { return; } if (!BBSSHApp.inst().requestPermission(ApplicationPermissions.PERMISSION_FILE_API, BBSSHResource.MSG_PERMISSIONS_MISSING_FILE_IMPORT)) { importType.setSelectedIndex(1); return; } if (importType.getSelectedIndex() == 1) { // @todo we need to use connection type full rnage... url = location.getText(); if (url.length() == 0) { Status.show(res.getString(MSG_INVALID_URL_OR_LOC)); return; } } else { if (fileName == null || fileName.length() == 0) { Status.show(res.getString(IMPORT_KEY_MSG_NO_FILE)); return; } url = fileName; } try { PleaseWaitTaskMonitorScreen wait = new PleaseWaitTaskMonitorScreen(new UpdatingBackgroundTask() { public void execute() { try { updateListener(res.getString(SETTINGS_LBL_LOADING_KEY)); key = KeyManager.loadKey(friendlyName.getText(), url); } catch (IOException ex) { failError = ex.getMessage(); Logger.error("Error checking for updates: " + failError); key = null; } } }, true); wait.launch(); if (failError != null) { Dialog.ask(Dialog.D_OK, failError); failError = null; } if (key == null) return; PEMStructure keyData = PEMDecoder.parsePEM(key.getData()); if (PEMDecoder.isPEMEncrypted(keyData)) { // if it's encrypted and no passphrase is supplied, warn the user that we can't validate // until they go to use it. String passPhrase = passwordControl.getPassword(); if (passPhrase.length() == 0) { if (Dialog.ask(Dialog.D_YES_NO, res.getString(MSG_PASSPHRASE_WARN)) == Dialog.NO) { passwordControl.setFocus(); key = null; return; } } else { try { PEMDecoder.decode(key.getData(), passPhrase); key.setPassphrase(passPhrase); } catch (Throwable e) { if (e.getMessage() == null || e.getMessage().length() == 0) { Status.show(res.getString(MSG_KEYPAIR_PASSWORD_FAILED)); } else { Status.show(res.getString(MSG_KEYPAIR_PASSWORD_FAILED) + " (" + e.getMessage() + ")"); } key = null; passwordControl.setFocus(); return; } } } Status.show(res.getString(MSG_KEYPAIR_IMPORT_SUCCESS)); close(); } catch (IllegalArgumentException e) { Dialog.ask(Dialog.OK, res.getString(MSG_KEYPAIR_ERROR_OCCURRED) + " (" + e.getMessage() + ")"); location.setFocus(); } catch (IOException e) { Dialog.ask(Dialog.OK, res.getString(MSG_KEYPAIR_ERROR_OCCURRED) + " (" + e.getMessage() + ")"); } } /* * (non-Javadoc) * * @see net.rim.device.api.ui.FieldChangeListener#fieldChanged(net.rim.device.api.ui.Field, int) */ public void fieldChanged(Field field, int context) { if (field == importButton) { if (generateNew) { doGenerate(); } else { doImport(); } } else if (field == cancelButton) { UiApplication.getUiApplication().popScreen(this); } else if (field == importFromURL) { // We receive two alerts : first is from selecting the new one // and the second is deselecting the old one. if (importType.getSelectedIndex() == 0) { replace(location, loadFile); } } else if (field == importFromFile) { if (importType.getSelectedIndex() == 1) { // changed selection replace(loadFile, location); } } else if (field == loadFile) { FileSelectorPopupScreen pop = PlatformServicesProvider.getFileSelectorPopup(); pop.setType(FileSelectorPopupScreen.TYPE_OPEN); fileName = pop.pickFile(); } } /** * @return the imported key object */ public Key getKey() { return key; } }