Skip to content

Commit

Permalink
Cherry-picked [PM-115] Cipher key encryption update (#2421)
Browse files Browse the repository at this point in the history
  • Loading branch information
fedemkr committed Sep 29, 2023
1 parent 4cc5e13 commit ee1cbae
Show file tree
Hide file tree
Showing 26 changed files with 241 additions and 85 deletions.
2 changes: 1 addition & 1 deletion src/App/Pages/Vault/AttachmentsPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ await _platformUtilsService.ShowDialogAsync(AppResources.MaxFileSize,
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
_cipherDomain = await _cipherService.SaveAttachmentRawWithServerAsync(
_cipherDomain, FileName, FileData);
_cipherDomain, Cipher, FileName, FileData);
Cipher = await _cipherDomain.DecryptAsync();
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.AttachementAdded);
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Abstractions/ICipherService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Task<Tuple<List<CipherView>, List<CipherView>, List<CipherView>>> GetAllDecrypte
Task<Cipher> GetAsync(string id);
Task<CipherView> GetLastUsedForUrlAsync(string url);
Task ReplaceAsync(Dictionary<string, CipherData> ciphers);
Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data);
Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, CipherView cipherView, string filename, byte[] data);
Task SaveCollectionsWithServerAsync(Cipher cipher);
Task SaveWithServerAsync(Cipher cipher);
Task<ShareWithServerError> ShareWithServerAsync(CipherView cipher, string organizationId, HashSet<string> collectionIds);
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ public static class Constants
public const int Argon2MemoryInMB = 64;
public const int Argon2Parallelism = 4;
public const int MasterPasswordMinimumChars = 12;
public const int CipherKeyRandomBytesLength = 64;
public const string CipherKeyEncryptionMinServerVersion = "2023.9.1";
public const string DefaultFido2KeyType = "public-key";
public const string DefaultFido2KeyAlgorithm = "ECDSA";
public const string DefaultFido2KeyCurve = "P-256";
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Models/Data/CipherData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public CipherData(CipherResponse response, string userId = null, HashSet<string>
Notes = response.Notes;
CollectionIds = collectionIds?.ToList() ?? response.CollectionIds;
Reprompt = response.Reprompt;
Key = response.Key;

try // Added to address Issue (https://github.com/bitwarden/mobile/issues/1006)
{
Expand Down Expand Up @@ -92,5 +93,6 @@ public CipherData(CipherResponse response, string userId = null, HashSet<string>
public List<PasswordHistoryData> PasswordHistory { get; set; }
public List<string> CollectionIds { get; set; }
public Enums.CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
}
}
35 changes: 19 additions & 16 deletions src/Core/Models/Domain/Attachment.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities;

namespace Bit.Core.Models.Domain
Expand All @@ -11,19 +13,19 @@ public class Attachment : Domain
{
private HashSet<string> _map = new HashSet<string>
{
"Id",
"Url",
"SizeName",
"FileName",
"Key"
nameof(Id),
nameof(Url),
nameof(SizeName),
nameof(FileName),
nameof(Key)
};

public Attachment() { }

public Attachment(AttachmentData obj, bool alreadyEncrypted = false)
{
Size = obj.Size;
BuildDomainModel(this, obj, _map, alreadyEncrypted, new HashSet<string> { "Id", "Url", "SizeName" });
BuildDomainModel(this, obj, _map, alreadyEncrypted, new HashSet<string> { nameof(Id), nameof(Url), nameof(SizeName) });
}

public string Id { get; set; }
Expand All @@ -33,25 +35,26 @@ public Attachment(AttachmentData obj, bool alreadyEncrypted = false)
public EncString Key { get; set; }
public EncString FileName { get; set; }

public async Task<AttachmentView> DecryptAsync(string orgId)
public async Task<AttachmentView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
var view = await DecryptObjAsync(new AttachmentView(this), this, new HashSet<string>
{
"FileName"
}, orgId);
nameof(FileName)
}, orgId, key);

