mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-21 11:25:56 +01:00
Added iOS passkeys integration, warning this branch has lots of logs to ease "debugging" extensions.
This commit is contained in:
parent
e9d1792dd7
commit
18beb4e5f4
@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MauiVersion>8.0.4-nightly.*</MauiVersion>
|
||||
<MauiVersion>8.0.7-nightly.*</MauiVersion>
|
||||
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
|
||||
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
|
||||
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
|
||||
|
@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface IFido2AuthenticatorService
|
||||
{
|
||||
void Init(IFido2UserInterface userInterface);
|
||||
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams);
|
||||
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
|
||||
// TODO: Should this return a List? Or maybe IEnumerable?
|
||||
|
@ -8,10 +8,27 @@ using System.Security.Cryptography;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class Fido2AuthenticatorService(ICipherService _cipherService, ISyncService _syncService, ICryptoFunctionService _cryptoFunctionService, IFido2UserInterface _userInterface) : IFido2AuthenticatorService
|
||||
public class Fido2AuthenticatorService: IFido2AuthenticatorService
|
||||
{
|
||||
// AAGUID: d548826e-79b4-db40-a3d8-11116f7e8349
|
||||
public static readonly byte[] AAGUID = [ 0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49 ];
|
||||
public static readonly byte[] AAGUID = new byte[] { 0xd5, 0x48, 0x82, 0x6e, 0x79, 0xb4, 0xdb, 0x40, 0xa3, 0xd8, 0x11, 0x11, 0x6f, 0x7e, 0x83, 0x49 };
|
||||
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly ISyncService _syncService;
|
||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||
private IFido2UserInterface _userInterface;
|
||||
|
||||
public Fido2AuthenticatorService(ICipherService cipherService, ISyncService syncService, ICryptoFunctionService cryptoFunctionService)
|
||||
{
|
||||
_cipherService = cipherService;
|
||||
_syncService = syncService;
|
||||
_cryptoFunctionService = cryptoFunctionService;
|
||||
}
|
||||
|
||||
public void Init(IFido2UserInterface userInterface)
|
||||
{
|
||||
_userInterface = userInterface;
|
||||
}
|
||||
|
||||
public async Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams)
|
||||
{
|
||||
@ -21,6 +38,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Warning(
|
||||
// $"[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}"
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}");
|
||||
throw new NotSupportedError();
|
||||
}
|
||||
|
||||
@ -34,6 +52,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting due to excluded credential found in vault."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting due to excluded credential found in vault");
|
||||
await _userInterface.InformExcludedCredential(existingCipherIds);
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
@ -51,6 +70,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because user confirmation was not recieved."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because user confirmation was not recieved");
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
@ -65,10 +85,11 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because user verification was unsuccessful");
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
cipher.Login.Fido2Credentials = [fido2Credential];
|
||||
cipher.Login.Fido2Credentials = new List<Fido2CredentialView> { fido2Credential };
|
||||
var reencrypted = await _cipherService.EncryptAsync(cipher);
|
||||
await _cipherService.SaveWithServerAsync(reencrypted);
|
||||
credentialId = fido2Credential.CredentialId;
|
||||
@ -92,10 +113,11 @@ namespace Bit.Core.Services
|
||||
};
|
||||
} catch (NotAllowedError) {
|
||||
throw;
|
||||
} catch (Exception) {
|
||||
} catch (Exception e) {
|
||||
// _logService.Error(
|
||||
// $"[Fido2Authenticator] Unknown error occured during attestation: {e.Message}"
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Unknown error occured during attestation: {e.Message}");
|
||||
|
||||
throw new UnknownError();
|
||||
}
|
||||
@ -121,6 +143,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because no matching credentials were found in the vault."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because no matching credentials were found in the vault");
|
||||
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
@ -151,6 +174,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because the selected credential could not be found."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because the selected credential could not be found");
|
||||
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
@ -160,6 +184,7 @@ namespace Bit.Core.Services
|
||||
// "[Fido2Authenticator] Aborting because user presence was required but not detected."
|
||||
// );
|
||||
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because user presence was required but not detected");
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
@ -167,6 +192,7 @@ namespace Bit.Core.Services
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
||||
// );
|
||||
ClipLogger.Log("[Fido2Authenticator] Aborting because user verification was unsuccessful");
|
||||
|
||||
throw new NotAllowedError();
|
||||
}
|
||||
@ -206,10 +232,11 @@ namespace Bit.Core.Services
|
||||
AuthenticatorData = authenticatorData,
|
||||
Signature = signature
|
||||
};
|
||||
} catch (Exception) {
|
||||
} catch (Exception e) {
|
||||
// _logService.Error(
|
||||
// $"[Fido2Authenticator] Unknown error occured during assertion: {e.Message}"
|
||||
// );
|
||||
ClipLogger.Log($"[Fido2Authenticator] Unknown error occured during assertion: {e.Message}");
|
||||
|
||||
throw new UnknownError();
|
||||
}
|
||||
@ -235,7 +262,7 @@ namespace Bit.Core.Services
|
||||
PublicKeyCredentialDescriptor[] credentials
|
||||
) {
|
||||
if (credentials == null || credentials.Length == 0) {
|
||||
return [];
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var ids = new List<string>();
|
||||
@ -249,7 +276,7 @@ namespace Bit.Core.Services
|
||||
}
|
||||
|
||||
if (ids.Count == 0) {
|
||||
return [];
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||
@ -358,12 +385,12 @@ namespace Bit.Core.Services
|
||||
);
|
||||
authData.Add(flags);
|
||||
|
||||
authData.AddRange([
|
||||
authData.AddRange(new List<byte> {
|
||||
(byte)(counter >> 24),
|
||||
(byte)(counter >> 16),
|
||||
(byte)(counter >> 8),
|
||||
(byte)counter
|
||||
]);
|
||||
});
|
||||
|
||||
if (isAttestation)
|
||||
{
|
||||
|
61
src/Core/Services/Logging/ClipLogger.cs
Normal file
61
src/Core/Services/Logging/ClipLogger.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
#if IOS
|
||||
using UIKit;
|
||||
#endif
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
public class ClipLogger : ILogger
|
||||
{
|
||||
private static readonly StringBuilder _currentBreadcrumbs = new StringBuilder();
|
||||
|
||||
static ILogger _instance;
|
||||
public static ILogger Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance is null)
|
||||
{
|
||||
_instance = new ClipLogger();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
protected ClipLogger()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Log(string breadcrumb)
|
||||
{
|
||||
_currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}");
|
||||
#if IOS
|
||||
UIPasteboard.General.String = _currentBreadcrumbs.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Error(string message, IDictionary<string, string> extraData = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
var classAndMethod = $"{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}";
|
||||
var filePathAndLineNumber = $"{Path.GetFileName(sourceFilePath)}:{sourceLineNumber}";
|
||||
var properties = new Dictionary<string, string>
|
||||
{
|
||||
["File"] = filePathAndLineNumber,
|
||||
["Method"] = memberName
|
||||
};
|
||||
|
||||
Log(message ?? $"Error found in: {classAndMethod}, {filePathAndLineNumber}");
|
||||
}
|
||||
|
||||
public void Exception(Exception ex) => Log(ex?.ToString());
|
||||
|
||||
public Task InitAsync() => Task.CompletedTask;
|
||||
|
||||
public Task<bool> IsEnabled() => Task.FromResult(true);
|
||||
|
||||
public Task SetEnabled(bool value) => Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
@ -22,10 +22,23 @@ namespace Bit.Core.Services
|
||||
#if !FDROID
|
||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
||||
// we need to track the error as well
|
||||
Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
//Microsoft.AppCenter.Crashes.Crashes.TrackError(ex);
|
||||
ClipLogger.Log(ex?.ToString());
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogBreadcrumb(string breadcrumb)
|
||||
{
|
||||
if (ServiceContainer.Resolve<ILogger>("logger", true) is ILogger logger)
|
||||
{
|
||||
logger.Error(breadcrumb);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClipLogger.Log(breadcrumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,31 @@
|
||||
using System.Threading.Tasks;
|
||||
using AuthenticationServices;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.iOS.Core.Utilities;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost
|
||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost, IFido2UserInterface
|
||||
{
|
||||
private IFido2AuthenticatorService _fido2AuthService;
|
||||
private IFido2AuthenticatorService Fido2AuthService
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fido2AuthService is null)
|
||||
{
|
||||
_fido2AuthService = ServiceContainer.Resolve<IFido2AuthenticatorService>();
|
||||
_fido2AuthService.Init(this);
|
||||
}
|
||||
return _fido2AuthService;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasskeyCredentialRequest passkeyCredentialRequest)
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
@ -25,7 +41,7 @@ namespace Bit.iOS.Autofill
|
||||
await ProvideCredentialAsync(false);
|
||||
}
|
||||
|
||||
public async Task CompleteAssertionRequestAsync(CipherView cipherView)
|
||||
public async Task CompleteAssertionRequestAsync(string rpId, NSData userHandleData, NSData credentialIdData, string cipherId)
|
||||
{
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||
{
|
||||
@ -33,22 +49,47 @@ namespace Bit.iOS.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
// // TODO: Generate the credential Signature and Auth data accordingly
|
||||
// var fido2AssertionResult = await _fido2AuthService.Value.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
||||
// {
|
||||
// RpId = cipherView.Login.MainFido2Credential.RpId,
|
||||
// Counter = cipherView.Login.MainFido2Credential.Counter,
|
||||
// CredentialId = cipherView.Login.MainFido2Credential.CredentialId
|
||||
// });
|
||||
if (_context.PasskeyCredentialRequest is null)
|
||||
{
|
||||
OnProvidingCredentialException(new InvalidOperationException("Trying to complete assertion request without a PasskeyCredentialRequest"));
|
||||
return;
|
||||
}
|
||||
|
||||
// CompleteAssertionRequest(new ASPasskeyAssertionCredential(
|
||||
// cipherView.Login.MainFido2Credential.UserHandle,
|
||||
// cipherView.Login.MainFido2Credential.RpId,
|
||||
// NSData.FromArray(fido2AssertionResult.Signature),
|
||||
// _context.PasskeyCredentialRequest?.ClientDataHash,
|
||||
// NSData.FromArray(fido2AssertionResult.AuthenticatorData),
|
||||
// cipherView.Login.MainFido2Credential.CredentialId
|
||||
// ));
|
||||
try
|
||||
{
|
||||
var fido2AssertionResult = await Fido2AuthService.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
||||
{
|
||||
RpId = rpId,
|
||||
ClientDataHash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
|
||||
RequireUserVerification = _context.PasskeyCredentialRequest.UserVerificationPreference == "required",
|
||||
RequireUserPresence = false,
|
||||
AllowCredentialDescriptorList = new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor[]
|
||||
{
|
||||
new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor { Id = credentialIdData.ToByteArray() }
|
||||
}
|
||||
});
|
||||
|
||||
var selectedUserHandleData = fido2AssertionResult.SelectedCredential != null
|
||||
? NSData.FromArray(fido2AssertionResult.SelectedCredential.UserHandle)
|
||||
: (NSData)userHandleData;
|
||||
|
||||
var selectedCredentialIdData = fido2AssertionResult.SelectedCredential != null
|
||||
? new Guid(fido2AssertionResult.SelectedCredential.Id).ToString()
|
||||
: credentialIdData;
|
||||
|
||||
CompleteAssertionRequest(new ASPasskeyAssertionCredential(
|
||||
selectedUserHandleData,
|
||||
rpId,
|
||||
NSData.FromArray(fido2AssertionResult.Signature),
|
||||
_context.PasskeyCredentialRequest.ClientDataHash,
|
||||
NSData.FromArray(fido2AssertionResult.AuthenticatorData),
|
||||
selectedCredentialIdData
|
||||
));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential)
|
||||
@ -71,6 +112,32 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
return _context.PasskeyCredentialRequest != null && !cipherView.Login.HasFido2Credentials;
|
||||
}
|
||||
|
||||
// IFido2UserInterface
|
||||
|
||||
public Task<Fido2PickCredentialResult> PickCredentialAsync(Fido2PickCredentialParams pickCredentialParams)
|
||||
{
|
||||
return Task.FromResult(new Fido2PickCredentialResult());
|
||||
}
|
||||
|
||||
public Task InformExcludedCredential(string[] existingCipherIds)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<Fido2ConfirmNewCredentialResult> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
|
||||
{
|
||||
return Task.FromResult(new Fido2ConfirmNewCredentialResult());
|
||||
}
|
||||
|
||||
public async Task EnsureUnlockedVaultAsync()
|
||||
{
|
||||
if (!await IsAuthed() || await IsLocked())
|
||||
{
|
||||
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
|
||||
throw new InvalidOperationException("Not authed or locked");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using UIKit;
|
||||
using static CoreFoundation.DispatchSource;
|
||||
using static Microsoft.Maui.ApplicationModel.Permissions;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
@ -31,7 +33,6 @@ namespace Bit.iOS.Autofill
|
||||
private IAccountsManager _accountsManager;
|
||||
|
||||
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
||||
private readonly LazyResolve<IFido2AuthenticationService> _fido2AuthService = new LazyResolve<IFido2AuthenticationService>();
|
||||
|
||||
public CredentialProviderViewController(IntPtr handle)
|
||||
: base(handle)
|
||||
@ -45,8 +46,12 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("ViewDidLoad");
|
||||
|
||||
InitAppIfNeeded();
|
||||
|
||||
ClipLogger.Log("Inited");
|
||||
|
||||
base.ViewDidLoad();
|
||||
|
||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||
@ -56,9 +61,11 @@ namespace Bit.iOS.Autofill
|
||||
ExtContext = ExtensionContext
|
||||
};
|
||||
|
||||
ClipLogger.Log("ViewDidLoad completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClipLogger.Log($"ViewDidLoad ex: {ex}");
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
@ -67,6 +74,7 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers");
|
||||
InitAppIfNeeded();
|
||||
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||
if (serviceIdentifiers.Length > 0)
|
||||
@ -104,22 +112,116 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
public override async void ProvideCredentialWithoutUserInteraction(IASCredentialRequest credentialRequest)
|
||||
[Export("prepareCredentialListForServiceIdentifiers:requestParameters:")]
|
||||
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers, ASPasskeyCredentialRequestParameters requestParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (credentialRequest)
|
||||
ClipLogger.Log("PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers, ASPasskeyCredentialRequestParameters requestParameters");
|
||||
InitAppIfNeeded();
|
||||
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||
if (serviceIdentifiers.Length > 0)
|
||||
{
|
||||
case ASPasswordCredentialRequest passwordRequest:
|
||||
await ProvideCredentialWithoutUserInteractionAsync(passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
var uri = serviceIdentifiers[0].Identifier;
|
||||
if (serviceIdentifiers[0].Type == ASCredentialServiceIdentifierType.Domain)
|
||||
{
|
||||
uri = string.Concat("https://", uri);
|
||||
}
|
||||
_context.UrlString = uri;
|
||||
}
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
}
|
||||
else if (await IsLocked())
|
||||
{
|
||||
PerformSegue("lockPasswordSegue", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||
{
|
||||
PerformSegue("loginSearchSegue", this);
|
||||
}
|
||||
else
|
||||
{
|
||||
PerformSegue("loginListSegue", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
[Export("provideCredentialWithoutUserInteractionForRequest:")]
|
||||
public override async void ProvideCredentialWithoutUserInteraction(IASCredentialRequest credentialRequest)
|
||||
{
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
ClipLogger.Log("ProvideCredentialWithoutUserInteraction(IASCredentialRequest credentialRequest");
|
||||
ClipLogger.Log($"PCWUI(IASC) -> R: {credentialRequest?.GetType().FullName}");
|
||||
ClipLogger.Log($"PCWUI(IASC) -> I: {credentialRequest?.CredentialIdentity?.GetType().FullName}");
|
||||
|
||||
var crType = credentialRequest.GetType();
|
||||
foreach (var item in crType.GetProperties())
|
||||
{
|
||||
ClipLogger.Log($"PCWUI(IASC) -> R -> {item.Name} -- {item.PropertyType}");
|
||||
}
|
||||
|
||||
var ciType = credentialRequest.CredentialIdentity.GetType();
|
||||
foreach (var item in ciType.GetProperties())
|
||||
{
|
||||
ClipLogger.Log($"PCWUI(IASC) -> I -> {item.Name} -- {item.PropertyType}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var cc = (ASPasskeyCredentialRequest)credentialRequest;
|
||||
ClipLogger.Log($"PCWUI(IASC) -> R -> Force cast {cc}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClipLogger.Log($"PCWUI(IASC) -> R -> Force cast bad - {ex}");
|
||||
}
|
||||
|
||||
|
||||
switch (credentialRequest?.Type)
|
||||
{
|
||||
case ASCredentialRequestType.Password:
|
||||
ClipLogger.Log($"PCWUI(IASC) -> Type P {credentialRequest.CredentialIdentity}");
|
||||
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
break;
|
||||
case ASPasskeyCredentialRequest passkeyRequest:
|
||||
await ProvideCredentialWithoutUserInteractionAsync(passkeyRequest);
|
||||
case ASCredentialRequestType.PasskeyAssertion:
|
||||
var bpa = credentialRequest is ASPasskeyCredentialRequest;
|
||||
ClipLogger.Log($"PCWUI(IASC) -> Type PA {bpa}");
|
||||
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest as ASPasskeyCredentialRequest);
|
||||
break;
|
||||
default:
|
||||
ClipLogger.Log($"PCWUI(IASC) -> Type not P nor PA");
|
||||
CancelRequest(ASExtensionErrorCode.Failed);
|
||||
break;
|
||||
}
|
||||
|
||||
//switch (credentialRequest)
|
||||
//{
|
||||
// case ASPasswordCredentialRequest passwordRequest:
|
||||
// await ProvideCredentialWithoutUserInteractionAsync(passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
// break;
|
||||
// case ASPasskeyCredentialRequest passkeyRequest:
|
||||
// await ProvideCredentialWithoutUserInteractionAsync(passkeyRequest);
|
||||
// break;
|
||||
// default:
|
||||
// CancelRequest(ASExtensionErrorCode.Failed);
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -127,86 +229,89 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProvideCredentialWithoutUserInteractionAsync(credentialIdentity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||
if (!await IsAuthed() || await IsLocked())
|
||||
{
|
||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||
ExtensionContext.CancelRequest(err);
|
||||
return;
|
||||
}
|
||||
_context.PasswordCredentialIdentity = credentialIdentity;
|
||||
await ProvideCredentialAsync(false);
|
||||
}
|
||||
//public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// ClipLogger.Log("ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity");
|
||||
// await ProvideCredentialWithoutUserInteractionAsync(credentialIdentity);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// OnProvidingCredentialException(ex);
|
||||
// }
|
||||
//}
|
||||
|
||||
[Export("prepareInterfaceToProvideCredentialForRequest:")]
|
||||
public override async void PrepareInterfaceToProvideCredential(IASCredentialRequest credentialRequest)
|
||||
{
|
||||
try
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||
{
|
||||
switch (credentialRequest)
|
||||
{
|
||||
case ASPasswordCredentialRequest passwordRequest:
|
||||
PrepareInterfaceToProvideCredential(passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
break;
|
||||
case ASPasskeyCredentialRequest passkeyRequest:
|
||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = passkeyRequest);
|
||||
break;
|
||||
default:
|
||||
ExtensionContext?.CancelRequest(new NSError(ASExtensionErrorCodeExtensions.GetDomain(ASExtensionErrorCode.Failed), (int)ASExtensionErrorCode.Failed));
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
try
|
||||
{
|
||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialIdentity);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PrepareInterfaceToProvideCredentialAsync(Action<Context> updateContext)
|
||||
{
|
||||
InitAppIfNeeded();
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
return;
|
||||
}
|
||||
updateContext(_context);
|
||||
await CheckLockAsync(async () => await ProvideCredentialAsync());
|
||||
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("PrepareInterfaceToProvideCredential(IASCredentialRequest credentialRequest");
|
||||
ClipLogger.Log($"PITPC(IASCR) -> R: {credentialRequest?.GetType().FullName}");
|
||||
ClipLogger.Log($"PITPC(IASCR) -> I: {credentialRequest?.CredentialIdentity?.GetType().FullName}");
|
||||
|
||||
switch (credentialRequest?.Type)
|
||||
{
|
||||
case ASCredentialRequestType.Password:
|
||||
ClipLogger.Log($"PITPC(IASCR) -> Type P {credentialRequest.CredentialIdentity}");
|
||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
break;
|
||||
case ASCredentialRequestType.PasskeyAssertion:
|
||||
var bpa = credentialRequest is ASPasskeyCredentialRequest;
|
||||
ClipLogger.Log($"PITPC(IASCR) -> Type PA {bpa}");
|
||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = credentialRequest as ASPasskeyCredentialRequest);
|
||||
break;
|
||||
default:
|
||||
ClipLogger.Log($"PITPC(IASCR) -> Type not P nor PA");
|
||||
CancelRequest(ASExtensionErrorCode.Failed);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
//switch (credentialRequest)
|
||||
//{
|
||||
// case ASPasswordCredentialRequest passwordRequest:
|
||||
// //PrepareInterfaceToProvideCredential(passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
// await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
||||
// break;
|
||||
// case ASPasskeyCredentialRequest passkeyRequest:
|
||||
// await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = passkeyRequest);
|
||||
// break;
|
||||
// default:
|
||||
// CancelRequest(ASExtensionErrorCode.Failed);
|
||||
// break;
|
||||
//}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
//public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// ClipLogger.Log("PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity");
|
||||
// await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialIdentity);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// OnProvidingCredentialException(ex);
|
||||
// }
|
||||
//}
|
||||
|
||||
public override async void PrepareInterfaceForExtensionConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("PrepareInterfaceForExtensionConfiguration");
|
||||
InitAppIfNeeded();
|
||||
_context.Configuring = true;
|
||||
if (!await IsAuthed())
|
||||
@ -221,10 +326,41 @@ namespace Bit.iOS.Autofill
|
||||
OnProvidingCredentialException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasswordCredentialIdentity credentialIdentity)
|
||||
{
|
||||
ClipLogger.Log("ProvideCredentialWithoutUserInteractionAsync(ASPasswordCredentialIdentity credentialIdentity");
|
||||
InitAppIfNeeded();
|
||||
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
|
||||
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
|
||||
if (!await IsAuthed() || await IsLocked())
|
||||
{
|
||||
var err = new NSError(new NSString("ASExtensionErrorDomain"),
|
||||
Convert.ToInt32(ASExtensionErrorCode.UserInteractionRequired), null);
|
||||
ExtensionContext.CancelRequest(err);
|
||||
return;
|
||||
}
|
||||
_context.PasswordCredentialIdentity = credentialIdentity;
|
||||
await ProvideCredentialAsync(false);
|
||||
}
|
||||
|
||||
private async Task PrepareInterfaceToProvideCredentialAsync(Action<Context> updateContext)
|
||||
{
|
||||
ClipLogger.Log("PrepareInterfaceToProvideCredentialAsync(Action<Context> updateContext");
|
||||
InitAppIfNeeded();
|
||||
if (!await IsAuthed())
|
||||
{
|
||||
await _accountsManager.NavigateOnAccountChangeAsync(false);
|
||||
return;
|
||||
}
|
||||
updateContext(_context);
|
||||
await CheckLockAsync(async () => await ProvideCredentialAsync());
|
||||
}
|
||||
|
||||
public void CompleteRequest(string id = null, string username = null,
|
||||
string password = null, string totp = null)
|
||||
{
|
||||
ClipLogger.Log("CompleteRequest");
|
||||
if ((_context?.Configuring ?? true) && string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
ServiceContainer.Reset();
|
||||
@ -261,13 +397,13 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private void OnProvidingCredentialException(Exception ex)
|
||||
{
|
||||
//LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
UIPasteboard.General.String = ex.ToString();
|
||||
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||
CancelRequest(ASExtensionErrorCode.Failed);
|
||||
}
|
||||
|
||||
private void CancelRequest(ASExtensionErrorCode code)
|
||||
{
|
||||
ClipLogger.Log("CancelRequest" + code);
|
||||
//var err = new NSError(new NSString("ASExtensionErrorDomain"), Convert.ToInt32(code), null);
|
||||
var err = new NSError(ASExtensionErrorCodeExtensions.GetDomain(code), (int)code);
|
||||
ExtensionContext?.CancelRequest(err);
|
||||
@ -277,6 +413,7 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("Preparing for Segue");
|
||||
if (segue.DestinationViewController is UINavigationController navController)
|
||||
{
|
||||
if (navController.TopViewController is LoginListViewController listLoginController)
|
||||
@ -315,13 +452,15 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
public async void DismissLockAndContinue()
|
||||
public void DismissLockAndContinue()
|
||||
{
|
||||
ClipLogger.Log("DismissLockAndContinue");
|
||||
DismissViewController(false, async () => await OnLockDismissedAsync());
|
||||
}
|
||||
|
||||
private void NavigateToPage(ContentPage page)
|
||||
{
|
||||
ClipLogger.Log("NavigateToPage" + page.GetType().FullName);
|
||||
var navigationPage = new NavigationPage(page);
|
||||
var uiController = navigationPage.ToUIViewController(MauiContextSingleton.Instance.MauiContext);
|
||||
uiController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||
@ -333,23 +472,28 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_context.PasswordCredentialIdentity != null)
|
||||
ClipLogger.Log("OnLockDismissedAsync");
|
||||
if (_context.PasswordCredentialIdentity != null || _context.IsPasskey)
|
||||
{
|
||||
ClipLogger.Log("OnLockDismissedAsync -> ProvideCredentialAsync");
|
||||
await MainThread.InvokeOnMainThreadAsync(() => ProvideCredentialAsync());
|
||||
return;
|
||||
}
|
||||
if (_context.Configuring)
|
||||
{
|
||||
ClipLogger.Log("OnLockDismissedAsync -> Configuring");
|
||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("setupSegue", this));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||
{
|
||||
ClipLogger.Log("OnLockDismissedAsync -> loginSearchSegue");
|
||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginSearchSegue", this));
|
||||
}
|
||||
else
|
||||
{
|
||||
ClipLogger.Log("OnLockDismissedAsync -> loginListSegue");
|
||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginListSegue", this));
|
||||
}
|
||||
}
|
||||
@ -363,14 +507,27 @@ namespace Bit.iOS.Autofill
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipLogger.Log("ProvideCredentialAsync");
|
||||
if (!ServiceContainer.TryResolve<ICipherService>(out var cipherService)
|
||||
||
|
||||
_context.RecordIdentifier == null)
|
||||
{
|
||||
ClipLogger.Log("ProvideCredentialAsync -> CredentialIdentityNotFound");
|
||||
CancelRequest(ASExtensionErrorCode.CredentialIdentityNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_context.IsPasskey)
|
||||
{
|
||||
ClipLogger.Log("ProvideCredentialAsync -> IsPasskey");
|
||||
await CompleteAssertionRequestAsync(_context.PasskeyCredentialIdentity.RelyingPartyIdentifier,
|
||||
_context.PasskeyCredentialIdentity.UserHandle,
|
||||
_context.PasskeyCredentialIdentity.CredentialId,
|
||||
_context.RecordIdentifier);
|
||||
return;
|
||||
}
|
||||
|
||||
ClipLogger.Log("ProvideCredentialAsync -> IsPassword");
|
||||
var cipher = await cipherService.GetAsync(_context.RecordIdentifier);
|
||||
if (cipher?.Login is null || cipher.Type != CipherType.Login)
|
||||
{
|
||||
@ -410,12 +567,6 @@ namespace Bit.iOS.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
if (_context.IsPasskey)
|
||||
{
|
||||
await CompleteAssertionRequestAsync(decCipher);
|
||||
return;
|
||||
}
|
||||
|
||||
string totpCode = null;
|
||||
if (await _stateService.Value.GetDisableAutoTotpCopyAsync() != true)
|
||||
{
|
||||
@ -437,6 +588,7 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private async Task CheckLockAsync(Action notLockedAction)
|
||||
{
|
||||
ClipLogger.Log("CheckLockAsync");
|
||||
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||
{
|
||||
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
|
||||
@ -460,6 +612,7 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private void LogoutIfAuthed()
|
||||
{
|
||||
ClipLogger.Log("LogoutIfAuthed");
|
||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||
{
|
||||
try
|
||||
@ -482,12 +635,14 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
private void InitApp()
|
||||
{
|
||||
ClipLogger.Log("InitApp");
|
||||
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey,
|
||||
_nfcSession, out _nfcDelegate, out _accountsManager);
|
||||
}
|
||||
|
||||
private void InitAppIfNeeded()
|
||||
{
|
||||
ClipLogger.Log("InitAppIfNeeded");
|
||||
if (ServiceContainer.RegisteredServices == null || ServiceContainer.RegisteredServices.Count == 0)
|
||||
{
|
||||
InitApp();
|
||||
@ -685,6 +840,7 @@ namespace Bit.iOS.Autofill
|
||||
|
||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||
{
|
||||
ClipLogger.Log("Navigate" + navTarget);
|
||||
switch (navTarget)
|
||||
{
|
||||
case NavigationTarget.HomeLogin:
|
||||
|
15
src/iOS.Core/Utilities/NSDataExtensions.cs
Normal file
15
src/iOS.Core/Utilities/NSDataExtensions.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Foundation;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
{
|
||||
public static class NSDataExtensions
|
||||
{
|
||||
public static byte[] ToByteArray(this NSData data)
|
||||
{
|
||||
var bytes = new byte[data.Length];
|
||||
Marshal.Copy(data.Bytes, bytes, 0, Convert.ToInt32(data.Length));
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -123,7 +123,7 @@ namespace Bit.iOS.Core.Utilities
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
logger = DebugLogger.Instance;
|
||||
logger = ClipLogger.Instance;
|
||||
#else
|
||||
logger = Logger.Instance;
|
||||
#endif
|
||||
@ -138,6 +138,7 @@ namespace Bit.iOS.Core.Utilities
|
||||
logger!.Exception(nreAppGroupContainer);
|
||||
throw nreAppGroupContainer;
|
||||
}
|
||||
|
||||
var liteDbStorage = new LiteDbStorageService(
|
||||
Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db"));
|
||||
var localizeService = new LocalizeService();
|
||||
@ -189,6 +190,11 @@ namespace Bit.iOS.Core.Utilities
|
||||
|
||||
public static void RegisterFinallyBeforeBootstrap()
|
||||
{
|
||||
ServiceContainer.Register<IFido2AuthenticatorService>(new Fido2AuthenticatorService(
|
||||
ServiceContainer.Resolve<ICipherService>(),
|
||||
ServiceContainer.Resolve<ISyncService>(),
|
||||
ServiceContainer.Resolve<ICryptoFunctionService>()));
|
||||
|
||||
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
|
||||
ServiceContainer.Resolve<IEnvironmentService>(),
|
||||
ServiceContainer.Resolve<IStateService>(),
|
||||
|
Loading…
Reference in New Issue
Block a user