mirror of
https://github.com/bitwarden/mobile
synced 2025-12-10 13:23:39 +00:00
Added iOS passkeys integration, warning this branch has lots of logs to ease "debugging" extensions.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MauiVersion>8.0.4-nightly.*</MauiVersion>
|
<MauiVersion>8.0.7-nightly.*</MauiVersion>
|
||||||
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
|
<ReleaseCodesignProvision>Automatic:AppStore</ReleaseCodesignProvision>
|
||||||
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
|
<ReleaseCodesignKey>iPhone Distribution</ReleaseCodesignKey>
|
||||||
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
|
<IncludeBitwardeniOSExtensions>True</IncludeBitwardeniOSExtensions>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IFido2AuthenticatorService
|
public interface IFido2AuthenticatorService
|
||||||
{
|
{
|
||||||
|
void Init(IFido2UserInterface userInterface);
|
||||||
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams);
|
Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams);
|
||||||
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
|
Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams);
|
||||||
// TODO: Should this return a List? Or maybe IEnumerable?
|
// TODO: Should this return a List? Or maybe IEnumerable?
|
||||||
|
|||||||
@@ -8,10 +8,27 @@ using System.Security.Cryptography;
|
|||||||
|
|
||||||
namespace Bit.Core.Services
|
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
|
// 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)
|
public async Task<Fido2AuthenticatorMakeCredentialResult> MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams)
|
||||||
{
|
{
|
||||||
@@ -21,6 +38,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Warning(
|
// _logService.Warning(
|
||||||
// $"[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}"
|
// $"[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}"
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] No compatible algorithms found, RP requested: {requestedAlgorithms}");
|
||||||
throw new NotSupportedError();
|
throw new NotSupportedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +52,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting due to excluded credential found in vault."
|
// "[Fido2Authenticator] Aborting due to excluded credential found in vault."
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Aborting due to excluded credential found in vault");
|
||||||
await _userInterface.InformExcludedCredential(existingCipherIds);
|
await _userInterface.InformExcludedCredential(existingCipherIds);
|
||||||
throw new NotAllowedError();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
@@ -51,6 +70,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting because user confirmation was not recieved."
|
// "[Fido2Authenticator] Aborting because user confirmation was not recieved."
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Aborting because user confirmation was not recieved");
|
||||||
throw new NotAllowedError();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +85,11 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Aborting because user verification was unsuccessful");
|
||||||
throw new NotAllowedError();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
cipher.Login.Fido2Credentials = [fido2Credential];
|
cipher.Login.Fido2Credentials = new List<Fido2CredentialView> { fido2Credential };
|
||||||
var reencrypted = await _cipherService.EncryptAsync(cipher);
|
var reencrypted = await _cipherService.EncryptAsync(cipher);
|
||||||
await _cipherService.SaveWithServerAsync(reencrypted);
|
await _cipherService.SaveWithServerAsync(reencrypted);
|
||||||
credentialId = fido2Credential.CredentialId;
|
credentialId = fido2Credential.CredentialId;
|
||||||
@@ -92,10 +113,11 @@ namespace Bit.Core.Services
|
|||||||
};
|
};
|
||||||
} catch (NotAllowedError) {
|
} catch (NotAllowedError) {
|
||||||
throw;
|
throw;
|
||||||
} catch (Exception) {
|
} catch (Exception e) {
|
||||||
// _logService.Error(
|
// _logService.Error(
|
||||||
// $"[Fido2Authenticator] Unknown error occured during attestation: {e.Message}"
|
// $"[Fido2Authenticator] Unknown error occured during attestation: {e.Message}"
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Unknown error occured during attestation: {e.Message}");
|
||||||
|
|
||||||
throw new UnknownError();
|
throw new UnknownError();
|
||||||
}
|
}
|
||||||
@@ -121,6 +143,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting because no matching credentials were found in the vault."
|
// "[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();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
@@ -151,6 +174,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting because the selected credential could not be found."
|
// "[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();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
@@ -160,6 +184,7 @@ namespace Bit.Core.Services
|
|||||||
// "[Fido2Authenticator] Aborting because user presence was required but not detected."
|
// "[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();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +192,7 @@ namespace Bit.Core.Services
|
|||||||
// _logService.Info(
|
// _logService.Info(
|
||||||
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Aborting because user verification was unsuccessful");
|
||||||
|
|
||||||
throw new NotAllowedError();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
@@ -206,10 +232,11 @@ namespace Bit.Core.Services
|
|||||||
AuthenticatorData = authenticatorData,
|
AuthenticatorData = authenticatorData,
|
||||||
Signature = signature
|
Signature = signature
|
||||||
};
|
};
|
||||||
} catch (Exception) {
|
} catch (Exception e) {
|
||||||
// _logService.Error(
|
// _logService.Error(
|
||||||
// $"[Fido2Authenticator] Unknown error occured during assertion: {e.Message}"
|
// $"[Fido2Authenticator] Unknown error occured during assertion: {e.Message}"
|
||||||
// );
|
// );
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] Unknown error occured during assertion: {e.Message}");
|
||||||
|
|
||||||
throw new UnknownError();
|
throw new UnknownError();
|
||||||
}
|
}
|
||||||
@@ -235,7 +262,7 @@ namespace Bit.Core.Services
|
|||||||
PublicKeyCredentialDescriptor[] credentials
|
PublicKeyCredentialDescriptor[] credentials
|
||||||
) {
|
) {
|
||||||
if (credentials == null || credentials.Length == 0) {
|
if (credentials == null || credentials.Length == 0) {
|
||||||
return [];
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var ids = new List<string>();
|
var ids = new List<string>();
|
||||||
@@ -249,7 +276,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ids.Count == 0) {
|
if (ids.Count == 0) {
|
||||||
return [];
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
@@ -358,12 +385,12 @@ namespace Bit.Core.Services
|
|||||||
);
|
);
|
||||||
authData.Add(flags);
|
authData.Add(flags);
|
||||||
|
|
||||||
authData.AddRange([
|
authData.AddRange(new List<byte> {
|
||||||
(byte)(counter >> 24),
|
(byte)(counter >> 24),
|
||||||
(byte)(counter >> 16),
|
(byte)(counter >> 16),
|
||||||
(byte)(counter >> 8),
|
(byte)(counter >> 8),
|
||||||
(byte)counter
|
(byte)counter
|
||||||
]);
|
});
|
||||||
|
|
||||||
if (isAttestation)
|
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;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
@@ -22,10 +22,23 @@ namespace Bit.Core.Services
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
// just in case the caller throws the exception in a moment where the logger can't be resolved
|
// 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
|
// 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
|
#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 System.Threading.Tasks;
|
||||||
using AuthenticationServices;
|
using AuthenticationServices;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.iOS.Core.Utilities;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
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)
|
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasskeyCredentialRequest passkeyCredentialRequest)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
@@ -25,7 +41,7 @@ namespace Bit.iOS.Autofill
|
|||||||
await ProvideCredentialAsync(false);
|
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))
|
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||||
{
|
{
|
||||||
@@ -33,22 +49,47 @@ namespace Bit.iOS.Autofill
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // TODO: Generate the credential Signature and Auth data accordingly
|
if (_context.PasskeyCredentialRequest is null)
|
||||||
// var fido2AssertionResult = await _fido2AuthService.Value.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
{
|
||||||
// {
|
OnProvidingCredentialException(new InvalidOperationException("Trying to complete assertion request without a PasskeyCredentialRequest"));
|
||||||
// RpId = cipherView.Login.MainFido2Credential.RpId,
|
return;
|
||||||
// Counter = cipherView.Login.MainFido2Credential.Counter,
|
}
|
||||||
// CredentialId = cipherView.Login.MainFido2Credential.CredentialId
|
|
||||||
// });
|
|
||||||
|
|
||||||
// CompleteAssertionRequest(new ASPasskeyAssertionCredential(
|
try
|
||||||
// cipherView.Login.MainFido2Credential.UserHandle,
|
{
|
||||||
// cipherView.Login.MainFido2Credential.RpId,
|
var fido2AssertionResult = await Fido2AuthService.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
||||||
// NSData.FromArray(fido2AssertionResult.Signature),
|
{
|
||||||
// _context.PasskeyCredentialRequest?.ClientDataHash,
|
RpId = rpId,
|
||||||
// NSData.FromArray(fido2AssertionResult.AuthenticatorData),
|
ClientDataHash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
|
||||||
// cipherView.Login.MainFido2Credential.CredentialId
|
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)
|
public void CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential)
|
||||||
@@ -71,6 +112,32 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
return _context.PasskeyCredentialRequest != null && !cipherView.Login.HasFido2Credentials;
|
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.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using static CoreFoundation.DispatchSource;
|
||||||
|
using static Microsoft.Maui.ApplicationModel.Permissions;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
@@ -31,7 +33,6 @@ namespace Bit.iOS.Autofill
|
|||||||
private IAccountsManager _accountsManager;
|
private IAccountsManager _accountsManager;
|
||||||
|
|
||||||
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
|
||||||
private readonly LazyResolve<IFido2AuthenticationService> _fido2AuthService = new LazyResolve<IFido2AuthenticationService>();
|
|
||||||
|
|
||||||
public CredentialProviderViewController(IntPtr handle)
|
public CredentialProviderViewController(IntPtr handle)
|
||||||
: base(handle)
|
: base(handle)
|
||||||
@@ -45,8 +46,12 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("ViewDidLoad");
|
||||||
|
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
|
|
||||||
|
ClipLogger.Log("Inited");
|
||||||
|
|
||||||
base.ViewDidLoad();
|
base.ViewDidLoad();
|
||||||
|
|
||||||
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
Logo.Image = new UIImage(ThemeHelpers.LightTheme ? "logo.png" : "logo_white.png");
|
||||||
@@ -56,9 +61,11 @@ namespace Bit.iOS.Autofill
|
|||||||
ExtContext = ExtensionContext
|
ExtContext = ExtensionContext
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ClipLogger.Log("ViewDidLoad completed");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log($"ViewDidLoad ex: {ex}");
|
||||||
OnProvidingCredentialException(ex);
|
OnProvidingCredentialException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +74,7 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers");
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
_context.ServiceIdentifiers = serviceIdentifiers;
|
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||||
if (serviceIdentifiers.Length > 0)
|
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
|
try
|
||||||
{
|
{
|
||||||
switch (credentialRequest)
|
ClipLogger.Log("PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers, ASPasskeyCredentialRequestParameters requestParameters");
|
||||||
|
InitAppIfNeeded();
|
||||||
|
_context.ServiceIdentifiers = serviceIdentifiers;
|
||||||
|
if (serviceIdentifiers.Length > 0)
|
||||||
{
|
{
|
||||||
case ASPasswordCredentialRequest passwordRequest:
|
var uri = serviceIdentifiers[0].Identifier;
|
||||||
await ProvideCredentialWithoutUserInteractionAsync(passwordRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
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;
|
break;
|
||||||
case ASPasskeyCredentialRequest passkeyRequest:
|
case ASCredentialRequestType.PasskeyAssertion:
|
||||||
await ProvideCredentialWithoutUserInteractionAsync(passkeyRequest);
|
var bpa = credentialRequest is ASPasskeyCredentialRequest;
|
||||||
|
ClipLogger.Log($"PCWUI(IASC) -> Type PA {bpa}");
|
||||||
|
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest as ASPasskeyCredentialRequest);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
ClipLogger.Log($"PCWUI(IASC) -> Type not P nor PA");
|
||||||
CancelRequest(ASExtensionErrorCode.Failed);
|
CancelRequest(ASExtensionErrorCode.Failed);
|
||||||
break;
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -127,86 +229,89 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
//public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
|
||||||
{
|
//{
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
await ProvideCredentialWithoutUserInteractionAsync(credentialIdentity);
|
// ClipLogger.Log("ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity");
|
||||||
}
|
// await ProvideCredentialWithoutUserInteractionAsync(credentialIdentity);
|
||||||
catch (Exception ex)
|
// }
|
||||||
{
|
// catch (Exception ex)
|
||||||
OnProvidingCredentialException(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[Export("prepareInterfaceToProvideCredentialForRequest:")]
|
||||||
public override async void PrepareInterfaceToProvideCredential(IASCredentialRequest credentialRequest)
|
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;
|
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()
|
public override async void PrepareInterfaceForExtensionConfiguration()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("PrepareInterfaceForExtensionConfiguration");
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
_context.Configuring = true;
|
_context.Configuring = true;
|
||||||
if (!await IsAuthed())
|
if (!await IsAuthed())
|
||||||
@@ -221,10 +326,41 @@ namespace Bit.iOS.Autofill
|
|||||||
OnProvidingCredentialException(ex);
|
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,
|
public void CompleteRequest(string id = null, string username = null,
|
||||||
string password = null, string totp = null)
|
string password = null, string totp = null)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("CompleteRequest");
|
||||||
if ((_context?.Configuring ?? true) && string.IsNullOrWhiteSpace(password))
|
if ((_context?.Configuring ?? true) && string.IsNullOrWhiteSpace(password))
|
||||||
{
|
{
|
||||||
ServiceContainer.Reset();
|
ServiceContainer.Reset();
|
||||||
@@ -261,13 +397,13 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private void OnProvidingCredentialException(Exception ex)
|
private void OnProvidingCredentialException(Exception ex)
|
||||||
{
|
{
|
||||||
//LoggerHelper.LogEvenIfCantBeResolved(ex);
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
UIPasteboard.General.String = ex.ToString();
|
|
||||||
CancelRequest(ASExtensionErrorCode.Failed);
|
CancelRequest(ASExtensionErrorCode.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancelRequest(ASExtensionErrorCode code)
|
private void CancelRequest(ASExtensionErrorCode code)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("CancelRequest" + code);
|
||||||
//var err = new NSError(new NSString("ASExtensionErrorDomain"), Convert.ToInt32(code), null);
|
//var err = new NSError(new NSString("ASExtensionErrorDomain"), Convert.ToInt32(code), null);
|
||||||
var err = new NSError(ASExtensionErrorCodeExtensions.GetDomain(code), (int)code);
|
var err = new NSError(ASExtensionErrorCodeExtensions.GetDomain(code), (int)code);
|
||||||
ExtensionContext?.CancelRequest(err);
|
ExtensionContext?.CancelRequest(err);
|
||||||
@@ -277,6 +413,7 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("Preparing for Segue");
|
||||||
if (segue.DestinationViewController is UINavigationController navController)
|
if (segue.DestinationViewController is UINavigationController navController)
|
||||||
{
|
{
|
||||||
if (navController.TopViewController is LoginListViewController listLoginController)
|
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());
|
DismissViewController(false, async () => await OnLockDismissedAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NavigateToPage(ContentPage page)
|
private void NavigateToPage(ContentPage page)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("NavigateToPage" + page.GetType().FullName);
|
||||||
var navigationPage = new NavigationPage(page);
|
var navigationPage = new NavigationPage(page);
|
||||||
var uiController = navigationPage.ToUIViewController(MauiContextSingleton.Instance.MauiContext);
|
var uiController = navigationPage.ToUIViewController(MauiContextSingleton.Instance.MauiContext);
|
||||||
uiController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
uiController.ModalPresentationStyle = UIModalPresentationStyle.FullScreen;
|
||||||
@@ -333,23 +472,28 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_context.PasswordCredentialIdentity != null)
|
ClipLogger.Log("OnLockDismissedAsync");
|
||||||
|
if (_context.PasswordCredentialIdentity != null || _context.IsPasskey)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("OnLockDismissedAsync -> ProvideCredentialAsync");
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => ProvideCredentialAsync());
|
await MainThread.InvokeOnMainThreadAsync(() => ProvideCredentialAsync());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_context.Configuring)
|
if (_context.Configuring)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("OnLockDismissedAsync -> Configuring");
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("setupSegue", this));
|
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("setupSegue", this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
if (_context.ServiceIdentifiers == null || _context.ServiceIdentifiers.Length == 0)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("OnLockDismissedAsync -> loginSearchSegue");
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginSearchSegue", this));
|
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginSearchSegue", this));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("OnLockDismissedAsync -> loginListSegue");
|
||||||
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginListSegue", this));
|
await MainThread.InvokeOnMainThreadAsync(() => PerformSegue("loginListSegue", this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,14 +507,27 @@ namespace Bit.iOS.Autofill
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("ProvideCredentialAsync");
|
||||||
if (!ServiceContainer.TryResolve<ICipherService>(out var cipherService)
|
if (!ServiceContainer.TryResolve<ICipherService>(out var cipherService)
|
||||||
||
|
||
|
||||||
_context.RecordIdentifier == null)
|
_context.RecordIdentifier == null)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("ProvideCredentialAsync -> CredentialIdentityNotFound");
|
||||||
CancelRequest(ASExtensionErrorCode.CredentialIdentityNotFound);
|
CancelRequest(ASExtensionErrorCode.CredentialIdentityNotFound);
|
||||||
return;
|
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);
|
var cipher = await cipherService.GetAsync(_context.RecordIdentifier);
|
||||||
if (cipher?.Login is null || cipher.Type != CipherType.Login)
|
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;
|
string totpCode = null;
|
||||||
if (await _stateService.Value.GetDisableAutoTotpCopyAsync() != true)
|
if (await _stateService.Value.GetDisableAutoTotpCopyAsync() != true)
|
||||||
{
|
{
|
||||||
@@ -437,6 +588,7 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private async Task CheckLockAsync(Action notLockedAction)
|
private async Task CheckLockAsync(Action notLockedAction)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("CheckLockAsync");
|
||||||
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
|
||||||
{
|
{
|
||||||
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
|
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
|
||||||
@@ -460,6 +612,7 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private void LogoutIfAuthed()
|
private void LogoutIfAuthed()
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("LogoutIfAuthed");
|
||||||
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -482,12 +635,14 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
private void InitApp()
|
private void InitApp()
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("InitApp");
|
||||||
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey,
|
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey,
|
||||||
_nfcSession, out _nfcDelegate, out _accountsManager);
|
_nfcSession, out _nfcDelegate, out _accountsManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitAppIfNeeded()
|
private void InitAppIfNeeded()
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("InitAppIfNeeded");
|
||||||
if (ServiceContainer.RegisteredServices == null || ServiceContainer.RegisteredServices.Count == 0)
|
if (ServiceContainer.RegisteredServices == null || ServiceContainer.RegisteredServices.Count == 0)
|
||||||
{
|
{
|
||||||
InitApp();
|
InitApp();
|
||||||
@@ -685,6 +840,7 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("Navigate" + navTarget);
|
||||||
switch (navTarget)
|
switch (navTarget)
|
||||||
{
|
{
|
||||||
case NavigationTarget.HomeLogin:
|
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
|
else
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
logger = DebugLogger.Instance;
|
logger = ClipLogger.Instance;
|
||||||
#else
|
#else
|
||||||
logger = Logger.Instance;
|
logger = Logger.Instance;
|
||||||
#endif
|
#endif
|
||||||
@@ -138,6 +138,7 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
logger!.Exception(nreAppGroupContainer);
|
logger!.Exception(nreAppGroupContainer);
|
||||||
throw nreAppGroupContainer;
|
throw nreAppGroupContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
var liteDbStorage = new LiteDbStorageService(
|
var liteDbStorage = new LiteDbStorageService(
|
||||||
Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db"));
|
Path.Combine(appGroupContainer.Path, "Library", "bitwarden.db"));
|
||||||
var localizeService = new LocalizeService();
|
var localizeService = new LocalizeService();
|
||||||
@@ -189,6 +190,11 @@ namespace Bit.iOS.Core.Utilities
|
|||||||
|
|
||||||
public static void RegisterFinallyBeforeBootstrap()
|
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.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
|
||||||
ServiceContainer.Resolve<IEnvironmentService>(),
|
ServiceContainer.Resolve<IEnvironmentService>(),
|
||||||
ServiceContainer.Resolve<IStateService>(),
|
ServiceContainer.Resolve<IStateService>(),
|
||||||
|
|||||||
Reference in New Issue
Block a user