/* ======================================================================== * 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; } }