mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
11 Commits
net7-platf
...
PM-2456-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fed85140b | ||
|
|
d8a64f0f75 | ||
|
|
e19b86ee00 | ||
|
|
bcd47b3b3e | ||
|
|
b150d883c0 | ||
|
|
1a0e9e961d | ||
|
|
7cdc5a6233 | ||
|
|
74190e0125 | ||
|
|
a3b18dced6 | ||
|
|
c8555ef1e7 | ||
|
|
2bb72076ae |
@@ -123,7 +123,7 @@ namespace Bit.App.Pages
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Bit.Core.Abstractions
|
||||
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 SaveNeverDomainAsync(string domain);
|
||||
Task SaveWithServerAsync(Cipher cipher);
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
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.3.0";
|
||||
|
||||
public static readonly string[] AndroidAllClearCipherCacheKeys =
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Bit.Core.Models.Data
|
||||
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)
|
||||
{
|
||||
@@ -86,5 +87,6 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
@@ -33,6 +36,11 @@ namespace Bit.Core.Models.Domain
|
||||
LocalData = localData;
|
||||
Reprompt = obj.Reprompt;
|
||||
|
||||
if (obj.Key != null)
|
||||
{
|
||||
Key = new EncString(obj.Key);
|
||||
}
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case Enums.CipherType.Login:
|
||||
@@ -79,29 +87,43 @@ 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 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 +135,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 +150,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 +165,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 +193,12 @@ namespace Bit.Core.Models.Domain
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Bit.Core.Models.Request
|
||||
Favorite = cipher.Favorite;
|
||||
LastKnownRevisionDate = cipher.RevisionDate;
|
||||
Reprompt = cipher.Reprompt;
|
||||
Key = cipher.Key?.EncryptedString;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
@@ -124,5 +125,6 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,6 @@ 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ 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 ItemView Item
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
#define ENABLE_NEW_CIPHER_KEY_ENCRYPTION_ON_CREATION
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -30,6 +32,7 @@ namespace Bit.Core.Services
|
||||
private readonly IStorageService _storageService;
|
||||
private readonly II18nService _i18nService;
|
||||
private readonly Func<ISearchService> _searchService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly string _clearCipherCacheKey;
|
||||
private readonly string[] _allClearCipherCacheKeys;
|
||||
private Dictionary<string, HashSet<string>> _domainMatchBlacklist = new Dictionary<string, HashSet<string>>
|
||||
@@ -48,6 +51,7 @@ namespace Bit.Core.Services
|
||||
IStorageService storageService,
|
||||
II18nService i18nService,
|
||||
Func<ISearchService> searchService,
|
||||
IConfigService configService,
|
||||
string clearCipherCacheKey,
|
||||
string[] allClearCipherCacheKeys)
|
||||
{
|
||||
@@ -59,6 +63,7 @@ namespace Bit.Core.Services
|
||||
_storageService = storageService;
|
||||
_i18nService = i18nService;
|
||||
_searchService = searchService;
|
||||
_configService = configService;
|
||||
_clearCipherCacheKey = clearCipherCacheKey;
|
||||
_allClearCipherCacheKeys = allClearCipherCacheKeys;
|
||||
}
|
||||
@@ -180,6 +185,26 @@ namespace Bit.Core.Services
|
||||
Reprompt = model.Reprompt
|
||||
};
|
||||
|
||||
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 +214,36 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
var tasks = new List<Task>
|
||||
if (!await ShouldUseCipherKeyEncryptionAsync())
|
||||
{
|
||||
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;
|
||||
return key;
|
||||
}
|
||||
|
||||
if (cipherView.Key != null)
|
||||
{
|
||||
cipher.Key = await _cryptoService.EncryptAsync(cipherView.Key.Key, key);
|
||||
return cipherView.Key;
|
||||
}
|
||||
#if ENABLE_NEW_CIPHER_KEY_ENCRYPTION_ON_CREATION
|
||||
// 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
|
||||
}
|
||||
|
||||
private async Task<bool> ShouldUseCipherKeyEncryptionAsync()
|
||||
{
|
||||
var config = await _configService.GetAsync();
|
||||
|
||||
return config != null
|
||||
&&
|
||||
VersionHelpers.IsServerVersionGreaterThanOrEqualTo(config.Version, Constants.CipherKeyEncryptionMinServerVersion);
|
||||
}
|
||||
|
||||
public async Task<Cipher> GetAsync(string id)
|
||||
@@ -526,6 +567,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);
|
||||
@@ -555,11 +597,12 @@ namespace Bit.Core.Services
|
||||
await UpsertAsync(data);
|
||||
}
|
||||
|
||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, CipherView cipherView, string filename, byte[] data)
|
||||
{
|
||||
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||
var key = await UpdateCipherAndGetCipherKeyAsync(cipher, cipherView, orgKey);
|
||||
var encFileName = await _cryptoService.EncryptAsync(filename, key);
|
||||
var (attachmentKey, encAttachmentKey) = await _cryptoService.MakeEncKeyAsync(key);
|
||||
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||
|
||||
CipherResponse response;
|
||||
@@ -567,18 +610,19 @@ namespace Bit.Core.Services
|
||||
{
|
||||
var request = new AttachmentRequest
|
||||
{
|
||||
Key = orgEncAttachmentKey.EncryptedString,
|
||||
Key = encAttachmentKey.EncryptedString,
|
||||
FileName = encFileName.EncryptedString,
|
||||
FileSize = encFileData.Buffer.Length,
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
|
||||
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, encAttachmentKey);
|
||||
}
|
||||
|
||||
var userId = await _stateService.GetActiveUserIdAsync();
|
||||
@@ -1054,7 +1098,7 @@ namespace Bit.Core.Services
|
||||
{
|
||||
await EncryptObjPropertyAsync(model, attachment, new HashSet<string>
|
||||
{
|
||||
"FileName"
|
||||
nameof(AttachmentView.FileName)
|
||||
}, key);
|
||||
if (model.Key != null)
|
||||
{
|
||||
|
||||
@@ -44,8 +44,9 @@ namespace Bit.Core.Utilities
|
||||
var organizationService = new OrganizationService(stateService, apiService);
|
||||
var settingsService = new SettingsService(stateService);
|
||||
var fileUploadService = new FileUploadService(apiService);
|
||||
var configService = new ConfigService(apiService, stateService, logger);
|
||||
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,
|
||||
fileUploadService, storageService, i18nService, () => searchService, clearCipherCacheKey,
|
||||
fileUploadService, storageService, i18nService, () => searchService, configService, clearCipherCacheKey,
|
||||
allClearCipherCacheKeys);
|
||||
var folderService = new FolderService(cryptoService, stateService, apiService, i18nService, cipherService);
|
||||
var collectionService = new CollectionService(cryptoService, stateService, i18nService);
|
||||
@@ -87,7 +88,6 @@ namespace Bit.Core.Utilities
|
||||
var userVerificationService = new UserVerificationService(apiService, platformUtilsService, i18nService,
|
||||
cryptoService);
|
||||
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);
|
||||
var configService = new ConfigService(apiService, stateService, logger);
|
||||
|
||||
Register<IConditionedAwaiterManager>(conditionedRunner);
|
||||
Register<ITokenService>("tokenService", tokenService);
|
||||
@@ -95,6 +95,7 @@ namespace Bit.Core.Utilities
|
||||
Register<IAppIdService>("appIdService", appIdService);
|
||||
Register<IOrganizationService>("organizationService", organizationService);
|
||||
Register<ISettingsService>("settingsService", settingsService);
|
||||
Register<IConfigService>(configService);
|
||||
Register<ICipherService>("cipherService", cipherService);
|
||||
Register<IFolderService>("folderService", folderService);
|
||||
Register<ICollectionService>("collectionService", collectionService);
|
||||
@@ -113,7 +114,6 @@ namespace Bit.Core.Utilities
|
||||
Register<IKeyConnectorService>("keyConnectorService", keyConnectorService);
|
||||
Register<IUserVerificationService>("userVerificationService", userVerificationService);
|
||||
Register<IUsernameGenerationService>(usernameGenerationService);
|
||||
Register<IConfigService>(configService);
|
||||
}
|
||||
|
||||
public static void Register<T>(string serviceName, T obj)
|
||||
|
||||
34
src/Core/Utilities/VersionHelpers.cs
Normal file
34
src/Core/Utilities/VersionHelpers.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public static class VersionHelpers
|
||||
{
|
||||
private const char HOTFIX_SEPARATOR = '-';
|
||||
|
||||
/// <summary>
|
||||
/// Compares two server versions and gets whether the <paramref name="targetVersion"/>
|
||||
/// is greater than or equal to <paramref name="compareToVersion"/>.
|
||||
/// WARNING: This doesn't take into account hotfix suffix.
|
||||
/// </summary>
|
||||
/// <param name="targetVersion">Version to compare</param>
|
||||
/// <param name="compareToVersion">Version to compare against</param>
|
||||
/// <returns>
|
||||
/// <c>True</c> if <paramref name="targetVersion"/> is greater than or equal to <paramref name="compareToVersion"/>; <c>False</c> otherwise.
|
||||
/// </returns>
|
||||
public static bool IsServerVersionGreaterThanOrEqualTo(string targetVersion, string compareToVersion)
|
||||
{
|
||||
return GetServerVersionWithoutHotfix(targetVersion).CompareTo(GetServerVersionWithoutHotfix(compareToVersion)) >= 0;
|
||||
}
|
||||
|
||||
public static Version GetServerVersionWithoutHotfix(string version)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
}
|
||||
|
||||
return new Version(version.Split(HOTFIX_SEPARATOR)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Models.Domain;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
@@ -26,16 +28,36 @@ namespace Bit.Core.Test.AutoFixture
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipherView : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
byte[] getRandomBytes(int size)
|
||||
{
|
||||
Random random = new Random();
|
||||
|
||||
byte[] bytes = new byte[size];
|
||||
random.NextBytes(bytes);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
fixture.Customize<CipherView>(composer => composer
|
||||
.Without(c => c.OrganizationId)
|
||||
.Without(c => c.Attachments)
|
||||
.With(c => c.Key, new SymmetricCryptoKey(getRandomBytes(32), Enums.EncryptionType.AesCbc128_HmacSha256_B64)));
|
||||
}
|
||||
}
|
||||
|
||||
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||
{
|
||||
public UserCipherAutoDataAttribute() : base(new SutProviderCustomization(),
|
||||
new UserCipher())
|
||||
new UserCipher(), new UserCipherView())
|
||||
{ }
|
||||
}
|
||||
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||
{
|
||||
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||
typeof(UserCipher) }, values)
|
||||
typeof(UserCipher), typeof(UserCipherView) }, values)
|
||||
{ }
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Bit.Core.Test.Services
|
||||
{
|
||||
[Theory, UserCipherAutoData]
|
||||
public async Task SaveWithServerAsync_PrefersFileUploadService(SutProvider<CipherService> sutProvider,
|
||||
Cipher cipher, string fileName, EncByteArray data, AttachmentUploadDataResponse uploadDataResponse, EncString encKey)
|
||||
Cipher cipher, CipherView cipherView, string fileName, EncByteArray data, AttachmentUploadDataResponse uploadDataResponse, EncString encKey)
|
||||
{
|
||||
var encFileName = new EncString(fileName);
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
@@ -33,7 +33,7 @@ namespace Bit.Core.Test.Services
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||
.Returns(uploadDataResponse);
|
||||
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, cipherView, fileName, data.Buffer);
|
||||
|
||||
await sutProvider.GetDependency<IFileUploadService>().Received(1)
|
||||
.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName, data);
|
||||
@@ -43,7 +43,7 @@ namespace Bit.Core.Test.Services
|
||||
[InlineUserCipherAutoData(HttpStatusCode.NotFound)]
|
||||
[InlineUserCipherAutoData(HttpStatusCode.MethodNotAllowed)]
|
||||
public async Task SaveWithServerAsync_FallsBackToLegacyFormData(HttpStatusCode statusCode,
|
||||
SutProvider<CipherService> sutProvider, Cipher cipher, string fileName, EncByteArray data,
|
||||
SutProvider<CipherService> sutProvider, Cipher cipher, CipherView cipherView, string fileName, EncByteArray data,
|
||||
CipherResponse response, EncString encKey)
|
||||
{
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
@@ -56,7 +56,7 @@ namespace Bit.Core.Test.Services
|
||||
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
|
||||
.Returns(response);
|
||||
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, cipherView, fileName, data.Buffer);
|
||||
|
||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||
.PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>());
|
||||
@@ -64,7 +64,7 @@ namespace Bit.Core.Test.Services
|
||||
|
||||
[Theory, UserCipherAutoData]
|
||||
public async Task SaveWithServerAsync_ThrowsOnBadRequestApiException(SutProvider<CipherService> sutProvider,
|
||||
Cipher cipher, string fileName, EncByteArray data, EncString encKey)
|
||||
Cipher cipher, CipherView cipherView, string fileName, EncByteArray data, EncString encKey)
|
||||
{
|
||||
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||
.Returns(new EncString(fileName));
|
||||
@@ -77,7 +77,7 @@ namespace Bit.Core.Test.Services
|
||||
.Throws(expectedException);
|
||||
|
||||
var actualException = await Assert.ThrowsAsync<ApiException>(async () =>
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer));
|
||||
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, cipherView, fileName, data.Buffer));
|
||||
|
||||
Assert.Equal(expectedException.Error.StatusCode, actualException.Error.StatusCode);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user