diff --git a/src/Core/Abstractions/ICryptoFunctionService.cs b/src/Core/Abstractions/ICryptoFunctionService.cs index 39b6ba6a1..90fb33ad4 100644 --- a/src/Core/Abstractions/ICryptoFunctionService.cs +++ b/src/Core/Abstractions/ICryptoFunctionService.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; +using Bit.Core.Models.Domain; namespace Bit.Core.Abstractions { @@ -20,6 +21,7 @@ namespace Bit.Core.Abstractions Task HkdfAsync(byte[] ikm, byte[] salt, byte[] info, int outputByteSize, HkdfAlgorithm algorithm); Task HkdfExpandAsync(byte[] prk, string info, int outputByteSize, HkdfAlgorithm algorithm); Task HkdfExpandAsync(byte[] prk, byte[] info, int outputByteSize, HkdfAlgorithm algorithm); + Task SignAsync(byte[] data, byte[] privateKey, ICryptoSignOptions options); Task HashAsync(string value, CryptoHashAlgorithm algorithm); Task HashAsync(byte[] value, CryptoHashAlgorithm algorithm); Task HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm); diff --git a/src/Core/Models/Domain/CryptoSignEcdsaOptions.cs b/src/Core/Models/Domain/CryptoSignEcdsaOptions.cs new file mode 100644 index 000000000..6a944c5d4 --- /dev/null +++ b/src/Core/Models/Domain/CryptoSignEcdsaOptions.cs @@ -0,0 +1,17 @@ +namespace Bit.Core.Models.Domain +{ + public struct CryptoSignEcdsaOptions : ICryptoSignOptions + { + public enum EcdsaAlgorithm : byte { + EcdsaP256Sha256 = 0, + } + + public enum DsaSignatureFormat : byte { + IeeeP1363FixedFieldConcatenation = 0, + Rfc3279DerSequence = 1 + } + + public EcdsaAlgorithm Algorithm { get; set; } + public DsaSignatureFormat SignatureFormat { get; set; } + } +} diff --git a/src/Core/Models/Domain/ICryptoSignOptions.cs b/src/Core/Models/Domain/ICryptoSignOptions.cs new file mode 100644 index 000000000..154a95e0b --- /dev/null +++ b/src/Core/Models/Domain/ICryptoSignOptions.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Models.Domain +{ + public interface ICryptoSignOptions + { + } +} diff --git a/src/Core/Models/View/Fido2CredentialView.cs b/src/Core/Models/View/Fido2CredentialView.cs index 2cf74f380..c0e4b6c49 100644 --- a/src/Core/Models/View/Fido2CredentialView.cs +++ b/src/Core/Models/View/Fido2CredentialView.cs @@ -38,6 +38,11 @@ namespace Bit.Core.Models.View set => UserHandle = value == null ? null : CoreHelpers.Base64UrlEncode(value); } + public byte[] KeyBytes { + get => KeyValue == null ? null : CoreHelpers.Base64UrlDecode(KeyValue); + set => KeyValue = value == null ? null : CoreHelpers.Base64UrlEncode(value); + } + public override string SubTitle => UserName; public override List> LinkedFieldOptions => new List>(); public bool IsDiscoverable => bool.TryParse(Discoverable, out var isDiscoverable) && isDiscoverable; diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index 1816457d6..810c401e2 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -1,6 +1,7 @@ using Bit.Core.Abstractions; using Bit.Core.Models.View; using Bit.Core.Enums; +using Bit.Core.Models.Domain; using Bit.Core.Utilities.Fido2; using System.Buffers.Binary; @@ -88,15 +89,15 @@ namespace Bit.Core.Services rpId: selectedFido2Credential.RpId, userPresence: true, userVerification: userVerified, - counter: selectedFido2Credential.CounterValue); + counter: selectedFido2Credential.CounterValue + ); - // const signature = await generateSignature({ - // authData: authenticatorData, - // clientDataHash: params.hash, - // privateKey: await getPrivateKeyFromFido2Credential(selectedFido2Credential), - // }); + var signature = await GenerateSignature( + authData: authenticatorData, + clientDataHash: assertionParams.Hash, + privateKey: selectedFido2Credential.KeyBytes + ); - // TODO: IMPLEMENT this return new Fido2AuthenticatorGetAssertionResult { SelectedCredential = new Fido2AuthenticatorGetAssertionSelectedCredential @@ -105,7 +106,7 @@ namespace Bit.Core.Services UserHandle = selectedFido2Credential.UserHandleValue }, AuthenticatorData = authenticatorData, - Signature = new byte[8] + Signature = signature }; } catch { _logService.Info( @@ -204,6 +205,21 @@ namespace Bit.Core.Services return flags; } + private async Task GenerateSignature( + byte[] authData, + byte[] clientDataHash, + byte[] privateKey + ) + { + var sigBase = authData.Concat(clientDataHash).ToArray(); + var signature = await _cryptoFunctionService.SignAsync(sigBase, privateKey, new CryptoSignEcdsaOptions + { + Algorithm = CryptoSignEcdsaOptions.EcdsaAlgorithm.EcdsaP256Sha256, + SignatureFormat = CryptoSignEcdsaOptions.DsaSignatureFormat.Rfc3279DerSequence + }); + + return signature; + } private string GuidToStandardFormat(byte[] bytes) { diff --git a/src/Core/Services/PclCryptoFunctionService.cs b/src/Core/Services/PclCryptoFunctionService.cs index 50c555065..3a55db662 100644 --- a/src/Core/Services/PclCryptoFunctionService.cs +++ b/src/Core/Services/PclCryptoFunctionService.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Bit.Core.Abstractions; using Bit.Core.Enums; +using Bit.Core.Models.Domain; using PCLCrypto; using static PCLCrypto.WinRTCrypto; @@ -121,6 +122,16 @@ namespace Bit.Core.Services return okm.Take(outputByteSize).ToArray(); } + public Task SignAsync(byte[] data, byte[] privateKey, ICryptoSignOptions options) + { + throw new NotSupportedException(); + + // Not supported on iOS and Android + // var provider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.EcdsaP256Sha256); + // var cryptoKey = provider.ImportKeyPair(privateKey, CryptographicPrivateKeyBlobType.Pkcs8RawPrivateKeyInfo); + // return Task.FromResult(CryptographicEngine.Sign(cryptoKey, data)); + } + public Task HashAsync(string value, CryptoHashAlgorithm algorithm) { return HashAsync(Encoding.UTF8.GetBytes(value), algorithm); diff --git a/test/Core.Test/Services/Fido2AuthenticatorTests.cs b/test/Core.Test/Services/Fido2AuthenticatorTests.cs index 2f8ec44f8..0708803b7 100644 --- a/test/Core.Test/Services/Fido2AuthenticatorTests.cs +++ b/test/Core.Test/Services/Fido2AuthenticatorTests.cs @@ -306,6 +306,15 @@ namespace Bit.Core.Test.Services var rpIdHashMock = RandomBytes(32); sutProvider.GetDependency().HashAsync(aParams.RpId, CryptoHashAlgorithm.Sha256).Returns(rpIdHashMock); cipherView.Login.MainFido2Credential.CounterValue = 9000; + var signatureMock = RandomBytes(32); + sutProvider.GetDependency().SignAsync( + Arg.Any(), + Arg.Any(), + new CryptoSignEcdsaOptions { + Algorithm = CryptoSignEcdsaOptions.EcdsaAlgorithm.EcdsaP256Sha256, + SignatureFormat = CryptoSignEcdsaOptions.DsaSignatureFormat.Rfc3279DerSequence + } + ).Returns(signatureMock); // Act var result = await sutProvider.Sut.GetAssertionAsync(aParams); @@ -316,12 +325,12 @@ namespace Bit.Core.Test.Services 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... + Assert.Equal(Guid.Parse(cipherView.Login.MainFido2Credential.CredentialId).ToByteArray(), result.SelectedCredential.Id); + Assert.Equal(CoreHelpers.Base64UrlDecode(cipherView.Login.MainFido2Credential.UserHandle), result.SelectedCredential.UserHandle); + Assert.Equal(rpIdHashMock, rpIdHash); + Assert.Equal(new byte[] { 0b00000101 }, flags); // UP = true, UV = true + Assert.Equal(new byte[] { 0, 0, 0x23, 0x29 }, counter); // 9001 in binary big-endian format + Assert.Equal(signatureMock, result.Signature); } [Theory]