Browse Source

[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
pull/3417/head
Andreas Coroiu 2 years ago committed by GitHub
parent
commit
e401fc0983
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Api/Auth/Controllers/WebAuthnController.cs
  2. 16
      src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs
  3. 5
      src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs
  4. 19
      src/Core/Auth/Entities/WebAuthnCredential.cs
  5. 8
      src/Core/Auth/Enums/WebAuthnPrfStatus.cs
  6. 2
      src/Core/Services/IUserService.cs
  7. 12
      src/Core/Services/Implementations/UserService.cs
  8. 2
      test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs
  9. 2
      test/Core.Test/Services/UserServiceTests.cs

2
src/Api/Auth/Controllers/WebAuthnController.cs

@ -75,7 +75,7 @@ public class WebAuthnController : Controller @@ -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.");

16
src/Api/Auth/Models/Request/WebAuthn/WebAuthnCredentialRequestModel.cs

@ -1,4 +1,5 @@ @@ -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 @@ -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; }
}

5
src/Api/Auth/Models/Response/WebAuthn/WebAuthnCredentialResponseModel.cs

@ -1,4 +1,5 @@ @@ -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 @@ -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; }
}

19
src/Core/Auth/Entities/WebAuthnCredential.cs

@ -1,4 +1,5 @@ @@ -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> @@ -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> @@ -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

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace Bit.Core.Auth.Enums;
public enum WebAuthnPrfStatus
{
Enabled = 0,
Supported = 1,
Unsupported = 2
}

2
src/Core/Services/IUserService.cs

@ -28,7 +28,7 @@ public interface IUserService @@ -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);

12
src/Core/Services/Implementations/UserService.cs

@ -552,9 +552,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable @@ -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 @@ -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);

2
test/Api.Test/Auth/Controllers/WebAuthnControllerTests.cs

@ -116,7 +116,7 @@ public class WebAuthnControllerTests @@ -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)

2
test/Core.Test/Services/UserServiceTests.cs

@ -195,7 +195,7 @@ public class UserServiceTests @@ -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…
Cancel
Save