1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-10 05:13:31 +00:00

[PM-5731] feat: implement assertion without signature

This commit is contained in:
Andreas Coroiu
2024-01-22 16:08:15 +01:00
parent d0e0f0ecdb
commit e8f6c37c06
4 changed files with 136 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain; using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
namespace Bit.Core.Models.View namespace Bit.Core.Models.View
{ {
@@ -32,6 +33,11 @@ namespace Bit.Core.Models.View
set => Counter = value.ToString(); set => Counter = value.ToString();
} }
public byte[] UserHandleValue {
get => UserHandle == null ? null : CoreHelpers.Base64UrlDecode(UserHandle);
set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value);
}
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>>();
public bool IsDiscoverable => bool.TryParse(Discoverable, out var isDiscoverable) && isDiscoverable; public bool IsDiscoverable => bool.TryParse(Discoverable, out var isDiscoverable) && isDiscoverable;

View File

@@ -2,6 +2,7 @@
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
using System.Buffers.Binary;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@@ -10,13 +11,15 @@ namespace Bit.Core.Services
private INativeLogService _logService; private INativeLogService _logService;
private ICipherService _cipherService; private ICipherService _cipherService;
private ISyncService _syncService; private ISyncService _syncService;
private ICryptoFunctionService _cryptoFunctionService;
private IFido2UserInterface _userInterface; private IFido2UserInterface _userInterface;
public Fido2AuthenticatorService(INativeLogService logService, ICipherService cipherService, ISyncService syncService, IFido2UserInterface userInterface) public Fido2AuthenticatorService(INativeLogService logService, ICipherService cipherService, ISyncService syncService, ICryptoFunctionService cryptoFunctionService, IFido2UserInterface userInterface)
{ {
_logService = logService; _logService = logService;
_cipherService = cipherService; _cipherService = cipherService;
_syncService = syncService; _syncService = syncService;
_cryptoFunctionService = cryptoFunctionService;
_userInterface = userInterface; _userInterface = userInterface;
} }
@@ -77,10 +80,33 @@ namespace Bit.Core.Services
++selectedFido2Credential.CounterValue; ++selectedFido2Credential.CounterValue;
} }
await _cipherService.UpdateLastUsedDateAsync(selectedCipher.Id);
var encrypted = await _cipherService.EncryptAsync(selectedCipher); var encrypted = await _cipherService.EncryptAsync(selectedCipher);
await _cipherService.SaveWithServerAsync(encrypted); await _cipherService.SaveWithServerAsync(encrypted);
var authenticatorData = await GenerateAuthData(
rpId: selectedFido2Credential.RpId,
userPresence: true,
userVerification: userVerified,
counter: selectedFido2Credential.CounterValue);
// const signature = await generateSignature({
// authData: authenticatorData,
// clientDataHash: params.hash,
// privateKey: await getPrivateKeyFromFido2Credential(selectedFido2Credential),
// });
// TODO: IMPLEMENT this
return new Fido2AuthenticatorGetAssertionResult
{
SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential
{
Id = GuidToRawFormat(selectedCredentialId),
UserHandle = selectedFido2Credential.UserHandleValue
},
AuthenticatorData = authenticatorData,
Signature = new byte[8]
};
} catch { } catch {
_logService.Info( _logService.Info(
"[Fido2Authenticator] Aborting because no matching credentials were found in the vault." "[Fido2Authenticator] Aborting because no matching credentials were found in the vault."
@@ -88,13 +114,6 @@ namespace Bit.Core.Services
throw new UnknownError(); throw new UnknownError();
} }
// TODO: IMPLEMENT this
return new Fido2AuthenticatorGetAssertionResult
{
AuthenticatorData = new byte[32],
Signature = new byte[8]
};
} }
private async Task<List<CipherView>> FindCredentialsById(PublicKeyCredentialDescriptor[] credentials, string rpId) private async Task<List<CipherView>> FindCredentialsById(PublicKeyCredentialDescriptor[] credentials, string rpId)
@@ -137,9 +156,63 @@ namespace Bit.Core.Services
); );
} }
private async Task<byte[]> GenerateAuthData(
string rpId,
bool userVerification,
bool userPresence,
int counter
// byte[] credentialId,
// CryptoKey? cryptoKey - only needed for attestation
) {
List<byte> authData = new List<byte>();
var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256);
authData.AddRange(rpIdHash);
var flags = AuthDataFlags(false, false, userVerification, userPresence);
authData.Add(flags);
authData.AddRange([
(byte)(counter >> 24),
(byte)(counter >> 16),
(byte)(counter >> 8),
(byte)counter
]);
return authData.ToArray();
}
private byte AuthDataFlags(bool extensionData, bool attestationData, bool userVerification, bool userPresence) {
byte flags = 0;
if (extensionData) {
flags |= 0b1000000;
}
if (attestationData) {
flags |= 0b01000000;
}
if (userVerification) {
flags |= 0b00000100;
}
if (userPresence) {
flags |= 0b00000001;
}
return flags;
}
private string GuidToStandardFormat(byte[] bytes) private string GuidToStandardFormat(byte[] bytes)
{ {
return new Guid(bytes).ToString(); return new Guid(bytes).ToString();
} }
private byte[] GuidToRawFormat(string guid)
{
return Guid.Parse(guid).ToByteArray();
}
} }
} }

