From ce550fee745f4c7b248bca2e2416dd7e84f9ae27 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 23 Jan 2024 14:29:20 +0100 Subject: [PATCH] [PM-5731] feat: scaffold make credential --- .../IFido2AuthenticatorService.cs | 1 + .../Services/Fido2AuthenticatorService.cs | 189 +++++++++--------- .../Fido2AuthenticatorMakeCredentialParams.cs | 48 +++++ .../Fido2AuthenticatorMakeCredentialResult.cs | 16 ++ .../PublicKeyUserCredentialUserEntity.cs | 9 + 5 files changed, 171 insertions(+), 92 deletions(-) create mode 100644 src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs create mode 100644 src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialResult.cs create mode 100644 src/Core/Utilities/Fido2/PublicKeyUserCredentialUserEntity.cs diff --git a/src/Core/Abstractions/IFido2AuthenticatorService.cs b/src/Core/Abstractions/IFido2AuthenticatorService.cs index b6b160928..3cd861c62 100644 --- a/src/Core/Abstractions/IFido2AuthenticatorService.cs +++ b/src/Core/Abstractions/IFido2AuthenticatorService.cs @@ -4,6 +4,7 @@ namespace Bit.Core.Abstractions { public interface IFido2AuthenticatorService { + Task MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams); Task GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams); } } diff --git a/src/Core/Services/Fido2AuthenticatorService.cs b/src/Core/Services/Fido2AuthenticatorService.cs index f29893a4e..c2e9c4c88 100644 --- a/src/Core/Services/Fido2AuthenticatorService.cs +++ b/src/Core/Services/Fido2AuthenticatorService.cs @@ -116,118 +116,123 @@ namespace Bit.Core.Services } } - private async Task> FindCredentialsById(PublicKeyCredentialDescriptor[] credentials, string rpId) - { - var ids = new List(); + public Task MakeCredentialAsync(Fido2AuthenticatorMakeCredentialParams makeCredentialParams) { + throw new NotImplementedException(); + } - foreach (var credential in credentials) + private async Task> FindCredentialsById(PublicKeyCredentialDescriptor[] credentials, string rpId) { - try + var ids = new List(); + + foreach (var credential in credentials) { - ids.Add(GuidToStandardFormat(credential.Id)); + try + { + ids.Add(GuidToStandardFormat(credential.Id)); + } + catch {} } - catch {} + + if (ids.Count == 0) + { + return new List(); + } + + var ciphers = await _cipherService.GetAllDecryptedAsync(); + return ciphers.FindAll((cipher) => + !cipher.IsDeleted && + cipher.Type == CipherType.Login && + cipher.Login.HasFido2Credentials && + cipher.Login.MainFido2Credential.RpId == rpId && + ids.Contains(cipher.Login.MainFido2Credential.CredentialId) + ); } - if (ids.Count == 0) + private async Task> FindCredentialsByRp(string rpId) { - return new List(); + var ciphers = await _cipherService.GetAllDecryptedAsync(); + return ciphers.FindAll((cipher) => + !cipher.IsDeleted && + cipher.Type == CipherType.Login && + cipher.Login.HasFido2Credentials && + cipher.Login.MainFido2Credential.RpId == rpId && + cipher.Login.MainFido2Credential.IsDiscoverable + ); } - var ciphers = await _cipherService.GetAllDecryptedAsync(); - return ciphers.FindAll((cipher) => - !cipher.IsDeleted && - cipher.Type == CipherType.Login && - cipher.Login.HasFido2Credentials && - cipher.Login.MainFido2Credential.RpId == rpId && - ids.Contains(cipher.Login.MainFido2Credential.CredentialId) - ); - } + private async Task GenerateAuthData( + string rpId, + bool userVerification, + bool userPresence, + int counter + // byte[] credentialId, + // CryptoKey? cryptoKey - only needed for attestation + ) { + List authData = new List(); - private async Task> FindCredentialsByRp(string rpId) - { - var ciphers = await _cipherService.GetAllDecryptedAsync(); - return ciphers.FindAll((cipher) => - !cipher.IsDeleted && - cipher.Type == CipherType.Login && - cipher.Login.HasFido2Credentials && - cipher.Login.MainFido2Credential.RpId == rpId && - cipher.Login.MainFido2Credential.IsDiscoverable - ); - } + var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256); + authData.AddRange(rpIdHash); - private async Task GenerateAuthData( - string rpId, - bool userVerification, - bool userPresence, - int counter - // byte[] credentialId, - // CryptoKey? cryptoKey - only needed for attestation - ) { - List authData = new List(); + var flags = AuthDataFlags(false, false, userVerification, userPresence); + authData.Add(flags); - var rpIdHash = await _cryptoFunctionService.HashAsync(rpId, CryptoHashAlgorithm.Sha256); - authData.AddRange(rpIdHash); + authData.AddRange([ + (byte)(counter >> 24), + (byte)(counter >> 16), + (byte)(counter >> 8), + (byte)counter + ]); - 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; + return authData.ToArray(); } - if (attestationData) { - flags |= 0b01000000; + 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; } - if (userVerification) { - flags |= 0b00000100; - } - - if (userPresence) { - flags |= 0b00000001; - } - - 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 + private async Task GenerateSignature( + byte[] authData, + byte[] clientDataHash, + byte[] privateKey + ) { - Algorithm = CryptoSignEcdsaOptions.EcdsaAlgorithm.EcdsaP256Sha256, - SignatureFormat = CryptoSignEcdsaOptions.DsaSignatureFormat.Rfc3279DerSequence - }); + 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; - } + return signature; + } - private string GuidToStandardFormat(byte[] bytes) - { - return new Guid(bytes).ToString(); - } + private string GuidToStandardFormat(byte[] bytes) + { + return new Guid(bytes).ToString(); + } + + private byte[] GuidToRawFormat(string guid) + { + return Guid.Parse(guid).ToByteArray(); + } - private byte[] GuidToRawFormat(string guid) - { - return Guid.Parse(guid).ToByteArray(); - } } } diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs new file mode 100644 index 000000000..9cc7e0191 --- /dev/null +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialParams.cs @@ -0,0 +1,48 @@ +namespace Bit.Core.Utilities.Fido2 +{ + public class Fido2AuthenticatorMakeCredentialParams + { + /// + /// The caller’s RP ID, as determined by the user agent and the client. */ + /// + public string RpId { get; set; } + + /// + /// The Relying Party's PublicKeyCredentialRpEntity. */ + /// + public PublicKeyCredentialUserEntity UserEntity { get; set; } + + /// + /// The hash of the serialized client data, provided by the client. */ + /// + public byte[] Hash { get; set; } + + /// + /// A sequence of pairs of PublicKeyCredentialType and public key algorithms (COSEAlgorithmIdentifier) requested by the Relying Party. This sequence is ordered from most preferred to least preferred. The authenticator makes a best-effort to create the most preferred credential that it can. */ + /// + public PublicKeyCredentialDescriptor[] CredTypesAndPubKeyAlgs { get; set; } + + /// + /// The effective resident key requirement for credential creation, a Boolean value determined by the client. Resident is synonymous with discoverable. */ + /// + public bool RequireResidentKey { get; set; } + + /// + /// The effective user verification requirement for assertion, a Boolean value provided by the client. */ + /// + public bool RequireUserVerification { get; set; } + + /// + /// CTAP2 authenticators support setting this to false, but we only support the WebAuthn authenticator model which does not have that option. */ + /// + // public bool RequireUserPresence { get; set; } // Always required + + /// + /// The authenticator's attestation preference, a string provided by the client. This is a hint that the client gives to the authenticator about what kind of attestation statement it would like. The authenticator makes a best-effort to satisfy the preference. + /// Note: Attestation statements are not supported at this time. + /// + // public string AttestationPreference { get; set; } + + public object Extensions { get; set; } + } +} diff --git a/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialResult.cs b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialResult.cs new file mode 100644 index 000000000..a566e03c4 --- /dev/null +++ b/src/Core/Utilities/Fido2/Fido2AuthenticatorMakeCredentialResult.cs @@ -0,0 +1,16 @@ + +namespace Bit.Core.Utilities.Fido2 +{ + public class Fido2AuthenticatorMakeCredentialResult + { + public byte[] CredentialId { get; set; } + + public byte[] AttestationObject { get; set; } + + public byte[] AuthData { get; set; } + + public byte[] PublicKey { get; set; } + + public int PublicKeyAlgorithm { get; set; } + } +} diff --git a/src/Core/Utilities/Fido2/PublicKeyUserCredentialUserEntity.cs b/src/Core/Utilities/Fido2/PublicKeyUserCredentialUserEntity.cs new file mode 100644 index 000000000..d931d1a85 --- /dev/null +++ b/src/Core/Utilities/Fido2/PublicKeyUserCredentialUserEntity.cs @@ -0,0 +1,9 @@ +namespace Bit.Core.Utilities.Fido2 +{ + public class PublicKeyCredentialUserEntity { + public byte[] Id { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public string Icon { get; set; } + } +}