Skip to content

Commit

Permalink
Error handling and very basic libfido2 CBOR-info support
Browse files Browse the repository at this point in the history
  • Loading branch information
BryanJacobs committed Jan 13, 2024
1 parent 2c93626 commit 6aacd62
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 59 deletions.
2 changes: 1 addition & 1 deletion docs/dropin.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Feature Status:
| Device Listing | Implemented |
| Open Device from Listing Result | Implemented |
| Open Device by Path | Not Implemented |
| Get Device Info | Not Implemented |
| Get Device Info | Very Limited |
| Create Credential | Implemented |
| Set/get raw CBOR | Not Implemented |
| Verify Credential | Not Implemented |
Expand Down
60 changes: 31 additions & 29 deletions library/src/nativeMain/kotlin/us/q3q/fidok/fido2compat/Assert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import kotlinx.cinterop.pointed
import kotlinx.cinterop.value
import kotlinx.coroutines.runBlocking
import platform.posix.size_t
import us.q3q.fidok.ctap.CTAPError
import us.q3q.fidok.ctap.CTAPOption
import us.q3q.fidok.ctap.CTAPPermission
import us.q3q.fidok.ctap.commands.Extension
Expand Down Expand Up @@ -100,8 +99,9 @@ fun fido_dev_get_assert(
extensions.add(hmacSecretExtension)
}

