diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpPublicPrivateKeyUtils.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpPublicPrivateKeyUtils.java new file mode 100644 index 00000000..72b6601d --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpPublicPrivateKeyUtils.java @@ -0,0 +1,216 @@ +package keepass2android.javafilestorage; + +import android.util.Pair; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringReader; +import java.util.regex.Pattern; + +import androidx.annotation.Nullable; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.KeyPair; + +class SftpPublicPrivateKeyUtils { + + private enum Validity { + NOT_ATTEMPTED, VALID, NOT_VALID; + } + + private static final String SFTP_CUSTOM_KEY_DIRNAME = "user_keys"; + + private static final String KP2A_PRIVATE_KEY_FILENAME = "id_kp2a_rsa"; + + private final File appBaseDir; + + /** + * Do NOT access this variable directly! Use {@link #baseDir()} instead. + */ + private final File customKeyBaseDir; + private volatile Validity validDir = Validity.NOT_ATTEMPTED; + + SftpPublicPrivateKeyUtils(String appBaseDir) { + // Assume app base directory exists already + this.appBaseDir = new File(appBaseDir); + + // Intentionally skipping existence/creation checking in constructor + // See baseDir() + this.customKeyBaseDir = new File(appBaseDir, SFTP_CUSTOM_KEY_DIRNAME); + } + + private Pair baseDir() { + if (validDir == Validity.NOT_ATTEMPTED) { + synchronized (this) { + if (!customKeyBaseDir.exists()) { + customKeyBaseDir.mkdirs(); + } + if (customKeyBaseDir.exists() && customKeyBaseDir.isDirectory()) { + validDir = Validity.VALID; + } else { + validDir = Validity.NOT_VALID; + } + } + } + return new Pair<>(customKeyBaseDir, validDir == Validity.VALID); + } + + boolean deleteCustomKey(String keyName) throws FileNotFoundException { + File f = getCustomKeyFile(keyName); + return f.isFile() && f.delete(); + } + + String[] getCustomKeyNames() { + Pair base = baseDir(); + if (!base.second) { + // Log it? + return new String[]{}; + } + return base.first.list(); + } + + void savePrivateKeyContent(String keyName, String keyContent) throws IOException, Exception { + keyContent = PrivateKeyValidator.ensureValidContent(keyContent); + + File f = getCustomKeyFile(keyName); + try (BufferedWriter w = new BufferedWriter(new FileWriter(f))) { + w.write(keyContent); + } + } + + String getCustomKeyFilePath(String customKeyName) throws FileNotFoundException { + return getCustomKeyFile(customKeyName).getAbsolutePath(); + } + + String resolveKeyFilePath(JSch jschInst, @Nullable String customKeyName) { + // Custom private key configured + if (customKeyName != null) { + try { + return getCustomKeyFilePath(customKeyName); + } catch (FileNotFoundException e) { + System.out.println(e); + } + } + // Use KP2A's public/private key + String keyFilePath = getAppKeyFileName(); + try{ + createKeyPair(jschInst, keyFilePath); + } catch (Exception ex) { + System.out.println(ex); + } + return keyFilePath; + } + + String createKeyPair(JSch jschInst) throws IOException, JSchException { + return createKeyPair(jschInst, getAppKeyFileName()); + } + + /** + * Exposed for testing purposes only + * @param keyName + * @return + */ + String getSanitizedCustomKeyName(String keyName) { + return PrivateKeyValidator.sanitizeKeyAsFilename(keyName); + } + + /** + * Exposed for testing purposes only. + * @param keyContent + * @return + * @throws Exception + */ + String getValidatedCustomKeyContent(String keyContent) throws Exception { + return PrivateKeyValidator.ensureValidContent(keyContent); + } + + private String createKeyPair(JSch jschInst, String key_filename) throws JSchException, IOException { + String public_key_filename = key_filename + ".pub"; + File file = new File(key_filename); + if (file.exists()) + return public_key_filename; + int type = KeyPair.RSA; + KeyPair kpair = KeyPair.genKeyPair(jschInst, type, 4096); + kpair.writePrivateKey(key_filename); + + kpair.writePublicKey(public_key_filename, "generated by Keepass2Android"); + //ret = "Fingerprint: " + kpair.getFingerPrint(); + kpair.dispose(); + return public_key_filename; + } + + private String getAppKeyFileName() { + return new File(appBaseDir, KP2A_PRIVATE_KEY_FILENAME).getAbsolutePath(); + } + + private File getCustomKeyFile(String customKeyName) throws FileNotFoundException { + Pair base = baseDir(); + if (!base.second) { + throw new FileNotFoundException("Custom key directory"); + } + + String keyFileName = PrivateKeyValidator.sanitizeKeyAsFilename(customKeyName); + if (!keyFileName.isEmpty()) { + File keyFile = new File(base.first, keyFileName); + // Protect against bad actors trying to navigate away from the base directory. + // This is probably overkill, given sanitizeKeyAsFilename(...) but better safe than sorry. + if (base.first.equals(keyFile.getParentFile())) { + return keyFile; + } + } + // The key was sanitized to nothing, or the parent check above failed. + throw new FileNotFoundException("Malformed key name"); + } + + + private static class PrivateKeyValidator { + private static final Pattern CONTENT_FIRST_LINE = Pattern.compile("^-+BEGIN\\s[^\\s]+\\sPRIVATE\\sKEY-+$"); + private static final Pattern CONTENT_LAST_LINE = Pattern.compile("^-+END\\s[^\\s]+\\sPRIVATE\\sKEY-+$"); + + /** + * Key-to-filename sanitizer solution sourced from: + * + */ + private static final Pattern KEY_SANITIZER = Pattern.compile("([^\\p{L}\\s\\d\\-_~,;:\\[\\]\\(\\).'])", + Pattern.CASE_INSENSITIVE); + + static String sanitizeKeyAsFilename(String key) { + return KEY_SANITIZER.matcher(key.trim()).replaceAll(""); + } + + static String ensureValidContent(String content) throws Exception { + content = content.trim(); + + boolean isValid = true; + try (BufferedReader r = new BufferedReader(new StringReader(content))) { + boolean validFirst = false; + String line; + String last = null; + while ((line = r.readLine()) != null) { + if (!validFirst) { + if (CONTENT_FIRST_LINE.matcher(line).matches()) { + validFirst = true; + } else { + isValid = false; + break; + } + } + last = line; + } + if (!isValid || last == null || !CONTENT_LAST_LINE.matcher(last).matches()) { + throw new RuntimeException("Malformed private key content"); + } + } catch (Exception e) { + android.util.Log.d(SftpStorage.class.getName(), "Invalid key content", e); + throw e; + } + + return content; + } + } +} diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java index 01bc17fe..c9b4d0aa 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java @@ -2,7 +2,6 @@ package keepass2android.javafilestorage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -20,7 +19,6 @@ import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.KeyPair; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpATTRS; import com.jcraft.jsch.SftpException; @@ -30,11 +28,32 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +@SuppressWarnings("unused") // Exposed by JavaFileStorageBindings public class SftpStorage extends JavaFileStorageBase { + @FunctionalInterface + interface ValueResolver { + /** + * Takes a raw value and resolves it to either a String containing the String representation + * of that value, or null. The latter signifying that the raw value could not be "resolved". + * + * @param value + * @return String, or null if not resolvable + */ + String resolve(T value); + } public static final int DEFAULT_SFTP_PORT = 22; public static final int UNSET_SFTP_CONNECT_TIMEOUT = -1; private static final String SFTP_CONNECT_TIMEOUT_OPTION_NAME = "connectTimeout"; + private static final String SFTP_KEYNAME_OPTION_NAME = "key"; + private static final String SFTP_KEYPASSPHRASE_OPTION_NAME = "phrase"; + + private static final ValueResolver cTimeoutResolver = c -> + c == null || c == UNSET_SFTP_CONNECT_TIMEOUT ? null : String.valueOf(c); + + private static final ValueResolver nonBlankStringResolver = s -> + s == null || s.isBlank() ? null : s; + JSch jsch; @@ -44,39 +63,36 @@ public class SftpStorage extends JavaFileStorageBase { public String username; public String password; public String localPath; + public String keyName; + public String keyPassphrase; public int port; public int connectTimeoutSec = UNSET_SFTP_CONNECT_TIMEOUT; public String toString() { return "ConnectionInfo{host=" + host + ",port=" + port + ",user=" + username + - ",pwd=,path=" + localPath + ",connectTimeout=" + connectTimeoutSec + + ",pwd=,localPath=" + localPath + ",key=" + keyName + + ",phrase=,connectTimeout=" + connectTimeoutSec + "}"; } + } - boolean hasOptions() { - return connectTimeoutSec != UNSET_SFTP_CONNECT_TIMEOUT; + private static Map buildOptionMap(ConnectionInfo ci, boolean includeSensitive) { + OptionMapBuilder b = new OptionMapBuilder() + .addOption(SFTP_CONNECT_TIMEOUT_OPTION_NAME, ci.connectTimeoutSec, cTimeoutResolver) + .addOption(SFTP_KEYNAME_OPTION_NAME, ci.keyName, nonBlankStringResolver); + if (includeSensitive) { + b.addOption(SFTP_KEYPASSPHRASE_OPTION_NAME, ci.keyPassphrase, nonBlankStringResolver); } + return b.build(); } - private static Map buildOptionMap(ConnectionInfo ci) { - return buildOptionMap(ci.connectTimeoutSec); - } - - private static Map buildOptionMap(int connectTimeoutSec) { - Map opts = new HashMap<>(); - if (connectTimeoutSec != UNSET_SFTP_CONNECT_TIMEOUT) { - opts.put(SFTP_CONNECT_TIMEOUT_OPTION_NAME, String.valueOf(connectTimeoutSec)); - } - return opts; - } - - Context _appContext; + private final SftpPublicPrivateKeyUtils _keyUtils; public SftpStorage(Context appContext) { _appContext = appContext; - + _keyUtils = new SftpPublicPrivateKeyUtils(getBaseDir()); } private static final String SFTP_PROTOCOL_ID = "sftp"; @@ -184,7 +200,8 @@ public class SftpStorage extends JavaFileStorageBase { tryDisconnect(c); return buildFullPath(cInfo.host, cInfo.port, newPath, - cInfo.username, cInfo.password, cInfo.connectTimeoutSec); + cInfo.username, cInfo.password, cInfo.connectTimeoutSec, + cInfo.keyName, cInfo.keyPassphrase); } catch (Exception e) { throw convertException(e); } @@ -402,28 +419,23 @@ public class SftpStorage extends JavaFileStorageBase { return java.net.URLEncoder.encode(unencoded, UTF_8); } + ChannelSftp init(ConnectionInfo cInfo) throws JSchException, UnsupportedEncodingException { jsch = new JSch(); String base_dir = getBaseDir(); jsch.setKnownHosts(base_dir + "/known_hosts"); - String key_filename = getKeyFileName(); - try{ - createKeyPair(key_filename); - } catch (Exception ex) { - System.out.println(ex); - } + String key_filepath = _keyUtils.resolveKeyFilePath(jsch, cInfo.keyName); try { - jsch.addIdentity(key_filename); - } catch (java.lang.Exception e) - { + jsch.addIdentity(key_filepath); + } catch (java.lang.Exception e) { } Session session = jsch.getSession(cInfo.username, cInfo.host, cInfo.port); - UserInfo ui = new SftpUserInfo(cInfo.password, _appContext); + UserInfo ui = new SftpUserInfo(cInfo.password, cInfo.keyPassphrase, _appContext); session.setUserInfo(ui); session.setConfig("PreferredAuthentications", "publickey,password"); @@ -434,7 +446,6 @@ public class SftpStorage extends JavaFileStorageBase { channel.connect(); ChannelSftp c = (ChannelSftp) channel; - logDebug("success: init Sftp"); return c; } @@ -451,38 +462,60 @@ public class SftpStorage extends JavaFileStorageBase { return _appContext.getFilesDir().getAbsolutePath(); } - private String getKeyFileName() { - return getBaseDir() + "/id_kp2a_rsa"; + public boolean deleteCustomKey(String keyName) throws FileNotFoundException { + return _keyUtils.deleteCustomKey(keyName); } + public String[] getCustomKeyNames() { + return _keyUtils.getCustomKeyNames(); + } + + @SuppressWarnings("unused") // Exposed by JavaFileStorageBindings public String createKeyPair() throws IOException, JSchException { - return createKeyPair(getKeyFileName()); - + return _keyUtils.createKeyPair(jsch); } - private String createKeyPair(String key_filename) throws JSchException, IOException { - String public_key_filename = key_filename + ".pub"; - File file = new File(key_filename); - if (file.exists()) - return public_key_filename; - int type = KeyPair.RSA; - KeyPair kpair = KeyPair.genKeyPair(jsch, type, 4096); - kpair.writePrivateKey(key_filename); - - kpair.writePublicKey(public_key_filename, "generated by Keepass2Android"); - //ret = "Fingerprint: " + kpair.getFingerPrint(); - kpair.dispose(); - return public_key_filename; + @SuppressWarnings("unused") // Exposed by JavaFileStorageBindings + public void savePrivateKeyContent(String keyName, String keyContent) throws IOException, Exception { + _keyUtils.savePrivateKeyContent(keyName, keyContent); } + /** + * Exposed for testing purposes only. + * @param keyName + * @return + */ + public String sanitizeCustomKeyName(String keyName) { + return _keyUtils.getSanitizedCustomKeyName(keyName); + } + + /** + * Exposed for testing purposes only. + * @param keyContent + * @return + * @throws Exception + */ + public String getValidatedCustomKeyContent(String keyContent) throws Exception { + return _keyUtils.getValidatedCustomKeyContent(keyContent); + } + + public ConnectionInfo splitStringToConnectionInfo(String filename) throws UnsupportedEncodingException { ConnectionInfo ci = new ConnectionInfo(); ci.host = extractUserPwdHostPort(filename); + String userPwd = ci.host.substring(0, ci.host.indexOf('@')); - ci.username = decode(userPwd.substring(0, userPwd.indexOf(":"))); - ci.password = decode(userPwd.substring(userPwd.indexOf(":")+1)); + int sepIdx = userPwd.indexOf(":"); + if (sepIdx > 0) { + ci.username = decode(userPwd.substring(0, sepIdx)); + ci.password = decode(userPwd.substring(sepIdx + 1)); + } else { + ci.username = userPwd; + ci.password = null; + } + ci.host = ci.host.substring(ci.host.indexOf('@') + 1); ci.port = DEFAULT_SFTP_PORT; int portSeparatorIndex = ci.host.indexOf(":"); @@ -503,6 +536,12 @@ public class SftpStorage extends JavaFileStorageBase { logDebug(SFTP_CONNECT_TIMEOUT_OPTION_NAME + " option not a number: " + optVal); } } + if (options.containsKey(SFTP_KEYNAME_OPTION_NAME)) { + ci.keyName = options.get(SFTP_KEYNAME_OPTION_NAME); + } + if (options.containsKey(SFTP_KEYPASSPHRASE_OPTION_NAME)) { + ci.keyPassphrase = options.get(SFTP_KEYPASSPHRASE_OPTION_NAME); + } return ci; } @@ -544,9 +583,7 @@ public class SftpStorage extends JavaFileStorageBase { .append("@") .append(ci.host) .append(ci.localPath); - if (ci.hasOptions()) { - appendOptions(dName, buildOptionMap(ci)); - } + appendOptions(dName, buildOptionMap(ci, false)); return dName.toString(); } catch (Exception e) @@ -580,11 +617,15 @@ public class SftpStorage extends JavaFileStorageBase { public String buildFullPath(String host, int port, String localPath, String username, String password, - int connectTimeoutSec) + int connectTimeoutSec, + String keyName, String keyPassphrase) throws UnsupportedEncodingException { - StringBuilder uri = new StringBuilder(getProtocolPrefix()) - .append(encode(username)).append(":").append(encode(password)) - .append("@").append(host); + + StringBuilder uri = new StringBuilder(getProtocolPrefix()).append(encode(username)); + if (password != null) { + uri.append(":").append(encode(password)); + } + uri.append("@").append(host); if (port != DEFAULT_SFTP_PORT) { uri.append(":").append(port); @@ -593,8 +634,11 @@ public class SftpStorage extends JavaFileStorageBase { uri.append(localPath); } - Map opts = buildOptionMap(connectTimeoutSec); - appendOptions(uri, opts); + appendOptions(uri, new OptionMapBuilder() + .addOption(SFTP_CONNECT_TIMEOUT_OPTION_NAME, connectTimeoutSec, cTimeoutResolver) + .addOption(SFTP_KEYNAME_OPTION_NAME, keyName, nonBlankStringResolver) + .addOption(SFTP_KEYPASSPHRASE_OPTION_NAME, keyPassphrase, nonBlankStringResolver) + .build()); return uri.toString(); } @@ -635,4 +679,31 @@ public class SftpStorage extends JavaFileStorageBase { return o1.getKey().compareTo(o2.getKey()); } } + + private static class OptionMapBuilder { + private final Map options = new HashMap<>(); + + /** + * Attempts to add a raw value oVal to the underlying option map with key oName + * iff the resolver produces a non-null output when invoked using the raw value. + * + * @param oName the name/key associated with the value, if added + * @param oVal the raw value attempting to be added + * @param resolver the resolver that determines if the value will be added + * + * @return OptionMapBuilder (updated) + * @param the raw value type + */ + OptionMapBuilder addOption(final String oName, T oVal, ValueResolver resolver) { + String resolved = resolver.resolve(oVal); + if (resolved != null) { + options.put(oName, resolved); + } + return this; + } + + Map build() { + return new HashMap<>(options); + } + } } diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpUserInfo.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpUserInfo.java index 6737535c..569996da 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpUserInfo.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpUserInfo.java @@ -113,17 +113,19 @@ public class SftpUserInfo implements UserInfo { Context _appContext; String _password; + String _passphrase; - public SftpUserInfo(String password, Context appContext) + public SftpUserInfo(String password, String passphrase, Context appContext) { _password = password; + _passphrase = passphrase; _appContext = appContext; } @Override public String getPassphrase() { - - return null; + + return _passphrase; } @Override @@ -134,12 +136,12 @@ public class SftpUserInfo implements UserInfo { @Override public boolean promptPassword(String message) { - return true; + return _password != null; } @Override public boolean promptPassphrase(String message) { - return false; //passphrase not supported + return _passphrase != null; } @Override diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest2/MainActivity.java b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest2/MainActivity.java index 0fff315a..2b13fd92 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest2/MainActivity.java +++ b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest2/MainActivity.java @@ -697,12 +697,11 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag if (protocolId.equals("sftp")) { final View view = getLayoutInflater().inflate(R.layout.sftp_credentials, null); + final SftpStorage sftpStorage = (SftpStorage)storageToTest; view.findViewById(R.id.send_public_key).setOnClickListener(v -> { Intent sendIntent = new Intent(); - - SftpStorage sftpStorage = (SftpStorage)storageToTest; try { String pub_filename = sftpStorage.createKeyPair(); @@ -715,51 +714,121 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag } catch (Exception ex) { - Toast.makeText(this,"Failed to create key pair: " + ex.getMessage(), Toast.LENGTH_LONG); - return; + Toast.makeText(this,"Failed to create key pair: " + ex.getMessage(), Toast.LENGTH_LONG).show(); } - - }); + + view.findViewById(R.id.list_private_keys).setOnClickListener(v -> { + String[] keys = sftpStorage.getCustomKeyNames(); + Toast.makeText(this, "keys: " + String.join(",", keys), Toast.LENGTH_LONG).show(); + }); + + view.findViewById(R.id.add_private_key).setOnClickListener(v -> { + EditText etKeyName = view.findViewById(R.id.private_key_name); + String keyName = etKeyName.getText().toString(); + EditText etKeyContent = view.findViewById(R.id.private_key_content); + String keyContent = etKeyContent.getText().toString(); + + try { + sftpStorage.savePrivateKeyContent(keyName, keyContent); + Toast.makeText(this, "Add successful", Toast.LENGTH_LONG).show(); + } + catch (Exception e) { + Toast.makeText(this, "Add failed: " + e.getMessage(), Toast.LENGTH_LONG).show(); + } + }); + + view.findViewById(R.id.delete_private_key).setOnClickListener(v -> { + EditText etKeyName = view.findViewById(R.id.private_key_name); + String keyName = etKeyName.getText().toString(); + + String exMessage = null; + boolean success = false; + try { + success = sftpStorage.deleteCustomKey(keyName); + } + catch (Exception e) { + exMessage = e.getMessage(); + } + StringBuilder msg = new StringBuilder("Delete "); + msg.append(success ? "succeeded" : "FAILED"); + if (exMessage != null) { + msg.append(" (").append(exMessage).append(")"); + } + Toast.makeText(this, msg.toString(), Toast.LENGTH_LONG).show(); + }); + + view.findViewById(R.id.validate_private_key).setOnClickListener(v -> { + EditText etKeyName = view.findViewById(R.id.private_key_name); + String inKeyName = etKeyName.getText().toString(); + + if (!inKeyName.isEmpty()) { + String keyResponse; + try { + keyResponse = sftpStorage.sanitizeCustomKeyName(inKeyName); + } catch (Exception e) { + keyResponse = "EX:" + e.getMessage(); + } + String msg = "key: [" + inKeyName + "] -> [" + keyResponse + "]"; + Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); + } + + EditText etKeyContent = view.findViewById(R.id.private_key_content); + String inKeyContent = etKeyContent.getText().toString(); + String msg; + if (!inKeyContent.isEmpty()) { + try { + // We could print the key, but I don't it's that helpful + sftpStorage.getValidatedCustomKeyContent(inKeyContent); + msg = "Key content is valid"; + } catch (Exception e) { + msg = "Invalid key content: " + e.getMessage(); + } + Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); + } + }); + new AlertDialog.Builder(this) .setView(view) .setTitle("Enter SFTP credentials") - .setPositiveButton("OK",new DialogInterface.OnClickListener() { + .setPositiveButton("OK", (dialog, which) -> { - @Override - public void onClick(DialogInterface dialog, int which) { + Toast.makeText(MainActivity.this, "Hey", Toast.LENGTH_LONG).show(); - Toast.makeText(MainActivity.this, "Hey", Toast.LENGTH_LONG).show(); - - SftpStorage sftpStorage = (SftpStorage)storageToTest; - try { - EditText etHost = ((EditText)view.findViewById(R.id.sftp_host)); - String host = etHost.getText().toString(); - EditText etUser = ((EditText)view.findViewById(R.id.sftp_user)); - String user = etUser.getText().toString(); - EditText etPwd = ((EditText)view.findViewById(R.id.sftp_password)); - String pwd = etPwd.getText().toString(); - EditText etPort = ((EditText)view.findViewById(R.id.sftp_port)); - int port = Integer.parseInt(etPort.getText().toString()); - EditText etInitDir = ((EditText)view.findViewById(R.id.sftp_initial_dir)); - String initialDir = etInitDir.getText().toString(); - EditText etConnectTimeout = ((EditText)view.findViewById(R.id.sftp_connect_timeout)); - int connectTimeout = SftpStorage.UNSET_SFTP_CONNECT_TIMEOUT; - String ctStr = etConnectTimeout.getText().toString(); - if (!ctStr.isEmpty()) { - try { - int ct = Integer.parseInt(ctStr); - if (connectTimeout != ct) { - connectTimeout = ct; - } - } catch (NumberFormatException parseEx) { + SftpStorage sftpStorage1 = (SftpStorage)storageToTest; + try { + EditText etHost = view.findViewById(R.id.sftp_host); + String host = etHost.getText().toString(); + EditText etUser = view.findViewById(R.id.sftp_user); + String user = etUser.getText().toString(); + EditText etPwd = view.findViewById(R.id.sftp_password); + String pwd = etPwd.getText().toString(); + EditText etPort = view.findViewById(R.id.sftp_port); + int port = Integer.parseInt(etPort.getText().toString()); + EditText etInitDir = view.findViewById(R.id.sftp_initial_dir); + String initialDir = etInitDir.getText().toString(); + EditText etConnectTimeout = view.findViewById(R.id.sftp_connect_timeout); + int connectTimeout = SftpStorage.UNSET_SFTP_CONNECT_TIMEOUT; + String ctStr = etConnectTimeout.getText().toString(); + if (!ctStr.isEmpty()) { + try { + int ct = Integer.parseInt(ctStr); + if (connectTimeout != ct) { + connectTimeout = ct; } + } catch (NumberFormatException parseEx) { } - onReceivePathForFileSelect(requestCode, sftpStorage.buildFullPath( host, port, initialDir, user, pwd, connectTimeout)); - } catch (UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); } + EditText etKeyName = view.findViewById(R.id.private_key_name); + String keyName = etKeyName.getText().toString(); + EditText etKeyPassphrase = view.findViewById(R.id.private_key_passphrase); + String keyPassphrase = etKeyPassphrase.getText().toString(); + + onReceivePathForFileSelect(requestCode, sftpStorage1.buildFullPath( + host, port, initialDir, user, pwd, connectTimeout, + keyName, keyPassphrase)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); } }) .create() diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/res/layout/sftp_credentials.xml b/src/java/JavaFileStorageTest-AS/app/src/main/res/layout/sftp_credentials.xml index 02c6e6d4..43f5aef0 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/res/layout/sftp_credentials.xml +++ b/src/java/JavaFileStorageTest-AS/app/src/main/res/layout/sftp_credentials.xml @@ -77,5 +77,59 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="send public key" /> + + + +