Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Server] Fix: Return full CertificateChain after Certificate Update #2855

Closed
Closed
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
18 changes: 13 additions & 5 deletions Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
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)
Expand Down Expand Up @@ -397,7 +397,7 @@

// 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);

Expand Down Expand Up @@ -451,8 +451,8 @@
{
// 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)
Expand Down Expand Up @@ -677,7 +677,15 @@
// 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.LogCritical(ex, "Failed to sucessfully Apply Changes: Error updating application instance certificates. Server could be in faulted state.");
throw ex;

Check warning on line 687 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client.ComplexTypes

Re-throwing caught exception changes stack information (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200)

Check warning on line 687 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Gds

Re-throwing caught exception changes stack information (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200)

Check warning on line 687 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Server

Re-throwing caught exception changes stack information (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200)

Check warning on line 687 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client

Re-throwing caught exception changes stack information (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2200)

Check warning on line 687 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs#L686-L687

Added lines #L686 - L687 were not covered by tests
}
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion Libraries/Opc.Ua.Server/Server/StandardServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,9 @@

foreach (EndpointDescription description in m_descriptions)
{
ServerBase.SetServerCertificateInEndpointDescription(description,
m_serverCertProvider.SendCertificateChain,
ServerBase.SetServerCertificateInEndpointDescriptionAsync(description,

Check warning on line 480 in Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs#L480

Added line #L480 was not covered by tests
certificateTypeProvider,
false);
false).GetAwaiter().GetResult();

Check warning on line 482 in Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs#L482

Added line #L482 was not covered by tests
}

Start();
Expand Down
65 changes: 32 additions & 33 deletions Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
/// Loads the cached certificate chain blob of a certificate for use in a secure channel as raw byte array.
/// </summary>
/// <param name="certificate">The application certificate.</param>
public byte[] LoadCertificateChainRaw(X509Certificate2 certificate)
public async Task<byte[]> LoadCertificateChainRawAsync(X509Certificate2 certificate)
{
if (certificate == null)
{
Expand All @@ -134,7 +134,13 @@
return result.Item2;
}

return certificate.RawData;
// load certificate chain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change contradicts the idea of the cache, which was that certs and certchains are being updated outside the normal create session flows, once.
Then no async load operation has to be done and no async has to be wired up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mregen but shouldn't we handle a "Cache miss". I can change the logic back to a separate update function, If we don't need this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question is why we have a cache miss, it should be loaded once on startup, or when application certificates are updated. handling the cache miss is a band aid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, I will rewrite to loop all security policies to update each of the application certificates on certificate update. I did not think the async overhead was such a concern.

Tuple<X509Certificate2Collection, byte[]> dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate).ConfigureAwait(false);

// update cached values
m_certificateChain[certificate.Thumbprint] = dictionaryValue;

return dictionaryValue.Item2;
}

/// <summary>
Expand All @@ -154,51 +160,44 @@
}

// load certificate chain.
var certificateChain = new X509Certificate2Collection(certificate);
var issuers = new List<Opc.Ua.CertificateIdentifier>();
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<X509Certificate2Collection, byte[]>(certificateChain, certificateChainRaw);
Tuple<X509Certificate2Collection, byte[]> dictionaryValue = await LoadCertificateChainFromStoreAsync(certificate).ConfigureAwait(false);

// update cached values
m_certificateChain[certificate.Thumbprint] = dictionaryValue;

return certificateChain;
return dictionaryValue.Item1;
}

/// <summary>
/// Loads the certificate chain for an application certificate from cache.
/// Update the security configuration of the cert type provider.
/// </summary>
/// <param name="certificate">The application certificate.</param>
public X509Certificate2Collection LoadCertificateChain(X509Certificate2 certificate)
/// <param name="securityConfiguration">The new security configuration.</param>
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;
//ToDo intialize internal CertificateValidator after Certificate Update to clear cache of old application certificates
await Task.CompletedTask.ConfigureAwait(false);
}

