Skip to content

Commit

Permalink
BREAKING CHANGE: improve the certificate handler, use user certificat…
Browse files Browse the repository at this point in the history
…e store
  • Loading branch information
Pejman Nikram committed Jan 5, 2024
1 parent 85ba654 commit a481633
Show file tree
Hide file tree
Showing 20 changed files with 228 additions and 102 deletions.
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@ Lazvard Message is an AMQP server simulator that is **unofficially** compatible

## Setup

Download the last version of the Lazvard from release page or clone and build the project, the application will create the default config file if couldn't find it on the first run.

The config file is in TOML format. Before running Lazvard, you need to define all the queues, topics, and subscriptions in the config file. The AMQP server require a valid and trusted X.509 certificate (PFX - PKCS #12). In Windows and macOS, the application can create and trust a certificate (using a copy of the Microsoft .NET CLI - certificate manager). However, for Linux, you will need to manually set the certificate as trusted.
You need to have .NET 8 installed on your operating system to run this project. You can download it from [here](https://dotnet.microsoft.com/en-us/download).

It's important to note that Lazvard is stateless. Therefore, once you close it, all messages and information will be lost.

Since this application is not signed, you may encounter issues running it. The simplest way to run the project is to clone and build it on your operating system:
`
git clone https://github.com/PejmanNik/lazvard-message.git
cd lazvard-message
dotnet run --project ./src/Lazvard.Message.Cli
`

Alternatively, you can download the latest version of Lazvard from the release page. At least on Windows, you will need to manually trust the application in Microsoft's SmartScreen upon first run.

`
wget https://github.com/PejmanNik/lazvard-message/releases/download/v0.1.0/win-x64.zip
`

The application will create a default config file if it's not found on the first run. This config file is in TOML format. Before running Lazvard, you need to define all the queues, topics, and subscriptions in the config file. The AMQP server requires a valid and trusted X.509 certificate (PFX - PKCS #12). On Windows and macOS, the application can create and trust certificates (powered by dotnet dev-certs). However, for Linux, you will need to manually set the certificate as trusted.

It's important to note that Lazvard is stateless, meaning that once you close it, all messages and information will be lost.


## Different behavior
Expand Down
2 changes: 1 addition & 1 deletion src/Lazvard.Message.Amqp.Server/BrokerConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public TopicConfig(string name, IEnumerable<TopicSubscriptionConfig> subscriptio
public class BrokerConfig
{
public int Port { get; set; } = AmqpConstants.DefaultSecurePort;
public string IP { get; set; } = "127.0.0.1";
public string IP { get; set; } = "localhost";
public uint MaxFrameSize { get; set; } = 64 * 1024;
public uint ConnectionIdleTimeOut { get; set; } = 4 * 60 * 1000;
public uint MaxMessageSize { get; set; } = 64 * 1024 * 1024;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
<Authors>Pejman Nikram</Authors>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
<Version>1.1.0</Version>
<Version>1.2.0</Version>
</PropertyGroup>

<ItemGroup>
<None Remove="Program - Copy.cs;" />
</ItemGroup>

<ItemGroup>
<None Include="..\..\README.md">
<Pack>True</Pack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal enum CertificateKeyExportFormat
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

#nullable enable

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal abstract class CertificateManager
{
internal const int CurrentAspNetCoreCertificateVersion = 2;

// OID used for HTTPS certs
internal const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1";
internal const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate";
internal static string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate";

private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1";
private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication";
Expand Down Expand Up @@ -75,7 +78,7 @@ public IList<X509Certificate2> ListCertificates(
{
using var store = new X509Store(storeName, location);
store.Open(OpenFlags.ReadOnly);
PopulateCertificatesFromStore(store, certificates);
PopulateCertificatesFromStore(store, certificates, requireExportable);
IEnumerable<X509Certificate2> matchingCertificates = certificates;
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetHttpsOid));
Expand All @@ -93,7 +96,7 @@ public IList<X509Certificate2> ListCertificates(
var now = DateTimeOffset.Now;
var validCertificates = matchingCertificates
.Where(c => IsValidCertificate(c, now, requireExportable))
.OrderByDescending(c => GetCertificateVersion(c))
.OrderByDescending(GetCertificateVersion)
.ToArray();

if (Log.IsEnabled())
Expand Down Expand Up @@ -158,7 +161,7 @@ bool IsValidCertificate(X509Certificate2 certificate, DateTimeOffset currentDate
GetCertificateVersion(certificate) >= AspNetHttpsCertificateVersion;
}

protected virtual void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates)
protected virtual void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates, bool requireExportable)
{
certificates.AddRange(store.Certificates.OfType<X509Certificate2>());
}
Expand Down Expand Up @@ -546,6 +549,14 @@ internal static void ExportCertificate(X509Certificate2 certificate, string path
try
{
Log.WriteCertificateToDisk(path);

// Create a temp file with the correct Unix file mode before moving it to the expected path.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var tempFilename = Path.GetTempFileName();
File.Move(tempFilename, path, overwrite: true);
}

File.WriteAllBytes(path, bytes);
}
catch (Exception ex) when (Log.IsEnabled())
Expand All @@ -566,6 +577,14 @@ internal static void ExportCertificate(X509Certificate2 certificate, string path
{
var keyPath = Path.ChangeExtension(path, ".key");
Log.WritePemKeyToDisk(keyPath);

// Create a temp file with the correct Unix file mode before moving it to the expected path.
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var tempFilename = Path.GetTempFileName();
File.Move(tempFilename, keyPath, overwrite: true);
}

File.WriteAllBytes(keyPath, pemEnvelope);
}
catch (Exception ex) when (Log.IsEnabled())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal enum CertificatePurpose
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal enum EnsureCertificateResult
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal enum ImportCertificateResult
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal sealed class MacOSCertificateManager : CertificateManager
{
Expand Down Expand Up @@ -372,25 +373,28 @@ protected override IList<X509Certificate2> GetCertificatesToRemove(StoreName sto
return ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: false);
}

protected override void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates)
protected override void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates, bool requireExportable)
{
if (store.Name! == StoreName.My.ToString() && store.Location == StoreLocation.CurrentUser && Directory.Exists(MacOSUserHttpsCertificateLocation))
{
var certsFromDisk = GetCertsFromDisk();

var certsFromStore = new List<X509Certificate2>();
base.PopulateCertificatesFromStore(store, certsFromStore);
base.PopulateCertificatesFromStore(store, certsFromStore, requireExportable);

// Certs created by pre-.NET 7.
var onlyOnKeychain = certsFromStore.Except(certsFromDisk, ThumbprintComparer.Instance);

// Certs created (or "upgraded") by .NET 7+.
// .NET 7+ installs the certificate on disk as well as on the user keychain (for backwards
// compatibility with pre-.NET 7).
// Note that the actual certs we populate need to be the ones from the store location, and
// not the version from disk, since we may do other operations with these certs later (such
// as exporting) which would fail with crypto errors otherwise.
var onDiskAndKeychain = certsFromStore.Intersect(certsFromDisk, ThumbprintComparer.Instance);
// Note that if we require exportable certs, the actual certs we populate need to be the ones
// from the store location, and not the version from disk. If we don't require exportability,
// we favor the version of the cert that's on disk (avoiding unnecessary keychain access
// prompts). Intersect compares with the specified comparer and returns the matching elements
// from the first set.
var onDiskAndKeychain = requireExportable ? certsFromStore.Intersect(certsFromDisk, ThumbprintComparer.Instance)
: certsFromDisk.Intersect(certsFromStore, ThumbprintComparer.Instance);

// The only times we can find a certificate on the keychain and a certificate on keychain+disk
// are when the certificate on disk and keychain has expired and a pre-.NET 7 SDK has been
Expand All @@ -402,7 +406,7 @@ protected override void PopulateCertificatesFromStore(X509Store store, List<X509
}
else
{
base.PopulateCertificatesFromStore(store, certificates);
base.PopulateCertificatesFromStore(store, certificates, requireExportable);
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/Lazvard.Message.Cli/CertificateGeneration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
This is a copy of ASP.NET Core CertificateGeneration project

Repo: https://github.com/dotnet/aspnetcore
Path: src/Shared/CertificateGeneration/

To update these file run update.sh or update.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

internal sealed class UnixCertificateManager : CertificateManager
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace Lazvard.Message.Cli.CertificateGeneration;
namespace Microsoft.AspNetCore.Certificates.Generation;

[SupportedOSPlatform("windows")]
internal sealed class WindowsCertificateManager : CertificateManager
Expand All @@ -29,10 +32,10 @@ protected override bool IsExportable(X509Certificate2 c)
return true;
#else
using var key = c.GetRSAPrivateKey();
return key is RSACryptoServiceProvider rsaPrivateKey &&
rsaPrivateKey.CspKeyContainerInfo.Exportable ||
key is RSACng cngPrivateKey &&
cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport;
return (key is RSACryptoServiceProvider rsaPrivateKey &&
rsaPrivateKey.CspKeyContainerInfo.Exportable) ||
(key is RSACng cngPrivateKey &&
cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport);
#endif
}

Expand Down
16 changes: 16 additions & 0 deletions src/Lazvard.Message.Cli/CertificateGeneration/update.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Variables
$RepoUrl = "https://github.com/dotnet/aspnetcore.git"
$PathInRepo = "src/Shared/CertificateGeneration/"

# Create a temporary directory
$TempDir = New-Item -ItemType Directory -Force -Path "$env:TEMP\$(Get-Random)"

# Clone the repository to the temporary directory
git clone $RepoUrl $TempDir

# Copy files from the specified path to the current directory
$SourcePath = Join-Path -Path $TempDir -ChildPath $PathInRepo
Copy-Item -Path "$SourcePath\*" -Destination "." -Recurse

# Cleanup: Delete the temporary directory
Remove-Item -Path $TempDir -Recurse -Force
15 changes: 15 additions & 0 deletions src/Lazvard.Message.Cli/CertificateGeneration/update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# Variables
REPO_URL=https://github.com/dotnet/aspnetcore.git
PATH_IN_REPO=src/Shared/CertificateGeneration/
TEMP_DIR=$(mktemp -d)

# Clone the repository to a temporary directory
git clone $REPO_URL $TEMP_DIR

# Copy files from the specified path to the current directory
cp -r $TEMP_DIR/$PATH_IN_REPO/* .

# Cleanup: Delete the temporary directory
rm -rf $TEMP_DIR
Loading

0 comments on commit a481633

Please sign in to comment.