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

Commit 4dda3c2

Browse files
tadfishermsfjarvis
authored andcommitted
Add decryption callback to CryptoHandler
1 parent fadc3a4 commit 4dda3c2

File tree

16 files changed

+396
-237
lines changed

16 files changed

+396
-237
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dependencies {
5757
coreLibraryDesugaring(libs.android.desugarJdkLibs)
5858
implementation(projects.autofillParser)
5959
implementation(projects.coroutineUtils)
60+
implementation(projects.cryptoHwsecurity)
6061
implementation(projects.cryptoPgpainless)
6162
implementation(projects.formatCommonImpl)
6263
implementation(projects.passgen.diceware)

app/src/main/java/app/passwordstore/Application.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ package app.passwordstore
77
import android.content.SharedPreferences
88
import android.os.Build
99
import android.os.StrictMode
10-
import androidx.appcompat.app.AppCompatDelegate.*
10+
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
11+
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
12+
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
13+
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
14+
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
15+
import app.passwordstore.crypto.HWSecurityManager
1116
import app.passwordstore.injection.context.FilesDirPath
1217
import app.passwordstore.injection.prefs.SettingsPreferences
1318
import app.passwordstore.util.extensions.getString
@@ -42,16 +47,18 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
4247
@Inject lateinit var proxyUtils: ProxyUtils
4348
@Inject lateinit var gitSettings: GitSettings
4449
@Inject lateinit var features: Features
50+
@Inject lateinit var deviceManager: HWSecurityManager
4551

4652
override fun onCreate() {
4753
super.onCreate()
4854
instance = this
4955
LeakCanary.config =
5056
LeakCanary.config.copy(eventListeners = LeakCanary.config.eventListeners + SentryLeakUploader)
51-
if (
57+
58+
val enableLogging =
5259
BuildConfig.ENABLE_DEBUG_FEATURES ||
5360
prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)
54-
) {
61+
if (enableLogging) {
5562
LogcatLogger.install(AndroidLogcatLogger(DEBUG))
5663
AppWatcher.manualInstall(this)
5764
setVmPolicy()
@@ -62,6 +69,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
6269
runMigrations(filesDirPath, prefs, gitSettings)
6370
proxyUtils.setDefaultProxy()
6471
DynamicColors.applyToActivitiesIfAvailable(this)
72+
deviceManager.init(enableLogging)
6573
Sentry.configureScope { scope ->
6674
val user = User()
6775
user.data =

app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package app.passwordstore.data.crypto
77

88
import android.content.SharedPreferences
99
import app.passwordstore.crypto.GpgIdentifier
10+
import app.passwordstore.crypto.HWSecurityDeviceHandler
1011
import app.passwordstore.crypto.PGPDecryptOptions
1112
import app.passwordstore.crypto.PGPEncryptOptions
1213
import app.passwordstore.crypto.PGPKeyManager
@@ -16,11 +17,13 @@ import app.passwordstore.injection.prefs.SettingsPreferences
1617
import app.passwordstore.util.settings.PreferenceKeys
1718
import com.github.michaelbull.result.Result
1819
import com.github.michaelbull.result.getAll
20+
import com.github.michaelbull.result.getOrThrow
1921
import com.github.michaelbull.result.unwrap
2022
import java.io.ByteArrayInputStream
2123
import java.io.ByteArrayOutputStream
2224
import javax.inject.Inject
2325
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.runBlocking
2427
import kotlinx.coroutines.withContext
2528

2629
class CryptoRepository
@@ -29,6 +32,7 @@ constructor(
2932
private val pgpKeyManager: PGPKeyManager,
3033
private val pgpCryptoHandler: PGPainlessCryptoHandler,
3134
@SettingsPreferences private val settings: SharedPreferences,
35+
private val deviceHandler: HWSecurityDeviceHandler,
3236
) {
3337

3438
suspend fun decrypt(
@@ -50,7 +54,10 @@ constructor(
5054
): Result<Unit, CryptoHandlerException> {
5155
val decryptionOptions = PGPDecryptOptions.Builder().build()
5256
val keys = pgpKeyManager.getAllKeys().unwrap()
53-
return pgpCryptoHandler.decrypt(keys, password, message, out, decryptionOptions)
57+
return pgpCryptoHandler.decrypt(keys, password, message, out, decryptionOptions) {
58+
encryptedSessionKey ->
59+
runBlocking { deviceHandler.decryptSessionKey(encryptedSessionKey).getOrThrow() }
60+
}
5461
}
5562

5663
private suspend fun encryptPgp(

app/src/main/java/app/passwordstore/injection/crypto/CryptoHandlerModule.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,31 @@
55

66
package app.passwordstore.injection.crypto
77

8+
import android.app.Activity
9+
import androidx.fragment.app.FragmentActivity
10+
import app.passwordstore.crypto.HWSecurityDeviceHandler
11+
import app.passwordstore.crypto.HWSecurityManager
812
import app.passwordstore.crypto.PGPainlessCryptoHandler
913
import dagger.Module
1014
import dagger.Provides
1115
import dagger.hilt.InstallIn
12-
import dagger.hilt.components.SingletonComponent
16+
import dagger.hilt.android.components.ActivityComponent
17+
import dagger.hilt.android.scopes.ActivityScoped
1318

1419
@Module
15-
@InstallIn(SingletonComponent::class)
20+
@InstallIn(ActivityComponent::class)
1621
object CryptoHandlerModule {
22+
23+
@Provides
24+
@ActivityScoped
25+
fun provideDeviceHandler(
26+
activity: Activity,
27+
deviceManager: HWSecurityManager
28+
): HWSecurityDeviceHandler =
29+
HWSecurityDeviceHandler(
30+
deviceManager = deviceManager,
31+
fragmentManager = (activity as FragmentActivity).supportFragmentManager
32+
)
33+
1734
@Provides fun providePgpCryptoHandler() = PGPainlessCryptoHandler()
1835
}

crypto-common/src/main/kotlin/app/passwordstore/crypto/CryptoHandler.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@ import java.io.InputStream
1111
import java.io.OutputStream
1212

1313
/** Generic interface to implement cryptographic operations on top of. */
14-
public interface CryptoHandler<Key, EncOpts : CryptoOptions, DecryptOpts : CryptoOptions> {
14+
public interface CryptoHandler<
15+
Key,
16+
EncOpts : CryptoOptions,
17+
DecryptOpts : CryptoOptions,
18+
EncryptedSessionKey,
19+
DecryptedSessionKey,
20+
> {
1521

1622
/**
1723
* Decrypt the given [ciphertextStream] using a set of potential [keys] and [passphrase], and
@@ -25,6 +31,7 @@ public interface CryptoHandler<Key, EncOpts : CryptoOptions, DecryptOpts : Crypt
2531
ciphertextStream: InputStream,
2632
outputStream: OutputStream,
2733
options: DecryptOpts,
34+
onDecryptSessionKey: (EncryptedSessionKey) -> DecryptedSessionKey,
2835
): Result<Unit, CryptoHandlerException>
2936

3037
/**

crypto-common/src/main/kotlin/app/passwordstore/crypto/errors/CryptoException.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ public sealed class CryptoException(message: String? = null, cause: Throwable? =
66
Exception(message, cause)
77

88
/** Sealed exception types for [KeyManager]. */
9-
public sealed class KeyManagerException(message: String? = null, cause: Throwable? = null) : CryptoException(message, cause)
9+
public sealed class KeyManagerException(message: String? = null, cause: Throwable? = null) :
10+
CryptoException(message, cause)
1011

1112
/** Store contains no keys. */
1213
public object NoKeysAvailableException : KeyManagerException("No keys were found")
@@ -46,13 +47,12 @@ public class NoKeysProvided(message: String?) : CryptoHandlerException(message,
4647
/** An unexpected error that cannot be mapped to a known type. */
4748
public class UnknownError(cause: Throwable) : CryptoHandlerException(null, cause)
4849

49-
public class KeySpecific(public val key: Any, cause: Throwable?) : CryptoHandlerException(key.toString(), cause)
50+
public class KeySpecific(public val key: Any, cause: Throwable?) :
51+
CryptoHandlerException(key.toString(), cause)
5052

5153
/** Wrapper containing possibly multiple child exceptions via [suppressedExceptions]. */
52-
public class MultipleKeySpecific(
53-
message: String?,
54-
public val errors: List<KeySpecific>
55-
) : CryptoHandlerException(message) {
54+
public class MultipleKeySpecific(message: String?, public val errors: List<KeySpecific>) :
55+
CryptoHandlerException(message) {
5656
init {
5757
for (error in errors) {
5858
addSuppressed(error)
@@ -68,7 +68,8 @@ public sealed class DeviceHandlerException(message: String? = null, cause: Throw
6868
public class DeviceOperationCanceled(message: String) : DeviceHandlerException(message, null)
6969

7070
/** The device crypto operation failed. */
71-
public class DeviceOperationFailed(message: String?, cause: Throwable? = null) : DeviceHandlerException(message, cause)
71+
public class DeviceOperationFailed(message: String?, cause: Throwable? = null) :
72+
DeviceHandlerException(message, cause)
7273

7374
/** The device's key fingerprint doesn't match the fingerprint we are trying to pair it to. */
7475
public class DeviceFingerprintMismatch(
Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,56 @@
11
@file:Suppress("MagicNumber")
2+
23
package app.passwordstore.crypto
34

45
@JvmInline
5-
public value class DeviceIdentifier(
6-
private val aid: ByteArray
7-
) {
8-
init {
9-
require(aid.size == 16) { "Invalid device application identifier" }
10-
}
6+
public value class DeviceIdentifier(private val aid: ByteArray) {
7+
init {
8+
require(aid.size == 16) { "Invalid device application identifier" }
9+
}
1110

12-
public val openPgpVersion: String get() = "${aid[6]}.${aid[7]}"
11+
public val openPgpVersion: String
12+
get() = "${aid[6]}.${aid[7]}"
1313

14-
public val manufacturer: Int
15-
get() = ((aid[8].toInt() and 0xff) shl 8) or (aid[9].toInt() and 0xff)
14+
public val manufacturer: Int
15+
get() = ((aid[8].toInt() and 0xff) shl 8) or (aid[9].toInt() and 0xff)
1616

17-
public val serialNumber: ByteArray get() = aid.sliceArray(10..13)
18-
}
17+
public val serialNumber: ByteArray
18+
get() = aid.sliceArray(10..13)
19+
}
1920

2021
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=scd/app-openpgp.c;hb=HEAD#l292
21-
public val DeviceIdentifier.manufacturerName: String get() = when (manufacturer) {
22-
0x0001 -> "PPC Card Systems"
23-
0x0002 -> "Prism"
24-
0x0003 -> "OpenFortress"
25-
0x0004 -> "Wewid"
26-
0x0005 -> "ZeitControl"
27-
0x0006 -> "Yubico"
28-
0x0007 -> "OpenKMS"
29-
0x0008 -> "LogoEmail"
30-
0x0009 -> "Fidesmo"
31-
0x000A -> "VivoKey"
32-
0x000B -> "Feitian Technologies"
33-
0x000D -> "Dangerous Things"
34-
0x000E -> "Excelsecu"
35-
0x000F -> "Nitrokey"
36-
0x002A -> "Magrathea"
37-
0x0042 -> "GnuPG e.V."
38-
0x1337 -> "Warsaw Hackerspace"
39-
0x2342 -> "warpzone"
40-
0x4354 -> "Confidential Technologies"
41-
0x5343 -> "SSE Carte à puce"
42-
0x5443 -> "TIF-IT e.V."
43-
0x63AF -> "Trustica"
44-
0xBA53 -> "c-base e.V."
45-
0xBD0E -> "Paranoidlabs"
46-
0xCA05 -> "Atos CardOS"
47-
0xF1D0 -> "CanoKeys"
48-
0xF517 -> "FSIJ"
49-
0xF5EC -> "F-Secure"
50-
0x0000, 0xFFFF -> "test card"
51-
else -> "unknown"
52-
}
22+
public val DeviceIdentifier.manufacturerName: String
23+
get() =
24+
when (manufacturer) {
25+
0x0001 -> "PPC Card Systems"
26+
0x0002 -> "Prism"
27+
0x0003 -> "OpenFortress"
28+
0x0004 -> "Wewid"
29+
0x0005 -> "ZeitControl"
30+
0x0006 -> "Yubico"
31+
0x0007 -> "OpenKMS"
32+
0x0008 -> "LogoEmail"
33+
0x0009 -> "Fidesmo"
34+
0x000A -> "VivoKey"
35+
0x000B -> "Feitian Technologies"
36+
0x000D -> "Dangerous Things"
37+
0x000E -> "Excelsecu"
38+
0x000F -> "Nitrokey"
39+
0x002A -> "Magrathea"
40+
0x0042 -> "GnuPG e.V."
41+
0x1337 -> "Warsaw Hackerspace"
42+
0x2342 -> "warpzone"
43+
0x4354 -> "Confidential Technologies"
44+
0x5343 -> "SSE Carte à puce"
45+
0x5443 -> "TIF-IT e.V."
46+
0x63AF -> "Trustica"
47+
0xBA53 -> "c-base e.V."
48+
0xBD0E -> "Paranoidlabs"
49+
0xCA05 -> "Atos CardOS"
50+
0xF1D0 -> "CanoKeys"
51+
0xF517 -> "FSIJ"
52+
0xF5EC -> "F-Secure"
53+
0x0000,
54+
0xFFFF -> "test card"
55+
else -> "unknown"
56+
}

crypto-hwsecurity/src/main/kotlin/app/passwordstore/crypto/DeviceKeyInfo.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
44
import org.pgpainless.key.OpenPgpFingerprint
55

66
public data class DeviceKeyInfo(
7-
public val algorithm: PublicKeyAlgorithm,
8-
public val fingerprint: OpenPgpFingerprint
7+
public val algorithm: PublicKeyAlgorithm,
8+
public val fingerprint: OpenPgpFingerprint
99
) {
10-
override fun toString(): String = "${algorithm.displayName()} ${fingerprint.prettyPrint()}"
10+
override fun toString(): String = "${algorithm.displayName()} ${fingerprint.prettyPrint()}"
1111
}
1212

1313
@Suppress("DEPRECATION")
14-
private fun PublicKeyAlgorithm.displayName(): String = when (this) {
14+
private fun PublicKeyAlgorithm.displayName(): String =
15+
when (this) {
1516
PublicKeyAlgorithm.RSA_GENERAL -> "RSA"
1617
PublicKeyAlgorithm.RSA_ENCRYPT -> "RSA (encrypt-only, deprecated)"
1718
PublicKeyAlgorithm.RSA_SIGN -> "RSA (sign-only, deprecated)"
@@ -23,4 +24,4 @@ private fun PublicKeyAlgorithm.displayName(): String = when (this) {
2324
PublicKeyAlgorithm.ELGAMAL_GENERAL -> "ElGamal (general, deprecated)"
2425
PublicKeyAlgorithm.DIFFIE_HELLMAN -> "Diffie-Hellman"
2526
PublicKeyAlgorithm.EDDSA -> "EDDSA"
26-
}
27+
}

crypto-hwsecurity/src/main/kotlin/app/passwordstore/crypto/HWSecurityDevice.kt

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,38 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm
88
import org.pgpainless.key.OpenPgpFingerprint
99

1010
public class HWSecurityDevice(
11-
public val id: DeviceIdentifier,
12-
public val name: String,
13-
public val encryptKeyInfo: DeviceKeyInfo?,
14-
public val signKeyInfo: DeviceKeyInfo?,
15-
public val authKeyInfo: DeviceKeyInfo?,
11+
public val id: DeviceIdentifier,
12+
public val name: String,
13+
public val encryptKeyInfo: DeviceKeyInfo?,
14+
public val signKeyInfo: DeviceKeyInfo?,
15+
public val authKeyInfo: DeviceKeyInfo?,
1616
)
1717

1818
internal fun OpenPgpSecurityKey.toDevice(): HWSecurityDevice =
19-
with (openPgpAppletConnection.openPgpCapabilities) {
20-
HWSecurityDevice(
21-
id = DeviceIdentifier(aid),
22-
name = securityKeyName,
23-
encryptKeyInfo = keyInfo(encryptKeyFormat, fingerprintEncrypt),
24-
signKeyInfo = keyInfo(signKeyFormat, fingerprintSign),
25-
authKeyInfo = keyInfo(authKeyFormat, fingerprintAuth)
26-
)
27-
}
19+
with(openPgpAppletConnection.openPgpCapabilities) {
20+
HWSecurityDevice(
21+
id = DeviceIdentifier(aid),
22+
name = securityKeyName,
23+
encryptKeyInfo = keyInfo(encryptKeyFormat, fingerprintEncrypt),
24+
signKeyInfo = keyInfo(signKeyFormat, fingerprintSign),
25+
authKeyInfo = keyInfo(authKeyFormat, fingerprintAuth)
26+
)
27+
}
2828

29-
internal fun keyInfo(
30-
format: KeyFormat?,
31-
fingerprint: ByteArray?
32-
): DeviceKeyInfo? {
33-
if (format == null || fingerprint == null) return null
34-
return DeviceKeyInfo(format.toKeyAlgorithm(), OpenPgpFingerprint.parseFromBinary(fingerprint))
29+
internal fun keyInfo(format: KeyFormat?, fingerprint: ByteArray?): DeviceKeyInfo? {
30+
if (format == null || fingerprint == null) return null
31+
return DeviceKeyInfo(format.toKeyAlgorithm(), OpenPgpFingerprint.parseFromBinary(fingerprint))
3532
}
3633

37-
internal fun KeyFormat.toKeyAlgorithm(): PublicKeyAlgorithm = when (this) {
34+
internal fun KeyFormat.toKeyAlgorithm(): PublicKeyAlgorithm =
35+
when (this) {
3836
is RsaKeyFormat -> PublicKeyAlgorithm.RSA_GENERAL
39-
is EcKeyFormat -> when (val id = algorithmId()) {
37+
is EcKeyFormat ->
38+
when (val id = algorithmId()) {
4039
PublicKeyAlgorithm.ECDH.algorithmId -> PublicKeyAlgorithm.ECDH
4140
PublicKeyAlgorithm.ECDSA.algorithmId -> PublicKeyAlgorithm.ECDSA
4241
PublicKeyAlgorithm.EDDSA.algorithmId -> PublicKeyAlgorithm.EDDSA
4342
else -> throw IllegalArgumentException("Unknown EC algorithm ID: $id")
44-
}
43+
}
4544
else -> throw IllegalArgumentException("Unknown key format")
46-
}
45+
}

0 commit comments

Comments
 (0)