PM-5154 Fixed select passkey flow and started implementing create passkey on iOS

This commit is contained in:
Federico Maccaroni 2024-02-15 21:16:54 -03:00
parent 8b5a7b257d
commit 7381d5278a
No known key found for this signature in database
GPG Key ID: 5D233F8F2B034536
8 changed files with 391 additions and 68 deletions

View File

@ -50,6 +50,8 @@ namespace Bit.Core.Abstractions
/// Whether or not the user must be verified before completing the operation.
/// </summary>
public bool UserVerification { get; set; }
public string RpId { get; set; }
}
public struct Fido2ConfirmNewCredentialResult

View File

@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System.Text.Json.Serialization;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
@ -28,29 +29,36 @@ namespace Bit.Core.Models.View
public string Counter { get; set; }
public DateTime CreationDate { get; set; }
[JsonIgnore]
public int CounterValue {
get => int.TryParse(Counter, out var counter) ? counter : 0;
set => Counter = value.ToString();
}
[JsonIgnore]
public byte[] UserHandleValue {
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
}
[JsonIgnore]
public byte[] KeyBytes {
get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue);
set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value);
}
[JsonIgnore]
public bool DiscoverableValue {
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
set => Discoverable = value.ToString();
}
[JsonIgnore]
public override string SubTitle => UserName;
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
[JsonIgnore]
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
[JsonIgnore]
public string LaunchUri => $"https://{RpId}";
public bool IsUniqueAgainst(Fido2CredentialView fido2View) => fido2View?.RpId != RpId || fido2View?.UserName != UserName;

View File