if (Key != null)
{
var cryptoService = ServiceContainer.Resolve<ICryptoService>("cryptoService");
try
{
var orgKey = await cryptoService.GetOrgKeyAsync(orgId);
var decValue = await cryptoService.DecryptToBytesAsync(Key, orgKey);
var cryptoService = ServiceContainer.Resolve<ICryptoService>();

var decryptKey = key ?? await cryptoService.GetOrgKeyAsync(orgId);
var decValue = await cryptoService.DecryptToBytesAsync(Key, decryptKey);
view.Key = new SymmetricCryptoKey(decValue);
}
catch
catch (Exception ex)
{
// TODO: error?
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
return view;
Expand All @@ -61,7 +64,7 @@ public AttachmentData ToAttachmentData()
{
var a = new AttachmentData();
a.Size = Size;
BuildDataModel(this, a, _map, new HashSet<string> { "Id", "Url", "SizeName" });
BuildDataModel(this, a, _map, new HashSet<string> { nameof(Id), nameof(Url), nameof(SizeName) });
return a;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/Card.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ public Card(CardData obj, bool alreadyEncrypted = false)
public EncString ExpYear { get; set; }
public EncString Code { get; set; }

public Task<CardView> DecryptAsync(string orgId)
public Task<CardView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new CardView(this), this, _map, orgId);
return DecryptObjAsync(new CardView(this), this, _map, orgId, key);
}

public CardData ToCardData()
Expand Down
58 changes: 40 additions & 18 deletions src/Core/Models/Domain/Cipher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.View;
using Bit.Core.Utilities;

namespace Bit.Core.Models.Domain
{
Expand All @@ -16,12 +18,12 @@ public Cipher(CipherData obj, bool alreadyEncrypted = false, Dictionary<string,
{
BuildDomainModel(this, obj, new HashSet<string>
{
"Id",
"OrganizationId",
"FolderId",
"Name",
"Notes"
}, alreadyEncrypted, new HashSet<string> { "Id", "OrganizationId", "FolderId" });
nameof(Id),
nameof(OrganizationId),
nameof(FolderId),
nameof(Name),
nameof(Notes)
}, alreadyEncrypted, new HashSet<string> { nameof(Id), nameof(OrganizationId), nameof(FolderId) });

Type = obj.Type;
Favorite = obj.Favorite;
Expand All @@ -34,6 +36,11 @@ public Cipher(CipherData obj, bool alreadyEncrypted = false, Dictionary<string,
LocalData = localData;
Reprompt = obj.Reprompt;

if (obj.Key != null)
{
Key = new EncString(obj.Key);
}

switch (Type)
{
case Enums.CipherType.Login:
Expand Down Expand Up @@ -85,29 +92,43 @@ public Cipher(CipherData obj, bool alreadyEncrypted = false, Dictionary<string,
public List<PasswordHistory> PasswordHistory { get; set; }
public HashSet<string> CollectionIds { get; set; }
public CipherRepromptType Reprompt { get; set; }
public EncString Key { get; set; }

public async Task<CipherView> DecryptAsync()
{
var model = new CipherView(this);

if (Key != null)
{
// HACK: I don't like resolving this here but I can't see a better way without
// refactoring a lot of things.
var cryptoService = ServiceContainer.Resolve<ICryptoService>();

var orgKey = await cryptoService.GetOrgKeyAsync(OrganizationId);

var key = await cryptoService.DecryptToBytesAsync(Key, orgKey);
model.Key = new CipherKey(key);
}

await DecryptObjAsync(model, this, new HashSet<string>
{
"Name",
"Notes"
}, OrganizationId);
nameof(Name),
nameof(Notes)
}, OrganizationId, model.Key);

switch (Type)
{
case Enums.CipherType.Login:
model.Login = await Login.DecryptAsync(OrganizationId);
model.Login = await Login.DecryptAsync(OrganizationId, model.Key);
break;
case Enums.CipherType.SecureNote:
model.SecureNote = await SecureNote.DecryptAsync(OrganizationId);
model.SecureNote = await SecureNote.DecryptAsync(OrganizationId, model.Key);
break;
case Enums.CipherType.Card:
model.Card = await Card.DecryptAsync(OrganizationId);
model.Card = await Card.DecryptAsync(OrganizationId, model.Key);
break;
case Enums.CipherType.Identity:
model.Identity = await Identity.DecryptAsync(OrganizationId);
model.Identity = await Identity.DecryptAsync(OrganizationId, model.Key);
break;
case Enums.CipherType.Fido2Key:
model.Fido2Key = await Fido2Key.DecryptAsync(OrganizationId);
Expand All @@ -122,7 +143,7 @@ public async Task<CipherView> DecryptAsync()
var tasks = new List<Task>();
async Task decryptAndAddAttachmentAsync(Attachment attachment)
{
var decAttachment = await attachment.DecryptAsync(OrganizationId);
var decAttachment = await attachment.DecryptAsync(OrganizationId, model.Key);
model.Attachments.Add(decAttachment);
}
foreach (var attachment in Attachments)
Expand All @@ -137,7 +158,7 @@ async Task decryptAndAddAttachmentAsync(Attachment attachment)
var tasks = new List<Task>();
async Task decryptAndAddFieldAsync(Field field)
{
var decField = await field.DecryptAsync(OrganizationId);
var decField = await field.DecryptAsync(OrganizationId, model.Key);
model.Fields.Add(decField);
}
foreach (var field in Fields)
Expand All @@ -152,7 +173,7 @@ async Task decryptAndAddFieldAsync(Field field)
var tasks = new List<Task>();
async Task decryptAndAddHistoryAsync(PasswordHistory ph)
{
var decPh = await ph.DecryptAsync(OrganizationId);
var decPh = await ph.DecryptAsync(OrganizationId, model.Key);
model.PasswordHistory.Add(decPh);
}
foreach (var ph in PasswordHistory)
Expand Down Expand Up @@ -181,11 +202,12 @@ public CipherData ToCipherData(string userId)
CollectionIds = CollectionIds.ToList(),
DeletedDate = DeletedDate,
Reprompt = Reprompt,
Key = Key?.EncryptedString
};
BuildDataModel(this, c, new HashSet<string>
{
"Name",
"Notes"
nameof(Name),
nameof(Notes)
});
switch (c.Type)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/Fido2Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ public Fido2Key(Fido2KeyData data, bool alreadyEncrypted = false)
public EncString UserName { get; set; }
public EncString Counter { get; set; }

public async Task<Fido2KeyView> DecryptAsync(string orgId)
public async Task<Fido2KeyView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return await DecryptObjAsync(new Fido2KeyView(), this, EncryptableProperties, orgId);
return await DecryptObjAsync(new Fido2KeyView(), this, EncryptableProperties, orgId, key);
}

public Fido2KeyData ToFido2KeyData()
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/Field.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public Field(FieldData obj, bool alreadyEncrypted = false)
public FieldType Type { get; set; }
public LinkedIdType? LinkedId { get; set; }

public Task<FieldView> DecryptAsync(string orgId)
public Task<FieldView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new FieldView(this), this, _map, orgId);
return DecryptObjAsync(new FieldView(this), this, _map, orgId, key);
}

public FieldData ToFieldData()
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/Identity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public Identity(IdentityData obj, bool alreadyEncrypted = false)
public EncString PassportNumber { get; set; }
public EncString LicenseNumber { get; set; }

public Task<IdentityView> DecryptAsync(string orgId)
public Task<IdentityView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new IdentityView(this), this, _map, orgId);
return DecryptObjAsync(new IdentityView(this), this, _map, orgId, key);
}

