[WebAuthn] Enable concurrent Authenticator requests where supported by the native...
authorFilip Skrzeczkowski <110346745+feedop@users.noreply.github.com>
Thu, 24 Oct 2024 07:52:45 +0000 (09:52 +0200)
committerGitHub <noreply@github.com>
Thu, 24 Oct 2024 07:52:45 +0000 (09:52 +0200)
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.

src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn.csproj
src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/Authenticator.cs
src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorAssertionResponse.cs
src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorGetAssertionStorage.cs [new file with mode: 0644]
src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorMakeCredentialStorage.cs [new file with mode: 0644]
src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorStorage.cs

index bb1db212d74fdb3242cf151132af8f54238ff3c0..96a7e89bf0b82fd0a49d85a4c1bc30d1221174ef 100644 (file)
@@ -1,7 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <OutputType>library</OutputType>
     <TargetFramework>net6.0</TargetFramework>
   </PropertyGroup>
 
index 68707c8b3a7dc039cc4e2d9e9950a28d868297f3..803c3915604fd31686b929fee39c577214298c7a 100644 (file)
@@ -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;
@@ -31,14 +30,9 @@ namespace Tizen.Security.WebAuthn
     {
         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>
@@ -86,32 +80,36 @@ namespace Tizen.Security.WebAuthn
         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;
             }
         }
@@ -146,29 +144,33 @@ namespace Tizen.Security.WebAuthn
         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;
             }
         }
@@ -195,7 +197,7 @@ namespace Tizen.Security.WebAuthn
             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;
 
@@ -210,7 +212,7 @@ namespace Tizen.Security.WebAuthn
                 callbacks.ResponseCallback(pubkeyCredManaged, result, _userData);
 
                 if (result != WauthnError.None)
-                    Cleanup();
+                    Cleanup(id);
             }
 
             void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
@@ -219,17 +221,17 @@ namespace Tizen.Security.WebAuthn
                 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;
 
@@ -244,7 +246,7 @@ namespace Tizen.Security.WebAuthn
                 callbacks.ResponseCallback(pubkeyCredManaged, result, _userData);
 
                 if (result != WauthnError.None)
-                    Cleanup();
+                    Cleanup(id);
             }
 
             void linkedDataWrapper(IntPtr linkedData, WauthnError result, IntPtr _)
@@ -253,29 +255,26 @@ namespace Tizen.Security.WebAuthn
                 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
index c8bc6723d9ff4a18ad9e6885c785a7b6c14f7cc6..b17fa49726c4cff289876d253124d31e145049c5 100644 (file)
@@ -31,7 +31,7 @@ namespace Tizen.Security.WebAuthn
         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);
