Dependency Injection container added. SecurityService work stated, not finished. Minor UI improvements. BROKEN

This commit is contained in:
Andrey Shabarshov 2022-02-16 17:35:00 +00:00
parent 21f16b862c
commit 551a44c38d
24 changed files with 385 additions and 139 deletions

View File

@ -36,7 +36,9 @@
/// Convert ClientToMerchantResponse to string to be used as QR Code source (along with client signature)
/// </summary>
/// <returns> Converted string</returns>
public string AsQRCodeString() => $"{ClientId}|{TimeStampUTC:O}|{MerchantRequest.AsQRCodeString()}";
public string AsQRCodeString() => $"{AsDataForSignature()}|{ClientSignature}";
public string AsDataForSignature() => $"{ClientId}|{TimeStampUTC:O}|{MerchantRequest.AsQRCodeString()}";
/// <summary>
/// Convert from string
@ -56,7 +58,8 @@
{
MerchantRequest = MerchantToClientRequest.FromString(string.Join("|", s.Skip(2))),
ClientId = s[0],
TimeStampUTC = DateTime.ParseExact(s[1], "O", null)
TimeStampUTC = DateTime.ParseExact(s[1], "O", null),
ClientSignature = s[3]
};
return res;

View File

@ -44,7 +44,9 @@ namespace QRBee.Core.Data
/// Convert MerchantToClientRequest to string to be used as QR Code source (along with merchant signature)
/// </summary>
/// <returns>String conversion</returns>
public string AsQRCodeString() => $"{MerchantTransactionId}|{MerchantId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}";
public string AsQRCodeString() => $"{AsDataForSignature()}|{MerchantSignature}";
public string AsDataForSignature() => $"{MerchantId}|{MerchantTransactionId}|{Name}|{Amount.ToString("0.00", CultureInfo.InvariantCulture)}|{TimeStampUTC:O}";
/// <summary>
/// Convert from string
@ -55,9 +57,9 @@ namespace QRBee.Core.Data
public static MerchantToClientRequest FromString(string input)
{
var s = input.Split('|');
if (s.Length != 5)
if (s.Length != 6)
{
throw new ApplicationException("Expected 5 elements");
throw new ApplicationException("Expected 6 elements");
}
var res = new MerchantToClientRequest
@ -66,7 +68,8 @@ namespace QRBee.Core.Data
MerchantTransactionId = s[1],
Name = s[2],
Amount = Convert.ToDecimal(s[3], CultureInfo.InvariantCulture),
TimeStampUTC = DateTime.ParseExact(s[4],"O",null)
TimeStampUTC = DateTime.ParseExact(s[4],"O",null),
MerchantSignature = s[5]
};

View File

@ -17,14 +17,14 @@ namespace QRBee.Core.Security
/// Generate new private key and store it
/// </summary>
/// <param name="subjectName"></param>
/// <returns>Certificate request to be sent to CA</returns>
byte [] GeneratePrivateKey(string? subjectName = null);
/// <returns>Certificate request to be sent to CA in PEM format</returns>
string GeneratePrivateKey(string? subjectName = null);
/// <summary>
/// Re-create certificate request if CA response was not received in time.
/// </summary>
/// <returns></returns>
byte[] CreateCertificateRequest();
/// <returns>Certificate request to be sent to CA in PEM format</returns>
string CreateCertificateRequest();
/// <summary>
/// Attach CA-generated public key certificate to the private key

View File

@ -0,0 +1,39 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace QRBee.Core.Security
{
/// <summary>
/// Private key handler for API server
/// </summary>
public class PrivateKeyHandlerBase : IPrivateKeyHandler
{
protected string CommonName { get; set; }
protected string CertificatePassword { get; set; }
public bool Exists()
{
throw new NotImplementedException();
}
public string GeneratePrivateKey(string? subjectName = null)
{
throw new NotImplementedException();
}
public string CreateCertificateRequest()
{
throw new NotImplementedException();
}
public void AttachCertificate(X509Certificate2 cert)
{
throw new NotImplementedException();
}
public X509Certificate2 LoadPrivateKey()
{
throw new NotImplementedException();
}
}
}

View File