public IdentityData ToIdentityData()
Expand Down
8 changes: 4 additions & 4 deletions src/Core/Models/Domain/Login.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,25 @@ public Login(LoginData obj, bool alreadyEncrypted = false)
public EncString Totp { get; set; }
public Fido2Key Fido2Key { get; set; }

public async Task<LoginView> DecryptAsync(string orgId)
public async Task<LoginView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
var view = await DecryptObjAsync(new LoginView(this), this, new HashSet<string>
{
"Username",
"Password",
"Totp"
}, orgId);
}, orgId, key);
if (Uris != null)
{
view.Uris = new List<LoginUriView>();
foreach (var uri in Uris)
{
view.Uris.Add(await uri.DecryptAsync(orgId));
view.Uris.Add(await uri.DecryptAsync(orgId, key));
}
}
if (Fido2Key != null)
{
view.Fido2Key = await Fido2Key.DecryptAsync(orgId);
view.Fido2Key = await Fido2Key.DecryptAsync(orgId, key);
}
return view;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/LoginUri.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public LoginUri(LoginUriData obj, bool alreadyEncrypted = false)
public EncString Uri { get; set; }
public UriMatchType? Match { get; set; }

public Task<LoginUriView> DecryptAsync(string orgId)
public Task<LoginUriView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new LoginUriView(this), this, _map, orgId);
return DecryptObjAsync(new LoginUriView(this), this, _map, orgId, key);
}

public LoginUriData ToLoginUriData()
Expand Down
4 changes: 2 additions & 2 deletions src/Core/Models/Domain/PasswordHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public PasswordHistory(PasswordHistoryData obj, bool alreadyEncrypted = false)
public EncString Password { get; set; }
public DateTime LastUsedDate { get; set; }

public Task<PasswordHistoryView> DecryptAsync(string orgId)
public Task<PasswordHistoryView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return DecryptObjAsync(new PasswordHistoryView(this), this, _map, orgId);
return DecryptObjAsync(new PasswordHistoryView(this), this, _map, orgId, key);
}

public PasswordHistoryData ToPasswordHistoryData()
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Models/Domain/SecureNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public SecureNote(SecureNoteData obj, bool alreadyEncrypted = false)

public SecureNoteType Type { get; set; }

public Task<SecureNoteView> DecryptAsync(string orgId)
public Task<SecureNoteView> DecryptAsync(string orgId, SymmetricCryptoKey key = null)
{
return Task.FromResult(new SecureNoteView(this));
}
Expand Down
8 changes: 7 additions & 1 deletion src/Core/Models/Domain/SymmetricCryptoKey.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using Bit.Core.Enums;

namespace Bit.Core.Models.Domain
Expand Down Expand Up @@ -102,4 +101,11 @@ public OrgKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}

public class CipherKey : SymmetricCryptoKey
{
public CipherKey(byte[] key, EncryptionType? encType = null)
: base(key, encType)
{ }
}
}
Loading

0 comments on commit ee1cbae

Please sign in to comment.