diff --git a/QRBee.Core/Data/ClientCardData.cs b/QRBee.Core/Data/ClientCardData.cs
index 4ac2dd7..a407e50 100644
--- a/QRBee.Core/Data/ClientCardData.cs
+++ b/QRBee.Core/Data/ClientCardData.cs
@@ -2,49 +2,20 @@
{
public class ClientCardData
{
-
- public string CardNumber
- {
- get;
- set;
- }
-
- public string ExpirationDateMMYY
- {
- get;
- set;
- }
-
- public string ValidFrom
- {
- get;
- set;
- }
-
- public string CardHolderName
- {
- get;
- set;
- }
-
- public string CVC
- {
- get;
- set;
- }
-
- public int? IssueNo
- {
- get;
- set;
- }
+ public string TransactionId { get; set; }
+ public string CardNumber { get; set; }
+ public string ExpirationDateMMYY { get; set; }
+ public string ValidFrom { get; set; }
+ public string CardHolderName { get; set; }
+ public string CVC { get; set; }
+ public int? IssueNo { get; set; }
///
/// Convert ClientCardData to string to be used as a source for encryption.
/// WARNING: this should always be encrypted and never transmitted in clear text form.
///
/// Converted string
- public string AsString() => $"{CardNumber}|{ExpirationDateMMYY}|{ValidFrom}|{CardHolderName}|{CVC}|{IssueNo}";
+ public string AsString() => $"{TransactionId}|{CardNumber}|{ExpirationDateMMYY}|{ValidFrom}|{CardHolderName}|{CVC}|{IssueNo}";
}
}
diff --git a/QRBee.Core/Data/RegistrationResponse.cs b/QRBee.Core/Data/RegistrationResponse.cs
index 945277d..602d479 100644
--- a/QRBee.Core/Data/RegistrationResponse.cs
+++ b/QRBee.Core/Data/RegistrationResponse.cs
@@ -12,7 +12,13 @@ namespace QRBee.Core.Data
set;
}
- public string Certificate
+ public string ClientCertificate
+ {
+ get;
+ set;
+ }
+
+ public string APIServerCertificate
{
get;
set;
diff --git a/QRBee.Core/Security/IPrivateKeyHandler.cs b/QRBee.Core/Security/IPrivateKeyHandler.cs
index 4396bf3..16a447b 100644
--- a/QRBee.Core/Security/IPrivateKeyHandler.cs
+++ b/QRBee.Core/Security/IPrivateKeyHandler.cs
@@ -1,4 +1,5 @@
-using System.Security.Cryptography.X509Certificates;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
namespace QRBee.Core.Security
{
@@ -38,6 +39,12 @@ namespace QRBee.Core.Security
/// self-signed until CA issues a proper certificate
///
///
- X509Certificate2 LoadPrivateKey();
+ RSA LoadPrivateKey();
+
+ ///
+ /// Get public key certificate
+ ///
+ /// Public key certificate
+ X509Certificate2 GetCertificate();
}
}
diff --git a/QRBee.Core/Security/ISecurityService.cs b/QRBee.Core/Security/ISecurityService.cs
index e151c1c..1da7dba 100644
--- a/QRBee.Core/Security/ISecurityService.cs
+++ b/QRBee.Core/Security/ISecurityService.cs
@@ -47,6 +47,11 @@ namespace QRBee.Core.Security
// -------------------------- certificate services --------------------------
+ ///
+ /// API Server certificate
+ ///
+ X509Certificate2 APIServerCertificate { get; set; }
+
///
/// Convert binary block to X509Certificate2.
///
diff --git a/QRBee.Core/Security/SecurityServiceBase.cs b/QRBee.Core/Security/SecurityServiceBase.cs
index c16d2ac..9f8f74b 100644
--- a/QRBee.Core/Security/SecurityServiceBase.cs
+++ b/QRBee.Core/Security/SecurityServiceBase.cs
@@ -39,12 +39,13 @@ namespace QRBee.Core.Security
///
public byte[] Decrypt(byte[] data)
{
- using var myCert = LoadPrivateKey();
- using var rsa = myCert.GetRSAPrivateKey();
+ using var rsa = LoadPrivateKey();
var res = rsa?.Decrypt(data, RSAEncryptionPadding.Pkcs1) ?? throw new CryptographicException("No private key found");
return res;
}
+ public abstract X509Certificate2 APIServerCertificate { get; set; }
+
///
public byte [] Encrypt(byte[] data, X509Certificate2 destCert)
{
@@ -63,8 +64,7 @@ namespace QRBee.Core.Security
///
public byte[] Sign(byte[] data)
{
- using var myCert = LoadPrivateKey();
- using var rsa = myCert.GetRSAPrivateKey();
+ using var rsa = LoadPrivateKey();
var res = rsa?.SignData(data, 0, data.Length, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1) ?? throw new CryptographicException("No private key found");
return res;
}
@@ -86,16 +86,12 @@ namespace QRBee.Core.Security
return serNo;
}
- private X509Certificate2 LoadPrivateKey()
+ private RSA LoadPrivateKey()
{
if (!PrivateKeyHandler.Exists())
PrivateKeyHandler.GeneratePrivateKey(); //TODO: subject name
var pk = PrivateKeyHandler.LoadPrivateKey();
- if (!IsValid(pk) )
- {
- throw new CryptographicException("CA private key is not valid");
- }
return pk;
}
diff --git a/QRBee/QRBee.Android/Services/AndroidPrivateKeyHandler.cs b/QRBee/QRBee.Android/Services/AndroidPrivateKeyHandler.cs
index f54f32e..74ff2bb 100644
--- a/QRBee/QRBee.Android/Services/AndroidPrivateKeyHandler.cs
+++ b/QRBee/QRBee.Android/Services/AndroidPrivateKeyHandler.cs
@@ -81,7 +81,7 @@ namespace QRBee.Droid.Services
///
public ReadableCertificateRequest CreateCertificateRequest(string subjectName)
{
- using var rsa = LoadRsaPrivateKey();
+ using var rsa = LoadPrivateKey();
var request = new ReadableCertificateRequest
{
@@ -96,85 +96,68 @@ namespace QRBee.Droid.Services
return request;
}
- ///
- /// Generate EXPORTABLE certificate
- ///
- ///
- ///
- private X509Certificate2 CreateSelfSignedClientCertificate(string subjectName)
+ /////
+ ///// Generate EXPORTABLE certificate
+ /////
+ /////
+ /////
+ //private X509Certificate2 CreateSelfSignedClientCertificate(string subjectName)
+ //{
+ // // https://stackoverflow.com/questions/42786986/how-to-create-a-valid-self-signed-x509certificate2-programmatically-not-loadin
+
+ // var distinguishedName = new X500DistinguishedName($"CN={subjectName}");
+
+ // using var rsa = RSA.Create(RSABits);
+ // var request = CreateRequest(distinguishedName, rsa);
+
+ // var certificate = request.CreateSelfSigned(
+ // new DateTimeOffset(DateTime.UtcNow.AddDays(-1)),
+ // new DateTimeOffset(DateTime.UtcNow.AddDays(CertificateValidityDays))
+ // );
+
+ // return certificate;
+ //}
+
+ /////
+ ///// Generate CA certificate request (i.e. with KeyCertSign usage extension)
+ /////
+ /////
+ /////
+ /////
+ //private static CertificateRequest CreateRequest(X500DistinguishedName distinguishedName, RSA rsa)
+ //{
+ // //TODO not supported on Android
+ // var request = new CertificateRequest(
+ // distinguishedName,
+ // rsa,
+ // HashAlgorithmName.SHA256,
+ // RSASignaturePadding.Pkcs1
+ // );
+
+ // request.CertificateExtensions.Add(
+ // new X509KeyUsageExtension(
+ // X509KeyUsageFlags.DataEncipherment
+ // | X509KeyUsageFlags.KeyEncipherment
+ // | X509KeyUsageFlags.DigitalSignature,
+ // false));
+
+ // return request;
+ //}
+
+
+ public X509Certificate2 GetCertificate()
{
- // https://stackoverflow.com/questions/42786986/how-to-create-a-valid-self-signed-x509certificate2-programmatically-not-loadin
-
- var distinguishedName = new X500DistinguishedName($"CN={subjectName}");
-
- using var rsa = RSA.Create(RSABits);
- var request = CreateRequest(distinguishedName, rsa);
-
- var certificate = request.CreateSelfSigned(
- new DateTimeOffset(DateTime.UtcNow.AddDays(-1)),
- new DateTimeOffset(DateTime.UtcNow.AddDays(CertificateValidityDays))
- );
-
- return certificate;
- }
-
- ///
- /// Generate CA certificate request (i.e. with KeyCertSign usage extension)
- ///
- ///
- ///
- ///
- private static CertificateRequest CreateRequest(X500DistinguishedName distinguishedName, RSA rsa)
- {
- //TODO not supported on Android
- var request = new CertificateRequest(
- distinguishedName,
- rsa,
- HashAlgorithmName.SHA256,
- RSASignaturePadding.Pkcs1
- );
-
- request.CertificateExtensions.Add(
- new X509KeyUsageExtension(
- X509KeyUsageFlags.DataEncipherment
- | X509KeyUsageFlags.KeyEncipherment
- | X509KeyUsageFlags.DigitalSignature,
- false));
-
- return request;
- }
-
- ///
- public X509Certificate2 LoadPrivateKey()
- {
- if (_certificate != null)
- return _certificate;
-
- // double locking
- lock ( _syncObject )
- {
- if (_certificate != null)
- return _certificate;
-
- if (!Exists())
- throw new CryptographicException("PrivateKey does not exist");
-
- _certificate = new X509Certificate2(PrivateKeyFileName, VeryBadNeverUsePrivateKeyPassword);
- return _certificate;
- }
+
+ var cert = new X509Certificate2(PrivateKeyFileName);
+ return cert;
}
///
public void AttachCertificate(X509Certificate2 cert)
{
- // heavily modified version of:
- // https://stackoverflow.com/questions/18462064/associate-a-private-key-with-the-x509certificate2-class-in-net
- using var rsa = LoadRsaPrivateKey();
+ var bytes = cert.Export(X509ContentType.Cert);
- var newPk = cert.CopyWithPrivateKey(rsa);
-
- var pkcs12data = newPk.Export(X509ContentType.Pfx, VeryBadNeverUsePrivateKeyPassword);
- File.WriteAllBytes(PrivateKeyFileName, pkcs12data);
+ File.WriteAllBytes(PrivateKeyFileName, bytes);
lock ( _syncObject )
{
@@ -184,7 +167,7 @@ namespace QRBee.Droid.Services
}
}
- private RSA LoadRsaPrivateKey()
+ public RSA LoadPrivateKey()
{
var bytes = File.ReadAllBytes(PrivateRsaKeyFileName);
var s = CryptoHelper.DecryptStringAES(bytes, VeryBadNeverUsePrivateKeyPassword);
diff --git a/QRBee/QRBee.Android/Services/AndroidSecurityService.cs b/QRBee/QRBee.Android/Services/AndroidSecurityService.cs
index 83f3ede..af1b040 100644
--- a/QRBee/QRBee.Android/Services/AndroidSecurityService.cs
+++ b/QRBee/QRBee.Android/Services/AndroidSecurityService.cs
@@ -1,12 +1,15 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.IO;
using QRBee.Core.Security;
namespace QRBee.Droid.Services
{
internal class AndroidSecurityService : SecurityServiceBase
{
+ private X509Certificate2 _apiServerCertificate;
+ private string ApiServerCertificateFileName => $"{Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)}/ApiServerCertificate.bin";
public AndroidSecurityService(IPrivateKeyHandler privateKeyHandler)
: base(privateKeyHandler)
@@ -51,6 +54,29 @@ namespace QRBee.Droid.Services
return builder.ToString();
}
+
+ public override X509Certificate2 APIServerCertificate
+ {
+ get
+ {
+ if (_apiServerCertificate != null)
+ {
+ return _apiServerCertificate;
+ }
+
+ if (!File.Exists(ApiServerCertificateFileName))
+ throw new ApplicationException($"File not found: {ApiServerCertificateFileName}");
+ var bytes = File.ReadAllBytes(ApiServerCertificateFileName);
+ _apiServerCertificate = new X509Certificate2(bytes);
+ return _apiServerCertificate;
+ }
+ set
+ {
+ _apiServerCertificate = value;
+ var bytes = _apiServerCertificate.Export(X509ContentType.Cert);
+ File.WriteAllBytes(ApiServerCertificateFileName,bytes);
+ }
+ }
}
}
diff --git a/QRBee/QRBee/Services/ILocalSettings.cs b/QRBee/QRBee/Services/ILocalSettings.cs
index b1ebbcf..c40ebec 100644
--- a/QRBee/QRBee/Services/ILocalSettings.cs
+++ b/QRBee/QRBee/Services/ILocalSettings.cs
@@ -7,7 +7,7 @@ namespace QRBee.Services
{
public class Settings
{
- //TODO add ClientId
+
public string ClientId { get; set; }
public string PIN { get; set; }
public bool IsRegistered => !string.IsNullOrWhiteSpace(ClientId);
@@ -20,7 +20,7 @@ namespace QRBee.Services
public string ExpirationDate { get; set; }
public string CardHolderName { get; set; }
public string CVC { get; set; }
- public string IssueNo { get; set; }
+ public int? IssueNo { get; set; }
public string Password { get; set; }
}
diff --git a/QRBee/QRBee/ViewModels/ClientPageViewModel.cs b/QRBee/QRBee/ViewModels/ClientPageViewModel.cs
index b7e6829..ed6c7ba 100644
--- a/QRBee/QRBee/ViewModels/ClientPageViewModel.cs
+++ b/QRBee/QRBee/ViewModels/ClientPageViewModel.cs
@@ -12,6 +12,7 @@ namespace QRBee.ViewModels
{
private readonly IQRScanner _scanner;
private readonly ISecurityService _securityService;
+ private readonly ILocalSettings _localSettings;
public bool _isAcceptDenyButtonVisible;
public bool _isQrVisible;
public bool _isScanButtonVisible;
@@ -21,10 +22,11 @@ namespace QRBee.ViewModels
private string _qrCode;
private MerchantToClientRequest _merchantToClientRequest;
- public ClientPageViewModel(IQRScanner scanner, ISecurityService securityService)
+ public ClientPageViewModel(IQRScanner scanner, ISecurityService securityService, ILocalSettings localSettings)
{
_scanner = scanner;
_securityService = securityService;
+ _localSettings = localSettings;
ScanCommand = new Command(OnScanButtonClicked);
AcceptQrCommand = new Command(OnAcceptQrCommand);
DenyQrCommand = new Command(OnDenyQrCommand);
@@ -149,16 +151,14 @@ namespace QRBee.ViewModels
var answer = await Application.Current.MainPage.DisplayAlert("Confirmation", "Would you like to accept the offer?", "Yes", "No");
if (!answer) return;
-
+ var settings = _localSettings.LoadSettings();
var response = new ClientToMerchantResponse
{
- //TODO get client id from database
- ClientId = Guid.NewGuid().ToString("D"),
+ ClientId = settings.ClientId,
TimeStampUTC = DateTime.UtcNow,
- MerchantRequest = _merchantToClientRequest
-
+ MerchantRequest = _merchantToClientRequest,
+ EncryptedClientCardData = EncryptCardData(settings, _merchantToClientRequest.MerchantTransactionId)
};
- // TODO Create client signature.
var clientSignature = _securityService.Sign(Encoding.UTF8.GetBytes(response.AsDataForSignature()));
response.ClientSignature = Convert.ToBase64String(clientSignature);
@@ -168,13 +168,30 @@ namespace QRBee.ViewModels
IsScanButtonVisible = true;
}
+ private string EncryptCardData(Settings settings, string transactionId)
+ {
+ var clientCardData = new ClientCardData
+ {
+ TransactionId = transactionId,
+ CardNumber = settings.CardNumber,
+ ExpirationDateMMYY = settings.ExpirationDate,
+ ValidFrom = settings.ValidFrom,
+ CardHolderName = settings.CardHolderName,
+ CVC = settings.CVC,
+ IssueNo = settings.IssueNo
+ };
+
+ var bytes = _securityService.Encrypt(Encoding.UTF8.GetBytes(clientCardData.AsString()),_securityService.APIServerCertificate);
+ return Convert.ToBase64String(bytes);
+ }
+
public void OnDenyQrCommand(object obj)
{
- QrCode = null;
- IsQrVisible = false;
+ QrCode = null;
+ IsQrVisible = false;
IsAcceptDenyButtonVisible = false;
- IsScanButtonVisible = true;
- Amount = "";
+ IsScanButtonVisible = true;
+ Amount = "";
}
}
diff --git a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
index 6ec73fa..fcaba51 100644
--- a/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
+++ b/QRBee/QRBee/ViewModels/MerchantPageViewModel.cs
@@ -131,8 +131,7 @@ namespace QRBee.ViewModels
{
var trans = new MerchantToClientRequest
{
- //TODO get merchant id from database
- MerchantId = Guid.NewGuid().ToString("D"),
+ MerchantId = _settings.LoadSettings().ClientId,
MerchantTransactionId = Guid.NewGuid().ToString("D"),
Name = Name,
Amount = Amount,
diff --git a/QRBee/QRBee/ViewModels/RegisterViewModel.cs b/QRBee/QRBee/ViewModels/RegisterViewModel.cs
index 34ad1eb..e73afd5 100644
--- a/QRBee/QRBee/ViewModels/RegisterViewModel.cs
+++ b/QRBee/QRBee/ViewModels/RegisterViewModel.cs
@@ -50,7 +50,7 @@ namespace QRBee.ViewModels
public string ExpirationDate { get; set; }
public string CardHolderName { get; set; }
public string CVC { get; set; }
- public string IssueNo { get; set; }
+ public int? IssueNo { get; set; }
public Color Password1Color { get; set; }
public Color Password2Color { get; set;}
@@ -125,7 +125,7 @@ namespace QRBee.ViewModels
await _settings.SaveSettings(settings);
- if (!_privateKeyHandler.Exists())
+ //if (!_privateKeyHandler.Exists())
{
_privateKeyHandler.GeneratePrivateKey(settings.Name);
}
@@ -149,6 +149,7 @@ namespace QRBee.ViewModels
try
{
+ //TODO Register if not, otherwise update
// FOR TESTING PURPOSES
//!settings.IsRegistered
if (true)
@@ -158,10 +159,14 @@ namespace QRBee.ViewModels
// Save ClientId to LocalSettings
settings = _settings.LoadSettings();
settings.ClientId = response.ClientId;
+
+ // Save server public key certificate
+ _securityService.APIServerCertificate = _securityService.Deserialize(response.APIServerCertificate);
+
await _settings.SaveSettings(settings);
// Attach certificate to privateKey (replace self-sighed with server issued certificate)
- _privateKeyHandler.AttachCertificate(_securityService.Deserialize(response.Certificate));
+ _privateKeyHandler.AttachCertificate(_securityService.Deserialize(response.ClientCertificate));
var page = Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault();
await page.DisplayAlert("Success", "You have been registered successfully", "Ok");
diff --git a/QRBeeApi/Services/QRBeeAPIService.cs b/QRBeeApi/Services/QRBeeAPIService.cs
index 8355289..57814de 100644
--- a/QRBeeApi/Services/QRBeeAPIService.cs
+++ b/QRBeeApi/Services/QRBeeAPIService.cs
@@ -62,7 +62,8 @@ namespace QRBee.Api.Services
return new RegistrationResponse
{
ClientId = clientId,
- Certificate = _securityService.Serialize(clientCertificate)
+ ClientCertificate = _securityService.Serialize(clientCertificate),
+ APIServerCertificate = _securityService.Serialize(_securityService.APIServerCertificate)
};
}
diff --git a/QRBeeApi/Services/SecurityService.cs b/QRBeeApi/Services/SecurityService.cs
index 72f2250..c915f64 100644
--- a/QRBeeApi/Services/SecurityService.cs
+++ b/QRBeeApi/Services/SecurityService.cs
@@ -29,7 +29,10 @@ namespace QRBee.Api.Services
var req = CreateClientCertRequest(distinguishedName, rsa);
var pk = PrivateKeyHandler.LoadPrivateKey();
- var clientCert = req.Create(pk,
+ var cert = PrivateKeyHandler.GetCertificate();
+ var newCert = cert.CopyWithPrivateKey(pk);
+
+ var clientCert = req.Create(newCert,
DateTimeOffset.UtcNow - TimeSpan.FromDays(1),
DateTimeOffset.UtcNow + TimeSpan.FromDays(CertificateValidityPeriodDays),
Guid.NewGuid()
@@ -92,6 +95,13 @@ namespace QRBee.Api.Services
return builder.ToString();
}
+
+ ///
+ public override X509Certificate2 APIServerCertificate
+ {
+ get => PrivateKeyHandler.GetCertificate();
+ set => throw new ApplicationException("Do not call this");
+ }
}
}
diff --git a/QRBeeApi/Services/ServerPrivateKeyHandler.cs b/QRBeeApi/Services/ServerPrivateKeyHandler.cs
index ddf59b4..2857bd4 100644
--- a/QRBeeApi/Services/ServerPrivateKeyHandler.cs
+++ b/QRBeeApi/Services/ServerPrivateKeyHandler.cs
@@ -55,7 +55,7 @@ namespace QRBee.Api.Services
public ReadableCertificateRequest CreateCertificateRequest(string subjectName)
{
//TODO in fact server should create certificate request in standard format if we ever want to get externally sighed certificate.
- var pk = LoadPrivateKey();
+ var pk = Load();
var rsa = pk.GetRSAPrivateKey();
if (rsa == null)
@@ -172,7 +172,7 @@ namespace QRBee.Api.Services
///
- public X509Certificate2 LoadPrivateKey()
+ private X509Certificate2 Load()
{
if (_certificate != null)
return _certificate;
@@ -191,6 +191,20 @@ namespace QRBee.Api.Services
}
}
+ public RSA LoadPrivateKey()
+ {
+ var pk = Load();
+ return pk.GetRSAPrivateKey()?? throw new ApplicationException("Private key not found");
+ }
+
+ public X509Certificate2 GetCertificate()
+ {
+ var pk = Load();
+ var bytes = pk.Export(X509ContentType.Cert);
+ var cert = new X509Certificate2(bytes);
+ return cert;
+ }
+
///
public void AttachCertificate(X509Certificate2 cert)
{