From 37146702872c301e01e4b548956a25e519d8fe4d Mon Sep 17 00:00:00 2001 From: Artem Chepurnoy Date: Sat, 14 Sep 2024 17:16:36 +0300 Subject: [PATCH] feature: Add OTP code by scanning Google Authenticator single export code #556 --- common/build.gradle.kts | 1 + .../common/model/CryptoHashAlgorithm.kt | 1 + .../keyguard/common/model/TotpToken.kt | 24 +-- .../OtpMigrationService.kt | 13 ++ .../impl/OtpMigrationServiceImpl.kt | 40 +++++ .../model/OtpAuthMigrationData.kt | 144 ++++++++++++++++++ .../util/OtpMigrationConst.kt | 5 + .../util/OtpMigrationConverter.kt | 87 +++++++++++ .../util/OtpMigrationParser.kt | 40 +++++ .../service/totp/impl/TotpServiceImpl.kt | 1 + .../keyguard/feature/add/AddScreen.kt | 2 +- .../keyguard/feature/add/AddStateItem.kt | 1 + .../home/vault/add/AddStateProducer.kt | 12 ++ .../artemchep/keyguard/di/GlobalModuleJvm.kt | 13 ++ .../kotlin/com/artemchep/keyguard/Main.kt | 14 ++ gradle/libs.versions.toml | 1 + 16 files changed, 386 insertions(+), 13 deletions(-) create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/OtpMigrationService.kt create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/impl/OtpMigrationServiceImpl.kt create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/model/OtpAuthMigrationData.kt create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConst.kt create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConverter.kt create mode 100644 common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationParser.kt diff --git a/common/build.gradle.kts b/common/build.gradle.kts index e25c9d8a..6cec144c 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -119,6 +119,7 @@ kotlin { api(libs.kotlinx.datetime) api(libs.kotlinx.serialization.json) api(libs.kotlinx.serialization.cbor) + api(libs.kotlinx.serialization.protobuf) api(libs.arrow.arrow.core) api(libs.arrow.arrow.optics) api(libs.kodein.kodein.di) diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/CryptoHashAlgorithm.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/CryptoHashAlgorithm.kt index 23d637f8..81370d76 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/CryptoHashAlgorithm.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/CryptoHashAlgorithm.kt @@ -4,4 +4,5 @@ enum class CryptoHashAlgorithm { SHA_1, SHA_256, SHA_512, + MD5, } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/TotpToken.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/TotpToken.kt index 4c8a017b..bc428357 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/TotpToken.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/model/TotpToken.kt @@ -157,12 +157,8 @@ private fun parseTotpAuth( keyBase32 = secretParam } params["algorithm"]?.also { algorithmParam -> - val alg = when (algorithmParam.lowercase()) { - "sha1" -> CryptoHashAlgorithm.SHA_1 - "sha256" -> CryptoHashAlgorithm.SHA_256 - "sha512" -> CryptoHashAlgorithm.SHA_512 - else -> return@also - } + val alg = parseHashAlgorithmOrNull(algorithmParam) + ?: return@also builder.algorithm = alg } @@ -201,12 +197,8 @@ private fun parseHotpAuth( keyBase32 = secretParam } params["algorithm"]?.also { algorithmParam -> - val alg = when (algorithmParam.lowercase()) { - "sha1" -> CryptoHashAlgorithm.SHA_1 - "sha256" -> CryptoHashAlgorithm.SHA_256 - "sha512" -> CryptoHashAlgorithm.SHA_512 - else -> return@also - } + val alg = parseHashAlgorithmOrNull(algorithmParam) + ?: return@also builder.algorithm = alg } @@ -220,6 +212,14 @@ private fun parseHotpAuth( ) } +private fun parseHashAlgorithmOrNull(name: String) = when (name.lowercase()) { + "sha1" -> CryptoHashAlgorithm.SHA_1 + "sha256" -> CryptoHashAlgorithm.SHA_256 + "sha512" -> CryptoHashAlgorithm.SHA_512 + "md5" -> CryptoHashAlgorithm.MD5 + else -> null +} + private fun parseOtpSteam( url: String, ): Either = Either.catch { diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/OtpMigrationService.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/OtpMigrationService.kt new file mode 100644 index 00000000..d3d49551 --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/OtpMigrationService.kt @@ -0,0 +1,13 @@ +package com.artemchep.keyguard.common.service.googleauthenticator + +import arrow.core.Either + +interface OtpMigrationService { + /** + * Returns a migrations service to use, or + * `null` if there no tool to use. + */ + fun handler(uri: String): OtpMigrationService? + + fun convert(uri: String): Either +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/impl/OtpMigrationServiceImpl.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/impl/OtpMigrationServiceImpl.kt new file mode 100644 index 00000000..2595191a --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/impl/OtpMigrationServiceImpl.kt @@ -0,0 +1,40 @@ +package com.artemchep.keyguard.common.service.googleauthenticator.impl + +import arrow.core.Either +import arrow.core.flatMap +import arrow.core.flatten +import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationConst +import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService +import com.artemchep.keyguard.common.service.googleauthenticator.util.build +import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationParser +import com.artemchep.keyguard.common.service.text.Base32Service +import org.kodein.di.DirectDI +import org.kodein.di.instance + +class OtpMigrationServiceImpl( + private val base32Service: Base32Service, + private val otpMigrationParser: OtpMigrationParser, +) : OtpMigrationService { + constructor( + directDI: DirectDI, + ) : this( + base32Service = directDI.instance(), + otpMigrationParser = directDI.instance(), + ) + + override fun handler(uri: String): OtpMigrationService? = + this.takeIf { uri.startsWith(OtpMigrationConst.PREFIX) } + + override fun convert( + uri: String, + ): Either = Either.catch { + otpMigrationParser.parse(uri) + .flatMap { + it.otpParameters + .first() + .build( + base32Service = base32Service, + ) + } + }.flatten() +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/model/OtpAuthMigrationData.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/model/OtpAuthMigrationData.kt new file mode 100644 index 00000000..121b6910 --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/model/OtpAuthMigrationData.kt @@ -0,0 +1,144 @@ +package com.artemchep.keyguard.common.service.googleauthenticator.model + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber + +/* +Schema is available here +https://github.com/dim13/otpauth/blob/master/migration/migration.proto +under ISC License 2020 Dimitri Sokolyuk + +message Payload { + message OtpParameters { + enum Algorithm { + ALGORITHM_UNSPECIFIED = 0; + ALGORITHM_SHA1 = 1; + ALGORITHM_SHA256 = 2; + ALGORITHM_SHA512 = 3; + ALGORITHM_MD5 = 4; + } + enum DigitCount { + DIGIT_COUNT_UNSPECIFIED = 0; + DIGIT_COUNT_SIX = 1; + DIGIT_COUNT_EIGHT = 2; + } + enum OtpType { + OTP_TYPE_UNSPECIFIED = 0; + OTP_TYPE_HOTP = 1; + OTP_TYPE_TOTP = 2; + } + bytes secret = 1; + string name = 2; + string issuer = 3; + Algorithm algorithm = 4; + DigitCount digits = 5; + OtpType type = 6; + uint64 counter = 7; + } + repeated OtpParameters otp_parameters = 1; + int32 version = 2; + int32 batch_size = 3; + int32 batch_index = 4; + int32 batch_id = 5; +} + */ +@OptIn(ExperimentalSerializationApi::class) +@Serializable +data class OtpAuthMigrationData( + @ProtoNumber(1) + @SerialName("otp_parameters") + val otpParameters: List, + @ProtoNumber(2) + val version: Int = 0, + @ProtoNumber(3) + @SerialName("batch_size") + val batchSize: Int = 0, + @ProtoNumber(4) + @SerialName("batch_index") + val batchIndex: Int = 0, + @ProtoNumber(5) + @SerialName("batch_id") + val batchId: Int = 0, +) { + @Serializable + data class OtpParameters( + @ProtoNumber(1) + val secret: ByteArray, + @ProtoNumber(2) + val name: String? = null, + @ProtoNumber(3) + val issuer: String? = null, + @ProtoNumber(4) + val algorithm: Algorithm = Algorithm.ALGORITHM_UNSPECIFIED, + @ProtoNumber(5) + val digits: DigitCount = DigitCount.DIGIT_COUNT_UNSPECIFIED, + @ProtoNumber(6) + val type: Type = Type.OTP_TYPE_UNSPECIFIED, + @ProtoNumber(7) + val counter: Int? = null, + ) { + @Serializable + enum class Algorithm { + @ProtoNumber(0) + ALGORITHM_UNSPECIFIED, + @ProtoNumber(1) + ALGORITHM_SHA1, + @ProtoNumber(2) + ALGORITHM_SHA256, + @ProtoNumber(3) + ALGORITHM_SHA512, + @ProtoNumber(4) + ALGORITHM_MD5, + } + + @Serializable + enum class DigitCount { + @ProtoNumber(0) + DIGIT_COUNT_UNSPECIFIED, + @ProtoNumber(1) + DIGIT_COUNT_SIX, + @ProtoNumber(2) + DIGIT_COUNT_EIGHT, + } + + @Serializable + enum class Type { + @ProtoNumber(0) + OTP_TYPE_UNSPECIFIED, + @ProtoNumber(1) + OTP_TYPE_HOTP, + @ProtoNumber(2) + OTP_TYPE_TOTP, + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as OtpParameters + + if (!secret.contentEquals(other.secret)) return false + if (name != other.name) return false + if (issuer != other.issuer) return false + if (algorithm != other.algorithm) return false + if (digits != other.digits) return false + if (type != other.type) return false + if (counter != other.counter) return false + + return true + } + + override fun hashCode(): Int { + var result = secret.contentHashCode() + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (issuer?.hashCode() ?: 0) + result = 31 * result + algorithm.hashCode() + result = 31 * result + digits.hashCode() + result = 31 * result + type.hashCode() + result = 31 * result + (counter ?: 0) + return result + } + } +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConst.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConst.kt new file mode 100644 index 00000000..24434720 --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConst.kt @@ -0,0 +1,5 @@ +package com.artemchep.keyguard.common.service.googleauthenticator.util + +object OtpMigrationConst { + const val PREFIX = "otpauth-migration://" +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConverter.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConverter.kt new file mode 100644 index 00000000..4811540f --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationConverter.kt @@ -0,0 +1,87 @@ +package com.artemchep.keyguard.common.service.googleauthenticator.util + +import arrow.core.Either +import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData +import com.artemchep.keyguard.common.service.text.Base32Service +import io.ktor.http.* + +/** + * Builds a valid OTP URI for use with all other + * than Google Authenticator apps. + */ +fun OtpAuthMigrationData.OtpParameters.build( + base32Service: Base32Service, +): Either = Either.catch { + when (type) { + OtpAuthMigrationData.OtpParameters.Type.OTP_TYPE_TOTP -> buildTotp(base32Service) + OtpAuthMigrationData.OtpParameters.Type.OTP_TYPE_HOTP -> buildHotp(base32Service) + else -> throw IllegalArgumentException("Unsupported OTP type!") + } +} + +private val OtpAuthMigrationData.OtpParameters.DigitCount.count + get() = when (this) { + OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_EIGHT -> 8 + OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_SIX -> 6 + OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_UNSPECIFIED -> null + } + +private val OtpAuthMigrationData.OtpParameters.Algorithm.str + get() = when (this) { + OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA1 -> "sha1" + OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA256 -> "sha256" + OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA512 -> "sha512" + OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_MD5 -> "md5" + OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_UNSPECIFIED -> null + } + +private fun OtpAuthMigrationData.OtpParameters.buildTotp( + base32Service: Base32Service, +): String = build( + "totp", + base32Service = base32Service, +) { + // period + parameters.append("period", "30") +} + +private fun OtpAuthMigrationData.OtpParameters.buildHotp( + base32Service: Base32Service, +): String = build( + "hotp", + base32Service = base32Service, +) { + // counter + val counter = counter + if (counter != null) { + parameters.append("counter", counter.toString()) + } +} + +private fun OtpAuthMigrationData.OtpParameters.build( + host: String, + base32Service: Base32Service, + builder: URLBuilder.() -> Unit, +): String { + return URLBuilder("otpauth://$host/").apply { + val path = issuer.orEmpty() + ":" + name.orEmpty() + appendPathSegments(path) + + // digits + val digits = digits.count + if (digits != null) { + parameters.append("digits", digits.toString()) + } + + // secret + val secret = base32Service.encodeToString(secret) + parameters.append("secret", secret) + // algorithm + val algorithm = algorithm.str + if (algorithm != null) { + parameters.append("algorithm", algorithm) + } + + builder() + }.buildString() +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationParser.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationParser.kt new file mode 100644 index 00000000..e349fd6c --- /dev/null +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/googleauthenticator/util/OtpMigrationParser.kt @@ -0,0 +1,40 @@ +package com.artemchep.keyguard.common.service.googleauthenticator.util + +import arrow.core.Either +import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData +import com.artemchep.keyguard.common.service.text.Base64Service +import io.ktor.http.* +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import org.kodein.di.DirectDI +import org.kodein.di.instance + +class OtpMigrationParser( + private val base64Service: Base64Service, +) { + constructor( + directDI: DirectDI, + ) : this( + base64Service = directDI.instance(), + ) + + fun parse( + uri: String, + ): Either = Either.catch { + parseData(uri) + } + + @OptIn(ExperimentalSerializationApi::class) + private fun parseData( + uri: String, + ): OtpAuthMigrationData { + val protoDataBase64 = Url(uri) + .parameters["data"] + requireNotNull(protoDataBase64) { + "URI must have the data parameter!" + } + val protoData = base64Service.decode(protoDataBase64) + return ProtoBuf.decodeFromByteArray(protoData) + } +} diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/totp/impl/TotpServiceImpl.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/totp/impl/TotpServiceImpl.kt index ec027ac0..b4a0add2 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/totp/impl/TotpServiceImpl.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/totp/impl/TotpServiceImpl.kt @@ -197,6 +197,7 @@ class TotpServiceImpl( CryptoHashAlgorithm.SHA_1 -> "HmacSHA1" CryptoHashAlgorithm.SHA_256 -> "HmacSHA256" CryptoHashAlgorithm.SHA_512 -> "HmacSHA512" + CryptoHashAlgorithm.MD5 -> "MD5" } val hash = Mac.getInstance(algorithmName).run { init(SecretKeySpec(key, "RAW")) // The hard-coded value 'RAW' is specified in the RFC diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddScreen.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddScreen.kt index d6d8c7cd..73473f01 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddScreen.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddScreen.kt @@ -455,7 +455,7 @@ private fun TotpTextField( maxLines = 1, trailing = { ScanQrButton( - onValueChange = state.value.onChange, + onValueChange = state.onScanned, ) }, leading = { diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddStateItem.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddStateItem.kt index b4d36a51..94798f3d 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddStateItem.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/add/AddStateItem.kt @@ -101,6 +101,7 @@ sealed interface AddStateItem { data class State( val copyText: CopyText, val value: TextFieldModel2, + val onScanned: ((String) -> Unit)? = null, val totpToken: TotpToken? = null, ) } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/add/AddStateProducer.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/add/AddStateProducer.kt index 3c333ade..3e713dab 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/add/AddStateProducer.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/add/AddStateProducer.kt @@ -79,6 +79,7 @@ import com.artemchep.keyguard.common.model.firstOrNull import com.artemchep.keyguard.common.model.title import com.artemchep.keyguard.common.model.titleH import com.artemchep.keyguard.common.service.clipboard.ClipboardService +import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService import com.artemchep.keyguard.common.service.logging.LogRepository import com.artemchep.keyguard.common.usecase.AddCipher import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck @@ -198,6 +199,7 @@ fun produceAddScreenState( getMarkdown = instance(), logRepository = instance(), clipboardService = instance(), + otpMigrationService = instance(), cipherUnsecureUrlCheck = instance(), showMessage = instance(), addCipher = instance(), @@ -227,6 +229,7 @@ fun produceAddScreenState( getMarkdown: GetMarkdown, logRepository: LogRepository, clipboardService: ClipboardService, + otpMigrationService: OtpMigrationService, cipherUnsecureUrlCheck: CipherUnsecureUrlCheck, showMessage: ShowMessage, addCipher: AddCipher, @@ -300,6 +303,7 @@ fun produceAddScreenState( copyText = copyText, getTotpCode = getTotpCode, getGravatarUrl = getGravatarUrl, + otpMigrationService = otpMigrationService, ) val cardHolder = produceCardState( args = args, @@ -2160,6 +2164,7 @@ private suspend fun RememberStateFlowScope.produceLoginState( copyText: CopyText, getTotpCode: GetTotpCode, getGravatarUrl: GetGravatarUrl, + otpMigrationService: OtpMigrationService, ): TmpLogin { val prefix = "login" @@ -2328,6 +2333,13 @@ private suspend fun RememberStateFlowScope.produceLoginState( AddStateItem.Totp.State( value = value, copyText = copyText, + onScanned = { uri -> + val convertedUri = otpMigrationService.handler(uri) + ?.convert(uri) + ?.getOrNull() + ?: uri + state.value = convertedUri + }, totpToken = totp, ) } diff --git a/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt b/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt index 1d7099c4..d8ae1983 100644 --- a/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt +++ b/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt @@ -16,6 +16,9 @@ import com.artemchep.keyguard.common.service.export.JsonExportService import com.artemchep.keyguard.common.service.export.impl.JsonExportServiceImpl import com.artemchep.keyguard.common.service.extract.impl.LinkInfoExtractorExecute import com.artemchep.keyguard.common.service.extract.impl.LinkInfoPlatformExtractor +import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService +import com.artemchep.keyguard.common.service.googleauthenticator.impl.OtpMigrationServiceImpl +import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationParser import com.artemchep.keyguard.common.service.gpmprivapps.PrivilegedAppsService import com.artemchep.keyguard.common.service.gpmprivapps.impl.PrivilegedAppsServiceImpl import com.artemchep.keyguard.common.service.id.IdRepository @@ -1200,6 +1203,16 @@ fun globalModuleJvm() = DI.Module( directDI = this, ) } + bindSingleton { + OtpMigrationServiceImpl( + directDI = this, + ) + } + bindSingleton { + OtpMigrationParser( + directDI = this, + ) + } bindSingleton { PrivilegedAppsServiceImpl( directDI = this, diff --git a/desktopApp/src/jvmMain/kotlin/com/artemchep/keyguard/Main.kt b/desktopApp/src/jvmMain/kotlin/com/artemchep/keyguard/Main.kt index 06f20f98..891c2fd3 100644 --- a/desktopApp/src/jvmMain/kotlin/com/artemchep/keyguard/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/artemchep/keyguard/Main.kt @@ -24,6 +24,7 @@ import com.artemchep.keyguard.common.io.bind import com.artemchep.keyguard.common.model.MasterSession import com.artemchep.keyguard.common.model.PersistedSession import com.artemchep.keyguard.common.model.ToastMessage +import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData import com.artemchep.keyguard.common.service.session.VaultSessionLocker import com.artemchep.keyguard.common.service.vault.KeyReadWriteRepository import com.artemchep.keyguard.common.usecase.GetAccounts @@ -76,6 +77,9 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.datetime.Clock +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider import org.jetbrains.compose.resources.painterResource @@ -89,8 +93,11 @@ import org.kodein.di.direct import org.kodein.di.instance import java.security.Security import java.util.Locale +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.reflect.KClass +@OptIn(ExperimentalEncodingApi::class) fun main() { // Add BouncyCastle as the first security provider // to make OkHTTP use its TLS instead of a platform @@ -99,6 +106,13 @@ fun main() { Security.insertProviderAt(BouncyCastleProvider(), 1) Security.insertProviderAt(BouncyCastleJsseProvider(), 2) + val g = ProtoBufSchemaGenerator.generateSchemaText(listOf(OtpAuthMigrationData.serializer().descriptor)) + println("help!") + println(g) + println("help!") + val f = ProtoBuf.decodeFromByteArray(Base64.decode("Cj4KCpgu67BGYPX7QLkSH01pY3Jvc29mdDphcnRlbWNoZXBAb3V0bG9vay5jb20aCU1pY3Jvc29mdCABKAEwAgoxChRAhdYwy3lUa0se1A2wvpXq5vZnlxIJYXJ0ZW1jaGVwGghGYWNlYm9vayABKAEwAhABGAEgACjeyrmK/f////8B")) + println(f) + val kamelConfig = KamelConfig { this.takeFrom(KamelConfig.Default) mapper(FaviconUrlMapper) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3ececc6..2a4e14e8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -222,6 +222,7 @@ kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines- kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinSerialization" } +kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinSerialization" } ktor-ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }