/* ========================================================================
* 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;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua.Security.Certificates;
namespace Opc.Ua.Server
{
///
/// The implementation of a server trustlist.
///
public class TrustList
{
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.
///
public TrustList(
TrustListState node,
CertificateStoreIdentifier trustedListStore,
CertificateStoreIdentifier issuerListStore,
SecureAccess readAccess,
SecureAccess writeAccess,
ITelemetryContext telemetry,
int maxTrustListSize = 0)
{
m_telemetry = telemetry;
m_logger = telemetry.CreateLogger();
m_node = node;
m_trustedStore = trustedListStore;
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;
// Register both sync and async handlers per MethodState. The async path is
// preferred (and is what the in-tree containers — ConfigurationNodeManager
// and ApplicationsNodeManager — dispatch through). The sync OnCall handlers
// are compat shims for legacy CustomNodeManager2 subclasses that host this
// TrustList but do not implement ICallAsyncNodeManager; those subclasses
// dispatch through MethodState.Call (sync) which would otherwise return
// BadNotImplemented because OnCallAsync is not consulted on the sync path.
node.Open!.OnCall = new OpenMethodStateMethodCallHandler(Open);
node.Open.OnCallAsync = new OpenMethodStateMethodAsyncCallHandler(OpenAsync);
node.OpenWithMasks!.OnCall
= new OpenWithMasksMethodStateMethodCallHandler(OpenWithMasks);
node.OpenWithMasks.OnCallAsync
= new OpenWithMasksMethodStateMethodAsyncCallHandler(OpenWithMasksAsync);
node.Read!.OnCall = new ReadMethodStateMethodCallHandler(Read);
node.Read.OnCallAsync = new ReadMethodStateMethodAsyncCallHandler(ReadAsync);
node.Write!.OnCall = new WriteMethodStateMethodCallHandler(Write);
node.Write.OnCallAsync = new WriteMethodStateMethodAsyncCallHandler(WriteAsync);
node.Close!.OnCall = new CloseMethodStateMethodCallHandler(Close);
node.Close.OnCallAsync = new CloseMethodStateMethodAsyncCallHandler(CloseAsync);
node.CloseAndUpdate!.OnCall
= new CloseAndUpdateMethodStateMethodCallHandler(CloseAndUpdate);
node.CloseAndUpdate.OnCallAsync
= new CloseAndUpdateMethodStateMethodAsyncCallHandler(CloseAndUpdateAsync);
node.AddCertificate!.OnCall
= new AddCertificateMethodStateMethodCallHandler(AddCertificate);
node.AddCertificate.OnCallAsync
= new AddCertificateMethodStateMethodAsyncCallHandler(AddCertificateAsync);
node.RemoveCertificate!.OnCall
= new RemoveCertificateMethodStateMethodCallHandler(RemoveCertificate);
node.RemoveCertificate.OnCallAsync
= new RemoveCertificateMethodStateMethodAsyncCallHandler(RemoveCertificateAsync);
}
///
/// Delegate to validate the access to the trust list.
///
/// System context
/// the path to identify the trustList
public delegate void SecureAccess(
ISystemContext context,
CertificateStoreIdentifier trustedStore);
private ServiceResult Open(
ISystemContext context,
MethodState method,
NodeId objectId,
byte mode,
ref uint fileHandle)
{
OpenMethodStateResult result = OpenAsync(
context,
method,
objectId,
mode,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
fileHandle = result.FileHandle;
return result.ServiceResult;
}
private ValueTask OpenAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
byte mode,
CancellationToken cancellationToken)
{
return OpenCoreAsync(
context,
method,
objectId,
(OpenFileMode)mode,
TrustListMasks.All,
cancellationToken);
}
private ServiceResult OpenWithMasks(
ISystemContext context,
MethodState method,
NodeId objectId,
uint masks,
ref uint fileHandle)
{
OpenWithMasksMethodStateResult result = OpenWithMasksAsync(
context,
method,
objectId,
masks,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
fileHandle = result.FileHandle;
return result.ServiceResult;
}
private async ValueTask OpenWithMasksAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
uint masks,
CancellationToken cancellationToken)
{
OpenMethodStateResult result = await OpenCoreAsync(
context,
method,
objectId,
OpenFileMode.Read,
(TrustListMasks)masks,
cancellationToken).ConfigureAwait(false);
return new OpenWithMasksMethodStateResult
{
ServiceResult = result.ServiceResult,
FileHandle = result.FileHandle
};
}
private async ValueTask OpenCoreAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
OpenFileMode mode,
TrustListMasks masks,
CancellationToken cancellationToken)
{
if (mode == OpenFileMode.Read)
{
HasSecureReadAccess(context);
}
else if ((int)mode == ((int)OpenFileMode.Write | (int)OpenFileMode.EraseExisting))
{
HasSecureWriteAccess(context);
}
else
{
return new OpenMethodStateResult
{
ServiceResult = StatusCodes.BadNotWritable,
FileHandle = 0
};
}
uint fileHandle = 0;
MemoryStream? strm = null;
try
{
var trustList = new TrustListDataType { SpecifiedLists = (uint)masks };
ICertificateStore store = m_trustedStore.OpenStore(m_telemetry);
try
{
if (store == null)
{
throw ServiceResultException.ConfigurationError(
"Failed to open trusted certificate store.");
}
if (((int)masks & (int)TrustListMasks.TrustedCertificates) != 0)
{
using CertificateCollection certificates = await store.EnumerateAsync(cancellationToken)
.ConfigureAwait(false);
trustList.TrustedCertificates = trustList.TrustedCertificates.AddItems(
certificates
.Select(certificate => certificate.RawData.ToByteString()));
}
if (((int)masks & (int)TrustListMasks.TrustedCrls) != 0)
{
X509CRLCollection crls = await store.EnumerateCRLsAsync(cancellationToken)
.ConfigureAwait(false);
trustList.TrustedCrls = trustList.TrustedCrls.AddItems(
crls.Select(crl => crl.RawData.ToByteString()));
}
}
finally
{
store.Close();
}
store = m_issuerStore.OpenStore(m_telemetry);
try
{
if (store == null)
{
throw ServiceResultException.ConfigurationError(
"Failed to open issuer certificate store.");
}
if (((int)masks & (int)TrustListMasks.IssuerCertificates) != 0)
{
using CertificateCollection certificates = await store.EnumerateAsync(cancellationToken)
.ConfigureAwait(false);
trustList.IssuerCertificates = trustList.IssuerCertificates.AddItems(certificates
.Select(certificate => certificate.RawData.ToByteString()));
}
if (((int)masks & (int)TrustListMasks.IssuerCrls) != 0)
{
X509CRLCollection crls = await store.EnumerateCRLsAsync(cancellationToken)
.ConfigureAwait(false);
trustList.IssuerCrls = trustList.IssuerCrls.AddItems(crls
.Select(crl => crl.RawData.ToByteString()));
}
}
finally
{
store.Close();
}
if (mode == OpenFileMode.Read)
{
strm = EncodeTrustListData(context, trustList);
}
else
{
strm = new MemoryStream(kDefaultTrustListCapacity);
}
lock (m_lock)
{
if (!m_sessionId.IsNull)
{
// to avoid deadlocks, last open always wins
m_sessionId = default;
m_strm?.Dispose();
m_strm = null;
m_node.OpenCount!.Value = 0;
}
m_sessionId = (context as ISessionSystemContext)?.SessionId ?? default;
fileHandle = ++m_fileHandle;
m_totalBytesProcessed = 0; // Reset counter for new file operation
m_strm = strm;
m_node.OpenCount!.Value = 1;
}
}
catch
{
strm?.Dispose();
throw;
}
return new OpenMethodStateResult
{
ServiceResult = ServiceResult.Good,
FileHandle = fileHandle
};
}
private ServiceResult Read(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
int length,
ref ByteString data)
{
ReadMethodStateResult result = ReadAsync(
context,
method,
objectId,
fileHandle,
length,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
data = result.Data;
return result.ServiceResult;
}
private ValueTask ReadAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
int length,
CancellationToken cancellationToken)
{
HasSecureReadAccess(context);
ByteString data;
lock (m_lock)
{
if (context is ISessionSystemContext session &&
m_sessionId != null! &&
!m_sessionId.Equals(session.SessionId))
{
return new ValueTask(new ReadMethodStateResult
{
ServiceResult = ServiceResult.Create(
StatusCodes.BadUserAccessDenied,
"Session not authorized"),
Data = default
});
}
if (m_fileHandle != fileHandle)
{
return new ValueTask(new ReadMethodStateResult
{
ServiceResult = ServiceResult.Create(
StatusCodes.BadInvalidArgument,
"Invalid file handle"),
Data = default
});
}
// Check if we would exceed the maximum trust list size
if (m_totalBytesProcessed + length > m_maxTrustListSize)
{
return new ValueTask(new ReadMethodStateResult
{
ServiceResult = ServiceResult.Create(
StatusCodes.BadEncodingLimitsExceeded,
"Trust list size exceeds maximum allowed size of {0} bytes",
m_maxTrustListSize),
Data = default
});
}
byte[] buffer = new byte[length];
int bytesRead = m_strm!.Read(buffer, 0, length);
Debug.Assert(bytesRead >= 0);
data = ByteString.From(buffer)[..bytesRead];
m_totalBytesProcessed += bytesRead;
}
return new ValueTask(new ReadMethodStateResult
{
ServiceResult = ServiceResult.Good,
Data = data
});
}
private ServiceResult Write(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
ByteString data)
{
WriteMethodStateResult result = WriteAsync(
context,
method,
objectId,
fileHandle,
data,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
return result.ServiceResult;
}
private ValueTask WriteAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
ByteString data,
CancellationToken cancellationToken)
{
HasSecureWriteAccess(context);
lock (m_lock)
{
if (context is ISessionSystemContext session &&
m_sessionId != null! &&
!m_sessionId.Equals(session.SessionId))
{
return new ValueTask(new WriteMethodStateResult
{
ServiceResult = StatusCodes.BadUserAccessDenied
});
}
if (m_fileHandle != fileHandle)
{
return new ValueTask(new WriteMethodStateResult
{
ServiceResult = StatusCodes.BadInvalidArgument
});
}
// Check if we would exceed the maximum trust list size
if (m_totalBytesProcessed + data.Length > m_maxTrustListSize)
{
return new ValueTask(new WriteMethodStateResult
{
ServiceResult = ServiceResult.Create(
StatusCodes.BadEncodingLimitsExceeded,
"Trust list size exceeds maximum allowed size of {0} bytes",
m_maxTrustListSize)
});
}
m_strm!.Write(data.ToArray(), 0, data.Length);
m_totalBytesProcessed += data.Length;
}
return new ValueTask(new WriteMethodStateResult
{
ServiceResult = ServiceResult.Good
});
}
private ServiceResult Close(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle)
{
CloseMethodStateResult result = CloseAsync(
context,
method,
objectId,
fileHandle,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
return result.ServiceResult;
}
private ValueTask CloseAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
CancellationToken cancellationToken)
{
HasSecureReadAccess(context);
lock (m_lock)
{
if (context is ISessionSystemContext session &&
m_sessionId != null! &&
!m_sessionId.Equals(session.SessionId))
{
return new ValueTask(new CloseMethodStateResult
{
ServiceResult = StatusCodes.BadUserAccessDenied
});
}
if (m_fileHandle != fileHandle)
{
return new ValueTask(new CloseMethodStateResult
{
ServiceResult = StatusCodes.BadInvalidArgument
});
}
m_sessionId = default;
m_strm?.Dispose();
m_strm = null;
m_node.OpenCount!.Value = 0;
}
return new ValueTask(new CloseMethodStateResult
{
ServiceResult = ServiceResult.Good
});
}
private ServiceResult CloseAndUpdate(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
ref bool restartRequired)
{
CloseAndUpdateMethodStateResult result = CloseAndUpdateAsync(
context,
method,
objectId,
fileHandle,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
restartRequired = result.ApplyChangesRequired;
return result.ServiceResult;
}
private async ValueTask CloseAndUpdateAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
uint fileHandle,
CancellationToken cancellationToken)
{
ArrayOf inputParameters = [fileHandle];
m_node.ReportTrustListUpdateRequestedAuditEvent(
context,
objectId,
"Method/CloseAndUpdate",
method.NodeId,
inputParameters,
m_logger);
HasSecureWriteAccess(context);
ServiceResult result = StatusCodes.Good;
MemoryStream? strm;
lock (m_lock)
{
if (context is ISessionSystemContext session &&
m_sessionId != null! &&
!m_sessionId.Equals(session.SessionId))
{
return new CloseAndUpdateMethodStateResult
{
ServiceResult = StatusCodes.BadUserAccessDenied,
ApplyChangesRequired = false
};
}
if (m_fileHandle != fileHandle)
{
return new CloseAndUpdateMethodStateResult
{
ServiceResult = StatusCodes.BadInvalidArgument,
ApplyChangesRequired = false
};
}
strm = m_strm;
}
CertificateCollection? issuerCertificates = null;
CertificateCollection? trustedCertificates = null;
try
{
TrustListDataType trustList = DecodeTrustListData(context, strm!);
int masks = (int)trustList.SpecifiedLists;
X509CRLCollection? issuerCrls = null;
X509CRLCollection? trustedCrls = null;
// test integrity of all CRLs
if ((masks & (int)TrustListMasks.IssuerCertificates) != 0)
{
issuerCertificates = [];
foreach (ByteString cert in trustList.IssuerCertificates)
{
issuerCertificates.Add(Certificate.FromRawData(cert));
}
}
if ((masks & (int)TrustListMasks.IssuerCrls) != 0)
{
issuerCrls = [];
foreach (ByteString crl in trustList.IssuerCrls)
{
issuerCrls.Add(new X509CRL(crl.ToArray()));
}
}
if ((masks & (int)TrustListMasks.TrustedCertificates) != 0)
{
trustedCertificates = [];
foreach (ByteString cert in trustList.TrustedCertificates)
{
trustedCertificates.Add(Certificate.FromRawData(cert));
}
}
if ((masks & (int)TrustListMasks.TrustedCrls) != 0)
{
trustedCrls = [];
foreach (ByteString crl in trustList.TrustedCrls)
{
trustedCrls.Add(new X509CRL(crl.ToArray()));
}
}
// update store
int updateMasks = (int)TrustListMasks.None;
if ((masks & (int)TrustListMasks.IssuerCertificates) != 0 &&
await UpdateStoreCertificatesAsync(m_issuerStore, issuerCertificates!, cancellationToken)
.ConfigureAwait(false))
{
updateMasks |= (int)TrustListMasks.IssuerCertificates;
}
if ((masks & (int)TrustListMasks.IssuerCrls) != 0 &&
await UpdateStoreCrlsAsync(m_issuerStore, issuerCrls!, cancellationToken).ConfigureAwait(false))
{
updateMasks |= (int)TrustListMasks.IssuerCrls;
}
if ((masks & (int)TrustListMasks.TrustedCertificates) != 0 &&
await UpdateStoreCertificatesAsync(m_trustedStore, trustedCertificates!, cancellationToken)
.ConfigureAwait(false))
{
updateMasks |= (int)TrustListMasks.TrustedCertificates;
}
if ((masks & (int)TrustListMasks.TrustedCrls) != 0 &&
await UpdateStoreCrlsAsync(m_trustedStore, trustedCrls!, cancellationToken).ConfigureAwait(false))
{
updateMasks |= (int)TrustListMasks.TrustedCrls;
}
if (masks != updateMasks)
{
result = StatusCodes.BadCertificateInvalid;
}
}
catch
{
result = StatusCodes.BadCertificateInvalid;
}
finally
{
issuerCertificates?.Dispose();
trustedCertificates?.Dispose();
lock (m_lock)
{
m_sessionId = default;
m_strm?.Dispose();
m_strm = null;
m_node.LastUpdateTime!.Value = DateTime.UtcNow;
m_node.OpenCount!.Value = 0;
}
}
// report the TrustListUpdatedAuditEvent
m_node.ReportTrustListUpdatedAuditEvent(
context,
objectId,
"Method/CloseAndUpdate",
method.NodeId,
inputParameters,
result.StatusCode,
m_logger);
return new CloseAndUpdateMethodStateResult
{
ServiceResult = result,
ApplyChangesRequired = false
};
}
private ServiceResult AddCertificate(
ISystemContext context,
MethodState method,
NodeId objectId,
ByteString certificate,
bool isTrustedCertificate)
{
AddCertificateMethodStateResult result = AddCertificateAsync(
context,
method,
objectId,
certificate,
isTrustedCertificate,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
return result.ServiceResult;
}
private async ValueTask AddCertificateAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
ByteString certificate,
bool isTrustedCertificate,
CancellationToken cancellationToken)
{
ArrayOf inputParameters = [certificate, isTrustedCertificate];
m_node.ReportTrustListUpdateRequestedAuditEvent(
context,
objectId,
"Method/AddCertificate",
method.NodeId,
inputParameters,
m_logger);
HasSecureWriteAccess(context);
ServiceResult result = StatusCodes.Good;
bool isSessionOpen;
lock (m_lock)
{
isSessionOpen = !m_sessionId.IsNull;
}
if (isSessionOpen)
{
result = StatusCodes.BadInvalidState;
}
else if (certificate.IsEmpty)
{
result = StatusCodes.BadInvalidArgument;
}
else
{
Certificate? cert = null;
try
{
cert = Certificate.FromRawData(certificate);
}
catch
{
// note: a previous version of the sample code accepted also CRL,
// but the behaviour was not as specified and removed
// https://mantis.opcfoundation.org/view.php?id=6342
result = StatusCodes.BadCertificateInvalid;
}
if (cert != null)
{
CertificateStoreIdentifier storeIdentifier = isTrustedCertificate
? m_trustedStore
: m_issuerStore;
ICertificateStore store = storeIdentifier.OpenStore(m_telemetry);
try
{
if (store != null)
{
await store.AddAsync(cert, null, cancellationToken).ConfigureAwait(false);
}
}
finally
{
store?.Close();
}
lock (m_lock)
{
m_node.LastUpdateTime!.Value = DateTime.UtcNow;
}
}
}
// report the TrustListUpdatedAuditEvent
m_node.ReportTrustListUpdatedAuditEvent(
context,
objectId,
"Method/AddCertificate",
method.NodeId,
inputParameters,
result.StatusCode,
m_logger);
return new AddCertificateMethodStateResult
{
ServiceResult = result
};
}
private ServiceResult RemoveCertificate(
ISystemContext context,
MethodState method,
NodeId objectId,
string thumbprint,
bool isTrustedCertificate)
{
RemoveCertificateMethodStateResult result = RemoveCertificateAsync(
context,
method,
objectId,
thumbprint,
isTrustedCertificate,
CancellationToken.None).AsTask().ConfigureAwait(false).GetAwaiter().GetResult();
return result.ServiceResult;
}
private async ValueTask RemoveCertificateAsync(
ISystemContext context,
MethodState method,
NodeId objectId,
string thumbprint,
bool isTrustedCertificate,
CancellationToken cancellationToken)
{
ArrayOf inputParameters = [thumbprint, isTrustedCertificate];
m_node.ReportTrustListUpdateRequestedAuditEvent(
context,
objectId,
"Method/RemoveCertificate",
method.NodeId,
inputParameters,
m_logger);
HasSecureWriteAccess(context);
ServiceResult result = StatusCodes.Good;
bool isSessionOpen;
lock (m_lock)
{
isSessionOpen = !m_sessionId.IsNull;
}
if (isSessionOpen)
{
result = StatusCodes.BadInvalidState;
}
else if (string.IsNullOrEmpty(thumbprint))
{
result = StatusCodes.BadInvalidArgument;
}
else
{
CertificateStoreIdentifier storeIdentifier = isTrustedCertificate
? m_trustedStore
: m_issuerStore;
using (ICertificateStore store = storeIdentifier.OpenStore(m_telemetry))
{
if (store == null)
{
throw new ServiceResultException(
StatusCodes.BadConfigurationError,
"Failed to open certificate store.");
}
using CertificateCollection certCollection = await store
.FindByThumbprintAsync(thumbprint, cancellationToken)
.ConfigureAwait(false);
if (certCollection.Count == 0)
{
result = StatusCodes.BadInvalidArgument;
}
else
{
// delete all CRLs signed by cert
var crlsToDelete = new X509CRLCollection();
X509CRLCollection crls = await store.EnumerateCRLsAsync(cancellationToken)
.ConfigureAwait(false);
foreach (X509CRL crl in crls)
{
foreach (Certificate cert in certCollection)
{
if (X509Utils.CompareDistinguishedName(
cert.SubjectName,
crl.IssuerName) &&
crl.VerifySignature(cert, false))
{
crlsToDelete.Add(crl);
break;
}
}
}
if (!await store.DeleteAsync(thumbprint, cancellationToken)
.ConfigureAwait(false))
{
result = StatusCodes.BadInvalidArgument;
}
else
{
foreach (X509CRL crl in crlsToDelete)
{
if (!await store.DeleteCRLAsync(crl, cancellationToken)
.ConfigureAwait(false))
{
// intentionally ignore errors, try best effort
m_logger.LogError(
"RemoveCertificate: Failed to delete CRL {Crl}.",
crl.ToString());
}
}
}
}
}
lock (m_lock)
{
m_node.LastUpdateTime!.Value = DateTime.UtcNow;
}
}
// report the TrustListUpdatedAuditEvent
m_node.ReportTrustListUpdatedAuditEvent(
context,
objectId,
"Method/RemoveCertificate",
method.NodeId,
inputParameters,
result.StatusCode,
m_logger);
return new RemoveCertificateMethodStateResult
{
ServiceResult = result
};
}
private static MemoryStream EncodeTrustListData(
ISystemContext context,
TrustListDataType trustList)
{
IServiceMessageContext messageContext =
new ServiceMessageContext(context.Telemetry, context.EncodeableFactory)
{
NamespaceUris = context.NamespaceUris,
ServerUris = context.ServerUris
};
var strm = new MemoryStream();
using (var encoder = new BinaryEncoder(strm, messageContext, true))
{
encoder.WriteEncodeable(null, trustList);
}
strm.Position = 0;
return strm;
}
private static TrustListDataType DecodeTrustListData(
ISystemContext context,
MemoryStream strm)
{
var trustList = new TrustListDataType();
IServiceMessageContext messageContext =
new ServiceMessageContext(context.Telemetry, context.EncodeableFactory)
{
NamespaceUris = context.NamespaceUris,
ServerUris = context.ServerUris
};
strm.Position = 0;
using (var decoder = new BinaryDecoder(strm, messageContext))
{
trustList.Decode(decoder);
}
return trustList;
}
private async Task UpdateStoreCrlsAsync(
CertificateStoreIdentifier storeIdentifier,
X509CRLCollection updatedCrls,
CancellationToken cancellationToken = default)
{
bool result = true;
try
{
ICertificateStore store = storeIdentifier.OpenStore(m_telemetry);
try
{
if (store == null)
{
throw ServiceResultException.ConfigurationError(
"Failed to open certificate store.");
}
X509CRLCollection storeCrls = await store.EnumerateCRLsAsync(cancellationToken)
.ConfigureAwait(false);
foreach (X509CRL crl in storeCrls)
{
if (!updatedCrls.Remove(crl) &&
!await store.DeleteCRLAsync(crl, cancellationToken).ConfigureAwait(false))
{
result = false;
}
}
foreach (X509CRL crl in updatedCrls)
{
await store.AddCRLAsync(crl, cancellationToken).ConfigureAwait(false);
}
}
finally
{
store.Close();
}
}
catch
{
result = false;
}
return result;
}
private async Task UpdateStoreCertificatesAsync(
CertificateStoreIdentifier storeIdentifier,
CertificateCollection updatedCerts,
CancellationToken cancellationToken = default)
{
bool result = true;
try
{
ICertificateStore store = storeIdentifier.OpenStore(m_telemetry);
try
{
if (store == null)
{
throw ServiceResultException.ConfigurationError(
"Failed to open certificate store.");
}
using CertificateCollection storeCerts = await store.EnumerateAsync(cancellationToken)
.ConfigureAwait(false);
foreach (Certificate cert in storeCerts)
{
if (!updatedCerts.Remove(cert) &&
!await store.DeleteAsync(cert.Thumbprint, cancellationToken).ConfigureAwait(false))
{
result = false;
}
}
foreach (Certificate cert in updatedCerts)
{
await store.AddAsync(cert, null, cancellationToken).ConfigureAwait(false);
}
}
finally
{
store.Close();
}
}
catch
{
result = false;
}
return result;
}
private void HasSecureReadAccess(ISystemContext context)
{
if (m_readAccess != null)
{
m_readAccess.Invoke(context, m_trustedStore);
}
else
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied);
}
}
private void HasSecureWriteAccess(ISystemContext context)
{
if (m_writeAccess != null)
{
m_writeAccess.Invoke(context, m_trustedStore);
}
else
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied);
}
}
private readonly Lock m_lock = new();
private readonly SecureAccess m_readAccess;
private readonly SecureAccess m_writeAccess;
private NodeId m_sessionId;
private uint m_fileHandle;
private readonly CertificateStoreIdentifier m_trustedStore;
private readonly CertificateStoreIdentifier m_issuerStore;
private readonly ITelemetryContext m_telemetry;
private readonly ILogger m_logger;
private readonly TrustListState m_node;
private MemoryStream? m_strm;
private readonly int m_maxTrustListSize;
private long m_totalBytesProcessed;
}
}