Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ public GlobalDiscoveryServerClient(
AdminCredentials = adminUserIdentity;
}

/// <summary>
/// 1MB default max trust list size
/// </summary>
private const int kDefaultMaxTrustListSize = 1 * 1024 * 1024;

/// <summary>
/// Gets the application.
/// </summary>
Expand Down Expand Up @@ -1442,7 +1447,8 @@ public TrustListDataType ReadTrustList(NodeId trustListId)
/// <summary>
/// Reads the trust list.
/// </summary>
public async Task<TrustListDataType> ReadTrustListAsync(NodeId trustListId, CancellationToken ct = default)
/// <exception cref="ServiceResultException"></exception>
public async Task<TrustListDataType> ReadTrustListAsync(NodeId trustListId, long maxTrustListSize = 0, CancellationToken ct = default)
{
ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false);

Expand All @@ -1456,6 +1462,14 @@ public async Task<TrustListDataType> 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;
Expand All @@ -1468,6 +1482,17 @@ public async Task<TrustListDataType> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public ServerPushConfigurationClient(
};
}

/// <summary>
/// 1MB default max trust list size
/// </summary>
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; }
Expand Down Expand Up @@ -462,8 +467,10 @@ public TrustListDataType ReadTrustList(TrustListMasks masks = TrustListMasks.All
/// <summary>
/// Reads the trust list.
/// </summary>
/// <exception cref="ServiceResultException"></exception>
public async Task<TrustListDataType> ReadTrustListAsync(
TrustListMasks masks = TrustListMasks.All,
long maxTrustListSize = 0,
CancellationToken ct = default)
{
ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false);
Expand All @@ -489,6 +496,14 @@ public async Task<TrustListDataType> 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;
Expand All @@ -510,6 +525,17 @@ public async Task<TrustListDataType> 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)
Expand Down Expand Up @@ -581,7 +607,8 @@ public bool UpdateTrustList(TrustListDataType trustList)
/// <summary>
/// Updates the trust list.
/// </summary>
public async Task<bool> UpdateTrustListAsync(TrustListDataType trustList, CancellationToken ct = default)
/// <exception cref="ServiceResultException"></exception>
public async Task<bool> UpdateTrustListAsync(TrustListDataType trustList, long maxTrustListSize = 0, CancellationToken ct = default)
{
ISession session = await ConnectIfNeededAsync(ct).ConfigureAwait(false);
IUserIdentity oldUser = await ElevatePermissionsAsync(session, ct).ConfigureAwait(false);
Expand All @@ -595,6 +622,22 @@ public async Task<bool> 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<object> outputArguments = await session.CallAsync(
ExpandedNodeId.ToNodeId(
Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup_TrustList,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
36 changes: 34 additions & 2 deletions Libraries/Opc.Ua.Server/Configuration/TrustList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ namespace Opc.Ua.Server
/// </summary>
public class TrustList
{
private const int kDefaultTrustListCapacity = 0x10000;
private const int kDefaultTrustListCapacity = 1 * 1024 * 1024;

/// <summary>
/// 1MB default max trust list size
/// </summary>
private const int kDefaultMaxTrustListSize = 1 * 1024 * 1024;

/// <summary>
/// Initialize the trustlist with default values.
Expand All @@ -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<TrustList>();
Expand All @@ -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
Expand Down Expand Up @@ -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 };

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
5 changes: 3 additions & 2 deletions Tests/Opc.Ua.Gds.Tests/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@ public static string PatchOnlyGDSEndpointUrlPort(string url, int port)

public static async Task<GlobalDiscoveryTestServer> StartGDSAsync(
bool clean,
string storeType = CertificateStoreType.Directory)
string storeType = CertificateStoreType.Directory,
int maxTrustListSize = 0)
{
GlobalDiscoveryTestServer server = null;
int testPort = ServerFixtureUtils.GetNextFreeIPPort();
Expand All @@ -409,7 +410,7 @@ public static async Task<GlobalDiscoveryTestServer> 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)
Expand Down
14 changes: 10 additions & 4 deletions Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GlobalDiscoveryTestServer>();
m_maxTrustListSize = maxTrustListSize;
}

public async Task StartServerAsync(
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -237,7 +238,8 @@ private static void RegisterDefaultUsers(IUserDatabase userDatabase)

private static async Task<ApplicationConfiguration> LoadAsync(
ApplicationInstance application,
int basePort)
int basePort,
int maxTrustListSize)
{
#if !USE_FILE_CONFIG
// load the application configuration.
Expand Down Expand Up @@ -313,12 +315,16 @@ private static async Task<ApplicationConfiguration> LoadAsync(
.CreateAsync()
.ConfigureAwait(false);
#endif

config.ServerConfiguration.MaxTrustListSize = maxTrustListSize;

TestUtils.PatchBaseAddressesPorts(config, basePort);
return config;
}

private static bool s_autoAccept;
private readonly ITelemetryContext m_telemetry;
private readonly ILogger m_logger;
private readonly int m_maxTrustListSize = 0;
}
}
1 change: 1 addition & 0 deletions Tests/Opc.Ua.Gds.Tests/PushTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading