aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/app/passwordstore/util/extensions/Extensions.kt5
-rw-r--r--app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt18
-rw-r--r--app/src/test/java/app/passwordstore/util/git/sshj/SshjConfigTest.kt14
3 files changed, 35 insertions, 2 deletions
diff --git a/app/src/main/java/app/passwordstore/util/extensions/Extensions.kt b/app/src/main/java/app/passwordstore/util/extensions/Extensions.kt
index 55a6276e..597a286f 100644
--- a/app/src/main/java/app/passwordstore/util/extensions/Extensions.kt
+++ b/app/src/main/java/app/passwordstore/util/extensions/Extensions.kt
@@ -33,10 +33,11 @@ fun File.contains(other: File): Boolean {
/**
* Checks if this [File] is in the password repository directory as given by
- * [PasswordRepository.getRepositoryDirectory]
+ * [PasswordRepository.getRepositoryDirectory]. Compares whole path components, so a sibling such as
+ * `store-evil` is not inside `store`.
*/
fun File.isInsideRepository(): Boolean {
- return canonicalPath.contains(PasswordRepository.getRepositoryDirectory().canonicalPath)
+ return canonicalFile.startsWith(PasswordRepository.getRepositoryDirectory().canonicalFile)
}
/** Recursively lists the files in this [File], skipping any directories it encounters. */
diff --git a/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt b/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
index b43d0007..3a3538ac 100644
--- a/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
+++ b/app/src/main/java/app/passwordstore/util/git/sshj/SshjConfig.kt
@@ -12,6 +12,7 @@ import com.hierynomus.sshj.transport.cipher.GcmCiphers
import com.hierynomus.sshj.transport.kex.ExtInfoClientFactory
import com.hierynomus.sshj.transport.mac.Macs
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile
+import java.security.Provider
import java.security.Security
import logcat.LogPriority.VERBOSE
import logcat.logcat
@@ -32,6 +33,18 @@ import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.slf4j.Logger
+private object Ed25519KeyFactoryProvider :
+ Provider("HardenedPassEd25519", 1.0, "Ed25519 KeyFactory from Bouncy Castle") {
+ private fun readResolve(): Any = Ed25519KeyFactoryProvider
+
+ init {
+ put(
+ "KeyFactory.Ed25519",
+ "org.bouncycastle.jcajce.provider.asymmetric.edec.KeyFactorySpi\$Ed25519",
+ )
+ }
+}
+
fun setUpBouncyCastleForSshj() {
// Replace the Android BC provider with the Java BouncyCastle provider since the former does
// not include all the required algorithms.
@@ -48,6 +61,11 @@ fun setUpBouncyCastleForSshj() {
runCatching { Class.forName("sun.security.jca.Providers") }
Security.insertProviderAt(BouncyCastleProvider(), bcIndex + 1)
}
+ // Recent Conscrypt (Android 14 mainline updates) serves KeyFactory "Ed25519" with keys whose
+ // algorithm name sshj does not map to ssh-ed25519 (KeyType.UNKNOWN, "Don't know how to encode
+ // key"), which breaks every Ed25519 host and user key. Pin only that factory to Bouncy Castle,
+ // ahead of every other provider; signatures still pick a provider per key.
+ Security.insertProviderAt(Ed25519KeyFactoryProvider, 1)
logcat("setUpBouncyCastleForSshj", priority = VERBOSE) {
"JCE providers: ${Security.getProviders().joinToString { "${it.name} (${it.version})" }}"
}
diff --git a/app/src/test/java/app/passwordstore/util/git/sshj/SshjConfigTest.kt b/app/src/test/java/app/passwordstore/util/git/sshj/SshjConfigTest.kt
index af6eee47..e4bc3eec 100644
--- a/app/src/test/java/app/passwordstore/util/git/sshj/SshjConfigTest.kt
+++ b/app/src/test/java/app/passwordstore/util/git/sshj/SshjConfigTest.kt
@@ -6,7 +6,11 @@
package app.passwordstore.util.git.sshj
import kotlin.test.Test
+import kotlin.test.assertEquals
import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import net.schmizz.sshj.common.Ed25519KeyFactory
+import net.schmizz.sshj.common.KeyType
import net.schmizz.sshj.common.SecurityUtils
class SshjConfigTest {
@@ -19,4 +23,14 @@ class SshjConfigTest {
setUpBouncyCastleForSshj()
assertNull(SecurityUtils.getSecurityProvider())
}
+
+ // Conscrypt's Ed25519 keys map to KeyType.UNKNOWN in sshj, so Ed25519 keys must come from
+ // Bouncy Castle whatever provider the platform puts first.
+ @Test
+ fun ed25519KeysComeFromBouncyCastle() {
+ setUpBouncyCastleForSshj()
+ val key = Ed25519KeyFactory.getPublicKey(ByteArray(32) { 1 })
+ assertTrue(key.javaClass.name.startsWith("org.bouncycastle."), key.javaClass.name)
+ assertEquals(KeyType.ED25519, KeyType.fromKey(key))
+ }
}