encyption wip

This commit is contained in:
TheXamlGuy
2024-04-29 21:42:04 +01:00
parent bfdffb2901
commit 2a4194ee22
27 changed files with 437 additions and 137 deletions
+14 -6
View File
@@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -55,14 +54,25 @@ public partial class App : Application
{
args.AddServices(services =>
{
services.AddTransient<IEncryptor, AesEncryptor>();
services.AddTransient<IDecryptor, AesDecryptor>();
services.AddTransient<IPasswordHasher, PasswordHasher>();
services.AddTransient<IKeyDeriver, KeyDeriver>();
services.AddDbContextFactory<VaultDbContext>(args =>
{
args.UseSqlite();
});
services.AddDbContextFactory<VaultDbContext>();
services.AddHandler<VaultStorageHandler>();
services.AddHandler<OpenVaultHandler>();
services.AddHandler<CreateVaultStorageHandler>();
services.AddHandler<OpenVaultStorageHandler>();
services.AddTemplate<VaultNavigationViewModel, VaultNavigationView>();
services.AddTemplate<AllNavigationViewModel, AllNavigationView>();
services.AddTemplate<StarredNavigationViewModel, StarredNavigationView>();
services.AddTemplate<CategoriesNavigationViewModel, CategoriesNavigationView>();
@@ -74,15 +84,13 @@ public partial class App : Application
})!);
services.AddSingleton<IVaultHostCollection, VaultHostCollection>();
services.AddHandler<VaultHandler>();
services.AddHandler<CreateVaultHandler>();
//services.AddInitializer<VaultsInitializer>();
services.AddInitializer<VaultCollectionInitializer>();
services.AddTemplate<MainViewModel, MainView>("Main");
services.AddTemplate<VaultNavigationViewModel, VaultNavigationView>();
services.AddHandler<VaultNavigationViewModelHandler>();
services.AddTransient<FooterViewModel>();
services.AddTemplate<ManageNavigationViewModel, ManageNavigationView>();
+10 -2
View File
@@ -12,7 +12,15 @@
Margin="0,0,0,18"
Text="{Binding Name}"
Watermark="Enter vault name" />
<TextBox Margin="0,0,0,18" Watermark="Enter password" />
<TextBox Watermark="Confirm password" />
<TextBox
Margin="0,0,0,18"
Classes="revealPasswordButton"
PasswordChar="&#x25CF;"
Text="{Binding Password}"
Watermark="Enter password" />
<TextBox
Classes="revealPasswordButton"
PasswordChar="&#x25CF;"
Watermark="Confirm password" />
</StackPanel>
</ContentDialog>
+8 -5
View File
@@ -1,16 +1,19 @@
<UserControl
x:Class="Bitvault.Avalonia.LockView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Bitvault"
x:DataType="vm:LockViewModel">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox
Width="300"
Classes="revealPasswordButton"
PasswordChar="&#x25CF;">
PasswordChar="&#x25CF;"
Text="{Binding Password}">
<Interaction.Behaviors>
<KeyBindingTriggerBehavior Gesture="Enter">
<NavigateAction Context="Main" Route="Vault" />
</KeyBindingTriggerBehavior>
<KeyBindingTriggerBehavior Gesture="Enter">
<InvokeCommandAction Command="{Binding UnlockCommand}" />
</KeyBindingTriggerBehavior>
</Interaction.Behaviors>
</TextBox>
</StackPanel>
+29
View File
@@ -0,0 +1,29 @@
using System.Security.Cryptography;
namespace Bitvault;
public class AesDecryptor :
IDecryptor
{
private const int IvSize = 16;
public string Decrypt(string cipherText, byte[] key)
{
byte[] cipherData = Convert.FromBase64String(cipherText);
byte[] iv = new byte[IvSize];
Array.Copy(cipherData, 0, iv, 0, IvSize); // Extract the IV from the start of the cipher data
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var memoryStream = new MemoryStream(cipherData, IvSize, cipherData.Length - IvSize);
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream))
{
return streamReader.ReadToEnd(); // Return the decrypted text
}
}
}
+29
View File
@@ -0,0 +1,29 @@
using System.Security.Cryptography;
namespace Bitvault;
public class AesEncryptor : IEncryptor
{
public string Encrypt(string plainText, byte[] key)
{
const int IvSize = 16;
using var aes = Aes.Create();
aes.Key = key;
aes.GenerateIV();
byte[] iv = aes.IV;
using var memoryStream = new MemoryStream();
memoryStream.Write(iv, 0, IvSize); // Store IV at the start of the stream
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
using (var streamWriter = new StreamWriter(cryptoStream))
{
streamWriter.Write(plainText);
}
return Convert.ToBase64String(memoryStream.ToArray()); // Return the encrypted data in base64
}
}
+32
View File
@@ -0,0 +1,32 @@
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public class CreateVaultHandler(IComponentFactory componentFactory) :
IHandler<Create<Vault>, bool>
{
public async Task<bool> Handle(Create<Vault> args,
CancellationToken cancellationToken)
{
if (args.Value is Vault vault)
{
if (vault.Name is { Length: > 0 } name && vault.Password is { Length: > 0 } password)
{
if (componentFactory.Create<IVaultComponent, VaultConfiguration>($"Vault:{name}", new VaultConfiguration { Name = name }) is IComponentHost host)
{
if (host.Services.GetRequiredService<IMediator>() is IMediator mediator)
{
if (await mediator.Handle<Create<VaultStorage>, bool>(Create.As(new VaultStorage(name, password)), cancellationToken))
{
await host.StartAsync(cancellationToken);
return true;
}
}
}
}
}
return false;
}
}
+73
View File
@@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using System.Security.Cryptography;
using Toolkit.Foundation;
namespace Bitvault;
public class CreateVaultStorageHandler(IHostEnvironment environment,
IKeyDeriver deriver,
IEncryptor encryptor,
IDecryptor decryptor,
IDbContextFactory<VaultDbContext> dbContextFactory) : IHandler<Create<VaultStorage>, bool>
{
public async Task<bool> Handle(Create<VaultStorage> args, CancellationToken cancellationToken)
{
if (args.Value is VaultStorage vault)
{
if (vault.Name is { Length: > 0 } name && vault.Password is { Length: > 0 } password)
{
byte[] salt = new byte[16];
RandomNumberGenerator.Fill(salt);
byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);
byte[] derivedKey = deriver.DeriveKey(password, salt);
string? encryptedKey = encryptor.Encrypt(Convert.ToBase64String(key), derivedKey);
byte[] derivedKey2 = deriver.DeriveKey(password, salt);
var dod = decryptor.Decrypt(encryptedKey, derivedKey2);
// Derive the key for encryption
//byte[] encryptionKey = deriver.DeriveKey(password, salt);
//// Derive the key for decryption
//byte[] decryptionKey = deriver.DeriveKey(password, salt);
//// Compare keys to ensure they're the same
//bool areKeysEqual = encryptionKey.SequenceEqual(decryptionKey);
////byte[] derivedKey = deriver.DeriveKey(password, salt);
//string? encrypted = encryptor.Encrypt(password, derivedKey);
//var storedSalt = Convert.ToBase64String(salt);
//byte[] derivedKey2 = deriver.DeriveKey(password, salt);
//var d = decryptor.Decrypt(encrypted, derivedKey2);
// Generate a hash
//string hash = hasher.HashPassword(password);
//string[] parts = hash.Split(':');
//// Store the salt only
//string storedSalt = parts[0];
//// Use the hash as the password
//string storedHash = parts[1];
//context.Database.SetConnectionString($"Data Source={Path.Combine(environment.ContentRootPath, name)}.vault;Mode=ReadWriteCreate;Password={storedHash}");
//await context.Database.EnsureCreatedAsync();
return true;
}
}
return false;
}
}
+5 -1
View File
@@ -17,8 +17,12 @@ public partial class CreateVaultViewModel(IServiceProvider provider,
[ObservableProperty]
private string name;
[MaybeNull]
[ObservableProperty]
private string password;
public async Task<bool> Confirm()
{
return await Mediator.Handle<Create<Vault>, bool>(Create.As(new Vault(Name, "")));
return await Mediator.Handle<Create<Vault>, bool>(Create.As(new Vault(Name, Password)));
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Bitvault
{
public interface IDecryptor
{
string? Decrypt(string cipherText, byte[] key);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace Bitvault
{
public interface IEncryptor
{
string? Encrypt(string plainText, byte[] key);
}
}
+6
View File
@@ -0,0 +1,6 @@
namespace Bitvault;
public interface IKeyDeriver
{
byte[] DeriveKey(string password, byte[] salt, int keySize = 32, int iterations = 10000);
}
+6
View File
@@ -0,0 +1,6 @@
namespace Bitvault;
public interface IPasswordHasher
{
string HashPassword(string password, int iterations = 10000);
}
+9
View File
@@ -0,0 +1,9 @@
namespace Bitvault
{
public interface IVaultConnectionPersistence
{
void Dispose();
string? Get(string key);
void Set(string key, string connection);
}
}
-11
View File
@@ -1,11 +0,0 @@
using Toolkit.Foundation;
namespace Bitvault
{
public interface IVaultFactory
{
IComponentHost? Create(string name,
string password,
VaultConfiguration configuration);
}
}
+13
View File
@@ -0,0 +1,13 @@
using System.Security.Cryptography;
namespace Bitvault;
public class KeyDeriver :
IKeyDeriver
{
public byte[] DeriveKey(string password, byte[] salt, int keySize = 32, int iterations = 100000)
{
using Rfc2898DeriveBytes pbkdf2 = new(password, salt, iterations, HashAlgorithmName.SHA256);
return pbkdf2.GetBytes(keySize);
}
}
+18 -3
View File
@@ -1,11 +1,26 @@
using Toolkit.Foundation;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Toolkit.Foundation;
namespace Bitvault;
public class LockViewModel(IServiceProvider provider,
public partial class LockViewModel(IServiceProvider provider,
IServiceFactory factory,
IMediator mediator,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer) :
ObservableViewModel(provider, factory, mediator, publisher, subscriber, disposer);
ObservableViewModel(provider, factory, mediator, publisher, subscriber, disposer)
{
[ObservableProperty]
private string? password;
[RelayCommand]
private void Unlock()
{
if (Password is { Length: > 0 })
{
Mediator.Handle<Open<Vault>, bool>(Open.As(new Vault(Password)));
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using Toolkit.Foundation;
namespace Bitvault;
public class OpenVaultHandler(IMediator mediator) :
IHandler<Open<Vault>, bool>
{
public async Task<bool> Handle(Open<Vault> args,
CancellationToken cancellationToken)
{
if (args.Value is Vault vault)
{
if (vault.Password is { Length: > 0 } password)
{
if (await mediator.Handle<Open<VaultStorage>, bool>(Open.As(new VaultStorage("Personal", password)), cancellationToken))
{
return true;
}
}
}
return false;
}
}
+41
View File
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Toolkit.Foundation;
namespace Bitvault;
public class OpenVaultStorageHandler(IHostEnvironment environment,
IDbContextFactory<VaultDbContext> dbContextFactory) : IHandler<Open<VaultStorage>, bool>
{
public async Task<bool> Handle(Open<VaultStorage> args, CancellationToken cancellationToken)
{
if (args.Value is VaultStorage vault)
{
if (vault.Name is { Length: > 0 } name && vault.Password is { Length: > 0 } password)
{
using VaultDbContext context = dbContextFactory.CreateDbContext();
var d = context.Database.GetDbConnection().ConnectionString;
context.Database.SetConnectionString($"Data Source={Path.Combine(environment.ContentRootPath, name)}.vault;Mode=ReadWriteCreate;Password={password}");
bool isOpen = false;
await Task.Run(async () =>
{
try
{
await context.Database.OpenConnectionAsync();
isOpen = true;
}
catch
{
// We are ignoring this exception as it is either a go, or not.
}
}, cancellationToken);
return isOpen;
}
}
return false;
}
}
+19
View File
@@ -0,0 +1,19 @@
using System.Security.Cryptography;
namespace Bitvault;
public class PasswordHasher :
IPasswordHasher
{
private const int SaltSize = 16;
public string HashPassword(string password, int iterations = 10000)
{
using Rfc2898DeriveBytes pbkdf2 = new(password, SaltSize, iterations, HashAlgorithmName.SHA256);
byte[] salt = pbkdf2.Salt;
byte[] hash = pbkdf2.GetBytes(32);
return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";
}
}
+28 -2
View File
@@ -1,3 +1,29 @@
namespace Bitvault;
using System.Diagnostics.CodeAnalysis;
public record Vault(string Name, string Password);
namespace Bitvault;
public record Vault
{
public Vault(string name, string password)
{
Name = name;
Password = password;
}
public Vault(string password)
{
Password = password;
}
public Vault()
{
}
[MaybeNull]
public string Name { get; }
[MaybeNull]
public string? Password { get; }
}
+21
View File
@@ -0,0 +1,21 @@
using Toolkit.Foundation;
namespace Bitvault;
public class VaultCollectionInitializer(IEnumerable<IConfigurationDescriptor<VaultConfiguration>> configurations,
IComponentFactory componentFactory,
IVaultHostCollection vaults) : IInitializer
{
public async Task Initialize()
{
foreach (IConfigurationDescriptor<VaultConfiguration> configuration in configurations)
{
if (componentFactory.Create<IVaultComponent, VaultConfiguration>(configuration.Section, configuration.Value) is IComponentHost host)
{
vaults.Add(host);
await host.StartAsync();
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
namespace Bitvault;
public class VaultConnectionPersistence :
IVaultConnectionPersistence,
IDisposable
{
private string? connection;
public void Dispose()
{
connection = null;
}
public string? Get(string key)
{
return connection;
}
public void Set(string key,
string connection)
{
this.connection = connection;
}
}
-29
View File
@@ -1,29 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public class VaultHandler(IComponentFactory factory) :
IHandler<Create<Vault>, bool>
{
public async Task<bool> Handle(Create<Vault> args,
CancellationToken cancellationToken)
{
if (args.Value is Vault vault)
{
if (factory.Create<IVaultComponent>($"Vault:{vault.Name}", new VaultConfiguration { Name = vault.Name }) is IComponentHost host)
{
if (host.Services.GetRequiredService<IMediator>() is IMediator mediator)
{
if (await mediator.Handle<Create<VaultStorage>, bool>(Create.As(new VaultStorage(vault.Name, vault.Password)), cancellationToken))
{
await host.StartAsync(cancellationToken);
}
}
}
}
return false;
}
}
-26
View File
@@ -1,26 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public class VaultInitializer(IServiceProvider provider,
IProxyService<IPublisher> publisher) : IInitializer
{
public async Task Initialize()
{
if (provider.GetService<IComponentHost>() is IComponentHost vault)
{
if (vault.Services.GetRequiredService<VaultConfiguration>() is VaultConfiguration configuration)
{
if (vault.Services.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<VaultNavigationViewModel>(configuration.Name) is VaultNavigationViewModel viewModel)
{
await publisher.Proxy.Publish(new Create<IMainNavigationViewModel>(viewModel),
nameof(MainViewModel));
}
}
}
}
}
}
+13 -7
View File
@@ -1,21 +1,27 @@
using Toolkit.Foundation;
using Microsoft.Extensions.DependencyInjection;
using Toolkit.Foundation;
namespace Bitvault;
public class VaultNavigationViewModelHandler(IPublisher publisher,
IServiceFactory factory,
IEnumerable<IConfigurationDescriptor<VaultConfiguration>> descriptors) :
IVaultHostCollection vaults) :
INotificationHandler<Enumerate<IMainNavigationViewModel>>
{
public async Task Handle(Enumerate<IMainNavigationViewModel> args,
CancellationToken cancellationToken = default)
{
foreach (IConfigurationDescriptor<VaultConfiguration> descriptor in descriptors)
foreach (IComponentHost vault in vaults)
{
if (factory.Create<VaultNavigationViewModel>(descriptor.Value.Name) is VaultNavigationViewModel viewModel)
if (vault.Services.GetRequiredService<VaultConfiguration>() is VaultConfiguration configuration)
{
await publisher.Publish(new Create<IMainNavigationViewModel>(viewModel),
nameof(MainViewModel), cancellationToken);
if (vault.Services.GetRequiredService<IServiceFactory>() is IServiceFactory factory)
{
if (factory.Create<VaultNavigationViewModel>(configuration.Name) is VaultNavigationViewModel viewModel)
{
await publisher.Publish(new Create<IMainNavigationViewModel>(viewModel),
nameof(MainViewModel), cancellationToken);
}
}
}
}
}
-27
View File
@@ -1,27 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Toolkit.Foundation;
namespace Bitvault;
public class VaultStorageHandler(IHostEnvironment environment,
IDbContextFactory<VaultDbContext> dbContextFactory) :
IHandler<Create<VaultStorage>, bool>
{
public async Task<bool> Handle(Create<VaultStorage> args, CancellationToken cancellationToken)
{
if (args.Value is VaultStorage storage)
{
using VaultDbContext context = dbContextFactory.CreateDbContext();
await Task.Run(async () =>
{
context.Database.SetConnectionString($"Data Source={Path.Combine(environment.ContentRootPath, storage.Name)}.vault;Mode=ReadWriteCreate;Password={storage.Password}");
await context.Database.EnsureCreatedAsync(cancellationToken);
}, cancellationToken);
return true;
}
return false;
}
}
-18
View File
@@ -1,18 +0,0 @@
using Toolkit.Foundation;
namespace Bitvault;
public class VaultsInitializer(IEnumerable<IConfigurationDescriptor<VaultConfiguration>> configurations,
IVaultFactory factory) : IInitializer
{
public async Task Initialize()
{
foreach (IConfigurationDescriptor<VaultConfiguration> configuration in configurations)
{
//if (factory.Create(configuration.Section, configuration.Value) is IComponentHost host)
//{
// await host.StartAsync();
//}
}
}
}