Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit e1f81e8

Browse files
Use a custom sshj config (#878)
* Use a custom sshj config * Get random numbers directly from SecureRandom * Use Timber calls for logging * Remove all algorithms that are not in the Mozilla Intermediate SSH config * Address review comments * Fixup slf4j's custom format string format Co-authored-by: Harsh Shandilya <[email protected]>
1 parent eb31385 commit e1f81e8

File tree

3 files changed

+274
-23
lines changed

3 files changed

+274
-23
lines changed

app/src/main/java/com/zeapo/pwdstore/Application.kt

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
1313
import androidx.preference.PreferenceManager
1414
import com.github.ajalt.timberkt.Timber.DebugTree
1515
import com.github.ajalt.timberkt.Timber.plant
16-
import org.bouncycastle.jce.provider.BouncyCastleProvider
17-
import java.security.Security
16+
import com.zeapo.pwdstore.git.config.setUpBouncyCastleForSshj
1817

1918
@Suppress("Unused")
2019
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
@@ -29,7 +28,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
2928
}
3029
prefs?.registerOnSharedPreferenceChangeListener(this)
3130
setNightMode()
32-
setUpBouncyCastle()
31+
setUpBouncyCastleForSshj()
3332
}
3433

3534
override fun onTerminate() {
@@ -43,25 +42,6 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
4342
}
4443
}
4544

