1
0
mirror of https://github.com/bitwarden/mobile synced 2026-01-11 04:53:52 +00:00

PM-115 Added new cipher key and encryption/decryption mechanisms on cipher

This commit is contained in:
Federico Andrés Maccaroni
2023-03-20 13:41:21 -03:00
parent a18f74a72a
commit 2bb72076ae
15 changed files with 130 additions and 63 deletions

View File

@@ -59,6 +59,7 @@
public const int Argon2MemoryInMB = 64;
public const int Argon2Parallelism = 4;
public const int MasterPasswordMinimumChars = 12;
public const int CipherKeyRandomBytesLength = 64;
public static readonly string[] AndroidAllClearCipherCacheKeys =
{

View File

@@ -26,6 +26,8 @@ namespace Bit.Core.Models.Data
Notes = response.Notes;
CollectionIds = collectionIds?.ToList() ?? response.CollectionIds;
Reprompt = response.Reprompt;
Key = response.Key;
ForceKeyRotation = response.ForceKeyRotation;
try // Added to address Issue (https://github.com/bitwarden/mobile/issues/1006)
{
@@ -86,5 +88,7 @@ namespace Bit.Core.Models.Data
public List<string> CollectionIds { get; set; }
public DateTime? DeletedDate { get; set; }
public Enums.CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
public bool ForceKeyRotation { get; set; }
}
}

View File

@@ -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
@@ -11,11 +13,11 @@ namespace Bit.Core.Models.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() { }
@@ -23,7 +25,7 @@ namespace Bit.Core.Models.Domain
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; }
@@ -33,25 +35,26 @@ namespace Bit.Core.Models.Domain
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;
@@ -61,7 +64,7 @@ namespace Bit.Core.Models.Domain
{
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;
}
}

View File

@@ -31,9 +31,9 @@ namespace Bit.Core.Models.Domain
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()

View File

@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
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
{
@@ -16,12 +19,12 @@ namespace Bit.Core.Models.Domain
{
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;
@@ -32,6 +35,12 @@ namespace Bit.Core.Models.Domain
CollectionIds = obj.CollectionIds != null ? new HashSet<string>(obj.CollectionIds) : null;
LocalData = localData;
Reprompt = obj.Reprompt;
ForceKeyRotation = obj.ForceKeyRotation;
if (obj.Key != null)
{
Key = new EncString(obj.Key);
}
switch (Type)
{
@@ -79,29 +88,44 @@ namespace Bit.Core.Models.Domain
public HashSet<string> CollectionIds { get; set; }
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public EncString Key { get; set; }
public bool ForceKeyRotation { 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 SymmetricCryptoKey(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;
default:
break;
@@ -113,7 +137,7 @@ namespace Bit.Core.Models.Domain
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)
@@ -128,7 +152,7 @@ namespace Bit.Core.Models.Domain
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)
@@ -143,7 +167,7 @@ namespace Bit.Core.Models.Domain
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)
@@ -171,11 +195,13 @@ namespace Bit.Core.Models.Domain
CollectionIds = CollectionIds.ToList(),
DeletedDate = DeletedDate,
Reprompt = Reprompt,
Key = Key?.EncryptedString,
ForceKeyRotation = ForceKeyRotation
};
BuildDataModel(this, c, new HashSet<string>
{
"Name",
"Notes"
nameof(Name),
nameof(Notes)
});
switch (c.Type)
{

View File

@@ -28,9 +28,9 @@ namespace Bit.Core.Models.Domain
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()

View File

@@ -55,9 +55,9 @@ namespace Bit.Core.Models.Domain
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()

View File

@@ -29,20 +29,20 @@ namespace Bit.Core.Models.Domain
public DateTime? PasswordRevisionDate { get; set; }
public EncString Totp { 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));
}
}
return view;

View File

@@ -24,9 +24,9 @@ namespace Bit.Core.Models.Domain
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()

View File

@@ -24,9 +24,9 @@ namespace Bit.Core.Models.Domain
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()

View File

@@ -16,7 +16,7 @@ namespace Bit.Core.Models.Domain
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));
}

View File

