aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/zeapo/pwdstore/git/config
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/git/config')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java383
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/SshjConfig.kt275
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt180
3 files changed, 0 insertions, 838 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
deleted file mode 100644
index 03760741..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git.config;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.SharedPreferences;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.preference.PreferenceManager;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.jcraft.jsch.Identity;
-import com.jcraft.jsch.JSch;
-import com.jcraft.jsch.JSchException;
-import com.jcraft.jsch.Session;
-import com.zeapo.pwdstore.R;
-import com.zeapo.pwdstore.git.BaseGitActivity;
-import com.zeapo.pwdstore.utils.PreferenceKeys;
-
-import org.eclipse.jgit.transport.JschConfigSessionFactory;
-import org.eclipse.jgit.transport.OpenSshConfig;
-import org.eclipse.jgit.util.Base64;
-import org.eclipse.jgit.util.FS;
-import org.openintents.ssh.authentication.ISshAuthenticationService;
-import org.openintents.ssh.authentication.SshAuthenticationApi;
-import org.openintents.ssh.authentication.SshAuthenticationApiError;
-import org.openintents.ssh.authentication.SshAuthenticationConnection;
-import org.openintents.ssh.authentication.request.KeySelectionRequest;
-import org.openintents.ssh.authentication.request.Request;
-import org.openintents.ssh.authentication.request.SigningRequest;
-import org.openintents.ssh.authentication.request.SshPublicKeyRequest;
-import org.openintents.ssh.authentication.util.SshAuthenticationApiUtils;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-public class SshApiSessionFactory extends JschConfigSessionFactory {
- /**
- * Intent request code indicating a completed signature that should be posted to an outstanding
- * ApiIdentity
- */
- public static final int POST_SIGNATURE = 301;
-
- private final Identity identity;
-
- public SshApiSessionFactory(Identity identity) {
- this.identity = identity;
- }
-
- @NonNull
- @Override
- protected JSch getJSch(@NonNull final OpenSshConfig.Host hc, @NonNull FS fs)
- throws JSchException {
- JSch jsch = super.getJSch(hc, fs);
- jsch.removeAllIdentity();
- jsch.addIdentity(identity, null);
- return jsch;
- }
-
- @Override
- protected void configure(@NonNull OpenSshConfig.Host hc, Session session) {
- session.setConfig("StrictHostKeyChecking", "no");
- session.setConfig("PreferredAuthentications", "publickey");
- }
-
- /**
- * Helper to build up an ApiIdentity via the invocation of several pending intents that
- * communicate with OpenKeychain. The user of this class must handle onActivityResult and keep
- * feeding the resulting intents into the IdentityBuilder until it can successfully complete the
- * build.
- */
- public static class IdentityBuilder {
- private final SshAuthenticationConnection connection;
- private final BaseGitActivity callingActivity;
- private final SharedPreferences settings;
- private SshAuthenticationApi api;
- private String keyId, description, alg;
- private byte[] publicKey;
-
- /**
- * Construct a new IdentityBuilder
- *
- * @param callingActivity Activity that will be used to launch pending intents and that will
- * receive and handle the results.
- */
- public IdentityBuilder(BaseGitActivity callingActivity) {
- this.callingActivity = callingActivity;
-
- List<String> providers =
- SshAuthenticationApiUtils.getAuthenticationProviderPackageNames(
- callingActivity);
- if (providers.isEmpty())
- throw new RuntimeException(callingActivity.getString(R.string.no_ssh_api_provider));
-
- // TODO: Handle multiple available providers? Are there actually any in practice beyond
- // OpenKeychain?
- connection = new SshAuthenticationConnection(callingActivity, providers.get(0));
-
- settings =
- PreferenceManager.getDefaultSharedPreferences(
- callingActivity.getApplicationContext());
- keyId = settings.getString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null);
- }
-
- /**
- * Free any resources associated with this IdentityBuilder
- */
- public void close() {
- if (connection != null && connection.isConnected()) connection.disconnect();
- }
-
- /**
- * Helper to invoke an OpenKeyshain SSH API method and correctly interpret the result.
- *
- * @param request The request intent to launch
- * @param requestCode The request code to use if a pending intent needs to be sent
- * @return The resulting intent if the request completed immediately, or null if we had to
- * launch a pending intent to interact with the user
- */
- private Intent executeApi(Request request, int requestCode) {
- Intent result = api.executeApi(request.toIntent());
-
- switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
- case SshAuthenticationApi.RESULT_CODE_ERROR:
- SshAuthenticationApiError error =
- result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
- // On an OpenKeychain SSH API error, clear out the stored keyid
- settings.edit().putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, null).apply();
-
- switch (error.getError()) {
- // If the problem was just a bad keyid, reset to allow them to choose a
- // different one
- case (SshAuthenticationApiError.NO_SUCH_KEY):
- case (SshAuthenticationApiError.NO_AUTH_KEY):
- keyId = null;
- publicKey = null;
- description = null;
- alg = null;
- return executeApi(new KeySelectionRequest(), requestCode);
-
- // Other errors are fatal
- default:
- throw new RuntimeException(error.getMessage());
- }
- case SshAuthenticationApi.RESULT_CODE_SUCCESS:
- break;
- case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- PendingIntent pendingIntent =
- result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
- try {
- callingActivity.startIntentSenderForResult(
- pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
- return null;
- } catch (IntentSender.SendIntentException e) {
- e.printStackTrace();
- throw new RuntimeException(
- callingActivity.getString(R.string.ssh_api_pending_intent_failed));
- }
- default:
- throw new RuntimeException(
- callingActivity.getString(R.string.ssh_api_unknown_error));
- }
-
- return result;
- }
-
- /**
- * Parse a given intent to see if it is the result of an OpenKeychain pending intent. If so,
- * extract any updated state from it.
- *
- * @param intent The intent to inspect
- */
- public void consume(Intent intent) {
- if (intent == null) return;
-
- if (intent.hasExtra(SshAuthenticationApi.EXTRA_KEY_ID)) {
- keyId = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_ID);
- description = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_DESCRIPTION);
- settings.edit().putString(PreferenceKeys.SSH_OPENKEYSTORE_KEYID, keyId).apply();
- }
-
- if (intent.hasExtra(SshAuthenticationApi.EXTRA_SSH_PUBLIC_KEY)) {
- String keyStr = intent.getStringExtra(SshAuthenticationApi.EXTRA_SSH_PUBLIC_KEY);
- String[] keyParts = keyStr.split(" ");
- alg = keyParts[0];
- publicKey = Base64.decode(keyParts[1]);
- }
- }
-
- /**
- * Try to build an ApiIdentity that will perform SSH authentication via OpenKeychain.
- *
- * @param requestCode The request code to use if a pending intent needs to be sent
- * @return The built identity, or null of user interaction is still required (in which case
- * a pending intent will have already been launched)
- */
- public ApiIdentity tryBuild(int requestCode) {
- // First gate, need to initiate a connection to the service and wait for it to connect.
- if (api == null) {
- connection.connect(
- new SshAuthenticationConnection.OnBound() {
- @Override
- public void onBound(ISshAuthenticationService sshAgent) {
- api = new SshAuthenticationApi(callingActivity, sshAgent);
- // We can immediately try the next phase without needing to post
- // back
- // though onActivityResult
- callingActivity.onActivityResult(
- requestCode, AppCompatActivity.RESULT_OK, null);
- }
-
- @Override
- public void onError() {
- new MaterialAlertDialogBuilder(callingActivity)
- .setMessage(
- callingActivity.getString(
- R.string.openkeychain_ssh_api_connect_fail))
- .show();
- }
- });
-
- return null;
- }
-
- // Second gate, need the user to select which key they want to use
- if (keyId == null) {
- consume(executeApi(new KeySelectionRequest(), requestCode));
- // If we did not immediately get the result, bail for now and wait to be re-entered
- if (keyId == null) return null;
- }
-
- // Third gate, need to get the public key for the selected key. This one often does not
- // need use interaction.
- if (publicKey == null) {
- consume(executeApi(new SshPublicKeyRequest(keyId), requestCode));
- // If we did not immediately get the result, bail for now and wait to be re-entered
- if (publicKey == null) return null;
- }
-
- // Have everything we need for now, build the identify
- return new ApiIdentity(keyId, description, publicKey, alg, callingActivity, api);
- }
- }
-
- /**
- * A Jsch identity that delegates key operations via the OpenKeychain SSH API
- */
- public static class ApiIdentity implements Identity {
- private final String keyId;
- private final String description;
- private final String alg;
- private final byte[] publicKey;
- private final AppCompatActivity callingActivity;
- private final SshAuthenticationApi api;
- private CountDownLatch latch;
- private byte[] signature;
-
- ApiIdentity(
- String keyId,
- String description,
- byte[] publicKey,
- String alg,
- AppCompatActivity callingActivity,
- SshAuthenticationApi api) {
- this.keyId = keyId;
- this.description = description;
- this.publicKey = publicKey;
- this.alg = alg;
- this.callingActivity = callingActivity;
- this.api = api;
- }
-
- @Override
- public boolean setPassphrase(byte[] passphrase) {
- // We are not encrypted with a passphrase
- return true;
- }
-
- @Override
- public byte[] getPublicKeyBlob() {
- return publicKey;
- }
-
- /**
- * Helper to handle the result of an OpenKeyshain SSH API signing request
- *
- * @param result The result intent to handle
- * @return The signed challenge, or null if it was not immediately available, in which case
- * the latch has been initialized and the pending intent started
- */
- private byte[] handleSignResult(Intent result) {
- switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
- case SshAuthenticationApi.RESULT_CODE_ERROR:
- SshAuthenticationApiError error =
- result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
- throw new RuntimeException(error.getMessage());
- case SshAuthenticationApi.RESULT_CODE_SUCCESS:
- return result.getByteArrayExtra(SshAuthenticationApi.EXTRA_SIGNATURE);
- case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- PendingIntent pendingIntent =
- result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
- try {
- latch = new CountDownLatch(1);
- callingActivity.startIntentSenderForResult(
- pendingIntent.getIntentSender(), POST_SIGNATURE, null, 0, 0, 0);
- return null;
-
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException(
- callingActivity.getString(R.string.ssh_api_pending_intent_failed));
- }
- default:
- if (result.hasExtra(SshAuthenticationApi.EXTRA_CHALLENGE))
- return handleSignResult(api.executeApi(result));
- throw new RuntimeException(
- callingActivity.getString(R.string.ssh_api_unknown_error));
- }
- }
-
- @Override
- public byte[] getSignature(byte[] data) {
- Intent request = new SigningRequest(data, keyId, SshAuthenticationApi.SHA1).toIntent();
- signature = handleSignResult(api.executeApi(request));
-
- // If we did not immediately get a signature (probable), we will block on a latch until
- // the main activity gets the intent result and posts to us.
- if (signature == null) {
- try {
- latch.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- return signature;
- }
-
- /**
- * Post a signature response back to an in-progress operation using this ApiIdentity.
- *
- * @param data The signature data (hopefully)
- */
- public void postSignature(Intent data) {
- try {
- if (data != null) {
- signature = handleSignResult(data);
- }
- } finally {
- if (latch != null) latch.countDown();
- }
- }
-
- @Override
- public boolean decrypt() {
- return true;
- }
-
- @Override
- public String getAlgName() {
- return alg;
- }
-
- @Override
- public String getName() {
- return description;
- }
-
- @Override
- public boolean isEncrypted() {
- return false;
- }
-
- @Override
- public void clear() {
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjConfig.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjConfig.kt
deleted file mode 100644
index 6c409329..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjConfig.kt
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git.config
-
-import com.github.ajalt.timberkt.Timber
-import com.github.ajalt.timberkt.d
-import com.hierynomus.sshj.key.KeyAlgorithms
-import com.hierynomus.sshj.transport.cipher.BlockCiphers
-import com.hierynomus.sshj.transport.kex.ExtInfoClientFactory
-import com.hierynomus.sshj.transport.mac.Macs
-import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile
-import java.security.Security
-import net.schmizz.keepalive.KeepAliveProvider
-import net.schmizz.sshj.ConfigImpl
-import net.schmizz.sshj.common.LoggerFactory
-import net.schmizz.sshj.transport.compression.NoneCompression
-import net.schmizz.sshj.transport.kex.Curve25519SHA256
-import net.schmizz.sshj.transport.kex.Curve25519SHA256.FactoryLibSsh
-import net.schmizz.sshj.transport.kex.DHGexSHA256
-import net.schmizz.sshj.transport.kex.ECDHNistP
-import net.schmizz.sshj.transport.random.JCERandom
-import net.schmizz.sshj.transport.random.SingletonRandomFactory
-import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile
-import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile
-import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile
-import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.slf4j.Logger
-import org.slf4j.Marker
-
-
-fun setUpBouncyCastleForSshj() {
- // Replace the Android BC provider with the Java BouncyCastle provider since the former does
- // not include all the required algorithms.
- // Note: This may affect crypto operations in other parts of the application.
- val bcIndex = Security.getProviders().indexOfFirst {
- it.name == BouncyCastleProvider.PROVIDER_NAME
- }
- if (bcIndex == -1) {
- // No Android BC found, install Java BC at lowest priority.
- Security.addProvider(BouncyCastleProvider())
- } else {
- // Replace Android BC with Java BC, inserted at the same position.
- Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
- // May be needed on Android Pie+ as per https://stackoverflow.com/a/57897224/297261
- try {
- Class.forName("sun.security.jca.Providers")
- } catch (e: ClassNotFoundException) {
- }
- Security.insertProviderAt(BouncyCastleProvider(), bcIndex + 1)
- }
- d { "JCE providers: ${Security.getProviders().joinToString { "${it.name} (${it.version})" }}" }
-}
-
-private abstract class AbstractLogger(private val name: String) : Logger {
-
- abstract fun t(message: String, t: Throwable? = null, vararg args: Any?)
- abstract fun d(message: String, t: Throwable? = null, vararg args: Any?)
- abstract fun i(message: String, t: Throwable? = null, vararg args: Any?)
- abstract fun w(message: String, t: Throwable? = null, vararg args: Any?)
- abstract fun e(message: String, t: Throwable? = null, vararg args: Any?)
-
- override fun getName() = name
-
- override fun isTraceEnabled(marker: Marker?): Boolean = isTraceEnabled
- override fun isDebugEnabled(marker: Marker?): Boolean = isDebugEnabled
- override fun isInfoEnabled(marker: Marker?): Boolean = isInfoEnabled
- override fun isWarnEnabled(marker: Marker?): Boolean = isWarnEnabled
- override fun isErrorEnabled(marker: Marker?): Boolean = isErrorEnabled
-
- override fun trace(msg: String) = t(msg)
- override fun trace(format: String, arg: Any?) = t(format, null, arg)
- override fun trace(format: String, arg1: Any?, arg2: Any?) = t(format, null, arg1, arg2)
- override fun trace(format: String, vararg arguments: Any?) = t(format, null, *arguments)
- override fun trace(msg: String, t: Throwable?) = t(msg, t)
- override fun trace(marker: Marker, msg: String) = trace(msg)
- override fun trace(marker: Marker?, format: String, arg: Any?) = trace(format, arg)
- override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
- trace(format, arg1, arg2)
-
- override fun trace(marker: Marker?, format: String, vararg arguments: Any?) =
- trace(format, *arguments)
-
- override fun trace(marker: Marker?, msg: String, t: Throwable?) = trace(msg, t)
-
- override fun debug(msg: String) = d(msg)
- override fun debug(format: String, arg: Any?) = d(format, null, arg)
- override fun debug(format: String, arg1: Any?, arg2: Any?) = d(format, null, arg1, arg2)
- override fun debug(format: String, vararg arguments: Any?) = d(format, null, *arguments)
- override fun debug(msg: String, t: Throwable?) = d(msg, t)
- override fun debug(marker: Marker, msg: String) = debug(msg)
- override fun debug(marker: Marker?, format: String, arg: Any?) = debug(format, arg)
- override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
- debug(format, arg1, arg2)
-
- override fun debug(marker: Marker?, format: String, vararg arguments: Any?) =
- debug(format, *arguments)
-
- override fun debug(marker: Marker?, msg: String, t: Throwable?) = debug(msg, t)
-
- override fun info(msg: String) = i(msg)
- override fun info(format: String, arg: Any?) = i(format, null, arg)
- override fun info(format: String, arg1: Any?, arg2: Any?) = i(format, null, arg1, arg2)
- override fun info(format: String, vararg arguments: Any?) = i(format, null, *arguments)
- override fun info(msg: String, t: Throwable?) = i(msg, t)
- override fun info(marker: Marker, msg: String) = info(msg)
- override fun info(marker: Marker?, format: String, arg: Any?) = info(format, arg)
- override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
- info(format, arg1, arg2)
-
- override fun info(marker: Marker?, format: String, vararg arguments: Any?) =
- info(format, *arguments)
-
- override fun info(marker: Marker?, msg: String, t: Throwable?) = info(msg, t)
-
- override fun warn(msg: String) = w(msg)
- override fun warn(format: String, arg: Any?) = w(format, null, arg)
- override fun warn(format: String, arg1: Any?, arg2: Any?) = w(format, null, arg1, arg2)
- override fun warn(format: String, vararg arguments: Any?) = w(format, null, *arguments)
- override fun warn(msg: String, t: Throwable?) = w(msg, t)
- override fun warn(marker: Marker, msg: String) = warn(msg)
- override fun warn(marker: Marker?, format: String, arg: Any?) = warn(format, arg)
- override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
- warn(format, arg1, arg2)
-
- override fun warn(marker: Marker?, format: String, vararg arguments: Any?) =
- warn(format, *arguments)
-
- override fun warn(marker: Marker?, msg: String, t: Throwable?) = warn(msg, t)
-
- override fun error(msg: String) = e(msg)
- override fun error(format: String, arg: Any?) = e(format, null, arg)
- override fun error(format: String, arg1: Any?, arg2: Any?) = e(format, null, arg1, arg2)
- override fun error(format: String, vararg arguments: Any?) = e(format, null, *arguments)
- override fun error(msg: String, t: Throwable?) = e(msg, t)
- override fun error(marker: Marker, msg: String) = error(msg)
- override fun error(marker: Marker?, format: String, arg: Any?) = error(format, arg)
- override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
- error(format, arg1, arg2)
-
- override fun error(marker: Marker?, format: String, vararg arguments: Any?) =
- error(format, *arguments)
-
- override fun error(marker: Marker?, msg: String, t: Throwable?) = error(msg, t)
-}
-
-object TimberLoggerFactory : LoggerFactory {
- private class TimberLogger(name: String) : AbstractLogger(name) {
-
- // We defer the log level checks to Timber.
- override fun isTraceEnabled() = true
- override fun isDebugEnabled() = true
- override fun isInfoEnabled() = true
- override fun isWarnEnabled() = true
- override fun isErrorEnabled() = true
-
- // Replace slf4j's "{}" format string style with standard Java's "%s".
- // The supposedly redundant escape on the } is not redundant.
- @Suppress("RegExpRedundantEscape")
- private fun String.fix() = replace("""(?!<\\)\{\}""".toRegex(), "%s")
-
- override fun t(message: String, t: Throwable?, vararg args: Any?) {
- Timber.tag(name).v(t, message.fix(), *args)
- }
-
- override fun d(message: String, t: Throwable?, vararg args: Any?) {
- Timber.tag(name).d(t, message.fix(), *args)
- }
-
- override fun i(message: String, t: Throwable?, vararg args: Any?) {
- Timber.tag(name).i(t, message.fix(), *args)
- }
-
- override fun w(message: String, t: Throwable?, vararg args: Any?) {
- Timber.tag(name).w(t, message.fix(), *args)
- }
-
- override fun e(message: String, t: Throwable?, vararg args: Any?) {
- Timber.tag(name).e(t, message.fix(), *args)
- }
- }
-
- override fun getLogger(name: String): Logger {
- return TimberLogger(name)
- }
-
- override fun getLogger(clazz: Class<*>): Logger {
- return TimberLogger(clazz.name)
- }
-
-}
-
-class SshjConfig : ConfigImpl() {
-
- init {
- loggerFactory = TimberLoggerFactory
- keepAliveProvider = KeepAliveProvider.HEARTBEAT
- version = "OpenSSH_8.2p1 Ubuntu-4ubuntu0.1"
-
- initKeyExchangeFactories()
- initKeyAlgorithms()
- initRandomFactory()
- initFileKeyProviderFactories()
- initCipherFactories()
- initCompressionFactories()
- initMACFactories()
- }
-
- private fun initKeyExchangeFactories() {
- keyExchangeFactories = listOf(
- Curve25519SHA256.Factory(),
- FactoryLibSsh(),
- ECDHNistP.Factory521(),
- ECDHNistP.Factory384(),
- ECDHNistP.Factory256(),
- DHGexSHA256.Factory(),
- // Sends "ext-info-c" with the list of key exchange algorithms. This is needed to get
- // rsa-sha2-* key types to work with some servers (e.g. GitHub).
- ExtInfoClientFactory(),
- )
- }
-
- private fun initKeyAlgorithms() {
- keyAlgorithms = listOf(
- KeyAlgorithms.SSHRSACertV01(),
- KeyAlgorithms.EdDSA25519(),
- KeyAlgorithms.RSASHA512(),
- KeyAlgorithms.RSASHA256(),
- KeyAlgorithms.ECDSASHANistp521(),
- KeyAlgorithms.ECDSASHANistp384(),
- KeyAlgorithms.ECDSASHANistp256(),
- KeyAlgorithms.SSHRSA(),
- )
- }
-
- private fun initRandomFactory() {
- randomFactory = SingletonRandomFactory(JCERandom.Factory())
- }
-
- private fun initFileKeyProviderFactories() {
- fileKeyProviderFactories = listOf(
- OpenSSHKeyV1KeyFile.Factory(),
- PKCS8KeyFile.Factory(),
- PKCS5KeyFile.Factory(),
- OpenSSHKeyFile.Factory(),
- PuTTYKeyFile.Factory(),
- )
- }
-
-
- private fun initCipherFactories() {
- cipherFactories = listOf(
- BlockCiphers.AES256CTR(),
- BlockCiphers.AES192CTR(),
- BlockCiphers.AES128CTR(),
- )
- }
-
- private fun initMACFactories() {
- macFactories = listOf(
- Macs.HMACSHA2512Etm(),
- Macs.HMACSHA2256Etm(),
- Macs.HMACSHA2512(),
- Macs.HMACSHA2256(),
- )
- }
-
- private fun initCompressionFactories() {
- compressionFactories = listOf(
- NoneCompression.Factory(),
- )
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt
deleted file mode 100644
index 85b5f753..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.git.config
-
-import android.util.Base64
-import com.github.ajalt.timberkt.d
-import com.github.ajalt.timberkt.w
-import java.io.File
-import java.io.IOException
-import java.io.InputStream
-import java.io.OutputStream
-import java.security.GeneralSecurityException
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.runBlocking
-import net.schmizz.sshj.SSHClient
-import net.schmizz.sshj.common.Buffer.PlainBuffer
-import net.schmizz.sshj.common.DisconnectReason
-import net.schmizz.sshj.common.SSHException
-import net.schmizz.sshj.common.SSHRuntimeException
-import net.schmizz.sshj.common.SecurityUtils
-import net.schmizz.sshj.connection.channel.direct.Session
-import net.schmizz.sshj.transport.verification.FingerprintVerifier
-import net.schmizz.sshj.transport.verification.HostKeyVerifier
-import net.schmizz.sshj.userauth.password.PasswordFinder
-import net.schmizz.sshj.userauth.password.Resource
-import org.eclipse.jgit.transport.CredentialsProvider
-import org.eclipse.jgit.transport.RemoteSession
-import org.eclipse.jgit.transport.SshSessionFactory
-import org.eclipse.jgit.transport.URIish
-import org.eclipse.jgit.util.FS
-
-sealed class SshAuthData {
- class Password(val passwordFinder: InteractivePasswordFinder) : SshAuthData()
- class PublicKeyFile(val keyFile: File, val passphraseFinder: InteractivePasswordFinder) : SshAuthData()
-}
-
-abstract class InteractivePasswordFinder : PasswordFinder {
-
- private var isRetry = false
-
- abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean)
-
- final override fun reqPassword(resource: Resource<*>?): CharArray {
- val password = runBlocking(Dispatchers.Main) {
- suspendCoroutine<String?> { cont ->
- askForPassword(cont, isRetry)
- }
- }
- isRetry = true
- return password?.toCharArray()
- ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)
- }
-
- final override fun shouldRetry(resource: Resource<*>?) = true
-}
-
-class SshjSessionFactory(private val authData: SshAuthData, private val hostKeyFile: File) : SshSessionFactory() {
-
- private var currentSession: SshjSession? = null
-
- override fun getSession(uri: URIish, credentialsProvider: CredentialsProvider?, fs: FS?, tms: Int): RemoteSession {
- return currentSession ?: SshjSession(uri, uri.user, authData, hostKeyFile).connect().also {
- d { "New SSH connection created" }
- currentSession = it
- }
- }
-
- fun close() {
- currentSession?.close()
- }
-}
-
-private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier {
- if (!hostKeyFile.exists()) {
- return HostKeyVerifier { _, _, key ->
- val digest = try {
- SecurityUtils.getMessageDigest("SHA-256")
- } catch (e: GeneralSecurityException) {
- throw SSHRuntimeException(e)
- }
- digest.update(PlainBuffer().putPublicKey(key).compactData)
- val digestData = digest.digest()
- val hostKeyEntry = "SHA256:${Base64.encodeToString(digestData, Base64.NO_WRAP)}"
- d { "Trusting host key on first use: $hostKeyEntry" }
- hostKeyFile.writeText(hostKeyEntry)
- true
- }
- } else {
- val hostKeyEntry = hostKeyFile.readText()
- d { "Pinned host key: $hostKeyEntry" }
- return FingerprintVerifier.getInstance(hostKeyEntry)
- }
-}
-
-private class SshjSession(uri: URIish, private val username: String, private val authData: SshAuthData, private val hostKeyFile: File) : RemoteSession {
-
- private lateinit var ssh: SSHClient
- private var currentCommand: Session? = null
-
- private val uri = if (uri.host.contains('@')) {
- // URIish's String constructor cannot handle '@' in the user part of the URI and the URL
- // constructor can't be used since Java's URL does not recognize the ssh scheme. We thus
- // need to patch everything up ourselves.
- d { "Before fixup: user=${uri.user}, host=${uri.host}" }
- val userPlusHost = "${uri.user}@${uri.host}"
- val realUser = userPlusHost.substringBeforeLast('@')
- val realHost = userPlusHost.substringAfterLast('@')
- uri.setUser(realUser).setHost(realHost).also { d { "After fixup: user=${it.user}, host=${it.host}" } }
- } else {
- uri
- }
-
- fun connect(): SshjSession {
- ssh = SSHClient(SshjConfig())
- ssh.addHostKeyVerifier(makeTofuHostKeyVerifier(hostKeyFile))
- ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
- if (!ssh.isConnected)
- throw IOException()
- when (authData) {
- is SshAuthData.Password -> {
- ssh.authPassword(username, authData.passwordFinder)
- }
- is SshAuthData.PublicKeyFile -> {
- ssh.authPublickey(username, ssh.loadKeys(authData.keyFile.absolutePath, authData.passphraseFinder))
- }
- }
- return this
- }
-
- override fun exec(commandName: String?, timeout: Int): Process {
- if (currentCommand != null) {
- w { "Killing old command" }
- disconnect()
- }
- val session = ssh.startSession()
- currentCommand = session
- return SshjProcess(session.exec(commandName), timeout.toLong())
- }
-
- /**
- * Kills the current command if one is running and returns the session into a state where `exec`
- * can be called.
- *
- * Note that this does *not* disconnect the session. Unfortunately, the function has to be
- * called `disconnect` to override the corresponding abstract function in `RemoteSession`.
- */
- override fun disconnect() {
- currentCommand?.close()
- currentCommand = null
- }
-
- fun close() {
- disconnect()
- ssh.close()
- }
-}
-
-private class SshjProcess(private val command: Session.Command, private val timeout: Long) : Process() {
-
- override fun waitFor(): Int {
- command.join(timeout, TimeUnit.SECONDS)
- command.close()
- return exitValue()
- }
-
- override fun destroy() = command.close()
-
- override fun getOutputStream(): OutputStream = command.outputStream
-
- override fun getErrorStream(): InputStream = command.errorStream
-
- override fun exitValue(): Int = command.exitStatus
-
- override fun getInputStream(): InputStream = command.inputStream
-}