cipher service encrypt

This commit is contained in:
Kyle Spearrin 2019-04-15 11:46:20 -04:00
parent 4b67ba027e
commit 694e4960ad
3 changed files with 296 additions and 22 deletions

View file

@ -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);
} }

View file

@ -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; }
} }
} }

View file

@ -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;
}
} }
} }