Skip to content

Commit

Permalink
[WebAuthn] Enable concurrent Authenticator requests where supported b…
Browse files Browse the repository at this point in the history
…y the native API (Samsung#6389)

Make concurrent calls forwarded unconditionally. It is now up to the native API whether it will honor the request or return an error.

Additionally, fix an error in AuthenticatorAssertionResponse data marshalling.
  • Loading branch information
feedop authored Oct 24, 2024
1 parent 38a1927 commit 4cda61e
Show file tree
Hide file tree
Showing 6 changed files with 351 additions and 236 deletions.
1 change: 0 additions & 1 deletion src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>library</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

Expand Down
129 changes: 64 additions & 65 deletions src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/Authenticator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using static Interop;
using static Tizen.Security.WebAuthn.ErrorFactory;
Expand All @@ -31,14 +30,9 @@ public static class Authenticator
{
private const int API_VERSION_NUMBER = 0x00000001;
private static bool _apiVersionSet = false;
private static bool _busy = false;
private static object _userData = null;
private static WauthnDisplayQrcodeCallback _qrCodeCallback;
private static WauthnMcOnResponseCallback _mcResponseCallback;
private static WauthnGaOnResponseCallback _gaResponseCallback;
private static WauthnUpdateLinkedDataCallback _linkedDataCallback;
private static WauthnMcCallbacks _wauthnMcCallbacks;
private static WauthnGaCallbacks _wauthnGaCallbacks;
private static Dictionary<int, AuthenticatorStorage> _apiCalls = new();
private static int _callId = 0;

#region Public API
/// <summary>
Expand Down Expand Up @@ -86,32 +80,36 @@ public static AuthenticatorTransport SupportedAuthenticators()
public static void MakeCredential(ClientData clientData, PubkeyCredCreationOptions options, MakeCredentialCallbacks callbacks)
{
CheckPreconditions();
try
{
CheckNullNThrow(clientData);
CheckNullNThrow(clientData.JsonData);
CheckNullNThrow(options);
CheckNullNThrow(options.Rp);
CheckNullNThrow(options.User);
CheckNullNThrow(options.PubkeyCredParams);
CheckNullNThrow(callbacks);
CheckNullNThrow(callbacks.QrcodeCallback);
CheckNullNThrow(callbacks.ResponseCallback);
CheckNullNThrow(callbacks.LinkedDataCallback);
CheckNullNThrow(clientData);
CheckNullNThrow(clientData.JsonData);
CheckNullNThrow(options);
CheckNullNThrow(options.Rp);
CheckNullNThrow(options.User);
CheckNullNThrow(options.PubkeyCredParams);
CheckNullNThrow(callbacks);
CheckNullNThrow(callbacks.QrcodeCallback);
CheckNullNThrow(callbacks.ResponseCallback);
CheckNullNThrow(callbacks.LinkedDataCallback);

// Create callback wrappers
WrapMcCallbacks(callbacks);
AuthenticatorStorage.SetDataForMakeCredential(clientData, options);
int id = unchecked(_callId++);
// Copy data to unmanaged memory
var storage = new AuthenticatorMakeCredentialStorage(clientData, options);
// Create callback wrappers
WrapMcCallbacks(callbacks, storage, id);
// Add the storage to a static dictionary to prevent GC from premature collecting
_apiCalls.Add(id, storage);

try
{
int ret = Libwebauthn.MakeCredential(
AuthenticatorStorage.WauthnClientData,
AuthenticatorStorage.WauthnPubkeyCredCreationOptions,
_wauthnMcCallbacks);
storage.WauthnClientData,
storage.WauthnPubkeyCredCreationOptions,
storage.WauthnMcCallbacks);
CheckErrNThrow(ret, "Make Credential");
}
catch
{
Cleanup();
Cleanup(id);
throw;
}
}
Expand Down Expand Up @@ -146,29 +144,33 @@ public static void MakeCredential(ClientData clientData, PubkeyCredCreationOptio
public static void GetAssertion(ClientData clientData, PubkeyCredRequestOptions options, GetAssertionCallbacks callbacks)
{
CheckPreconditions();
try
{
CheckNullNThrow(clientData);
CheckNullNThrow(clientData.JsonData);
CheckNullNThrow(options);
CheckNullNThrow(callbacks);
CheckNullNThrow(callbacks.QrcodeCallback);
CheckNullNThrow(callbacks.ResponseCallback);
CheckNullNThrow(callbacks.LinkedDataCallback);
CheckNullNThrow(clientData);
CheckNullNThrow(clientData.JsonData);
CheckNullNThrow(options);
CheckNullNThrow(callbacks);
CheckNullNThrow(callbacks.QrcodeCallback);
CheckNullNThrow(callbacks.ResponseCallback);
CheckNullNThrow(callbacks.LinkedDataCallback);

// Create callback wrappers
WrapGaCallbacks(callbacks);
AuthenticatorStorage.SetDataForGetAssertion(clientData, options);
int id = unchecked(_callId++);
// Copy data to unmanaged memory
var storage = new AuthenticatorGetAssertionStorage(clientData, options);
// Create callback wrappers
WrapGaCallbacks(callbacks, storage, id);
// Add the storage to a static dictionary to prevent GC from premature collecting
_apiCalls.Add(id, storage);

try
{
int ret = Libwebauthn.GetAssertion(
AuthenticatorStorage.WauthnClientData,
AuthenticatorStorage.WauthnPubkeyCredRequestOptions,
_wauthnGaCallbacks);
storage.WauthnClientData,
storage.WauthnPubkeyCredRequestOptions,
storage.WauthnGaCallbacks);
CheckErrNThrow(ret, "Get Assertion");
}
catch
{
Cleanup();
Cleanup(id);
throw;
}
}
Expand All @@ -195,7 +197,7 @@ private static void SetApiVersion(int apiVersionNumber)
CheckErrNThrow(ret, "Set API version");
_apiVersionSet = true;
}
private static void WrapMcCallbacks(MakeCredentialCallbacks callbacks)
private static void WrapMcCallbacks(MakeCredentialCallbacks callbacks, AuthenticatorMakeCredentialStorage storage, int id)
{
_userData = callbacks.UserData;

Expand All @@ -210,7 +212,7 @@ void onResponseWrapper(WauthnPubkeyCredentialAttestation pubkeyCred, WauthnError
callbacks.ResponseCallback(pubkeyCredManaged, result, _userData);

if (result != WauthnError.None)
Cleanup();
Cleanup(id);
}

void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
Expand All @@ -219,17 +221,17 @@ void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
callbacks.LinkedDataCallback(linkedDataManaged, result, _userData);

if (result != WauthnError.NoneAndWait)
Cleanup();
Cleanup(id);
}

_qrCodeCallback = new WauthnDisplayQrcodeCallback(qrCodeWrapper);
_mcResponseCallback = new WauthnMcOnResponseCallback(onResponseWrapper);
_linkedDataCallback = new WauthnUpdateLinkedDataCallback(linkedDataWrapper);

_wauthnMcCallbacks = new WauthnMcCallbacks(_qrCodeCallback, _mcResponseCallback, _linkedDataCallback);
storage.SetCallbacks(
new WauthnDisplayQrcodeCallback(qrCodeWrapper),
new WauthnMcOnResponseCallback(onResponseWrapper),
new WauthnUpdateLinkedDataCallback(linkedDataWrapper)
);
}

private static void WrapGaCallbacks(GetAssertionCallbacks callbacks)
private static void WrapGaCallbacks(GetAssertionCallbacks callbacks, AuthenticatorGetAssertionStorage storage, int id)
{
_userData = callbacks.UserData;

Expand All @@ -244,7 +246,7 @@ void onResponseWrapper(WauthnPubkeyCredentialAssertion pubkeyCred, WauthnError r
callbacks.ResponseCallback(pubkeyCredManaged, result, _userData);

if (result != WauthnError.None)
Cleanup();
Cleanup(id);
}

void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
Expand All @@ -253,29 +255,26 @@ void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
callbacks.LinkedDataCallback(linkedDataManaged, result, _userData);

if (result != WauthnError.NoneAndWait)
Cleanup();
Cleanup(id);
}
_qrCodeCallback = new WauthnDisplayQrcodeCallback(qrCodeWrapper);
_gaResponseCallback = new WauthnGaOnResponseCallback(onResponseWrapper);
_linkedDataCallback = new WauthnUpdateLinkedDataCallback(linkedDataWrapper);

_wauthnGaCallbacks = new WauthnGaCallbacks(_qrCodeCallback, _gaResponseCallback, _linkedDataCallback);
storage.SetCallbacks(
new WauthnDisplayQrcodeCallback(qrCodeWrapper),
new WauthnGaOnResponseCallback(onResponseWrapper),
new WauthnUpdateLinkedDataCallback(linkedDataWrapper)
);
}

