mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[PM-4167] Add PRF attestation flow during passkey registration (#3339)
* [PM-4167] feat: add support for `SupportsPrf` * [PM-4167] feat: add `prfStatus` property * [PM-4167] feat: add support for storing PRF keys * [PM-4167] fix: allow credentials to be created without encryption support * [PM-4167] fix: broken test * [PM-4167] chore: remove whitespace * [PM-4167] fix: controller test * [PM-4167] chore: improve readability of `GetPrfStatus` * [PM-4167] fix: make prf optional * [PM-4167] fix: commit missing controller change * [PM-4167] fix: tests
This commit is contained in:
parent
8256b58e00
commit
e401fc0983
@ -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.");
|
||||
|
@ -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; }
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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<Guid>
|
||||
[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<Guid>
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
|
||||
public WebAuthnPrfStatus GetPrfStatus()
|
||||
{
|
||||
if (!SupportsPrf)
|
||||
{
|
||||
return WebAuthnPrfStatus.Unsupported;
|
||||
}
|
||||
|
||||
if (EncryptedUserKey != null && EncryptedPrivateKey != null && EncryptedPublicKey != null)
|
||||
{
|
||||
return WebAuthnPrfStatus.Enabled;
|
||||
}
|
||||
|
||||
return WebAuthnPrfStatus.Supported;
|
||||
}
|
||||
}
|
||||
|
8
src/Core/Auth/Enums/WebAuthnPrfStatus.cs
Normal file
8
src/Core/Auth/Enums/WebAuthnPrfStatus.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Auth.Enums;
|
||||
|
||||
public enum WebAuthnPrfStatus
|
||||
{
|
||||
Enabled = 0,
|
||||
Supported = 1,
|
||||
Unsupported = 2
|
||||
}
|
@ -28,7 +28,7 @@ public interface IUserService
|
||||
Task<bool> DeleteWebAuthnKeyAsync(User user, int id);
|
||||
Task<bool> CompleteWebAuthRegistrationAsync(User user, int value, string name, AuthenticatorAttestationRawResponse attestationResponse);
|
||||
Task<CredentialCreateOptions> StartWebAuthnLoginRegistrationAsync(User user);
|
||||
Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse);
|
||||
Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name, CredentialCreateOptions options, AuthenticatorAttestationRawResponse attestationResponse, bool supportsPrf, string encryptedUserKey = null, string encryptedPublicKey = null, string encryptedPrivateKey = null);
|
||||
Task<AssertionOptions> StartWebAuthnLoginAssertionAsync(User user);
|
||||
Task<string> CompleteWebAuthLoginAssertionAsync(AuthenticatorAssertionRawResponse assertionResponse, User user);
|
||||
Task SendEmailVerificationAsync(User user);
|
||||
|
@ -552,9 +552,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
return options;
|
||||
}
|
||||
|
||||
public async Task<bool> CompleteWebAuthLoginRegistrationAsync(User user, string name,
|
||||
CredentialCreateOptions options,
|
||||
AuthenticatorAttestationRawResponse attestationResponse)
|
||||
public async Task<bool> 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<User>, 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);
|
||||
|
@ -116,7 +116,7 @@ public class WebAuthnControllerTests
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>())
|
||||
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>(), requestModel.SupportsPrf, requestModel.EncryptedUserKey, requestModel.EncryptedPublicKey, requestModel.EncryptedPrivateKey)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
||||
.Unprotect(requestModel.Token)
|
||||
|
@ -195,7 +195,7 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().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);
|
||||
|
Loading…
Reference in New Issue
Block a user