Cryptography service now handles random byte generation

Protected strings are now protected in memory
This commit is contained in:
Geoffroy BONNEVILLE
2020-05-18 14:14:28 +02:00
parent ceaf7dabd3
commit 9126307b4c
22 changed files with 134 additions and 105 deletions

View File

@@ -6,5 +6,6 @@ namespace ModernKeePass.Application.Common.Interfaces
{ {
Task<string> Protect(string value); Task<string> Protect(string value);
Task<string> UnProtect(string value); Task<string> UnProtect(string value);
byte[] Random(uint length);
} }
} }

View File

@@ -35,7 +35,7 @@ namespace ModernKeePass.Application.Common.Interfaces
EntryEntity GetEntry(string id); EntryEntity GetEntry(string id);
Task AddEntry(string parentGroupId, string entryId); Task AddEntry(string parentGroupId, string entryId);
Task MoveEntry(string parentGroupId, string entryId, int index); 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); void DeleteField(string entryId, string fieldName);
Task RemoveEntry(string parentGroupId, string entryId); Task RemoveEntry(string parentGroupId, string entryId);
EntryEntity CreateEntry(string parentGroupId); EntryEntity CreateEntry(string parentGroupId);

View File

@@ -1,4 +1,5 @@
using MediatR; using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions; using ModernKeePass.Domain.Exceptions;
@@ -11,7 +12,7 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField
public object FieldValue { get; set; } public object FieldValue { get; set; }
public bool IsProtected { get; set; } = true; public bool IsProtected { get; set; } = true;
public class UpsertFieldCommandHandler : IRequestHandler<UpsertFieldCommand> public class UpsertFieldCommandHandler : IAsyncRequestHandler<UpsertFieldCommand>
{ {
private readonly IDatabaseProxy _database; private readonly IDatabaseProxy _database;
@@ -20,11 +21,11 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField
_database = database; _database = database;
} }
public void Handle(UpsertFieldCommand message) public async Task Handle(UpsertFieldCommand message)
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); 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);
} }
} }
} }

View File

@@ -39,29 +39,20 @@ namespace ModernKeePass.Application.Entry.Models
public void Mapping(Profile profile) public void Mapping(Profile profile)
{ {
profile.CreateMap<EntryEntity, EntryVm>() profile.CreateMap<EntryEntity, EntryVm>()
.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 => .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 => .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 => .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 } )) 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 => .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 => .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 => .ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s =>
s.Fields.Where(f => !EntryFieldName.StandardFieldNames.Contains(f.Name, StringComparer.OrdinalIgnoreCase)))) 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.History, opts => opts.MapFrom(s => s.History.Reverse()))
.ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate)) .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon));
.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));
} }
} }
} }

View File

@@ -29,7 +29,7 @@ namespace ModernKeePass.Application.Group.Commands.CreateGroup
var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin); var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin);
var groupVm = _mapper.Map<GroupVm>(group); var groupVm = _mapper.Map<GroupVm>(group);
message.ParentGroup.SubGroups.Add(groupVm); message.ParentGroup.Groups.Add(groupVm);
return groupVm; return groupVm;
} }
} }

View File

@@ -26,7 +26,7 @@ namespace ModernKeePass.Application.Group.Commands.MoveGroup
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.MoveGroup(message.ParentGroup.Id, message.Group.Id, message.Index); 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);
} }
} }
} }

View File

@@ -24,7 +24,7 @@ namespace ModernKeePass.Application.Group.Commands.SortGroups
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.SortSubGroups(message.Group.Id); _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();
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace ModernKeePass.Application.Group.Models
public string Id { get; set; } public string Id { get; set; }
public string Title { get; set; } public string Title { get; set; }
public Icon Icon { get; set; } public Icon Icon { get; set; }
public List<GroupVm> SubGroups { get; set; } public List<GroupVm> Groups { get; set; }
public List<EntryVm> Entries { get; set; } public List<EntryVm> Entries { get; set; }
public override string ToString() public override string ToString()
@@ -26,13 +26,7 @@ namespace ModernKeePass.Application.Group.Models
public void Mapping(Profile profile) public void Mapping(Profile profile)
{ {
profile.CreateMap<GroupEntity, GroupVm>() profile.CreateMap<GroupEntity, GroupVm>()
.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.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); .MaxDepth(2);
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
@@ -14,11 +13,13 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile
{ {
private readonly ICredentialsProxy _security; private readonly ICredentialsProxy _security;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly ICryptographyClient _cryptography;
public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file) public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file, ICryptographyClient cryptography)
{ {
_security = security; _security = security;
_file = file; _file = file;
_cryptography = cryptography;
} }
public async Task Handle(GenerateKeyFileCommand message) public async Task Handle(GenerateKeyFileCommand message)
@@ -26,9 +27,7 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile
byte[] entropy = null; byte[] entropy = null;
if (message.AddAdditionalEntropy) if (message.AddAdditionalEntropy)
{ {
entropy = new byte[10]; entropy = _cryptography.Random(10);
var random = new Random();
random.NextBytes(entropy);
} }
var keyFile = _security.GenerateKeyFile(entropy); var keyFile = _security.GenerateKeyFile(entropy);
await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile); await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile);

