2014-11-16 23:14:06 -08:00
|
|
|
/*
|
2020-08-17 16:17:20 +02:00
|
|
|
* SPDX-FileCopyrightText: 2014 Samoilenko Yuri <kinnalru@gmail.com>
|
2014-11-16 23:14:06 -08:00
|
|
|
*
|
2020-08-17 16:17:20 +02:00
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
2018-12-25 00:10:58 +01:00
|
|
|
*/
|
2014-11-16 23:14:06 -08:00
|
|
|
|
2014-01-07 17:40:21 +04:00
|
|
|
package org.kde.kdeconnect.Plugins.SftpPlugin;
|
|
|
|
|
2014-01-13 00:15:17 +04:00
|
|
|
import android.content.Context;
|
2023-03-07 23:59:40 +01:00
|
|
|
import android.os.Build;
|
2014-01-07 17:40:21 +04:00
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import org.apache.sshd.SshServer;
|
2023-03-07 23:59:40 +01:00
|
|
|
import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory;
|
2018-08-31 21:04:22 +02:00
|
|
|
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
|
2023-10-01 16:24:16 +02:00
|
|
|
import org.apache.sshd.common.signature.SignatureDSA;
|
|
|
|
import org.apache.sshd.common.signature.SignatureECDSA;
|
|
|
|
import org.apache.sshd.common.signature.SignatureRSA;
|
2016-03-03 11:53:34 -08:00
|
|
|
import org.apache.sshd.common.util.SecurityUtils;
|
2014-01-07 17:40:21 +04:00
|
|
|
import org.apache.sshd.server.PasswordAuthenticator;
|
2014-01-16 09:51:32 +04:00
|
|
|
import org.apache.sshd.server.PublickeyAuthenticator;
|
2014-01-07 17:40:21 +04:00
|
|
|
import org.apache.sshd.server.command.ScpCommandFactory;
|
2016-06-09 13:42:15 +02:00
|
|
|
import org.apache.sshd.server.kex.DHG14;
|
2023-09-28 16:03:17 +02:00
|
|
|
import org.apache.sshd.server.kex.ECDHP256;
|
2020-04-13 20:13:31 -07:00
|
|
|
import org.apache.sshd.server.kex.ECDHP384;
|
2023-10-01 16:24:16 +02:00
|
|
|
import org.apache.sshd.server.kex.ECDHP521;
|
2014-01-07 17:40:21 +04:00
|
|
|
import org.apache.sshd.server.session.ServerSession;
|
|
|
|
import org.apache.sshd.server.sftp.SftpSubsystem;
|
2014-01-16 09:51:32 +04:00
|
|
|
import org.kde.kdeconnect.Device;
|
2016-02-17 04:48:01 -08:00
|
|
|
import org.kde.kdeconnect.Helpers.RandomHelper;
|
2018-08-31 21:04:22 +02:00
|
|
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2020-04-13 20:13:31 -07:00
|
|
|
import java.io.IOException;
|
2024-04-12 14:30:26 +02:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2019-02-12 13:10:22 +01:00
|
|
|
import java.security.GeneralSecurityException;
|
2018-08-31 21:04:22 +02:00
|
|
|
import java.security.KeyPair;
|
2024-04-12 14:30:26 +02:00
|
|
|
import java.security.MessageDigest;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
2018-08-31 21:04:22 +02:00
|
|
|
import java.security.PrivateKey;
|
2014-01-16 09:51:32 +04:00
|
|
|
import java.security.PublicKey;
|
2014-01-07 17:40:21 +04:00
|
|
|
import java.util.Arrays;
|
2015-08-10 00:26:58 -07:00
|
|
|
import java.util.Collections;
|
2019-03-08 13:44:54 +01:00
|
|
|
import java.util.List;
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2024-04-12 14:30:26 +02:00
|
|
|
import static org.kde.kdeconnect.Helpers.SecurityHelpers.ConstantTimeCompareKt.constantTimeCompare;
|
|
|
|
|
2014-01-07 17:40:21 +04:00
|
|
|
class SimpleSftpServer {
|
|
|
|
private static final int STARTPORT = 1739;
|
|
|
|
private static final int ENDPORT = 1764;
|
|
|
|
|
2016-12-11 13:31:17 +01:00
|
|
|
static final String USER = "kdeconnect";
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2016-12-11 13:31:17 +01:00
|
|
|
private int port = -1;
|
|
|
|
private boolean started = false;
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2016-12-11 13:31:17 +01:00
|
|
|
private final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
|
|
|
|
private final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2016-03-03 12:22:22 -08:00
|
|
|
static {
|
|
|
|
SecurityUtils.setRegisterBouncyCastle(false);
|
|
|
|
}
|
2018-03-03 16:06:52 +01:00
|
|
|
|
2023-03-19 11:56:25 +01:00
|
|
|
boolean initialized = false;
|
|
|
|
|
2016-03-03 12:22:22 -08:00
|
|
|
private final SshServer sshd = SshServer.setUpDefaultServer();
|
2023-03-07 23:59:40 +01:00
|
|
|
private AndroidFileSystemFactory safFileSystemFactory;
|
|
|
|
|
|
|
|
public void setSafRoots(List<SftpPlugin.StorageInfo> storageInfoList) {
|
|
|
|
safFileSystemFactory.initRoots(storageInfoList);
|
|
|
|
}
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2023-03-19 11:56:25 +01:00
|
|
|
void initialize(Context context, Device device) throws GeneralSecurityException {
|
2016-12-11 13:31:17 +01:00
|
|
|
|
2023-10-01 16:24:16 +02:00
|
|
|
sshd.setSignatureFactories(Arrays.asList(
|
|
|
|
new SignatureECDSA.NISTP256Factory(),
|
|
|
|
new SignatureECDSA.NISTP384Factory(),
|
|
|
|
new SignatureECDSA.NISTP521Factory(),
|
|
|
|
new SignatureDSA.Factory(),
|
|
|
|
new SignatureRSASHA256.Factory(),
|
|
|
|
new SignatureRSA.Factory() // Insecure SHA1, left for backwards compatibility
|
|
|
|
));
|
|
|
|
|
2016-03-03 12:22:22 -08:00
|
|
|
sshd.setKeyExchangeFactories(Arrays.asList(
|
2023-10-01 16:24:16 +02:00
|
|
|
new ECDHP256.Factory(), // ecdh-sha2-nistp256
|
|
|
|
new ECDHP384.Factory(), // ecdh-sha2-nistp384
|
|
|
|
new ECDHP521.Factory(), // ecdh-sha2-nistp521
|
2023-09-28 16:03:17 +02:00
|
|
|
new DHG14_256.Factory(), // diffie-hellman-group14-sha256
|
2023-10-01 16:24:16 +02:00
|
|
|
new DHG14.Factory() // Insecure diffie-hellman-group14-sha1, left for backwards-compatibility.
|
2020-04-13 20:13:31 -07:00
|
|
|
));
|
2016-03-03 12:22:22 -08:00
|
|
|
|
2018-08-31 21:04:22 +02:00
|
|
|
//Reuse this device keys for the ssh connection as well
|
2018-08-31 21:12:04 +02:00
|
|
|
final KeyPair keyPair;
|
|
|
|
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
|
|
|
PublicKey publicKey = RsaHelper.getPublicKey(context);
|
|
|
|
keyPair = new KeyPair(publicKey, privateKey);
|
2018-08-31 21:04:22 +02:00
|
|
|
sshd.setKeyPairProvider(new AbstractKeyPairProvider() {
|
|
|
|
@Override
|
2018-08-31 21:12:04 +02:00
|
|
|
public Iterable<KeyPair> loadKeys() {
|
|
|
|
return Collections.singletonList(keyPair);
|
2018-08-31 21:04:22 +02:00
|
|
|
}
|
|
|
|
});
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2023-03-07 23:59:40 +01:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
sshd.setFileSystemFactory(new NativeFileSystemFactory());
|
|
|
|
} else {
|
|
|
|
safFileSystemFactory = new AndroidFileSystemFactory(context);
|
|
|
|
sshd.setFileSystemFactory(safFileSystemFactory);
|
|
|
|
}
|
2014-01-07 17:40:21 +04:00
|
|
|
sshd.setCommandFactory(new ScpCommandFactory());
|
2018-08-31 21:12:04 +02:00
|
|
|
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));
|
2014-01-07 17:40:21 +04:00
|
|
|
|
2023-06-27 11:14:36 +00:00
|
|
|
keyAuth.deviceKey = device.getCertificate().getPublicKey();
|
2019-04-17 20:24:50 +02:00
|
|
|
|
2018-12-02 20:00:29 +01:00
|
|
|
sshd.setPublickeyAuthenticator(keyAuth);
|
2016-06-09 15:00:42 +02:00
|
|
|
sshd.setPasswordAuthenticator(passwordAuth);
|
2023-03-19 11:56:25 +01:00
|
|
|
|
|
|
|
initialized = true;
|
2014-01-07 17:40:21 +04:00
|
|
|
}
|
|
|
|
|
2023-03-07 23:59:40 +01:00
|
|
|
public boolean start() {
|
2014-01-07 17:40:21 +04:00
|
|
|
if (!started) {
|
2024-04-12 14:30:26 +02:00
|
|
|
regeneratePassword();
|
2014-01-07 17:40:21 +04:00
|
|
|
|
|
|
|
port = STARTPORT;
|
2018-03-03 16:06:52 +01:00
|
|
|
while (!started) {
|
2014-01-07 17:40:21 +04:00
|
|
|
try {
|
|
|
|
sshd.setPort(port);
|
|
|
|
sshd.start();
|
|
|
|
started = true;
|
2020-04-13 20:13:31 -07:00
|
|
|
} catch (IOException e) {
|
2014-01-07 17:40:21 +04:00
|
|
|
port++;
|
|
|
|
if (port >= ENDPORT) {
|
|
|
|
port = -1;
|
|
|
|
Log.e("SftpServer", "No more ports available");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void stop() {
|
|
|
|
try {
|
|
|
|
started = false;
|
2017-06-21 03:04:22 +02:00
|
|
|
sshd.stop(true);
|
2016-03-03 11:53:34 -08:00
|
|
|
} catch (Exception e) {
|
2019-03-31 20:09:44 +02:00
|
|
|
Log.e("SFTP", "Exception while stopping the server", e);
|
2014-01-07 17:40:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-08 13:44:54 +01:00
|
|
|
public boolean isStarted() {
|
|
|
|
return started;
|
|
|
|
}
|
|
|
|
|
2024-04-12 14:30:26 +02:00
|
|
|
String regeneratePassword() {
|
|
|
|
String password = RandomHelper.randomString(28);
|
|
|
|
passwordAuth.setPassword(password);
|
|
|
|
return password;
|
2016-12-11 13:31:17 +01:00
|
|
|
}
|
|
|
|
|
2018-12-25 00:09:52 +01:00
|
|
|
int getPort() {
|
2016-12-11 13:31:17 +01:00
|
|
|
return port;
|
|
|
|
}
|
|
|
|
|
2023-03-19 11:56:25 +01:00
|
|
|
public boolean isInitialized() {
|
|
|
|
return initialized;
|
|
|
|
}
|
|
|
|
|
2016-06-06 23:59:25 +02:00
|
|
|
static class SimplePasswordAuthenticator implements PasswordAuthenticator {
|
|
|
|
|
2024-04-12 14:30:26 +02:00
|
|
|
MessageDigest sha;
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
sha = MessageDigest.getInstance("SHA-256");
|
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setPassword(String password) {
|
|
|
|
sha.digest(password.getBytes(StandardCharsets.UTF_8));
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] passwordHash;
|
2016-06-06 23:59:25 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean authenticate(String user, String password, ServerSession session) {
|
2024-04-12 14:30:26 +02:00
|
|
|
byte[] receivedPasswordHash = sha.digest(password.getBytes(StandardCharsets.UTF_8));
|
|
|
|
return user.equals(SimpleSftpServer.USER) && constantTimeCompare(passwordHash, receivedPasswordHash);
|
2016-06-06 23:59:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
|
|
|
|
|
2018-10-26 23:53:58 +02:00
|
|
|
PublicKey deviceKey;
|
2016-06-06 23:59:25 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean authenticate(String user, PublicKey key, ServerSession session) {
|
2016-12-11 13:31:17 +01:00
|
|
|
return deviceKey.equals(key);
|
2016-06-06 23:59:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|