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

Individual Vault Item Encryption Feature (#3256)

* [SG-966] [SG-967] Add new cipher properties, update DB objects and create migrations (#2681)

* Updated cipher entity with two new columns

* Added sqlserver mifgration and updated applicable stored procedures and table

* Added EF Migrations

* Made changes to response model to include new column properties

* Fixed formatting

* Modified scripts to reflect suggestions made on PR

* Added column to cipher table using default

* Include constraint in create cipher table script

* Added key and forcerotatekey property to request model (#2716)

* Added key update on the Cipher_UpdateWithCollection stored procedure, ef (#2855)

* Added key and forceKeyRotation to BuildCiphersTable method (#2893)

* [PM-2211] Remove forceKeyRotation column (#2921)

* Removed forceKeyRotation column

* Adjusted date for migrtaion file

* Passed key column to update cipher script to update cipher key when it is rotated (#2967)

* [PM-2448] Update CipherDetails_Update SP to update attachment column (#2992)

* Updated the cipherdetails_update stored procedure to update the attachement column when encrypted with the cipher key

* Moved migration and renamed old migration file

* Fixed lint issues

* Fixed lint issues

* renamed sqlserver migration to have a more recent date

* [PM-2548] Added validation to edit and add attachments methods (#3130)

* PM-2548 Added validation to edit and add attachments methods

* PM-2548 Moved the validation to a private method

* PM-2548 Minor refactor

* Bumped up minimum version

* Bumped up minimum version

* Changed version for tests purposes

* Bumped up minimum version

* Updated encryption minimum version to match clients for QA.

* PM-3976 Passed Key column to update cipher on bulk edit (#3299)

* Updated minimum client version in preparation for release.

* Renamed migration with current date. (#3303)

---------

Co-authored-by: SmithThe4th <gsmith@bitwarden.com>
Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
Co-authored-by: Carlos Gonçalves <cgoncalves@bitwarden.com>
Co-authored-by: Carlos Gonçalves <carlosmaccam@gmail.com>
This commit is contained in:
Todd Martin
2023-09-28 08:45:13 -04:00
committed by GitHub
parent 464dac8f4d
commit 7ede956c32
28 changed files with 6990 additions and 17 deletions

View File

@ -36,6 +36,7 @@ public class CiphersController : Controller
private readonly ICurrentContext _currentContext;
private readonly ILogger<CiphersController> _logger;
private readonly GlobalSettings _globalSettings;
private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion);
public CiphersController(
ICipherRepository cipherRepository,
@ -177,6 +178,8 @@ public class CiphersController : Controller
throw new NotFoundException();
}
ValidateItemLevelEncryptionIsAvailable(cipher);
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ?
(Guid?)null : new Guid(model.OrganizationId);
@ -198,6 +201,9 @@ public class CiphersController : Controller
{
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);
ValidateItemLevelEncryptionIsAvailable(cipher);
if (cipher == null || !cipher.OrganizationId.HasValue ||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
{
@ -576,6 +582,8 @@ public class CiphersController : Controller
throw new NotFoundException();
}
ValidateItemLevelEncryptionIsAvailable(cipher);
if (request.FileSize > CipherService.MAX_FILE_SIZE)
{
throw new BadRequestException($"Max file size is {CipherService.MAX_FILE_SIZE_READABLE}.");
@ -795,4 +803,12 @@ public class CiphersController : Controller
throw new BadRequestException("Invalid content.");
}
}
private void ValidateItemLevelEncryptionIsAvailable(Cipher cipher)
{
if (cipher.Key != null && _currentContext.ClientVersion < _cipherKeyEncryptionMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}
}

View File

@ -18,6 +18,7 @@ public class CipherRequestModel
public string FolderId { get; set; }
public bool Favorite { get; set; }
public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
[Required]
[EncryptedString]
[EncryptedStringLength(1000)]
@ -86,6 +87,7 @@ public class CipherRequestModel
}
existingCipher.Reprompt = Reprompt;
existingCipher.Key = Key;
var hasAttachments2 = (Attachments2?.Count ?? 0) > 0;
var hasAttachments = (Attachments?.Count ?? 0) > 0;

View File

@ -63,6 +63,7 @@ public class CipherMiniResponseModel : ResponseModel
CreationDate = cipher.CreationDate;
DeletedDate = cipher.DeletedDate;
Reprompt = cipher.Reprompt.GetValueOrDefault(CipherRepromptType.None);
Key = cipher.Key;
}
public Guid Id { get; set; }
@ -83,6 +84,7 @@ public class CipherMiniResponseModel : ResponseModel
public DateTime CreationDate { get; set; }
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
}
public class CipherResponseModel : CipherMiniResponseModel

View File

@ -19,6 +19,8 @@ public static class Constants
/// their subscription has expired.
/// </summary>
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;
public const string CipherKeyEncryptionMinimumVersion = "2023.9.1";
}
public static class TokenPurposes

View File

@ -21,6 +21,7 @@ public class Cipher : ITableObject<Guid>, ICloneable
public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
public DateTime? DeletedDate { get; set; }
public Enums.CipherRepromptType? Reprompt { get; set; }
public string Key { get; set; }
public void SetNewId()
{

View File

@ -392,7 +392,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
SET
[Data] = TC.[Data],
[Attachments] = TC.[Attachments],
[RevisionDate] = TC.[RevisionDate]
[RevisionDate] = TC.[RevisionDate],
[Key] = TC.[Key]
FROM
[dbo].[Cipher] C
INNER JOIN
@ -506,7 +507,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
[Data] = TC.[Data],
[Attachments] = TC.[Attachments],
[RevisionDate] = TC.[RevisionDate],
[DeletedDate] = TC.[DeletedDate]
[DeletedDate] = TC.[DeletedDate],
[Key] = TC.[Key]
FROM
[dbo].[Cipher] C
INNER JOIN
@ -728,6 +730,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
ciphersTable.Columns.Add(deletedDateColumn);
var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short));
ciphersTable.Columns.Add(repromptColumn);
var keyColummn = new DataColumn(nameof(c.Key), typeof(string));
ciphersTable.Columns.Add(keyColummn);
foreach (DataColumn col in ciphersTable.Columns)
{
@ -754,6 +758,7 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
row[revisionDateColumn] = cipher.RevisionDate;
row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value;
row[repromptColumn] = cipher.Reprompt;
row[keyColummn] = cipher.Key;
ciphersTable.Rows.Add(row);
}

View File

@ -73,6 +73,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
Reprompt = c.Reprompt,
ViewPassword = true,
OrganizationUseTotp = false,
Key = c.Key
});
return union;
}

View File

@ -366,6 +366,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
Reprompt = c.Reprompt,
ViewPassword = true,
OrganizationUseTotp = false,
Key = c.Key
};
}
var ciphers = await cipherDetailsView.ToListAsync();
@ -591,6 +592,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
trackedCipher.Attachments = cipher.Attachments;
trackedCipher.RevisionDate = cipher.RevisionDate;
trackedCipher.DeletedDate = cipher.DeletedDate;
trackedCipher.Key = cipher.Key;
await transaction.CommitAsync();

