mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-28 12:35:40 +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.
|
/// Whether or not the user must be verified before completing the operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UserVerification { get; set; }
|
public bool UserVerification { get; set; }
|
||||||
|
|
||||||
|
public string RpId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Fido2ConfirmNewCredentialResult
|
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.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
@ -28,29 +29,36 @@ namespace Bit.Core.Models.View
|
|||||||
public string Counter { get; set; }
|
public string Counter { get; set; }
|
||||||
public DateTime CreationDate { get; set; }
|
public DateTime CreationDate { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public int CounterValue {
|
public int CounterValue {
|
||||||
get => int.TryParse(Counter, out var counter) ? counter : 0;
|
get => int.TryParse(Counter, out var counter) ? counter : 0;
|
||||||
set => Counter = value.ToString();
|
set => Counter = value.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public byte[] UserHandleValue {
|
public byte[] UserHandleValue {
|
||||||
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
|
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
|
||||||
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public byte[] KeyBytes {
|
public byte[] KeyBytes {
|
||||||
get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue);
|
get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue);
|
||||||
set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public bool DiscoverableValue {
|
public bool DiscoverableValue {
|
||||||
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
|
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
|
||||||
set => Discoverable = value.ToString();
|
set => Discoverable = value.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
public override string SubTitle => UserName;
|
public override string SubTitle => UserName;
|
||||||
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
|
public override List<KeyValuePair<string, LinkedIdType>> LinkedFieldOptions => new List<KeyValuePair<string, LinkedIdType>>();
|
||||||
|
[JsonIgnore]
|
||||||
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
|
public bool CanLaunch => !string.IsNullOrEmpty(RpId);
|
||||||
|
[JsonIgnore]
|
||||||
public string LaunchUri => $"https://{RpId}";
|
public string LaunchUri => $"https://{RpId}";
|
||||||
|
|
||||||
public bool IsUniqueAgainst(Fido2CredentialView fido2View) => fido2View?.RpId != RpId || fido2View?.UserName != UserName;
|
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 {
|
var response = await _userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams {
|
||||||
CredentialName = makeCredentialParams.RpEntity.Name,
|
CredentialName = makeCredentialParams.RpEntity.Name,
|
||||||
UserName = makeCredentialParams.UserEntity.Name,
|
UserName = makeCredentialParams.UserEntity.Name,
|
||||||
UserVerification = makeCredentialParams.RequireUserVerification
|
UserVerification = makeCredentialParams.RequireUserVerification,
|
||||||
|
RpId = makeCredentialParams.RpEntity.Id
|
||||||
});
|
});
|
||||||
|
|
||||||
var cipherId = response.CipherId;
|
var cipherId = response.CipherId;
|
||||||
@ -131,11 +132,16 @@ namespace Bit.Core.Services
|
|||||||
await _syncService.FullSyncAsync(false);
|
await _syncService.FullSyncAsync(false);
|
||||||
|
|
||||||
if (assertionParams.AllowCredentialDescriptorList?.Length > 0) {
|
if (assertionParams.AllowCredentialDescriptorList?.Length > 0) {
|
||||||
|
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Finding credentials with credential descriptor list");
|
||||||
|
|
||||||
cipherOptions = await FindCredentialsByIdAsync(
|
cipherOptions = await FindCredentialsByIdAsync(
|
||||||
assertionParams.AllowCredentialDescriptorList,
|
assertionParams.AllowCredentialDescriptorList,
|
||||||
assertionParams.RpId
|
assertionParams.RpId
|
||||||
);
|
);
|
||||||
} else {
|
} else
|
||||||
|
{
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] Finding credentials with RP");
|
||||||
cipherOptions = await FindCredentialsByRpAsync(assertionParams.RpId);
|
cipherOptions = await FindCredentialsByRpAsync(assertionParams.RpId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,12 +160,15 @@ namespace Bit.Core.Services
|
|||||||
// TODO: We might want reconsider allowing user presence to be optional
|
// TODO: We might want reconsider allowing user presence to be optional
|
||||||
if (assertionParams.AllowCredentialDescriptorList?.Length == 1 && assertionParams.RequireUserPresence == false)
|
if (assertionParams.AllowCredentialDescriptorList?.Length == 1 && assertionParams.RequireUserPresence == false)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] AllowCredentialDescriptorList + RequireUserPresence false");
|
||||||
selectedCipherId = cipherOptions[0].Id;
|
selectedCipherId = cipherOptions[0].Id;
|
||||||
userVerified = false;
|
userVerified = false;
|
||||||
userPresence = false;
|
userPresence = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("[Fido2Authenticator] PickCredentialAsync");
|
||||||
|
|
||||||
var response = await _userInterface.PickCredentialAsync(new Fido2PickCredentialParams {
|
var response = await _userInterface.PickCredentialAsync(new Fido2PickCredentialParams {
|
||||||
CipherIds = cipherOptions.Select((cipher) => cipher.Id).ToArray(),
|
CipherIds = cipherOptions.Select((cipher) => cipher.Id).ToArray(),
|
||||||
UserVerification = assertionParams.RequireUserVerification
|
UserVerification = assertionParams.RequireUserVerification
|
||||||
@ -197,10 +206,13 @@ namespace Bit.Core.Services
|
|||||||
throw new NotAllowedError();
|
throw new NotAllowedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
var selectedFido2Credential = selectedCipher.Login.MainFido2Credential;
|
var selectedFido2Credential = selectedCipher.Login.MainFido2Credential;
|
||||||
var selectedCredentialId = selectedFido2Credential.CredentialId;
|
var selectedCredentialId = selectedFido2Credential.CredentialId;
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] Selected fido2 cred {selectedFido2Credential.CredentialId}");
|
||||||
|
|
||||||
if (selectedFido2Credential.CounterValue != 0) {
|
if (selectedFido2Credential.CounterValue != 0) {
|
||||||
++selectedFido2Credential.CounterValue;
|
++selectedFido2Credential.CounterValue;
|
||||||
}
|
}
|
||||||
@ -211,17 +223,24 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
var authenticatorData = await GenerateAuthDataAsync(
|
var authenticatorData = await GenerateAuthDataAsync(
|
||||||
rpId: selectedFido2Credential.RpId,
|
rpId: selectedFido2Credential.RpId,
|
||||||
userPresence: userPresence,
|
userPresence: true,
|
||||||
userVerification: userVerified,
|
userVerification: true,
|
||||||
counter: selectedFido2Credential.CounterValue
|
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(
|
var signature = GenerateSignature(
|
||||||
authData: authenticatorData,
|
authData: authenticatorData,
|
||||||
clientDataHash: assertionParams.Hash,
|
clientDataHash: assertionParams.Hash,
|
||||||
privateKey: selectedFido2Credential.KeyBytes
|
privateKey: selectedFido2Credential.KeyBytes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ClipLogger.Log($"signature base64 from bytes: {Convert.ToBase64String(signature, Base64FormattingOptions.None)}");
|
||||||
|
|
||||||
return new Fido2AuthenticatorGetAssertionResult
|
return new Fido2AuthenticatorGetAssertionResult
|
||||||
{
|
{
|
||||||
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
|
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
|
||||||
@ -301,17 +320,35 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
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));
|
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)
|
if (ids.Count == 0)
|
||||||
{
|
{
|
||||||
return new List<CipherView>();
|
return new List<CipherView>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> {ids[0]}");
|
||||||
|
|
||||||
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
var ciphers = await _cipherService.GetAllDecryptedAsync();
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] FindCredentialsByIdAsync -> ciphers count: {ciphers?.Count}");
|
||||||
|
|
||||||
return ciphers.FindAll((cipher) =>
|
return ciphers.FindAll((cipher) =>
|
||||||
!cipher.IsDeleted &&
|
!cipher.IsDeleted &&
|
||||||
cipher.Type == CipherType.Login &&
|
cipher.Type == CipherType.Login &&
|
||||||
@ -347,9 +384,9 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return new Fido2CredentialView {
|
return new Fido2CredentialView {
|
||||||
CredentialId = Guid.NewGuid().ToString(),
|
CredentialId = Guid.NewGuid().ToString(),
|
||||||
KeyType = "public-key",
|
KeyType = Bit.Core.Constants.DefaultFido2CredentialType,
|
||||||
KeyAlgorithm = "ECDSA",
|
KeyAlgorithm = Bit.Core.Constants.DefaultFido2CredentialAlgorithm,
|
||||||
KeyCurve = "P-256",
|
KeyCurve = Bit.Core.Constants.DefaultFido2CredentialCurve,
|
||||||
KeyValue = CoreHelpers.Base64UrlEncode(privateKey),
|
KeyValue = CoreHelpers.Base64UrlEncode(privateKey),
|
||||||
RpId = makeCredentialsParams.RpEntity.Id,
|
RpId = makeCredentialsParams.RpEntity.Id,
|
||||||
UserHandle = CoreHelpers.Base64UrlEncode(makeCredentialsParams.UserEntity.Id),
|
UserHandle = CoreHelpers.Base64UrlEncode(makeCredentialsParams.UserEntity.Id),
|
||||||
@ -377,6 +414,9 @@ namespace Bit.Core.Services
|
|||||||
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
|
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
|
||||||
authData.AddRange(rpIdHash);
|
authData.AddRange(rpIdHash);
|
||||||
|
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> ad: {isAttestation} - uv: {userVerification} - up: {userPresence}");
|
||||||
|
|
||||||
var flags = AuthDataFlags(
|
var flags = AuthDataFlags(
|
||||||
extensionData: false,
|
extensionData: false,
|
||||||
attestationData: isAttestation,
|
attestationData: isAttestation,
|
||||||
@ -385,6 +425,10 @@ namespace Bit.Core.Services
|
|||||||
);
|
);
|
||||||
authData.Add(flags);
|
authData.Add(flags);
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> flags: {flags}");
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> counter: {counter}");
|
||||||
|
|
||||||
authData.AddRange(new List<byte> {
|
authData.AddRange(new List<byte> {
|
||||||
(byte)(counter >> 24),
|
(byte)(counter >> 24),
|
||||||
(byte)(counter >> 16),
|
(byte)(counter >> 16),
|
||||||
@ -407,13 +451,14 @@ namespace Bit.Core.Services
|
|||||||
attestedCredentialData.AddRange(credentialId);
|
attestedCredentialData.AddRange(credentialId);
|
||||||
attestedCredentialData.AddRange(publicKey.ExportCose());
|
attestedCredentialData.AddRange(publicKey.ExportCose());
|
||||||
|
|
||||||
|
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> adding attestedCD: {attestedCredentialData}");
|
||||||
authData.AddRange(attestedCredentialData);
|
authData.AddRange(attestedCredentialData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return authData.ToArray();
|
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;
|
byte flags = 0;
|
||||||
|
|
||||||
if (extensionData) {
|
if (extensionData) {
|
||||||
@ -424,6 +469,16 @@ namespace Bit.Core.Services
|
|||||||
flags |= 0b01000000;
|
flags |= 0b01000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (backupEligibility)
|
||||||
|
{
|
||||||
|
flags |= 0b00001000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backupState)
|
||||||
|
{
|
||||||
|
flags |= 0b00010000;
|
||||||
|
}
|
||||||
|
|
||||||
if (userVerification) {
|
if (userVerification) {
|
||||||
flags |= 0b00000100;
|
flags |= 0b00000100;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,11 @@
|
|||||||
public byte[] Signature { get; set; }
|
public byte[] Signature { get; set; }
|
||||||
|
|
||||||
public Fido2AuthenticatorGetAssertionSelectedCredential SelectedCredential { 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 {
|
public class Fido2AuthenticatorGetAssertionSelectedCredential {
|
||||||
|
@ -2,6 +2,7 @@ namespace Bit.Core.Utilities.Fido2
|
|||||||
{
|
{
|
||||||
public class PublicKeyCredentialDescriptor {
|
public class PublicKeyCredentialDescriptor {
|
||||||
public byte[] Id { get; set; }
|
public byte[] Id { get; set; }
|
||||||
|
public string IdStr { get; set; }
|
||||||
public string[] Transports { get; set; }
|
public string[] Transports { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
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.Abstractions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Core.Utilities.Fido2;
|
||||||
using Bit.iOS.Core.Utilities;
|
using Bit.iOS.Core.Utilities;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using Microsoft.Maui.ApplicationModel;
|
||||||
|
using ObjCRuntime;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
using Vision;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill
|
namespace Bit.iOS.Autofill
|
||||||
{
|
{
|
||||||
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost, IFido2UserInterface
|
public partial class CredentialProviderViewController : ASCredentialProviderViewController, IAccountsManagerHost, IFido2UserInterface
|
||||||
{
|
{
|
||||||
|
private readonly LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
|
||||||
|
|
||||||
private IFido2AuthenticatorService _fido2AuthService;
|
private IFido2AuthenticatorService _fido2AuthService;
|
||||||
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)
|
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasskeyCredentialRequest passkeyCredentialRequest)
|
||||||
{
|
{
|
||||||
InitAppIfNeeded();
|
InitAppIfNeeded();
|
||||||
@ -57,27 +196,43 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
try
|
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
|
var fido2AssertionResult = await Fido2AuthService.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
||||||
{
|
{
|
||||||
RpId = rpId,
|
RpId = rpId,
|
||||||
ClientDataHash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
|
Hash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
|
||||||
RequireUserVerification = _context.PasskeyCredentialRequest.UserVerificationPreference == "required",
|
RequireUserVerification = _context.PasskeyCredentialRequest.UserVerificationPreference == "required",
|
||||||
RequireUserPresence = false,
|
RequireUserPresence = false,
|
||||||
AllowCredentialDescriptorList = new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor[]
|
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
|
var selectedUserHandleData = fido2AssertionResult.SelectedCredential != null
|
||||||
? NSData.FromArray(fido2AssertionResult.SelectedCredential.UserHandle)
|
? NSData.FromArray(fido2AssertionResult.SelectedCredential.UserHandle)
|
||||||
: (NSData)userHandleData;
|
: (NSData)userHandleData;
|
||||||
|
|
||||||
|
|
||||||
|
ClipLogger.Log("selectedUserHandleData:" + selectedUserHandleData);
|
||||||
|
|
||||||
var selectedCredentialIdData = fido2AssertionResult.SelectedCredential != null
|
var selectedCredentialIdData = fido2AssertionResult.SelectedCredential != null
|
||||||
? new Guid(fido2AssertionResult.SelectedCredential.Id).ToString()
|
? new Guid(fido2AssertionResult.SelectedCredential.Id).ToString()
|
||||||
: credentialIdData;
|
: credentialIdData;
|
||||||
|
|
||||||
CompleteAssertionRequest(new ASPasskeyAssertionCredential(
|
ClipLogger.Log("selectedCredentialIdData:" + selectedCredentialIdData);
|
||||||
|
|
||||||
|
await CompleteAssertionRequest(new ASPasskeyAssertionCredential(
|
||||||
selectedUserHandleData,
|
selectedUserHandleData,
|
||||||
rpId,
|
rpId,
|
||||||
NSData.FromArray(fido2AssertionResult.Signature),
|
NSData.FromArray(fido2AssertionResult.Signature),
|
||||||
@ -88,24 +243,44 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
|
ClipLogger.Log("CompleteAssertionRequestAsync -> InvalidOperationException NoOp");
|
||||||
return;
|
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();
|
ServiceContainer.Reset();
|
||||||
CancelRequest(ASExtensionErrorCode.UserCanceled);
|
CancelRequest(ASExtensionErrorCode.UserCanceled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSRunLoop.Main.BeginInvokeOnMainThread(() =>
|
//NSRunLoop.Main.BeginInvokeOnMainThread(() =>
|
||||||
{
|
//{
|
||||||
ServiceContainer.Reset();
|
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)
|
private bool CanProvideCredentialOnPasskeyRequest(CipherView cipherView)
|
||||||
@ -125,13 +300,74 @@ namespace Bit.iOS.Autofill
|
|||||||
return Task.CompletedTask;
|
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()
|
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())
|
if (!await IsAuthed() || await IsLocked())
|
||||||
{
|
{
|
||||||
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
|
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
|
||||||
|
@ -20,6 +20,7 @@ using Foundation;
|
|||||||
using Microsoft.Maui.ApplicationModel;
|
using Microsoft.Maui.ApplicationModel;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
|
using ObjCRuntime;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
using static CoreFoundation.DispatchSource;
|
using static CoreFoundation.DispatchSource;
|
||||||
using static Microsoft.Maui.ApplicationModel.Permissions;
|
using static Microsoft.Maui.ApplicationModel.Permissions;
|
||||||
@ -166,44 +167,37 @@ namespace Bit.iOS.Autofill
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
ClipLogger.Log("ProvideCredentialWithoutUserInteraction(IASCredentialRequest credentialRequest");
|
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();
|
//ClipLogger.Log($"PCWUI(IASC) -> R: {credentialRequest?.GetType().FullName}");
|
||||||
foreach (var item in crType.GetProperties())
|
//ClipLogger.Log($"PCWUI(IASC) -> I: {credentialRequest?.CredentialIdentity?.GetType().FullName}");
|
||||||
{
|
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> R -> {item.Name} -- {item.PropertyType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var ciType = credentialRequest.CredentialIdentity.GetType();
|
//ClipLogger.Log($"PCWUI(IASC) -> R k: {asPasskeyCredentialRequest?.GetType().FullName}");
|
||||||
foreach (var item in ciType.GetProperties())
|
//ClipLogger.Log($"PCWUI(IASC) -> I k: {asPasskeyCredentialRequest?.CredentialIdentity?.GetType().FullName}");
|
||||||
{
|
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> I -> {item.Name} -- {item.PropertyType}");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
//var crType = asPasskeyCredentialRequest.GetType();
|
||||||
{
|
//foreach (var item in crType.GetProperties())
|
||||||
var cc = (ASPasskeyCredentialRequest)credentialRequest;
|
//{
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> R -> Force cast {cc}");
|
// ClipLogger.Log($"PCWUI(IASC) -> R -> {item.Name} -- {item.PropertyType}");
|
||||||
}
|
//}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
//var ciType = asPasskeyCredentialRequest.CredentialIdentity.GetType();
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> R -> Force cast bad - {ex}");
|
//foreach (var item in ciType.GetProperties())
|
||||||
}
|
//{
|
||||||
|
// ClipLogger.Log($"PCWUI(IASC) -> I -> {item.Name} -- {item.PropertyType}");
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
switch (credentialRequest?.Type)
|
switch (credentialRequest?.Type)
|
||||||
{
|
{
|
||||||
case ASCredentialRequestType.Password:
|
case ASCredentialRequestType.Password:
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> Type P {credentialRequest.CredentialIdentity}");
|
var passwordCredentialIdentity = Runtime.GetNSObject<ASPasswordCredentialIdentity>(credentialRequest.CredentialIdentity.GetHandle());
|
||||||
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
ClipLogger.Log($"PCWUI(IASC) -> Type P {passwordCredentialIdentity}");
|
||||||
|
await ProvideCredentialWithoutUserInteractionAsync(passwordCredentialIdentity);
|
||||||
break;
|
break;
|
||||||
case ASCredentialRequestType.PasskeyAssertion:
|
case ASCredentialRequestType.PasskeyAssertion:
|
||||||
var bpa = credentialRequest is ASPasskeyCredentialRequest;
|
var asPasskeyCredentialRequest = Runtime.GetNSObject<ASPasskeyCredentialRequest>(credentialRequest.GetHandle());
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> Type PA {bpa}");
|
await ProvideCredentialWithoutUserInteractionAsync(asPasskeyCredentialRequest);
|
||||||
await ProvideCredentialWithoutUserInteractionAsync(credentialRequest as ASPasskeyCredentialRequest);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ClipLogger.Log($"PCWUI(IASC) -> Type not P nor PA");
|
ClipLogger.Log($"PCWUI(IASC) -> Type not P nor PA");
|
||||||
@ -254,19 +248,17 @@ namespace Bit.iOS.Autofill
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClipLogger.Log("PrepareInterfaceToProvideCredential(IASCredentialRequest credentialRequest");
|
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)
|
switch (credentialRequest?.Type)
|
||||||
{
|
{
|
||||||
case ASCredentialRequestType.Password:
|
case ASCredentialRequestType.Password:
|
||||||
|
var passwordCredentialIdentity = Runtime.GetNSObject<ASPasswordCredentialIdentity>(credentialRequest.CredentialIdentity.GetHandle());
|
||||||
ClipLogger.Log($"PITPC(IASCR) -> Type P {credentialRequest.CredentialIdentity}");
|
ClipLogger.Log($"PITPC(IASCR) -> Type P {credentialRequest.CredentialIdentity}");
|
||||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = credentialRequest.CredentialIdentity as ASPasswordCredentialIdentity);
|
await PrepareInterfaceToProvideCredentialAsync(c => c.PasswordCredentialIdentity = passwordCredentialIdentity);
|
||||||
break;
|
break;
|
||||||
case ASCredentialRequestType.PasskeyAssertion:
|
case ASCredentialRequestType.PasskeyAssertion:
|
||||||
var bpa = credentialRequest is ASPasskeyCredentialRequest;
|
var asPasskeyCredentialRequest = Runtime.GetNSObject<ASPasskeyCredentialRequest>(credentialRequest.GetHandle());
|
||||||
ClipLogger.Log($"PITPC(IASCR) -> Type PA {bpa}");
|
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = asPasskeyCredentialRequest);
|
||||||
await PrepareInterfaceToProvideCredentialAsync(c => c.PasskeyCredentialRequest = credentialRequest as ASPasskeyCredentialRequest);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ClipLogger.Log($"PITPC(IASCR) -> Type not P nor PA");
|
ClipLogger.Log($"PITPC(IASCR) -> Type not P nor PA");
|
||||||
@ -474,6 +466,14 @@ namespace Bit.iOS.Autofill
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClipLogger.Log("OnLockDismissedAsync");
|
ClipLogger.Log("OnLockDismissedAsync");
|
||||||
|
|
||||||
|
if (_context.IsCreatingPasskey)
|
||||||
|
{
|
||||||
|
ClipLogger.Log("OnLockDismissedAsync -> IsCreatingPasskey");
|
||||||
|
_context._unlockVaultTcs.SetResult(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_context.PasswordCredentialIdentity != null || _context.IsPasskey)
|
if (_context.PasswordCredentialIdentity != null || _context.IsPasskey)
|
||||||
{
|
{
|
||||||
ClipLogger.Log("OnLockDismissedAsync -> ProvideCredentialAsync");
|
ClipLogger.Log("OnLockDismissedAsync -> ProvideCredentialAsync");
|
||||||
@ -509,6 +509,28 @@ namespace Bit.iOS.Autofill
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
ClipLogger.Log("ProvideCredentialAsync");
|
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)
|
if (!ServiceContainer.TryResolve<ICipherService>(out var cipherService)
|
||||||
||
|
||
|
||||||
_context.RecordIdentifier == null)
|
_context.RecordIdentifier == null)
|
||||||
@ -518,16 +540,6 @@ namespace Bit.iOS.Autofill
|
|||||||
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");
|
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)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using AuthenticationServices;
|
using System.Threading.Tasks;
|
||||||
|
using AuthenticationServices;
|
||||||
using Bit.iOS.Core.Models;
|
using Bit.iOS.Core.Models;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
|
using ObjCRuntime;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace Bit.iOS.Autofill.Models
|
namespace Bit.iOS.Autofill.Models
|
||||||
@ -12,14 +14,16 @@ namespace Bit.iOS.Autofill.Models
|
|||||||
public ASPasswordCredentialIdentity PasswordCredentialIdentity { get; set; }
|
public ASPasswordCredentialIdentity PasswordCredentialIdentity { get; set; }
|
||||||
public ASPasskeyCredentialRequest PasskeyCredentialRequest { get; set; }
|
public ASPasskeyCredentialRequest PasskeyCredentialRequest { get; set; }
|
||||||
public bool Configuring { get; set; }
|
public bool Configuring { get; set; }
|
||||||
|
public bool IsCreatingPasskey { get; set; }
|
||||||
|
public TaskCompletionSource<bool> _unlockVaultTcs { get; set; }
|
||||||
|
|
||||||
public ASPasskeyCredentialIdentity PasskeyCredentialIdentity
|
public ASPasskeyCredentialIdentity PasskeyCredentialIdentity
|
||||||
{
|
{
|
||||||
get
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user