mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
Dependency Injection container added. SecurityService work stated, not finished. Minor UI improvements. BROKEN
This commit is contained in:
parent
21f16b862c
commit
551a44c38d
@ -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;
|
||||
|
||||
@ -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]
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
39
QRBee.Core/Security/PrivateKeyHandlerBase.cs
Normal file
39
QRBee.Core/Security/PrivateKeyHandlerBase.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>()
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
56
QRBee/QRBee.Android/Services/AndroidSecurityService.cs
Normal file
56
QRBee/QRBee.Android/Services/AndroidSecurityService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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">
|
||||
|
||||
@ -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()
|
||||
{
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
23
QRBee/QRBee/Services/ClientPrivateKeyHandler.cs
Normal file
23
QRBee/QRBee/Services/ClientPrivateKeyHandler.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -11,8 +11,8 @@ namespace QRBee.Views
|
||||
{
|
||||
public ClientPage()
|
||||
{
|
||||
BindingContext = App.GetViewModel<ClientPageViewModel>();
|
||||
InitializeComponent();
|
||||
this.BindingContext = new ClientPageViewModel(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,4 @@
|
||||
<views:ClientPage Title="Client"/>
|
||||
<views:MerchantPage Title="Merchant"/>
|
||||
</TabbedPage.Children>
|
||||
|
||||
<!-- <ContentPage Title="Merchant" /> -->
|
||||
</TabbedPage>
|
||||
@ -8,7 +8,7 @@ namespace QRBee.Views
|
||||
{
|
||||
public MerchantPage()
|
||||
{
|
||||
BindingContext = new MerchantPageViewModel();
|
||||
BindingContext = App.GetViewModel<MerchantPageViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ namespace QRBee.Views
|
||||
public RegisterPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.BindingContext = new RegisterViewModel();
|
||||
BindingContext = App.GetViewModel<RegisterViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
QRBeeApi/Services/ServerPrivateKeyHandler.cs
Normal file
21
QRBeeApi/Services/ServerPrivateKeyHandler.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user