diff --git a/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/AssertWebAuthnLoginCredentialCommand.cs b/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/AssertWebAuthnLoginCredentialCommand.cs index 88e5ec32b2f9..2764c8ebb54d 100644 --- a/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/AssertWebAuthnLoginCredentialCommand.cs +++ b/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/AssertWebAuthnLoginCredentialCommand.cs @@ -26,13 +26,13 @@ public AssertWebAuthnLoginCredentialCommand(IFido2 fido2, IWebAuthnCredentialRep { if (!GuidUtilities.TryParseBytes(assertionResponse.Response.UserHandle, out var userId)) { - throw new BadRequestException("Invalid credential."); + ThrowInvalidCredentialException(); } var user = await _userRepository.GetByIdAsync(userId); if (user == null) { - throw new BadRequestException("Invalid credential."); + ThrowInvalidCredentialException(); } var userCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id); @@ -40,14 +40,23 @@ public AssertWebAuthnLoginCredentialCommand(IFido2 fido2, IWebAuthnCredentialRep var credential = userCredentials.FirstOrDefault(c => c.CredentialId == assertedCredentialId); if (credential == null) { - throw new BadRequestException("Invalid credential."); + ThrowInvalidCredentialException(); } // Always return true, since we've already filtered the credentials after user id IsUserHandleOwnerOfCredentialIdAsync callback = (args, cancellationToken) => Task.FromResult(true); var credentialPublicKey = CoreHelpers.Base64UrlDecode(credential.PublicKey); - var assertionVerificationResult = await _fido2.MakeAssertionAsync( - assertionResponse, options, credentialPublicKey, (uint)credential.Counter, callback); + + Fido2NetLib.Objects.AssertionVerificationResult assertionVerificationResult = null; + try + { + assertionVerificationResult = await _fido2.MakeAssertionAsync( + assertionResponse, options, credentialPublicKey, (uint)credential.Counter, callback); + } + catch (Fido2VerificationException) + { + ThrowInvalidCredentialException(); + } // Update SignatureCounter credential.Counter = (int)assertionVerificationResult.Counter; @@ -55,9 +64,14 @@ public AssertWebAuthnLoginCredentialCommand(IFido2 fido2, IWebAuthnCredentialRep if (assertionVerificationResult.Status != "ok") { - throw new BadRequestException("Invalid credential."); + ThrowInvalidCredentialException(); } return (user, credential); } + + private void ThrowInvalidCredentialException() + { + throw new BadRequestException("Invalid credential."); + } } diff --git a/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/CreateWebAuthnLoginCredentialCommand.cs b/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/CreateWebAuthnLoginCredentialCommand.cs index 65c98dea3b63..5eebc8b7e20e 100644 --- a/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/CreateWebAuthnLoginCredentialCommand.cs +++ b/src/Core/Auth/UserFeatures/WebAuthnLogin/Implementations/CreateWebAuthnLoginCredentialCommand.cs @@ -3,20 +3,25 @@ using Bit.Core.Entities; using Bit.Core.Utilities; using Fido2NetLib; +using Microsoft.Extensions.Logging; namespace Bit.Core.Auth.UserFeatures.WebAuthnLogin.Implementations; -internal class CreateWebAuthnLoginCredentialCommand : ICreateWebAuthnLoginCredentialCommand +public class CreateWebAuthnLoginCredentialCommand : ICreateWebAuthnLoginCredentialCommand { public const int MaxCredentialsPerUser = 5; private readonly IFido2 _fido2; private readonly IWebAuthnCredentialRepository _webAuthnCredentialRepository; + private readonly ILogger _logger; - public CreateWebAuthnLoginCredentialCommand(IFido2 fido2, IWebAuthnCredentialRepository webAuthnCredentialRepository) + public CreateWebAuthnLoginCredentialCommand(IFido2 fido2, + IWebAuthnCredentialRepository webAuthnCredentialRepository, + ILogger logger) { _fido2 = fido2; _webAuthnCredentialRepository = webAuthnCredentialRepository; + _logger = logger; } public async Task CreateWebAuthnLoginCredentialAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf, string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null) @@ -30,16 +35,25 @@ public async Task CreateWebAuthnLoginCredentialAsync(User user, string nam var existingCredentialIds = existingCredentials.Select(c => c.CredentialId); IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => Task.FromResult(!existingCredentialIds.Contains(CoreHelpers.Base64UrlEncode(args.CredentialId))); - var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); + Fido2.CredentialMakeResult credentialResponse = null; + try + { + credentialResponse = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); + } + catch (Fido2VerificationException e) + { + _logger.LogError(e, "Unable to verify WebAuthn credential."); + return false; + } var credential = new WebAuthnCredential { Name = name, - CredentialId = CoreHelpers.Base64UrlEncode(success.Result.CredentialId), - PublicKey = CoreHelpers.Base64UrlEncode(success.Result.PublicKey), - Type = success.Result.CredType, - AaGuid = success.Result.Aaguid, - Counter = (int)success.Result.Counter, + CredentialId = CoreHelpers.Base64UrlEncode(credentialResponse.Result.CredentialId), + PublicKey = CoreHelpers.Base64UrlEncode(credentialResponse.Result.PublicKey), + Type = credentialResponse.Result.CredType, + AaGuid = credentialResponse.Result.Aaguid, + Counter = (int)credentialResponse.Result.Counter, UserId = user.Id, SupportsPrf = supportsPrf, EncryptedUserKey = encryptedUserKey, diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index cd1d58acd30d..ad3b30e6af7e 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -407,19 +407,28 @@ public async Task CompleteWebAuthRegistrationAsync(User user, int id, stri // account uses the same 2FA key. IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => Task.FromResult(true); - var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); + Fido2.CredentialMakeResult credentialResponse = null; + try + { + credentialResponse = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback); + } + catch (Fido2VerificationException e) + { + base.Logger.LogError(e, "Unable to verify WebAuthn credential."); + return false; + } provider.MetaData.Remove("pending"); provider.MetaData[keyId] = new TwoFactorProvider.WebAuthnData { Name = name, - Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId), - PublicKey = success.Result.PublicKey, - UserHandle = success.Result.User.Id, - SignatureCounter = success.Result.Counter, - CredType = success.Result.CredType, + Descriptor = new PublicKeyCredentialDescriptor(credentialResponse.Result.CredentialId), + PublicKey = credentialResponse.Result.PublicKey, + UserHandle = credentialResponse.Result.User.Id, + SignatureCounter = credentialResponse.Result.Counter, + CredType = credentialResponse.Result.CredType, RegDate = DateTime.Now, - AaGuid = success.Result.Aaguid + AaGuid = credentialResponse.Result.Aaguid }; var providers = user.GetTwoFactorProviders();