@@ -19,6 +19,8 @@ namespace Bit.Core.Models.Request
Favorite = cipher.Favorite;
LastKnownRevisionDate = cipher.RevisionDate;
Reprompt = cipher.Reprompt;
Key = cipher.Key?.EncryptedString;
ForceKeyRotation = cipher.ForceKeyRotation;
switch (Type)
{
@@ -124,5 +126,7 @@ namespace Bit.Core.Models.Request
public Dictionary<string, AttachmentRequest> Attachments2 { get; set; }
public DateTime LastKnownRevisionDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
public bool ForceKeyRotation { get; set; }
}
}

View File

@@ -28,5 +28,7 @@ namespace Bit.Core.Models.Response
public List<string> CollectionIds { get; set; }
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
public bool ForceKeyRotation { get; set; }
}
}

View File

@@ -25,6 +25,7 @@ namespace Bit.Core.Models.View
RevisionDate = c.RevisionDate;
DeletedDate = c.DeletedDate;
Reprompt = c.Reprompt;
ForceKeyRotation = c.ForceKeyRotation;
}
public string Id { get; set; }
@@ -49,6 +50,8 @@ namespace Bit.Core.Models.View
public DateTime RevisionDate { get; set; }
public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; }
public SymmetricCryptoKey Key { get; set; }
public bool ForceKeyRotation { get; set; }
public ItemView Item
{

View File

@@ -177,9 +177,30 @@ namespace Bit.Core.Services
Type = model.Type,
CollectionIds = model.CollectionIds,
RevisionDate = model.RevisionDate,
Reprompt = model.Reprompt
Reprompt = model.Reprompt,
ForceKeyRotation = model.ForceKeyRotation
};
key = await UpdateCipherAndGetCipherKeyAsync(cipher, model, key);
var tasks = new List<Task>
{
EncryptObjPropertyAsync(model, cipher, new HashSet<string>
{
nameof(CipherView.Name),
nameof(CipherView.Notes)
}, key),
EncryptCipherDataAsync(cipher, model, key),
EncryptFieldsAsync(model.Fields, key, cipher),
EncryptPasswordHistoriesAsync(model.PasswordHistory, key, cipher),
EncryptAttachmentsAsync(model.Attachments, key, cipher)
};
await Task.WhenAll(tasks);
return cipher;
}
private async Task<SymmetricCryptoKey> UpdateCipherAndGetCipherKeyAsync(Cipher cipher, CipherView cipherView, SymmetricCryptoKey key = null)
{
if (key == null && cipher.OrganizationId != null)
{
key = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
@@ -189,20 +210,21 @@ namespace Bit.Core.Services
}
}
var tasks = new List<Task>
if (cipherView.Key != null && !cipherView.ForceKeyRotation)
{
EncryptObjPropertyAsync(model, cipher, new HashSet<string>
{
"Name",
"Notes"
}, key),
EncryptCipherDataAsync(cipher, model, key),
EncryptFieldsAsync(model.Fields, key, cipher),
EncryptPasswordHistoriesAsync(model.PasswordHistory, key, cipher),
EncryptAttachmentsAsync(model.Attachments, key, cipher)
};
await Task.WhenAll(tasks);
return cipher;
cipher.Key = await _cryptoService.EncryptAsync(cipherView.Key.Key, key);
return cipherView.Key;
}
#if DEBUG
// turned on, only on debug to check that the enc/decryption is working fine at the cipher level.
// this will be allowed on production on a later release.
var cfs = ServiceContainer.Resolve<ICryptoFunctionService>();
var newKey = new SymmetricCryptoKey(await cfs.RandomBytesAsync(Core.Constants.CipherKeyRandomBytesLength));
cipher.Key = await _cryptoService.EncryptAsync(newKey.Key, key);
return newKey;
#else
return key;
#endif
}
public async Task<Cipher> GetAsync(string id)
@@ -526,6 +548,7 @@ namespace Bit.Core.Services
var request = new CipherRequest(cipher);
response = await _apiService.PutCipherAsync(cipher.Id, request);
}
var userId = await _stateService.GetActiveUserIdAsync();
var data = new CipherData(response, userId, cipher.CollectionIds);
await UpsertAsync(data);
@@ -574,6 +597,7 @@ namespace Bit.Core.Services
var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request);
response = uploadDataResponse.CipherResponse;
await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName, encFileData);
}
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
@@ -1054,7 +1078,7 @@ namespace Bit.Core.Services
{
await EncryptObjPropertyAsync(model, attachment, new HashSet<string>
{
"FileName"
nameof(AttachmentView.FileName)
}, key);
if (model.Key != null)
{