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

Commit 17d4b80

Browse files
committed
fix(format/common): validate TOTP secret ahead of time
Fixes #2949
1 parent 825c8af commit 17d4b80

File tree

2 files changed

+34
-13
lines changed

2 files changed

+34
-13
lines changed

format/common/src/main/kotlin/app/passwordstore/data/passfile/PasswordEntry.kt

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import androidx.annotation.VisibleForTesting
99
import app.passwordstore.util.time.UserClock
1010
import app.passwordstore.util.totp.Otp
1111
import app.passwordstore.util.totp.TotpFinder
12+
import com.github.michaelbull.result.Err
13+
import com.github.michaelbull.result.Ok
14+
import com.github.michaelbull.result.Result
1215
import com.github.michaelbull.result.mapBoth
1316
import dagger.assisted.Assisted
1417
import dagger.assisted.AssistedFactory
@@ -60,14 +63,22 @@ constructor(
6063
require(totpSecret != null) { "Cannot collect this flow without a TOTP secret" }
6164
do {
6265
val otp = calculateTotp()
63-
emit(otp)
64-
delay(THOUSAND_MILLIS.milliseconds)
66+
if (otp.isOk) {
67+
emit(otp.value)
68+
delay(THOUSAND_MILLIS.milliseconds)
69+
} else {
70+
throw otp.error
71+
}
6572
} while (coroutineContext.isActive)
6673
}
6774

6875
/** Obtain the [Totp.value] for this [PasswordEntry] at the current time. */
6976
public val currentOtp: String
70-
get() = calculateTotp().value
77+
get() {
78+
val otp = calculateTotp()
79+
check(otp.isOk)
80+
return otp.value.value
81+
}
7182

7283
/**
7384
* String representation of [extraContent] but with authentication related data such as TOTP URIs
@@ -83,7 +94,14 @@ constructor(
8394
extraContentWithoutAuthData = generateExtraContentWithoutAuthData()
8495
extraContent = generateExtraContentPairs()
8596
username = findUsername()
86-
totpSecret = totpFinder.findSecret(content)
97+
// Verify the TOTP secret is valid and disable TOTP if not.
98+
val secret = totpFinder.findSecret(content)
99+
totpSecret =
100+
if (secret != null && calculateTotp(secret).isOk) {
101+
secret
102+
} else {
103+
null
104+
}
87105
}
88106

89107
public fun hasTotp(): Boolean {
@@ -175,26 +193,21 @@ constructor(
175193
return null
176194
}
177195

178-
private fun calculateTotp(): Totp {
196+
private fun calculateTotp(secret: String = totpSecret!!): Result<Totp, Throwable> {
179197
val digits = totpFinder.findDigits(content)
180198
val totpPeriod = totpFinder.findPeriod(content)
181199
val totpAlgorithm = totpFinder.findAlgorithm(content)
182200
val issuer = totpFinder.findIssuer(content)
183201
val millis = clock.millis()
184202
val remainingTime = (totpPeriod - ((millis / THOUSAND_MILLIS) % totpPeriod)).seconds
185-
Otp.calculateCode(
186-
totpSecret!!,
203+
return Otp.calculateCode(
204+
secret,
187205
millis / (THOUSAND_MILLIS * totpPeriod),
188206
totpAlgorithm,
189207
digits,
190208
issuer
191209
)
192-
.mapBoth(
193-
{ code ->
194-
return Totp(code, remainingTime)
195-
},
196-
{ throwable -> throw throwable }
197-
)
210+
.mapBoth({ code -> Ok(Totp(code, remainingTime)) }, ::Err)
198211
}
199212

200213
@AssistedFactory

format/common/src/test/kotlin/app/passwordstore/data/passfile/PasswordEntryTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import app.passwordstore.util.totp.UriTotpFinder
1313
import java.util.Locale
1414
import kotlin.test.Test
1515
import kotlin.test.assertEquals
16+
import kotlin.test.assertFalse
1617
import kotlin.test.assertNotNull
1718
import kotlin.test.assertNull
1819
import kotlin.test.assertTrue
@@ -181,6 +182,13 @@ class PasswordEntryTest {
181182
}
182183
}
183184

185+
// https://github.com/android-password-store/Android-Password-Store/issues/2949
186+
@Test
187+
fun disablesTotpForInvalidUri() = runTest {
188+
val entry = makeEntry("password\notpauth://totp/otp-secret?secret=")
189+
assertFalse(entry.hasTotp())
190+
}
191+
184192
@Test
185193
fun onlyLooksForUriInFirstLine() {
186194
val entry = makeEntry("id:\n$TOTP_URI")

0 commit comments

Comments
 (0)