@ -6,11 +6,15 @@ using Android.Runtime;
using Android.OS;
using Android.Support.V4.Content;
using AndroidX.Core.App;
using Microsoft.Extensions.DependencyInjection;
using Plugin.Fingerprint;
using QRBee.Core.Security;
using QRBee.Droid.Services;
using QRBee.Services;
namespace QRBee.Droid
{
[Activity(Label = "QRBee", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
[Activity(Label = "QRBee", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Orientation, ScreenOrientation = ScreenOrientation.Portrait)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
@ -23,7 +27,7 @@ namespace QRBee.Droid
CrossFingerprint.SetCurrentActivityResolver(()=>Xamarin.Essentials.Platform.CurrentActivity);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
LoadApplication(new App(AddServices));
ZXing.Mobile.MobileBarcodeScanner.Initialize(Application);
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.Camera) == (int) Permission.Granted)
@ -37,11 +41,22 @@ namespace QRBee.Droid
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
private static void AddServices(IServiceCollection services)
{
services
.AddSingleton<ISecurityService,AndroidSecurityService>()
.AddSingleton<ILocalSettings, LocalSettings>()
.AddSingleton<IQRScanner, QRScannerService>()
;
}
}
}

View File

@ -59,6 +59,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection">
<Version>6.0.0</Version>
</PackageReference>
<PackageReference Include="Plugin.Fingerprint">
<Version>2.1.4</Version>
</PackageReference>
@ -80,7 +83,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\LocalSettings.cs" />
<Compile Include="Services\QRScannerService.cs" />
<Compile Include="Services\SecurityService.cs" />
<Compile Include="Services\AndroidSecurityService.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launcher_background">#FFFFFF</color>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="colorPrimary">#FFC928</color>
<color name="colorPrimaryDark">#FFC928</color>
<color name="colorAccent">#FFC928</color>
</resources>

View File