val assertResponse =
try {
var assertResponse: List<GetAssertionResponse>? = null
val result =
fido_do_with_error_handling {
val pinUVToken =
if (pin != null && client.getInfoIfUnset().options?.get(CTAPOption.CLIENT_PIN.value) == true) {
runBlocking {
Expand All @@ -114,38 +114,40 @@ fun fido_dev_get_assert(
null
}

client.getAssertions(
clientDataHash = assertHandle.clientDataHash,
rpId = rpId,
pinUvToken = pinUVToken,
extensions = ExtensionSetup(extensions),
allowList =
assertHandle.allowList.map {
PublicKeyCredentialDescriptor(it)
},
)
} catch (e: CTAPError) {
assertHandle.assertions = listOf()
return e.code.toInt()
assertResponse =
client.getAssertions(
clientDataHash = assertHandle.clientDataHash,
rpId = rpId,
pinUvToken = pinUVToken,
extensions = ExtensionSetup(extensions),
allowList =
assertHandle.allowList.map {
PublicKeyCredentialDescriptor(it)
},
)
}

assertHandle.assertions = assertResponse
if (hmacSecretExtension != null) {
assertHandle.hmacSecrets =
(1..assertResponse.size).map {
val firstAndSecondSecret = hmacSecretExtension.getResult()
val firstSecret = firstAndSecondSecret.first
if (firstSecret == null) {
null
} else {
(firstSecret.toList() + (firstAndSecondSecret.second?.toList() ?: listOf())).toByteArray()
if (result == FIDO_OK) {
assertHandle.assertions = assertResponse!!
if (hmacSecretExtension != null) {
assertHandle.hmacSecrets =
(1..assertResponse!!.size).map {
val firstAndSecondSecret = hmacSecretExtension.getResult()
val firstSecret = firstAndSecondSecret.first
if (firstSecret == null) {
null
} else {
(firstSecret.toList() + (firstAndSecondSecret.second?.toList() ?: listOf())).toByteArray()
}
}
}
} else {
assertHandle.hmacSecrets = listOf()
}
} else {
assertHandle.hmacSecrets = listOf()
assertHandle.assertions = listOf()
}

return FIDO_OK
return result
}

@OptIn(ExperimentalForeignApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import kotlinx.cinterop.pointed
import kotlinx.cinterop.value
import kotlinx.coroutines.runBlocking
import platform.posix.size_t
import us.q3q.fidok.ctap.CTAPError
import us.q3q.fidok.ctap.CTAPOption
import us.q3q.fidok.ctap.CTAPPermission
import us.q3q.fidok.ctap.commands.COSEAlgorithmIdentifier
Expand Down Expand Up @@ -133,8 +132,9 @@ fun fido_dev_make_cred(
extensions.add(credProtect)
}

val credResponse =
try {
var credResponse: MakeCredentialResponse? = null
val result =
fido_do_with_error_handling {
val pinUVToken =
if (pin != null && client.getInfoIfUnset().options?.get(CTAPOption.CLIENT_PIN.value) == true) {
runBlocking {
Expand All @@ -147,32 +147,36 @@ fun fido_dev_make_cred(
null
}

client.makeCredential(
clientDataHash = credHandle.clientDataHash,
rpId = rpId,
rpName = credHandle.rpName,
userId = credHandle.userId,
userName = credHandle.userName,
userDisplayName = credHandle.userDisplayName,
pubKeyCredParams =
listOf(
PublicKeyCredentialParameters(alg = credHandle.type),
),
pinUvToken = pinUVToken,
extensions = ExtensionSetup(extensions),
discoverableCredential = credHandle.rk ?: false,
)
} catch (e: CTAPError) {
credHandle.cred = null
return e.code.toInt()
credResponse =
client.makeCredential(
clientDataHash = credHandle.clientDataHash,
rpId = rpId,
rpName = credHandle.rpName,
userId = credHandle.userId,
userName = credHandle.userName,
userDisplayName = credHandle.userDisplayName,
pubKeyCredParams =
listOf(
PublicKeyCredentialParameters(alg = credHandle.type),
),
pinUvToken = pinUVToken,
extensions = ExtensionSetup(extensions),
discoverableCredential = credHandle.rk ?: false,
)
}

credHandle.cred = credResponse
if (credProtect != null) {
credHandle.prot = credProtect.getLevel()
if (result == FIDO_OK) {
credHandle.cred = credResponse
if (credProtect != null) {
credHandle.prot = credProtect.getLevel()
} else {
credHandle.prot = null
}
} else {
credHandle.cred = null
}

return FIDO_OK
return result
}

@OptIn(ExperimentalForeignApi::class)
Expand Down
13 changes: 9 additions & 4 deletions library/src/nativeMain/kotlin/us/q3q/fidok/fido2compat/Device.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import kotlinx.cinterop.pointed
import kotlinx.cinterop.value
import us.q3q.fidok.ctap.AuthenticatorDevice
import us.q3q.fidok.ctap.CTAPOption
import us.q3q.fidok.ctap.DeviceCommunicationException
import kotlin.experimental.ExperimentalNativeApi

typealias fido_dev_t = COpaquePointer
Expand Down Expand Up @@ -48,7 +49,6 @@ fun fido_dev_free(dev_p: CPointer<CPointerVarOf<fido_dev_t>>?) {
dev_p.pointed.value = null
}

@OptIn(ExperimentalForeignApi::class)
@CName("fido_dev_open")
fun fido_dev_open(
dev: fido_dev_t?,
Expand All @@ -74,7 +74,12 @@ fun fido_dev_close(dev: fido_dev_t?) {
@CName("fido_dev_has_pin")
fun fido_dev_has_pin(dev: fido_dev_t?): Boolean {
val authenticator = dev?.asStableRef<FidoDevHandle>()?.get()?.authenticatorDevice ?: return false
return get_fidocompat_lib().ctapClient(authenticator).getInfoIfUnset().options?.get(
CTAPOption.CLIENT_PIN.value,
) == true
val client = get_fidocompat_lib().ctapClient(authenticator)
return try {
client.getInfoIfUnset().options?.get(
CTAPOption.CLIENT_PIN.value,
) == true
} catch (e: DeviceCommunicationException) {
false
}
}
124 changes: 124 additions & 0 deletions library/src/nativeMain/kotlin/us/q3q/fidok/fido2compat/Info.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
@file:Suppress("FunctionName", "unused", "LocalVariableName")

package us.q3q.fidok.fido2compat

import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.CArrayPointer
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.CPointerVar
import kotlinx.cinterop.CPointerVarOf
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.convert
import kotlinx.cinterop.cstr
import kotlinx.cinterop.get
import kotlinx.cinterop.getBytes
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.pointed
import kotlinx.cinterop.set
import kotlinx.cinterop.value
import platform.posix.size_t
import us.q3q.fidok.ctap.commands.GetInfoResponse
import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.ref.createCleaner

typealias fido_info_t = COpaquePointer

class FidoInfoHandle(
var info: GetInfoResponse? = null,
var nativeInfoPtr: CArrayPointer<CPointerVar<ByteVar>>? = null,
)

@OptIn(ExperimentalForeignApi::class)
@CName("fido_cbor_info_new")
fun fido_cbor_info_new(): COpaquePointer {
val infoHandle = FidoInfoHandle()
return StableRef.create(infoHandle).asCPointer()
}

@OptIn(ExperimentalForeignApi::class)
@CName("fido_cbor_info_free")
fun fido_cbor_info_free(ci_p: CPointer<CPointerVarOf<fido_info_t>>?) {
val stableRef = ci_p?.pointed?.value ?: return
val target = stableRef.asStableRef<FidoInfoHandle>()
target.dispose()
ci_p.pointed.value = null
}

@OptIn(ExperimentalForeignApi::class)
@CName("fido_dev_get_cbor_info")
fun fido_dev_get_cbor_info(
dev: fido_dev_t,
ci: fido_info_t,
): Int {
val devHandle = dev.asStableRef<FidoDevHandle>().get()
val infoHandle = ci.asStableRef<FidoInfoHandle>().get()

val authenticator = devHandle.authenticatorDevice ?: return FIDO_ERR_TX

val client =
get_fidocompat_lib().ctapClient(
authenticator,
)

return fido_do_with_error_handling {
infoHandle.info = client.getInfo()
}
}

internal fun cleanUpNativeInfoAlloc(handle: FidoInfoHandle) {
val extensionsInPlace = handle.info?.extensions
val nativePtr = handle.nativeInfoPtr
handle.nativeInfoPtr = null
if (extensionsInPlace != null && nativePtr != null) {
for (i in extensionsInPlace.indices) {
val ptr = nativePtr[i]
if (ptr != null) {
nativeHeap.free(ptr.rawValue)
}
}
nativeHeap.free(nativePtr.rawValue)
}
}

@OptIn(ExperimentalForeignApi::class)
@CName("fido_cbor_info_extensions_ptr")
fun fido_cbor_info_extensions_ptr(ci: fido_info_t): CPointerVarOf<CPointer<ByteVar>>? {
val infoHandle = ci.asStableRef<FidoInfoHandle>().get()

val extensions = infoHandle.info?.extensions ?: return null

val extensionsPtr = nativeHeap.allocArray<CPointerVar<ByteVar>>(extensions.size)

for (i in extensions.indices) {
val cstr = extensions[i].cstr
val heapAlloc = nativeHeap.allocArray<ByteVar>(cstr.size)
for (j in 0..<cstr.size) {
heapAlloc[j] = cstr.getBytes()[j]
}
extensionsPtr[i] = heapAlloc
}

if (infoHandle.nativeInfoPtr == null) {
createCleaner(infoHandle) {
cleanUpNativeInfoAlloc(it)
}
} else {
cleanUpNativeInfoAlloc(infoHandle)
}
infoHandle.nativeInfoPtr = extensionsPtr

return extensionsPtr.pointed
}

@OptIn(ExperimentalForeignApi::class)
@CName("fido_cbor_info_extensions_len")
fun fido_cbor_info_extensions_len(ci: fido_info_t): size_t {
val infoHandle = ci.asStableRef<FidoInfoHandle>().get()

return infoHandle.info?.extensions?.size?.convert() ?: 0.convert()
}
13 changes: 13 additions & 0 deletions library/src/nativeMain/kotlin/us/q3q/fidok/fido2compat/Init.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package us.q3q.fidok.fido2compat

import us.q3q.fidok.BotanCryptoProvider
import us.q3q.fidok.ctap.CTAPError
import us.q3q.fidok.ctap.DeviceCommunicationException
import us.q3q.fidok.ctap.FIDOkLibrary
import us.q3q.fidok.platformDeviceProviders
import kotlin.experimental.ExperimentalNativeApi
Expand All @@ -25,3 +27,14 @@ fun get_fidocompat_lib(): FIDOkLibrary {
return library
?: throw IllegalStateException("fido_init not called")
}

fun fido_do_with_error_handling(c: () -> Unit): Int {
try {
c()
return FIDO_OK
} catch (e: CTAPError) {
return e.code.toInt()
} catch (e: DeviceCommunicationException) {
return FIDO_ERR_TX
}
}

0 comments on commit 6aacd62

Please sign in to comment.