From c1461ab16b88fc6ae7d96a2c7bfc9db1722475d9 Mon Sep 17 00:00:00 2001 From: Matt Portune Date: Mon, 30 Aug 2021 13:38:03 -0400 Subject: [PATCH] FIDO2 implementation using Google Play Services on Android --- src/Android/Android.csproj | 3 + src/Android/Fido2System/Fido2BuilderObject.cs | 106 ++++++++ src/Android/Fido2System/Fido2Service.cs | 249 ++++++++++++++++++ src/Android/MainActivity.cs | 83 +++++- src/Android/Services/DeviceActionService.cs | 8 +- src/App/Pages/Accounts/TwoFactorPage.xaml.cs | 21 +- .../Pages/Accounts/TwoFactorPageViewModel.cs | 11 +- src/App/Resources/AppResources.Designer.cs | 48 ++++ src/App/Resources/AppResources.resx | 24 ++ src/Core/Enums/Fido2CodesTypes.cs | 8 + .../Models/Data/Fido2AssertionResponse.cs | 16 ++ .../Data/Fido2AuthenticatorSelection.cs | 14 + .../Models/Data/Fido2CredentialDescriptor.cs | 15 ++ src/Core/Models/Data/Fido2PubKeyCredParam.cs | 12 + src/Core/Models/Data/Fido2RP.cs | 14 + src/Core/Models/Data/Fido2User.cs | 16 ++ .../Fido2AuthenticationChallengeRequest.cs | 19 ++ .../Fido2AuthenticationChallengeResponse.cs | 22 ++ .../Fido2RegistrationChallengeResponse.cs | 28 ++ 19 files changed, 713 insertions(+), 4 deletions(-) create mode 100644 src/Android/Fido2System/Fido2BuilderObject.cs create mode 100644 src/Android/Fido2System/Fido2Service.cs create mode 100644 src/Core/Enums/Fido2CodesTypes.cs create mode 100644 src/Core/Models/Data/Fido2AssertionResponse.cs create mode 100644 src/Core/Models/Data/Fido2AuthenticatorSelection.cs create mode 100644 src/Core/Models/Data/Fido2CredentialDescriptor.cs create mode 100644 src/Core/Models/Data/Fido2PubKeyCredParam.cs create mode 100644 src/Core/Models/Data/Fido2RP.cs create mode 100644 src/Core/Models/Data/Fido2User.cs create mode 100644 src/Core/Models/Request/Fido2AuthenticationChallengeRequest.cs create mode 100644 src/Core/Models/Response/Fido2AuthenticationChallengeResponse.cs create mode 100644 src/Core/Models/Response/Fido2RegistrationChallengeResponse.cs diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 5414e8255..71a1c31fc 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -92,6 +92,7 @@ + 117.0.0 @@ -115,6 +116,8 @@ + + diff --git a/src/Android/Fido2System/Fido2BuilderObject.cs b/src/Android/Fido2System/Fido2BuilderObject.cs new file mode 100644 index 000000000..7df6b4ecc --- /dev/null +++ b/src/Android/Fido2System/Fido2BuilderObject.cs @@ -0,0 +1,106 @@ +#if !FDROID +using System.Collections.Generic; +using Android.Gms.Fido.Common; +using Android.Gms.Fido.Fido2.Api.Common; +using Bit.Core.Models.Data; +using Bit.Core.Models.Response; +using Bit.Core.Utilities; +using Java.Lang; +using Newtonsoft.Json.Linq; + +namespace Bit.Droid.Fido2System +{ + class Fido2BuilderObject + { + public static PublicKeyCredentialRequestOptions ParsePublicKeyCredentialRequestOptions( + Fido2AuthenticationChallengeResponse data) + { + if (data == null) + { + return null; + } + + var builder = new PublicKeyCredentialRequestOptions.Builder(); + + if (!string.IsNullOrEmpty(data.Challenge)) + { + builder.SetChallenge(CoreHelpers.Base64UrlDecode(data.Challenge)); + } + if (data.AllowCredentials != null && data.AllowCredentials.Count > 0) + { + builder.SetAllowList(ParseCredentialDescriptors(data.AllowCredentials)); + } + if (!string.IsNullOrEmpty(data.RpId)) + { + builder.SetRpId(data.RpId); + } + if (data.Timeout > 0) + { + builder.SetTimeoutSeconds((Double)(data.Timeout / 1000)); + } + if (data.Extensions != null) + { + builder.SetAuthenticationExtensions(ParseExtensions((JObject)data.Extensions)); + } + return builder.Build(); + } + + private static List ParseCredentialDescriptors( + List listData) + { + if (listData == null || listData.Count == 0) + { + return new List(); + } + + var credentials = new List(); + + foreach (var data in listData) + { + string id = null; + string type = null; + var transports = new List(); + + if (!string.IsNullOrEmpty(data.Id)) + { + id = data.Id; + } + if (!string.IsNullOrEmpty(data.Type)) + { + type = data.Type; + } + if (data.Transports != null && data.Transports.Count > 0) + { + foreach (var transport in data.Transports) + { + transports.Add(Transport.FromString(transport)); + } + } + + credentials.Add(new PublicKeyCredentialDescriptor(type, CoreHelpers.Base64UrlDecode(id), transports)); + } + + return credentials; + } + + private static AuthenticationExtensions ParseExtensions(JObject extensions) + { + var builder = new AuthenticationExtensions.Builder(); + + if (extensions.ContainsKey("appid")) + { + var appId = new FidoAppIdExtension((string)extensions.GetValue("appid")); + builder.SetFido2Extension(appId); + } + + if (extensions.ContainsKey("uvm")) + { + var uvm = new UserVerificationMethodExtension((bool)extensions.GetValue("uvm")); + builder.SetUserVerificationMethodExtension(uvm); + } + + return builder.Build(); + } + } +} +#endif diff --git a/src/Android/Fido2System/Fido2Service.cs b/src/Android/Fido2System/Fido2Service.cs new file mode 100644 index 000000000..577c287e3 --- /dev/null +++ b/src/Android/Fido2System/Fido2Service.cs @@ -0,0 +1,249 @@ +#if !FDROID +using Android.App; +using Android.Content; +using Android.Gms.Fido; +using Android.Gms.Fido.Fido2; +using Android.Gms.Fido.Fido2.Api.Common; +using Android.Gms.Tasks; +using Android.Util; +using AndroidX.AppCompat.App; +using Bit.App.Services; +using Bit.Core.Abstractions; +using Bit.Core.Enums; +using Bit.Core.Models.Data; +using Bit.Core.Models.Request; +using Bit.Core.Models.Response; +using Bit.Core.Utilities; +using Java.Lang; +using Newtonsoft.Json; +using Xamarin.Forms; +using Enum = System.Enum; + +namespace Bit.Droid.Fido2System +{ + public class Fido2Service + { + public static readonly string _tag_log = "Fido2Service"; + + public static Fido2Service INSTANCE = new Fido2Service(); + + private readonly MobileI18nService _i18nService; + private readonly IPlatformUtilsService _platformUtilsService; + + private AppCompatActivity _activity; + private Fido2ApiClient _fido2ApiClient; + private Fido2CodesTypes _fido2CodesType; + + public Fido2Service() + { + _i18nService = ServiceContainer.Resolve("i18nService") as MobileI18nService; + _platformUtilsService = ServiceContainer.Resolve("platformUtilsService"); + } + + public void Start(AppCompatActivity activity) + { + _activity = activity; + _fido2ApiClient = Fido.GetFido2ApiClient(_activity); + } + + public void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + if (resultCode == Result.Ok && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode)) + { + switch ((Fido2CodesTypes)requestCode) + { + case Fido2CodesTypes.RequestSignInUser: + var errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra); + if (errorExtra != null) + { + HandleErrorCode(errorExtra); + } + else + { + if (data != null) + { + SignInUserResponse(data); + } + } + break; + // TODO: Key registration, should we ever choose to implement client-side + /*case Fido2CodesTypes.RequestRegisterNewKey: + errorExtra = data?.GetByteArrayExtra(Fido.Fido2KeyErrorExtra); + if (errorExtra != null) + { + HandleErrorCode(errorExtra); + } + else + { + if (data != null) + { + // begin registration flow + } + } + break;*/ + } + } + else if (resultCode == Result.Canceled && Enum.IsDefined(typeof(Fido2CodesTypes), requestCode)) + { + Log.Info(_tag_log, "cancelled"); + _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2AbortError"), + _i18nService.T("Fido2Title")); + } + } + + public void OnSuccess(Object result) + { + if (result != null && Enum.IsDefined(typeof(Fido2CodesTypes), _fido2CodesType)) + { + try + { + _activity.StartIntentSenderForResult(((PendingIntent)result).IntentSender, (int)_fido2CodesType, + null, 0, 0, 0); + } + catch (System.Exception e) + { + Log.Error(_tag_log, e.Message); + _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"), + _i18nService.T("Fido2Title")); + } + } + } + + public void OnFailure(Exception e) + { + Log.Error(_tag_log, e.Message ?? "OnFailure: No error message returned"); + _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"), + _i18nService.T("Fido2Title")); + } + + public void OnComplete(Task task) + { + Log.Debug(_tag_log, "OnComplete"); + } + + public async System.Threading.Tasks.Task SignInUserRequestAsync(string dataJson) + { + try + { + var dataObject = JsonConvert.DeserializeObject(dataJson); + _fido2CodesType = Fido2CodesTypes.RequestSignInUser; + var options = Fido2BuilderObject.ParsePublicKeyCredentialRequestOptions(dataObject); + var task = _fido2ApiClient.GetSignPendingIntent(options); + task.AddOnSuccessListener((IOnSuccessListener)_activity) + .AddOnFailureListener((IOnFailureListener)_activity) + .AddOnCompleteListener((IOnCompleteListener)_activity); + } + catch (System.Exception e) + { + Log.Error(_tag_log, e.StackTrace); + await _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"), + _i18nService.T("Fido2Title")); + } + finally + { + Log.Info(_tag_log, "SignInUserRequest() -> finally()"); + } + } + + private void SignInUserResponse(Intent data) + { + try + { + var response = + AuthenticatorAssertionResponse.DeserializeFromBytes( + data.GetByteArrayExtra(Fido.Fido2KeyResponseExtra)); + var responseJson = JsonConvert.SerializeObject(new Fido2AuthenticationChallengeRequest + { + Id = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()), + RawId = CoreHelpers.Base64UrlEncode(response.GetKeyHandle()), + Type = "public-key", + Response = new Fido2AssertionResponse + { + AuthenticatorData = CoreHelpers.Base64UrlEncode(response.GetAuthenticatorData()), + ClientDataJson = CoreHelpers.Base64UrlEncode(response.GetClientDataJSON()), + Signature = CoreHelpers.Base64UrlEncode(response.GetSignature()), + UserHandle = (response.GetUserHandle() != null + ? CoreHelpers.Base64UrlEncode(response.GetUserHandle()) : null), + }, + Extensions = null + } + ); + Device.BeginInvokeOnMainThread(() => ((MainActivity)_activity).Fido2Submission(responseJson)); + } + catch (System.Exception e) + { + Log.Error(_tag_log, e.Message); + _platformUtilsService.ShowDialogAsync(_i18nService.T("Fido2SomethingWentWrong"), + _i18nService.T("Fido2Title")); + } + finally + { + Log.Info(_tag_log, "SignInUserResponse() -> finally()"); + } + } + + public void HandleErrorCode(byte[] errorExtra) + { + var error = AuthenticatorErrorResponse.DeserializeFromBytes(errorExtra); + if (error.ErrorMessage.Length > 0) + { + Log.Info(_tag_log, error.ErrorMessage); + } + string message = ""; + if (error.ErrorCode == ErrorCode.AbortErr) + { + message = "Fido2AbortError"; + } + else if (error.ErrorCode == ErrorCode.TimeoutErr) + { + message = "Fido2TimeoutError"; + } + else if (error.ErrorCode == ErrorCode.AttestationNotPrivateErr) + { + message = "Fido2PrivacyError"; + } + else if (error.ErrorCode == ErrorCode.ConstraintErr) + { + message = "Fido2SomethingWentWrong"; + } + else if (error.ErrorCode == ErrorCode.DataErr) + { + message = "Fido2ServerDataFail"; + } + else if (error.ErrorCode == ErrorCode.EncodingErr) + { + message = "Fido2SomethingWentWrong"; + } + else if (error.ErrorCode == ErrorCode.InvalidStateErr) + { + message = "Fido2SomethingWentWrong"; + } + else if (error.ErrorCode == ErrorCode.NetworkErr) + { + message = "Fido2NetworkFail"; + } + else if (error.ErrorCode == ErrorCode.NotAllowedErr) + { + message = "Fido2NoPermission"; + } + else if (error.ErrorCode == ErrorCode.NotSupportedErr) + { + message = "Fido2NotSupportedError"; + } + else if (error.ErrorCode == ErrorCode.SecurityErr) + { + message = "Fido2SecurityError"; + } + else if (error.ErrorCode == ErrorCode.UnknownErr) + { + message = "Fido2SomethingWentWrong"; + } + else + { + message = "Fido2SomethingWentWrong"; + } + _platformUtilsService.ShowDialogAsync(_i18nService.T(message), _i18nService.T("Fido2Title")); + } + } +} +#endif diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 3208295f4..43cf087c0 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -9,6 +9,7 @@ using Bit.Core.Utilities; using Bit.Core.Abstractions; using System.IO; using System; +using System.Collections.Generic; using Android.Content; using Bit.Droid.Utilities; using Bit.Droid.Receivers; @@ -17,7 +18,11 @@ using Bit.Core.Enums; using Android.Nfc; using Bit.App.Utilities; using System.Threading.Tasks; +using Android.Util; using AndroidX.Core.Content; +#if !FDROID +using Bit.Droid.Fido2System; +#endif using ZXing.Net.Mobile.Android; namespace Bit.Droid @@ -42,7 +47,10 @@ namespace Bit.Droid @"text/*" })] [Register("com.x8bit.bitwarden.MainActivity")] - public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity + public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity, + Android.Gms.Tasks.IOnSuccessListener, + Android.Gms.Tasks.IOnCompleteListener, + Android.Gms.Tasks.IOnFailureListener { private IDeviceActionService _deviceActionService; private IMessagingService _messagingService; @@ -57,6 +65,7 @@ namespace Bit.Droid private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}"; private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$"); + private string _fidoDataJson; protected override void OnCreate(Bundle savedInstanceState) { @@ -91,6 +100,7 @@ namespace Bit.Droid #if !FDROID var appCenterHelper = new AppCenterHelper(_appIdService, _userService); var appCenterTask = appCenterHelper.InitAsync(); + Fido2Service.INSTANCE.Start(this); #endif Xamarin.Essentials.Platform.Init(this, savedInstanceState); @@ -112,6 +122,14 @@ namespace Bit.Droid { Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish()); } + else if (message.Command == "listenFido2") + { + ListenFido2((Dictionary)message.Data); + } + else if (message.Command == "listenFido2TryAgain") + { + ListenFido2(); + } else if (message.Command == "listenYubiKeyOTP") { ListenYubiKey((bool)message.Data); @@ -260,6 +278,34 @@ namespace Bit.Droid return; } } + else if (resultCode == Result.Ok && + Enum.IsDefined(typeof(Fido2CodesTypes), requestCode)) + { +#if !FDROID + Fido2Service.INSTANCE.OnActivityResult(requestCode, resultCode, data); +#endif + } + } + + public void OnSuccess(Java.Lang.Object result) + { +#if !FDROID + Fido2Service.INSTANCE.OnSuccess(result); +#endif + } + + public void OnComplete(Android.Gms.Tasks.Task task) + { +#if !FDROID + Fido2Service.INSTANCE.OnComplete(task); +#endif + } + + public void OnFailure(Java.Lang.Exception e) + { +#if !FDROID + Fido2Service.INSTANCE.OnFailure(e); +#endif } protected override void OnDestroy() @@ -268,6 +314,41 @@ namespace Bit.Droid _broadcasterService.Unsubscribe(_activityKey); } + private void ListenFido2(Dictionary data = null) + { + if (!_deviceActionService.SupportsFido2()) + { + return; + } + +#if !FDROID + RunOnUiThread(async () => + { + try + { + if (data != null) + { + _fidoDataJson = Newtonsoft.Json.JsonConvert.SerializeObject(data); + await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson); + } + else + { + await Fido2Service.INSTANCE.SignInUserRequestAsync(_fidoDataJson); + } + } + catch (Exception e) + { + Log.Error(Fido2Service._tag_log, e.Message); + } + }); +#endif + } + + public void Fido2Submission(string token) + { + _messagingService.Send("gotFido2Token", token); + } + private void ListenYubiKey(bool listen) { if (!_deviceActionService.SupportsNfc()) diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index 45fd32f27..118786278 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -778,7 +778,13 @@ namespace Bit.Droid.Services public bool SupportsFido2() { - return true; +#if !FDROID + if ((int)Build.VERSION.SdkInt >= 21) + { + return true; + } +#endif + return false; } private bool DeleteDir(Java.IO.File dir) diff --git a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs index 44307a22e..7d5ec9651 100644 --- a/src/App/Pages/Accounts/TwoFactorPage.xaml.cs +++ b/src/App/Pages/Accounts/TwoFactorPage.xaml.cs @@ -75,6 +75,18 @@ namespace Bit.App.Pages }); } } + else if (message.Command == "gotFido2Token") + { + var token = (string)message.Data; + if (!string.IsNullOrWhiteSpace(token)) + { + Device.BeginInvokeOnMainThread(async () => + { + _vm.Token = token; + await _vm.SubmitAsync(); + }); + } + } else if (message.Command == "resumeYubiKey") { if (_vm.YubikeyMethod) @@ -174,7 +186,14 @@ namespace Bit.App.Pages { if (_vm.Fido2Method) { - await _vm.Fido2AuthenticateAsync(); + if (Device.RuntimePlatform == Device.Android) + { + _messagingService.Send("listenFido2TryAgain", true); + } + else + { + await _vm.Fido2AuthenticateAsync(); + } } else if (_vm.YubikeyMethod) { diff --git a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs index 2289e4559..5e45df75f 100644 --- a/src/App/Pages/Accounts/TwoFactorPageViewModel.cs +++ b/src/App/Pages/Accounts/TwoFactorPageViewModel.cs @@ -10,6 +10,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; +using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Bit.App.Utilities; using Newtonsoft.Json; @@ -151,7 +153,14 @@ namespace Bit.App.Pages switch (SelectedProviderType.Value) { case TwoFactorProviderType.Fido2WebAuthn: - Fido2AuthenticateAsync(providerData); + if (Device.RuntimePlatform == Device.Android) + { + _messagingService.Send("listenFido2", providerData); + } + else + { + Fido2AuthenticateAsync(providerData); + } break; case TwoFactorProviderType.YubiKey: _messagingService.Send("listenYubiKeyOTP", true); diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs index b7dee1148..e4c16c108 100644 --- a/src/App/Resources/AppResources.Designer.cs +++ b/src/App/Resources/AppResources.Designer.cs @@ -3590,5 +3590,53 @@ namespace Bit.App.Resources { return ResourceManager.GetString("Fido2SomethingWentWrong", resourceCulture); } } + + public static string Fido2AbortError { + get { + return ResourceManager.GetString("Fido2AbortError", resourceCulture); + } + } + + public static string Fido2NetworkFail { + get { + return ResourceManager.GetString("Fido2NetworkFail", resourceCulture); + } + } + + public static string Fido2NoPermission { + get { + return ResourceManager.GetString("Fido2NoPermission", resourceCulture); + } + } + + public static string Fido2NotSupportedError { + get { + return ResourceManager.GetString("Fido2NotSupportedError", resourceCulture); + } + } + + public static string Fido2PrivacyError { + get { + return ResourceManager.GetString("Fido2PrivacyError", resourceCulture); + } + } + + public static string Fido2SecurityError { + get { + return ResourceManager.GetString("Fido2SecurityError", resourceCulture); + } + } + + public static string Fido2ServerDataFail { + get { + return ResourceManager.GetString("Fido2ServerDataFail", resourceCulture); + } + } + + public static string Fido2TimeoutError { + get { + return ResourceManager.GetString("Fido2TimeoutError", resourceCulture); + } + } } } diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx index 5301e085d..a512eff80 100644 --- a/src/App/Resources/AppResources.resx +++ b/src/App/Resources/AppResources.resx @@ -2031,4 +2031,28 @@ Something Went Wrong. Try again. + + Aborted FIDO2 operation. Try again. + + + No internet connection. Try again. + + + Permission was not given. Try again. + + + Unsupported device. + + + Privacy issues encountered. Try again. + + + Security issues encountered. + + + The server returned invalid data. Try again. + + + Timeout for FIDO2. Try again + diff --git a/src/Core/Enums/Fido2CodesTypes.cs b/src/Core/Enums/Fido2CodesTypes.cs new file mode 100644 index 000000000..1bae25d5b --- /dev/null +++ b/src/Core/Enums/Fido2CodesTypes.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Enums +{ + public enum Fido2CodesTypes + { + RequestSignInUser = 994, + RequestRegisterNewKey = 995, + } +} diff --git a/src/Core/Models/Data/Fido2AssertionResponse.cs b/src/Core/Models/Data/Fido2AssertionResponse.cs new file mode 100644 index 000000000..2bc3f2323 --- /dev/null +++ b/src/Core/Models/Data/Fido2AssertionResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2AssertionResponse : Data + { + [JsonProperty("authenticatorData")] + public string AuthenticatorData { get; set; } + [JsonProperty("signature")] + public string Signature { get; set; } + [JsonProperty("clientDataJson")] + public string ClientDataJson { get; set; } + [JsonProperty("userHandle")] + public string UserHandle { get; set; } + } +} diff --git a/src/Core/Models/Data/Fido2AuthenticatorSelection.cs b/src/Core/Models/Data/Fido2AuthenticatorSelection.cs new file mode 100644 index 000000000..03a168bea --- /dev/null +++ b/src/Core/Models/Data/Fido2AuthenticatorSelection.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2AuthenticatorSelection : Data + { + [JsonProperty("authenticatorAttachment")] + public string AuthenticatorAttachment { get; set; } + [JsonProperty("userVerification")] + public string UserVerification { get; set; } + [JsonProperty("requireResidentKey")] + public string RequireResidentKey { get; set; } + } +} diff --git a/src/Core/Models/Data/Fido2CredentialDescriptor.cs b/src/Core/Models/Data/Fido2CredentialDescriptor.cs new file mode 100644 index 000000000..f4f8cb033 --- /dev/null +++ b/src/Core/Models/Data/Fido2CredentialDescriptor.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2CredentialDescriptor : Data + { + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("transports", NullValueHandling = NullValueHandling.Ignore)] + public List Transports { get; set; } + } +} diff --git a/src/Core/Models/Data/Fido2PubKeyCredParam.cs b/src/Core/Models/Data/Fido2PubKeyCredParam.cs new file mode 100644 index 000000000..0d6b59a2f --- /dev/null +++ b/src/Core/Models/Data/Fido2PubKeyCredParam.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2PubKeyCredParam : Data + { + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("alg")] + public int Alg { get; set; } + } +} diff --git a/src/Core/Models/Data/Fido2RP.cs b/src/Core/Models/Data/Fido2RP.cs new file mode 100644 index 000000000..b219e9e63 --- /dev/null +++ b/src/Core/Models/Data/Fido2RP.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2RP : Data + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("icon")] + public string Icon { get; set; } + } +} diff --git a/src/Core/Models/Data/Fido2User.cs b/src/Core/Models/Data/Fido2User.cs new file mode 100644 index 000000000..af1da6a71 --- /dev/null +++ b/src/Core/Models/Data/Fido2User.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Bit.Core.Models.Data +{ + public class Fido2User : Data + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("displayName")] + public string DisplayName { get; set; } + [JsonProperty("icon")] + public string Icon { get; set; } + } +} diff --git a/src/Core/Models/Request/Fido2AuthenticationChallengeRequest.cs b/src/Core/Models/Request/Fido2AuthenticationChallengeRequest.cs new file mode 100644 index 000000000..8405e4f03 --- /dev/null +++ b/src/Core/Models/Request/Fido2AuthenticationChallengeRequest.cs @@ -0,0 +1,19 @@ +using Bit.Core.Models.Data; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Request +{ + public class Fido2AuthenticationChallengeRequest + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("rawId")] + public string RawId { get; set; } + [JsonProperty("response")] + public Fido2AssertionResponse Response { get; set; } + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("extensions", NullValueHandling = NullValueHandling.Ignore)] + public string Extensions { get; set; } + } +} diff --git a/src/Core/Models/Response/Fido2AuthenticationChallengeResponse.cs b/src/Core/Models/Response/Fido2AuthenticationChallengeResponse.cs new file mode 100644 index 000000000..ceb1e0657 --- /dev/null +++ b/src/Core/Models/Response/Fido2AuthenticationChallengeResponse.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Bit.Core.Models.Data; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Response +{ + public class Fido2AuthenticationChallengeResponse + { + [JsonProperty("challenge")] + public string Challenge { get; set; } + [JsonProperty("rpId")] + public string RpId { get; set; } + [JsonProperty("timeout")] + public double Timeout { get; set; } + [JsonProperty("allowCredentials")] + public List AllowCredentials { get; set; } + [JsonProperty("userVerification")] + public string UserVerification { get; set; } + [JsonProperty("extensions", NullValueHandling = NullValueHandling.Ignore)] + public object Extensions { get; set; } + } +} diff --git a/src/Core/Models/Response/Fido2RegistrationChallengeResponse.cs b/src/Core/Models/Response/Fido2RegistrationChallengeResponse.cs new file mode 100644 index 000000000..bbc47cdd6 --- /dev/null +++ b/src/Core/Models/Response/Fido2RegistrationChallengeResponse.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Bit.Core.Models.Data; +using Newtonsoft.Json; + +namespace Bit.Core.Models.Response +{ + public class Fido2RegistrationChallengeResponse + { + [JsonProperty("challenge")] + public string Challenge { get; set; } + [JsonProperty("timeout")] + public double Timeout { get; set; } + [JsonProperty("rp")] + public Fido2RP Rp { get; set; } + [JsonProperty("user")] + public Fido2User User { get; set; } + [JsonProperty("pubKeyCredParams")] + public List PubKeyCredParams { get; set; } + [JsonProperty("excludeCredentials")] + public List ExcludeCredentials { get; set; } + [JsonProperty("authenticatorSelection")] + public Fido2AuthenticatorSelection AuthenticatorSelection { get; set; } + [JsonProperty("attestation", NullValueHandling = NullValueHandling.Ignore)] + public object Attestation { get; set; } + [JsonProperty("extensions", NullValueHandling = NullValueHandling.Ignore)] + public object Extensions { get; set; } + } +}