diff --git a/src/Api/Auth/Controllers/WebAuthnController.cs b/src/Api/Auth/Controllers/WebAuthnController.cs index 908915662d..e62d1a95ba 100644 --- a/src/Api/Auth/Controllers/WebAuthnController.cs +++ b/src/Api/Auth/Controllers/WebAuthnController.cs @@ -75,7 +75,7 @@ public class WebAuthnController : Controller throw new BadRequestException("The token associated with your request is expired. A valid token is required to continue."); } - var success = await _userService.CompleteWebAuthLoginRegistrationAsync(user, model.Name, tokenable.Options, model.DeviceResponse); + var success = await _userService.CompleteWebAuthLoginRegistrationAsync(user, model.Name, tokenable.Options, model.DeviceResponse, model.SupportsPrf, model.EncryptedUserKey, model.EncryptedPublicKey, model.EncryptedPrivateKey); if (!success) { throw new BadRequestException("Unable to complete WebAuthn registration."); diff --git a/src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs b/src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs index 8f16fe7f50..43eae3a805 100644 --- a/src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs +++ b/src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Utilities; using Fido2NetLib; namespace Bit.Api.Auth.Models.Request.Webauthn; @@ -13,5 +14,20 @@ public class WebAuthnCredentialRequestModel [Required] public string Token { get; set; } + + [Required] + public bool SupportsPrf { get; set; } + + [EncryptedString] + [EncryptedStringLength(2000)] + public string EncryptedUserKey { get; set; } + + [EncryptedString] + [EncryptedStringLength(2000)] + public string EncryptedPublicKey { get; set; } + + [EncryptedString] + [EncryptedStringLength(2000)] + public string EncryptedPrivateKey { get; set; } } diff --git a/src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs b/src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs index 0e358c751d..01cf2559a6 100644 --- a/src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs +++ b/src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs @@ -1,4 +1,5 @@ using Bit.Core.Auth.Entities; +using Bit.Core.Auth.Enums; using Bit.Core.Models.Api; namespace Bit.Api.Auth.Models.Response.WebAuthn; @@ -11,10 +12,10 @@ public class WebAuthnCredentialResponseModel : ResponseModel { Id = credential.Id.ToString(); Name = credential.Name; - PrfSupport = false; + PrfStatus = credential.GetPrfStatus(); } public string Id { get; set; } public string Name { get; set; } - public bool PrfSupport { get; set; } + public WebAuthnPrfStatus PrfStatus { get; set; } } diff --git a/src/Core/Auth/Entities/WebAuthnCredential.cs b/src/Core/Auth/Entities/WebAuthnCredential.cs index b4b80ff654..486fd41e3f 100644 --- a/src/Core/Auth/Entities/WebAuthnCredential.cs +++ b/src/Core/Auth/Entities/WebAuthnCredential.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Auth.Enums; using Bit.Core.Entities; using Bit.Core.Utilities; @@ -18,8 +19,11 @@ public class WebAuthnCredential : ITableObject [MaxLength(20)] public string Type { get; set; } public Guid AaGuid { get; set; } + [MaxLength(2000)] public string EncryptedUserKey { get; set; } + [MaxLength(2000)] public string EncryptedPrivateKey { get; set; } + [MaxLength(2000)] public string EncryptedPublicKey { get; set; } public bool SupportsPrf { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; @@ -29,4 +33,19 @@ public class WebAuthnCredential : ITableObject { Id = CoreHelpers.GenerateComb(); } + + public WebAuthnPrfStatus GetPrfStatus() + { + if (!SupportsPrf) + { + return WebAuthnPrfStatus.Unsupported; + } + + if (EncryptedUserKey != null && EncryptedPrivateKey != null && EncryptedPublicKey != null) + { + return WebAuthnPrfStatus.Enabled; + } + + return WebAuthnPrfStatus.Supported; + } } diff --git a/src/Core/Auth/Enums/WebAuthnPrfStatus.cs b/src/Core/Auth/Enums/WebAuthnPrfStatus.cs new file mode 100644 index 0000000000..4977aacf71 --- /dev/null +++ b/src/Core/Auth/Enums/WebAuthnPrfStatus.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Auth.Enums; + +public enum WebAuthnPrfStatus +{ + Enabled = 0, + Supported = 1, + Unsupported = 2 +} diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 3340ec6a70..14401548b2 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -28,7 +28,7 @@ public interface IUserService Task DeleteWebAuthnKeyAsync(User user, int id); Task CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse); Task StartWebAuthnLoginRegistrationAsync(User user); - Task CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse); + Task CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf, string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null); Task StartWebAuthnLoginAssertionAsync(User user); Task CompleteWebAuthLoginAssertionAsync(AuthenticatorAssertionRawResponse assertionResponse, User user); Task SendEmailVerificationAsync(User user); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index abe01d2e9a..3792b30cf4 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -552,9 +552,9 @@ public class UserService : UserManager, IUserService, IDisposable return options; } - public async Task CompleteWebAuthLoginRegistrationAsync(User user, string name, - CredentialCreateOptions options, - AuthenticatorAttestationRawResponse attestationResponse) + public async Task CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, + AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf, + string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null) { var existingCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id); if (existingCredentials.Count >= 5) @@ -575,7 +575,11 @@ public class UserService : UserManager, IUserService, IDisposable Type = success.Result.CredType, AaGuid = success.Result.Aaguid, Counter = (int)success.Result.Counter, - UserId = user.Id + UserId = user.Id, + SupportsPrf = supportsPrf, + EncryptedUserKey = encryptedUserKey, + EncryptedPublicKey = encryptedPublicKey, + EncryptedPrivateKey = encryptedPrivateKey }; await _webAuthnCredentialRepository.CreateAsync(credential); diff --git a/test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs b/test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs index dd5ffb15fb..23908c334f 100644 --- a/test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs @@ -116,7 +116,7 @@ public class WebAuthnControllerTests .GetUserByPrincipalAsync(default) .ReturnsForAnyArgs(user); sutProvider.GetDependency() - .CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any()) + .CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any(), requestModel.SupportsPrf, requestModel.EncryptedUserKey, requestModel.EncryptedPublicKey, requestModel.EncryptedPrivateKey) .Returns(true); sutProvider.GetDependency>() .Unprotect(requestModel.Token) diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index cbedea4a7a..724532f3c5 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -195,7 +195,7 @@ public class UserServiceTests sutProvider.GetDependency().GetManyByUserIdAsync(user.Id).Returns(existingCredentials); // Act - var result = await sutProvider.Sut.CompleteWebAuthLoginRegistrationAsync(user, "name", options, response); + var result = await sutProvider.Sut.CompleteWebAuthLoginRegistrationAsync(user, "name", options, response, false, null, null, null); // Assert Assert.False(result);