Build OTP url

This commit is contained in:
J-Jamet
2019-11-07 19:28:54 +01:00
parent 221f81f51e
commit 23859a61bb
5 changed files with 57 additions and 39 deletions

View File

@@ -40,6 +40,7 @@ import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
@@ -355,7 +356,10 @@ class EntryEditActivity : LockingHideActivity(),
R.id.menu_add_otp -> { R.id.menu_add_otp -> {
SetOTPDialogFragment().apply { SetOTPDialogFragment().apply {
createOTPElementListener = { otpElement -> createOTPElementListener = { otpElement ->
// TODO Add custom field val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
entryEditContentsView?.addNewCustomField(otpField.name, otpField.protectedValue)
} }
}.show(supportFragmentManager, "addOTPDialog") }.show(supportFragmentManager, "addOTPDialog")
return true return true

View File

@@ -25,6 +25,7 @@ import android.util.Log
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.TokenCalculator.* import com.kunzisoft.keepass.otp.TokenCalculator.*
import java.lang.StringBuilder
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -161,48 +162,50 @@ object OtpEntryFields {
otpElement.period = stepParam.toInt() otpElement.period = stepParam.toInt()
val algorithmParam = uri.getQueryParameter(ALGORITHM_URL_PARAM) val algorithmParam = uri.getQueryParameter(ALGORITHM_URL_PARAM)
if (algorithmParam != null && algorithmParam.isNotEmpty()) if (algorithmParam != null && algorithmParam.isNotEmpty()) {
otpElement.algorithm = HashAlgorithm.valueOf(algorithmParam.toUpperCase(Locale.ENGLISH)) otpElement.algorithm = HashAlgorithm.fromString(algorithmParam)
}
return true return true
} }
return false return false
} }
private fun buildOtpUri(otpElement: OtpElement): Uri { private fun buildOtpUri(otpElement: OtpElement, title: String?, username: String?): Uri {
val counterOrPeriodLabel: String val counterOrPeriodLabel: String
val counterOrPeriodValue: String val counterOrPeriodValue: String
val otpAuthority = when (otpElement.type) { val otpAuthority: String
when (otpElement.type) {
OtpType.TOTP -> { OtpType.TOTP -> {
counterOrPeriodLabel = PERIOD_URL_PARAM counterOrPeriodLabel = PERIOD_URL_PARAM
counterOrPeriodValue = otpElement.period.toString() counterOrPeriodValue = otpElement.period.toString()
TOTP_AUTHORITY otpAuthority = TOTP_AUTHORITY
} }
else -> { else -> {
counterOrPeriodLabel = COUNTER_URL_PARAM counterOrPeriodLabel = COUNTER_URL_PARAM
counterOrPeriodValue = otpElement.counter.toString() counterOrPeriodValue = otpElement.counter.toString()
HOTP_AUTHORITY otpAuthority = HOTP_AUTHORITY
} }
} }
val accountName = "OTP" val issuer = if (otpElement.issuer.isEmpty()) {
val issuer = "None" if (title == null || title.isEmpty())"None" else title
val otpLabel = "$issuer:$accountName" } else {
val otpSecret = otpElement.getBase32Secret() otpElement.issuer
val otpDigits = otpElement.digits }
val otpIssuer = otpElement.issuer val accountName = if (username == null || username.isEmpty()) "OTP" else username
val otpAlgorithm = otpElement.algorithm.toString() val uriString = StringBuilder("otpauth://$otpAuthority/$issuer:$accountName" +
"?$SECRET_URL_PARAM=${otpElement.getBase32Secret()}" +
val uriString = "otpauth://$otpAuthority/$otpLabel" +
"?$SECRET_URL_PARAM=$otpSecret" +
"&$counterOrPeriodLabel=$counterOrPeriodValue" + "&$counterOrPeriodLabel=$counterOrPeriodValue" +
"&$DIGITS_URL_PARAM=$otpDigits" + "&$DIGITS_URL_PARAM=${otpElement.digits}" +
"&$ISSUER_URL_PARAM=$otpIssuer" + "&$ISSUER_URL_PARAM=$issuer")
"&$ALGORITHM_URL_PARAM=$otpAlgorithm" + if (otpElement.tokenType == OtpTokenType.STEAM) {
"\n" uriString.append("&$ENCODER_URL_PARAM=${otpElement.tokenType}")
} else {
uriString.append("&$ALGORITHM_URL_PARAM=${otpElement.algorithm}")
}
// TODO steam with ENCODER_URL_PARAM return Uri.parse(uriString.toString())
return Uri.parse(uriString)
} }
private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
@@ -296,8 +299,9 @@ object OtpEntryFields {
/** /**
* Build Otp field from an OtpElement * Build Otp field from an OtpElement
*/ */
fun buildOtpField(otpElement: OtpElement): Field { fun buildOtpField(otpElement: OtpElement, title: String?, username: String?): Field {
return Field(OTP_FIELD, ProtectedString(true, buildOtpUri(otpElement).toString())) return Field(OTP_FIELD, ProtectedString(true,
buildOtpUri(otpElement, title, username).toString()))
} }
/** /**

View File

@@ -42,7 +42,16 @@ public class TokenCalculator {
}; };
public enum HashAlgorithm { public enum HashAlgorithm {
SHA1, SHA256, SHA512 SHA1, SHA256, SHA512;
static HashAlgorithm fromString(String hashString) {
String hash = hashString.replace("[^a-zA-Z0-9]", "").toUpperCase();
try {
return valueOf(hash);
} catch (Exception e) {
return SHA1;
}
}
} }
public static final HashAlgorithm DEFAULT_ALGORITHM = HashAlgorithm.SHA1; public static final HashAlgorithm DEFAULT_ALGORITHM = HashAlgorithm.SHA1;

View File

@@ -168,6 +168,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context,
/** /**
* Add a new view to fill in the information of the customized field * Add a new view to fill in the information of the customized field
*/ */
// TODO replace if name already exists
fun addNewCustomField(name: String = "", value: ProtectedString = ProtectedString(false, "")) { fun addNewCustomField(name: String = "", value: ProtectedString = ProtectedString(false, "")) {
val entryEditCustomField = EntryEditCustomField(context).apply { val entryEditCustomField = EntryEditCustomField(context).apply {
setData(name, value) setData(name, value)

View File

@@ -88,6 +88,18 @@
android:layout_margin="@dimen/default_margin" android:layout_margin="@dimen/default_margin"
android:orientation="vertical"> android:orientation="vertical">
<!-- Algorithm -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/otp_algorithm"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/setup_otp_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp" />
<!-- Period / Counter --> <!-- Period / Counter -->
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/setup_otp_period_title" android:id="@+id/setup_otp_period_title"
@@ -141,18 +153,6 @@
tools:targetApi="jelly_bean" /> tools:targetApi="jelly_bean" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<!-- Algorithm -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/otp_algorithm"
style="@style/KeepassDXStyle.TextAppearance.LabelTextStyle"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/setup_otp_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</LinearLayout> </LinearLayout>