private static void CheckPreconditions()
{
if (!_apiVersionSet)
SetApiVersion(API_VERSION_NUMBER);
if (_busy)
throw new InvalidOperationException("Authenticator busy");

_busy = true;
}

private static void Cleanup()
private static void Cleanup(int id)
{
_busy = false;
AuthenticatorStorage.Cleanup();
_apiCalls[id]?.Dispose();
_apiCalls.Remove(id);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class AuthenticatorAssertionResponse
internal AuthenticatorAssertionResponse(WauthnAuthenticatorAssertionResponse wauthnResponse)
{
ClientDataJson = NullSafeMarshal.PtrToArray(wauthnResponse.clientDataJson);
AuthenticatorData = NullSafeMarshal.PtrToArray(wauthnResponse.attestationObject);
AuthenticatorData = NullSafeMarshal.PtrToArray(wauthnResponse.authenticatorData);
Signature = NullSafeMarshal.PtrToArray(wauthnResponse.signature);
UserHandle = NullSafeMarshal.PtrToArray(wauthnResponse.userHandle);
AttestationObject = NullSafeMarshal.PtrToArray(wauthnResponse.attestationObject);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/

using static Interop;

namespace Tizen.Security.WebAuthn
{
internal class AuthenticatorGetAssertionStorage : AuthenticatorStorage
{
private WauthnGaOnResponseCallback _responseCallback;
private bool _disposed = false;

public AuthenticatorGetAssertionStorage(ClientData clientData, PubkeyCredRequestOptions options)
{
CopyClientData(clientData);
CopyCredRequestOptions(options);
}

public void SetCallbacks(
WauthnDisplayQrcodeCallback qrcodeCallback,
WauthnGaOnResponseCallback responseCallback,
WauthnUpdateLinkedDataCallback linkedDataCallback)
{
_qrcodeCallback = qrcodeCallback;
_responseCallback = responseCallback;
_linkedDataCallback = linkedDataCallback;

WauthnGaCallbacks = new WauthnGaCallbacks(_qrcodeCallback, _responseCallback, _linkedDataCallback);
}

public override void Dispose()
{
if (_disposed)
return;

base.Dispose();

_disposed = true;
}

private void CopyCredRequestOptions(PubkeyCredRequestOptions options)
{
CopyCredentials(options.AllowCredentials);
CopyHints(options.Hints);
CopyAttestationFormats(options.AttestationFormats);
CopyExtensions(options.Extensions);
CopyLinkedDevice(options.LinkedDevice);
WauthnPubkeyCredRequestOptions = new WauthnPubkeyCredRequestOptions(
(nuint)options.Timeout,
options.RpId,
_credentialsUnmanaged,
options.UserVerification,
_hintsUnmanaged,
options.Attestation,
_attestationFormatsUnmanaged,
_extensionsUnmanaged,
_linkedDeviceUnmanaged);
}

public WauthnGaCallbacks WauthnGaCallbacks { get; private set; }
public WauthnPubkeyCredRequestOptions WauthnPubkeyCredRequestOptions { get; private set; }
}
}
Loading

0 comments on commit 4cda61e

Please sign in to comment.