PM-5154 [Passkeys iOS] Fix Credential ID handling on bytes and string formats. Fix Discoverable to be lowercase on set so it doesn't break parsing on clients. Added UserDisplayName on Fido2 entities. Extracted the Guid Standard/Raw format helpers to a extensions class.
This commit is contained in:
parent
a1c9ebf01f
commit
41161864db
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.Domain;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
|
@ -21,6 +20,7 @@ namespace Bit.Core.Models.Api
|
|||
RpName = fido2Key.RpName?.EncryptedString;
|
||||
UserHandle = fido2Key.UserHandle?.EncryptedString;
|
||||
UserName = fido2Key.UserName?.EncryptedString;
|
||||
UserDisplayName = fido2Key.UserDisplayName?.EncryptedString;
|
||||
Counter = fido2Key.Counter?.EncryptedString;
|
||||
CreationDate = fido2Key.CreationDate;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ namespace Bit.Core.Models.Api
|
|||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Bit.Core.Models.Data
|
|||
RpName = apiData.RpName;
|
||||
UserHandle = apiData.UserHandle;
|
||||
UserName = apiData.UserName;
|
||||
UserDisplayName = apiData.UserDisplayName;
|
||||
Counter = apiData.Counter;
|
||||
CreationDate = apiData.CreationDate;
|
||||
}
|
||||
|
@ -33,6 +34,7 @@ namespace Bit.Core.Models.Data
|
|||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.Core.Models.Domain
|
||||
|
@ -21,6 +17,7 @@ namespace Bit.Core.Models.Domain
|
|||
nameof(RpName),
|
||||
nameof(UserHandle),
|
||||
nameof(UserName),
|
||||
nameof(UserDisplayName),
|
||||
nameof(Counter)
|
||||
};
|
||||
|
||||
|
@ -48,6 +45,7 @@ namespace Bit.Core.Models.Domain
|
|||
public EncString RpName { get; set; }
|
||||
public EncString UserHandle { get; set; }
|
||||
public EncString UserName { get; set; }
|
||||
public EncString UserDisplayName { get; set; }
|
||||
public EncString Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Bit.Core.Models.View
|
|||
public string RpName { get; set; }
|
||||
public string UserHandle { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserDisplayName { get; set; }
|
||||
public string Counter { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
|
||||
|
@ -50,7 +51,7 @@ namespace Bit.Core.Models.View
|
|||
[JsonIgnore]
|
||||
public bool DiscoverableValue {
|
||||
get => bool.TryParse(Discoverable, out var discoverable) && discoverable;
|
||||
set => Discoverable = value.ToString();
|
||||
set => Discoverable = value.ToString().ToLower(); // must be lowercase so it can be parsed in the current version of clients
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
|
|
|
@ -79,6 +79,8 @@ namespace Bit.Core.Services
|
|||
var keyPair = GenerateKeyPair();
|
||||
var fido2Credential = CreateCredentialView(makeCredentialParams, keyPair.privateKey);
|
||||
|
||||
ClipLogger.Log($"[Fido2Authenticator] IsDiscoverable {fido2Credential.Discoverable} - {fido2Credential.DiscoverableValue}");
|
||||
|
||||
var encrypted = await _cipherService.GetAsync(cipherId);
|
||||
var cipher = await encrypted.DecryptAsync();
|
||||
|
||||
|
@ -95,18 +97,20 @@ namespace Bit.Core.Services
|
|||
await _cipherService.SaveWithServerAsync(reencrypted);
|
||||
credentialId = fido2Credential.CredentialId;
|
||||
|
||||
ClipLogger.Log($"[Fido2Authenticator] IsDiscoverable {cipher.Login.MainFido2Credential.Discoverable} - {cipher.Login.MainFido2Credential.DiscoverableValue}");
|
||||
|
||||
var authData = await GenerateAuthDataAsync(
|
||||
rpId: makeCredentialParams.RpEntity.Id,
|
||||
counter: fido2Credential.CounterValue,
|
||||
userPresence: true,
|
||||
userVerification: userVerified,
|
||||
credentialId: GuidToRawFormat(credentialId),
|
||||
credentialId: credentialId.GuidToRawFormat(),
|
||||
publicKey: keyPair.publicKey
|
||||
);
|
||||
|
||||
return new Fido2AuthenticatorMakeCredentialResult
|
||||
{
|
||||
CredentialId = GuidToRawFormat(credentialId),
|
||||
CredentialId = credentialId.GuidToRawFormat(),
|
||||
AttestationObject = EncodeAttestationObject(authData),
|
||||
AuthData = authData,
|
||||
PublicKey = keyPair.publicKey.ExportDer(),
|
||||
|
@ -197,6 +201,9 @@ namespace Bit.Core.Services
|
|||
throw new NotAllowedError();
|
||||
}
|
||||
|
||||
// TODO: Remove this hardcoding
|
||||
userVerified = true;
|
||||
|
||||
if (!userVerified && (assertionParams.RequireUserVerification || selectedCipher.Reprompt != CipherRepromptType.None)) {
|
||||
// _logService.Info(
|
||||
// "[Fido2Authenticator] Aborting because user verification was unsuccessful."
|
||||
|
@ -221,6 +228,9 @@ namespace Bit.Core.Services
|
|||
var encrypted = await _cipherService.EncryptAsync(selectedCipher);
|
||||
await _cipherService.SaveWithServerAsync(encrypted);
|
||||
|
||||
ClipLogger.Log($"[Fido2Authenticator] Selected fido2 cred RPID {selectedFido2Credential.RpId}");
|
||||
ClipLogger.Log($"[Fido2Authenticator] param RpId {assertionParams.RpId}");
|
||||
|
||||
var authenticatorData = await GenerateAuthDataAsync(
|
||||
rpId: selectedFido2Credential.RpId,
|
||||
userPresence: true,
|
||||
|
@ -245,7 +255,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
|
||||
{
|
||||
Id = GuidToRawFormat(selectedCredentialId),
|
||||
Id = selectedCredentialId.GuidToRawFormat(),
|
||||
UserHandle = selectedFido2Credential.UserHandleValue
|
||||
},
|
||||
AuthenticatorData = authenticatorData,
|
||||
|
@ -265,7 +275,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
var credentials = (await FindCredentialsByRpAsync(rpId)).Select(cipher => new Fido2AuthenticatorDiscoverableCredentialMetadata {
|
||||
Type = "public-key",
|
||||
Id = GuidToRawFormat(cipher.Login.MainFido2Credential.CredentialId),
|
||||
Id = cipher.Login.MainFido2Credential.CredentialId.GuidToRawFormat(),
|
||||
RpId = cipher.Login.MainFido2Credential.RpId,
|
||||
UserHandle = cipher.Login.MainFido2Credential.UserHandleValue,
|
||||
UserName = cipher.Login.MainFido2Credential.UserName
|
||||
|
@ -290,7 +300,7 @@ namespace Bit.Core.Services
|
|||
{
|
||||
try
|
||||
{
|
||||
ids.Add(GuidToStandardFormat(credential.Id));
|
||||
ids.Add(credential.Id.GuidToStandardFormat());
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
@ -320,15 +330,8 @@ 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));
|
||||
ids.Add(credential.Id.GuidToStandardFormat());
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
@ -384,16 +387,16 @@ namespace Bit.Core.Services
|
|||
{
|
||||
return new Fido2CredentialView {
|
||||
CredentialId = Guid.NewGuid().ToString(),
|
||||
KeyType = Bit.Core.Constants.DefaultFido2CredentialType,
|
||||
KeyAlgorithm = Bit.Core.Constants.DefaultFido2CredentialAlgorithm,
|
||||
KeyCurve = Bit.Core.Constants.DefaultFido2CredentialCurve,
|
||||
KeyType = Constants.DefaultFido2CredentialType,
|
||||
KeyAlgorithm = Constants.DefaultFido2CredentialAlgorithm,
|
||||
KeyCurve = Constants.DefaultFido2CredentialCurve,
|
||||
KeyValue = CoreHelpers.Base64UrlEncode(privateKey),
|
||||
RpId = makeCredentialsParams.RpEntity.Id,
|
||||
UserHandle = CoreHelpers.Base64UrlEncode(makeCredentialsParams.UserEntity.Id),
|
||||
UserName = makeCredentialsParams.UserEntity.Name,
|
||||
CounterValue = 0,
|
||||
RpName = makeCredentialsParams.RpEntity.Name,
|
||||
// UserDisplayName = makeCredentialsParams.UserEntity.DisplayName,
|
||||
UserDisplayName = makeCredentialsParams.UserEntity.DisplayName,
|
||||
DiscoverableValue = makeCredentialsParams.RequireResidentKey,
|
||||
CreationDate = DateTime.Now
|
||||
};
|
||||
|
@ -414,6 +417,7 @@ namespace Bit.Core.Services
|
|||
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
|
||||
authData.AddRange(rpIdHash);
|
||||
|
||||
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> rpIdHash: {rpIdHash}");
|
||||
|
||||
ClipLogger.Log($"[Fido2Authenticator] GenerateAuthDataAsync -> ad: {isAttestation} - uv: {userVerification} - up: {userPresence}");
|
||||
|
||||
|
@ -520,16 +524,6 @@ namespace Bit.Core.Services
|
|||
return dsa.SignData(sigBase, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
|
||||
}
|
||||
|
||||
private string GuidToStandardFormat(byte[] bytes)
|
||||
{
|
||||
return new Guid(bytes).ToString();
|
||||
}
|
||||
|
||||
private byte[] GuidToRawFormat(string guid)
|
||||
{
|
||||
return Guid.Parse(guid).ToByteArray();
|
||||
}
|
||||
|
||||
private class PublicKey
|
||||
{
|
||||
private readonly ECDsa _dsa;
|
||||
|
|
|
@ -2,7 +2,6 @@ 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; }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public static class GuidExtensions
|
||||
{
|
||||
public static string GuidToStandardFormat(this byte[] bytes)
|
||||
{
|
||||
return new Guid(bytes).ToString();
|
||||
}
|
||||
|
||||
public static byte[] GuidToRawFormat(this string guid)
|
||||
{
|
||||
return Guid.Parse(guid).ToByteArray();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ using Foundation;
|
|||
using Microsoft.Maui.ApplicationModel;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
using Vision;
|
||||
|
||||
namespace Bit.iOS.Autofill
|
||||
{
|
||||
|
@ -110,10 +109,13 @@ namespace Bit.iOS.Autofill
|
|||
UserEntity = new PublicKeyCredentialUserEntity
|
||||
{
|
||||
Id = credIdentity.UserHandle.ToArray(),
|
||||
Name = credIdentity.UserName
|
||||
Name = credIdentity.UserName,
|
||||
DisplayName = credIdentity.UserName
|
||||
}
|
||||
});
|
||||
|
||||
await ASHelpers.ReplaceAllIdentitiesAsync();
|
||||
|
||||
ClipLogger.Log($"PIFPR Completing");
|
||||
ClipLogger.Log($"PIFPR Completing - RpId: {credIdentity.RelyingPartyIdentifier}");
|
||||
ClipLogger.Log($"PIFPR Completing - CDH: {passkeyRegistrationRequest.ClientDataHash.GetBase64EncodedString(NSDataBase64EncodingOptions.None)}");
|
||||
|
@ -127,15 +129,6 @@ namespace Bit.iOS.Autofill
|
|||
NSData.FromArray(result.AttestationObject)));
|
||||
|
||||
ClipLogger.Log($"CompleteRegistrationRequestAsync: {expired}");
|
||||
|
||||
//else if (await IsLocked())
|
||||
//{
|
||||
// PerformSegue("lockPasswordSegue", this);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// PerformSegue("loginListSegue", this);
|
||||
//}
|
||||
}
|
||||
|
||||
private PublicKeyCredentialParameters[] GetCredTypesAndPubKeyAlgs(NSNumber[] supportedAlgorithms)
|
||||
|
@ -204,14 +197,14 @@ namespace Bit.iOS.Autofill
|
|||
var fido2AssertionResult = await Fido2AuthService.GetAssertionAsync(new Bit.Core.Utilities.Fido2.Fido2AuthenticatorGetAssertionParams
|
||||
{
|
||||
RpId = rpId,
|
||||
Hash = _context.PasskeyCredentialRequest.ClientDataHash.ToByteArray(),
|
||||
Hash = _context.PasskeyCredentialRequest.ClientDataHash.ToArray(),
|
||||
RequireUserVerification = _context.PasskeyCredentialRequest.UserVerificationPreference == "required",
|
||||
RequireUserPresence = false,
|
||||
AllowCredentialDescriptorList = new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor[]
|
||||
{
|
||||
new Bit.Core.Utilities.Fido2.PublicKeyCredentialDescriptor
|
||||
{
|
||||
IdStr = credentialIdData.ToString()
|
||||
Id = credentialIdData.ToArray()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -227,7 +220,7 @@ namespace Bit.iOS.Autofill
|
|||
ClipLogger.Log("selectedUserHandleData:" + selectedUserHandleData);
|
||||
|
||||
var selectedCredentialIdData = fido2AssertionResult.SelectedCredential != null
|
||||
? new Guid(fido2AssertionResult.SelectedCredential.Id).ToString()
|
||||
? NSData.FromArray(fido2AssertionResult.SelectedCredential.Id)
|
||||
: credentialIdData;
|
||||
|
||||
ClipLogger.Log("selectedCredentialIdData:" + selectedCredentialIdData);
|
||||
|
|
|
@ -509,7 +509,7 @@ namespace Bit.iOS.Autofill
|
|||
try
|
||||
{
|
||||
ClipLogger.Log("ProvideCredentialAsync");
|
||||
|
||||
|
||||
if (_context.IsPasskey && UIDevice.CurrentDevice.CheckSystemVersion(17, 0))
|
||||
{
|
||||
if (_context.PasskeyCredentialIdentity is null)
|
||||
|
|
|
@ -3,6 +3,7 @@ using Bit.Core.Abstractions;
|
|||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Bit.iOS.Core.Utilities
|
||||
|
@ -146,7 +147,7 @@ namespace Bit.iOS.Core.Utilities
|
|||
|
||||
return new ASPasskeyCredentialIdentity(cipher.Login.MainFido2Credential.RpId,
|
||||
cipher.Login.MainFido2Credential.UserName,
|
||||
cipher.Login.MainFido2Credential.CredentialId,
|
||||
NSData.FromArray(cipher.Login.MainFido2Credential.CredentialId.GuidToRawFormat()),
|
||||
cipher.Login.MainFido2Credential.UserHandle,
|
||||
cipher.Id);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue