mirror of
https://github.com/bitwarden/server.git
synced 2025-06-20 02:48:03 -05:00
Add migration to user encryption v2
This commit is contained in:
parent
68237d0b6d
commit
ec955565e6
@ -108,6 +108,7 @@ public class AccountsKeyManagementController : Controller
|
|||||||
|
|
||||||
UserKeyEncryptedAccountPrivateKey = model.AccountKeys.UserKeyEncryptedAccountPrivateKey,
|
UserKeyEncryptedAccountPrivateKey = model.AccountKeys.UserKeyEncryptedAccountPrivateKey,
|
||||||
AccountPublicKey = model.AccountKeys.AccountPublicKey,
|
AccountPublicKey = model.AccountKeys.AccountPublicKey,
|
||||||
|
AccountKeys = model.AccountKeys,
|
||||||
|
|
||||||
MasterPasswordUnlockData = model.AccountUnlockData.MasterPasswordUnlockData.ToUnlockData(),
|
MasterPasswordUnlockData = model.AccountUnlockData.MasterPasswordUnlockData.ToUnlockData(),
|
||||||
EmergencyAccesses = await _emergencyAccessValidator.ValidateAsync(user, model.AccountUnlockData.EmergencyAccessUnlockData),
|
EmergencyAccesses = await _emergencyAccessValidator.ValidateAsync(user, model.AccountUnlockData.EmergencyAccessUnlockData),
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.KeyManagement.Models.Data.Models;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Api.KeyManagement.Models.Requests;
|
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||||
@ -7,4 +9,31 @@ public class AccountKeysRequestModel
|
|||||||
{
|
{
|
||||||
[EncryptedString] public required string UserKeyEncryptedAccountPrivateKey { get; set; }
|
[EncryptedString] public required string UserKeyEncryptedAccountPrivateKey { get; set; }
|
||||||
public required string AccountPublicKey { get; set; }
|
public required string AccountPublicKey { get; set; }
|
||||||
|
|
||||||
|
public PublicKeyEncryptionKeyPairRequestModel? PublicKeyEncryptionKeyPair { get; set; }
|
||||||
|
public SignatureKeyPairRequestModel? SignatureKeyPair { get; set; }
|
||||||
|
|
||||||
|
public UserAccountKeysData ToAccountKeysData()
|
||||||
|
{
|
||||||
|
// This will be cleaned up, after a compatibility period, at which point PublicKeyEncryptionKeyPair and SignatureKeyPair will be required.
|
||||||
|
if (PublicKeyEncryptionKeyPair == null || SignatureKeyPair == null)
|
||||||
|
{
|
||||||
|
return new UserAccountKeysData
|
||||||
|
{
|
||||||
|
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData
|
||||||
|
(
|
||||||
|
UserKeyEncryptedAccountPrivateKey,
|
||||||
|
AccountPublicKey
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new UserAccountKeysData
|
||||||
|
{
|
||||||
|
PublicKeyEncryptionKeyPairData = PublicKeyEncryptionKeyPair.ToPublicKeyEncryptionKeyPairData(),
|
||||||
|
SignatureKeyPairData = SignatureKeyPair.ToSignatureKeyPairData()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||||
|
|
||||||
|
public class PrivateEncryptionKeyPairRequestModel
|
||||||
|
{
|
||||||
|
[EncryptedString] public required string WrappedPrivateKey { get; set; }
|
||||||
|
public required string PublicKey { get; set; }
|
||||||
|
public required string SignedPublicKey { get; set; }
|
||||||
|
|
||||||
|
public PublicKeyEncryptionKeyPairData ToPrivateKeyEncryptionKeyPairData()
|
||||||
|
{
|
||||||
|
return new PublicKeyEncryptionKeyPairData(
|
||||||
|
WrappedPrivateKey,
|
||||||
|
PublicKey,
|
||||||
|
SignedPublicKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||||
|
|
||||||
|
public class PublicKeyEncryptionKeyPairRequestModel
|
||||||
|
{
|
||||||
|
[EncryptedString] public required string WrappedPrivateKey { get; set; }
|
||||||
|
public required string PublicKey { get; set; }
|
||||||
|
public required string SignedPublicKey { get; set; }
|
||||||
|
|
||||||
|
public PublicKeyEncryptionKeyPairData ToPublicKeyEncryptionKeyPairData()
|
||||||
|
{
|
||||||
|
return new PublicKeyEncryptionKeyPairData(
|
||||||
|
WrappedPrivateKey,
|
||||||
|
PublicKey,
|
||||||
|
SignedPublicKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Bit.Core.KeyManagement.Enums;
|
||||||
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||||
|
|
||||||
|
public class SignatureKeyPairRequestModel
|
||||||
|
{
|
||||||
|
public required SignatureAlgorithm SignatureAlgorithm { get; set; }
|
||||||
|
[EncryptedString] public required string WrappedSigningKey { get; set; }
|
||||||
|
public required string VerifyingKey { get; set; }
|
||||||
|
|
||||||
|
public SignatureKeyPairData ToSignatureKeyPairData()
|
||||||
|
{
|
||||||
|
return new SignatureKeyPairData(
|
||||||
|
SignatureAlgorithm,
|
||||||
|
WrappedSigningKey,
|
||||||
|
VerifyingKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.KeyManagement.Models.Data.Models;
|
||||||
using Bit.Core.Tools.Entities;
|
using Bit.Core.Tools.Entities;
|
||||||
using Bit.Core.Vault.Entities;
|
using Bit.Core.Vault.Entities;
|
||||||
|
|
||||||
@ -12,9 +13,13 @@ public class RotateUserAccountKeysData
|
|||||||
public string OldMasterKeyAuthenticationHash { get; set; }
|
public string OldMasterKeyAuthenticationHash { get; set; }
|
||||||
|
|
||||||
// Other keys encrypted by the userkey
|
// Other keys encrypted by the userkey
|
||||||
|
[Obsolete("Use AccountKeys instead")]
|
||||||
public string UserKeyEncryptedAccountPrivateKey { get; set; }
|
public string UserKeyEncryptedAccountPrivateKey { get; set; }
|
||||||
|
[Obsolete("Use AccountKeys instead")]
|
||||||
public string AccountPublicKey { get; set; }
|
public string AccountPublicKey { get; set; }
|
||||||
|
|
||||||
|
public UserAccountKeysData AccountKeys { get; set; }
|
||||||
|
|
||||||
// All methods to get to the userkey
|
// All methods to get to the userkey
|
||||||
public MasterPasswordUnlockData MasterPasswordUnlockData { get; set; }
|
public MasterPasswordUnlockData MasterPasswordUnlockData { get; set; }
|
||||||
public IEnumerable<EmergencyAccess> EmergencyAccesses { get; set; }
|
public IEnumerable<EmergencyAccess> EmergencyAccesses { get; set; }
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace Bit.Core.KeyManagement.Models.Data;
|
namespace Bit.Core.KeyManagement.Models.Data.Models;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.KeyManagement.Models.Data;
|
using Bit.Core.KeyManagement.Models.Data;
|
||||||
|
using Bit.Core.KeyManagement.Repositories;
|
||||||
using Bit.Core.Platform.Push;
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -26,6 +27,7 @@ namespace Bit.Core.KeyManagement.UserKey.Implementations;
|
|||||||
/// <param name="_pushService">Logs out user from other devices after successful rotation</param>
|
/// <param name="_pushService">Logs out user from other devices after successful rotation</param>
|
||||||
/// <param name="_identityErrorDescriber">Provides a password mismatch error if master password hash validation fails</param>
|
/// <param name="_identityErrorDescriber">Provides a password mismatch error if master password hash validation fails</param>
|
||||||
/// <param name="_credentialRepository">Provides a method to update re-encrypted WebAuthn keys</param>
|
/// <param name="_credentialRepository">Provides a method to update re-encrypted WebAuthn keys</param>
|
||||||
|
/// <param name="_userSignatureKeyPairRepository">Provides a method to update re-encrypted signature keys</param>
|
||||||
public class RotateUserAccountKeysCommand(
|
public class RotateUserAccountKeysCommand(
|
||||||
IUserService _userService,
|
IUserService _userService,
|
||||||
IUserRepository _userRepository,
|
IUserRepository _userRepository,
|
||||||
@ -38,7 +40,8 @@ public class RotateUserAccountKeysCommand(
|
|||||||
IPasswordHasher<User> _passwordHasher,
|
IPasswordHasher<User> _passwordHasher,
|
||||||
IPushNotificationService _pushService,
|
IPushNotificationService _pushService,
|
||||||
IdentityErrorDescriber _identityErrorDescriber,
|
IdentityErrorDescriber _identityErrorDescriber,
|
||||||
IWebAuthnCredentialRepository _credentialRepository
|
IWebAuthnCredentialRepository _credentialRepository,
|
||||||
|
IUserSignatureKeyPairRepository _userSignatureKeyPairRepository
|
||||||
) : IRotateUserAccountKeysCommand
|
) : IRotateUserAccountKeysCommand
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -58,17 +61,54 @@ public class RotateUserAccountKeysCommand(
|
|||||||
user.RevisionDate = user.AccountRevisionDate = now;
|
user.RevisionDate = user.AccountRevisionDate = now;
|
||||||
user.LastKeyRotationDate = now;
|
user.LastKeyRotationDate = now;
|
||||||
user.SecurityStamp = Guid.NewGuid().ToString();
|
user.SecurityStamp = Guid.NewGuid().ToString();
|
||||||
List<UpdateEncryptedDataForKeyRotation> saveEncryptedDataActions = new();
|
var currentSignatureKeyPair = await _userSignatureKeyPairRepository.GetByUserIdAsync(user.Id);
|
||||||
|
List<UpdateEncryptedDataForKeyRotation> saveEncryptedDataActions = [];
|
||||||
|
|
||||||
if (!model.MasterPasswordUnlockData.ValidateForUser(user))
|
if (!model.MasterPasswordUnlockData.ValidateForUser(user))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The provided master password unlock data is not valid for this user.");
|
throw new InvalidOperationException("The provided master password unlock data is not valid for this user.");
|
||||||
}
|
}
|
||||||
if (model.AccountPublicKey != user.PublicKey)
|
if (model.AccountKeys.PublicKeyEncryptionKeyPairData.PublicKey != user.PublicKey)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The provided account public key does not match the user's current public key, and changing the account asymmetric keypair is currently not supported during key rotation.");
|
throw new InvalidOperationException("The provided account public key does not match the user's current public key, and changing the account asymmetric keypair is currently not supported during key rotation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentSignatureKeyPair != null)
|
||||||
|
{
|
||||||
|
// user is already v2 user
|
||||||
|
if (model.AccountKeys.SignatureKeyPairData == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The provided signing key data is null, but the user already has signing keys.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model.AccountKeys.SignatureKeyPairData.VerifyingKey != currentSignatureKeyPair.VerifyingKey)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The provided signing key data does not match the user's current signing key data.");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("No signed public key provided, but the user already has a signature key pair.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (model.AccountKeys.SignatureKeyPairData != null)
|
||||||
|
{
|
||||||
|
// user is upgrading
|
||||||
|
if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.VerifyingKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The provided signing key data does not contain a valid verifying key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(model.AccountKeys.SignatureKeyPairData.WrappedSigningKey))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The provided signing key data does not contain a valid wrapped signing key.");
|
||||||
|
}
|
||||||
|
saveEncryptedDataActions.Add(_userSignatureKeyPairRepository.SetUserSignatureKeyPair(user.Id, model.AccountKeys.SignatureKeyPairData));
|
||||||
|
user.SignedPublicKey = model.AccountKeys.PublicKeyEncryptionKeyPairData.SignedPublicKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey;
|
user.Key = model.MasterPasswordUnlockData.MasterKeyEncryptedUserKey;
|
||||||
user.PrivateKey = model.UserKeyEncryptedAccountPrivateKey;
|
user.PrivateKey = model.UserKeyEncryptedAccountPrivateKey;
|
||||||
user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash);
|
user.MasterPassword = _passwordHasher.HashPassword(user, model.MasterPasswordUnlockData.MasterKeyAuthenticationHash);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user