mirror of
https://github.com/NecroticBamboo/QRBee.git
synced 2025-12-21 12:11:53 +00:00
Certificate information can now be stored in database. Second attempt at key generation under Android still no luck.
This commit is contained in:
parent
6da55bc4a5
commit
e07b45d43f
@ -6,6 +6,14 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -18,13 +18,13 @@ namespace QRBee.Core.Security
|
||||
/// </summary>
|
||||
/// <param name="subjectName"></param>
|
||||
/// <returns>Certificate request to be sent to CA in PEM format</returns>
|
||||
ReadableCertificateRequest GeneratePrivateKey(string? subjectName = null);
|
||||
ReadableCertificateRequest GeneratePrivateKey(string subjectName = null);
|
||||
|
||||
/// <summary>
|
||||
/// Re-create certificate request if CA response was not received in time.
|
||||
/// </summary>
|
||||
/// <returns>Certificate request to be sent to CA in PEM format</returns>
|
||||
ReadableCertificateRequest CreateCertificateRequest();
|
||||
ReadableCertificateRequest CreateCertificateRequest(string subjectName);
|
||||
|
||||
/// <summary>
|
||||
/// Attach CA-generated public key certificate to the private key
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
|
||||
<BundleAssemblies>false</BundleAssemblies>
|
||||
<MandroidI18n />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
||||
@ -13,49 +13,55 @@ namespace QRBee.Droid.Services
|
||||
/// </summary>
|
||||
public class AndroidPrivateKeyHandler : IPrivateKeyHandler
|
||||
{
|
||||
private X509Certificate2? _certificate;
|
||||
private X509Certificate2 _certificate;
|
||||
private readonly object _syncObject = new object();
|
||||
|
||||
private const string FileName = "private_key.p12";
|
||||
protected string CommonName { get; set; }
|
||||
private const string RawRsaKeyFileName = "rsa.key";
|
||||
private const string SignedCertificateFileName = "private_key.p12";
|
||||
private const string VeryBadNeverUsePrivateKeyPassword = "’³¶¾]Ô<N◄¾♪¢ :6TyŽ÷ç♦Mô¶–²ùPÎJj";
|
||||
private const int EncryptionIterationCount = 534;
|
||||
|
||||
private const int RSABits = 2048;
|
||||
private const int CertificateValidityDays = 3650;
|
||||
|
||||
protected string CertificatePassword { get; set; }
|
||||
|
||||
private string PrivateKeyFileName => $"{System.Environment.SpecialFolder.LocalApplicationData}/{FileName}";
|
||||
private string PrivateKeyFileName => $"{System.Environment.SpecialFolder.LocalApplicationData}/{SignedCertificateFileName}";
|
||||
private string PrivateRsaKeyFileName => $"{System.Environment.SpecialFolder.LocalApplicationData}/{RawRsaKeyFileName}";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Exists()
|
||||
=> File.Exists(PrivateKeyFileName);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadableCertificateRequest GeneratePrivateKey(string? subjectName)
|
||||
public ReadableCertificateRequest GeneratePrivateKey(string subjectName)
|
||||
{
|
||||
// locking used to make sure that only one thread generating a private key
|
||||
lock (_syncObject)
|
||||
{
|
||||
var pk = CreateSelfSignedClientCertificate(subjectName ?? CommonName);
|
||||
var pkcs12data = pk.Export(X509ContentType.Pfx, CertificatePassword);
|
||||
File.WriteAllBytes(PrivateKeyFileName, pkcs12data);
|
||||
if ( File.Exists(PrivateRsaKeyFileName) )
|
||||
File.Delete(PrivateRsaKeyFileName);
|
||||
|
||||
_certificate?.Dispose();
|
||||
_certificate = new X509Certificate2(pkcs12data, CertificatePassword);
|
||||
using var rsa = RSA.Create(RSABits);
|
||||
var bytes = rsa.ExportEncryptedPkcs8PrivateKey(VeryBadNeverUsePrivateKeyPassword, new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, EncryptionIterationCount));
|
||||
File.WriteAllBytes(PrivateRsaKeyFileName, bytes);
|
||||
}
|
||||
|
||||
return CreateCertificateRequest();
|
||||
return CreateCertificateRequest(subjectName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadableCertificateRequest CreateCertificateRequest()
|
||||
public ReadableCertificateRequest CreateCertificateRequest(string subjectName)
|
||||
{
|
||||
var pk = LoadPrivateKey();
|
||||
var rsa = pk.GetRSAPublicKey();
|
||||
if (File.Exists(PrivateRsaKeyFileName))
|
||||
throw new ApplicationException("Private key does not exist");
|
||||
|
||||
var bytes = File.ReadAllBytes(PrivateRsaKeyFileName);
|
||||
using var rsa = RSA.Create(RSABits);
|
||||
rsa.ImportEncryptedPkcs8PrivateKey(VeryBadNeverUsePrivateKeyPassword, bytes, out _);
|
||||
|
||||
var request = new ReadableCertificateRequest
|
||||
{
|
||||
RsaPublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey()),
|
||||
SubjectName = pk.SubjectName.Name
|
||||
SubjectName = subjectName
|
||||
};
|
||||
var data = Encoding.UTF8.GetBytes(request.AsDataForSignature());
|
||||
|
||||
@ -128,7 +134,7 @@ namespace QRBee.Droid.Services
|
||||
if (!Exists())
|
||||
throw new CryptographicException("PrivateKey does not exist");
|
||||
|
||||
_certificate = new X509Certificate2(PrivateKeyFileName, CertificatePassword);
|
||||
_certificate = new X509Certificate2(PrivateKeyFileName, VeryBadNeverUsePrivateKeyPassword);
|
||||
return _certificate;
|
||||
}
|
||||
}
|
||||
@ -140,14 +146,14 @@ namespace QRBee.Droid.Services
|
||||
// https://stackoverflow.com/questions/18462064/associate-a-private-key-with-the-x509certificate2-class-in-net
|
||||
|
||||
// we can't use LoadPrivateKey here as it creating non-exportable key
|
||||
var pk = new X509Certificate2(PrivateKeyFileName, CertificatePassword, X509KeyStorageFlags.Exportable);
|
||||
var pk = new X509Certificate2(PrivateKeyFileName, VeryBadNeverUsePrivateKeyPassword, X509KeyStorageFlags.Exportable);
|
||||
using var rsa = pk.GetRSAPrivateKey();
|
||||
if (rsa == null)
|
||||
throw new CryptographicException("Can't get PrivateKey");
|
||||
|
||||
var newPk = cert.CopyWithPrivateKey(rsa);
|
||||
|
||||
var pkcs12data = newPk.Export(X509ContentType.Pfx, CertificatePassword);
|
||||
var pkcs12data = newPk.Export(X509ContentType.Pfx, VeryBadNeverUsePrivateKeyPassword);
|
||||
File.WriteAllBytes(PrivateKeyFileName, pkcs12data);
|
||||
|
||||
lock ( _syncObject )
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
|
||||
<MtouchLink>None</MtouchLink>
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
|
||||
<DebugType>none</DebugType>
|
||||
@ -133,10 +134,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Plugin.Fingerprint">
|
||||
<Version>2.1.4</Version>
|
||||
<Version>2.1.5</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2244" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile">
|
||||
<Version>2.4.1</Version>
|
||||
</PackageReference>
|
||||
|
||||
@ -7,17 +7,19 @@
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</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" />
|
||||
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2337" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
|
||||
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -133,7 +133,7 @@ namespace QRBee.ViewModels
|
||||
DateOfBirth = DateOfBirth.ToString("yyyy-MM-dd"),
|
||||
Email = Email,
|
||||
Name = Name,
|
||||
CertificateRequest = _privateKeyHandler.CreateCertificateRequest(),
|
||||
CertificateRequest = _privateKeyHandler.CreateCertificateRequest(Email),
|
||||
RegisterAsMerchant = false
|
||||
};
|
||||
|
||||
|
||||
@ -7,6 +7,14 @@
|
||||
<UserSecretsId>3b7dc7f1-0b82-4746-b99b-73c43c8826e0</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="6.1.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.14.1" />
|
||||
|
||||
14
QRBeeApi/Services/Database/CertificateInfo.cs
Normal file
14
QRBeeApi/Services/Database/CertificateInfo.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace QRBee.Api.Services.Database
|
||||
{
|
||||
public class CertificateInfo
|
||||
{
|
||||
|
||||
[BsonId] public string? Id { get; set; }
|
||||
public string? ClientId { get; set; }
|
||||
public string? Certificate { get; set; }
|
||||
public DateTime ServerTimeStamp { get; set; }
|
||||
}
|
||||
}
|
||||
@ -30,5 +30,19 @@
|
||||
/// </summary>
|
||||
/// <param name="info">Information to be inserted</param>
|
||||
Task PutTransactionInfo(TransactionInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts CertificateInfo into database
|
||||
/// </summary>
|
||||
/// <param name="info">Information to be inserted</param>
|
||||
/// <returns></returns>
|
||||
Task InsertCertificate(CertificateInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve certificate information from database
|
||||
/// </summary>
|
||||
/// <param name="id">Identifier by which certificate information will be retrieved</param>
|
||||
/// <returns>Certificate information</returns>
|
||||
Task<CertificateInfo> GetCertificateInfo(string id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
using QRBee.Api.Controllers;
|
||||
|
||||
@ -100,5 +101,50 @@ namespace QRBee.Api.Services.Database
|
||||
|
||||
return cursor.Current.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task InsertCertificate(CertificateInfo info)
|
||||
{
|
||||
var collection = _database.GetCollection<CertificateInfo>("Certificates");
|
||||
|
||||
if (info.Id == null)
|
||||
{
|
||||
throw new ApplicationException("Info Id is null.");
|
||||
}
|
||||
|
||||
var certificate = await TryGetCertificateInfo(info.Id);
|
||||
|
||||
if (certificate == null)
|
||||
{
|
||||
await collection.InsertOneAsync(info);
|
||||
_logger.LogInformation($"Inserted new certificate with ID: {info.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Found certificate with ID: {info.Id}");
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find if the Certificate already exists in the database
|
||||
/// </summary>
|
||||
/// <param name="id">parameter by which to find CertificateInfo</param>
|
||||
/// <returns>null if certificate doesn't exist or CertificateInfo</returns>
|
||||
private async Task<CertificateInfo?> TryGetCertificateInfo(string id)
|
||||
{
|
||||
var collection = _database.GetCollection<CertificateInfo>("Transactions");
|
||||
using var cursor = await collection.FindAsync($"{{ Id: \"{id}\" }}");
|
||||
if (!await cursor.MoveNextAsync())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return cursor.Current.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<CertificateInfo> GetCertificateInfo(string id)
|
||||
{
|
||||
var certificate = await TryGetCertificateInfo(id);
|
||||
return certificate ?? throw new ApplicationException($"Certificate with Id: {id} not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QRBee.Core;
|
||||
using QRBee.Core.Data;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using QRBee.Api.Services.Database;
|
||||
@ -49,7 +50,9 @@ namespace QRBee.Api.Services
|
||||
|
||||
var clientId = await _storage.PutUserInfo(info);
|
||||
var clientCertificate = _securityService.CreateCertificate(clientId,System.Convert.FromBase64String(request.CertificateRequest.RsaPublicKey));
|
||||
//TODO save certificate to certificate mongoDB collection
|
||||
|
||||
var convertedClientCertificate = Convert(clientCertificate, clientId);
|
||||
await _storage.InsertCertificate(convertedClientCertificate);
|
||||
|
||||
return new RegistrationResponse
|
||||
{
|
||||
@ -124,5 +127,17 @@ namespace QRBee.Api.Services
|
||||
return new TransactionInfo(request, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
private CertificateInfo Convert(X509Certificate2 certificate, string clientId)
|
||||
{
|
||||
var convertedCertificate = _securityService.Serialize(certificate);
|
||||
return new CertificateInfo
|
||||
{
|
||||
Id = certificate.SerialNumber,
|
||||
ClientId = clientId,
|
||||
Certificate = convertedCertificate,
|
||||
ServerTimeStamp = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,11 +14,10 @@ namespace QRBee.Api.Services
|
||||
private readonly object _syncObject = new object();
|
||||
|
||||
private const string FileName = "private_key.p12";
|
||||
protected string CommonName { get; set; }
|
||||
private const int RSABits = 2048;
|
||||
private const int CertificateValidityDays = 3650;
|
||||
|
||||
protected string CertificatePassword { get; set; }
|
||||
|
||||
private const string VeryBadNeverUseCertificatePassword = "+ñèbòFëc׎ßRúß¿ãçPN";
|
||||
|
||||
private string PrivateKeyFileName => $"{System.Environment.SpecialFolder.LocalApplicationData}/{FileName}";
|
||||
|
||||
@ -27,33 +26,38 @@ namespace QRBee.Api.Services
|
||||
=> File.Exists(PrivateKeyFileName);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadableCertificateRequest GeneratePrivateKey(string? subjectName)
|
||||
public ReadableCertificateRequest GeneratePrivateKey(string subjectName)
|
||||
{
|
||||
// locking used to make sure that only one thread generating a private key
|
||||
lock (_syncObject)
|
||||
{
|
||||
var pk = CreateSelfSignedServerCertificate(subjectName ?? CommonName);
|
||||
var pkcs12data = pk.Export(X509ContentType.Pfx, CertificatePassword);
|
||||
var pk = CreateSelfSignedServerCertificate(subjectName);
|
||||
var pkcs12data = pk.Export(X509ContentType.Pfx, VeryBadNeverUseCertificatePassword);
|
||||
File.WriteAllBytes(PrivateKeyFileName, pkcs12data);
|
||||
|
||||
_certificate?.Dispose();
|
||||
_certificate = new X509Certificate2(pkcs12data, CertificatePassword);
|
||||
_certificate = new X509Certificate2(pkcs12data, VeryBadNeverUseCertificatePassword);
|
||||
}
|
||||
|
||||
return CreateCertificateRequest();
|
||||
return CreateCertificateRequest(subjectName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ReadableCertificateRequest CreateCertificateRequest()
|
||||
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 rsa = pk.GetRSAPublicKey();
|
||||
|
||||
if (rsa == null)
|
||||
{
|
||||
throw new ApplicationException("Object missing public key.");
|
||||
}
|
||||
|
||||
var request = new ReadableCertificateRequest
|
||||
{
|
||||
RsaPublicKey = Convert.ToBase64String(rsa.ExportRSAPublicKey()),
|
||||
SubjectName = pk.SubjectName.Name
|
||||
SubjectName = subjectName
|
||||
};
|
||||
var data = Encoding.UTF8.GetBytes(request.AsDataForSignature());
|
||||
|
||||
@ -147,7 +151,7 @@ namespace QRBee.Api.Services
|
||||
if (!Exists())
|
||||
throw new CryptographicException("PrivateKey does not exist");
|
||||
|
||||
_certificate = new X509Certificate2(PrivateKeyFileName, CertificatePassword);
|
||||
_certificate = new X509Certificate2(PrivateKeyFileName, VeryBadNeverUseCertificatePassword);
|
||||
return _certificate;
|
||||
}
|
||||
}
|
||||
@ -159,14 +163,14 @@ namespace QRBee.Api.Services
|
||||
// https://stackoverflow.com/questions/18462064/associate-a-private-key-with-the-x509certificate2-class-in-net
|
||||
|
||||
// we can't use LoadPrivateKey here as it creating non-exportable key
|
||||
var pk = new X509Certificate2(PrivateKeyFileName, CertificatePassword, X509KeyStorageFlags.Exportable);
|
||||
var pk = new X509Certificate2(PrivateKeyFileName, VeryBadNeverUseCertificatePassword, X509KeyStorageFlags.Exportable);
|
||||
using var rsa = pk.GetRSAPrivateKey();
|
||||
if (rsa == null)
|
||||
throw new CryptographicException("Can't get PrivateKey");
|
||||
|
||||
var newPk = cert.CopyWithPrivateKey(rsa);
|
||||
|
||||
var pkcs12data = newPk.Export(X509ContentType.Pfx, CertificatePassword);
|
||||
var pkcs12data = newPk.Export(X509ContentType.Pfx, VeryBadNeverUseCertificatePassword);
|
||||
File.WriteAllBytes(PrivateKeyFileName, pkcs12data);
|
||||
|
||||
lock ( _syncObject )
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user