1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-05 10:02:47 -05:00

support for user defined kdf parameters

This commit is contained in:
Kyle Spearrin
2018-08-14 15:30:04 -04:00
parent 20f45ca2de
commit 0932189ccb
18 changed files with 470 additions and 3 deletions

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Enums
{
public enum KdfType : byte
{
PBKDF2 = 0
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Bit.Core.Enums;
namespace Bit.Core.Models.Api
{
public class KdfRequestModel : PasswordRequestModel, IValidatableObject
{
[Required]
public KdfType? Kdf { get; set; }
[Required]
public int? KdfIterations { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(Kdf.HasValue && KdfIterations.HasValue)
{
switch(Kdf.Value)
{
case KdfType.PBKDF2:
if(KdfIterations.Value < 5000 || KdfIterations.Value > 1_000_000)
{
yield return new ValidationResult("KDF iterations must be between 5000 and 1000000.");
}
break;
default:
break;
}
}
}
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class PreloginRequestModel
{
[Required]
[EmailAddress]
[StringLength(50)]
public string Email { get; set; }
}
}

View File

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Bit.Core.Enums;
using Bit.Core.Models.Table;
namespace Bit.Core.Models.Api
{
public class RegisterRequestModel
public class RegisterRequestModel : IValidatableObject
{
[StringLength(50)]
public string Name { get; set; }
@ -21,6 +23,8 @@ namespace Bit.Core.Models.Api
public KeysRequestModel Keys { get; set; }
public string Token { get; set; }
public Guid? OrganizationUserId { get; set; }
public KdfType? Kdf { get; set; }
public int? KdfIterations { get; set; }
public User ToUser()
{
@ -28,7 +32,9 @@ namespace Bit.Core.Models.Api
{
Name = Name,
Email = Email,
MasterPasswordHint = MasterPasswordHint
MasterPasswordHint = MasterPasswordHint,
Kdf = Kdf.GetValueOrDefault(KdfType.PBKDF2),
KdfIterations = KdfIterations.GetValueOrDefault(5000)
};
if(Key != null)
@ -43,5 +49,23 @@ namespace Bit.Core.Models.Api
return user;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(Kdf.HasValue && KdfIterations.HasValue)
{
switch(Kdf.Value)
{
case KdfType.PBKDF2:
if(KdfIterations.Value < 5000 || KdfIterations.Value > 1_000_000)
{
yield return new ValidationResult("KDF iterations must be between 5000 and 1000000.");
}
break;
default:
break;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Core.Models.Api
{
public class PreloginResponseModel
{
public PreloginResponseModel(UserKdfInformation kdfInformation)
{
Kdf = kdfInformation.Kdf;
KdfIterations = kdfInformation.KdfIterations;
}
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using System;
using Bit.Core.Enums;
namespace Bit.Core.Models.Data
{
public class UserKdfInformation
{
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
}
}

View File

@ -39,6 +39,8 @@ namespace Bit.Core.Models.Table
public string GatewayCustomerId { get; set; }
public string GatewaySubscriptionId { get; set; }
public string LicenseKey { get; set; }
public KdfType Kdf { get; set; } = KdfType.PBKDF2;
public int KdfIterations { get; set; } = 5000;
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
namespace Bit.Core.Repositories
@ -8,6 +9,7 @@ namespace Bit.Core.Repositories
public interface IUserRepository : IRepository<User, Guid>
{
Task<User> GetByEmailAsync(string email);
Task<UserKdfInformation> GetKdfInformationByEmailAsync(string email);
Task<ICollection<User>> SearchAsync(string email, int skip, int take);
Task<ICollection<User>> GetManyByPremiumAsync(bool premium);
Task<ICollection<User>> GetManyByPremiumRenewalAsync();

View File

@ -4,6 +4,7 @@ using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Models.Data;
using Bit.Core.Models.Table;
using Dapper;
@ -37,6 +38,19 @@ namespace Bit.Core.Repositories.SqlServer
}
}
public async Task<UserKdfInformation> GetKdfInformationByEmailAsync(string email)
{
using(var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<UserKdfInformation>(
$"[{Schema}].[{Table}_ReadKdfByEmail]",
new { Email = email },
commandType: CommandType.StoredProcedure);
return results.SingleOrDefault();
}
}
public async Task<ICollection<User>> SearchAsync(string email, int skip, int take)
{
using(var connection = new SqlConnection(ConnectionString))

View File

@ -30,6 +30,8 @@ namespace Bit.Core.Services
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
string token, string key);
Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key);
Task<IdentityResult> ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key,
KdfType kdf, int kdfIterations);
Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);

View File

@ -446,6 +446,34 @@ namespace Bit.Core.Services
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
public async Task<IdentityResult> ChangeKdfAsync(User user, string masterPassword, string newMasterPassword,
string key, KdfType kdf, int kdfIterations)
{
if(user == null)
{
throw new ArgumentNullException(nameof(user));
}
if(await CheckPasswordAsync(user, masterPassword))
{
var result = await UpdatePasswordHash(user, newMasterPassword);
if(!result.Succeeded)
{
return result;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.Key = key;
user.Kdf = kdf;
user.KdfIterations = kdfIterations;
await _userRepository.ReplaceAsync(user);
return IdentityResult.Success;
}
Logger.LogWarning("Change KDF failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
public async Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders)
{
@ -477,7 +505,7 @@ namespace Bit.Core.Services
return IdentityResult.Success;
}
Logger.LogWarning("Update key for user {userId}.", user.Id);
Logger.LogWarning("Update key failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}