View File

@@ -6,8 +6,8 @@ namespace ModernKeePass.Domain.Entities
{ {
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string ParentId { get; set; } public string ParentGroupId { get; set; }
public string ParentName { get; set; } public string ParentGroupName { get; set; }
public DateTimeOffset LastModificationDate { get; set; } public DateTimeOffset ModificationDate { get; set; }
} }
} }

View File

@@ -5,7 +5,7 @@ namespace ModernKeePass.Domain.Entities
{ {
public class GroupEntity : BaseEntity public class GroupEntity : BaseEntity
{ {
public List<GroupEntity> SubGroups { get; set; } = new List<GroupEntity>(); public List<GroupEntity> Groups { get; set; } = new List<GroupEntity>();
public List<EntryEntity> Entries { get; set; } = new List<EntryEntity>(); public List<EntryEntity> Entries { get; set; } = new List<EntryEntity>();
public Icon Icon { get; set; } public Icon Icon { get; set; }
} }

View File

@@ -25,6 +25,7 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly IDateTime _dateTime; private readonly IDateTime _dateTime;
private readonly ICryptographyClient _cryptography;
private readonly PwDatabase _pwDatabase = new PwDatabase(); private readonly PwDatabase _pwDatabase = new PwDatabase();
private Credentials _credentials; private Credentials _credentials;
// Flag: Has Dispose already been called? // Flag: Has Dispose already been called?
@@ -94,10 +95,11 @@ namespace ModernKeePass.Infrastructure.KeePass
set { _pwDatabase.Compression = (PwCompressionAlgorithm) Enum.Parse(typeof(PwCompressionAlgorithm), value); } 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; _mapper = mapper;
_dateTime = dateTime; _dateTime = dateTime;
_cryptography = cryptography;
} }
public async Task Open(byte[] file, Credentials credentials) 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)); _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); var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
@@ -251,7 +253,8 @@ namespace ModernKeePass.Infrastructure.KeePass
case EntryFieldName.Password: case EntryFieldName.Password:
case EntryFieldName.Notes: case EntryFieldName.Notes:
case EntryFieldName.Url: 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; break;
case EntryFieldName.HasExpirationDate: case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue; pwEntry.Expires = (bool)fieldValue;
@@ -269,7 +272,8 @@ namespace ModernKeePass.Infrastructure.KeePass
pwEntry.ForegroundColor = (Color)fieldValue; pwEntry.ForegroundColor = (Color)fieldValue;
break; break;
default: default:
pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, fieldValue.ToString())); var unprotectedAdditionalFieldValue = isProtected ? await _cryptography.UnProtect(fieldValue.ToString()) : fieldValue.ToString();
pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, unprotectedAdditionalFieldValue));
break; break;
} }
} }
@@ -394,7 +398,7 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
Id = g.Uuid.ToHexString(), Id = g.Uuid.ToHexString(),
Name = g.Name, Name = g.Name,
ParentName = g.ParentGroup?.Name ParentGroupName = g.ParentGroup?.Name
}); });
return groups; return groups;
} }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using AutoMapper; using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Entities; using ModernKeePass.Domain.Entities;
using ModernKeePassLib; using ModernKeePassLib;
using ModernKeePassLib.Security; using ModernKeePassLib.Security;
@@ -14,32 +15,42 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
CreateMap<KeyValuePair<string, ProtectedString>, FieldEntity>() CreateMap<KeyValuePair<string, ProtectedString>, FieldEntity>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key)) .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<ProtectedStringResolver>())
.ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected)); .ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected));
CreateMap<PwEntry, EntryEntity>() CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString())) .ForMember(dest => dest.ParentGroupId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString()))
.ForMember(dest => dest.ParentName, opt => opt.MapFrom(src => src.ParentGroup.Name))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.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.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.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime)))
.ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires)) .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.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<string, byte[]> (b.Key, b.Value.ReadData()) ))); .ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Binaries.Select(b => new KeyValuePair<string, byte[]> (b.Key, b.Value.ReadData()) )));
CreateMap<PwGroup, GroupEntity>() CreateMap<PwGroup, GroupEntity>()
.ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString())) .ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString()))
.ForMember(d => d.ParentName, opts => opts.MapFrom(s => s.ParentGroup.Name))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.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.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime)) .ForMember(d => d.ModificationDate, 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))
.MaxDepth(2); .MaxDepth(2);
} }
} }
public class ProtectedStringResolver : IValueResolver<KeyValuePair<string, ProtectedString>, FieldEntity, string>
{
private readonly ICryptographyClient _cryptography;
public ProtectedStringResolver(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public string Resolve(KeyValuePair<string, ProtectedString> 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;
}
}
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Security.Cryptography; using Windows.Security.Cryptography;
using Windows.Security.Cryptography.DataProtection; using Windows.Security.Cryptography.DataProtection;
@@ -9,25 +10,35 @@ namespace ModernKeePass.Infrastructure.UWP
public class UwpCryptographyClient: ICryptographyClient public class UwpCryptographyClient: ICryptographyClient
{ {
public async Task<string> Protect(string value) public async Task<string> Protect(string value)
{
if (string.IsNullOrEmpty(value)) return value;
try
{ {
// Create a DataProtectionProvider object for the specified descriptor. // Create a DataProtectionProvider object for the specified descriptor.
var provider = new DataProtectionProvider(); var provider = new DataProtectionProvider("LOCAL=user");
// Encode the plaintext input message to a buffer. // Encode the plaintext input message to a buffer.
var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8); var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);
// Encrypt the message. // Encrypt the message.
var buffProtected = await provider.ProtectAsync(buffMsg); var buffProtected = await provider.ProtectAsync(buffMsg).AsTask().ConfigureAwait(false);
// Encode buffer to Base64 // Encode buffer to Base64
var stringProtected = CryptographicBuffer.EncodeToBase64String(buffProtected); var protectedValue = CryptographicBuffer.EncodeToBase64String(buffProtected);
// Return the encrypted string. // Return the encrypted string.
return stringProtected; return protectedValue;
}
catch (Exception e)
{
return string.Empty;
}
} }
public async Task<string> UnProtect(string value) public async Task<string> UnProtect(string value)
{ {
if (string.IsNullOrEmpty(value)) return value;
// Create a DataProtectionProvider object. // Create a DataProtectionProvider object.
var provider = new DataProtectionProvider(); var provider = new DataProtectionProvider();
@@ -35,13 +46,18 @@ namespace ModernKeePass.Infrastructure.UWP
var buffProtected = CryptographicBuffer.DecodeFromBase64String(value); var buffProtected = CryptographicBuffer.DecodeFromBase64String(value);
// Decrypt the protected message specified on input. // 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. // 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 the plaintext string.
return strClearText; return clearText;
}
public byte[] Random(uint length)
{
return CryptographicBuffer.GenerateRandom(length).ToArray();
} }
} }
} }

