mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-24 11:55:38 +01:00
PM-5154 Fixed select passkey flow and started implementing create passkey on iOS
This commit is contained in:
parent
8b5a7b257d
commit
7381d5278a
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user