diff --git a/src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorGetAssertionStorage.cs b/src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorGetAssertionStorage.cs
new file mode 100644 (file)
index 0000000..f483fbf
--- /dev/null
@@ -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; }
+    }
+}
\ No newline at end of file
diff --git a/src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorMakeCredentialStorage.cs b/src/Tizen.Security.WebAuthn/Tizen.Security.WebAuthn/AuthenticatorMakeCredentialStorage.cs
new file mode 100644 (file)
index 0000000..8cbbb63
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ *  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 System.Collections.Generic;
+using System.Linq;
+using static Interop;
+
+namespace Tizen.Security.WebAuthn
+{
+    internal class AuthenticatorMakeCredentialStorage : AuthenticatorStorage
+    {
+        #region Internal unmanaged memory
+        private UnmanagedMemory _rpNameUnmanaged = new();
+        private UnmanagedMemory _rpIdUnmanaged = new();
+        private UnmanagedMemory _rpUnmanaged = new();
+        private UnmanagedMemory _userNameUnmanaged = new();
+        private UnmanagedMemory _userIdDataUnmanaged = new();
+        private UnmanagedMemory _userIdConstBufferUnmanaged = new();
+        private UnmanagedMemory _userDisplayNameUnmanaged = new();
+        private UnmanagedMemory _userUnmanaged = new();
+        private UnmanagedMemory _pubkeyCredParamsParametersUnmanaged = new();
+        private UnmanagedMemory _pubkeyCredParamsUnmanaged = new();
+        private UnmanagedMemory _authenticatorSelectionUnmanaged = new();
+
+        private WauthnMcOnResponseCallback _responseCallback;
+        #endregion
+
+        private bool _disposed = false;
+
+        public AuthenticatorMakeCredentialStorage(ClientData clientData, PubkeyCredCreationOptions options)
+        {
+            CopyClientData(clientData);
+            CopyCredCreationOptions(options);
+        }
+
+        public void SetCallbacks(
+            WauthnDisplayQrcodeCallback qrcodeCallback,
+            WauthnMcOnResponseCallback responseCallback,
+            WauthnUpdateLinkedDataCallback linkedDataCallback)
+        {
+            _qrcodeCallback = qrcodeCallback;
+            _responseCallback = responseCallback;
+            _linkedDataCallback = linkedDataCallback;
+
+            WauthnMcCallbacks = new WauthnMcCallbacks(_qrcodeCallback, _responseCallback, _linkedDataCallback);
+        }
+
+        public override void Dispose()
+        {
+            if (_disposed)
+                return;
+
+            _rpNameUnmanaged.Dispose();
+            _rpIdUnmanaged.Dispose();
+            _rpUnmanaged.Dispose();
+            _userNameUnmanaged.Dispose();
+            _userIdConstBufferUnmanaged.Dispose();
+            _userDisplayNameUnmanaged.Dispose();
+            _userUnmanaged.Dispose();
+            _pubkeyCredParamsParametersUnmanaged.Dispose();
+            _pubkeyCredParamsUnmanaged.Dispose();
+
+            base.Dispose();
+
+            _disposed = true;
+        }
+
+        #region Data marshalling
+        private void CopyCredCreationOptions(PubkeyCredCreationOptions options)
+        {
+            CopyRp(options.Rp);
+            CopyUser(options.User);
+            CopyCredParams(options.PubkeyCredParams);
+            CopyCredentials(options.ExcludeCredentials);
+            CopyAuthenticatorSelection(options.AuthenticatorSelection);
+            CopyHints(options.Hints);
+            CopyAttestationFormats(options.AttestationFormats);
+            CopyExtensions(options.Extensions);
+            CopyLinkedDevice(options.LinkedDevice);
+
+            WauthnPubkeyCredCreationOptions = new WauthnPubkeyCredCreationOptions(
+                    _rpUnmanaged,
+                    _userUnmanaged,
+                    _pubkeyCredParamsUnmanaged,
+                    (nuint)options.Timeout,
+                    _credentialsUnmanaged,
+                    _authenticatorSelectionUnmanaged,
+                    _hintsUnmanaged,
+                    options.Attestation,
+                    _attestationFormatsUnmanaged,
+                    _extensionsUnmanaged,
+                    _linkedDeviceUnmanaged);
+        }
+
+        private void CopyRp(RelyingPartyEntity rp)
+        {
+            _rpNameUnmanaged = new UnmanagedMemory(rp.Name);
+            _rpIdUnmanaged = new UnmanagedMemory(rp.Id);
+            _rpUnmanaged = new UnmanagedMemory(new WauthnRpEntity(_rpNameUnmanaged, _rpIdUnmanaged));
+        }
+
+        private void CopyUser(UserEntity user)
+        {
+                _userNameUnmanaged = new UnmanagedMemory(user.Name);
+                _userIdDataUnmanaged = UnmanagedMemory.PinArray(user.Id);
+                _userIdConstBufferUnmanaged = new UnmanagedMemory(new WauthnConstBuffer(_userIdDataUnmanaged, (nuint)user.Id.Length));
+                _userDisplayNameUnmanaged = new UnmanagedMemory(user.DisplayName);
+                _userUnmanaged = new UnmanagedMemory(new WauthnUserEntity(
+                    _userNameUnmanaged,
+                    _userIdConstBufferUnmanaged,
+                    _userDisplayNameUnmanaged));
+        }
+
+        private void CopyCredParams(IEnumerable<PubkeyCredParam> credParams)
+        {
+            if (credParams is null || !credParams.Any())
+                return;
+
+            WauthnPubkeyCredParam[] pubkeyCredParamStructArray = credParams.Select((PubkeyCredParam param) => new WauthnPubkeyCredParam(param.Type, param.Alg)).ToArray();
+            _pubkeyCredParamsParametersUnmanaged = UnmanagedMemory.PinArray(pubkeyCredParamStructArray);
+            _pubkeyCredParamsUnmanaged = new UnmanagedMemory(new WauthnPubkeyCredParams((nuint)pubkeyCredParamStructArray.Length, _pubkeyCredParamsParametersUnmanaged));
+        }
+
+        protected void CopyAuthenticatorSelection(AuthenticationSelectionCriteria selection)
+        {
+            if (selection is null)
+                return;
+
+            _authenticatorSelectionUnmanaged = new UnmanagedMemory(new WauthnAuthenticationSelCri(
+                selection.Attachment,
+                selection.ResidentKey,
+                (byte)(selection.RequireResidentKey ? 1 : 0),
+                selection.UserVerification));
+        }
+        #endregion
+
+        public WauthnMcCallbacks WauthnMcCallbacks { get; private set; }
+        public WauthnPubkeyCredCreationOptions WauthnPubkeyCredCreationOptions { get; private set; }
+    }
+}
\ No newline at end of file
index a47c6718f89ecc9bc44505974215df8b68993a30..6456426be5f7766c35c20d75cf34a45c9a1ab872 100644 (file)
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.InteropServices;
 using static Interop;
