From 73fa1788e6a59812d1d28e00fdc8d16269f1a189 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 27 Aug 2026 13:58:00 +0800 Subject: [PATCH 1/4] Add validating reader --- .../ApiApprovals.Approve.approved.txt | 4 + ...censingComponent_Report_Signature_Tests.cs | 72 ++++------- .../ReportValidationResult.cs | 51 +------- .../ValidatingReportReader.cs | 119 ++++++++++++++++++ 4 files changed, 150 insertions(+), 96 deletions(-) create mode 100644 src/Particular.LicensingComponent.Report/ValidatingReportReader.cs diff --git a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt index 88ec48e..0a1c147 100644 --- a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt +++ b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt @@ -77,6 +77,10 @@ namespace Particular.LicensingComponent.Report public Particular.LicensingComponent.Report.Report ReportData { get; init; } public string Signature { get; init; } } + public static class ValidatingReportReader + { + public static Particular.LicensingComponent.Report.Report? Read(System.IO.Stream stream, out Particular.LicensingComponent.Report.ReportValidationResult validationResult) { } + } } namespace Particular.LicensingComponent.Report.Utility { diff --git a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs index 495b699..3fa8614 100644 --- a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs +++ b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using NUnit.Framework; @@ -34,10 +35,10 @@ public void Should_deserialize_report_with_signature() //Act var reportString = JsonSerializer.Serialize(report, SerializationOptions.NotIndentedWithNoEscaping); - var deserialized = JsonSerializer.Deserialize(reportString, SerializationOptions.NotIndentedWithNoEscaping); + var _ = Parse(reportString, out var validationResult); //Assert - Assert.That(ValidateReport(deserialized)); + Assert.That(validationResult.IsValid, Is.True); } [Test] @@ -49,26 +50,25 @@ public void Should_not_allow_tempering_with_report() //Act reportString = reportString.Replace("\"Throughput\": 42", "\"Throughput\": 13"); - var deserialized = JsonSerializer.Deserialize(reportString, SerializationOptions.NotIndentedWithNoEscaping); + var _ = Parse(reportString, out var validationResult); //Assert - Assert.That(ValidateReport(deserialized), Is.False); + Assert.That(validationResult.IsValid, Is.False); } [Test] public void Should_be_able_to_read_a_V1_report() { //Arrange - var reportString = GetResource("throughput-report-v1.0.json"); + using var stream = GetResourceStream("throughput-report-v1.0.json"); //Act - var report = JsonSerializer.Deserialize(reportString, SerializationOptions.NotIndentedWithNoEscaping); - var data = report!.ReportData; + var data = ValidatingReportReader.ReadAndValidate(stream!, out var validationResult); //Assert // Want to be explicit with asserts to ensure that a 1.0 report can be read correctly // An approval test would be too easy to just accept changes on - Assert.That(data.CustomerName, Is.EqualTo("Testing")); + Assert.That(data!.CustomerName, Is.EqualTo("Testing")); Assert.That(data.MessageTransport, Is.EqualTo("RabbitMQ")); Assert.That(data.ReportMethod, Is.EqualTo("ThroughputTool: RabbitMQ Admin")); Assert.That(data.ToolVersion, Is.EqualTo("1.0.0")); @@ -84,25 +84,22 @@ public void Should_be_able_to_read_a_V1_report() Assert.That(data.TotalQueues, Is.EqualTo(7)); - Assert.That(report.Signature, Is.EqualTo("ybIzoo9ogZtbSm5+jJa3GxncjCX3fxAfiLSI7eogG20KjJiv43aCE+7Lsvhkat7AALM34HgwI3VsgzRmyLYXD5n0+XRrWXNgeRGbLEG6d1W2djLRHNjXo423zpGTYDeMq3vhI9yAcil0K0dCC/ZCnw8dPd51pNmgKYIvrfELW0hyN70trUeCMDhYRfXruWLNe8Hfy+tS8Bm13B5vknXNlAjBIuGjXn3XILRRSVrTbb4QMIRzSluSnSTFPTCyE9wMWwC0BUGSf7ZEA0XdeN6UkaO/5URSOQVesiSLRqQWbfUc87XlY1hMs5Z7kLSOr5WByIQIfQKum1nGVjLMzshyhQ==")); - - Assert.That(ValidateReport(report)); + Assert.That(validationResult.IsValid, Is.True); } [Test] public void Should_be_able_to_read_a_V2_report() { //Arrange - var reportString = GetResource("throughput-report-v2.0.json"); + using var stream = GetResourceStream("throughput-report-v2.0.json"); //Act - var report = JsonSerializer.Deserialize(reportString, SerializationOptions.NotIndentedWithNoEscaping); - var data = report!.ReportData; + var data = ValidatingReportReader.ReadAndValidate(stream!, out var validationResult); //Assert // Want to be explicit with asserts to ensure that a 2.0 report can be read correctly // An approval test would be too easy to just accept changes on - Assert.That(data.CustomerName, Is.EqualTo("TestCustomer")); + Assert.That(data!.CustomerName, Is.EqualTo("TestCustomer")); Assert.That(data.MessageTransport, Is.EqualTo("AzureServiceBus")); Assert.That(data.ReportMethod, Is.EqualTo("Broker")); Assert.That(data.ToolType, Is.EqualTo("Platform Licensing Component")); @@ -129,9 +126,9 @@ public void Should_be_able_to_read_a_V2_report() Assert.That(data.EnvironmentInformation.EnvironmentData.ContainsKey("MonitoringEnabled"), Is.True); Assert.That(data.EnvironmentInformation.EnvironmentData["MonitoringEnabled"], Is.EqualTo("True")); - Assert.That(report.Signature, Is.EqualTo("IEbO4i0Jn54iHUzlwotHf9aw/fZIHY+dztY9cMRkWjVVo6AiYtihWR0mip793gRrWHOxHVobCpa4l5svRk16mBR+YAOrs3KNRVTzrl4+wL21e1u9zFuPNrHLtFeul+taJxV8ciA7zEgD7LMle9CcR/Vfm8BZ9mmD5W/DjsCYLCdVXfN4iRMlz+eW50mOHty21yJ0pOiYBooaN2EJexVY4Q+5FMyAkm0wucEPFyaQB6+SfcS37fEm807B7sXhtUPiW+einqDOX6uYF+MuXxUn1u9LxlEWKV9kPqXJnulxmoReHXHigP45pj/8m9jUzrQdagINl1uIOBkq5SMDccRfTA==")); + //Assert.That(report.Signature, Is.EqualTo("IEbO4i0Jn54iHUzlwotHf9aw/fZIHY+dztY9cMRkWjVVo6AiYtihWR0mip793gRrWHOxHVobCpa4l5svRk16mBR+YAOrs3KNRVTzrl4+wL21e1u9zFuPNrHLtFeul+taJxV8ciA7zEgD7LMle9CcR/Vfm8BZ9mmD5W/DjsCYLCdVXfN4iRMlz+eW50mOHty21yJ0pOiYBooaN2EJexVY4Q+5FMyAkm0wucEPFyaQB6+SfcS37fEm807B7sXhtUPiW+einqDOX6uYF+MuXxUn1u9LxlEWKV9kPqXJnulxmoReHXHigP45pj/8m9jUzrQdagINl1uIOBkq5SMDccRfTA==")); - Assert.That(ValidateReport(report)); + Assert.That(validationResult.IsValid, Is.True); } #if !DEBUG @@ -176,22 +173,23 @@ public void ValidateReport(string reportFile) //Arrange var reportString = File.ReadAllText(reportFile); - var report = JsonSerializer.Deserialize(reportString, SerializationOptions.NotIndentedWithNoEscaping); + var _ = Parse(reportString, out var validationResult); //Assert - Assert.That(ValidateReport(report)); + Assert.That(validationResult.IsValid, Is.True); } + public static Report? Parse(string rawJson, out ReportValidationResult validationResult) + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawJson)); + return ValidatingReportReader.ReadAndValidate(stream, out validationResult); + } - string GetResource(string resourceName) + Stream? GetResourceStream(string resourceName) { var assembly = typeof(LicensingComponent_Report_Signature_Tests).Assembly; var assemblyName = assembly.GetName().Name; - using (var stream = assembly.GetManifestResourceStream($"{assemblyName}.{resourceName}")) - using (var reader = new StreamReader(stream!)) - { - return reader.ReadToEnd(); - } + return assembly.GetManifestResourceStream($"{assemblyName}.{resourceName}"); } SignedReport CreateReport() @@ -228,28 +226,4 @@ SignedReport CreateReport() Signature = Signature.SignReport(reportData) }; } - - bool ValidateReport(SignedReport? signedReport) - { - if (signedReport == null) - { - return false; - } - - try - { - var validationResult = new ReportValidationResult(signedReport); - return validationResult.IsValid; - } - catch (NoPrivateKeyException) - { -#if DEBUG - // We don't distribute the private key to do local testing, this only happens during CI - Assert.Ignore("Ignoring report validation as this is a DEBUG build and the THROUGHPUT_REPORT_PRIVATEKEY_PEM environment variable is missing."); - return true; -#endif - - throw; - } - } } \ No newline at end of file diff --git a/src/Particular.LicensingComponent.Report/ReportValidationResult.cs b/src/Particular.LicensingComponent.Report/ReportValidationResult.cs index a0aa373..5bbbd08 100644 --- a/src/Particular.LicensingComponent.Report/ReportValidationResult.cs +++ b/src/Particular.LicensingComponent.Report/ReportValidationResult.cs @@ -1,8 +1,5 @@ namespace Particular.LicensingComponent.Report; -using System.Security.Cryptography; -using System.Text.Json; - /// /// Report validator /// @@ -14,52 +11,12 @@ public class ReportValidationResult public bool IsValid { get; internal set; } /// - /// Report id of the report being validated + /// Reason why the report is invalid, if applicable /// - public string ReportId { get; } - - internal ReportValidationResult(string reportId) - { - ReportId = reportId; - IsValid = true; - } + public string? InvalidReason { get; internal set; } /// - /// Method that tests whether the signed report is valid + /// Report id of the report being validated /// - /// - /// - public ReportValidationResult(SignedReport signedReport) - { - var reserializedReportBytes = JsonSerializer.SerializeToUtf8Bytes(signedReport.ReportData, SerializationOptions.NotIndentedWithNoEscaping); - - ReportId = Convert.ToHexString(SHA1.HashData(reserializedReportBytes)); - - if (signedReport?.Signature is null) - { - return; - } - - var pemData = Environment.GetEnvironmentVariable("THROUGHPUT_REPORT_PRIVATEKEY_PEM") ?? throw new NoPrivateKeyException(ReportId); - - byte[] signatureBytes; - try - { - signatureBytes = Convert.FromBase64String(signedReport.Signature); - } - catch (FormatException) - { - return; - } - - var correctSignature = Convert.ToBase64String(SHA512.HashData(reserializedReportBytes)); - - using var rsa = RSA.Create(); - - rsa.ImportFromPem(pemData); - var decryptedHash = rsa.Decrypt(signatureBytes, RSAEncryptionPadding.Pkcs1); - var decryptedSignature = Convert.ToBase64String(decryptedHash); - - IsValid = correctSignature == decryptedSignature; - } + public string? ReportId { get; internal set; } } diff --git a/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs b/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs new file mode 100644 index 0000000..cf47af0 --- /dev/null +++ b/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs @@ -0,0 +1,119 @@ +namespace Particular.LicensingComponent.Report; + +using System.Buffers; +using System.Security.Cryptography; +using System.Text.Encodings.Web; +using System.Text.Json; + +/// +/// Helper class to read and validate a signed report +/// +public static class ValidatingReportReader +{ + /// + /// Reads and validates a signed report from a stream + /// + /// + /// + public static Report? ReadAndValidate(Stream stream, out ReportValidationResult validationResult) + { + using var doc = JsonDocument.Parse(stream); + var root = doc.RootElement; + + if (!root.TryGetProperty(nameof(SignedReport.ReportData), out var reportDataElement)) + { + validationResult = new ReportValidationResult + { + IsValid = false, + InvalidReason = "Could not find ReportData property" + }; + return null; + } + + validationResult = ValidateSignature(root, reportDataElement); + + return reportDataElement.Deserialize(); + } + + static ReportValidationResult ValidateSignature(JsonElement root, JsonElement reportDataElement) + { + // NOTE: In .NET 9 we can get the bytes directly from the JsonElement, but in .NET 8 we have to get the raw text and convert it to bytes ourselves. + var reportBytes = MinifyJsonElementToBytes(reportDataElement); + + var reportId = Convert.ToHexString(SHA1.HashData(reportBytes)); + var validationResult = new ReportValidationResult + { + ReportId = reportId + }; + + if (!root.TryGetProperty(nameof(SignedReport.Signature), out var signatureElement)) + { + validationResult.InvalidReason = "Could not find signature"; + return validationResult; + } + + var signature = signatureElement.GetString() ?? string.Empty; + if (string.IsNullOrWhiteSpace(signature)) + { + validationResult.InvalidReason = "Signature is empty"; + return validationResult; + } + + // Validate signature + var pemData = Environment.GetEnvironmentVariable("THROUGHPUT_REPORT_PRIVATEKEY_PEM"); + if (string.IsNullOrWhiteSpace(pemData)) + { + validationResult.InvalidReason = "No private key available to validate signature"; + return validationResult; + } + + byte[] signatureBytes; + try + { + signatureBytes = Convert.FromBase64String(signature); + } + catch (FormatException) + { + // NOTE: The signature is not valid base64, which means it is invalid. We will return false for IsValid in this case. + validationResult.InvalidReason = "Signature could not be decoded"; + return validationResult; + } + + var correctSignature = Convert.ToBase64String(SHA512.HashData(reportBytes)); + + using var rsa = RSA.Create(); + + rsa.ImportFromPem(pemData); + var decryptedHash = rsa.Decrypt(signatureBytes, RSAEncryptionPadding.Pkcs1); + var decryptedSignature = Convert.ToBase64String(decryptedHash); + + if (correctSignature == decryptedSignature) + { + validationResult.IsValid = true; + } + else + { + validationResult.InvalidReason = "Signature does not match report data"; + } + + return validationResult; + } + + static readonly JsonWriterOptions MinifyOptions = new() + { + Indented = false, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + static byte[] MinifyJsonElementToBytes(JsonElement element) + { + var bufferWriter = new ArrayBufferWriter(initialCapacity: 4096); + using var memoryStream = new MemoryStream(); + using (var writer = new Utf8JsonWriter(bufferWriter, MinifyOptions)) + { + element.WriteTo(writer); + } + + return bufferWriter.WrittenMemory.ToArray(); + } +} From e642f1f76471520f7e3b7ad2656871ae5fc1b4f5 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 27 Aug 2026 14:58:42 +0800 Subject: [PATCH 2/4] Fix approval test --- .../ApprovalFiles/ApiApprovals.Approve.approved.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt index 0a1c147..8043d99 100644 --- a/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt +++ b/src/Particular.LicensingComponent.Report.UnitTests/ApprovalFiles/ApiApprovals.Approve.approved.txt @@ -58,9 +58,10 @@ namespace Particular.LicensingComponent.Report } public class ReportValidationResult { - public ReportValidationResult(Particular.LicensingComponent.Report.SignedReport signedReport) { } + public ReportValidationResult() { } + public string? InvalidReason { get; } public bool IsValid { get; } - public string ReportId { get; } + public string? ReportId { get; } } public static class SerializationOptions { @@ -79,7 +80,7 @@ namespace Particular.LicensingComponent.Report } public static class ValidatingReportReader { - public static Particular.LicensingComponent.Report.Report? Read(System.IO.Stream stream, out Particular.LicensingComponent.Report.ReportValidationResult validationResult) { } + public static Particular.LicensingComponent.Report.Report? ReadAndValidate(System.IO.Stream stream, out Particular.LicensingComponent.Report.ReportValidationResult validationResult) { } } } namespace Particular.LicensingComponent.Report.Utility From b5caaee3ccb310065350e031e3e804f3be57e6ed Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 28 Aug 2026 14:09:02 +0800 Subject: [PATCH 3/4] Clean up --- ...censingComponent_Report_Signature_Tests.cs | 3 +- .../ReportValidationResult.cs | 14 ++++++++ .../ValidatingReportReader.cs | 35 +++++-------------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs index 3fa8614..343dbe8 100644 --- a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs +++ b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs @@ -126,7 +126,6 @@ public void Should_be_able_to_read_a_V2_report() Assert.That(data.EnvironmentInformation.EnvironmentData.ContainsKey("MonitoringEnabled"), Is.True); Assert.That(data.EnvironmentInformation.EnvironmentData["MonitoringEnabled"], Is.EqualTo("True")); - //Assert.That(report.Signature, Is.EqualTo("IEbO4i0Jn54iHUzlwotHf9aw/fZIHY+dztY9cMRkWjVVo6AiYtihWR0mip793gRrWHOxHVobCpa4l5svRk16mBR+YAOrs3KNRVTzrl4+wL21e1u9zFuPNrHLtFeul+taJxV8ciA7zEgD7LMle9CcR/Vfm8BZ9mmD5W/DjsCYLCdVXfN4iRMlz+eW50mOHty21yJ0pOiYBooaN2EJexVY4Q+5FMyAkm0wucEPFyaQB6+SfcS37fEm807B7sXhtUPiW+einqDOX6uYF+MuXxUn1u9LxlEWKV9kPqXJnulxmoReHXHigP45pj/8m9jUzrQdagINl1uIOBkq5SMDccRfTA==")); Assert.That(validationResult.IsValid, Is.True); } @@ -179,7 +178,7 @@ public void ValidateReport(string reportFile) Assert.That(validationResult.IsValid, Is.True); } - public static Report? Parse(string rawJson, out ReportValidationResult validationResult) + static Report? Parse(string rawJson, out ReportValidationResult validationResult) { using var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawJson)); return ValidatingReportReader.ReadAndValidate(stream, out validationResult); diff --git a/src/Particular.LicensingComponent.Report/ReportValidationResult.cs b/src/Particular.LicensingComponent.Report/ReportValidationResult.cs index 5bbbd08..321539d 100644 --- a/src/Particular.LicensingComponent.Report/ReportValidationResult.cs +++ b/src/Particular.LicensingComponent.Report/ReportValidationResult.cs @@ -19,4 +19,18 @@ public class ReportValidationResult /// Report id of the report being validated /// public string? ReportId { get; internal set; } + + + internal static ReportValidationResult Valid(string reportId) => new() + { + IsValid = true, + ReportId = reportId + }; + + internal static ReportValidationResult Invalid(string? reportId, string reason) => new() + { + IsValid = false, + InvalidReason = reason, + ReportId = reportId + }; } diff --git a/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs b/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs index cf47af0..0d4cfc4 100644 --- a/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs +++ b/src/Particular.LicensingComponent.Report/ValidatingReportReader.cs @@ -22,11 +22,7 @@ public static class ValidatingReportReader if (!root.TryGetProperty(nameof(SignedReport.ReportData), out var reportDataElement)) { - validationResult = new ReportValidationResult - { - IsValid = false, - InvalidReason = "Could not find ReportData property" - }; + validationResult = ReportValidationResult.Invalid(null, "Could not find ReportData property"); return null; } @@ -41,30 +37,23 @@ static ReportValidationResult ValidateSignature(JsonElement root, JsonElement re var reportBytes = MinifyJsonElementToBytes(reportDataElement); var reportId = Convert.ToHexString(SHA1.HashData(reportBytes)); - var validationResult = new ReportValidationResult - { - ReportId = reportId - }; if (!root.TryGetProperty(nameof(SignedReport.Signature), out var signatureElement)) { - validationResult.InvalidReason = "Could not find signature"; - return validationResult; + return ReportValidationResult.Invalid(reportId, "Could not find signature"); } var signature = signatureElement.GetString() ?? string.Empty; if (string.IsNullOrWhiteSpace(signature)) { - validationResult.InvalidReason = "Signature is empty"; - return validationResult; + return ReportValidationResult.Invalid(reportId, "Signature is empty"); } // Validate signature var pemData = Environment.GetEnvironmentVariable("THROUGHPUT_REPORT_PRIVATEKEY_PEM"); if (string.IsNullOrWhiteSpace(pemData)) { - validationResult.InvalidReason = "No private key available to validate signature"; - return validationResult; + return ReportValidationResult.Invalid(reportId, "No private key available to validate signature"); } byte[] signatureBytes; @@ -75,8 +64,7 @@ static ReportValidationResult ValidateSignature(JsonElement root, JsonElement re catch (FormatException) { // NOTE: The signature is not valid base64, which means it is invalid. We will return false for IsValid in this case. - validationResult.InvalidReason = "Signature could not be decoded"; - return validationResult; + return ReportValidationResult.Invalid(reportId, "Signature could not be decoded"); } var correctSignature = Convert.ToBase64String(SHA512.HashData(reportBytes)); @@ -87,16 +75,9 @@ static ReportValidationResult ValidateSignature(JsonElement root, JsonElement re var decryptedHash = rsa.Decrypt(signatureBytes, RSAEncryptionPadding.Pkcs1); var decryptedSignature = Convert.ToBase64String(decryptedHash); - if (correctSignature == decryptedSignature) - { - validationResult.IsValid = true; - } - else - { - validationResult.InvalidReason = "Signature does not match report data"; - } - - return validationResult; + return (correctSignature == decryptedSignature) + ? ReportValidationResult.Valid(reportId) + : ReportValidationResult.Invalid(reportId, "Signature does not match report data"); } static readonly JsonWriterOptions MinifyOptions = new() From ef5e4b74e271cc13e3fad82bfc2947951cbc923c Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 28 Aug 2026 14:15:27 +0800 Subject: [PATCH 4/4] Restore ability to run tests locally without private key --- ...censingComponent_Report_Signature_Tests.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs index 343dbe8..aae8ec0 100644 --- a/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs +++ b/src/Particular.LicensingComponent.Report.UnitTests/LicensingComponent_Report_Signature_Tests.cs @@ -38,7 +38,7 @@ public void Should_deserialize_report_with_signature() var _ = Parse(reportString, out var validationResult); //Assert - Assert.That(validationResult.IsValid, Is.True); + Assert.That(Validate(validationResult)); } [Test] @@ -53,7 +53,7 @@ public void Should_not_allow_tempering_with_report() var _ = Parse(reportString, out var validationResult); //Assert - Assert.That(validationResult.IsValid, Is.False); + Assert.That(Validate(validationResult), Is.False); } [Test] @@ -84,7 +84,7 @@ public void Should_be_able_to_read_a_V1_report() Assert.That(data.TotalQueues, Is.EqualTo(7)); - Assert.That(validationResult.IsValid, Is.True); + Assert.That(Validate(validationResult)); } [Test] @@ -126,8 +126,7 @@ public void Should_be_able_to_read_a_V2_report() Assert.That(data.EnvironmentInformation.EnvironmentData.ContainsKey("MonitoringEnabled"), Is.True); Assert.That(data.EnvironmentInformation.EnvironmentData["MonitoringEnabled"], Is.EqualTo("True")); - - Assert.That(validationResult.IsValid, Is.True); + Assert.That(Validate(validationResult)); } #if !DEBUG @@ -178,6 +177,23 @@ public void ValidateReport(string reportFile) Assert.That(validationResult.IsValid, Is.True); } + static bool Validate(ReportValidationResult validationResult) + { + if (validationResult.IsValid) + { + return true; + } + +#if DEBUG + if (validationResult.InvalidReason == "No private key available to validate signature") + { + Assert.Ignore("Ignoring report validation as this is a DEBUG build and the THROUGHPUT_REPORT_PRIVATEKEY_PEM environment variable is missing."); + return true; + } +#endif + return false; + } + static Report? Parse(string rawJson, out ReportValidationResult validationResult) { using var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawJson));