mirror of
https://github.com/markusfisch/BinaryEye.git
synced 2024-09-20 12:02:17 +02:00
Remove parsing of OTPs
We should not check the content of the OTP code, instead let the receiver handle the content and give appropriate errors, if needed.
This commit is contained in:
parent
d925e136ef
commit
adecdf7e27
@ -1,22 +1,10 @@
|
||||
package de.markusfisch.android.binaryeye.actions.otpauth
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import de.markusfisch.android.binaryeye.R
|
||||
import de.markusfisch.android.binaryeye.actions.IntentAction
|
||||
import de.markusfisch.android.binaryeye.actions.SchemeAction
|
||||
|
||||
object OtpauthAction : IntentAction() {
|
||||
object OtpauthAction : SchemeAction() {
|
||||
override val iconResId: Int = R.drawable.ic_action_otpauth
|
||||
override val titleResId: Int = R.string.otpauth_add
|
||||
override val errorMsg: Int = R.string.otpauth_error
|
||||
|
||||
override fun canExecuteOn(data: ByteArray): Boolean {
|
||||
return OtpauthParser(String(data)) != null
|
||||
}
|
||||
|
||||
override suspend fun createIntent(context: Context, data: ByteArray): Intent? {
|
||||
return OtpauthParser(String(data))?.let {
|
||||
Intent(Intent.ACTION_VIEW, it.uri)
|
||||
}
|
||||
}
|
||||
override val scheme: String = "otpauth"
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.actions.otpauth
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
class OtpauthParser private constructor(private val match: MatchResult) {
|
||||
val otpType: String
|
||||
get() = match.groupValues[1]
|
||||
private val labelIssuer: String
|
||||
get() = match.groupValues[2]
|
||||
val accountName: String
|
||||
get() = match.groupValues[3].takeIf { it.isNotEmpty() } ?: match.groupValues[4]
|
||||
val secret: String
|
||||
get() = match.groupValues[5]
|
||||
private val parameterIssuer: String
|
||||
get() = match.groupValues[6]
|
||||
val algorithm: String
|
||||
get() = match.groupValues[7].takeIf { it.isNotEmpty() } ?: "SHA1"
|
||||
val digits: Int
|
||||
get() = match.groupValues[8].toIntOrNull() ?: 6
|
||||
val period: Int
|
||||
get() = match.groupValues[9].toIntOrNull() ?: 30
|
||||
private val parameterCounter: String
|
||||
get() = match.groupValues[10]
|
||||
val issuer: String
|
||||
get() = labelIssuer.takeIf { it.isNotEmpty() } ?: parameterIssuer
|
||||
val counter: Int
|
||||
get() = parameterCounter.toInt()
|
||||
private val isHotp: Boolean = otpType == "hotp"
|
||||
val isValid: Boolean =
|
||||
(labelIssuer.isNotEmpty() == parameterIssuer.isEmpty() || labelIssuer.isEmpty())
|
||||
&& secret.isNotEmpty() && isHotp == parameterCounter.isNotEmpty()
|
||||
|
||||
val uri: Uri
|
||||
get() = Uri.Builder().apply {
|
||||
scheme("otpauth")
|
||||
authority(otpType)
|
||||
|
||||
if (issuer.isNotEmpty()) {
|
||||
encodedPath("/$issuer:$accountName")
|
||||
appendQueryParameter("issuer", issuer)
|
||||
} else {
|
||||
encodedPath("/$accountName")
|
||||
}
|
||||
|
||||
appendQueryParameter("secret", secret)
|
||||
appendQueryParameter("algorithm", algorithm)
|
||||
appendQueryParameter("digits", digits.toString())
|
||||
|
||||
if (isHotp) {
|
||||
appendQueryParameter("counter", counter.toString())
|
||||
} else {
|
||||
appendQueryParameter("period", period.toString())
|
||||
}
|
||||
}.build()
|
||||
|
||||
companion object {
|
||||
private const val issuerAndAccount =
|
||||
"""(?:(?:([^?:]+)(?::|%3A)(?:%20)*([^?]+))|([^?]+))"""//.toRegex()
|
||||
/** allowing `padding` even though it should not be there, will be removed in output*/
|
||||
private const val secret = """(?:secret=([2-7A-Z]+)=*)"""//.toRegex()
|
||||
/** `\2` references the issuer of [issuerAndAccount] */
|
||||
private const val issuer = """(?:issuer=\2)|(?:issuer=([^&]+))"""//.toRegex()
|
||||
private const val algorithm = """(?:algorithm=(SHA(?:1|256|512)))"""//.toRegex()
|
||||
private const val digits = """(?:digits=([0-9]+))"""//.toRegex()
|
||||
private const val period = """(?:period=([0-9]+))"""//.toRegex()
|
||||
private const val counter = """(?:counter=([0-9]+))"""//.toRegex()
|
||||
private val otpauthRegex =
|
||||
"""^otpauth://([ht]otp)/$issuerAndAccount\?(?:&?(?:$secret|$issuer|$algorithm|$digits|$period|$counter))+$""".toRegex(
|
||||
RegexOption.IGNORE_CASE
|
||||
)
|
||||
|
||||
operator fun invoke(input: String): OtpauthParser? {
|
||||
return otpauthRegex.matchEntire(input)?.let(::OtpauthParser)?.takeIf { it.isValid }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
package de.markusfisch.android.binaryeye.actions.otpauth
|
||||
|
||||
import de.markusfisch.android.binaryeye.assertThrows
|
||||
import de.markusfisch.android.binaryeye.simpleFail
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class OtpauthParserTest {
|
||||
@Test
|
||||
fun noOtpauthUri() {
|
||||
assertNull(OtpauthParser("random string"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingSecret() {
|
||||
assertNull(OtpauthParser("otpauth://totp/issuerName:accountName"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingCounterHotp() {
|
||||
assertNull(OtpauthParser("otpauth://hotp/accountName?secret=secretString"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minimalHotp() {
|
||||
val parser = validParser("otpauth://hotp/accountName?secret=secretString&counter=50")
|
||||
assertEquals("hotp", parser.otpType)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA1", parser.algorithm)
|
||||
assertEquals(6, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertEquals(50, parser.counter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalHotp() {
|
||||
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=0")
|
||||
assertEquals("hotp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA1", parser.algorithm)
|
||||
assertEquals(6, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertEquals(0, parser.counter)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingIssuerParamHotp() {
|
||||
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=50&algorithm=SHA256&digits=8")
|
||||
assertEquals("hotp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA256", parser.algorithm)
|
||||
assertEquals(8, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertEquals(50, parser.counter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fullHotp() {
|
||||
val parser = validParser("otpauth://hotp/issuerName:accountName?secret=secretString&counter=50&issuer=issuerName&algorithm=SHA256&digits=8")
|
||||
assertEquals("hotp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA256", parser.algorithm)
|
||||
assertEquals(8, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertEquals(50, parser.counter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun minimalTotp() {
|
||||
val parser = validParser("otpauth://totp/accountName?secret=secretString")
|
||||
assertEquals("totp", parser.otpType)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA1", parser.algorithm)
|
||||
assertEquals(6, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertThrows<NumberFormatException> {
|
||||
parser.counter
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun normalTotp() {
|
||||
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString")
|
||||
assertEquals("totp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA1", parser.algorithm)
|
||||
assertEquals(6, parser.digits)
|
||||
assertEquals(30, parser.period)
|
||||
assertThrows<NumberFormatException> {
|
||||
parser.counter
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingIssuerParamTotp() {
|
||||
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString&algorithm=SHA256&digits=8&period=60")
|
||||
assertEquals("totp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA256", parser.algorithm)
|
||||
assertEquals(8, parser.digits)
|
||||
assertEquals(60, parser.period)
|
||||
assertThrows<NumberFormatException> {
|
||||
parser.counter
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fullTotp() {
|
||||
val parser = validParser("otpauth://totp/issuerName:accountName?secret=secretString&issuer=issuerName&algorithm=SHA256&digits=8&period=60")
|
||||
assertEquals("totp", parser.otpType)
|
||||
assertEquals("issuerName", parser.issuer)
|
||||
assertEquals("accountName", parser.accountName)
|
||||
assertEquals("secretString", parser.secret)
|
||||
assertEquals("SHA256", parser.algorithm)
|
||||
assertEquals(8, parser.digits)
|
||||
assertEquals(60, parser.period)
|
||||
assertThrows<NumberFormatException> {
|
||||
parser.counter
|
||||
}
|
||||
}
|
||||
|
||||
private fun validParser(input: String): OtpauthParser {
|
||||
return OtpauthParser(input) ?: simpleFail("Could not get parser of valid input")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user