From 38990045230fd5a002d2b542ff2c73178323e7a2 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 23 Nov 2024 17:45:08 +0100 Subject: [PATCH 1/4] fix CertificateTypes Provider --- .../Opc.Ua.Server/Server/StandardServer.cs | 2 +- .../Stack/Https/HttpsServiceHost.cs | 2 +- .../Stack/Https/HttpsTransportListener.cs | 5 +- .../Certificates/CertificateTypesProvider.cs | 64 +++++++++---------- Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs | 12 ++-- Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs | 6 +- .../Stack/Tcp/TcpTransportListener.cs | 2 +- .../Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs | 4 +- 8 files changed, 45 insertions(+), 52 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Server/StandardServer.cs b/Libraries/Opc.Ua.Server/Server/StandardServer.cs index 45dd363f88..1ee4c896ba 100644 --- a/Libraries/Opc.Ua.Server/Server/StandardServer.cs +++ b/Libraries/Opc.Ua.Server/Server/StandardServer.cs @@ -488,7 +488,7 @@ public override ResponseHeader CreateSession( // check if complete chain should be sent. if (InstanceCertificateTypesProvider.SendCertificateChain) { - serverCertificate = InstanceCertificateTypesProvider.LoadCertificateChainRaw(instanceCertificate); + serverCertificate = InstanceCertificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } else { diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs index 1ef99083af..3ed00e08ae 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsServiceHost.cs @@ -125,7 +125,7 @@ CertificateTypesProvider certificateTypesProvider // check if complete chain should be sent. if (certificateTypesProvider.SendCertificateChain) { - description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRaw(instanceCertificate); + description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRawAsync(instanceCertificate).GetAwaiter().GetResult(); } } diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs index 0f46ebdbca..2da30e11a9 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs @@ -477,10 +477,9 @@ public void CertificateUpdate( foreach (EndpointDescription description in m_descriptions) { - ServerBase.SetServerCertificateInEndpointDescription(description, - m_serverCertProvider.SendCertificateChain, + ServerBase.SetServerCertificateInEndpointDescriptionAsync(description, certificateTypeProvider, - false); + false).GetAwaiter().GetResult(); } Start(); diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs index 9e4c587569..41e778ba02 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -122,7 +122,7 @@ public X509Certificate2 GetInstanceCertificate(string securityPolicyUri) /// Loads the cached certificate chain blob of a certificate for use in a secure channel as raw byte array. /// /// The application certificate. - public byte[] LoadCertificateChainRaw(X509Certificate2 certificate) + public async Task LoadCertificateChainRawAsync(X509Certificate2 certificate) { if (certificate == null) { @@ -134,7 +134,13 @@ public byte[] LoadCertificateChainRaw(X509Certificate2 certificate) return result.Item2; } - return certificate.RawData; + // load certificate chain. + Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate); + + // update cached values + m_certificateChain[certificate.Thumbprint] = dictionaryValue; + + return dictionaryValue.Item2; } /// @@ -154,51 +160,43 @@ public async Task LoadCertificateChainAsync(X509Cert } // load certificate chain. - var certificateChain = new X509Certificate2Collection(certificate); - var issuers = new List(); - if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) - { - for (int i = 0; i < issuers.Count; i++) - { - certificateChain.Add(issuers[i].Certificate); - } - } - - byte[] certificateChainRaw = Utils.CreateCertificateChainBlob(certificateChain); - var dictionaryValue = new Tuple(certificateChain, certificateChainRaw); + Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate); // update cached values m_certificateChain[certificate.Thumbprint] = dictionaryValue; - return certificateChain; + return dictionaryValue.Item1; } /// - /// Loads the certificate chain for an application certificate from cache. + /// Update the security configuration of the cert type provider. /// - /// The application certificate. - public X509Certificate2Collection LoadCertificateChain(X509Certificate2 certificate) + /// The new security configuration. + public async Task UpdateAsync(SecurityConfiguration securityConfiguration) { - if (certificate == null) - { - return null; - } - - if (m_certificateChain.TryGetValue(certificate.Thumbprint, out var certificateChainTuple)) - { - return certificateChainTuple.Item1; - } - - return null; + m_securityConfiguration = securityConfiguration; + await InitializeAsync(); } /// - /// Update the security configuration of the cert type provider. + /// Builds the chain using the Issuer and Trusted Stores of the certificateValidator /// - /// The new security configuration. - public void Update(SecurityConfiguration securityConfiguration) + /// the certificate to load the chain for + /// + private async Task> LoadCertificateChainFromStoreAsync(X509Certificate2 certificate) { - m_securityConfiguration = securityConfiguration; + var certificateChain = new X509Certificate2Collection(certificate); + var issuers = new List(); + if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false)) + { + for (int i = 0; i < issuers.Count; i++) + { + certificateChain.Add(issuers[i].Certificate); + } + } + + byte[] certificateChainRaw = Utils.CreateCertificateChainBlob(certificateChain); + return new Tuple(certificateChain, certificateChainRaw); } CertificateValidator m_certificateValidator; diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index eb4f7de894..af5160154d 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -21,10 +21,10 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; -using Microsoft.Extensions.Logging; using Opc.Ua.Bindings; using System.Net.Sockets; using Opc.Ua.Security.Certificates; +using System.Threading.Tasks; namespace Opc.Ua { @@ -604,12 +604,10 @@ public static bool RequireEncryption(EndpointDescription description) /// Sets the Server Certificate in an Endpoint description if the description requires encryption. /// /// the endpoint Description to set the server certificate - /// true if the certificate chain shall be sent /// The provider to get the server certificate per certificate type. /// only set certificate if the endpoint does require Encryption - public static void SetServerCertificateInEndpointDescription( + public static async Task SetServerCertificateInEndpointDescriptionAsync( EndpointDescription description, - bool sendCertificateChain, CertificateTypesProvider certificateTypesProvider, bool checkRequireEncryption = true) { @@ -617,9 +615,9 @@ public static void SetServerCertificateInEndpointDescription( { X509Certificate2 serverCertificate = certificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); // check if complete chain should be sent. - if (sendCertificateChain) + if (certificateTypesProvider.SendCertificateChain) { - description.ServerCertificate = certificateTypesProvider.LoadCertificateChainRaw(serverCertificate); + description.ServerCertificate = await certificateTypesProvider.LoadCertificateChainRawAsync(serverCertificate); } else { @@ -796,7 +794,7 @@ protected virtual EndpointBase GetEndpointInstance(ServerBase server) /// protected virtual void OnCertificateUpdate(object sender, CertificateUpdateEventArgs e) { - InstanceCertificateTypesProvider.Update(e.SecurityConfiguration); + InstanceCertificateTypesProvider.UpdateAsync(e.SecurityConfiguration).GetAwaiter().GetResult(); foreach (var listener in TransportListeners) { listener.CertificateUpdate(e.CertificateValidator, InstanceCertificateTypesProvider); diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs index fa12741ec1..fa671e1e5d 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs @@ -76,7 +76,6 @@ public List CreateServiceHost( uri.Host = computerName; } - bool sendCertificateChain = instanceCertificateTypesProvider.SendCertificateChain; ITransportListener listener = this.Create(); if (listener != null) { @@ -96,10 +95,9 @@ public List CreateServiceHost( }; description.UserIdentityTokens = serverBase.GetUserTokenPolicies(configuration, description); - ServerBase.SetServerCertificateInEndpointDescription( + ServerBase.SetServerCertificateInEndpointDescriptionAsync( description, - sendCertificateChain, - instanceCertificateTypesProvider); + instanceCertificateTypesProvider).GetAwaiter().GetResult(); listenerEndpoints.Add(description); } diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index f651ab3d30..5469ff6875 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -490,7 +490,7 @@ CertificateTypesProvider certificateTypesProvider X509Certificate2 serverCertificate = certificateTypesProvider.GetInstanceCertificate(description.SecurityPolicyUri); if (certificateTypesProvider.SendCertificateChain) { - byte[] serverCertificateChainRaw = certificateTypesProvider.LoadCertificateChainRaw(serverCertificate); + byte[] serverCertificateChainRaw = certificateTypesProvider.LoadCertificateChainRawAsync(serverCertificate).GetAwaiter().GetResult(); description.ServerCertificate = serverCertificateChainRaw; } else diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs index a950d14cb1..4377c95734 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs @@ -898,7 +898,7 @@ protected void ReadAsymmetricMessageHeader( if (loadChain) { - m_serverCertificateChain = m_serverCertificateTypesProvider?.LoadCertificateChain(receiverCertificate); + m_serverCertificateChain = m_serverCertificateTypesProvider?.LoadCertificateChainAsync(receiverCertificate).GetAwaiter().GetResult(); } } else @@ -930,7 +930,7 @@ protected void ReviseSecurityMode(bool firstCall, MessageSecurityMode requestedM m_securityMode = endpoint.SecurityMode; m_selectedEndpoint = endpoint; m_serverCertificate = m_serverCertificateTypesProvider.GetInstanceCertificate(m_securityPolicyUri); - m_serverCertificateChain = m_serverCertificateTypesProvider.LoadCertificateChain(m_serverCertificate); + m_serverCertificateChain = m_serverCertificateTypesProvider.LoadCertificateChainAsync(m_serverCertificate).GetAwaiter().GetResult(); supported = true; break; } From 10b3d30ae19e84c8850ac4dfaa0b63945d6e40c3 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 23 Nov 2024 18:29:42 +0100 Subject: [PATCH 2/4] Update EndpointDescriptions after Certificate Update --- Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index af5160154d..6de909b4aa 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -795,6 +795,15 @@ protected virtual EndpointBase GetEndpointInstance(ServerBase server) protected virtual void OnCertificateUpdate(object sender, CertificateUpdateEventArgs e) { InstanceCertificateTypesProvider.UpdateAsync(e.SecurityConfiguration).GetAwaiter().GetResult(); + + //update certificate in the endpoint descriptions + foreach (EndpointDescription endpointDescription in m_endpoints) + { + SetServerCertificateInEndpointDescriptionAsync( + endpointDescription, + InstanceCertificateTypesProvider).GetAwaiter().GetResult(); + } + foreach (var listener in TransportListeners) { listener.CertificateUpdate(e.CertificateValidator, InstanceCertificateTypesProvider); From b2d8df5743c82c5f4618d776daf5047fec3fc5b3 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 23 Nov 2024 19:01:26 +0100 Subject: [PATCH 3/4] temporary fix --- .../Configuration/ConfigurationNodeManager.cs | 18 +++++++++++++----- .../Certificates/CertificateTypesProvider.cs | 5 ++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 860756bb54..eacb5b20e8 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -80,7 +80,7 @@ ApplicationConfiguration configuration ServerCertificateGroup defaultApplicationGroup = new ServerCertificateGroup { NodeId = Opc.Ua.ObjectIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup, BrowseName = Opc.Ua.BrowseNames.DefaultApplicationGroup, - CertificateTypes = new NodeId[]{}, + CertificateTypes = new NodeId[] { }, ApplicationCertificates = new CertificateIdentifierCollection(), IssuerStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath), TrustedStore = new CertificateStoreIdentifier(configuration.SecurityConfiguration.TrustedPeerCertificates.StorePath) @@ -397,7 +397,7 @@ private ServiceResult UpdateCertificate( // identify the existing certificate to be updated // it should be of the same type and same subject name as the new certificate - CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => + CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert => X509Utils.CompareDistinguishedName(cert.Certificate.Subject, newCert.Subject) && cert.CertificateType == certificateTypeId); @@ -451,8 +451,8 @@ private ServiceResult UpdateCertificate( { // verify cert with issuer chain CertificateValidator certValidator = new CertificateValidator(); -// TODO: why? -// certValidator.MinimumCertificateKeySize = 1024; + // TODO: why? + // certValidator.MinimumCertificateKeySize = 1024; CertificateTrustList issuerStore = new CertificateTrustList(); CertificateIdentifierCollection issuerCollection = new CertificateIdentifierCollection(); foreach (var issuerCert in newIssuerCollection) @@ -677,7 +677,15 @@ private ServiceResult ApplyChanges( // give the client some time to receive the response // before the certificate update may disconnect all sessions await Task.Delay(1000).ConfigureAwait(false); - await m_configuration.CertificateValidator.UpdateCertificateAsync(m_configuration.SecurityConfiguration).ConfigureAwait(false); + try + { + await m_configuration.CertificateValidator.UpdateCertificateAsync(m_configuration.SecurityConfiguration).ConfigureAwait(false); + } + catch (Exception ex) + { + Utils.LogError(ex, "Failed to sucessfully Apply Changes: Error updating application instance certificate"); + throw ex; + } } ); } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs index 41e778ba02..f04aad4f27 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -175,7 +175,10 @@ public async Task LoadCertificateChainAsync(X509Cert public async Task UpdateAsync(SecurityConfiguration securityConfiguration) { m_securityConfiguration = securityConfiguration; - await InitializeAsync(); + await Task.CompletedTask; + //ToDo intialize internal CertificateValidator after Certificate Update + //await InitializeAsync(); + } /// From bef4079f12d0f1b9f2ea49f2a9e4fa746fd8d353 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 24 Nov 2024 09:00:07 +0100 Subject: [PATCH 4/4] minor cleanup --- .../Configuration/ConfigurationNodeManager.cs | 4 ++-- .../Security/Certificates/CertificateTypesProvider.cs | 10 ++++------ Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index eacb5b20e8..d2502edfba 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -452,7 +452,7 @@ private ServiceResult UpdateCertificate( // verify cert with issuer chain CertificateValidator certValidator = new CertificateValidator(); // TODO: why? - // certValidator.MinimumCertificateKeySize = 1024; + // certValidator.MinimumCertificateKeySize = 1024; CertificateTrustList issuerStore = new CertificateTrustList(); CertificateIdentifierCollection issuerCollection = new CertificateIdentifierCollection(); foreach (var issuerCert in newIssuerCollection) @@ -683,7 +683,7 @@ private ServiceResult ApplyChanges( } catch (Exception ex) { - Utils.LogError(ex, "Failed to sucessfully Apply Changes: Error updating application instance certificate"); + Utils.LogCritical(ex, "Failed to sucessfully Apply Changes: Error updating application instance certificates. Server could be in faulted state."); throw ex; } } diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs index f04aad4f27..9d472af10d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs @@ -135,7 +135,7 @@ public async Task LoadCertificateChainRawAsync(X509Certificate2 certific } // load certificate chain. - Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate); + Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate).ConfigureAwait(false); // update cached values m_certificateChain[certificate.Thumbprint] = dictionaryValue; @@ -160,7 +160,7 @@ public async Task LoadCertificateChainAsync(X509Cert } // load certificate chain. - Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate); + Tuple dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate).ConfigureAwait(false); // update cached values m_certificateChain[certificate.Thumbprint] = dictionaryValue; @@ -175,10 +175,8 @@ public async Task LoadCertificateChainAsync(X509Cert public async Task UpdateAsync(SecurityConfiguration securityConfiguration) { m_securityConfiguration = securityConfiguration; - await Task.CompletedTask; - //ToDo intialize internal CertificateValidator after Certificate Update - //await InitializeAsync(); - + //ToDo intialize internal CertificateValidator after Certificate Update to clear cache of old application certificates + await Task.CompletedTask.ConfigureAwait(false); } /// diff --git a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs index 6de909b4aa..ffcbf0a807 100644 --- a/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs +++ b/Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs @@ -617,7 +617,7 @@ public static async Task SetServerCertificateInEndpointDescriptionAsync( // check if complete chain should be sent. if (certificateTypesProvider.SendCertificateChain) { - description.ServerCertificate = await certificateTypesProvider.LoadCertificateChainRawAsync(serverCertificate); + description.ServerCertificate = await certificateTypesProvider.LoadCertificateChainRawAsync(serverCertificate).ConfigureAwait(false); } else {