View File

@@ -126,7 +126,12 @@ namespace ModernKeePass.ViewModels
{ {
AdditionalFields = AdditionalFields =
new ObservableCollection<EntryFieldVm>( new ObservableCollection<EntryFieldVm>(
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<Attachment>(SelectedItem.Attachments.Select(f => new Attachment Attachments = new ObservableCollection<Attachment>(SelectedItem.Attachments.Select(f => new Attachment
{ {
Name = f.Key, Name = f.Key,
@@ -162,7 +167,6 @@ namespace ModernKeePass.ViewModels
} }
} }
public string Title public string Title
{ {
get { return SelectedItem.Title.Value; } get { return SelectedItem.Title.Value; }
@@ -186,11 +190,12 @@ namespace ModernKeePass.ViewModels
public string Password public string Password
{ {
get { return SelectedItem.Password.Value; } get { return _cryptography.UnProtect(SelectedItem.Password.Value).GetAwaiter().GetResult(); }
set set
{ {
SelectedItem.Password.Value = value; var protectedPassword = _cryptography.Protect(value).GetAwaiter().GetResult();
SetFieldValue(nameof(Password), value, true).Wait(); SelectedItem.Password.Value = protectedPassword;
SetFieldValue(nameof(Password), protectedPassword, true).Wait();
RaisePropertyChanged(nameof(Password)); RaisePropertyChanged(nameof(Password));
RaisePropertyChanged(nameof(PasswordComplexityIndicator)); RaisePropertyChanged(nameof(PasswordComplexityIndicator));
} }
@@ -316,6 +321,7 @@ namespace ModernKeePass.ViewModels
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly ISettingsProxy _settings; private readonly ISettingsProxy _settings;
private readonly ICryptographyClient _cryptography;
private GroupVm _parent; private GroupVm _parent;
private EntryVm _selectedItem; private EntryVm _selectedItem;
private int _selectedIndex; private int _selectedIndex;
@@ -324,7 +330,7 @@ namespace ModernKeePass.ViewModels
private bool _isRevealPassword; private bool _isRevealPassword;
private bool _isDirty; 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; _mediator = mediator;
_navigation = navigation; _navigation = navigation;
@@ -333,6 +339,7 @@ namespace ModernKeePass.ViewModels
_notification = notification; _notification = notification;
_file = file; _file = file;
_settings = settings; _settings = settings;
_cryptography = cryptography;
SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty); SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
@@ -417,7 +424,7 @@ namespace ModernKeePass.ViewModels
private void AddField() private void AddField()
{ {
AdditionalFields.Add(new EntryFieldVm(string.Empty, string.Empty, false)); AdditionalFields.Add(new EntryFieldVm(_cryptography));
AdditionalFieldSelectedIndex = AdditionalFields.Count - 1; AdditionalFieldSelectedIndex = AdditionalFields.Count - 1;
} }

View File

@@ -140,7 +140,7 @@ namespace ModernKeePass.ViewModels
Entries = new ObservableCollection<EntryVm>(_group.Entries); Entries = new ObservableCollection<EntryVm>(_group.Entries);
Entries.CollectionChanged += Entries_CollectionChanged; Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<GroupVm>(_group.Groups);
Groups.CollectionChanged += Groups_CollectionChanged; Groups.CollectionChanged += Groups_CollectionChanged;
} }
@@ -222,7 +222,7 @@ namespace ModernKeePass.ViewModels
{ {
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
var oldIndex = e.OldStartingIndex; var oldIndex = e.OldStartingIndex;
_reorderedGroup = _group.SubGroups[oldIndex]; _reorderedGroup = _group.Groups[oldIndex];
break; break;
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
if (_reorderedGroup == null) if (_reorderedGroup == null)
@@ -251,7 +251,7 @@ namespace ModernKeePass.ViewModels
private async Task SortGroupsAsync() private async Task SortGroupsAsync()
{ {
await _mediator.Send(new SortGroupsCommand {Group = _group}); await _mediator.Send(new SortGroupsCommand {Group = _group});
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<GroupVm>(_group.Groups);
RaisePropertyChanged(nameof(Groups)); RaisePropertyChanged(nameof(Groups));
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
} }

View File

@@ -128,7 +128,7 @@
<MenuFlyoutItem x:Uid="EntryItemCopyPassword"> <MenuFlyoutItem x:Uid="EntryItemCopyPassword">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click"> <core:EventTriggerBehavior EventName="Click">
<actions:ClipboardAction Text="{Binding Password}" /> <actions:ClipboardAction Text="{Binding Password}" IsProtected="True" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" /> <actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>

View File

@@ -1,8 +1 @@
Support for additional fields Data is now protected in memory as well as at rest
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

View File

@@ -1,8 +1 @@
Ajout des champs additionnels Protection des donnees en memoire en plus du chiffrement de la base de donnees
Ajout des pi<70>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

View File

@@ -19,19 +19,28 @@ namespace ModernKeePass.Actions
} }
public static readonly DependencyProperty TextProperty = 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) public object Execute(object sender, object parameter)
{ {
if (string.IsNullOrEmpty(Text)) return null; if (string.IsNullOrEmpty(Text)) return null;
var settings = App.Services.GetRequiredService<ISettingsProxy>(); var settings = App.Services.GetRequiredService<ISettingsProxy>();
var cryptography = App.Services.GetRequiredService<ICryptographyClient>();
_dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))}; _dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))};
_dispatcher.Tick += Dispatcher_Tick; _dispatcher.Tick += Dispatcher_Tick;
var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy }; var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy };
dataPackage.SetText(Text); dataPackage.SetText(IsProtected ? cryptography.UnProtect(Text).GetAwaiter().GetResult() : Text);
Clipboard.SetContent(dataPackage); Clipboard.SetContent(dataPackage);
_dispatcher.Start(); _dispatcher.Start();

View File

@@ -1,12 +1,14 @@
using System.Linq; using System.Linq;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using Messages; using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
namespace ModernKeePass.ViewModels.ListItems namespace ModernKeePass.ViewModels.ListItems
{ {
public class EntryFieldVm: ViewModelBase public class EntryFieldVm: ViewModelBase
{ {
private readonly ICryptographyClient _cryptography;
private string _name; private string _name;
private string _value; private string _value;
private bool _isProtected; private bool _isProtected;
@@ -24,7 +26,10 @@ namespace ModernKeePass.ViewModels.ListItems
public string Value public string Value
{ {
get { return _value; } get
{
return IsProtected? _cryptography.UnProtect(_value).GetAwaiter().GetResult() : _value;
}
set set
{ {
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = value, IsProtected = IsProtected }); 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; _name = fieldName;
_value = fieldValue; _value = fieldValue;

View File

@@ -51,7 +51,7 @@ namespace ModernKeePass.ViewModels.Settings
_mediator = mediator; _mediator = mediator;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); _database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult(); var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult();
Groups = new ObservableCollection<IEntityVm>(rootGroup.SubGroups); Groups = new ObservableCollection<IEntityVm>(rootGroup.Groups);
} }
} }
} }