@ -60,7 +60,8 @@ namespace Bit.Core.Services
var response = await _userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams {
CredentialName = makeCredentialParams.RpEntity.Name,
UserName = makeCredentialParams.UserEntity.Name,
UserVerification = makeCredentialParams.RequireUserVerification
UserVerification = makeCredentialParams.RequireUserVerification,
RpId = makeCredentialParams.RpEntity.Id
});
var cipherId = response.CipherId;
@ -131,11 +132,16 @@ namespace Bit.Core.Services
await _syncService.FullSyncAsync(false);
if (assertionParams.AllowCredentialDescriptorList?.Length > 0) {
ClipLogger.Log("[Fido2Authenticator] Finding credentials with credential descriptor list");
cipherOptions = await FindCredentialsByIdAsync(
assertionParams.AllowCredentialDescriptorList,
assertionParams.RpId
);
} else {
} else
{
ClipLogger.Log("[Fido2Authenticator] Finding credentials with RP");
cipherOptions = await FindCredentialsByRpAsync(assertionParams.RpId);
}
@ -154,12 +160,15 @@ namespace Bit.Core.Services
// TODO: We might want reconsider allowing user presence to be optional
if (assertionParams.AllowCredentialDescriptorList?.Length == 1 && assertionParams.RequireUserPresence == false)
{
ClipLogger.Log("[Fido2Authenticator] AllowCredentialDescriptorList + RequireUserPresence false");
selectedCipherId = cipherOptions[0].Id;
userVerified = false;
userPresence = false;
}
else
{
ClipLogger.Log("[Fido2Authenticator] PickCredentialAsync");
var response = await _userInterface.PickCredentialAsync(new Fido2PickCredentialParams {
CipherIds = cipherOptions.Select((cipher) => cipher.Id).ToArray(),
UserVerification = assertionParams.RequireUserVerification
@ -197,10 +206,13 @@ namespace Bit.Core.Services
throw new NotAllowedError();
}
try {
try
{
var selectedFido2Credential = selectedCipher.Login.MainFido2Credential;
var selectedCredentialId = selectedFido2Credential.CredentialId;
ClipLogger.Log($"[Fido2Authenticator] Selected fido2 cred {selectedFido2Credential.CredentialId}");
if (selectedFido2Credential.CounterValue != 0) {
++selectedFido2Credential.CounterValue;
}
@ -211,17 +223,24 @@ namespace Bit.Core.Services
var authenticatorData = await GenerateAuthDataAsync(
rpId: selectedFido2Credential.RpId,
userPresence: userPresence,
userVerification: userVerified,
userPresence: true,
userVerification: true,
counter: selectedFido2Credential.CounterValue
);
ClipLogger.Log($"authenticatorData base64 from bytes: {Convert.ToBase64String(authenticatorData, Base64FormattingOptions.None)}");
ClipLogger.Log($"ClientDataHash base64 from bytes: {Convert.ToBase64String(assertionParams.Hash, Base64FormattingOptions.None)}");
ClipLogger.Log($"selectedFido2Credential.KeyBytes base64 from bytes: {Convert.ToBase64String(selectedFido2Credential.KeyBytes, Base64FormattingOptions.None)}");
var signature = GenerateSignature(
authData: authenticatorData,
clientDataHash: assertionParams.Hash,
privateKey: selectedFido2Credential.KeyBytes
);
ClipLogger.Log($"signature base64 from bytes: {Convert.ToBase64String(signature, Base64FormattingOptions.None)}");
return new Fido2AuthenticatorGetAssertionResult
{
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
@ -301,17 +320,35 @@ namespace Bit.Core.Services
{
try
{
if (credential.IdStr != null)
{
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> Adding credID: {credential.IdStr}");
ids.Add(credential.IdStr);
continue;
}
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> Converting Guid byte length: {credential.Id.Length}");
ids.Add(GuidToStandardFormat(credential.Id));
}
catch {}
catch(Exception ex)
{
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> Converting Guid ex {ex}");
}
}
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> {credentials.Length} vs {ids.Count}");
if (ids.Count == 0)
{
return new List<CipherView>();
}
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> {ids[0]}");
var ciphers = await _cipherService.GetAllDecryptedAsync();
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> ciphers count: {ciphers?.Count}");
return ciphers.FindAll((cipher) =>
!cipher.IsDeleted &&
cipher.Type == CipherType.Login &&
@ -347,9 +384,9 @@ namespace Bit.Core.Services
{
return new Fido2CredentialView {
CredentialId = Guid.NewGuid().ToString(),
KeyType = "public-key",
KeyAlgorithm = "ECDSA",
KeyCurve = "P-256",
KeyType = Bit.Core.Constants.DefaultFido2CredentialType,
KeyAlgorithm = Bit.Core.Constants.DefaultFido2CredentialAlgorithm,
KeyCurve = Bit.Core.Constants.DefaultFido2CredentialCurve,
KeyValue = CoreHelpers.Base64UrlEncode(privateKey),
RpId = makeCredentialsParams.RpEntity.Id,
UserHandle = CoreHelpers.Base64UrlEncode(makeCredentialsParams.UserEntity.Id),
@ -377,6 +414,9 @@ namespace Bit.Core.Services
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
authData.AddRange(rpIdHash);
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> ad: {isAttestation} - uv: {userVerification} - up: {userPresence}");
var flags = AuthDataFlags(
extensionData: false,
attestationData: isAttestation,
@ -385,6 +425,10 @@ namespace Bit.Core.Services
);
authData.Add(flags);
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> flags: {flags}");
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> counter: {counter}");
authData.AddRange(new List<byte> {
(byte)(counter >> 24),
(byte)(counter >> 16),
@ -407,13 +451,14 @@ namespace Bit.Core.Services
attestedCredentialData.AddRange(credentialId);
attestedCredentialData.AddRange(publicKey.ExportCose());
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> adding attestedCD: {attestedCredentialData}");
authData.AddRange(attestedCredentialData);
}
return authData.ToArray();
}
private byte AuthDataFlags(bool extensionData, bool attestationData, bool userVerification, bool userPresence) {
private byte AuthDataFlags(bool extensionData, bool attestationData, bool userVerification, bool userPresence, bool backupEligibility = true, bool backupState = true) {
byte flags = 0;
if (extensionData) {
@ -424,6 +469,16 @@ namespace Bit.Core.Services
flags |= 0b01000000;
}
if (backupEligibility)
{
flags |= 0b00001000;
}
if (backupState)
{
flags |= 0b00010000;
}
if (userVerification) {
flags |= 0b00000100;
}

View File

@ -7,6 +7,11 @@
public byte[] Signature { get; set; }
public Fido2AuthenticatorGetAssertionSelectedCredential SelectedCredential { get; set; }
public override string ToString()
{
return $"AD: {AuthenticatorData.Length}; Sig: {Signature.Length}; SC: {SelectedCredential?.Id?.Length}; {SelectedCredential?.UserHandle?.Length}";
}
}
public class Fido2AuthenticatorGetAssertionSelectedCredential {

View File

@ -2,6 +2,7 @@ namespace Bit.Core.Utilities.Fido2
{
public class PublicKeyCredentialDescriptor {
public byte[] Id { get; set; }
public string IdStr { get; set; }
public string[] Transports { get; set; }
public string Type { get; set; }
}

View File

@ -1,18 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2;
using Bit.iOS.Core.Utilities;
using Foundation;
using Microsoft.Maui.ApplicationModel;
using ObjCRuntime;
using UIKit;
using Vision;
namespace Bit.iOS.Autofill
{
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost, IFido2UserInterface
{
private readonly LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
private IFido2AuthenticatorService _fido2AuthService;
private IFido2AuthenticatorService Fido2AuthService
{
@ -27,6 +36,136 @@ namespace Bit.iOS.Autofill
}
}
public override async void PrepareInterfaceForPasskeyRegistration(IASCredentialRequest registrationRequest)
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
return;
}
ClipLogger.Log($"PIFPR(IASC)");
try
{
switch (registrationRequest?.Type)
{
case ASCredentialRequestType.PasskeyAssertion:
ClipLogger.Log($"PIFPR(IASC) -> Passkey");
var passkeyRegistrationRequest = Runtime.GetNSObject<ASPasskeyCredentialRequest>(registrationRequest.GetHandle());
await PrepareInterfaceForPasskeyRegistrationAsync(passkeyRegistrationRequest);
break;
default:
ClipLogger.Log($"PIFPR(IASC) -> Type not PA");
CancelRequest(ASExtensionErrorCode.Failed);
break;
}
}
catch (Exception ex)
{
OnProvidingCredentialException(ex);
}
}
private async Task PrepareInterfaceForPasskeyRegistrationAsync(ASPasskeyCredentialRequest passkeyRegistrationRequest)
{
if (!UIDevice.CurrentDevice.CheckSystemVersion(17, 0) || passkeyRegistrationRequest?.CredentialIdentity is null)
{
ClipLogger.Log($"PIFPR Not iOS 17 or null passkey request/identity");
return;
}
InitAppIfNeeded();
if (!await IsAuthed())
{
ClipLogger.Log($"PIFPR Not Authed");
await _accountsManager.NavigateOnAccountChangeAsync(false);
return;
}
_context.PasskeyCredentialRequest = passkeyRegistrationRequest;
_context.IsCreatingPasskey = true;
var credIdentity = Runtime.GetNSObject<ASPasskeyCredentialIdentity>(passkeyRegistrationRequest.CredentialIdentity.GetHandle());
ClipLogger.Log($"PIFPR MakeCredentialAsync");
ClipLogger.Log($"PIFPR MakeCredentialAsync RpID: {credIdentity.RelyingPartyIdentifier}");
ClipLogger.Log($"PIFPR MakeCredentialAsync UserName: {credIdentity.UserName}");
ClipLogger.Log($"PIFPR MakeCredentialAsync UVP: {passkeyRegistrationRequest.UserVerificationPreference}");
ClipLogger.Log($"PIFPR MakeCredentialAsync SA: {passkeyRegistrationRequest.SupportedAlgorithms?.Select(a => (int)a)}");
ClipLogger.Log($"PIFPR MakeCredentialAsync UH: {credIdentity.UserHandle.GetBase64EncodedString(NSDataBase64EncodingOptions.None)}");
var result = await Fido2AuthService.MakeCredentialAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorMakeCredentialParams
{
Hash = passkeyRegistrationRequest.ClientDataHash.ToArray(),
CredTypesAndPubKeyAlgs = GetCredTypesAndPubKeyAlgs(passkeyRegistrationRequest.SupportedAlgorithms),
RequireUserVerification = passkeyRegistrationRequest.UserVerificationPreference == "required",
RequireResidentKey = true,
RpEntity = new PublicKeyCredentialRpEntity
{
Id = credIdentity.RelyingPartyIdentifier,
Name = credIdentity.RelyingPartyIdentifier
},
UserEntity = new PublicKeyCredentialUserEntity
{
Id = credIdentity.UserHandle.ToArray(),
Name = credIdentity.UserName
}
});
ClipLogger.Log($"PIFPR Completing");
ClipLogger.Log($"PIFPR Completing - RpId: {credIdentity.RelyingPartyIdentifier}");
ClipLogger.Log($"PIFPR Completing - CDH: {passkeyRegistrationRequest.ClientDataHash.GetBase64EncodedString(NSDataBase64EncodingOptions.None)}");
ClipLogger.Log($"PIFPR Completing - CID: {Convert.ToBase64String(result.CredentialId, Base64FormattingOptions.None)}");
ClipLogger.Log($"PIFPR Completing - AO: {Convert.ToBase64String(result.AttestationObject, Base64FormattingOptions.None)}");
var expired = await ExtensionContext.CompleteRegistrationRequestAsync(new ASPasskeyRegistrationCredential(
credIdentity.RelyingPartyIdentifier,
passkeyRegistrationRequest.ClientDataHash,
NSData.FromArray(result.CredentialId),
NSData.FromArray(result.AttestationObject)));
ClipLogger.Log($"CompleteRegistrationRequestAsync: {expired}");
//else if (await IsLocked())
//{
// PerformSegue("lockPasswordSegue", this);
//}
//else
//{
// PerformSegue("loginListSegue", this);
//}
}
private PublicKeyCredentialParameters[] GetCredTypesAndPubKeyAlgs(NSNumber[] supportedAlgorithms)
{
if (supportedAlgorithms?.Any() != true)
{
return new PublicKeyCredentialParameters[]
{
new PublicKeyCredentialParameters
{
Type = Bit.Core.Constants.DefaultFido2CredentialType,
Alg = (int)Fido2AlgorithmIdentifier.ES256
},
new PublicKeyCredentialParameters
{
Type = Bit.Core.Constants.DefaultFido2CredentialType,
Alg = (int)Fido2AlgorithmIdentifier.RS256
}
};
}
return supportedAlgorithms
.Where(alg => (int)alg == (int)Fido2AlgorithmIdentifier.ES256)
.Select(alg => new PublicKeyCredentialParameters
{
Type = Bit.Core.Constants.DefaultFido2CredentialType,
Alg = (int)alg
}).ToArray();
}
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasskeyCredentialRequest passkeyCredentialRequest)
{
InitAppIfNeeded();
@ -57,27 +196,43 @@ namespace Bit.iOS.Autofill
try
{
ClipLogger.Log($"ClientDataHash: {_context.PasskeyCredentialRequest.ClientDataHash}");
ClipLogger.Log($"ClientDataHash BA: {_context.PasskeyCredentialRequest.ClientDataHash.ToByteArray()}");
ClipLogger.Log($"ClientDataHash base64: {_context.PasskeyCredentialRequest.ClientDataHash.GetBase64EncodedString(NSDataBase64EncodingOptions.None)}");
ClipLogger.Log($"ClientDataHash base64 from bytes: {Convert.ToBase64String(_context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(), Base64FormattingOptions.None)}");
var fido2AssertionResult = await Fido2AuthService.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
{
RpId = rpId,
ClientDataHash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
Hash = _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() }
new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor
{
IdStr = credentialIdData.ToString()
}
}
});
ClipLogger.Log("fido2AssertionResult:" + fido2AssertionResult);
var selectedUserHandleData = fido2AssertionResult.SelectedCredential != null
? NSData.FromArray(fido2AssertionResult.SelectedCredential.UserHandle)
: (NSData)userHandleData;
ClipLogger.Log("selectedUserHandleData:" + selectedUserHandleData);
var selectedCredentialIdData = fido2AssertionResult.SelectedCredential != null
? new Guid(fido2AssertionResult.SelectedCredential.Id).ToString()
: credentialIdData;
CompleteAssertionRequest(new ASPasskeyAssertionCredential(
ClipLogger.Log("selectedCredentialIdData:" + selectedCredentialIdData);
await CompleteAssertionRequest(new ASPasskeyAssertionCredential(
selectedUserHandleData,
rpId,
NSData.FromArray(fido2AssertionResult.Signature),
@ -88,24 +243,44 @@ namespace Bit.iOS.Autofill
}
catch (InvalidOperationException)
{
ClipLogger.Log("CompleteAssertionRequestAsync -> InvalidOperationException NoOp");
return;
}
}
public void CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential)
public async Task CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential)
{
if (_context == null)
try
{
ClipLogger.Log("CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential");
if (assertionCredential is null)
{
ClipLogger.Log("CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential -> assertionCredential is null");
ServiceContainer.Reset();
CancelRequest(ASExtensionErrorCode.UserCanceled);
return;
}
NSRunLoop.Main.BeginInvokeOnMainThread(() =>
{
//NSRunLoop.Main.BeginInvokeOnMainThread(() =>
//{
ServiceContainer.Reset();
ASExtensionContext?.CompleteAssertionRequest(assertionCredential, null);
});
#pragma warning disable CA1416 // Validate platform compatibility
ClipLogger.Log("CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential -> completing");
var expired = await ExtensionContext.CompleteAssertionRequestAsync(assertionCredential);
//ExtensionContext.CompleteAssertionRequest(assertionCredential, expired =>
//{
// ClipLogger.Log($"ASExtensionContext?.CompleteAssertionRequest: {expired}");
//});
ClipLogger.Log($"CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential -> Completed {expired}");
#pragma warning restore CA1416 // Validate platform compatibility
//});
}
catch (Exception ex)
{
ClipLogger.Log($"CompleteAssertionRequest(ASPasskeyAssertionCredential assertionCredential -> failed {ex}");
}
}
private bool CanProvideCredentialOnPasskeyRequest(CipherView cipherView)
@ -125,13 +300,74 @@ namespace Bit.iOS.Autofill
return Task.CompletedTask;
}
public Task<Fido2ConfirmNewCredentialResult> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
public async Task<Fido2ConfirmNewCredentialResult> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
{
return Task.FromResult(new Fido2ConfirmNewCredentialResult());
// TODO: Show interface so the user can choose whether to create a new passkey or select one to add the passkey to.
var newCipher = new CipherView
{
Name = confirmNewCredentialParams.RpId,
Type = Bit.Core.Enums.CipherType.Login,
Login = new LoginView
{
Uris = new List<LoginUriView>
{
new LoginUriView
{
Uri = confirmNewCredentialParams.RpId
}
}
},
Card = new CardView(),
Identity = new IdentityView(),
SecureNote = new SecureNoteView
{
Type = Bit.Core.Enums.SecureNoteType.Generic
},
Reprompt = Bit.Core.Enums.CipherRepromptType.None
};
var encryptedCipher = await _cipherService.Value.EncryptAsync(newCipher);
await _cipherService.Value.SaveWithServerAsync(encryptedCipher);
return new Fido2ConfirmNewCredentialResult
{
CipherId = encryptedCipher.Id,
UserVerified = true
};
}
public async Task EnsureUnlockedVaultAsync()
{
if (_context.IsCreatingPasskey)
{
ClipLogger.Log($"EnsureUnlockedVaultAsync creating passkey");
if (!await IsLocked())
{
ClipLogger.Log($"EnsureUnlockedVaultAsync not locked");
return;
}
_context._unlockVaultTcs?.SetCanceled();
_context._unlockVaultTcs = new TaskCompletionSource<bool>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
ClipLogger.Log($"EnsureUnlockedVaultAsync performing lock segue");
PerformSegue("lockPasswordSegue", this);
}
catch (Exception ex)
{
ClipLogger.Log($"EnsureUnlockedVaultAsync {ex}");
}
});
ClipLogger.Log($"EnsureUnlockedVaultAsync awaiting for unlock");
await _context._unlockVaultTcs.Task;
return;
}
ClipLogger.Log($"EnsureUnlockedVaultAsync Passkey selection");
if (!await IsAuthed() || await IsLocked())
{
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);

View File

@ -20,6 +20,7 @@ using Foundation;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Platform;
using ObjCRuntime;
using UIKit;
using static CoreFoundation.DispatchSource;
using static Microsoft.Maui.ApplicationModel.Permissions;
@ -166,44 +167,37 @@ namespace Bit.iOS.Autofill
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}");
}
//ClipLogger.Log($"PCWUI(IASC) -> R: {credentialRequest?.GetType().FullName}");
//ClipLogger.Log($"PCWUI(IASC) -> I: {credentialRequest?.CredentialIdentity?.GetType().FullName}");
var ciType = credentialRequest.CredentialIdentity.GetType();
foreach (var item in ciType.GetProperties())
{
ClipLogger.Log($"PCWUI(IASC) -> I -> {item.Name} -- {item.PropertyType}");
}
//ClipLogger.Log($"PCWUI(IASC) -> R k: {asPasskeyCredentialRequest?.GetType().FullName}");
//ClipLogger.Log($"PCWUI(IASC) -> I k: {asPasskeyCredentialRequest?.CredentialIdentity?.GetType().FullName}");
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}");
}
//var crType = asPasskeyCredentialRequest.GetType();
//foreach (var item in crType.GetProperties())
//{
// ClipLogger.Log($"PCWUI(IASC) -> R -> {item.Name} -- {item.PropertyType}");
//}
//var ciType = asPasskeyCredentialRequest.CredentialIdentity.GetType();
//foreach (var item in ciType.GetProperties())
//{
// ClipLogger.Log($"PCWUI(IASC) -> I -> {item.Name} -- {item.PropertyType}");
//}
switch (credentialRequest?.Type)
{
case ASCredentialRequestType.Password:
ClipLogger.Log($"PCWUI(IASC) -> Type P {credentialRequest.CredentialIdentity}");
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
var passwordCredentialIdentity = Runtime.GetNSObject<ASPasswordCredentialIdentity>(credentialRequest.CredentialIdentity.GetHandle());
ClipLogger.Log($"PCWUI(IASC) -> Type P {passwordCredentialIdentity}");
await ProvideCredentialWithoutUserInteractionAsync(passwordCredentialIdentity);
break;
case ASCredentialRequestType.PasskeyAssertion:
var bpa = credentialRequest is ASPasskeyCredentialRequest;
ClipLogger.Log($"PCWUI(IASC) -> Type PA {bpa}");
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest as ASPasskeyCredentialRequest);
var asPasskeyCredentialRequest = Runtime.GetNSObject<ASPasskeyCredentialRequest>(credentialRequest.GetHandle());
await ProvideCredentialWithoutUserInteractionAsync(asPasskeyCredentialRequest);
break;
default:
ClipLogger.Log($"PCWUI(IASC) -> Type not P nor PA");
@ -254,19 +248,17 @@ namespace Bit.iOS.Autofill
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:
var passwordCredentialIdentity = Runtime.GetNSObject<ASPasswordCredentialIdentity>(credentialRequest.CredentialIdentity.GetHandle());
ClipLogger.Log($"PITPC(IASCR) -> Type P {credentialRequest.CredentialIdentity}");
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = passwordCredentialIdentity);
break;
case ASCredentialRequestType.PasskeyAssertion:
var bpa = credentialRequest is ASPasskeyCredentialRequest;
ClipLogger.Log($"PITPC(IASCR) -> Type PA {bpa}");
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = credentialRequest as ASPasskeyCredentialRequest);
var asPasskeyCredentialRequest = Runtime.GetNSObject<ASPasskeyCredentialRequest>(credentialRequest.GetHandle());
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = asPasskeyCredentialRequest);
break;
default:
ClipLogger.Log($"PITPC(IASCR) -> Type not P nor PA");
@ -474,6 +466,14 @@ namespace Bit.iOS.Autofill
try
{
ClipLogger.Log("OnLockDismissedAsync");
if (_context.IsCreatingPasskey)
{
ClipLogger.Log("OnLockDismissedAsync -> IsCreatingPasskey");
_context._unlockVaultTcs.SetResult(true);
return;
}
if (_context.PasswordCredentialIdentity != null || _context.IsPasskey)
{
ClipLogger.Log("OnLockDismissedAsync -> ProvideCredentialAsync");
@ -509,6 +509,28 @@ namespace Bit.iOS.Autofill
try
{
ClipLogger.Log("ProvideCredentialAsync");
if (_context.IsPasskey && UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
if (_context.PasskeyCredentialIdentity is null)
{
ClipLogger.Log("ProvideCredentialAsync -> IsPasskey failed");
CancelRequest(ASExtensionErrorCode.Failed);
}
ClipLogger.Log("ProvideCredentialAsync -> IsPasskey");
ClipLogger.Log($"ProvideCredentialAsync -> IsPasskey - RP: {_context.PasskeyCredentialIdentity.RelyingPartyIdentifier}");
ClipLogger.Log($"ProvideCredentialAsync -> IsPasskey - UH: {_context.PasskeyCredentialIdentity.UserHandle}");
ClipLogger.Log($"ProvideCredentialAsync -> IsPasskey - CID: {_context.PasskeyCredentialIdentity.CredentialId}");
ClipLogger.Log($"ProvideCredentialAsync -> IsPasskey - RI: {_context.RecordIdentifier}");
await CompleteAssertionRequestAsync(_context.PasskeyCredentialIdentity.RelyingPartyIdentifier,
_context.PasskeyCredentialIdentity.UserHandle,
_context.PasskeyCredentialIdentity.CredentialId,
_context.RecordIdentifier);
return;
}
if (!ServiceContainer.TryResolve<ICipherService>(out var cipherService)
||
_context.RecordIdentifier == null)
@ -518,16 +540,6 @@ namespace Bit.iOS.Autofill
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)

View File

@ -1,6 +1,8 @@
using AuthenticationServices;
using System.Threading.Tasks;
using AuthenticationServices;
using Bit.iOS.Core.Models;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Bit.iOS.Autofill.Models
@ -12,14 +14,16 @@ namespace Bit.iOS.Autofill.Models
public ASPasswordCredentialIdentity PasswordCredentialIdentity { get; set; }
public ASPasskeyCredentialRequest PasskeyCredentialRequest { get; set; }
public bool Configuring { get; set; }
public bool IsCreatingPasskey { get; set; }
public TaskCompletionSource<bool> _unlockVaultTcs { get; set; }
public ASPasskeyCredentialIdentity PasskeyCredentialIdentity
{
get
{
if (UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
if (PasskeyCredentialRequest != null && UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
{
return PasskeyCredentialRequest?.CredentialIdentity as ASPasskeyCredentialIdentity;
return Runtime.GetNSObject<ASPasskeyCredentialIdentity>(PasskeyCredentialRequest.CredentialIdentity.GetHandle());
}
return null;
}