46-
private fun setUpBouncyCastle() {
47-
// Replace the Android BC provider with the Java BouncyCastle provider since the former does
48-
// not include all the required algorithms.
49-
// TODO: Verify that we are indeed using the fast Android-native implementation whenever
50-
// possible.
51-
// Note: This may affect crypto operations in other parts of the application.
52-
val bcIndex = Security.getProviders().indexOfFirst {
53-
it.name == BouncyCastleProvider.PROVIDER_NAME
54-
}
55-
if (bcIndex == -1) {
56-
// No Android BC found, install Java BC at lowest priority.
57-
Security.addProvider(BouncyCastleProvider())
58-
} else {
59-
// Replace Android BC with Java BC, inserted at the same position.
60-
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
61-
Security.insertProviderAt(BouncyCastleProvider(), bcIndex + 1)
62-
}
63-
}
64-
6545
private fun setNightMode() {
6646
AppCompatDelegate.setDefaultNightMode(when (prefs?.getString("app_theme", getString(R.string.app_theme_def))) {
6747
"light" -> MODE_NIGHT_NO
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
package com.zeapo.pwdstore.git.config
6+
7+
import com.github.ajalt.timberkt.Timber
8+
import com.github.ajalt.timberkt.d
9+
import com.hierynomus.sshj.signature.SignatureEdDSA
10+
import com.hierynomus.sshj.transport.cipher.BlockCiphers
11+
import com.hierynomus.sshj.transport.mac.Macs
12+
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile
13+
import net.schmizz.keepalive.KeepAliveProvider
14+
import net.schmizz.sshj.ConfigImpl
15+
import net.schmizz.sshj.common.LoggerFactory
16+
import net.schmizz.sshj.signature.SignatureECDSA
17+
import net.schmizz.sshj.signature.SignatureRSA
18+
import net.schmizz.sshj.signature.SignatureRSA.FactoryCERT
19+
import net.schmizz.sshj.transport.compression.NoneCompression
20+
import net.schmizz.sshj.transport.kex.Curve25519SHA256
21+
import net.schmizz.sshj.transport.kex.Curve25519SHA256.FactoryLibSsh
22+
import net.schmizz.sshj.transport.kex.DHGexSHA256
23+
import net.schmizz.sshj.transport.kex.ECDHNistP
24+
import net.schmizz.sshj.transport.random.JCERandom
25+
import net.schmizz.sshj.transport.random.SingletonRandomFactory
26+
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile
27+
import net.schmizz.sshj.userauth.keyprovider.PKCS5KeyFile
28+
import net.schmizz.sshj.userauth.keyprovider.PKCS8KeyFile
29+
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile
30+
import org.bouncycastle.jce.provider.BouncyCastleProvider
31+
import org.slf4j.Logger
32+
import org.slf4j.Marker
33+
import java.security.Security
34+
35+
36+
fun setUpBouncyCastleForSshj() {
37+
// Replace the Android BC provider with the Java BouncyCastle provider since the former does
38+
// not include all the required algorithms.
39+
// Note: This may affect crypto operations in other parts of the application.
40+
val bcIndex = Security.getProviders().indexOfFirst {
41+
it.name == BouncyCastleProvider.PROVIDER_NAME
42+
}
43+
if (bcIndex == -1) {
44+
// No Android BC found, install Java BC at lowest priority.
45+
Security.addProvider(BouncyCastleProvider())
46+
} else {
47+
// Replace Android BC with Java BC, inserted at the same position.
48+
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
49+
// May be needed on Android Pie+ as per https://stackoverflow.com/a/57897224/297261
50+
try {
51+
Class.forName("sun.security.jca.Providers")
52+
} catch (e: ClassNotFoundException) {
53+
}
54+
Security.insertProviderAt(BouncyCastleProvider(), bcIndex + 1)
55+
}
56+
d { "JCE providers: ${Security.getProviders().joinToString { "${it.name} (${it.version})" }}" }
57+
}
58+
59+
private abstract class AbstractLogger(private val name: String) : Logger {
60+
61+
abstract fun t(message: String, t: Throwable? = null, vararg args: Any?)
62+
abstract fun d(message: String, t: Throwable? = null, vararg args: Any?)
63+
abstract fun i(message: String, t: Throwable? = null, vararg args: Any?)
64+
abstract fun w(message: String, t: Throwable? = null, vararg args: Any?)
65+
abstract fun e(message: String, t: Throwable? = null, vararg args: Any?)
66+
67+
override fun getName() = name
68+
69+
override fun isTraceEnabled(marker: Marker?): Boolean = isTraceEnabled
70+
override fun isDebugEnabled(marker: Marker?): Boolean = isDebugEnabled
71+
override fun isInfoEnabled(marker: Marker?): Boolean = isInfoEnabled
72+
override fun isWarnEnabled(marker: Marker?): Boolean = isWarnEnabled
73+
override fun isErrorEnabled(marker: Marker?): Boolean = isErrorEnabled
74+
75+
override fun trace(msg: String) = t(msg)
76+
override fun trace(format: String, arg: Any?) = t(format, null, arg)
77+
override fun trace(format: String, arg1: Any?, arg2: Any?) = t(format, null, arg1, arg2)
78+
override fun trace(format: String, vararg arguments: Any?) = t(format, null, *arguments)
79+
override fun trace(msg: String, t: Throwable?) = t(msg, t)
80+
override fun trace(marker: Marker, msg: String) = trace(msg)
81+
override fun trace(marker: Marker?, format: String, arg: Any?) = trace(format, arg)
82+
override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
83+
trace(format, arg1, arg2)
84+
85+
override fun trace(marker: Marker?, format: String, vararg arguments: Any?) =
86+
trace(format, *arguments)
87+
88+
override fun trace(marker: Marker?, msg: String, t: Throwable?) = trace(msg, t)
89+
90+
override fun debug(msg: String) = d(msg)
91+
override fun debug(format: String, arg: Any?) = d(format, null, arg)
92+
override fun debug(format: String, arg1: Any?, arg2: Any?) = d(format, null, arg1, arg2)
93+
override fun debug(format: String, vararg arguments: Any?) = d(format, null, *arguments)
94+
override fun debug(msg: String, t: Throwable?) = d(msg, t)
95+
override fun debug(marker: Marker, msg: String) = debug(msg)
96+
override fun debug(marker: Marker?, format: String, arg: Any?) = debug(format, arg)
97+
override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
98+
debug(format, arg1, arg2)
99+
100+
override fun debug(marker: Marker?, format: String, vararg arguments: Any?) =
101+
debug(format, *arguments)
102+
103+
override fun debug(marker: Marker?, msg: String, t: Throwable?) = debug(msg, t)
104+
105+
override fun info(msg: String) = i(msg)
106+
override fun info(format: String, arg: Any?) = i(format, null, arg)
107+
override fun info(format: String, arg1: Any?, arg2: Any?) = i(format, null, arg1, arg2)
108+
override fun info(format: String, vararg arguments: Any?) = i(format, null, *arguments)
109+
override fun info(msg: String, t: Throwable?) = i(msg, t)
110+
override fun info(marker: Marker, msg: String) = info(msg)
111+
override fun info(marker: Marker?, format: String, arg: Any?) = info(format, arg)
112+
override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
113+
info(format, arg1, arg2)
114+
115+
override fun info(marker: Marker?, format: String, vararg arguments: Any?) =
116+
info(format, *arguments)
117+
118+
override fun info(marker: Marker?, msg: String, t: Throwable?) = info(msg, t)
119+
120+
override fun warn(msg: String) = w(msg)
121+
override fun warn(format: String, arg: Any?) = w(format, null, arg)
122+
override fun warn(format: String, arg1: Any?, arg2: Any?) = w(format, null, arg1, arg2)
123+
override fun warn(format: String, vararg arguments: Any?) = w(format, null, *arguments)
124+
override fun warn(msg: String, t: Throwable?) = w(msg, t)
125+
override fun warn(marker: Marker, msg: String) = warn(msg)
126+
override fun warn(marker: Marker?, format: String, arg: Any?) = warn(format, arg)
127+
override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
128+
warn(format, arg1, arg2)
129+
130+
override fun warn(marker: Marker?, format: String, vararg arguments: Any?) =
131+
warn(format, *arguments)
132+
133+
override fun warn(marker: Marker?, msg: String, t: Throwable?) = warn(msg, t)
134+
135+
override fun error(msg: String) = e(msg)
136+
override fun error(format: String, arg: Any?) = e(format, null, arg)
137+
override fun error(format: String, arg1: Any?, arg2: Any?) = e(format, null, arg1, arg2)
138+
override fun error(format: String, vararg arguments: Any?) = e(format, null, *arguments)
139+
override fun error(msg: String, t: Throwable?) = e(msg, t)
140+
override fun error(marker: Marker, msg: String) = error(msg)
141+
override fun error(marker: Marker?, format: String, arg: Any?) = error(format, arg)
142+
override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) =
143+
error(format, arg1, arg2)
144+
145+
override fun error(marker: Marker?, format: String, vararg arguments: Any?) =
146+
error(format, *arguments)
147+
148+
override fun error(marker: Marker?, msg: String, t: Throwable?) = error(msg, t)
149+
}
150+
151+
object TimberLoggerFactory : LoggerFactory {
152+
private class TimberLogger(name: String) : AbstractLogger(name) {
153+
154+
// We defer the log level checks to Timber.
155+
override fun isTraceEnabled() = true
156+
override fun isDebugEnabled() = true
157+
override fun isInfoEnabled() = true
158+
override fun isWarnEnabled() = true
159+
override fun isErrorEnabled() = true
160+
161+
// Replace slf4j's "{}" format string style with standard Java's "%s".
162+
// The supposedly redundant escape on the } is not redundant.
163+
@Suppress("RegExpRedundantEscape")
164+
private fun String.fix() = replace("""(?!<\\)\{\}""".toRegex(), "%s")
165+
166+
override fun t(message: String, t: Throwable?, vararg args: Any?) {
167+
Timber.tag(name).v(t, message.fix(), *args)
168+
}
169+
170+
override fun d(message: String, t: Throwable?, vararg args: Any?) {
171+
Timber.tag(name).d(t, message.fix(), *args)
172+
}
173+
174+
override fun i(message: String, t: Throwable?, vararg args: Any?) {
175+
Timber.tag(name).i(t, message.fix(), *args)
176+
}
177+
178+
override fun w(message: String, t: Throwable?, vararg args: Any?) {
179+
Timber.tag(name).w(t, message.fix(), *args)
180+
}
181+
182+
override fun e(message: String, t: Throwable?, vararg args: Any?) {
183+
Timber.tag(name).e(t, message.fix(), *args)
184+
}
185+
}
186+
187+
override fun getLogger(name: String): Logger {
188+
return TimberLogger(name)
189+
}
190+
191+
override fun getLogger(clazz: Class<*>): Logger {
192+
return TimberLogger(clazz.name)
193+
}
194+
195+
}
196+
197+
class SshjConfig : ConfigImpl() {
198+
199+
init {
200+
loggerFactory = TimberLoggerFactory
201+
keepAliveProvider = KeepAliveProvider.HEARTBEAT
202+
203+
initKeyExchangeFactories()
204+
initSignatureFactories()
205+
initRandomFactory()
206+
initFileKeyProviderFactories()
207+
initCipherFactories()
208+
initCompressionFactories()
209+
initMACFactories()
210+
}
211+
212+
private fun initKeyExchangeFactories() {
213+
keyExchangeFactories = listOf(
214+
Curve25519SHA256.Factory(),
215+
FactoryLibSsh(),
216+
ECDHNistP.Factory521(),
217+
ECDHNistP.Factory384(),
218+
ECDHNistP.Factory256(),
219+
DHGexSHA256.Factory()
220+
)
221+
}
222+
223+
private fun initSignatureFactories() {
224+
signatureFactories = listOf(
225+
SignatureEdDSA.Factory(),
226+
SignatureECDSA.Factory256(),
227+
SignatureECDSA.Factory384(),
228+
SignatureECDSA.Factory521(),
229+
SignatureRSA.Factory(),
230+
FactoryCERT()
231+
)
232+
}
233+
234+
private fun initRandomFactory() {
235+
randomFactory = SingletonRandomFactory(JCERandom.Factory())
236+
}
237+
238+
private fun initFileKeyProviderFactories() {
239+
fileKeyProviderFactories = listOf(
240+
OpenSSHKeyV1KeyFile.Factory(),
241+
PKCS8KeyFile.Factory(),
242+
PKCS5KeyFile.Factory(),
243+
OpenSSHKeyFile.Factory(),
244+
PuTTYKeyFile.Factory()
245+
)
246+
}
247+
248+
249+
private fun initCipherFactories() {
250+
cipherFactories = listOf(
251+
BlockCiphers.AES128CTR(),
252+
BlockCiphers.AES192CTR(),
253+
BlockCiphers.AES256CTR()
254+
)
255+
}
256+
257+
private fun initMACFactories() {
258+
macFactories = listOf(
259+
Macs.HMACSHA2256(),
260+
Macs.HMACSHA2256Etm(),
261+
Macs.HMACSHA2512(),
262+
Macs.HMACSHA2512Etm()
263+
)
264+
}
265+
266+
private fun initCompressionFactories() {
267+
compressionFactories = listOf(
268+
NoneCompression.Factory()
269+
)
270+
}
271+
}

app/src/main/java/com/zeapo/pwdstore/git/config/SshjSessionFactory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ private class SshjSession(private val uri: URIish, private val username: String,
128128
private var currentCommand: Session? = null
129129

130130
fun connect(): SshjSession {
131-
ssh = SSHClient()
131+
ssh = SSHClient(SshjConfig())
132132
ssh.addHostKeyVerifier(makeTofuHostKeyVerifier(hostKeyFile))
133133
ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
134134
if (!ssh.isConnected)

0 commit comments

Comments
 (0)