diff --git a/Core b/Core
index cc66df0..e0b57f6 160000
--- a/Core
+++ b/Core
@@ -1 +1 @@
-Subproject commit cc66df014eee602c935a48719ba14c328e421810
+Subproject commit e0b57f6c944be5927c79242d1f54198af69e1bec
diff --git a/multiplatform-lib/build.gradle.kts b/multiplatform-lib/build.gradle.kts
index 179e531..0301948 100644
--- a/multiplatform-lib/build.gradle.kts
+++ b/multiplatform-lib/build.gradle.kts
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(core.plugins.android.kmp.library)
+ alias(core.plugins.kotlin.serialization)
alias(libs.plugins.skie)
alias(libs.plugins.androidx.room)
alias(libs.plugins.ksp)
@@ -52,7 +53,9 @@ kotlin {
commonMain {
dependencies {
implementation(core.kotlinx.coroutines.core)
+ implementation(core.kotlinx.serialization.core)
implementation(core.kotlinx.serialization.cbor)
+ implementation(core.okio)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negociation)
implementation(libs.ktor.client.json)
diff --git a/multiplatform-lib/src/androidDeviceTest/kotlin/WebAuthnTest.kt b/multiplatform-lib/src/androidDeviceTest/kotlin/WebAuthnTest.kt
new file mode 100644
index 0000000..513d6ee
--- /dev/null
+++ b/multiplatform-lib/src/androidDeviceTest/kotlin/WebAuthnTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth
+
+import com.infomaniak.auth.lib.RegisterPasskeyBuilder
+import com.infomaniak.auth.lib.internal.KeyPairManagerImpl
+import com.infomaniak.auth.lib.network.models.PasskeysOptions
+import com.infomaniak.auth.lib.network.models.PubKeyCredParam
+import com.infomaniak.auth.lib.network.models.RelyingParty
+import com.infomaniak.auth.lib.network.models.User
+import kotlinx.coroutines.test.runTest
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlin.test.Test
+
+class WebAuthnTest {
+
+ @OptIn(ExperimentalSerializationApi::class)
+ @Test
+ fun registerPasskeyGeneration() = runTest {
+ // This is sent from [GET] /api/authenticator/passkeys/options
+ val passkeysOptions = PasskeysOptions(
+ challenge = "U3NkRnF6RlVwUnpKRGhVMw",
+ relyingParty = RelyingParty(
+ id = "infomaniak.com",
+ name = "Infomaniak",
+ icon = null,
+ ),
+ user = User(
+ id = "MQ",
+ name = "test@user.com",
+ displayName = "Test"
+ ),
+ pubKeyCredParams = listOf(
+ PubKeyCredParam(
+ type = "public-key",
+ algorithm = -7 // ES256
+ )
+ ),
+ excludeCredentials = emptyList(),
+ )
+
+ // Just getting the public key to generate RegisterPasskey object
+ val keyPairManager = KeyPairManagerImpl()
+ keyPairManager.generateNewKey()
+ val publicKeyAsByteArray = keyPairManager.retrievePublicKey().firstOrNull()!!
+
+ // Nothing to test on the generated object for now
+ RegisterPasskeyBuilder(passkeysOptions, publicKeyAsByteArray).build()
+ }
+}
diff --git a/multiplatform-lib/src/androidDeviceTest/kotlin/internal/KeyPairManagerTest.kt b/multiplatform-lib/src/androidDeviceTest/kotlin/internal/KeyPairManagerTest.kt
index 5c444cd..a954edf 100644
--- a/multiplatform-lib/src/androidDeviceTest/kotlin/internal/KeyPairManagerTest.kt
+++ b/multiplatform-lib/src/androidDeviceTest/kotlin/internal/KeyPairManagerTest.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package internal
+package com.infomaniak.auth.internal
import com.infomaniak.auth.lib.internal.KeyPairManagerImpl
import com.infomaniak.auth.lib.internal.Xor
diff --git a/multiplatform-lib/src/androidMain/kotlin/PublicKeyUtils.kt b/multiplatform-lib/src/androidMain/kotlin/PublicKeyUtils.kt
new file mode 100644
index 0000000..a19ad4f
--- /dev/null
+++ b/multiplatform-lib/src/androidMain/kotlin/PublicKeyUtils.kt
@@ -0,0 +1,61 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.cbor.Cbor
+import kotlinx.serialization.encodeToByteArray
+import java.security.KeyFactory
+import java.security.PublicKey
+import java.security.interfaces.ECPublicKey
+import java.security.spec.X509EncodedKeySpec
+
+actual object PublicKeyUtils {
+
+ @OptIn(ExperimentalSerializationApi::class)
+ actual fun getPublicKeyCose(publicKeyByteArray: ByteArray): ByteArray {
+ val publicKey = getPublicKeyFromByteArray(publicKeyByteArray) as ECPublicKey
+ val w = publicKey.w
+ val x = w.affineX.toByteArray().padTo32Bytes()
+ val y = w.affineY.toByteArray().padTo32Bytes()
+ val coseKey = CoseKey(
+ kty = 2, // kty: EC2
+ alg = -7,// alg: ES256
+ crv = -1,// crv: P-2
+ x = x, // x coord
+ y = y // y coord
+ )
+ return Cbor.encodeToByteArray(coseKey)
+ }
+
+ private fun getPublicKeyFromByteArray(bytes: ByteArray): PublicKey {
+ val keySpec = X509EncodedKeySpec(bytes)
+ val keyFactory = KeyFactory.getInstance("EC")
+ return keyFactory.generatePublic(keySpec)
+ }
+
+ private fun ByteArray.padTo32Bytes(): ByteArray {
+ return if (this.size == 32) {
+ this
+ } else if (this.size > 32) {
+ this.copyOfRange(this.size - 32, this.size)
+ } else {
+ ByteArray(32 - this.size) { 0x00 } + this
+ }
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/Account.kt b/multiplatform-lib/src/commonMain/kotlin/Account.kt
index 216ca31..06049ba 100644
--- a/multiplatform-lib/src/commonMain/kotlin/Account.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/Account.kt
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.infomaniak.auth.lib
@ConsistentCopyVisibility
diff --git a/multiplatform-lib/src/commonMain/kotlin/AppStatus.kt b/multiplatform-lib/src/commonMain/kotlin/AppStatus.kt
index a0d7393..ac93c2f 100644
--- a/multiplatform-lib/src/commonMain/kotlin/AppStatus.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/AppStatus.kt
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.infomaniak.auth.lib
sealed interface AppStatus {
diff --git a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt
index 6eea7e1..e9f5a35 100644
--- a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorFacade.kt
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.infomaniak.auth.lib
import kotlinx.coroutines.flow.Flow
diff --git a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt
index 0e7ebb4..ccfeb1a 100644
--- a/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/AuthenticatorInjection.kt
@@ -15,29 +15,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+package com.infomaniak.auth.lib
+
import com.infomaniak.auth.lib.network.ApiClientProvider
import com.infomaniak.auth.lib.network.interfaces.CrashReportInterface
import network.repositories.WebAuthnRepository
import network.utils.ApiEnvironment
-/*
- Infomaniak Authenticator - Android
- Copyright (C) 2026 Infomaniak Network SA
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
- */
-
class AuthenticatorInjection(
private val environment: ApiEnvironment,
private val userAgent: String,
@@ -45,6 +29,6 @@ class AuthenticatorInjection(
private val crashReport: CrashReportInterface,
) {
private val apiClientProvider by lazy { ApiClientProvider(userAgent, crashReport) }
-
+
private val webAuthnRepository by lazy { WebAuthnRepository(apiClientProvider, environment) }
}
diff --git a/multiplatform-lib/src/commonMain/kotlin/CoseKey.kt b/multiplatform-lib/src/commonMain/kotlin/CoseKey.kt
new file mode 100644
index 0000000..1627e53
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/CoseKey.kt
@@ -0,0 +1,72 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlin.io.encoding.Base64
+
+@Serializable
+data class CoseKey(
+ val kty: Int,
+ val alg: Int,
+ val crv: Int,
+ @Serializable(with = ByteArrayAsByteListBase64Serializer::class)
+ val x: ByteArray,
+ @Serializable(with = ByteArrayAsByteListBase64Serializer::class)
+ val y: ByteArray,
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || this::class != other::class) return false
+
+ other as CoseKey
+
+ if (kty != other.kty) return false
+ if (alg != other.alg) return false
+ if (crv != other.crv) return false
+ if (!x.contentEquals(other.x)) return false
+ if (!y.contentEquals(other.y)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = kty
+ result = 31 * result + alg
+ result = 31 * result + crv
+ result = 31 * result + x.contentHashCode()
+ result = 31 * result + y.contentHashCode()
+ return result
+ }
+}
+
+object ByteArrayAsByteListBase64Serializer : KSerializer {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ByteArrayAsByteListBase64", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: ByteArray) {
+ encoder.encodeString(Base64.UrlSafe.encode(value))
+ }
+
+ override fun deserialize(decoder: Decoder) = Base64.UrlSafe.decode(decoder.decodeString())
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/CredentialsForMigration.kt b/multiplatform-lib/src/commonMain/kotlin/CredentialsForMigration.kt
index 2912f3a..efd308a 100644
--- a/multiplatform-lib/src/commonMain/kotlin/CredentialsForMigration.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/CredentialsForMigration.kt
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.infomaniak.auth.lib
data class CredentialsForMigration(
diff --git a/multiplatform-lib/src/commonMain/kotlin/NotConnectedAction.kt b/multiplatform-lib/src/commonMain/kotlin/NotConnectedAction.kt
index e1cdbeb..2bd3375 100644
--- a/multiplatform-lib/src/commonMain/kotlin/NotConnectedAction.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/NotConnectedAction.kt
@@ -15,7 +15,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.infomaniak.auth.lib
sealed interface NotConnectedAction {
diff --git a/multiplatform-lib/src/commonMain/kotlin/PublicKeyUtils.kt b/multiplatform-lib/src/commonMain/kotlin/PublicKeyUtils.kt
new file mode 100644
index 0000000..5bd18fc
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/PublicKeyUtils.kt
@@ -0,0 +1,23 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib
+
+expect object PublicKeyUtils {
+
+ fun getPublicKeyCose(publicKeyByteArray: ByteArray): ByteArray
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/RegisterPassKeyBuilder.kt b/multiplatform-lib/src/commonMain/kotlin/RegisterPassKeyBuilder.kt
new file mode 100644
index 0000000..5c9b8fd
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/RegisterPassKeyBuilder.kt
@@ -0,0 +1,115 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib
+
+import com.infomaniak.auth.lib.PublicKeyUtils.getPublicKeyCose
+import com.infomaniak.auth.lib.network.models.ClientExtensionResults
+import com.infomaniak.auth.lib.network.models.PasskeysOptions
+import com.infomaniak.auth.lib.network.models.RegisterPasskey
+import com.infomaniak.auth.lib.network.models.RegisterPasskeyResponse
+import com.infomaniak.auth.lib.network.models.WebAuthnAttestationObject
+import com.infomaniak.auth.lib.network.models.WebAuthnClientData
+import kotlinx.serialization.cbor.Cbor
+import kotlinx.serialization.encodeToByteArray
+import kotlinx.serialization.json.Json
+import okio.ByteString.Companion.encodeUtf8
+import kotlin.io.encoding.Base64
+import kotlin.random.Random
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
+
+@OptIn(ExperimentalUuidApi::class)
+class RegisterPasskeyBuilder(
+ private val passkeysOptions: PasskeysOptions,
+ private val publicKey: ByteArray,
+) {
+
+ fun build(): RegisterPasskey {
+ val publicKeyCose = getPublicKeyCose(publicKey)
+ val randomInt = Random(passkeysOptions.challenge.hashCode().toLong())
+ val stringId = "$randomInt${passkeysOptions.user}${passkeysOptions.relyingParty}"
+ val rawId = stringId.encodeToByteArray()
+ val id = Base64.encode(rawId)
+
+ // AttestationObject
+ val attestationObject = generateAttestationObject(
+ rpId = passkeysOptions.relyingParty.id,
+ credentialId = rawId,
+ publicKeyCose = publicKeyCose,
+ )
+
+ // AuthenticatorData
+ // Using rawId for credentialId because it has to be something unique
+ val authenticatorData = generateAuthData(
+ rpId = passkeysOptions.relyingParty.id,
+ credentialId = rawId,
+ publicKeyCose = publicKeyCose
+ )
+ val clientData = WebAuthnClientData(
+ type = "webauthn.create",
+ challenge = passkeysOptions.challenge,
+ origin = "infomaniak.com",
+ crossOrigin = false,
+ )
+
+ val response = RegisterPasskeyResponse(
+ attestationObject = Base64.encode(Cbor.encodeToByteArray(attestationObject)),
+ clientDataJSON = Base64.encode(Json.encodeToString(clientData).encodeToByteArray()),
+ transports = listOf("internal"),
+ publicKeyAlgorithm = -7,
+ publicKey = Base64.UrlSafe.encode(publicKey),
+ authenticatorData = Base64.encode(authenticatorData),
+ )
+ val type = "public-key"
+ val clientExtensionResult = ClientExtensionResults
+ val authenticatorAttachment = "platform"
+
+ return RegisterPasskey(
+ device = Uuid.random().toHexDashString(),
+ id = id,
+ rawId = Base64.encode(rawId),
+ registerPasskeyResponse = response,
+ type = type,
+ clientExtensionResults = clientExtensionResult,
+ authenticatorAttachment = authenticatorAttachment,
+ )
+ }
+
+ private fun generateAuthData(rpId: String, credentialId: ByteArray, publicKeyCose: ByteArray): ByteArray {
+ val rpIdHash = rpId.encodeUtf8().sha256().toByteArray()
+ val flags: Byte = 0x41
+ val signCount = byteArrayOf(0x00, 0x00, 0x00, 0x00)
+
+ // attestedCredentialData = AAGUID (16 bytes) + credentialId length + credentialId + publicKeyCose
+ val aaguid = ByteArray(16) { 0x00 }
+ val credentialIdLength = byteArrayOf((credentialId.size shr 8).toByte(), (credentialId.size and 0xFF).toByte())
+
+ return rpIdHash + flags + signCount + aaguid + credentialIdLength + credentialId + publicKeyCose
+ }
+
+ private fun generateAttestationObject(rpId: String, credentialId: ByteArray, publicKeyCose: ByteArray): ByteArray {
+ val authData = generateAuthData(rpId, credentialId, publicKeyCose)
+
+ val attestationObject = WebAuthnAttestationObject(
+ fmt = "none",
+ authData = authData
+ )
+
+ return Cbor.encodeToByteArray(attestationObject)
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt
index 1f8fa26..d21147f 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AllowCredential.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt
index 55622c6..8375996 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthResult.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt
index ca06ef0..c106ea9 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/AuthentificationOptions.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt
index 4da21af..a4fbb06 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/ClientExtensionResults.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt
index cd34ec8..5dfd3d5 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/ExcludeCredential.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt
index 53e1807..7938b82 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/PasskeysOptions.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt
index bcdb5ca..2e5fa68 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/PubKeyCredParam.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt
index 8fadffa..67ceded 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskey.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt
index e7d1e38..18ab67b 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RegisterPasskeyResponse.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt
index 3b05599..b3cf4aa 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/RelyingParty.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
@@ -24,4 +24,7 @@ data class RelyingParty(
val id: String,
val name: String,
val icon: String?,
-)
+) {
+
+ override fun toString() = "$id:$name"
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt
index 77e7b3c..fe71236 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/User.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
@@ -24,4 +24,7 @@ data class User(
val id: String,
val name: String,
val displayName: String?,
-)
+) {
+
+ override fun toString() = "$id:$name:$displayName"
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt
index f8d8810..95a5b66 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyAuthenticationData.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt
index e69d8b3..431afb3 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/VerifyResponse.kt
@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package network.models
+package com.infomaniak.auth.lib.network.models
import kotlinx.serialization.Serializable
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnAttestationObject.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnAttestationObject.kt
new file mode 100644
index 0000000..fcc0575
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnAttestationObject.kt
@@ -0,0 +1,64 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.models
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.ByteArraySerializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+
+@Serializable
+data class WebAuthnAttestationObject(
+ val fmt: String,
+ @Serializable(with = ByteArrayAsByteListSerializer::class)
+ val authData: ByteArray,
+) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || this::class != other::class) return false
+
+ other as WebAuthnAttestationObject
+
+ if (fmt != other.fmt) return false
+ if (!authData.contentEquals(other.authData)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = fmt.hashCode()
+ result = 31 * result + authData.contentHashCode()
+ return result
+ }
+}
+
+private object ByteArrayAsByteListSerializer : KSerializer {
+ private val delegate = ByteArraySerializer()
+
+ override val descriptor: SerialDescriptor = delegate.descriptor
+
+ override fun serialize(encoder: Encoder, value: ByteArray) {
+ delegate.serialize(encoder, value)
+ }
+
+ override fun deserialize(decoder: Decoder): ByteArray {
+ return delegate.deserialize(decoder)
+ }
+}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnClientData.kt b/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnClientData.kt
new file mode 100644
index 0000000..4987c9d
--- /dev/null
+++ b/multiplatform-lib/src/commonMain/kotlin/network/models/WebAuthnClientData.kt
@@ -0,0 +1,28 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib.network.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class WebAuthnClientData(
+ val type: String,
+ val challenge: String,
+ val origin: String,
+ val crossOrigin: Boolean
+)
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt b/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt
index 0a5977b..c3094f9 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/repositories/WebAuthnRepository.kt
@@ -18,15 +18,15 @@
package network.repositories
import com.infomaniak.auth.lib.network.ApiClientProvider
+import com.infomaniak.auth.lib.network.models.AuthResult
+import com.infomaniak.auth.lib.network.models.AuthenticationOptions
+import com.infomaniak.auth.lib.network.models.ClientExtensionResults
+import com.infomaniak.auth.lib.network.models.PasskeysOptions
+import com.infomaniak.auth.lib.network.models.RegisterPasskey
+import com.infomaniak.auth.lib.network.models.VerifyAuthenticationData
+import com.infomaniak.auth.lib.network.models.VerifyResponse
import io.ktor.client.HttpClient
import kotlinx.serialization.json.Json
-import network.models.AuthResult
-import network.models.AuthenticationOptions
-import network.models.ClientExtensionResults
-import network.models.PasskeysOptions
-import network.models.RegisterPasskey
-import network.models.VerifyAuthenticationData
-import network.models.VerifyResponse
import network.requests.AuthenticatorRequest
import network.utils.ApiEnvironment
@@ -42,13 +42,25 @@ class WebAuthnRepository internal constructor(private val authenticatorRequest:
internal constructor(environment: ApiEnvironment, json: Json, httpClient: HttpClient) :
this(AuthenticatorRequest(environment, json, httpClient))
+ // Generate WebAuthn registration options (authentified)
+ suspend fun getPasskeysOptions(): PasskeysOptions {
+ //TODO where to get the bearer from ?
+ return authenticatorRequest.getPasskeysOptions()
+ }
+
+ // Validate WebAuthn registration and save public key (authentified)
+ suspend fun registerPasskey(registerPasskey: RegisterPasskey): Result {
+ //TODO where to get the bearer from ?
+ return authenticatorRequest.registerPasskey(registerPasskey)
+ }
+
// Authentification challenge (not authentified)
- suspend fun getAuthenticationOptions(identity: Long): AuthenticationOptions {
+ suspend fun challenge(identity: Long): AuthenticationOptions {
return authenticatorRequest.challenge(identity)
}
// Authentification verification (not authentified)
- suspend fun verifyAuthentication(
+ suspend fun verify(
identity: Long,
id: String,
rawId: String,
@@ -69,16 +81,4 @@ class WebAuthnRepository internal constructor(private val authenticatorRequest:
)
)
}
-
- // Generate WebAuthn registration options (authentified)
- suspend fun getRegistrationOptions(): PasskeysOptions {
- //TODO where to get the bearer ?
- return authenticatorRequest.getPasskeysOptions()
- }
-
- // Validate WebAuthn registration and save public key (authentified)
- suspend fun registerCredential(registerPasskey: RegisterPasskey): Result {
- //TODO where to get the bearer ?
- return authenticatorRequest.registerPasskey(registerPasskey)
- }
}
diff --git a/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt b/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt
index dc49f90..337220e 100644
--- a/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/network/requests/AuthenticatorRequest.kt
@@ -17,13 +17,13 @@
*/
package network.requests
+import com.infomaniak.auth.lib.network.models.AuthResult
+import com.infomaniak.auth.lib.network.models.AuthenticationOptions
+import com.infomaniak.auth.lib.network.models.PasskeysOptions
+import com.infomaniak.auth.lib.network.models.RegisterPasskey
+import com.infomaniak.auth.lib.network.models.VerifyAuthenticationData
import io.ktor.client.HttpClient
import kotlinx.serialization.json.Json
-import network.models.AuthResult
-import network.models.AuthenticationOptions
-import network.models.PasskeysOptions
-import network.models.RegisterPasskey
-import network.models.VerifyAuthenticationData
import network.utils.ApiEnvironment
import network.utils.ApiRoutes
diff --git a/multiplatform-lib/src/commonMain/kotlin/room/accounts/AccountsDao.kt b/multiplatform-lib/src/commonMain/kotlin/room/accounts/AccountsDao.kt
index 56ce6b5..a6c666d 100644
--- a/multiplatform-lib/src/commonMain/kotlin/room/accounts/AccountsDao.kt
+++ b/multiplatform-lib/src/commonMain/kotlin/room/accounts/AccountsDao.kt
@@ -30,11 +30,11 @@ interface AccountsDao {
fun getAsFlow(): Flow>
@Insert(onConflict = OnConflictStrategy.REPLACE)
- fun update(account: AccountEntity)
+ suspend fun update(account: AccountEntity)
@Insert
- fun insert(account: AccountEntity)
+ suspend fun insert(account: AccountEntity)
@Query("DELETE FROM AccountEntity WHERE id = :id")
- fun delete(id: Long)
+ suspend fun delete(id: Long)
}
diff --git a/multiplatform-lib/src/iosMain/kotlin/PublicKeyUtils.kt b/multiplatform-lib/src/iosMain/kotlin/PublicKeyUtils.kt
new file mode 100644
index 0000000..c60d840
--- /dev/null
+++ b/multiplatform-lib/src/iosMain/kotlin/PublicKeyUtils.kt
@@ -0,0 +1,41 @@
+/*
+ * Infomaniak Authenticator - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.auth.lib
+
+import kotlinx.serialization.cbor.Cbor
+import kotlinx.serialization.encodeToByteArray
+
+actual object PublicKeyUtils {
+
+ actual fun getPublicKeyCose(publicKeyByteArray: ByteArray): ByteArray {
+ require(publicKeyByteArray.size == 65) { "Invalid public key format" }
+ require(publicKeyByteArray[0] == 0x04.toByte()) { "Expected uncompressed format" }
+
+ val x = publicKeyByteArray.copyOfRange(1, 33)
+ val y = publicKeyByteArray.copyOfRange(33, 65)
+
+ val coseKey = CoseKey(
+ kty = 2, // EC2
+ alg = -7,// ES256
+ crv = 1, // P-256
+ x = x, // x coord
+ y = y // y coord
+ )
+ return Cbor.encodeToByteArray(coseKey)
+ }
+}