@ -0,0 +1,56 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using QRBee.Core.Security;
namespace QRBee.Droid.Services
{
internal class AndroidSecurityService : SecurityServiceBase
{
public AndroidSecurityService(IPrivateKeyHandler privateKeyHandler)
: base(privateKeyHandler)
{
}
/// <inheritdoc/>
public override X509Certificate2 CreateCertificate(string subjectName, byte[] rsaPublicKey)
{
throw new ApplicationException("Client never issues certificates");
}
private const string CertHeader = "-----BEGIN CERTIFICATE-----";
private const string CertFooter = "-----END CERTIFICATE-----";
/// <inheritdoc/>
public override X509Certificate2 Deserialize(string pemData)
{
var start = pemData.IndexOf(CertHeader);
if (start == -1)
throw new ApplicationException("Invalid certificate format");
start = start + CertHeader.Length;
var end = pemData.IndexOf(CertFooter);
if (end == -1)
throw new ApplicationException("Invalid certificate format");
var base64 = pemData.Substring(start, end - start); // contains new lines, but it does not matter
var data = Convert.FromBase64String(base64);
return new X509Certificate2(data);
}
/// <inheritdoc/>
public override string Serialize(X509Certificate2 cert)
{
// https://stackoverflow.com/questions/43928064/export-private-public-keys-from-x509-certificate-to-pem
var builder = new StringBuilder();
builder.AppendLine(CertHeader);
builder.AppendLine(Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine(CertFooter);
return builder.ToString();
}
}
}

View File

@ -1,40 +0,0 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using QRBee.Core.Security;
namespace QRBee.Api.Services
{
internal class SecurityService : SecurityServiceBase
{
public SecurityService(IPrivateKeyHandler privateKeyHandler)
: base(privateKeyHandler)
{
}
/// <inheritdoc/>
public override X509Certificate2 CreateCertificate(string subjectName, byte[] rsaPublicKey)
{
throw new ApplicationException("Client never issues certificates");
}
/// <inheritdoc/>
public override X509Certificate2 Deserialize(string pemData)
{
throw new NotImplementedException();
//return X509Certificate2.CreateFromPem(pemData);
}
/// <inheritdoc/>
public override string Serialize(X509Certificate2 cert)
{
throw new NotImplementedException();
// https://stackoverflow.com/questions/43928064/export-private-public-keys-from-x509-certificate-to-pem
//var pem = PemEncoding.Write("CERTIFICATE", cert.RawData);
//return new string(pem);
}
}
}

View File

@ -7,7 +7,8 @@
-->
<Application.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#2196F3</Color>
<!--2296F3 -->
<Color x:Key="Primary">#FFC928</Color>
<Style TargetType="Button">
<Setter Property="TextColor" Value="White"></Setter>
<Setter Property="VisualStateManager.VisualStateGroups">

View File

@ -1,21 +1,52 @@
using QRBee.Services;
using QRBee.Views;
using System;
using Microsoft.Extensions.DependencyInjection;
using QRBee.Core.Security;
using QRBee.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace QRBee
{
public partial class App : Application
{
public App()
public App(Action<IServiceCollection> addPlatformServices = null)
{
InitializeComponent();
SetupServices(addPlatformServices);
DependencyService.Register<MockDataStore>();
MainPage = new AppShell();
}
protected static IServiceProvider ServiceProvider { get; set; }
void SetupServices(Action<IServiceCollection> addPlatformServices = null)
{
var services = new ServiceCollection();
// Add platform specific services
addPlatformServices?.Invoke(services);
// TODO: Add core services here
services
.AddSingleton<IPrivateKeyHandler, ClientPrivateKeyHandler>()
;
// Add ViewModels
services
.AddTransient<MerchantPageViewModel>()
.AddTransient<ClientPageViewModel>()
.AddTransient<RegisterViewModel>()
.AddTransient<LoginViewModel>()
;
ServiceProvider = services.BuildServiceProvider();
}
public static BaseViewModel GetViewModel<TViewModel>() where TViewModel : BaseViewModel => ServiceProvider.GetService<TViewModel>();
protected override void OnStart()
{

View File

@ -30,8 +30,6 @@
</Shell.Resources>
<TabBar>
<!-- <ShellContent Title="About" Icon="icon_about.png" Route="AboutPage" ContentTemplate="{DataTemplate local:AboutPage}" /> -->
<!-- <ShellContent Title="Browse" Icon="icon_feed.png" ContentTemplate="{DataTemplate local:ItemsPage}" /> -->
<ShellContent Title="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}"/>
</TabBar>

View File

@ -14,6 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Plugin.Fingerprint" Version="2.1.4" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2244" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />

View File

@ -0,0 +1,23 @@
using QRBee.Core.Security;
namespace QRBee.Services
{
/// <summary>
/// Private key handler for Client side
/// </summary>
public class ClientPrivateKeyHandler : PrivateKeyHandlerBase
{
private const string VeryBadAndInsecureCertificatePassword = "RîT=ζgÚrʯю™pudF";
public ClientPrivateKeyHandler(ILocalSettings settings)
{
CertificatePassword = VeryBadAndInsecureCertificatePassword;
var clientSettings = settings.LoadSettings();
CommonName = clientSettings?.ClientId;
}
}
}

View File

@ -1,5 +1,7 @@
using System;
using System.Text;
using QRBee.Core.Data;
using QRBee.Core.Security;
using QRBee.Services;
using QRBee.Views;
using Xamarin.Forms;
@ -8,17 +10,26 @@ namespace QRBee.ViewModels
{
internal class ClientPageViewModel: BaseViewModel
{
public bool _isVisible;
private readonly IQRScanner _scanner;
private readonly ISecurityService _securityService;
public bool _isAcceptDenyButtonVisible;
public bool _isQrVisible;
public bool _isScanButtonVisible;
public string _amount;
private string _qrCode;
private MerchantToClientRequest _merchantToClientRequest;
private readonly ClientPage _clientPage;
public ClientPageViewModel(Views.ClientPage clientPage)
public ClientPageViewModel(IQRScanner scanner, ISecurityService securityService)
{
_scanner = scanner;
_securityService = securityService;
ScanCommand = new Command(OnScanButtonClicked);
GenerateQrCommand = new Command(OnGenerateQrClicked);
_clientPage = clientPage;
AcceptQrCommand = new Command(OnAcceptQrCommand);
DenyQrCommand = new Command(OnDenyQrCommand);
IsScanButtonVisible = true;
}
public Command ScanCommand
@ -26,23 +37,33 @@ namespace QRBee.ViewModels
get;
}
public Command GenerateQrCommand
public Command AcceptQrCommand
{
get;
}
public Command DenyQrCommand
{
get;
}
private async void OnScanButtonClicked(object sender)
{
QrCode = null;
IsQrVisible = false;
IsAcceptDenyButtonVisible = false;
try
{
var scanner = DependencyService.Get<IQRScanner>();
var result = await scanner.ScanQR();
var result = await _scanner.ScanQR();
if (result == null)
return;
_merchantToClientRequest = MerchantToClientRequest.FromString(result);
Amount = $"{_merchantToClientRequest.Amount:N2}";
IsVisible = true;
IsAcceptDenyButtonVisible = true;
IsScanButtonVisible = false;
}
catch (Exception)
{
@ -64,17 +85,45 @@ namespace QRBee.ViewModels
}
}
public bool IsVisible
public bool IsAcceptDenyButtonVisible
{
get => _isVisible;
get => _isAcceptDenyButtonVisible;
set
{
if (value == _isVisible)
if (value == _isAcceptDenyButtonVisible)
{
return;
}
_isVisible = value;
OnPropertyChanged(nameof(IsVisible));
_isAcceptDenyButtonVisible = value;
OnPropertyChanged(nameof(IsAcceptDenyButtonVisible));
}
}
public bool IsQrVisible
{
get => _isQrVisible;
set
{
if (value == _isQrVisible)
{
return;
}
_isQrVisible = value;
OnPropertyChanged(nameof(IsQrVisible));
}
}
public bool IsScanButtonVisible
{
get => _isScanButtonVisible;
set
{
if (value == _isScanButtonVisible)
{
return;
}
_isScanButtonVisible = value;
OnPropertyChanged(nameof(IsScanButtonVisible));
}
}
@ -83,7 +132,6 @@ namespace QRBee.ViewModels
get => _qrCode;
set
{
// _qrCode = $"{Amount}/{Name}";
if (_qrCode == value)
return;
@ -96,12 +144,11 @@ namespace QRBee.ViewModels
/// Reaction on GenerateQR button clicked
/// </summary>
/// <param name="obj"></param>
public async void OnGenerateQrClicked(object obj)
public async void OnAcceptQrCommand(object obj)
{
var answer = await _clientPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No");
if (!answer)
return;
var answer = await Application.Current.MainPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No");
if (!answer) return;
var response = new ClientToMerchantResponse
{
@ -109,12 +156,25 @@ namespace QRBee.ViewModels
ClientId = Guid.NewGuid().ToString("D"),
TimeStampUTC = DateTime.UtcNow,
MerchantRequest = _merchantToClientRequest
};
// TODO Create merchant signature.
// TODO Create client signature.
var clientSignature = _securityService.Sign(Encoding.UTF8.GetBytes(response.AsDataForSignature()));
response.ClientSignature = Convert.ToBase64String(clientSignature);
QrCode = response.AsQRCodeString();
IsQrVisible = true;
IsAcceptDenyButtonVisible = false;
IsScanButtonVisible = true;
}
public void OnDenyQrCommand(object obj)
{
QrCode = null;
IsQrVisible = false;
IsAcceptDenyButtonVisible = false;
IsScanButtonVisible = true;
Amount = "";
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using QRBee.Core.Data;
using QRBee.Core.Security;
using QRBee.Services;
using Xamarin.Forms;
@ -9,6 +11,9 @@ namespace QRBee.ViewModels
{
internal class MerchantPageViewModel : BaseViewModel
{
private readonly IQRScanner _scanner;
private readonly ILocalSettings _settings;
private readonly ISecurityService _securityService;
private bool _isVisible;
private decimal _amount;
private string _qrCode;
@ -16,8 +21,11 @@ namespace QRBee.ViewModels
public Command GenerateQrCommand { get; }
public Command ScanCommand{ get; }
public MerchantPageViewModel()
public MerchantPageViewModel(IQRScanner scanner, ILocalSettings settings, ISecurityService securityService)
{
_scanner = scanner;
_settings = settings;
_securityService = securityService;
ScanCommand = new Command(OnScanButtonClicked);
GenerateQrCommand = new Command(OnGenerateQrClicked);
var localSettings = DependencyService.Resolve<ILocalSettings>();
@ -28,15 +36,13 @@ namespace QRBee.ViewModels
{
try
{
var scanner = DependencyService.Get<IQRScanner>();
var result = await scanner.ScanQR();
var result = await _scanner.ScanQR();
if (result == null)
return;
var client = new HttpClient(GetInsecureHandler());
var localSettings = DependencyService.Resolve<ILocalSettings>();
var service = new Core.Client.Client(localSettings.QRBeeApiUrl, client);
var service = new Core.Client.Client(_settings.QRBeeApiUrl, client);
var paymentRequest = PaymentRequest.FromString(result);
//QrCode = null;
@ -132,7 +138,10 @@ namespace QRBee.ViewModels
Amount = Amount,
TimeStampUTC = DateTime.UtcNow
};
// TODO Create merchant signature.
var merchantSignature = _securityService.Sign(Encoding.UTF8.GetBytes(trans.AsDataForSignature()));
trans.MerchantSignature = Convert.ToBase64String(merchantSignature);
QrCode = trans.AsQRCodeString();
IsVisible = true;
}

View File

@ -12,13 +12,14 @@ namespace QRBee.ViewModels
{
internal class RegisterViewModel: BaseViewModel
{
private readonly ILocalSettings _settings;
private string _password1;
private string _password2;
public RegisterViewModel()
public RegisterViewModel(ILocalSettings localSettings)
{
_settings = localSettings;
RegisterCommand = new Command(OnRegisterClicked);
var localSettings = DependencyService.Resolve<ILocalSettings>();
var settings = localSettings.LoadSettings();
Name = settings.Name;
@ -96,14 +97,13 @@ namespace QRBee.ViewModels
{
//TODO when to dispose the client?
var client = new HttpClient(GetInsecureHandler());
var localSettings = DependencyService.Resolve<ILocalSettings>();
var service = new Core.Client.Client(localSettings.QRBeeApiUrl,client);
var service = new Core.Client.Client(_settings.QRBeeApiUrl,client);
try
{
//TODO Check if ClientId already in LocalSettings. If Yes update data in database
var settings = localSettings.LoadSettings();
var settings = _settings.LoadSettings();
//save local settings
settings.CardHolderName = CardHolderName;
@ -117,7 +117,7 @@ namespace QRBee.ViewModels
settings.Name = Name;
settings.PIN = Pin;
await localSettings.SaveSettings(settings);
await _settings.SaveSettings(settings);
var request = new RegistrationRequest
{
@ -132,9 +132,9 @@ namespace QRBee.ViewModels
var response = await service.RegisterAsync(request);
// Save ClientId to LocalSettings
settings = localSettings.LoadSettings();
settings = _settings.LoadSettings();
settings.ClientId = response.ClientId;
await localSettings.SaveSettings(settings);
await _settings.SaveSettings(settings);
var page = Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
await page.DisplayAlert("Success", "You have been registered successfully", "Ok");

View File

@ -6,11 +6,9 @@
xmlns:common="clr-namespace:ZXing.Common;assembly=zxing.portable"
x:DataType="viewmodels:ClientPageViewModel"
x:Class="QRBee.Views.ClientPage">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<!-- <Label Text="Hello this is a test page." -->
<!-- VerticalOptions="CenterAndExpand" -->
<!-- HorizontalOptions="CenterAndExpand" /> -->
<StackLayout Orientation="Vertical" VerticalOptions="FillAndExpand">
<StackLayout Orientation="Vertical">
<Label VerticalOptions="FillAndExpand" Text="Amount:"/>
@ -21,7 +19,8 @@
BarcodeFormat="QR_CODE"
BarcodeValue="{Binding QrCode}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
VerticalOptions="FillAndExpand"
IsVisible="{Binding IsQrVisible}">
<forms:ZXingBarcodeImageView.BarcodeOptions>
<common:EncodingOptions Width="300" Height="300" />
</forms:ZXingBarcodeImageView.BarcodeOptions>
@ -31,10 +30,10 @@
<StackLayout Orientation="Vertical" VerticalOptions="End" Margin="0,0,0,10">
<StackLayout Orientation="Horizontal">
<Button Text="Accept" HorizontalOptions="FillAndExpand" BackgroundColor="DarkGreen" IsVisible="{Binding IsVisible}" Command="{Binding GenerateQrCommand}"/>
<Button Text="Deny" HorizontalOptions="FillAndExpand" BackgroundColor="DarkRed" IsVisible="{Binding IsVisible}"/>
<Button Text="Accept" HorizontalOptions="FillAndExpand" BackgroundColor="DarkGreen" IsVisible="{Binding IsAcceptDenyButtonVisible}" Command="{Binding AcceptQrCommand}"/>
<Button Text="Deny" HorizontalOptions="FillAndExpand" BackgroundColor="DarkRed" IsVisible="{Binding IsAcceptDenyButtonVisible}" Command="{Binding DenyQrCommand}"/>
</StackLayout>
<Button Text="Scan me" HorizontalOptions="FillAndExpand" BackgroundColor="Aqua" TextColor="Red" Command="{Binding ScanCommand}"/>
<Button Text="Scan merchant QR Code" HorizontalOptions="FillAndExpand" BackgroundColor="Aqua" TextColor="Red" Command="{Binding ScanCommand}" IsVisible="{Binding IsScanButtonVisible}"/>
</StackLayout>
</StackLayout>

View File

@ -11,8 +11,8 @@ namespace QRBee.Views
{
public ClientPage()
{
BindingContext = App.GetViewModel<ClientPageViewModel>();
InitializeComponent();
this.BindingContext = new ClientPageViewModel(this);
}
}
}

View File

@ -8,6 +8,4 @@
<views:ClientPage Title="Client"/>
<views:MerchantPage Title="Merchant"/>
</TabbedPage.Children>
<!-- <ContentPage Title="Merchant" /> -->
</TabbedPage>

View File

@ -8,7 +8,7 @@ namespace QRBee.Views
{
public MerchantPage()
{
BindingContext = new MerchantPageViewModel();
BindingContext = App.GetViewModel<MerchantPageViewModel>();
InitializeComponent();
}
}

View File

@ -15,7 +15,7 @@ namespace QRBee.Views
public RegisterPage()
{
InitializeComponent();
this.BindingContext = new RegisterViewModel();
BindingContext = App.GetViewModel<RegisterViewModel>();
}
}
}

View File

@ -1,5 +1,7 @@
using System.Security.Cryptography;
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using QRBee.Core.Security;
@ -16,46 +18,70 @@ namespace QRBee.Api.Services
/// <inheritdoc/>
public override X509Certificate2 CreateCertificate(string subjectName, byte[] rsaPublicKey)
{
if (!IsValidSubjectName(subjectName))
throw new CryptographicException("Invalid subject name");
// https://stackoverflow.com/questions/60930065/generate-and-sign-certificate-in-different-machines-c-sharp
using var publicKey = RSA.Create();
publicKey.ImportSubjectPublicKeyInfo(rsaPublicKey, out var nBytes);
//TODO: check that nBytes is within allowed range
var request = new CertificateRequest("CN=" + subjectName, publicKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.NonRepudiation, false));
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
// create a new certificate
using var caPrivateKey = PrivateKeyHandler.LoadPrivateKey();
var certificate = request.Create(
caPrivateKey,
DateTimeOffset.UtcNow.AddSeconds(-1), // user can use it now
DateTimeOffset.UtcNow.AddDays(30), // user need to login every 30 days
Guid.NewGuid().ToByteArray());
return certificate;
throw new ApplicationException("Client never issues certificates");
}
/// <summary>
/// Generate Client certificate request (i.e. without KeyCertSign usage extension)
/// </summary>
/// <param name="distinguishedName"></param>
/// <param name="rsa"></param>
/// <returns></returns>
private static CertificateRequest CreateRequest(X500DistinguishedName distinguishedName, RSA rsa)
{
var request = new CertificateRequest(
distinguishedName,
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1
);
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DataEncipherment
| X509KeyUsageFlags.KeyEncipherment
| X509KeyUsageFlags.DigitalSignature,
//| X509KeyUsageFlags.KeyCertSign,
false));
// request.CertificateExtensions.Add(
// new X509EnhancedKeyUsageExtension(
// new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
return request;
}
private const string CertHeader = "-----BEGIN CERTIFICATE-----";
private const string CertFooter = "-----END CERTIFICATE-----";
/// <inheritdoc/>
public override X509Certificate2 Deserialize(string pemData)
{
return X509Certificate2.CreateFromPem(pemData);
var start = pemData.IndexOf(CertHeader);
if (start == -1)
throw new ApplicationException("Invalid certificate format");
start = start + CertHeader.Length;
var end = pemData.IndexOf(CertFooter);
if (end == -1)
throw new ApplicationException("Invalid certificate format");
var base64 = pemData.Substring(start, end - start); // contains new lines, but it does not matter
var data = Convert.FromBase64String(base64);
return new X509Certificate2(data);
}
/// <inheritdoc/>
public override string Serialize(X509Certificate2 cert)
{
// https://stackoverflow.com/questions/43928064/export-private-public-keys-from-x509-certificate-to-pem
var pem = PemEncoding.Write("CERTIFICATE", cert.RawData);
return new string(pem);
var builder = new StringBuilder();
builder.AppendLine(CertHeader);
builder.AppendLine(Convert.ToBase64String(cert.RawData, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine(CertFooter);
return builder.ToString();
}
}

View File

@ -0,0 +1,21 @@
using QRBee.Core.Security;
namespace QRBee.Api.Services
{
/// <summary>
/// Private key handler for API server
/// </summary>
public class ServerPrivateKeyHandler : PrivateKeyHandlerBase
{
private const string CACommonName = "QRBee-CA";
private const string VeryBadAndInsecureCertificatePassword = "U…Š)+œ¶€=ø‘ c¬Í↨ð´áY/ÿ☼æX";
public ServerPrivateKeyHandler()
{
CertificatePassword = VeryBadAndInsecureCertificatePassword;
CommonName = CACommonName;
}
}
}