diff --git a/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs b/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs index b98040c..1125605 100644 --- a/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs +++ b/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs @@ -6,5 +6,6 @@ namespace ModernKeePass.Application.Common.Interfaces { Task Protect(string value); Task UnProtect(string value); + byte[] Random(uint length); } } \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs index d16dfe7..cb243fd 100644 --- a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs +++ b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs @@ -35,7 +35,7 @@ namespace ModernKeePass.Application.Common.Interfaces EntryEntity GetEntry(string id); Task AddEntry(string parentGroupId, string entryId); Task MoveEntry(string parentGroupId, string entryId, int index); - void UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected); + Task UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected); void DeleteField(string entryId, string fieldName); Task RemoveEntry(string parentGroupId, string entryId); EntryEntity CreateEntry(string parentGroupId); diff --git a/ModernKeePass.Application/Entry/Commands/UpsertField/UpsertFieldCommand.cs b/ModernKeePass.Application/Entry/Commands/UpsertField/UpsertFieldCommand.cs index 5fcbb0b..0aa39dc 100644 --- a/ModernKeePass.Application/Entry/Commands/UpsertField/UpsertFieldCommand.cs +++ b/ModernKeePass.Application/Entry/Commands/UpsertField/UpsertFieldCommand.cs @@ -1,4 +1,5 @@ -using MediatR; +using System.Threading.Tasks; +using MediatR; using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Domain.Exceptions; @@ -11,7 +12,7 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField public object FieldValue { get; set; } public bool IsProtected { get; set; } = true; - public class UpsertFieldCommandHandler : IRequestHandler + public class UpsertFieldCommandHandler : IAsyncRequestHandler { private readonly IDatabaseProxy _database; @@ -20,11 +21,11 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField _database = database; } - public void Handle(UpsertFieldCommand message) + public async Task Handle(UpsertFieldCommand message) { if (!_database.IsOpen) throw new DatabaseClosedException(); - _database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected); + await _database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected); } } } diff --git a/ModernKeePass.Application/Entry/Models/EntryVm.cs b/ModernKeePass.Application/Entry/Models/EntryVm.cs index cef4094..e3b9625 100644 --- a/ModernKeePass.Application/Entry/Models/EntryVm.cs +++ b/ModernKeePass.Application/Entry/Models/EntryVm.cs @@ -39,29 +39,20 @@ namespace ModernKeePass.Application.Entry.Models public void Mapping(Profile profile) { profile.CreateMap() - .ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentId)) - .ForMember(d => d.ParentGroupName, opts => opts.MapFrom(s => s.ParentName)) - .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => - f.Name.Equals(EntryFieldName.Title, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Title, IsProtected = true } )) + f.Name.Equals(EntryFieldName.Title, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Title, IsProtected = false } )) .ForMember(d => d.Username, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => - f.Name.Equals(EntryFieldName.UserName, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.UserName, IsProtected = true } )) + f.Name.Equals(EntryFieldName.UserName, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.UserName, IsProtected = false } )) .ForMember(d => d.Password, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => f.Name.Equals(EntryFieldName.Password, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Password, IsProtected = true } )) .ForMember(d => d.Url, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => - f.Name.Equals(EntryFieldName.Url, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Url, IsProtected = true } )) + f.Name.Equals(EntryFieldName.Url, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Url, IsProtected = false } )) .ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => - f.Name.Equals(EntryFieldName.Notes, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Notes, IsProtected = true } )) + f.Name.Equals(EntryFieldName.Notes, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Notes, IsProtected = false } )) .ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.Fields.Where(f => !EntryFieldName.StandardFieldNames.Contains(f.Name, StringComparer.OrdinalIgnoreCase)))) .ForMember(d => d.History, opts => opts.MapFrom(s => s.History.Reverse())) - .ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate)) - .ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate)) - .ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate)) - .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon)) - .ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor)) - .ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor)) - .ForMember(d => d.Attachments, opts => opts.MapFrom(s => s.Attachments)); + .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon)); } } } \ No newline at end of file diff --git a/ModernKeePass.Application/Group/Commands/CreateGroup/CreateGroupCommand.cs b/ModernKeePass.Application/Group/Commands/CreateGroup/CreateGroupCommand.cs index 33536c1..757ed6e 100644 --- a/ModernKeePass.Application/Group/Commands/CreateGroup/CreateGroupCommand.cs +++ b/ModernKeePass.Application/Group/Commands/CreateGroup/CreateGroupCommand.cs @@ -29,7 +29,7 @@ namespace ModernKeePass.Application.Group.Commands.CreateGroup var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin); var groupVm = _mapper.Map(group); - message.ParentGroup.SubGroups.Add(groupVm); + message.ParentGroup.Groups.Add(groupVm); return groupVm; } } diff --git a/ModernKeePass.Application/Group/Commands/MoveGroup/MoveGroupCommand.cs b/ModernKeePass.Application/Group/Commands/MoveGroup/MoveGroupCommand.cs index dcaf4e1..481ea0b 100644 --- a/ModernKeePass.Application/Group/Commands/MoveGroup/MoveGroupCommand.cs +++ b/ModernKeePass.Application/Group/Commands/MoveGroup/MoveGroupCommand.cs @@ -26,7 +26,7 @@ namespace ModernKeePass.Application.Group.Commands.MoveGroup if (!_database.IsOpen) throw new DatabaseClosedException(); await _database.MoveGroup(message.ParentGroup.Id, message.Group.Id, message.Index); - message.ParentGroup.SubGroups.Insert(message.Index, message.Group); + message.ParentGroup.Groups.Insert(message.Index, message.Group); } } } diff --git a/ModernKeePass.Application/Group/Commands/SortGroups/SortGroupsCommand.cs b/ModernKeePass.Application/Group/Commands/SortGroups/SortGroupsCommand.cs index f5c6208..a0a8a2f 100644 --- a/ModernKeePass.Application/Group/Commands/SortGroups/SortGroupsCommand.cs +++ b/ModernKeePass.Application/Group/Commands/SortGroups/SortGroupsCommand.cs @@ -24,7 +24,7 @@ namespace ModernKeePass.Application.Group.Commands.SortGroups if (!_database.IsOpen) throw new DatabaseClosedException(); _database.SortSubGroups(message.Group.Id); - message.Group.SubGroups = message.Group.SubGroups.OrderBy(g => g.Title).ToList(); + message.Group.Groups = message.Group.Groups.OrderBy(g => g.Title).ToList(); } } } diff --git a/ModernKeePass.Application/Group/Models/GroupVm.cs b/ModernKeePass.Application/Group/Models/GroupVm.cs index ff23eaf..8776759 100644 --- a/ModernKeePass.Application/Group/Models/GroupVm.cs +++ b/ModernKeePass.Application/Group/Models/GroupVm.cs @@ -15,7 +15,7 @@ namespace ModernKeePass.Application.Group.Models public string Id { get; set; } public string Title { get; set; } public Icon Icon { get; set; } - public List SubGroups { get; set; } + public List Groups { get; set; } public List Entries { get; set; } public override string ToString() @@ -26,13 +26,7 @@ namespace ModernKeePass.Application.Group.Models public void Mapping(Profile profile) { profile.CreateMap() - .ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentId)) - .ForMember(d => d.ParentGroupName, opts => opts.MapFrom(s => s.ParentName)) - .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name)) - .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon)) - .ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries)) - .ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.SubGroups)) .MaxDepth(2); } } diff --git a/ModernKeePass.Application/Security/Commands/GenerateKeyFile/GenerateKeyFileCommand.cs b/ModernKeePass.Application/Security/Commands/GenerateKeyFile/GenerateKeyFileCommand.cs index d10b692..67fd2ac 100644 --- a/ModernKeePass.Application/Security/Commands/GenerateKeyFile/GenerateKeyFileCommand.cs +++ b/ModernKeePass.Application/Security/Commands/GenerateKeyFile/GenerateKeyFileCommand.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using MediatR; using ModernKeePass.Application.Common.Interfaces; @@ -14,11 +13,13 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile { private readonly ICredentialsProxy _security; private readonly IFileProxy _file; + private readonly ICryptographyClient _cryptography; - public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file) + public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file, ICryptographyClient cryptography) { _security = security; _file = file; + _cryptography = cryptography; } public async Task Handle(GenerateKeyFileCommand message) @@ -26,9 +27,7 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile byte[] entropy = null; if (message.AddAdditionalEntropy) { - entropy = new byte[10]; - var random = new Random(); - random.NextBytes(entropy); + entropy = _cryptography.Random(10); } var keyFile = _security.GenerateKeyFile(entropy); await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile); diff --git a/ModernKeePass.Domain/Entities/BaseEntity.cs b/ModernKeePass.Domain/Entities/BaseEntity.cs index c734f1a..a9737bd 100644 --- a/ModernKeePass.Domain/Entities/BaseEntity.cs +++ b/ModernKeePass.Domain/Entities/BaseEntity.cs @@ -6,8 +6,8 @@ namespace ModernKeePass.Domain.Entities { public string Id { get; set; } public string Name { get; set; } - public string ParentId { get; set; } - public string ParentName { get; set; } - public DateTimeOffset LastModificationDate { get; set; } + public string ParentGroupId { get; set; } + public string ParentGroupName { get; set; } + public DateTimeOffset ModificationDate { get; set; } } } \ No newline at end of file diff --git a/ModernKeePass.Domain/Entities/GroupEntity.cs b/ModernKeePass.Domain/Entities/GroupEntity.cs index f538150..f42538a 100644 --- a/ModernKeePass.Domain/Entities/GroupEntity.cs +++ b/ModernKeePass.Domain/Entities/GroupEntity.cs @@ -5,7 +5,7 @@ namespace ModernKeePass.Domain.Entities { public class GroupEntity : BaseEntity { - public List SubGroups { get; set; } = new List(); + public List Groups { get; set; } = new List(); public List Entries { get; set; } = new List(); public Icon Icon { get; set; } } diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs index d8276d9..43e0535 100644 --- a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs +++ b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs @@ -25,6 +25,7 @@ namespace ModernKeePass.Infrastructure.KeePass { private readonly IMapper _mapper; private readonly IDateTime _dateTime; + private readonly ICryptographyClient _cryptography; private readonly PwDatabase _pwDatabase = new PwDatabase(); private Credentials _credentials; // Flag: Has Dispose already been called? @@ -94,10 +95,11 @@ namespace ModernKeePass.Infrastructure.KeePass set { _pwDatabase.Compression = (PwCompressionAlgorithm) Enum.Parse(typeof(PwCompressionAlgorithm), value); } } - public KeePassDatabaseClient(IMapper mapper, IDateTime dateTime) + public KeePassDatabaseClient(IMapper mapper, IDateTime dateTime, ICryptographyClient cryptography) { _mapper = mapper; _dateTime = dateTime; + _cryptography = cryptography; } public async Task Open(byte[] file, Credentials credentials) @@ -240,7 +242,7 @@ namespace ModernKeePass.Infrastructure.KeePass _pwDatabase.DeletedObjects.Add(new PwDeletedObject(BuildIdFromString(entityId), _dateTime.Now)); } - public void UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected) + public async Task UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected) { var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); @@ -251,7 +253,8 @@ namespace ModernKeePass.Infrastructure.KeePass case EntryFieldName.Password: case EntryFieldName.Notes: case EntryFieldName.Url: - pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(isProtected, fieldValue.ToString())); + var unprotectedFieldValue = isProtected ? await _cryptography.UnProtect(fieldValue.ToString()) : fieldValue.ToString(); + pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(isProtected, unprotectedFieldValue)); break; case EntryFieldName.HasExpirationDate: pwEntry.Expires = (bool)fieldValue; @@ -268,8 +271,9 @@ namespace ModernKeePass.Infrastructure.KeePass case EntryFieldName.ForegroundColor: pwEntry.ForegroundColor = (Color)fieldValue; break; - default: - pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, fieldValue.ToString())); + default: + var unprotectedAdditionalFieldValue = isProtected ? await _cryptography.UnProtect(fieldValue.ToString()) : fieldValue.ToString(); + pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, unprotectedAdditionalFieldValue)); break; } } @@ -394,7 +398,7 @@ namespace ModernKeePass.Infrastructure.KeePass { Id = g.Uuid.ToHexString(), Name = g.Name, - ParentName = g.ParentGroup?.Name + ParentGroupName = g.ParentGroup?.Name }); return groups; } diff --git a/ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs b/ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs index 592d09c..e969e63 100644 --- a/ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs +++ b/ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Domain.Entities; using ModernKeePassLib; using ModernKeePassLib.Security; @@ -14,32 +15,42 @@ namespace ModernKeePass.Infrastructure.KeePass { CreateMap, FieldEntity>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key)) - .ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value.ReadString())) + .ForMember(dest => dest.Value, opt => opt.ResolveUsing()) .ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected)); CreateMap() - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString())) - .ForMember(dest => dest.ParentName, opt => opt.MapFrom(src => src.ParentGroup.Name)) + .ForMember(dest => dest.ParentGroupId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString())) .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString())) .ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Strings)) - .ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor)) - .ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor)) .ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime))) .ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires)) .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId))) - .ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime))) + .ForMember(dest => dest.ModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime))) .ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Binaries.Select(b => new KeyValuePair (b.Key, b.Value.ReadData()) ))); CreateMap() - .ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString())) - .ForMember(d => d.ParentName, opts => opts.MapFrom(s => s.ParentGroup.Name)) + .ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString())) .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Uuid.ToHexString())) - .ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name)) .ForMember(d => d.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId))) - .ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime)) - .ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries)) - .ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.Groups)) + .ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationTime)) .MaxDepth(2); } } + + public class ProtectedStringResolver : IValueResolver, FieldEntity, string> + { + private readonly ICryptographyClient _cryptography; + + public ProtectedStringResolver(ICryptographyClient cryptography) + { + _cryptography = cryptography; + } + + public string Resolve(KeyValuePair source, FieldEntity destination, string destMember, ResolutionContext context) + { + // TODO: this variable will contain (temporarily) the decrypted string + var decryptedString = source.Value.ReadString(); + return source.Value.IsProtected ? _cryptography.Protect(decryptedString).GetAwaiter().GetResult() : decryptedString; + } + } } \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs b/ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs index 362407d..8b8e117 100644 --- a/ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs +++ b/ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using Windows.Security.Cryptography; using Windows.Security.Cryptography.DataProtection; @@ -10,24 +11,34 @@ namespace ModernKeePass.Infrastructure.UWP { public async Task Protect(string value) { - // Create a DataProtectionProvider object for the specified descriptor. - var provider = new DataProtectionProvider(); + if (string.IsNullOrEmpty(value)) return value; + try + { + // Create a DataProtectionProvider object for the specified descriptor. + var provider = new DataProtectionProvider("LOCAL=user"); + + // Encode the plaintext input message to a buffer. + var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8); - // Encode the plaintext input message to a buffer. - var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8); + // Encrypt the message. + var buffProtected = await provider.ProtectAsync(buffMsg).AsTask().ConfigureAwait(false); + + // Encode buffer to Base64 + var protectedValue = CryptographicBuffer.EncodeToBase64String(buffProtected); - // Encrypt the message. - var buffProtected = await provider.ProtectAsync(buffMsg); - - // Encode buffer to Base64 - var stringProtected = CryptographicBuffer.EncodeToBase64String(buffProtected); - - // Return the encrypted string. - return stringProtected; + // Return the encrypted string. + return protectedValue; + } + catch (Exception e) + { + return string.Empty; + } } public async Task UnProtect(string value) { + if (string.IsNullOrEmpty(value)) return value; + // Create a DataProtectionProvider object. var provider = new DataProtectionProvider(); @@ -35,13 +46,18 @@ namespace ModernKeePass.Infrastructure.UWP var buffProtected = CryptographicBuffer.DecodeFromBase64String(value); // Decrypt the protected message specified on input. - var buffUnprotected = await provider.UnprotectAsync(buffProtected); + var buffUnprotected = await provider.UnprotectAsync(buffProtected).AsTask().ConfigureAwait(false); // Convert the unprotected message from an IBuffer object to a string. - var strClearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected); + var clearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected); // Return the plaintext string. - return strClearText; + return clearText; + } + + public byte[] Random(uint length) + { + return CryptographicBuffer.GenerateRandom(length).ToArray(); } } } \ No newline at end of file diff --git a/ModernKeePass/ViewModels/EntryDetailVm.cs b/ModernKeePass/ViewModels/EntryDetailVm.cs index 500df58..06b33fc 100644 --- a/ModernKeePass/ViewModels/EntryDetailVm.cs +++ b/ModernKeePass/ViewModels/EntryDetailVm.cs @@ -126,7 +126,12 @@ namespace ModernKeePass.ViewModels { AdditionalFields = new ObservableCollection( - SelectedItem.AdditionalFields.Select(f => new EntryFieldVm(f.Name, f.Value, f.IsProtected))); + SelectedItem.AdditionalFields.Select(f => + { + var field = new EntryFieldVm(_cryptography); + field.Initialize(f.Name, f.Value, f.IsProtected); + return field; + })); Attachments = new ObservableCollection(SelectedItem.Attachments.Select(f => new Attachment { Name = f.Key, @@ -161,8 +166,7 @@ namespace ModernKeePass.ViewModels DeleteAdditionalField.RaiseCanExecuteChanged(); } } - - + public string Title { get { return SelectedItem.Title.Value; } @@ -186,11 +190,12 @@ namespace ModernKeePass.ViewModels public string Password { - get { return SelectedItem.Password.Value; } + get { return _cryptography.UnProtect(SelectedItem.Password.Value).GetAwaiter().GetResult(); } set { - SelectedItem.Password.Value = value; - SetFieldValue(nameof(Password), value, true).Wait(); + var protectedPassword = _cryptography.Protect(value).GetAwaiter().GetResult(); + SelectedItem.Password.Value = protectedPassword; + SetFieldValue(nameof(Password), protectedPassword, true).Wait(); RaisePropertyChanged(nameof(Password)); RaisePropertyChanged(nameof(PasswordComplexityIndicator)); } @@ -316,6 +321,7 @@ namespace ModernKeePass.ViewModels private readonly INotificationService _notification; private readonly IFileProxy _file; private readonly ISettingsProxy _settings; + private readonly ICryptographyClient _cryptography; private GroupVm _parent; private EntryVm _selectedItem; private int _selectedIndex; @@ -324,7 +330,7 @@ namespace ModernKeePass.ViewModels private bool _isRevealPassword; private bool _isDirty; - public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file, ISettingsProxy settings) + public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file, ISettingsProxy settings, ICryptographyClient cryptography) { _mediator = mediator; _navigation = navigation; @@ -333,6 +339,7 @@ namespace ModernKeePass.ViewModels _notification = notification; _file = file; _settings = settings; + _cryptography = cryptography; SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty); GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); @@ -417,7 +424,7 @@ namespace ModernKeePass.ViewModels private void AddField() { - AdditionalFields.Add(new EntryFieldVm(string.Empty, string.Empty, false)); + AdditionalFields.Add(new EntryFieldVm(_cryptography)); AdditionalFieldSelectedIndex = AdditionalFields.Count - 1; } diff --git a/ModernKeePass/ViewModels/GroupDetailVm.cs b/ModernKeePass/ViewModels/GroupDetailVm.cs index dbf7e92..21d5a6e 100644 --- a/ModernKeePass/ViewModels/GroupDetailVm.cs +++ b/ModernKeePass/ViewModels/GroupDetailVm.cs @@ -140,7 +140,7 @@ namespace ModernKeePass.ViewModels Entries = new ObservableCollection(_group.Entries); Entries.CollectionChanged += Entries_CollectionChanged; - Groups = new ObservableCollection(_group.SubGroups); + Groups = new ObservableCollection(_group.Groups); Groups.CollectionChanged += Groups_CollectionChanged; } @@ -222,7 +222,7 @@ namespace ModernKeePass.ViewModels { case NotifyCollectionChangedAction.Remove: var oldIndex = e.OldStartingIndex; - _reorderedGroup = _group.SubGroups[oldIndex]; + _reorderedGroup = _group.Groups[oldIndex]; break; case NotifyCollectionChangedAction.Add: if (_reorderedGroup == null) @@ -251,7 +251,7 @@ namespace ModernKeePass.ViewModels private async Task SortGroupsAsync() { await _mediator.Send(new SortGroupsCommand {Group = _group}); - Groups = new ObservableCollection(_group.SubGroups); + Groups = new ObservableCollection(_group.Groups); RaisePropertyChanged(nameof(Groups)); SaveCommand.RaiseCanExecuteChanged(); } diff --git a/ModernKeePass/Views/GroupDetailPage.xaml b/ModernKeePass/Views/GroupDetailPage.xaml index 19c423a..7dcc16a 100644 --- a/ModernKeePass/Views/GroupDetailPage.xaml +++ b/ModernKeePass/Views/GroupDetailPage.xaml @@ -128,7 +128,7 @@ - + diff --git a/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt b/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt index 9e3d180..31f167c 100644 --- a/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt +++ b/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt @@ -1,8 +1 @@ -Support for additional fields -Support for attachments -Add an expiration timer for clipboard data -Ability to manually reorder groups -Ability to set max history count and size -Design changes -Update to KeePassLib version 2.45 -Bug corrections \ No newline at end of file +Data is now protected in memory as well as at rest \ No newline at end of file diff --git a/ModernKeePass/appMetadata/fr-fr/baselisting/releaseNotes.txt b/ModernKeePass/appMetadata/fr-fr/baselisting/releaseNotes.txt index 346b4e6..d8254c6 100644 --- a/ModernKeePass/appMetadata/fr-fr/baselisting/releaseNotes.txt +++ b/ModernKeePass/appMetadata/fr-fr/baselisting/releaseNotes.txt @@ -1,8 +1 @@ -Ajout des champs additionnels -Ajout des pièces-jointes -Ajout d'une expiration du presse papier -Possibilite de reorganiser les groupes manuellement -Possibilite de parametrer le nombre max et la taille de l'historique -Quelques changements de design -Mise a jour de la KeePassLib version 2.45 -Correction de bugs \ No newline at end of file +Protection des donnees en memoire en plus du chiffrement de la base de donnees \ No newline at end of file diff --git a/WinAppCommon/Actions/ClipboardAction.cs b/WinAppCommon/Actions/ClipboardAction.cs index 65a6ad6..29330f3 100644 --- a/WinAppCommon/Actions/ClipboardAction.cs +++ b/WinAppCommon/Actions/ClipboardAction.cs @@ -19,19 +19,28 @@ namespace ModernKeePass.Actions } public static readonly DependencyProperty TextProperty = - DependencyProperty.Register("Text", typeof(string), typeof(ClipboardAction), new PropertyMetadata(string.Empty)); + DependencyProperty.Register(nameof(Text), typeof(string), typeof(ClipboardAction), new PropertyMetadata(string.Empty)); + public bool IsProtected + { + get { return (bool)GetValue(IsProtectedProperty); } + set { SetValue(IsProtectedProperty, value); } + } + + public static readonly DependencyProperty IsProtectedProperty = + DependencyProperty.Register(nameof(IsProtected), typeof(bool), typeof(ClipboardAction), new PropertyMetadata(false)); public object Execute(object sender, object parameter) { if (string.IsNullOrEmpty(Text)) return null; var settings = App.Services.GetRequiredService(); + var cryptography = App.Services.GetRequiredService(); _dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))}; _dispatcher.Tick += Dispatcher_Tick; var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy }; - dataPackage.SetText(Text); + dataPackage.SetText(IsProtected ? cryptography.UnProtect(Text).GetAwaiter().GetResult() : Text); Clipboard.SetContent(dataPackage); _dispatcher.Start(); diff --git a/WinAppCommon/ViewModels/Items/EntryFieldVm.cs b/WinAppCommon/ViewModels/Items/EntryFieldVm.cs index dbc0041..e375dd5 100644 --- a/WinAppCommon/ViewModels/Items/EntryFieldVm.cs +++ b/WinAppCommon/ViewModels/Items/EntryFieldVm.cs @@ -1,12 +1,14 @@ using System.Linq; using GalaSoft.MvvmLight; using Messages; +using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Domain.Enums; namespace ModernKeePass.ViewModels.ListItems { public class EntryFieldVm: ViewModelBase { + private readonly ICryptographyClient _cryptography; private string _name; private string _value; private bool _isProtected; @@ -24,7 +26,10 @@ namespace ModernKeePass.ViewModels.ListItems public string Value { - get { return _value; } + get + { + return IsProtected? _cryptography.UnProtect(_value).GetAwaiter().GetResult() : _value; + } set { MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = value, IsProtected = IsProtected }); @@ -41,7 +46,12 @@ namespace ModernKeePass.ViewModels.ListItems } } - public EntryFieldVm(string fieldName, string fieldValue, bool isProtected) + public EntryFieldVm(ICryptographyClient cryptography) + { + _cryptography = cryptography; + } + + public void Initialize(string fieldName, string fieldValue, bool isProtected) { _name = fieldName; _value = fieldValue; diff --git a/WinAppCommon/ViewModels/Settings/RecycleBinVm.cs b/WinAppCommon/ViewModels/Settings/RecycleBinVm.cs index 34f7979..ade66d9 100644 --- a/WinAppCommon/ViewModels/Settings/RecycleBinVm.cs +++ b/WinAppCommon/ViewModels/Settings/RecycleBinVm.cs @@ -51,7 +51,7 @@ namespace ModernKeePass.ViewModels.Settings _mediator = mediator; _database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult(); - Groups = new ObservableCollection(rootGroup.SubGroups); + Groups = new ObservableCollection(rootGroup.Groups); } } } \ No newline at end of file