View File

@@ -1,11 +1,19 @@
using System; namespace Bit.Core.Utilities.Fido2
namespace Bit.Core.Utilities.Fido2
{ {
public class Fido2AuthenticatorGetAssertionResult public class Fido2AuthenticatorGetAssertionResult
{ {
public byte[] AuthenticatorData { get; set; } public byte[] AuthenticatorData { get; set; }
public byte[] Signature { get; set; } public byte[] Signature { get; set; }
public Fido2AuthenticatorGetAssertionSelectedCredential SelectedCredential { get; set; }
}
public class Fido2AuthenticatorGetAssertionSelectedCredential {
public byte[] Id { get; set; }
#nullable enable
public byte[]? UserHandle { get; set; }
} }
} }

View File

@@ -288,6 +288,42 @@ namespace Bit.Core.Test.Services
)); ));
} }
[Theory]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
// Spec: Increment the credential associated signature counter
public async Task GetAssertionAsync_ReturnsAssertion(SutProvider<Fido2AuthenticatorService> sutProvider, Fido2AuthenticatorGetAssertionParams aParams, Cipher encryptedCipher) {
// Common Arrange
var cipherView = CreateCipherView(null, "bitwarden.com", true);
aParams.RpId = cipherView.Login.MainFido2Credential.RpId;
aParams.AllowCredentialDescriptorList = null;
sutProvider.GetDependency<ICipherService>().GetAllDecryptedAsync().Returns(new List<CipherView> { cipherView });
sutProvider.GetDependency<IFido2UserInterface>().PickCredentialAsync(Arg.Any<Fido2PickCredentialParams>()).Returns(new Fido2PickCredentialResult {
CipherId = cipherView.Id,
UserVerified = true
});
// Arrange
var rpIdHashMock = RandomBytes(32);
sutProvider.GetDependency<ICryptoFunctionService>().HashAsync(aParams.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock);
cipherView.Login.MainFido2Credential.CounterValue = 9000;
// Act
var result = await sutProvider.Sut.GetAssertionAsync(aParams);
// Assert
var encAuthData = result.AuthenticatorData;
var rpIdHash = encAuthData.Take(32);
var flags = encAuthData.Skip(32).Take(1);
var counter = encAuthData.Skip(33).Take(4);
Assert.Equal(result.SelectedCredential.Id, Guid.Parse(cipherView.Login.MainFido2Credential.CredentialId).ToByteArray());
Assert.Equal(result.SelectedCredential.UserHandle, CoreHelpers.Base64UrlDecode(cipherView.Login.MainFido2Credential.UserHandle));
Assert.Equal(rpIdHash, rpIdHashMock);
Assert.Equal(flags, new byte[] { 0b00000101 }); // UP = true, UV = true
Assert.Equal(counter, new byte[] { 0, 0, 0x23, 0x29 }); // 9001 in binary big-endian format
// TODO: Assert signature...
}
[Theory] [Theory]
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })] [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
// Spec: Increment the credential associated signature counter // Spec: Increment the credential associated signature counter
@@ -331,7 +367,8 @@ namespace Bit.Core.Test.Services
new Fido2CredentialView { new Fido2CredentialView {
CredentialId = credentialId ?? Guid.NewGuid().ToString(), CredentialId = credentialId ?? Guid.NewGuid().ToString(),
RpId = rpId ?? "bitwarden.com", RpId = rpId ?? "bitwarden.com",
Discoverable = discoverable.HasValue ? discoverable.ToString() : "true" Discoverable = discoverable.HasValue ? discoverable.ToString() : "true",
UserHandleValue = RandomBytes(32),
} }
} }
} }