View File

@ -29,6 +29,7 @@ public class CipherDetailsQuery : IQuery<CipherDetails>
RevisionDate = c.RevisionDate,
DeletedDate = c.DeletedDate,
Reprompt = c.Reprompt,
Key = c.Key,
Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"),
FolderId = (_ignoreFolders || !_userId.HasValue || c.Folders == null || !c.Folders.ToLowerInvariant().Contains(_userId.Value.ToString())) ?
null :

View File

@ -26,6 +26,7 @@ SELECT
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId],
C.[DeletedDate],
C.[Reprompt]
C.[Reprompt],
C.[Key]
FROM
[dbo].[Cipher] C

View File

@ -15,7 +15,8 @@
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7),
@Reprompt TINYINT
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -35,7 +36,8 @@ BEGIN
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt]
[Reprompt],
[Key]
)
VALUES
(
@ -49,7 +51,8 @@ BEGIN
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt
@Reprompt,
@Key
)
IF @OrganizationId IS NOT NULL

View File

@ -16,6 +16,7 @@
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
@ -23,7 +24,7 @@ BEGIN
EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @ViewPassword,
@OrganizationUseTotp, @DeletedDate, @Reprompt
@OrganizationUseTotp, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds

View File

@ -6,7 +6,7 @@
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@ -15,7 +15,8 @@
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(2),
@Reprompt TINYINT
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -48,10 +49,12 @@ BEGIN
ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL)
END,
[Attachments] = @Attachments,
[Reprompt] = @Reprompt,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate
[DeletedDate] = @DeletedDate,
[Key] = @Key
WHERE
[Id] = @Id

View File

@ -10,7 +10,8 @@
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -28,7 +29,8 @@ BEGIN
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt]
[Reprompt],
[Key]
)
VALUES
(
@ -43,7 +45,8 @@ BEGIN
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt
@Reprompt,
@Key
)
IF @OrganizationId IS NOT NULL

View File

@ -11,13 +11,14 @@
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt
@Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds

View File

@ -10,7 +10,8 @@
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
@ -28,7 +29,8 @@ BEGIN
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Reprompt] = @Reprompt
[Reprompt] = @Reprompt,
[Key] = @Key
WHERE
[Id] = @Id

View File

@ -11,6 +11,7 @@
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
@ -36,7 +37,8 @@ BEGIN
[Data] = @Data,
[Attachments] = @Attachments,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate
[DeletedDate] = @DeletedDate,
[Key] = @Key
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
WHERE
[Id] = @Id

View File

@ -12,6 +12,7 @@ CREATE TABLE [dbo].[Cipher] (
[RevisionDate] DATETIME2 (7) NOT NULL,
[DeletedDate] DATETIME2 (7) NULL,
[Reprompt] TINYINT NULL,
[Key] VARCHAR(MAX) NULL,
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])