feature: Add OTP code by scanning Google Authenticator single export code #556
This commit is contained in:
parent
5e074d9586
commit
3714670287
|
@ -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)
|
||||
|
|
|
@ -4,4 +4,5 @@ enum class CryptoHashAlgorithm {
|
|||
SHA_1,
|
||||
SHA_256,
|
||||
SHA_512,
|
||||
MD5,
|
||||
}
|
||||
|
|
|
@ -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<Throwable, TotpToken.SteamAuth> = Either.catch {
|
||||
|
|
|
@ -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<Throwable, String>
|
||||
}
|
|
@ -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<Throwable, String> = Either.catch {
|
||||
otpMigrationParser.parse(uri)
|
||||
.flatMap {
|
||||
it.otpParameters
|
||||
.first()
|
||||
.build(
|
||||
base32Service = base32Service,
|
||||
)
|
||||
}
|
||||
}.flatten()
|
||||
}
|
|
@ -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 <demon@dim13.org>
|
||||
|
||||
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<OtpParameters>,
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.artemchep.keyguard.common.service.googleauthenticator.util
|
||||
|
||||
object OtpMigrationConst {
|
||||
const val PREFIX = "otpauth-migration://"
|
||||
}
|
|
@ -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<Throwable, String> = 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()
|
||||
}
|
|
@ -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<Throwable, OtpAuthMigrationData> = 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<OtpAuthMigrationData>(protoData)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -455,7 +455,7 @@ private fun TotpTextField(
|
|||
maxLines = 1,
|
||||
trailing = {
|
||||
ScanQrButton(
|
||||
onValueChange = state.value.onChange,
|
||||
onValueChange = state.onScanned,
|
||||
)
|
||||
},
|
||||
leading = {
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<OtpMigrationService> {
|
||||
OtpMigrationServiceImpl(
|
||||
directDI = this,
|
||||
)
|
||||
}
|
||||
bindSingleton<OtpMigrationParser> {
|
||||
OtpMigrationParser(
|
||||
directDI = this,
|
||||
)
|
||||
}
|
||||
bindSingleton<PrivilegedAppsService> {
|
||||
PrivilegedAppsServiceImpl(
|
||||
directDI = this,
|
||||
|
|
|
@ -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<OtpAuthMigrationData>(Base64.decode("Cj4KCpgu67BGYPX7QLkSH01pY3Jvc29mdDphcnRlbWNoZXBAb3V0bG9vay5jb20aCU1pY3Jvc29mdCABKAEwAgoxChRAhdYwy3lUa0se1A2wvpXq5vZnlxIJYXJ0ZW1jaGVwGghGYWNlYm9vayABKAEwAhABGAEgACjeyrmK/f////8B"))
|
||||
println(f)
|
||||
|
||||
val kamelConfig = KamelConfig {
|
||||
this.takeFrom(KamelConfig.Default)
|
||||
mapper(FaviconUrlMapper)
|
||||
|
|
|
@ -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" }
|
||||
|
|
Loading…
Reference in New Issue