mirror of
https://github.com/bitwarden/android.git
synced 2024-12-25 18:38:27 +03:00
cipher service encrypt
This commit is contained in:
parent
4b67ba027e
commit
694e4960ad
3 changed files with 296 additions and 22 deletions
|
@ -74,28 +74,28 @@ namespace Bit.Core.Models.Domain
|
||||||
public List<PasswordHistory> PasswordHistory { get; set; }
|
public List<PasswordHistory> PasswordHistory { get; set; }
|
||||||
public List<string> CollectionIds { get; set; }
|
public List<string> CollectionIds { get; set; }
|
||||||
|
|
||||||
public async Task<CipherView> DecryptAsync(string orgId)
|
public async Task<CipherView> DecryptAsync()
|
||||||
{
|
{
|
||||||
var model = new CipherView(this);
|
var model = new CipherView(this);
|
||||||
await DecryptObjAsync(model, this, new HashSet<string>
|
await DecryptObjAsync(model, this, new HashSet<string>
|
||||||
{
|
{
|
||||||
"Name",
|
"Name",
|
||||||
"Notes"
|
"Notes"
|
||||||
}, orgId);
|
}, OrganizationId);
|
||||||
|
|
||||||
switch(Type)
|
switch(Type)
|
||||||
{
|
{
|
||||||
case Enums.CipherType.Login:
|
case Enums.CipherType.Login:
|
||||||
model.Login = await Login.DecryptAsync(orgId);
|
model.Login = await Login.DecryptAsync(OrganizationId);
|
||||||
break;
|
break;
|
||||||
case Enums.CipherType.SecureNote:
|
case Enums.CipherType.SecureNote:
|
||||||
model.SecureNote = await SecureNote.DecryptAsync(orgId);
|
model.SecureNote = await SecureNote.DecryptAsync(OrganizationId);
|
||||||
break;
|
break;
|
||||||
case Enums.CipherType.Card:
|
case Enums.CipherType.Card:
|
||||||
model.Card = await Card.DecryptAsync(orgId);
|
model.Card = await Card.DecryptAsync(OrganizationId);
|
||||||
break;
|
break;
|
||||||
case Enums.CipherType.Identity:
|
case Enums.CipherType.Identity:
|
||||||
model.Identity = await Identity.DecryptAsync(orgId);
|
model.Identity = await Identity.DecryptAsync(OrganizationId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -107,7 +107,7 @@ namespace Bit.Core.Models.Domain
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach(var attachment in Attachments)
|
foreach(var attachment in Attachments)
|
||||||
{
|
{
|
||||||
var t = attachment.DecryptAsync(orgId)
|
var t = attachment.DecryptAsync(OrganizationId)
|
||||||
.ContinueWith(async decAttachment => model.Attachments.Add(await decAttachment));
|
.ContinueWith(async decAttachment => model.Attachments.Add(await decAttachment));
|
||||||
tasks.Add(t);
|
tasks.Add(t);
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ namespace Bit.Core.Models.Domain
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach(var field in Fields)
|
foreach(var field in Fields)
|
||||||
{
|
{
|
||||||
var t = field.DecryptAsync(orgId)
|
var t = field.DecryptAsync(OrganizationId)
|
||||||
.ContinueWith(async decField => model.Fields.Add(await decField));
|
.ContinueWith(async decField => model.Fields.Add(await decField));
|
||||||
tasks.Add(t);
|
tasks.Add(t);
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ namespace Bit.Core.Models.Domain
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
foreach(var ph in PasswordHistory)
|
foreach(var ph in PasswordHistory)
|
||||||
{
|
{
|
||||||
var t = ph.DecryptAsync(orgId)
|
var t = ph.DecryptAsync(OrganizationId)
|
||||||
.ContinueWith(async decPh => model.PasswordHistory.Add(await decPh));
|
.ContinueWith(async decPh => model.PasswordHistory.Add(await decPh));
|
||||||
tasks.Add(t);
|
tasks.Add(t);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,6 @@ namespace Bit.Core.Models.View
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public DateTime? LastUsedDate { get; set; }
|
public DateTime LastUsedDate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
|
@ -69,12 +69,78 @@ namespace Bit.Core.Services
|
||||||
// Adjust password history
|
// Adjust password history
|
||||||
if(model.Id != null)
|
if(model.Id != null)
|
||||||
{
|
{
|
||||||
// TODO
|
if(originalCipher == null)
|
||||||
|
{
|
||||||
|
originalCipher = await GetAsync(model.Id);
|
||||||
|
}
|
||||||
|
if(originalCipher != null)
|
||||||
|
{
|
||||||
|
var existingCipher = await originalCipher.DecryptAsync();
|
||||||
|
if(model.PasswordHistory == null)
|
||||||
|
{
|
||||||
|
model.PasswordHistory = new List<PasswordHistoryView>();
|
||||||
|
}
|
||||||
|
if(model.Type == CipherType.Login && existingCipher.Type == CipherType.Login)
|
||||||
|
{
|
||||||
|
if(!string.IsNullOrWhiteSpace(existingCipher.Login.Password) &&
|
||||||
|
existingCipher.Login.Password != model.Login.Password)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
var ph = new PasswordHistoryView
|
||||||
|
{
|
||||||
|
Password = existingCipher.Login.Password,
|
||||||
|
LastUsedDate = now
|
||||||
|
};
|
||||||
|
model.Login.PasswordRevisionDate = now;
|
||||||
|
model.PasswordHistory.Insert(0, ph);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model.Login.PasswordRevisionDate = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(existingCipher.HasFields)
|
||||||
|
{
|
||||||
|
var existingHiddenFields = existingCipher.Fields.Where(f =>
|
||||||
|
f.Type == FieldType.Hidden && !string.IsNullOrWhiteSpace(f.Name) &&
|
||||||
|
!string.IsNullOrWhiteSpace(f.Value));
|
||||||
|
var hiddenFields = model.Fields?.Where(f =>
|
||||||
|
f.Type == FieldType.Hidden && !string.IsNullOrWhiteSpace(f.Name)) ??
|
||||||
|
new List<FieldView>();
|
||||||
|
foreach(var ef in existingHiddenFields)
|
||||||
|
{
|
||||||
|
var matchedField = hiddenFields.FirstOrDefault(f => f.Name == ef.Name);
|
||||||
|
if(matchedField == null || matchedField.Value != ef.Value)
|
||||||
|
{
|
||||||
|
var ph = new PasswordHistoryView
|
||||||
|
{
|
||||||
|
Password = string.Format("{0}: {1}", ef.Name, ef.Value),
|
||||||
|
LastUsedDate = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
model.PasswordHistory.Insert(0, ph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!model.PasswordHistory?.Any() ?? false)
|
||||||
|
{
|
||||||
|
model.PasswordHistory = null;
|
||||||
|
}
|
||||||
|
else if(model.PasswordHistory != null && model.PasswordHistory.Count > 5)
|
||||||
|
{
|
||||||
|
model.PasswordHistory = model.PasswordHistory.Take(5).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cipher = new Cipher();
|
var cipher = new Cipher
|
||||||
cipher.Id = model.Id;
|
{
|
||||||
// TODO others
|
Id = model.Id,
|
||||||
|
FolderId = model.FolderId,
|
||||||
|
Favorite = model.Favorite,
|
||||||
|
OrganizationId = model.OrganizationId,
|
||||||
|
Type = model.Type,
|
||||||
|
CollectionIds = model.CollectionIds
|
||||||
|
};
|
||||||
|
|
||||||
if(key == null && cipher.OrganizationId != null)
|
if(key == null && cipher.OrganizationId != null)
|
||||||
{
|
{
|
||||||
|
@ -85,16 +151,52 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>
|
||||||
tasks.Add(EncryptObjPropertyAsync(model, cipher, new HashSet<string>
|
|
||||||
{
|
{
|
||||||
nameof(model.Name)
|
EncryptObjPropertyAsync(model, cipher, new HashSet<string>
|
||||||
}, key));
|
{
|
||||||
|
"Name",
|
||||||
|
"Notes"
|
||||||
|
}, key),
|
||||||
|
EncryptCipherDataAsync(cipher, model, key),
|
||||||
|
EncryptFieldsAsync(model.Fields, key)
|
||||||
|
.ContinueWith(async fields => cipher.Fields = await fields),
|
||||||
|
EncryptPasswordHistoriesAsync(model.PasswordHistory, key)
|
||||||
|
.ContinueWith(async phs => cipher.PasswordHistory = await phs),
|
||||||
|
EncryptAttachmentsAsync(model.Attachments, key)
|
||||||
|
.ContinueWith(async attachments => cipher.Attachments = await attachments)
|
||||||
|
};
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Cipher> GetAsync(string id)
|
||||||
|
{
|
||||||
|
var userId = await _userService.GetUserIdAsync();
|
||||||
|
var localData = await _storageService.GetAsync<Dictionary<string, Dictionary<string, object>>>(
|
||||||
|
Keys_LocalData);
|
||||||
|
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(
|
||||||
|
string.Format(Keys_CiphersFormat, userId));
|
||||||
|
if(!ciphers?.ContainsKey(id) ?? true)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Cipher(ciphers[id], false,
|
||||||
|
localData?.ContainsKey(id) ?? false ? localData[id] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<Cipher>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var userId = await _userService.GetUserIdAsync();
|
||||||
|
var localData = await _storageService.GetAsync<Dictionary<string, Dictionary<string, object>>>(
|
||||||
|
Keys_LocalData);
|
||||||
|
var ciphers = await _storageService.GetAsync<Dictionary<string, CipherData>>(
|
||||||
|
string.Format(Keys_CiphersFormat, userId));
|
||||||
|
var response = ciphers.Select(c => new Cipher(c.Value, false,
|
||||||
|
localData?.ContainsKey(c.Key) ?? false ? localData[c.Key] : null));
|
||||||
|
return response.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private Task EncryptObjPropertyAsync<V, D>(V model, D obj, HashSet<string> map, SymmetricCryptoKey key)
|
private Task EncryptObjPropertyAsync<V, D>(V model, D obj, HashSet<string> map, SymmetricCryptoKey key)
|
||||||
where V : View
|
where V : View
|
||||||
where D : Domain
|
where D : Domain
|
||||||
|
@ -125,5 +227,177 @@ namespace Bit.Core.Services
|
||||||
}
|
}
|
||||||
return Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<Attachment>> EncryptAttachmentsAsync(
|
||||||
|
List<AttachmentView> attachmentsModel, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(!attachmentsModel?.Any() ?? true)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var encAttachments = new List<Attachment>();
|
||||||
|
foreach(var model in attachmentsModel)
|
||||||
|
{
|
||||||
|
var attachment = new Attachment
|
||||||
|
{
|
||||||
|
Id = model.Id,
|
||||||
|
Size = model.Size,
|
||||||
|
SizeName = model.SizeName,
|
||||||
|
Url = model.Url
|
||||||
|
};
|
||||||
|
var task = EncryptObjPropertyAsync(model, attachment, new HashSet<string>
|
||||||
|
{
|
||||||
|
"FileName"
|
||||||
|
}, key).ContinueWith(async (t) =>
|
||||||
|
{
|
||||||
|
if(model.Key != null)
|
||||||
|
{
|
||||||
|
attachment.Key = await _cryptoService.EncryptAsync(model.Key.Key, key);
|
||||||
|
}
|
||||||
|
encAttachments.Add(attachment);
|
||||||
|
});
|
||||||
|
tasks.Add(task);
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
return encAttachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EncryptCipherDataAsync(Cipher cipher, CipherView model, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
switch(cipher.Type)
|
||||||
|
{
|
||||||
|
case Enums.CipherType.Login:
|
||||||
|
cipher.Login = new Login
|
||||||
|
{
|
||||||
|
PasswordRevisionDate = model.Login.PasswordRevisionDate
|
||||||
|
};
|
||||||
|
await EncryptObjPropertyAsync(model.Login, cipher.Login, new HashSet<string>
|
||||||
|
{
|
||||||
|
"Username",
|
||||||
|
"Password",
|
||||||
|
"Totp"
|
||||||
|
}, key);
|
||||||
|
if(model.Login.Uris != null)
|
||||||
|
{
|
||||||
|
cipher.Login.Uris = new List<LoginUri>();
|
||||||
|
foreach(var uri in model.Login.Uris)
|
||||||
|
{
|
||||||
|
var loginUri = new LoginUri
|
||||||
|
{
|
||||||
|
Match = uri.Match
|
||||||
|
};
|
||||||
|
await EncryptObjPropertyAsync(uri, loginUri, new HashSet<string> { "Uri" }, key);
|
||||||
|
cipher.Login.Uris.Add(loginUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enums.CipherType.SecureNote:
|
||||||
|
cipher.SecureNote = new SecureNote
|
||||||
|
{
|
||||||
|
Type = model.SecureNote.Type
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case Enums.CipherType.Card:
|
||||||
|
cipher.Card = new Card();
|
||||||
|
await EncryptObjPropertyAsync(model.Card, cipher.Card, new HashSet<string>
|
||||||
|
{
|
||||||
|
"CardholderName",
|
||||||
|
"Brand",
|
||||||
|
"Number",
|
||||||
|
"ExpMonth",
|
||||||
|
"ExpYear",
|
||||||
|
"Code"
|
||||||
|
}, key);
|
||||||
|
break;
|
||||||
|
case Enums.CipherType.Identity:
|
||||||
|
cipher.Identity = new Identity();
|
||||||
|
await EncryptObjPropertyAsync(model.Identity, cipher.Identity, new HashSet<string>
|
||||||
|
{
|
||||||
|
"Title",
|
||||||
|
"FirstName",
|
||||||
|
"MiddleName",
|
||||||
|
"LastName",
|
||||||
|
"Address1",
|
||||||
|
"Address2",
|
||||||
|
"Address3",
|
||||||
|
"City",
|
||||||
|
"State",
|
||||||
|
"PostalCode",
|
||||||
|
"Country",
|
||||||
|
"Company",
|
||||||
|
"Email",
|
||||||
|
"Phone",
|
||||||
|
"SSN",
|
||||||
|
"Username",
|
||||||
|
"PassportNumber",
|
||||||
|
"LicenseNumber"
|
||||||
|
}, key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown cipher type.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<Field>> EncryptFieldsAsync(List<FieldView> fieldsModel, SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(!fieldsModel?.Any() ?? true)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var encFields = new List<Field>();
|
||||||
|
foreach(var model in fieldsModel)
|
||||||
|
{
|
||||||
|
var field = new Field
|
||||||
|
{
|
||||||
|
Type = model.Type
|
||||||
|
};
|
||||||
|
// normalize boolean type field values
|
||||||
|
if(model.Type == FieldType.Boolean && model.Value != "true")
|
||||||
|
{
|
||||||
|
model.Value = "false";
|
||||||
|
}
|
||||||
|
var task = EncryptObjPropertyAsync(model, field, new HashSet<string>
|
||||||
|
{
|
||||||
|
"Name",
|
||||||
|
"Value"
|
||||||
|
}, key).ContinueWith((t) =>
|
||||||
|
{
|
||||||
|
encFields.Add(field);
|
||||||
|
});
|
||||||
|
tasks.Add(task);
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
return encFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<PasswordHistory>> EncryptPasswordHistoriesAsync(List<PasswordHistoryView> phModels,
|
||||||
|
SymmetricCryptoKey key)
|
||||||
|
{
|
||||||
|
if(!phModels?.Any() ?? true)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
var encPhs = new List<PasswordHistory>();
|
||||||
|
foreach(var model in phModels)
|
||||||
|
{
|
||||||
|
var ph = new PasswordHistory
|
||||||
|
{
|
||||||
|
LastUsedDate = model.LastUsedDate
|
||||||
|
};
|
||||||
|
var task = EncryptObjPropertyAsync(model, ph, new HashSet<string>
|
||||||
|
{
|
||||||
|
"Password"
|
||||||
|
}, key).ContinueWith((t) =>
|
||||||
|
{
|
||||||
|
encPhs.Add(ph);
|
||||||
|
});
|
||||||
|
tasks.Add(task);
|
||||||
|
}
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
return encPhs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue