From c6ff386d0cda108454cfa1d9facbb0432d601cde Mon Sep 17 00:00:00 2001 From: Sven Nissel Date: Tue, 22 Feb 2022 00:18:35 +0100 Subject: [PATCH 1/3] #582 update to active supported fork of jsch support of newer cyphers --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8e632ed6..7cf087b0 100644 --- a/build.gradle +++ b/build.gradle @@ -50,7 +50,7 @@ project(':Common-libs') { compile 'com.beust:jcommander:1.29' compile 'com.google.guava:guava:23.0' compile 'com.google.code.gson:gson:2.8.2' - compile 'com.jcraft:jsch:0.1.+' + compile 'com.github.mwiede:jsch:0.2.0' compile 'com.jcraft:jzlib:1.+' compile 'com.miglayout:miglayout-swing:4.2' compile 'com.fifesoft:rsyntaxtextarea:2.6.1' From 4fbd89126d3919b0b8fbd2fcc4f567dd169e7345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20N=C3=BCssgens?= Date: Sun, 6 Mar 2022 18:18:04 +0100 Subject: [PATCH 2/3] #273 enable pageant sftp authentication (for windows) --- .../otros/vfs/browser/FileObjectWrapper.java | 59 +++++++++++++- .../browser/auth/SftpUserAuthenticator.java | 27 +++++-- .../PageantIdentityRepositoryFactory.java | 77 +++++++++++++++++++ .../pl/otros/vfs/browser/util/VFSUtils.java | 4 +- .../main/resources/i18n/messages.properties | 2 + .../resources/i18n/messages_de.properties | 2 + build.gradle | 4 +- 7 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/PageantIdentityRepositoryFactory.java diff --git a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/FileObjectWrapper.java b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/FileObjectWrapper.java index 6117b93a..715c4412 100644 --- a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/FileObjectWrapper.java +++ b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/FileObjectWrapper.java @@ -18,8 +18,10 @@ import org.apache.commons.vfs2.*; import org.apache.commons.vfs2.operations.FileOperations; +import org.jetbrains.annotations.NotNull; import java.net.URL; +import java.util.Iterator; import java.util.List; public class FileObjectWrapper implements FileObject { @@ -62,6 +64,11 @@ public FileObject getParent() throws FileSystemException { return parent.getParent(); } + @Override + public String getPublicURIString() { + return parent.getPublicURIString(); + } + public FileSystem getFileSystem() { return parent.getFileSystem(); } @@ -75,16 +82,31 @@ public FileObject getChild(String name) throws FileSystemException { } public FileObject resolveFile(String name, NameScope scope) - throws FileSystemException { + throws FileSystemException { return parent.resolveFile(name, scope); } + @Override + public boolean setExecutable(boolean executable, boolean ownerOnly) throws FileSystemException { + return parent.setExecutable(executable, ownerOnly); + } + + @Override + public boolean setReadable(boolean readable, boolean ownerOnly) throws FileSystemException { + return parent.setReadable(readable, ownerOnly); + } + + @Override + public boolean setWritable(boolean writable, boolean ownerOnly) throws FileSystemException { + return parent.setWritable(writable, ownerOnly); + } + public FileObject resolveFile(String path) throws FileSystemException { return parent.resolveFile(path); } public FileObject[] findFiles(FileSelector selector) - throws FileSystemException { + throws FileSystemException { return parent.findFiles(selector); } @@ -101,6 +123,11 @@ public int delete(FileSelector selector) throws FileSystemException { return parent.delete(selector); } + @Override + public int deleteAll() throws FileSystemException { + return parent.deleteAll(); + } + public void createFolder() throws FileSystemException { parent.createFolder(); } @@ -110,7 +137,7 @@ public void createFile() throws FileSystemException { } public void copyFrom(FileObject srcFile, FileSelector selector) - throws FileSystemException { + throws FileSystemException { parent.copyFrom(srcFile, selector); } @@ -142,7 +169,33 @@ public boolean isContentOpen() { return parent.isContentOpen(); } + @Override + public boolean isExecutable() throws FileSystemException { + return parent.isExecutable(); + } + + @Override + public boolean isFile() throws FileSystemException { + return parent.isFile(); + } + + @Override + public boolean isFolder() throws FileSystemException { + return parent.isFolder(); + } + public FileOperations getFileOperations() throws FileSystemException { return parent.getFileOperations(); } + + @Override + public int compareTo(@NotNull FileObject o) { + return parent.compareTo(o); + } + + @NotNull + @Override + public Iterator iterator() { + return parent.iterator(); + } } diff --git a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java index 2703fec0..5a86c931 100644 --- a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java +++ b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java @@ -16,17 +16,18 @@ package pl.otros.vfs.browser.auth; -import pl.otros.vfs.browser.i18n.Messages; import org.apache.commons.lang.StringUtils; -import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.FileSystemOptions; import org.apache.commons.vfs2.UserAuthenticationData; import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder; +import pl.otros.vfs.browser.i18n.Messages; +import pl.otros.vfs.browser.util.PageantIdentityRepositoryFactory; import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; +import java.util.OptionalInt; public class SftpUserAuthenticator extends UserPassUserAuthenticator { @@ -44,12 +45,8 @@ protected void getAuthenticationData(UserAuthenticationData authenticationData) authenticationData.setData(UserAuthenticationDataWrapper.SSH_KEY, sshKeyFileField.getText().trim().toCharArray()); if (StringUtils.isNotBlank(sshKeyFileField.getText())) { - try { SftpFileSystemConfigBuilder.getInstance().setIdentities(getFileSystemOptions(), new File[]{new File(sshKeyFileField.getText())}); //TODO set user auth data file path - } catch (FileSystemException e) { - e.printStackTrace(); - } } } @@ -79,12 +76,26 @@ public void actionPerformed(ActionEvent e) { } } }); - panel.add(browseButton,"wrap"); - panel.add(new JLabel(Messages.getMessage("authenticator.sshKeyFileDescription")),"span"); + panel.add(browseButton, "wrap"); + panel.add(new JLabel(Messages.getMessage("authenticator.sshKeyFileDescription")), "span"); + + OptionalInt pageantActive = this.isPageantActive(); + String pageantInfo; + if (pageantActive.isPresent()) { + pageantInfo = Messages.getMessage("authenticator.pageantActiveCount", pageantActive.getAsInt()); + } else { + pageantInfo = Messages.getMessage("authenticator.pageantInactive"); + } + + panel.add(new JLabel(pageantInfo), "span"); return panel; } + private OptionalInt isPageantActive() { + return PageantIdentityRepositoryFactory.getIdentitiesCount(); + } + @Override protected void userSelectedHook(UserAuthenticationData userAuthenticationData) { diff --git a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/PageantIdentityRepositoryFactory.java b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/PageantIdentityRepositoryFactory.java new file mode 100644 index 00000000..e126c78d --- /dev/null +++ b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/PageantIdentityRepositoryFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 Krzysztof Otrebski (otros.systems@gmail.com) + * + * 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 pl.otros.vfs.browser.util; + +import com.jcraft.jsch.*; +import org.apache.commons.vfs2.provider.sftp.IdentityRepositoryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.OptionalInt; + + +public final class PageantIdentityRepositoryFactory implements IdentityRepositoryFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(PageantIdentityRepositoryFactory.class); + private static final byte SSH2_AGENTC_REQUEST_IDENTITIES = 11; + private static final byte SSH2_AGENT_IDENTITIES_ANSWER = 12; + private static final int MAX_AGENT_IDENTITIES = 2048; + + @Override + public IdentityRepository create(JSch jsch) { + try { + AgentConnector con = new PageantConnector(); + return new AgentIdentityRepository(con); + } catch (AgentProxyException | RuntimeException e) { + LOGGER.info("Unable to load PageantConnector", e); + return null; + } + + } + + public static OptionalInt getIdentitiesCount() { + try { + AgentConnector connector = new PageantConnector(); + + byte[] buf = new byte[1024]; + Buffer buffer = new Buffer(buf); + + int required_size = 1 + 4; + buffer.reset(); + buffer.putInt(required_size - 4); + buffer.putByte(SSH2_AGENTC_REQUEST_IDENTITIES); + + try { + connector.query(buffer); + } catch (AgentProxyException e) { + return OptionalInt.empty(); + } + + int rcode = buffer.getByte(); + if (rcode != SSH2_AGENT_IDENTITIES_ANSWER) { + return OptionalInt.empty(); + } + + int count = buffer.getInt(); + if (count <= 0 || count > MAX_AGENT_IDENTITIES) { + return OptionalInt.empty(); + } + return OptionalInt.of(count); + } catch (AgentProxyException e) { + return OptionalInt.empty(); + } + } +} diff --git a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/VFSUtils.java b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/VFSUtils.java index b5a7975e..d30f175f 100755 --- a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/VFSUtils.java +++ b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/util/VFSUtils.java @@ -47,6 +47,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.time.Duration; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -294,6 +295,7 @@ public static FileObject resolveFileObject(String filePath) throws FileSystemExc builder.setStrictHostKeyChecking(opts, "no"); builder.setUserDirIsRoot(opts, false); builder.setCompression(opts, "zlib,none"); + builder.setIdentityRepositoryFactory(opts, new PageantIdentityRepositoryFactory()); } else if (filePath.startsWith("smb://")) { @@ -333,7 +335,7 @@ public static FileObject resolveFileObject(String filePath, FileSystemOptions op builder.setStrictHostKeyChecking(opts, "no"); builder.setUserDirIsRoot(opts, false); builder.setCompression(opts, "zlib,none"); - builder.setTimeout(opts,5000); + builder.setSessionTimeout(opts, Duration.ofSeconds(5)); } DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(options, authenticator); diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages.properties index 93ccfaf7..b859dafb 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages.properties @@ -23,6 +23,8 @@ authenticator.selectSshKey=Select SSH key file authenticator.sshKeyFile=SSH key file: authenticator.sshKeyFileDescription=Your key has to be without paraphrase authenticator.username=User name: +authenticator.pageantActiveCount=Pageant is active ({0} key(s) loaded) +authenticator.pageantInactive=Pageant is not active browser.checkingSFtpLinksTask=Checking for symbolic links browser.nameFilter.clearFilterText=Clear file name filter browser.nameFilter.defaultText=Type name filter diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties index cb9102d8..73cf7e65 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties @@ -22,6 +22,8 @@ authenticator.selectSshKey=SSH Key selektieren authenticator.sshKeyFile=SSH Key: authenticator.sshKeyFileDescription=Das SSH Key darf nicht mit Password gesichert werden authenticator.username=Benutzername: +authenticator.pageantActiveCount=Pageant ist aktiv ({0} Schl\u00FCssel geladen) +authenticator.pageantInactive=Pageant ist nicht aktiv browser.checkingSFtpLinksTask=Suche nach symbolische Links browser.nameFilter.clearFilterText=Filter l\u00F6schen browser.nameFilter.defaultText=Filter eingeben (CTRL+F setzt den Fokus) diff --git a/build.gradle b/build.gradle index 7cf087b0..e4e71771 100644 --- a/build.gradle +++ b/build.gradle @@ -51,6 +51,8 @@ project(':Common-libs') { compile 'com.google.guava:guava:23.0' compile 'com.google.code.gson:gson:2.8.2' compile 'com.github.mwiede:jsch:0.2.0' + compile 'net.java.dev.jna:jna-jpms:5.10.0' + compile 'net.java.dev.jna:jna-platform-jpms:5.10.0' compile 'com.jcraft:jzlib:1.+' compile 'com.miglayout:miglayout-swing:4.2' compile 'com.fifesoft:rsyntaxtextarea:2.6.1' @@ -70,7 +72,7 @@ project(':Common-libs') { compile 'org.apache.logging.log4j:log4j-api:2.17.1' compile 'org.apache.logging.log4j:log4j-core:2.17.1' compile 'org.apache.commons:commons-compress:1.3' - compile 'org.apache.commons:commons-vfs2:2.0' + compile 'org.apache.commons:commons-vfs2:2.9.0' compile 'org.slf4j:slf4j-api:1.7.33' compile 'org.swinglabs.swingx:swingx-all:1.+' compile 'org.swinglabs:jxlayer:3.0.4' From 6a5146c4094fe88544afce1bc1d7b7ce871a2df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20N=C3=BCssgens?= Date: Sun, 6 Mar 2022 19:18:50 +0100 Subject: [PATCH 3/3] #273 Use password field for SSH_KEY. SSH key can now be secured by passphrase --- .../browser/auth/SftpUserAuthenticator.java | 21 ++++++++++++++----- .../main/resources/i18n/messages.properties | 1 - .../resources/i18n/messages_de.properties | 1 - .../resources/i18n/messages_it.properties | 1 - .../resources/i18n/messages_pl.properties | 1 - .../resources/i18n/messages_ru.properties | 1 - .../resources/i18n/messages_uk.properties | 1 - 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java index 5a86c931..36ed73a6 100644 --- a/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java +++ b/OtrosVfsBrowser/src/main/java/pl/otros/vfs/browser/auth/SftpUserAuthenticator.java @@ -19,6 +19,8 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.vfs2.FileSystemOptions; import org.apache.commons.vfs2.UserAuthenticationData; +import org.apache.commons.vfs2.provider.sftp.IdentityInfo; +import org.apache.commons.vfs2.provider.sftp.IdentityProvider; import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder; import pl.otros.vfs.browser.i18n.Messages; import pl.otros.vfs.browser.util.PageantIdentityRepositoryFactory; @@ -41,12 +43,22 @@ public SftpUserAuthenticator(AuthStore authStore, String url, FileSystemOptions @Override protected void getAuthenticationData(UserAuthenticationData authenticationData) { - super.getAuthenticationData(authenticationData); - authenticationData.setData(UserAuthenticationDataWrapper.SSH_KEY, sshKeyFileField.getText().trim().toCharArray()); + authenticationData.setData(UserAuthenticationData.USERNAME, nameTf.getSelectedItem().toString().toCharArray()); if (StringUtils.isNotBlank(sshKeyFileField.getText())) { - SftpFileSystemConfigBuilder.getInstance().setIdentities(getFileSystemOptions(), new File[]{new File(sshKeyFileField.getText())}); - //TODO set user auth data file path + //use SSH KEY + authenticationData.setData(UserAuthenticationDataWrapper.SSH_KEY, sshKeyFileField.getText().trim().toCharArray()); + IdentityProvider sshKeyAuth; + if (passTx.getPassword() != null && passTx.getPassword().length > 0) { + //SSH KEY secured with password + String stringPass = new String(passTx.getPassword()); + sshKeyAuth = new IdentityInfo(new File(sshKeyFileField.getText()), stringPass.getBytes()); + } else { + sshKeyAuth = new IdentityInfo(new File(sshKeyFileField.getText())); + } + SftpFileSystemConfigBuilder.getInstance().setIdentityProvider(getFileSystemOptions(), sshKeyAuth); + } else { + authenticationData.setData(UserAuthenticationData.PASSWORD, passTx.getPassword()); } } @@ -77,7 +89,6 @@ public void actionPerformed(ActionEvent e) { } }); panel.add(browseButton, "wrap"); - panel.add(new JLabel(Messages.getMessage("authenticator.sshKeyFileDescription")), "span"); OptionalInt pageantActive = this.isPageantActive(); String pageantInfo; diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages.properties index b859dafb..05ecd29a 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages.properties @@ -21,7 +21,6 @@ authenticator.password=Password: authenticator.savePassword=Save password authenticator.selectSshKey=Select SSH key file authenticator.sshKeyFile=SSH key file: -authenticator.sshKeyFileDescription=Your key has to be without paraphrase authenticator.username=User name: authenticator.pageantActiveCount=Pageant is active ({0} key(s) loaded) authenticator.pageantInactive=Pageant is not active diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties index 73cf7e65..1624cb3e 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_de.properties @@ -20,7 +20,6 @@ authenticator.password=Password: authenticator.savePassword=Password speichern authenticator.selectSshKey=SSH Key selektieren authenticator.sshKeyFile=SSH Key: -authenticator.sshKeyFileDescription=Das SSH Key darf nicht mit Password gesichert werden authenticator.username=Benutzername: authenticator.pageantActiveCount=Pageant ist aktiv ({0} Schl\u00FCssel geladen) authenticator.pageantInactive=Pageant ist nicht aktiv diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_it.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_it.properties index d19213d4..64ea0ad9 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_it.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_it.properties @@ -21,7 +21,6 @@ authenticator.password=Password: authenticator.savePassword=Salva la password authenticator.selectSshKey=Selezionare il file chiave SSH authenticator.sshKeyFile=File chiave SSH: -authenticator.sshKeyFileDescription=La vostra chiave deve essere senza password authenticator.username=Nome utente: browser.checkingSFtpLinksTask=Verifica di link simbolici browser.nameFilter.clearFilterText=Eliminare filtro nome del file diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_pl.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_pl.properties index 3dfbf15c..15d43a46 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_pl.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_pl.properties @@ -21,7 +21,6 @@ authenticator.password=Has\u0142o: authenticator.savePassword=Zapisz has\u0142o authenticator.selectSshKey=Wybierz plik z kluczem SSH authenticator.sshKeyFile=Plik z kluczem SSH -authenticator.sshKeyFileDescription=Klucz nie moze zabezpieczony has?em authenticator.username=Nazwa u\u017Cytkownika: browser.checkingSFtpLinksTask=Sprawdzam wyst\u0119powanie link\u00F3w symbolicznych browser.nameFilter.clearFilterText=Wyczy\u015B\u0107 filtr nazwy diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_ru.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_ru.properties index 2aae790b..54014748 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_ru.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_ru.properties @@ -20,7 +20,6 @@ authenticator.password=\u041f\u0430\u0440\u043e\u043b\u044c: authenticator.savePassword=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c authenticator.selectSshKey=\u0412\u044b\u0431\u0440\u0430\u0442\u044c SSH \u0444\u0430\u0439\u043b-\u043a\u043b\u044e\u0447 authenticator.sshKeyFile=SSH \u0444\u0430\u0439\u043b-\u043a\u043b\u044e\u0447: -authenticator.sshKeyFileDescription=Your key has to be without paraphrase authenticator.username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f: browser.checkingSFtpLinksTask=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430 \u0441\u0441\u044b\u043b\u043a\u0438 browser.nameFilter.clearFilterText=\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0444\u0438\u043B\u044C\u0442\u0440 \u043F\u043E \u0438\u043C\u0435\u043D\u0438 diff --git a/OtrosVfsBrowser/src/main/resources/i18n/messages_uk.properties b/OtrosVfsBrowser/src/main/resources/i18n/messages_uk.properties index 8db8bfda..5741c0da 100644 --- a/OtrosVfsBrowser/src/main/resources/i18n/messages_uk.properties +++ b/OtrosVfsBrowser/src/main/resources/i18n/messages_uk.properties @@ -20,7 +20,6 @@ authenticator.password=\u041f\u0430\u0440\u043e\u043b\u044c: authenticator.savePassword=\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043f\u0430\u0440\u043e\u043b\u044c authenticator.selectSshKey=\u043e\u0431\u0440\u0430\u0442\u0438 SSH \u0444\u0430\u0439\u043b-\u043a\u043b\u044e\u0447 authenticator.sshKeyFile=SSH \u0444\u0430\u0439\u043b-\u043a\u043b\u044e\u0447: -authenticator.sshKeyFileDescription=Your key has to be without paraphrase authenticator.username=\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430: browser.checkingSFtpLinksTask=\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u043a\u0430 \u043d\u0430 \u0437\u0441\u0438\u043b\u043a\u0438 browser.nameFilter.clearFilterText=\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0444\u0456\u043B\u044C\u0442\u0440 \u043F\u043E \u0456\u043C\u0435\u043D\u0456