diff --git a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs index 2841c8f5a..c1ef8089b 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs +++ b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs @@ -68,6 +68,11 @@ public GlobalDiscoveryServerClient( AdminCredentials = adminUserIdentity; } + /// + /// 1MB default max trust list size + /// + private const int kDefaultMaxTrustListSize = 1 * 1024 * 1024; + /// /// Gets the application. /// @@ -1442,7 +1447,8 @@ public TrustListDataType ReadTrustList(NodeId trustListId) /// /// Reads the trust list. /// - public async Task ReadTrustListAsync(NodeId trustListId, CancellationToken ct = default) + /// + public async Task ReadTrustListAsync(NodeId trustListId, long maxTrustListSize = 0, CancellationToken ct = default) { ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false); @@ -1456,6 +1462,14 @@ public async Task ReadTrustListAsync(NodeId trustListId, Canc using var ostrm = new MemoryStream(); try { + // Use a reasonable maximum size limit for trust lists + if (maxTrustListSize == 0) + { + maxTrustListSize = kDefaultMaxTrustListSize; + } + + long totalBytesRead = 0; + while (true) { const int length = 4096; @@ -1468,6 +1482,17 @@ public async Task ReadTrustListAsync(NodeId trustListId, Canc length).ConfigureAwait(false); byte[] bytes = (byte[])outputArguments[0]; + + // Validate total size before writing + totalBytesRead += bytes.Length; + if (totalBytesRead > maxTrustListSize) + { + throw ServiceResultException.Create( + StatusCodes.BadEncodingLimitsExceeded, + "Trust list size exceeds maximum allowed size of {0} bytes", + maxTrustListSize); + } + ostrm.Write(bytes, 0, bytes.Length); if (length != bytes.Length) diff --git a/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs b/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs index e2927da54..d70c73a52 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs +++ b/Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs @@ -64,6 +64,11 @@ public ServerPushConfigurationClient( }; } + /// + /// 1MB default max trust list size + /// + private const int kDefaultMaxTrustListSize = 1 * 1024 * 1024; + public NodeId DefaultApplicationGroup { get; private set; } public NodeId DefaultHttpsGroup { get; private set; } public NodeId DefaultUserTokenGroup { get; private set; } @@ -462,8 +467,10 @@ public TrustListDataType ReadTrustList(TrustListMasks masks = TrustListMasks.All /// /// Reads the trust list. /// + /// public async Task ReadTrustListAsync( TrustListMasks masks = TrustListMasks.All, + long maxTrustListSize = 0, CancellationToken ct = default) { ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false); @@ -489,6 +496,14 @@ public async Task ReadTrustListAsync( using var ostrm = new MemoryStream(); try { + // Use a reasonable maximum size limit for trust lists + if (maxTrustListSize == 0) + { + maxTrustListSize = kDefaultMaxTrustListSize; + } + + long totalBytesRead = 0; + while (true) { const int length = 256; @@ -510,6 +525,17 @@ public async Task ReadTrustListAsync( .ConfigureAwait(false); byte[] bytes = (byte[])outputArguments[0]; + + // Validate total size before reading + totalBytesRead += bytes.Length; + if (totalBytesRead > maxTrustListSize) + { + throw ServiceResultException.Create( + StatusCodes.BadEncodingLimitsExceeded, + "Trust list size exceeds maximum allowed size of {0} bytes", + maxTrustListSize); + } + ostrm.Write(bytes, 0, bytes.Length); if (length != bytes.Length) @@ -581,7 +607,8 @@ public bool UpdateTrustList(TrustListDataType trustList) /// /// Updates the trust list. /// - public async Task UpdateTrustListAsync(TrustListDataType trustList, CancellationToken ct = default) + /// + public async Task UpdateTrustListAsync(TrustListDataType trustList, long maxTrustListSize = 0, CancellationToken ct = default) { ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false); IUserIdentity oldUser = await ElevatePermissionsAsync(session, ct).ConfigureAwait(false); @@ -595,6 +622,22 @@ public async Task UpdateTrustListAsync(TrustListDataType trustList, Cancel } strm.Position = 0; + // Use a reasonable maximum size limit for trust lists + if (maxTrustListSize == 0) + { + maxTrustListSize = kDefaultMaxTrustListSize; + } + + // Validate trust list size before attempting to write + if (strm.Length > maxTrustListSize) + { + throw ServiceResultException.Create( + StatusCodes.BadEncodingLimitsExceeded, + "Trust list size {0} exceeds maximum allowed size of {1} bytes", + strm.Length, + maxTrustListSize); + } + System.Collections.Generic.IList outputArguments = await session.CallAsync( ExpandedNodeId.ToNodeId( Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup_TrustList, diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index fde4bcd1a..8eae6cba7 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -319,7 +319,8 @@ .. configuration.ServerConfiguration.SupportedPrivateKeyFormats certGroup.IssuerStore, new TrustList.SecureAccess(HasApplicationSecureAdminAccess), new TrustList.SecureAccess(HasApplicationSecureAdminAccess), - Server.Telemetry); + Server.Telemetry, + m_configuration.ServerConfiguration.MaxTrustListSize); certGroup.Node.ClearChangeMasks(systemContext, true); } diff --git a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs index 4cdd06754..481eb8bcf 100644 --- a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs +++ b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs @@ -43,7 +43,12 @@ namespace Opc.Ua.Server /// public class TrustList { - private const int kDefaultTrustListCapacity = 0x10000; + private const int kDefaultTrustListCapacity = 1 * 1024 * 1024; + + /// + /// 1MB default max trust list size + /// + private const int kDefaultMaxTrustListSize = 1 * 1024 * 1024; /// /// Initialize the trustlist with default values. @@ -54,7 +59,8 @@ public TrustList( CertificateStoreIdentifier issuerListStore, SecureAccess readAccess, SecureAccess writeAccess, - ITelemetryContext telemetry) + ITelemetryContext telemetry, + int maxTrustListSize = 0) { m_telemetry = telemetry; m_logger = telemetry.CreateLogger(); @@ -63,6 +69,8 @@ public TrustList( m_issuerStore = issuerListStore; m_readAccess = readAccess; m_writeAccess = writeAccess; + // If maxTrustListSize is 0 (unlimited), use a sensible default limit + m_maxTrustListSize = maxTrustListSize > 0 ? maxTrustListSize : kDefaultMaxTrustListSize; node.Open.OnCall = new OpenMethodStateMethodCallHandler(Open); node.OpenWithMasks.OnCall @@ -155,6 +163,7 @@ private ServiceResult Open( m_readMode = mode == OpenFileMode.Read; m_sessionId = (context as ISessionSystemContext)?.SessionId; fileHandle = ++m_fileHandle; + m_totalBytesProcessed = 0; // Reset counter for new file operation var trustList = new TrustListDataType { SpecifiedLists = (uint)masks }; @@ -264,11 +273,22 @@ private ServiceResult Read( "Invalid file handle"); } + // Check if we would exceed the maximum trust list size + if (m_totalBytesProcessed + length > m_maxTrustListSize) + { + return ServiceResult.Create( + StatusCodes.BadEncodingLimitsExceeded, + "Trust list size exceeds maximum allowed size of {0} bytes", + m_maxTrustListSize); + } + data = new byte[length]; int bytesRead = m_strm.Read(data, 0, length); Debug.Assert(bytesRead >= 0); + m_totalBytesProcessed += bytesRead; + if (bytesRead < length) { byte[] bytes = new byte[bytesRead]; @@ -302,7 +322,17 @@ private ServiceResult Write( return StatusCodes.BadInvalidArgument; } + // Check if we would exceed the maximum trust list size + if (m_totalBytesProcessed + data.Length > m_maxTrustListSize) + { + return ServiceResult.Create( + StatusCodes.BadEncodingLimitsExceeded, + "Trust list size exceeds maximum allowed size of {0} bytes", + m_maxTrustListSize); + } + m_strm.Write(data, 0, data.Length); + m_totalBytesProcessed += data.Length; } return ServiceResult.Good; @@ -825,5 +855,7 @@ private void HasSecureWriteAccess(ISystemContext context) private readonly TrustListState m_node; private MemoryStream m_strm; private bool m_readMode; + private readonly int m_maxTrustListSize; + private long m_totalBytesProcessed; } } diff --git a/Tests/Opc.Ua.Gds.Tests/Common.cs b/Tests/Opc.Ua.Gds.Tests/Common.cs index da14daddf..23c499375 100644 --- a/Tests/Opc.Ua.Gds.Tests/Common.cs +++ b/Tests/Opc.Ua.Gds.Tests/Common.cs @@ -399,7 +399,8 @@ public static string PatchOnlyGDSEndpointUrlPort(string url, int port) public static async Task StartGDSAsync( bool clean, - string storeType = CertificateStoreType.Directory) + string storeType = CertificateStoreType.Directory, + int maxTrustListSize = 0) { GlobalDiscoveryTestServer server = null; int testPort = ServerFixtureUtils.GetNextFreeIPPort(); @@ -409,7 +410,7 @@ public static async Task StartGDSAsync( { try { - server = new GlobalDiscoveryTestServer(true, NUnitTelemetryContext.Create(true)); + server = new GlobalDiscoveryTestServer(true, NUnitTelemetryContext.Create(true), maxTrustListSize); await server.StartServerAsync(clean, testPort, storeType).ConfigureAwait(false); } catch (ServiceResultException sre) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs index 5816d8d00..f368e58c2 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs @@ -47,11 +47,12 @@ public class GlobalDiscoveryTestServer public ApplicationConfiguration Config { get; private set; } public int BasePort { get; private set; } - public GlobalDiscoveryTestServer(bool autoAccept, ITelemetryContext telemetry) + public GlobalDiscoveryTestServer(bool autoAccept, ITelemetryContext telemetry, int maxTrustListSize) { s_autoAccept = autoAccept; m_telemetry = telemetry; m_logger = telemetry.CreateLogger(); + m_maxTrustListSize = maxTrustListSize; } public async Task StartServerAsync( @@ -79,7 +80,7 @@ public async Task StartServerAsync( }; BasePort = basePort; - Config = await LoadAsync(Application, basePort).ConfigureAwait(false); + Config = await LoadAsync(Application, basePort, m_maxTrustListSize).ConfigureAwait(false); if (clean) { @@ -110,7 +111,7 @@ await TestUtils Config.SecurityConfiguration.RejectedCertificateStore, m_telemetry) .ConfigureAwait(false); - Config = await LoadAsync(Application, basePort).ConfigureAwait(false); + Config = await LoadAsync(Application, basePort, m_maxTrustListSize).ConfigureAwait(false); } // check the application certificate. @@ -237,7 +238,8 @@ private static void RegisterDefaultUsers(IUserDatabase userDatabase) private static async Task LoadAsync( ApplicationInstance application, - int basePort) + int basePort, + int maxTrustListSize) { #if !USE_FILE_CONFIG // load the application configuration. @@ -313,6 +315,9 @@ private static async Task LoadAsync( .CreateAsync() .ConfigureAwait(false); #endif + + config.ServerConfiguration.MaxTrustListSize = maxTrustListSize; + TestUtils.PatchBaseAddressesPorts(config, basePort); return config; } @@ -320,5 +325,6 @@ private static async Task LoadAsync( private static bool s_autoAccept; private readonly ITelemetryContext m_telemetry; private readonly ILogger m_logger; + private readonly int m_maxTrustListSize = 0; } } diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs index 00db3c87e..586be09e7 100644 --- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -1053,6 +1053,7 @@ private async Task RegisterPushServerApplicationAsync( NodeId trustListId = await m_gdsClient.GDSClient.GetTrustListAsync(id, null, ct).ConfigureAwait(false); TrustListDataType trustList = await m_gdsClient.GDSClient.ReadTrustListAsync( trustListId, + 0, ct).ConfigureAwait(false); bool result = await AddTrustListToStoreAsync( m_gdsClient.Configuration.SecurityConfiguration, diff --git a/Tests/Opc.Ua.Gds.Tests/TrustListValidationTest.cs b/Tests/Opc.Ua.Gds.Tests/TrustListValidationTest.cs new file mode 100644 index 000000000..06b71ea0e --- /dev/null +++ b/Tests/Opc.Ua.Gds.Tests/TrustListValidationTest.cs @@ -0,0 +1,305 @@ +/* ======================================================================== + * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using NUnit.Framework; +using Opc.Ua.Tests; +using Assert = NUnit.Framework.Legacy.ClassicAssert; + +namespace Opc.Ua.Gds.Tests +{ + [TestFixture] + [Category("GDSPush")] + [Category("GDS")] + [SetCulture("en-us")] + [SetUICulture("en-us")] + [NonParallelizable] + public class TrustListValidationTest + { + private GlobalDiscoveryTestServer m_server; + private ServerConfigurationPushTestClient m_pushClient; + private ITelemetryContext m_telemetry; + + [OneTimeSetUp] + public async Task OneTimeSetUpAsync() + { + // Start GDS server + m_telemetry = NUnitTelemetryContext.Create(); + m_server = await TestUtils.StartGDSAsync(true, CertificateStoreType.Directory).ConfigureAwait(false); + + // Load client + m_pushClient = new ServerConfigurationPushTestClient(true, m_telemetry); + await m_pushClient.LoadClientConfigurationAsync(m_server.BasePort).ConfigureAwait(false); + + // Set admin credentials and connect + await m_pushClient.ConnectAsync(SecurityPolicies.Aes256_Sha256_RsaPss).ConfigureAwait(false); + m_pushClient.PushClient.AdminCredentials = m_pushClient.SysAdminUser; + } + + [OneTimeTearDown] + public async Task OneTimeTearDownAsync() + { + try + { + await m_pushClient.DisconnectClientAsync().ConfigureAwait(false); + await m_server.StopServerAsync().ConfigureAwait(false); + } + catch + { + } + finally + { + m_pushClient?.Dispose(); + m_pushClient = null; + m_server = null; + } + } + + /// + /// Test that normal-sized trust lists work correctly. + /// + [Test] + [Order(100)] + public async Task NormalSizeTrustListAsync() + { + // Create a normal-sized trust list + var normalTrustList = new TrustListDataType + { + SpecifiedLists = (uint)TrustListMasks.TrustedCertificates, + TrustedCertificates = [], + TrustedCrls = [], + IssuerCertificates = [], + IssuerCrls = [] + }; + + // Add a reasonable number of certificates (10) + for (int i = 0; i < 10; i++) + { + using X509Certificate2 cert = CertificateFactory + .CreateCertificate($"urn:test:cert{i}", $"NormalCert{i}", $"CN=NormalCert{i}, O=OPC Foundation", null) + .CreateForRSA(); + normalTrustList.TrustedCertificates.Add(cert.RawData); + } + + // This should succeed + bool requireReboot = await m_pushClient.PushClient.UpdateTrustListAsync(normalTrustList).ConfigureAwait(false); + Assert.False(requireReboot); + + // Read it back to verify + TrustListDataType readTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); + Assert.IsNotNull(readTrustList); + Assert.AreEqual(normalTrustList.TrustedCertificates.Count, readTrustList.TrustedCertificates.Count); + } + + /// + /// Test that writing a trust list exceeding the size limit fails. + /// + [Test] + [Order(200)] + public async Task WriteTrustListExceedsSizeLimitAsync() + { + // Create a trust list with a few certificates + var oversizedTrustList = new TrustListDataType + { + SpecifiedLists = (uint)TrustListMasks.TrustedCertificates, + TrustedCertificates = [], + TrustedCrls = [], + IssuerCertificates = [], + IssuerCrls = [] + }; + + for (int i = 0; i < 20; i++) + { + using X509Certificate2 cert = CertificateFactory + .CreateCertificate($"urn:test:cert{i}", $"TestCert{i}", $"CN=TestCert{i}, O=OPC Foundation", null) + .SetRSAKeySize(2048) + .CreateForRSA(); + oversizedTrustList.TrustedCertificates.Add(cert.RawData); + } + + // Calculate the encoded size + long encodedSize = GetEncodedSize(oversizedTrustList); + TestContext.Out.WriteLine($"Generated trust list with encoded size: {encodedSize} bytes."); + + // Set the client's max trust list size to be smaller than the actual size + uint maxTrustListSize = (uint)encodedSize - 1; + TestContext.Out.WriteLine($"Client MaxTrustListSize set to: {maxTrustListSize}"); + + // This should throw ServiceResultException with BadEncodingLimitsExceeded + ServiceResultException ex = Assert.ThrowsAsync( + async () => await m_pushClient.PushClient.UpdateTrustListAsync(oversizedTrustList, maxTrustListSize).ConfigureAwait(false)); + + Assert.IsNotNull(ex); + Assert.AreEqual(StatusCodes.BadEncodingLimitsExceeded, ex.StatusCode); + TestContext.Out.WriteLine($"Expected exception caught: {ex.Message}"); + } + + /// + /// Test boundary condition - trust list just under the limit. + /// + [Test] + [Order(300)] + public async Task TrustListJustUnderLimitAsync() + { + var boundaryTrustList = new TrustListDataType + { + SpecifiedLists = (uint)TrustListMasks.TrustedCertificates, + TrustedCertificates = [], + TrustedCrls = [], + IssuerCertificates = [], + IssuerCrls = [] + }; + + for (int i = 0; i < 20; i++) + { + using X509Certificate2 cert = CertificateFactory + .CreateCertificate($"urn:test:cert{i}", $"BoundaryCert{i}", $"CN=BoundaryCert{i}, O=OPC Foundation", null) + .SetRSAKeySize(2048) + .CreateForRSA(); + boundaryTrustList.TrustedCertificates.Add(cert.RawData); + } + + // Calculate the encoded size + long encodedSize = GetEncodedSize(boundaryTrustList); + TestContext.Out.WriteLine($"Generated trust list with encoded size: {encodedSize} bytes."); + + // Set the client's max trust list size to be exactly the encoded size (should pass) + uint maxTrustListSize = (uint)encodedSize; + TestContext.Out.WriteLine($"Client MaxTrustListSize set to: {maxTrustListSize}"); + + // This should succeed + bool requireReboot = await m_pushClient.PushClient.UpdateTrustListAsync(boundaryTrustList, maxTrustListSize).ConfigureAwait(false); + Assert.False(requireReboot); + + // Read it back + TrustListDataType readTrustList = await m_pushClient.PushClient.ReadTrustListAsync().ConfigureAwait(false); + Assert.IsNotNull(readTrustList); + Assert.AreEqual(boundaryTrustList.TrustedCertificates.Count, readTrustList.TrustedCertificates.Count); + } + + /// + /// Test reading and writing with a custom MaxTrustListSize set in the ServerConfiguration. + /// + [Test] + [Order(400)] + public async Task ReadWriteWithCustomServerMaxTrustListSizeAsync() + { + // Define a custom size limit for the server + const int customMaxTrustListSize = 8192; // 8 KB + + // Update server configuration + await m_server.StopServerAsync().ConfigureAwait(false); + m_server = await TestUtils.StartGDSAsync(false, CertificateStoreType.Directory, customMaxTrustListSize).ConfigureAwait(false); + await m_pushClient.LoadClientConfigurationAsync(m_server.BasePort).ConfigureAwait(false); + await m_pushClient.ConnectAsync(SecurityPolicies.Aes256_Sha256_RsaPss).ConfigureAwait(false); + m_pushClient.PushClient.AdminCredentials = m_pushClient.SysAdminUser; + + TestContext.Out.WriteLine($"Server MaxTrustListSize set to: {customMaxTrustListSize}"); + + try + { + // 1. Test writing a trust list that exceeds the server's limit + var oversizedTrustList = new TrustListDataType + { + SpecifiedLists = (uint)TrustListMasks.TrustedCertificates, + TrustedCertificates = [] + }; + + long currentSize = 0; + int certCount = 0; + while (currentSize <= customMaxTrustListSize) + { + using X509Certificate2 cert = + CertificateFactory.CreateCertificate($"urn:test:oversized{certCount}", "Oversized", "CN=Oversized", null).CreateForRSA(); + oversizedTrustList.TrustedCertificates.Add(cert.RawData); + currentSize = GetEncodedSize(oversizedTrustList); + certCount++; + } + TestContext.Out.WriteLine($"Oversized trust list created with {certCount} certs and size {currentSize}"); + + ServiceResultException ex = Assert.ThrowsAsync(async () => + await m_pushClient.PushClient.UpdateTrustListAsync(oversizedTrustList).ConfigureAwait(false)); + Assert.AreEqual(StatusCodes.BadEncodingLimitsExceeded, ex.StatusCode); + TestContext.Out.WriteLine("Successfully caught exception for writing oversized trust list to server."); + + // 2. Test writing a valid trust list (under the server's limit) + var validTrustList = new TrustListDataType + { + SpecifiedLists = (uint)TrustListMasks.TrustedCertificates, + TrustedCertificates = [] + }; + for (int i = 0; i < 2; i++) + { + using X509Certificate2 cert = CertificateFactory.CreateCertificate($"urn:test:valid{i}", "Valid", "CN=Valid", null).CreateForRSA(); + validTrustList.TrustedCertificates.Add(cert.RawData); + } + long validSize = GetEncodedSize(validTrustList); + Assert.True(validSize < customMaxTrustListSize); + TestContext.Out.WriteLine($"Valid trust list created with size {validSize}"); + + bool reboot = await m_pushClient.PushClient.UpdateTrustListAsync(validTrustList).ConfigureAwait(false); + Assert.False(reboot); + TestContext.Out.WriteLine("Successfully wrote valid trust list to server."); + + // 3. Test reading the trust list with a client limit that is too small + ServiceResultException exRead = Assert.ThrowsAsync(async () => + await m_pushClient.PushClient.ReadTrustListAsync(TrustListMasks.TrustedCertificates, (uint)validSize - 1).ConfigureAwait(false)); + Assert.AreEqual(StatusCodes.BadEncodingLimitsExceeded, exRead.StatusCode); + TestContext.Out.WriteLine("Successfully caught exception for reading trust list with small client limit."); + + // 4. Test reading with a sufficient client limit + TrustListDataType readTrustList = await m_pushClient.PushClient + .ReadTrustListAsync(TrustListMasks.TrustedCertificates, (uint)validSize).ConfigureAwait(false); + Assert.IsNotNull(readTrustList); + Assert.AreEqual(validTrustList.TrustedCertificates.Count, readTrustList.TrustedCertificates.Count); + TestContext.Out.WriteLine("Successfully read trust list with sufficient client limit."); + } + finally + { + // Restore original server configuration + await m_server.StopServerAsync().ConfigureAwait(false); + m_server = await TestUtils.StartGDSAsync(false, CertificateStoreType.Directory, 0).ConfigureAwait(false); + await m_pushClient.LoadClientConfigurationAsync(m_server.BasePort).ConfigureAwait(false); + await m_pushClient.ConnectAsync(SecurityPolicies.Aes256_Sha256_RsaPss).ConfigureAwait(false); + m_pushClient.PushClient.AdminCredentials = m_pushClient.SysAdminUser; + + TestContext.Out.WriteLine("Restored original server configuration."); + } + } + + private long GetEncodedSize(TrustListDataType trustList) + { + using var stream = new System.IO.MemoryStream(); + using var encoder = new BinaryEncoder(stream, m_pushClient.PushClient.Session.MessageContext, false); + encoder.WriteEncodeable(null, trustList, trustList.GetType()); + return stream.Length; + } + } +}