-using static Tizen.Security.WebAuthn.ErrorFactory;
 
 namespace Tizen.Security.WebAuthn
 {
-    internal static class AuthenticatorStorage
+    internal abstract class AuthenticatorStorage : IDisposable
     {
         #region Internal unmanaged memory
-        private static UnmanagedMemory[] _credentialsIdUnmanagedDataArray;
-        private static UnmanagedMemory[] _credentialsIdUnmanagedConstBufferArray;
-        private static UnmanagedMemory[] _attestationFormatsUnmanagedDataArray;
-        private static UnmanagedMemory[] _attestationFormatsUnmanagedConstBufferArray;
-        private static UnmanagedMemory[] _extensionIdUnmanagedDataArray;
-        private static UnmanagedMemory[] _extensionIdUnmanagedConstBufferArray;
-        private static UnmanagedMemory[] _extensionValueUnmanagedDataArray;
-        private static UnmanagedMemory[] _extensionValueUnmanagedConstBufferArray;
-        private static UnmanagedMemory _jsonDataUnmanaged = new();
-        private static UnmanagedMemory _jsonDataConstBufferUnmanaged = new();
-        private static UnmanagedMemory _rpNameUnmanaged = new();
-        private static UnmanagedMemory _rpIdUnmanaged = new();
-        private static UnmanagedMemory _rpUnmanaged = new();
-        private static UnmanagedMemory _userNameUnmanaged = new();
-        private static UnmanagedMemory _userIdDataUnmanaged = new();
-        private static UnmanagedMemory _userIdConstBufferUnmanaged = new();
-        private static UnmanagedMemory _userDisplayNameUnmanaged = new();
-        private static UnmanagedMemory _userUnmanaged = new();
-        private static UnmanagedMemory _pubkeyCredParamsParametersUnmanaged = new();
-        private static UnmanagedMemory _pubkeyCredParamsUnmanaged = new();
-        private static UnmanagedMemory _credentialsDescriptorsUnmanaged = new();
-        private static UnmanagedMemory _credentialsUnmanaged = new();
-        private static UnmanagedMemory _authenticatorSelectionUnmanaged = new();
-        private static UnmanagedMemory _hintsArrayUnmanaged = new();
-        private static UnmanagedMemory _hintsUnmanaged = new();
-        private static UnmanagedMemory _attestationFormatsArrayUnmanaged = new();
-        private static UnmanagedMemory _attestationFormatsUnmanaged = new();
-        private static UnmanagedMemory _extensionsArrayUnmanaged = new();
-        private static UnmanagedMemory _extensionsUnmanaged = new();
-        private static UnmanagedMemory _contactIdDataUnmanaged = new();
-        private static UnmanagedMemory _contactIdUnmanaged = new();
-        private static UnmanagedMemory _linkIdDataUnmanaged = new();
-        private static UnmanagedMemory _linkIdUnmanaged = new();
-        private static UnmanagedMemory _linkSecretDataUnmanaged = new();
-        private static UnmanagedMemory _linkSecretUnmanaged = new();
-        private static UnmanagedMemory _authenticatorPubkeyDataUnmanaged = new();
-        private static UnmanagedMemory _authenticatorPubkeyUnmanaged = new();
-        private static UnmanagedMemory _authenticatorNameDataUnmanaged = new();
-        private static UnmanagedMemory _authenticatorNameUnmanaged = new();
-        private static UnmanagedMemory _signatureDataUnmanaged = new();
-        private static UnmanagedMemory _signatureUnmanaged = new();
-        private static UnmanagedMemory _tunnelServerDomainDataUnmanaged = new();
-        private static UnmanagedMemory _tunnelServerDomainUnmanaged = new();
-        private static UnmanagedMemory _identityKeyDataUnmanaged = new();
-        private static UnmanagedMemory _identityKeyUnmanaged = new();
-        private static UnmanagedMemory _linkedDeviceUnmanaged = new();
+        protected UnmanagedMemory _credentialsUnmanaged = new();
+        protected UnmanagedMemory _hintsUnmanaged = new();
+        protected UnmanagedMemory _attestationFormatsUnmanaged = new();
+        protected UnmanagedMemory _extensionsUnmanaged = new();
+        protected UnmanagedMemory _linkedDeviceUnmanaged = new();
+
+        protected WauthnDisplayQrcodeCallback _qrcodeCallback;
+        protected WauthnUpdateLinkedDataCallback _linkedDataCallback;
+
+        private UnmanagedMemory[] _credentialsIdUnmanagedDataArray;
+        private UnmanagedMemory[] _credentialsIdUnmanagedConstBufferArray;
+        private UnmanagedMemory[] _attestationFormatsUnmanagedDataArray;
+        private UnmanagedMemory[] _attestationFormatsUnmanagedConstBufferArray;
+        private UnmanagedMemory[] _extensionIdUnmanagedDataArray;
+        private UnmanagedMemory[] _extensionIdUnmanagedConstBufferArray;
+        private UnmanagedMemory[] _extensionValueUnmanagedDataArray;
+        private UnmanagedMemory[] _extensionValueUnmanagedConstBufferArray;
+
+        private UnmanagedMemory _jsonDataUnmanaged = new();
+        private UnmanagedMemory _jsonDataConstBufferUnmanaged = new();
+        private UnmanagedMemory _credentialsDescriptorsUnmanaged = new();
+        private UnmanagedMemory _hintsArrayUnmanaged = new();
+        private UnmanagedMemory _attestationFormatsArrayUnmanaged = new();
+        private UnmanagedMemory _extensionsArrayUnmanaged = new();
+        private UnmanagedMemory _contactIdDataUnmanaged = new();
+        private UnmanagedMemory _contactIdUnmanaged = new();
+        private UnmanagedMemory _linkIdDataUnmanaged = new();
+        private UnmanagedMemory _linkIdUnmanaged = new();
+        private UnmanagedMemory _linkSecretDataUnmanaged = new();
+        private UnmanagedMemory _linkSecretUnmanaged = new();
+        private UnmanagedMemory _authenticatorPubkeyDataUnmanaged = new();
+        private UnmanagedMemory _authenticatorPubkeyUnmanaged = new();
+        private UnmanagedMemory _authenticatorNameDataUnmanaged = new();
+        private UnmanagedMemory _authenticatorNameUnmanaged = new();
+        private UnmanagedMemory _signatureDataUnmanaged = new();
+        private UnmanagedMemory _signatureUnmanaged = new();
+        private UnmanagedMemory _tunnelServerDomainDataUnmanaged = new();
+        private UnmanagedMemory _tunnelServerDomainUnmanaged = new();
+        private UnmanagedMemory _identityKeyDataUnmanaged = new();
+        private UnmanagedMemory _identityKeyUnmanaged = new();
         #endregion
 
-        public static void SetDataForMakeCredential(ClientData clientData, PubkeyCredCreationOptions options)
-        {
-            CopyClientData(clientData);
-            CopyCredCreationOptions(options);
-        }
+        private bool _disposed = false;
 
-        public static void SetDataForGetAssertion(ClientData clientData, PubkeyCredRequestOptions options)
+        public virtual void Dispose()
         {
-            CopyClientData(clientData);
-            CopyCredRequestOptions(options);
-        }
+            if (_disposed)
+                return;
 
-        public static void Cleanup()
-        {
             CleanupArray(_credentialsIdUnmanagedDataArray);
             CleanupArray(_credentialsIdUnmanagedConstBufferArray);
             CleanupArray(_attestationFormatsUnmanagedDataArray);
@@ -99,18 +84,8 @@ namespace Tizen.Security.WebAuthn
 
             _jsonDataUnmanaged.Dispose();
             _jsonDataConstBufferUnmanaged.Dispose();
-            _rpNameUnmanaged.Dispose();
-            _rpIdUnmanaged.Dispose();
-            _rpUnmanaged.Dispose();
-            _userNameUnmanaged.Dispose();
-            _userIdConstBufferUnmanaged.Dispose();
-            _userDisplayNameUnmanaged.Dispose();
-            _userUnmanaged.Dispose();
-            _pubkeyCredParamsParametersUnmanaged.Dispose();
-            _pubkeyCredParamsUnmanaged.Dispose();
             _credentialsDescriptorsUnmanaged.Dispose();
             _credentialsUnmanaged.Dispose();
-            _authenticatorSelectionUnmanaged.Dispose();
             _hintsArrayUnmanaged.Dispose();
             _hintsUnmanaged.Dispose();
             _attestationFormatsArrayUnmanaged.Dispose();
@@ -134,90 +109,19 @@ namespace Tizen.Security.WebAuthn
             _identityKeyDataUnmanaged.Dispose();
             _identityKeyUnmanaged.Dispose();
             _linkedDeviceUnmanaged.Dispose();
+
+            _disposed = true;
         }
 
-        private static void CopyClientData(ClientData clientData)
+        #region Data marshalling
+        protected void CopyClientData(ClientData clientData)
         {
             _jsonDataUnmanaged = UnmanagedMemory.PinArray(clientData.JsonData);
             _jsonDataConstBufferUnmanaged = new UnmanagedMemory(new WauthnConstBuffer(_jsonDataUnmanaged, (nuint)clientData.JsonData.Length));
             WauthnClientData = new WauthnClientData(_jsonDataConstBufferUnmanaged, clientData.HashAlgo);
         }
 
-        private static void CopyCredCreationOptions(PubkeyCredCreationOptions options)
-        {
-            CopyRp(options.Rp);
-            CopyUser(options.User);
-            CopyCredParams(options.PubkeyCredParams);
-            CopyCredentials(options.ExcludeCredentials);
-            CopyAuthenticatorSelection(options.AuthenticatorSelection);
-            CopyHints(options.Hints);
-            CopyAttestationFormats(options.AttestationFormats);
-            CopyExtensions(options.Extensions);
-            CopyLinkedDevice(options.LinkedDevice);
-
-            WauthnPubkeyCredCreationOptions = new WauthnPubkeyCredCreationOptions(
-                    _rpUnmanaged,
-                    _userUnmanaged,
-                    _pubkeyCredParamsUnmanaged,
-                    (nuint)options.Timeout,
-                    _credentialsUnmanaged,
-                    _authenticatorSelectionUnmanaged,
-                    _hintsUnmanaged,
-                    options.Attestation,
-                    _attestationFormatsUnmanaged,
-                    _extensionsUnmanaged,
-                    _linkedDeviceUnmanaged);
-        }
-
-        private static 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);
-        }
-
-        private static void CopyRp(RelyingPartyEntity rp)
-        {
-            _rpNameUnmanaged = new UnmanagedMemory(rp.Name);
-            _rpIdUnmanaged = new UnmanagedMemory(rp.Id);
-            _rpUnmanaged = new UnmanagedMemory(new WauthnRpEntity(_rpNameUnmanaged, _rpIdUnmanaged));
-        }
-
-        private static void CopyUser(UserEntity user)
-        {
-                _userNameUnmanaged = new UnmanagedMemory(user.Name);
-                _userIdDataUnmanaged = UnmanagedMemory.PinArray(user.Id);
-                _userIdConstBufferUnmanaged = new UnmanagedMemory(new WauthnConstBuffer(_userIdDataUnmanaged, (nuint)user.Id.Length));
-                _userDisplayNameUnmanaged = new UnmanagedMemory(user.DisplayName);
-                _userUnmanaged = new UnmanagedMemory(new WauthnUserEntity(
-                    _userNameUnmanaged,
-                    _userIdConstBufferUnmanaged,
-                    _userDisplayNameUnmanaged));
-        }
-
-        private static void CopyCredParams(IEnumerable<PubkeyCredParam> credParams)
-        {
-            if (credParams is null || !credParams.Any())
-                return;
-
-            WauthnPubkeyCredParam[] pubkeyCredParamStructArray = credParams.Select((PubkeyCredParam param) => new WauthnPubkeyCredParam(param.Type, param.Alg)).ToArray();
-            _pubkeyCredParamsParametersUnmanaged = UnmanagedMemory.PinArray(pubkeyCredParamStructArray);
-            _pubkeyCredParamsUnmanaged = new UnmanagedMemory(new WauthnPubkeyCredParams((nuint)pubkeyCredParamStructArray.Length, _pubkeyCredParamsParametersUnmanaged));
-        }
-
-        private static void CopyCredentials(IEnumerable<PubkeyCredDescriptor> credentials)
+        protected void CopyCredentials(IEnumerable<PubkeyCredDescriptor> credentials)
         {
             if (credentials is null || !credentials.Any())
                 return;
@@ -239,19 +143,7 @@ namespace Tizen.Security.WebAuthn
             _credentialsUnmanaged = new UnmanagedMemory(new WauthnPubkeyCredDescriptors((nuint)credentialsCount, _credentialsDescriptorsUnmanaged));
         }
 
-        private static void CopyAuthenticatorSelection(AuthenticationSelectionCriteria selection)
-        {
-            if (selection is null)
-                return;
-
-            _authenticatorSelectionUnmanaged = new UnmanagedMemory(new WauthnAuthenticationSelCri(
-                selection.Attachment,
-                selection.ResidentKey,
-                (byte)(selection.RequireResidentKey ? 1 : 0),
-                selection.UserVerification));
-        }
-
-        private static void CopyHints(IEnumerable<PubkeyCredHint> hints)
+        protected void CopyHints(IEnumerable<PubkeyCredHint> hints)
         {
             if (hints is null || !hints.Any())
                 return;
@@ -261,7 +153,7 @@ namespace Tizen.Security.WebAuthn
             _hintsUnmanaged = new UnmanagedMemory(new WauthnPubkeyCredHints((nuint)hintsArray.Length, _hintsArrayUnmanaged));
         }
 
-        private static void CopyAttestationFormats(IEnumerable<byte[]> attestationFormats)
+        protected void CopyAttestationFormats(IEnumerable<byte[]> attestationFormats)
         {
             if (attestationFormats is null || !attestationFormats.Any())
                 return;
@@ -283,7 +175,7 @@ namespace Tizen.Security.WebAuthn
             _attestationFormatsUnmanaged = new UnmanagedMemory(new WauthnAttestationFormats((nuint)attestationFormatsCount, _attestationFormatsArrayUnmanaged));
         }
 
