diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml
new file mode 100644
index 0000000..b6c5b95
--- /dev/null
+++ b/.github/workflows/greetings.yml
@@ -0,0 +1,19 @@
+name: Greetings
+
+on: pull_request_target
+
+jobs:
+ greeting:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: actions/first-interaction@v1
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ pr-message: |
+ Welcome to Aegis repository! We appreciate you taking the time to contribute.
+
+ We're excited to review your pull request and look forward to collaborating with you. Please let us know if you have any questions or need any assistance.
+
+ Thank you for your contribution!
\ No newline at end of file
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..d247572
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,37 @@
+# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
+#
+# You can adjust the behavior by modifying this file.
+# For more information, see:
+# https://github.com/actions/stale
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: '44 8 * * *'
+
+jobs:
+ stale:
+
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+
+ steps:
+ - uses: actions/stale@v5
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: |
+ This issue has been automatically marked as stale because it has not had recent activity.
+ It will be closed if no further activity occurs.
+
+ If this issue is still relevant, please leave a comment indicating that you would like it to remain open.
+ Thank you for your contributions.
+ stale-pr-message: |
+ This pull request has been automatically marked as stale because it has not had recent activity.
+ It will be closed if no further activity occurs.
+
+ If you are still working on this pull request, please leave a comment indicating that you would like it to remain open.
+ Thank you for your contributions.
+ stale-issue-label: 'no-issue-activity'
+ stale-pr-label: 'no-pr-activity'
\ No newline at end of file
diff --git a/README.md b/README.md
index 1d310d8..6035fd8 100644
--- a/README.md
+++ b/README.md
@@ -271,11 +271,12 @@ adapt the provided code to your specific needs.
### Custom Validation Rules
-Aegis allows you to implement custom validation rules to enforce specific licensing requirements beyond the built-in validation logic. This provides greater flexibility and control over your licensing system.
+Aegis allows you to implement custom validation rules to enforce specific licensing requirements beyond the built-in validation logic.
+This provides greater flexibility and control over your licensing system.
1. **Implementing a Custom Rule**
-
- To create a custom validation rule, implement the `IValidationRule` interface:
+
+ To create a custom validation rule, implement the `IValidationRule` interface:
```csharp
using Aegis.Interfaces;
@@ -304,15 +305,16 @@ Aegis allows you to implement custom validation rules to enforce specific licens
2. **Registering the Rule**
- Once you have implemented your custom rule, register it with the `LicenseValidator`:
-
+ Once you have implemented your custom rule, register it with the `LicenseValidator`:
+
```csharp
LicenseValidator.AddValidationRule(new MyCustomRule());
```
3. **Example: Advanced Hardware Validation for Node-Locked Licenses**
- This example demonstrates a more robust hardware validation for node-locked licenses, considering multiple hardware factors.
+ This example demonstrates a more robust hardware validation for node-locked licenses, considering multiple hardware
+ factors.
```csharp
using Aegis.Interfaces;
@@ -349,19 +351,100 @@ Aegis allows you to implement custom validation rules to enforce specific licens
}
```
- **Usage:**
-
+ **Usage:**
+
```csharp
LicenseValidator.AddValidationRule(new AdvancedHardwareRule());
// ... during license loading ...
var license = await LicenseManager.LoadLicenseAsync("license.bin");
```
-
- This example showcases how you can create a custom rule to enhance the security of your node-locked licenses by validating against multiple hardware identifiers.
- You can adapt this example and implement your own logic for combining and validating different hardware factors to suit your specific needs. Remember to provide clear documentation and error messages within your custom rules to make them easy to understand and maintain.
+ This example showcases how you can create a custom rule to enhance the security of your node-locked licenses by
+ validating against multiple hardware identifiers.
+
+ You can adapt this example and implement your own logic for combining and validating different hardware factors to
+ suit your specific needs. Remember to provide clear documentation and error messages within your custom rules to make
+ them easy to understand and maintain.
+
+### Custom Hardware Identification
+
+By default, Aegis uses a `DefaultHardwareIdentifier` that combines Machine Name, User Name, OS Version, and MAC Address.
+To implement a custom hardware identifier:
+
+1. **Create a class that implements `IHardwareIdentifier`:**
+
+ ```csharp
+ using Aegis.Interfaces;
+
+ public class MyCustomHardwareIdentifier : IHardwareIdentifier
+ {
+ public string GetHardwareIdentifier()
+ {
+ // Your custom logic to retrieve the hardware identifier.
+ // Example: CPU ID, Motherboard serial number, etc.
+ return "...";
+ }
+
+ public bool ValidateHardwareIdentifier(string hardwareIdentifier)
+ {
+ // Your custom logic to validate the hardware identifier.
+ return GetHardwareIdentifier() == hardwareIdentifier;
+ }
+ }
+ ```
+
+2. **Set the custom identifier:**
+
+ ```csharp
+ LicenseManager.SetHardwareIdentifier(new MyCustomHardwareIdentifier());
+ ```
+
+### Custom Serialization
+
+Aegis uses a `JsonLicenseSerializer` by default. To implement a custom serializer:
+
+1. **Create a class that implements `ILicenseSerializer`:**
+
+ ```csharp
+ using Aegis.Interfaces;
+ using Aegis.Models;
+
+ public class MyCustomSerializer : ILicenseSerializer
+ {
+ public string Serialize(BaseLicense license)
+ {
+ // Your custom serialization logic.
+ // Example: XML serialization, binary serialization, etc.
+ return "...";
+ }
+
+ public BaseLicense? Deserialize(string data)
+ {
+ // Your custom deserialization logic.
+ return ...;
+ }
+ }
+ ```
+
+2. **Set the custom serializer:**
+
+ ```csharp
+ LicenseManager.SetSerializer(new MyCustomSerializer());
+ ```
+
+### Disabling Built-in Validation
+
+You can disable Aegis's built-in validation logic if you want to rely solely on your custom validation rules or have a
+different validation process.
+
+```csharp
+LicenseManager.SetBuiltInValidation(false);
+```
+This will prevent `LicenseManager` from performing its default validation checks for license type, expiry date, and
+other built-in criteria. You will be responsible for implementing all necessary validation logic in your custom rules or
+through other means.
### Architecture
diff --git a/src/Aegis.Server/Aegis.Server.csproj b/src/Aegis.Server/Aegis.Server.csproj
index 944d126..5949561 100644
--- a/src/Aegis.Server/Aegis.Server.csproj
+++ b/src/Aegis.Server/Aegis.Server.csproj
@@ -4,8 +4,8 @@
net8.0
enable
enable
- 1.1.0
- 1.1.0
+ 1.3.0
+ 1.3.0
Aegis.Server
Aegis is a robust and flexible .NET licensing library that simplifies the implementation of various licensing models for your applications. It offers strong security features, both online and offline validation, and easy integration with your existing projects. Securely manage your software licenses with Aegis.
Copyright (c) 2024 LSXPrime
@@ -16,7 +16,7 @@
Aegis.png
README.md
license, key, licensing, protect, shield
- 1.2.0
+ 1.3.0
https://github.com/LSXPrime/Aegis/releases
diff --git a/src/Aegis/Aegis.csproj b/src/Aegis/Aegis.csproj
index 21f8cef..76ef397 100644
--- a/src/Aegis/Aegis.csproj
+++ b/src/Aegis/Aegis.csproj
@@ -4,8 +4,8 @@
net8.0
enable
enable
- 1.2.0
- 1.2.0
+ 1.3.0
+ 1.3.0
Aegis
Aegis is a robust and flexible .NET licensing library that simplifies the implementation of various licensing models for your applications. It offers strong security features, both online and offline validation, and easy integration with your existing projects. Securely manage your software licenses with Aegis.
Copyright (c) 2024 LSXPrime
@@ -16,12 +16,11 @@
Aegis.png
README.md
license, key, licensing, protect, shield
- 1.2.0
+ 1.3.0
https://github.com/LSXPrime/Aegis/releases
-
diff --git a/src/Aegis/Exceptions/InvalidLicenseFormatException.cs b/src/Aegis/Exceptions/InvalidLicenseFormatException.cs
index e35ce39..ed62eac 100644
--- a/src/Aegis/Exceptions/InvalidLicenseFormatException.cs
+++ b/src/Aegis/Exceptions/InvalidLicenseFormatException.cs
@@ -1,3 +1,12 @@
namespace Aegis.Exceptions;
-public class InvalidLicenseFormatException(string message) : LicenseValidationException(message);
\ No newline at end of file
+public class InvalidLicenseFormatException : LicenseValidationException
+{
+ public InvalidLicenseFormatException(string message) : base(message)
+ {
+ }
+
+ public InvalidLicenseFormatException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/Aegis/Interfaces/IHardwareIdentifier.cs b/src/Aegis/Interfaces/IHardwareIdentifier.cs
new file mode 100644
index 0000000..e224d22
--- /dev/null
+++ b/src/Aegis/Interfaces/IHardwareIdentifier.cs
@@ -0,0 +1,7 @@
+namespace Aegis.Interfaces;
+
+public interface IHardwareIdentifier
+{
+ string GetHardwareIdentifier();
+ bool ValidateHardwareIdentifier(string hardwareIdentifier);
+}
\ No newline at end of file
diff --git a/src/Aegis/Interfaces/ILicenseSerializer.cs b/src/Aegis/Interfaces/ILicenseSerializer.cs
new file mode 100644
index 0000000..31c374e
--- /dev/null
+++ b/src/Aegis/Interfaces/ILicenseSerializer.cs
@@ -0,0 +1,9 @@
+using Aegis.Models;
+
+namespace Aegis.Interfaces;
+
+public interface ILicenseSerializer
+{
+ string Serialize(BaseLicense license);
+ BaseLicense? Deserialize(string data);
+}
\ No newline at end of file
diff --git a/src/Aegis/LicenseBuilder.cs b/src/Aegis/LicenseBuilder.cs
index e82d8ad..be86033 100644
--- a/src/Aegis/LicenseBuilder.cs
+++ b/src/Aegis/LicenseBuilder.cs
@@ -79,7 +79,7 @@ public static BaseLicense WithLicenseKey(this BaseLicense baseLicense, string li
/// The license object that was saved.
public static BaseLicense SaveLicense(this BaseLicense baseLicense, string filePath)
{
- LicenseManager.SaveLicense(baseLicense, filePath);
+ LicenseManager.SaveLicenseToPath(baseLicense, filePath);
return baseLicense;
}
}
\ No newline at end of file
diff --git a/src/Aegis/LicenseGenerator.cs b/src/Aegis/LicenseGenerator.cs
index 3c83d7a..d1de7e2 100644
--- a/src/Aegis/LicenseGenerator.cs
+++ b/src/Aegis/LicenseGenerator.cs
@@ -1,10 +1,13 @@
-using Aegis.Models;
+using Aegis.Interfaces;
+using Aegis.Models;
using Aegis.Utilities;
namespace Aegis;
public static class LicenseGenerator
{
+ private static IHardwareIdentifier _hardwareIdentifier = new DefaultHardwareIdentifier();
+
///
/// Generates a standard license.
///
@@ -35,7 +38,7 @@ public static TrialLicense GenerateTrialLicense(TimeSpan trialPeriod)
/// A new NodeLockedLicense object.
public static NodeLockedLicense GenerateNodeLockedLicense(string? hardwareId = null)
{
- hardwareId ??= HardwareUtils.GetHardwareId();
+ hardwareId ??= _hardwareIdentifier.GetHardwareIdentifier();
return new NodeLockedLicense(hardwareId);
}
@@ -71,4 +74,8 @@ public static ConcurrentLicense GenerateConcurrentLicense(string userName, int m
{
return new ConcurrentLicense(userName, maxActiveUsersCount);
}
+
+
+
+ internal static void SetHardwareIdentifier(IHardwareIdentifier hardwareIdentifier) => _hardwareIdentifier = hardwareIdentifier;
}
\ No newline at end of file
diff --git a/src/Aegis/LicenseManager.cs b/src/Aegis/LicenseManager.cs
index a92e5c2..2c37201 100644
--- a/src/Aegis/LicenseManager.cs
+++ b/src/Aegis/LicenseManager.cs
@@ -1,10 +1,13 @@
using System.Globalization;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
+using System.Text;
using System.Text.Json;
using Aegis.Enums;
using Aegis.Exceptions;
+using Aegis.Interfaces;
using Aegis.Models;
+using Aegis.Serialization;
using Aegis.Utilities;
[assembly: InternalsVisibleTo("Aegis.Tests")]
@@ -21,6 +24,8 @@ public static class LicenseManager
private static readonly string DisconnectEndpoint = $"{_serverBaseEndpoint}/disconnect";
private static TimeSpan _heartbeatInterval = TimeSpan.FromMinutes(5); // Should be less than server timeout
private static Timer? _heartbeatTimer;
+ private static ILicenseSerializer _serializer = new JsonLicenseSerializer();
+ private static bool _builtInValidation = true;
public static BaseLicense? Current { get; private set; }
///
@@ -57,15 +62,45 @@ public static void SetHeartbeatInterval(TimeSpan heartbeatInterval)
_heartbeatInterval = heartbeatInterval;
}
+ ///
+ /// Sets the serializer for license serialization.
+ ///
+ /// The serializer that implements the interface.
+ /// Thrown if the serializer is null.
+ public static void SetSerializer(ILicenseSerializer serializer)
+ {
+ ArgumentNullException.ThrowIfNull(serializer);
+ _serializer = serializer;
+ LicenseValidator.SetSerializer(_serializer);
+ }
+
+ ///
+ /// Sets the hardware identifier for license validation and generation.
+ ///
+ /// The hardware identifier that implements the interface.
+ /// Thrown if the identifier is null.
+ public static void SetHardwareIdentifier(IHardwareIdentifier identifier)
+ {
+ ArgumentNullException.ThrowIfNull(identifier);
+ LicenseValidator.SetHardwareIdentifier(identifier);
+ LicenseGenerator.SetHardwareIdentifier(identifier);
+ }
+
+ ///
+ /// Sets whether to use built-in validation or not.
+ ///
+ /// Whether to use built-in validation or not.
+ public static void SetBuiltInValidation(bool value) => _builtInValidation = value;
+
///
/// Saves a license to a file.
///
/// The type of license to save.
/// The license object to save.
/// The path to the file to save the license to.
- /// The private key for signing.
+ /// The private key for signing.
/// Thrown if the license or file path is null.
- public static void SaveLicense(T license, string filePath, string? privateKey = null) where T : BaseLicense
+ public static void SaveLicenseToPath(T license, string filePath, string? secretKey = null) where T : BaseLicense
{
ArgumentNullException.ThrowIfNull(license);
ArgumentNullException.ThrowIfNull(filePath);
@@ -74,7 +109,7 @@ public static void SaveLicense(T license, string filePath, string? privateKey
throw new ArgumentException("Invalid file path.", nameof(filePath));
// Save the combined data to the specified file
- File.WriteAllBytes(filePath, SaveLicense(license, privateKey));
+ File.WriteAllBytes(filePath, SaveLicense(license, secretKey));
}
///
@@ -90,8 +125,7 @@ public static byte[] SaveLicense(T license, string? privateKey = null) where
ArgumentNullException.ThrowIfNull(license);
// Serialize the license object
- var licenseData =
- JsonSerializer.SerializeToUtf8Bytes(license, new JsonSerializerOptions { WriteIndented = true });
+ var licenseData = Encoding.UTF8.GetBytes(_serializer.Serialize(license));
// Generate a unique AES secret key
var aesKey = SecurityUtils.GenerateAesKey();
@@ -145,7 +179,7 @@ public static byte[] SaveLicense(T license, string? privateKey = null) where
{
if (!LicenseValidator.VerifyLicenseData(licenseData, out var license, true) || license == null)
throw new InvalidLicenseFormatException("Invalid license file format.");
-
+
// Set the current license based on type
license = license.Type switch
{
@@ -185,7 +219,7 @@ public static void ThrowIfNotAllowed(string featureName)
if (!IsFeatureEnabled(featureName))
throw new FeatureNotLicensedException($"Feature '{featureName}' is not allowed in your licensing model.");
}
-
+
///
/// Closes connection to the licensing server and releases any resources.
///
@@ -205,7 +239,7 @@ public static async Task CloseAsync()
}
// Helper methods
-
+
///
/// Validates the license asynchronously.
///
@@ -269,24 +303,28 @@ private static async Task ValidateLicenseOnlineAsync(BaseLicense license, byte[]
private static void ValidateLicenseOffline(BaseLicense license, byte[] licenseData,
Dictionary? validationParams = null)
{
- var isLicenseValid = license.Type switch
+ var isLicenseValid = false;
+ if (_builtInValidation)
{
- LicenseType.Standard => LicenseValidator.ValidateStandardLicense(licenseData,
- validationParams?["UserName"]!, validationParams?["SerialNumber"]!),
- LicenseType.Trial => LicenseValidator.ValidateTrialLicense(licenseData),
- LicenseType.NodeLocked => LicenseValidator.ValidateNodeLockedLicense(licenseData,
- validationParams?["HardwareId"]),
- LicenseType.Subscription => LicenseValidator.ValidateSubscriptionLicense(licenseData),
- LicenseType.Floating => LicenseValidator.ValidateFloatingLicense(licenseData,
- validationParams?["UserName"]!, int.Parse(validationParams?["MaxActiveUsersCount"]!)),
- LicenseType.Concurrent => LicenseValidator.ValidateConcurrentLicense(licenseData,
- validationParams?["UserName"]!, int.Parse(validationParams?["MaxActiveUsersCount"]!)),
- _ => false
- };
-
- if (isLicenseValid)
- isLicenseValid = LicenseValidator.ValidateLicenseRules(license, validationParams);
+ isLicenseValid = license.Type switch
+ {
+ LicenseType.Standard => LicenseValidator.ValidateStandardLicense(licenseData,
+ validationParams?["UserName"]!, validationParams?["SerialNumber"]!),
+ LicenseType.Trial => LicenseValidator.ValidateTrialLicense(licenseData),
+ LicenseType.NodeLocked => LicenseValidator.ValidateNodeLockedLicense(licenseData,
+ validationParams?["HardwareId"]),
+ LicenseType.Subscription => LicenseValidator.ValidateSubscriptionLicense(licenseData),
+ LicenseType.Floating => LicenseValidator.ValidateFloatingLicense(licenseData,
+ validationParams?["UserName"]!, int.Parse(validationParams?["MaxActiveUsersCount"]!)),
+ LicenseType.Concurrent => LicenseValidator.ValidateConcurrentLicense(licenseData,
+ validationParams?["UserName"]!, int.Parse(validationParams?["MaxActiveUsersCount"]!)),
+ _ => false
+ };
+ }
+ if (isLicenseValid || !_builtInValidation)
+ isLicenseValid = LicenseValidator.ValidateLicenseRules(license, validationParams);
+
if (!isLicenseValid)
throw new LicenseValidationException("License validation failed.");
}
@@ -365,7 +403,7 @@ private static async Task SendDisconnectAsync()
throw new HeartbeatException(
$"Concurrent user disconnect failed: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
}
-
+
private static byte[] CombineLicenseData(byte[] hash, byte[] signature, byte[] encryptedData, byte[] aesKey)
{
var combinedData = new byte[
diff --git a/src/Aegis/LicenseValidator.cs b/src/Aegis/LicenseValidator.cs
index 96a234f..9c3c6d6 100644
--- a/src/Aegis/LicenseValidator.cs
+++ b/src/Aegis/LicenseValidator.cs
@@ -1,7 +1,9 @@
-using System.Text.Json;
+using System.Text;
+using System.Text.Json;
using Aegis.Exceptions;
using Aegis.Interfaces;
using Aegis.Models;
+using Aegis.Serialization;
using Aegis.Utilities;
namespace Aegis;
@@ -10,6 +12,19 @@ public static class LicenseValidator
{
private static readonly List ValidationRules = [];
private static readonly Dictionary ValidationRuleGroups = new();
+ private static ILicenseSerializer _serializer = new JsonLicenseSerializer();
+ private static IHardwareIdentifier _hardwareIdentifier = new DefaultHardwareIdentifier();
+
+ internal static void SetSerializer(ILicenseSerializer serializer)
+ {
+ ArgumentNullException.ThrowIfNull(serializer);
+ _serializer = serializer;
+ }
+
+ internal static void SetHardwareIdentifier(IHardwareIdentifier hardwareIdentifier)
+ {
+ _hardwareIdentifier = hardwareIdentifier;
+ }
public static void AddValidationRule(IValidationRule rule)
{
@@ -86,7 +101,7 @@ public static bool ValidateNodeLockedLicense(byte[] licenseData, string? hardwar
return licenseObj is NodeLockedLicense license &&
(!license.ExpirationDate.HasValue || !(license.ExpirationDate < DateTime.UtcNow)) &&
- HardwareUtils.ValidateHardwareId(hardwareId ?? license.HardwareId);
+ _hardwareIdentifier.ValidateHardwareIdentifier(hardwareId ?? license.HardwareId);
}
///
@@ -177,7 +192,7 @@ internal static bool VerifyLicenseData(byte[] licenseData, out BaseLicense? lice
var decryptedData = SecurityUtils.DecryptData(encryptedData, aesKey);
// Deserialize the license object
- license = JsonSerializer.Deserialize(decryptedData);
+ license = _serializer.Deserialize(Encoding.UTF8.GetString(decryptedData));
return license != null;
}
diff --git a/src/Aegis/Serialization/Converters/JsonLicenseConverter.cs b/src/Aegis/Serialization/Converters/JsonLicenseConverter.cs
new file mode 100644
index 0000000..a7c191b
--- /dev/null
+++ b/src/Aegis/Serialization/Converters/JsonLicenseConverter.cs
@@ -0,0 +1,43 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Aegis.Enums;
+using Aegis.Exceptions;
+using Aegis.Models;
+
+namespace Aegis.Serialization.Converters;
+
+public class JsonLicenseConverter : JsonConverter
+{
+ public override BaseLicense Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ // Read the license type discriminator
+ Utf8JsonReader readerClone = reader;
+ if (!readerClone.Read() || readerClone.TokenType != JsonTokenType.StartObject)
+ throw new JsonException("Expected start of object.");
+
+ if(!readerClone.Read() || readerClone.TokenType != JsonTokenType.PropertyName || readerClone.GetString() != "Type")
+ throw new JsonException("Expected Type property.");
+
+ readerClone.Read();
+
+ var licenseType = (LicenseType)Enum.Parse(typeof(LicenseType), readerClone.GetString()!);
+
+ // Deserialize based on the discriminator
+ return licenseType switch
+ {
+ LicenseType.Standard => JsonSerializer.Deserialize(ref reader, options)!,
+ LicenseType.Trial => JsonSerializer.Deserialize(ref reader, options)!,
+ LicenseType.NodeLocked => JsonSerializer.Deserialize(ref reader, options)!,
+ LicenseType.Subscription => JsonSerializer.Deserialize(ref reader, options)!,
+ LicenseType.Floating => JsonSerializer.Deserialize(ref reader, options)!,
+ LicenseType.Concurrent => JsonSerializer.Deserialize(ref reader, options)!,
+ _ => throw new InvalidLicenseFormatException($"Unknown license type: {licenseType}")
+ };
+ }
+
+
+ public override void Write(Utf8JsonWriter writer, BaseLicense value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(writer, value, value.GetType(), options);
+ }
+}
\ No newline at end of file
diff --git a/src/Aegis/Serialization/JsonLicenseSerializer.cs b/src/Aegis/Serialization/JsonLicenseSerializer.cs
new file mode 100644
index 0000000..563cb6f
--- /dev/null
+++ b/src/Aegis/Serialization/JsonLicenseSerializer.cs
@@ -0,0 +1,32 @@
+using System.Text.Json;
+using Aegis.Exceptions;
+using Aegis.Interfaces;
+using Aegis.Models;
+
+namespace Aegis.Serialization;
+
+public class JsonLicenseSerializer(JsonSerializerOptions? options = null) : ILicenseSerializer
+{
+ private readonly JsonSerializerOptions _options = options ?? new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ };
+
+ public string Serialize(BaseLicense license)
+ {
+ return JsonSerializer.Serialize(license, _options);
+ }
+
+ public BaseLicense? Deserialize(string data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+ try
+ {
+ return JsonSerializer.Deserialize(data, _options);
+ }
+ catch (JsonException ex)
+ {
+ throw new InvalidLicenseFormatException("Invalid license format.", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Aegis/Utilities/DefaultHardwareIdentifier.cs b/src/Aegis/Utilities/DefaultHardwareIdentifier.cs
new file mode 100644
index 0000000..03756c3
--- /dev/null
+++ b/src/Aegis/Utilities/DefaultHardwareIdentifier.cs
@@ -0,0 +1,47 @@
+using System.Net.NetworkInformation;
+using Aegis.Interfaces;
+
+namespace Aegis.Utilities;
+
+public class DefaultHardwareIdentifier : IHardwareIdentifier
+{
+ ///
+ /// Gets a unique hardware identifier for the current machine.
+ ///
+ /// A string representing the hardware identifier.
+ public string GetHardwareIdentifier()
+ {
+ return $"{Environment.MachineName}-{Environment.UserName}-{Environment.OSVersion}-{GetMacAddress()}";
+ }
+
+ ///
+ /// Validates a hardware identifier against the current machine's hardware identifier.
+ ///
+ /// The hardware identifier to validate.
+ /// True if the hardware identifier matches, false otherwise.
+ public bool ValidateHardwareIdentifier(string hardwareId)
+ {
+ return GetHardwareIdentifier() == hardwareId;
+ }
+
+ private static string GetMacAddress()
+ {
+ var values = NetworkInterface.GetAllNetworkInterfaces()
+ .Where(x => (x.NetworkInterfaceType != NetworkInterfaceType.Wireless80211) && x.Name != "docker0")
+ .Select(x => x.GetPhysicalAddress().ToString())
+ .Where(x => x != "000000000000")
+ .Select(x =>
+ {
+ if (x.Length != 12 && x.Length != 16)
+ return x;
+
+ var parts = Enumerable.Range(0, x.Length / 2).Select(i => x.Substring(i * 2, 2));
+ return string.Join(":", parts.ToArray());
+ })
+ .ToArray();
+
+ return values.Length > 0
+ ? string.Join(",", values.ToArray())
+ : "000000000000";
+ }
+}
\ No newline at end of file
diff --git a/src/Aegis/Utilities/HardwareUtils.cs b/src/Aegis/Utilities/HardwareUtils.cs
deleted file mode 100644
index 8f7922d..0000000
--- a/src/Aegis/Utilities/HardwareUtils.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using DeviceId;
-
-namespace Aegis.Utilities;
-
-public static class HardwareUtils
-{
- ///
- /// Gets a unique hardware identifier for the current machine.
- ///
- /// A string representing the hardware identifier.
- public static string GetHardwareId()
- {
- return new DeviceIdBuilder()
- .AddMachineName()
- .AddUserName()
- .AddOsVersion()
- .AddMacAddress(true, true)
- .ToString();
- }
-
- ///
- /// Validates a hardware identifier against the current machine's hardware identifier.
- ///
- /// The hardware identifier to validate.
- /// True if the hardware identifier matches, false otherwise.
- public static bool ValidateHardwareId(string hardwareId)
- {
- return GetHardwareId() == hardwareId;
- }
-}
\ No newline at end of file
diff --git a/tests/Aegis.Tests/LicenseManagerTests.cs b/tests/Aegis.Tests/LicenseManagerTests.cs
index a06212b..8532075 100644
--- a/tests/Aegis.Tests/LicenseManagerTests.cs
+++ b/tests/Aegis.Tests/LicenseManagerTests.cs
@@ -40,7 +40,7 @@ public void SaveLicense_SavesLicenseToFileCorrectly()
var filePath = Path.GetTempFileName(); // Use a temporary file
// Act
- LicenseManager.SaveLicense(license, filePath);
+ LicenseManager.SaveLicenseToPath(license, filePath);
// Assert
Assert.True(File.Exists(filePath));
@@ -69,7 +69,7 @@ public void SaveLicense_ThrowsExceptionForNullFilePath()
var license = GenerateLicense();
// Act & Assert
- Assert.Throws(() => LicenseManager.SaveLicense(license, null!));
+ Assert.Throws(() => LicenseManager.SaveLicenseToPath(license, null!));
}
[Fact]
@@ -80,7 +80,7 @@ public void SaveLicense_ThrowsExceptionForEmptyFilePath()
var license = GenerateLicense();
// Act & Assert
- Assert.Throws(() => LicenseManager.SaveLicense(license, ""));
+ Assert.Throws(() => LicenseManager.SaveLicenseToPath(license, ""));
}
[Fact]
@@ -92,7 +92,7 @@ public void SaveLicense_ThrowsExceptionForInvalidFilePath()
const string filePath = "Invalid/File/Path"; // This should be an invalid path on most systems
// Act & Assert
- Assert.Throws(() => LicenseManager.SaveLicense(license, filePath));
+ Assert.Throws(() => LicenseManager.SaveLicenseToPath(license, filePath));
}
[Fact]
@@ -102,7 +102,7 @@ public async Task LoadLicenseAsync_LoadsLicenseFromFileCorrectly()
LoadSecretKeys();
var license = GenerateLicense();
var filePath = Path.GetTempFileName();
- LicenseManager.SaveLicense(license, filePath);
+ LicenseManager.SaveLicenseToPath(license, filePath);
// Act
var loadedLicense = await LicenseManager.LoadLicenseAsync(filePath);
diff --git a/tests/Aegis.Tests/LicenseValidatorTests.cs b/tests/Aegis.Tests/LicenseValidatorTests.cs
index 240d8ec..0ec73bf 100644
--- a/tests/Aegis.Tests/LicenseValidatorTests.cs
+++ b/tests/Aegis.Tests/LicenseValidatorTests.cs
@@ -157,7 +157,7 @@ public void ValidateNodeLockedLicense_ReturnsTrue_ForValidLicense()
{
// Arrange
LoadSecretKeys();
- var hardwareId = HardwareUtils.GetHardwareId();
+ var hardwareId = new DefaultHardwareIdentifier().GetHardwareIdentifier();
var license = GenerateNodeLockedLicense(hardwareId);
var licenseData = LicenseManager.SaveLicense(license);