/// <summary>
/// Update the security configuration of the cert type provider.
/// Builds the chain using the Issuer and Trusted Stores of the certificateValidator
/// </summary>
/// <param name="securityConfiguration">The new security configuration.</param>
public void Update(SecurityConfiguration securityConfiguration)
/// <param name="certificate">the certificate to load the chain for</param>
/// <returns></returns>
private async Task<Tuple<X509Certificate2Collection, byte[]>> LoadCertificateChainFromStoreAsync(X509Certificate2 certificate)
{
m_securityConfiguration = securityConfiguration;
var certificateChain = new X509Certificate2Collection(certificate);
var issuers = new List<Opc.Ua.CertificateIdentifier>();
if (await m_certificateValidator.GetIssuers(certificate, issuers).ConfigureAwait(false))
{
for (int i = 0; i < issuers.Count; i++)
{
certificateChain.Add(issuers[i].Certificate);

Check warning on line 195 in Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs

View check run for this annotation

Codecov / codecov/patch

Stack/Opc.Ua.Core/Security/Certificates/CertificateTypesProvider.cs#L195

Added line #L195 was not covered by tests
}
}

byte[] certificateChainRaw = Utils.CreateCertificateChainBlob(certificateChain);
return new Tuple<X509Certificate2Collection, byte[]>(certificateChain, certificateChainRaw);
}

CertificateValidator m_certificateValidator;
Expand Down
21 changes: 14 additions & 7 deletions Stack/Opc.Ua.Core/Stack/Server/ServerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -604,22 +604,20 @@ public static bool RequireEncryption(EndpointDescription description)
/// Sets the Server Certificate in an Endpoint description if the description requires encryption.
/// </summary>
/// <param name="description">the endpoint Description to set the server certificate</param>
/// <param name="sendCertificateChain">true if the certificate chain shall be sent</param>
/// <param name="certificateTypesProvider">The provider to get the server certificate per certificate type.</param>
/// <param name="checkRequireEncryption">only set certificate if the endpoint does require Encryption</param>
public static void SetServerCertificateInEndpointDescription(
public static async Task SetServerCertificateInEndpointDescriptionAsync(
EndpointDescription description,
bool sendCertificateChain,
CertificateTypesProvider certificateTypesProvider,
bool checkRequireEncryption = true)
{
if (!checkRequireEncryption || RequireEncryption(description))
{
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).ConfigureAwait(false);
}
else
{
Expand Down Expand Up @@ -796,7 +794,16 @@ protected virtual EndpointBase GetEndpointInstance(ServerBase server)
/// </summary>
protected virtual void OnCertificateUpdate(object sender, CertificateUpdateEventArgs e)
{
InstanceCertificateTypesProvider.Update(e.SecurityConfiguration);
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);
Expand Down
6 changes: 2 additions & 4 deletions Stack/Opc.Ua.Core/Stack/Tcp/TcpServiceHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ public List<EndpointDescription> CreateServiceHost(
uri.Host = computerName;
}

bool sendCertificateChain = instanceCertificateTypesProvider.SendCertificateChain;
ITransportListener listener = this.Create();
if (listener != null)
{
Expand All @@ -96,10 +95,9 @@ public List<EndpointDescription> CreateServiceHost(
};
description.UserIdentityTokens = serverBase.GetUserTokenPolicies(configuration, description);

ServerBase.SetServerCertificateInEndpointDescription(
ServerBase.SetServerCertificateInEndpointDescriptionAsync(
description,
sendCertificateChain,
instanceCertificateTypesProvider);
instanceCertificateTypesProvider).GetAwaiter().GetResult();

listenerEndpoints.Add(description);
}
Expand Down
2 changes: 1 addition & 1 deletion Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
try
{
var channelIdString = globalChannelId.Substring(ListenerId.Length + 1);
var channelId = Convert.ToUInt32(channelIdString);

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Configuration

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Configuration

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client.ComplexTypes

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Gds

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Core

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Security.Certificates

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Security.Certificates

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-PubSub

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Server

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / test-windows-latest-Client

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

Check warning on line 188 in Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The behavior of 'Convert.ToUInt32(string)' could vary based on the current user's locale settings. Replace this call in 'TcpTransportListener.UpdateChannelLastActiveTime(string)' with a call to 'Convert.ToUInt32(string, IFormatProvider)'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1305)

TcpListenerChannel channel = null;
if (channelId > 0 &&
Expand Down Expand Up @@ -490,7 +490,7 @@
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
Expand Down
4 changes: 2 additions & 2 deletions Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ protected void ReadAsymmetricMessageHeader(

if (loadChain)
{
m_serverCertificateChain = m_serverCertificateTypesProvider?.LoadCertificateChain(receiverCertificate);
m_serverCertificateChain = m_serverCertificateTypesProvider?.LoadCertificateChainAsync(receiverCertificate).GetAwaiter().GetResult();
}
}
else
Expand Down Expand Up @@ -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;
}
Expand Down
Loading