-        private static void CopyExtensions(IEnumerable<AuthenticationExtension> extensions)
+        protected void CopyExtensions(IEnumerable<AuthenticationExtension> extensions)
         {
             if (extensions is null || !extensions.Any())
                 return;
@@ -312,7 +204,7 @@ namespace Tizen.Security.WebAuthn
             _extensionsUnmanaged = new UnmanagedMemory(new WauthnAuthenticationExts((nuint)extensionCount, _extensionsArrayUnmanaged));
         }
 
-        private static void CopyLinkedDevice(HybridLinkedData linkedDevice)
+        protected void CopyLinkedDevice(HybridLinkedData linkedDevice)
         {
             if (linkedDevice is null)
                 return;
@@ -350,12 +242,10 @@ namespace Tizen.Security.WebAuthn
                 _signatureUnmanaged,
                 _tunnelServerDomainUnmanaged,
                 _identityKeyUnmanaged));
-
-            if (_linkedDeviceUnmanaged == IntPtr.Zero)
-                throw new TimeoutException("linked null");
         }
+        #endregion
 
-        public static void CleanupArray(UnmanagedMemory[] array)
+        protected static void CleanupArray(UnmanagedMemory[] array)
         {
             if (array is null)
                 return;
@@ -363,8 +253,6 @@ namespace Tizen.Security.WebAuthn
                 memory.Dispose();
         }
 
-        public static WauthnClientData WauthnClientData { get; private set; }
-        public static WauthnPubkeyCredCreationOptions WauthnPubkeyCredCreationOptions { get; private set; }
-        public static WauthnPubkeyCredRequestOptions WauthnPubkeyCredRequestOptions { get; private set; }
+        public WauthnClientData WauthnClientData { get; private set; }
     }
 }
\ No newline at end of file