mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-03 23:50:18 -04:00
Cryptography service now handles random byte generation
Protected strings are now protected in memory
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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);
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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; }
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
@@ -268,8 +271,9 @@ namespace ModernKeePass.Infrastructure.KeePass
|
|||||||
case EntryFieldName.ForegroundColor:
|
case EntryFieldName.ForegroundColor:
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -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;
|
||||||
@@ -10,24 +11,34 @@ namespace ModernKeePass.Infrastructure.UWP
|
|||||||
{
|
{
|
||||||
public async Task<string> Protect(string value)
|
public async Task<string> Protect(string value)
|
||||||
{
|
{
|
||||||
// Create a DataProtectionProvider object for the specified descriptor.
|
if (string.IsNullOrEmpty(value)) return value;
|
||||||
var provider = new DataProtectionProvider();
|
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.
|
// Encrypt the message.
|
||||||
var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);
|
var buffProtected = await provider.ProtectAsync(buffMsg).AsTask().ConfigureAwait(false);
|
||||||
|
|
||||||
|
// Encode buffer to Base64
|
||||||
|
var protectedValue = CryptographicBuffer.EncodeToBase64String(buffProtected);
|
||||||
|
|
||||||
// Encrypt the message.
|
// Return the encrypted string.
|
||||||
var buffProtected = await provider.ProtectAsync(buffMsg);
|
return protectedValue;
|
||||||
|
}
|
||||||
// Encode buffer to Base64
|
catch (Exception e)
|
||||||
var stringProtected = CryptographicBuffer.EncodeToBase64String(buffProtected);
|
{
|
||||||
|
return string.Empty;
|
||||||
// Return the encrypted string.
|
}
|
||||||
return stringProtected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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,
|
||||||
@@ -161,8 +166,7 @@ namespace ModernKeePass.ViewModels
|
|||||||
DeleteAdditionalField.RaiseCanExecuteChanged();
|
DeleteAdditionalField.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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
|
|
@@ -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
|
|
@@ -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();
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user