Skip to content

Commit

Permalink
Openstore returns null if CertificateIdentifier is empty (#2761)
Browse files Browse the repository at this point in the history
- OpenStore behavior changed to return null if the storepath or storetype are invalid or empty, which is a change to the previous release. Hence some support functions should check the return value of OpenStore.
- Fix: Check for null in LoadPrivateKeyEx
- in addition: try fix the new flaky test issue
- fix #2637: an exception when the https server starts and a X509Store certificate has a non exportable private key, it is not needed to create a copy for the TLS transport. Ignore the error and start the cert with the original cert.
- restructure which platforms are supported by https and complextypes, to avoid dependency collisions. Remove netcoreapp3.1 (eol), support netstandard2.0/2.1 instead.

---------

Co-authored-by: Hoelterhoff, Noah <[email protected]>
  • Loading branch information
mregen and NoahHoelterhoff authored Sep 12, 2024
1 parent 436ca73 commit cf2e788
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 18 deletions.
4 changes: 3 additions & 1 deletion .azurepipelines/signlistDebug.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Stack\Opc.Ua.Core\bin\Debug\net472\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Debug\net48\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Debug\net6.0\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Debug\net8.0\Opc.Ua.Core.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\netcoreapp3.1\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\netstandard2.0\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\netstandard2.1\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\net472\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\net48\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Debug\net6.0\Opc.Ua.Bindings.Https.dll
Expand All @@ -22,6 +23,7 @@ Libraries\Opc.Ua.Client\bin\Debug\net48\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client\bin\Debug\net6.0\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client\bin\Debug\net8.0\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net462\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net472\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net48\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Debug\net6.0\Opc.Ua.Client.ComplexTypes.dll
Expand Down
4 changes: 3 additions & 1 deletion .azurepipelines/signlistRelease.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Stack\Opc.Ua.Core\bin\Release\net472\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Release\net48\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Release\net6.0\Opc.Ua.Core.dll
Stack\Opc.Ua.Core\bin\Release\net8.0\Opc.Ua.Core.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\netcoreapp3.1\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\netstandard2.0\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\netstandard2.1\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\net472\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\net48\Opc.Ua.Bindings.Https.dll
Stack\Opc.Ua.Bindings.Https\bin\Release\net6.0\Opc.Ua.Bindings.Https.dll
Expand All @@ -22,6 +23,7 @@ Libraries\Opc.Ua.Client\bin\Release\net48\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client\bin\Release\net6.0\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client\bin\Release\net8.0\Opc.Ua.Client.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\netstandard2.1\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net462\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net472\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net48\Opc.Ua.Client.ComplexTypes.dll
Libraries\Opc.Ua.Client.ComplexTypes\bin\Release\net6.0\Opc.Ua.Client.ComplexTypes.dll
Expand Down
27 changes: 20 additions & 7 deletions Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System.IO;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -262,17 +263,29 @@ public void Start()
{
Startup.Listener = this;
m_hostBuilder = new WebHostBuilder();

// prepare the server TLS certificate
var serverCertificate = m_serverCertificate;
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1 || NET472_OR_GREATER || NET5_0_OR_GREATER
try
{
// Create a copy of the certificate with the private key on platforms
// which default to the ephemeral KeySet. Also a new certificate must be reloaded.
// If the key fails to copy, its probably a non exportable key from the X509Store.
// Then we can use the original certificate, the private key is already in the key store.
serverCertificate = X509Utils.CreateCopyWithPrivateKey(m_serverCertificate, false);
}
catch (CryptographicException ce)
{
Utils.LogTrace("Copy of the private key for https was denied: {0}", ce.Message);
}
#endif

var httpsOptions = new HttpsConnectionAdapterOptions() {
CheckCertificateRevocation = false,
ClientCertificateMode = ClientCertificateMode.NoCertificate,
// note: this is the TLS certificate!
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1 || NET472_OR_GREATER || NET5_0_OR_GREATER
// Create a copy of the certificate with the private key on platforms
// which default to the ephemeral KeySet.
ServerCertificate = X509Utils.CreateCopyWithPrivateKey(m_serverCertificate, false)
#else
ServerCertificate = m_serverCertificate
#endif
ServerCertificate = serverCertificate,
};

#if NET462
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,14 @@ public async Task<X509Certificate2> LoadPrivateKeyEx(ICertificatePasswordProvide
var certificateStoreIdentifier = new CertificateStoreIdentifier(this.StorePath, this.StoreType, false);
using (ICertificateStore store = certificateStoreIdentifier.OpenStore())
{
if (store.SupportsLoadPrivateKey)
if (store?.SupportsLoadPrivateKey == true)
{
string password = passwordProvider?.GetPassword(this);
m_certificate = await store.LoadPrivateKey(this.Thumbprint, this.SubjectName, password).ConfigureAwait(false);
return m_certificate;
}
}
return null;
}
return await Find(true).ConfigureAwait(false);
}
Expand All @@ -202,6 +203,11 @@ public async Task<X509Certificate2> Find(bool needPrivateKey)
var certificateStoreIdentifier = new CertificateStoreIdentifier(StorePath, false);
using (ICertificateStore store = certificateStoreIdentifier.OpenStore())
{
if (store == null)
{
return null;
}

X509Certificate2Collection collection = await store.Enumerate().ConfigureAwait(false);

certificate = Find(collection, m_thumbprint, m_subjectName, needPrivateKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,21 @@ public void Dispose()
m_trustedStore = null;
m_rejectedStore = null;
var path = Utils.ReplaceSpecialFolderNames(m_pkiRoot);
if (Directory.Exists(path))
int retries = 5;
while (retries-- > 0)
{
Directory.Delete(path, true);
try
{
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}
retries = 0;
}
catch (IOException)
{
Thread.Sleep(1000);
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions Tests/Opc.Ua.Core.Tests/Stack/Client/ClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ public void DiscoveryEndPointUrls(string urlString)

Assert.AreEqual(uri.OriginalString, uriBuilder.Uri.OriginalString);
}

[Test]
public void ValidateAppConfigWithoutAppCert()
{
var appConfig = new ApplicationConfiguration() {
ApplicationName = "Test",
ClientConfiguration = new ClientConfiguration() {},
SecurityConfiguration = new SecurityConfiguration() {
ApplicationCertificate = new CertificateIdentifier()
}
};
Assert.DoesNotThrow(() => appConfig.Validate(ApplicationType.Client).GetAwaiter().GetResult());
}
#endregion
}
}
12 changes: 6 additions & 6 deletions targets.props
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
<LibTargetFrameworks>netstandard2.1</LibTargetFrameworks>
<LibCoreTargetFrameworks>netstandard2.1</LibCoreTargetFrameworks>
<LibxTargetFrameworks>netstandard2.1</LibxTargetFrameworks>
<HttpsTargetFrameworks>netcoreapp3.1</HttpsTargetFrameworks>
<HttpsTargetFrameworks>netstandard2.1</HttpsTargetFrameworks>
</PropertyGroup>
</When>
<When Condition="'$(CustomTestTarget)' == 'net48'">
Expand Down Expand Up @@ -108,9 +108,9 @@
<HttpsTargetFrameworks>net8.0</HttpsTargetFrameworks>
</PropertyGroup>
</When>
<!-- Note: .NET Core 2.x and 3.x is end of life, removed netcoreapp2.1/3.1 from any target except https. -->
<!-- Note: .NET Framework 4.6.2 deprecated for 1.4.372, removed net462 from any target. -->
<!-- Visual Studio 2022, supports .NET Framework 4.7.2/4.8, .NET Standard2.1, .NET 6 and .NET 8-->
<!-- Note: .NET Core 2.x and 3.x is end of life, removed netcoreapp2.1/3.1 from any target. -->
<!-- Note: .NET Framework 4.6.2 deprecated for 1.4.372, removed net462 from any target except complex types. -->
<!-- Visual Studio 2022, supports .NET Framework 4.7.2/4.8, .NET Standard2.0/2.1, .NET 6 and .NET 8-->
<When Condition="'$(VisualStudioVersion)' == '17.0'">
<PropertyGroup>
<AnalysisLevel>preview-all</AnalysisLevel>
Expand All @@ -119,8 +119,8 @@
<TestsTargetFrameworks>net48;net8.0</TestsTargetFrameworks>
<LibTargetFrameworks>net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0</LibTargetFrameworks>
<LibCoreTargetFrameworks>net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0</LibCoreTargetFrameworks>
<LibxTargetFrameworks>net472;net48;netstandard2.1;net6.0;net8.0</LibxTargetFrameworks>
<HttpsTargetFrameworks>net472;net48;netcoreapp3.1;net6.0;net8.0</HttpsTargetFrameworks>
<LibxTargetFrameworks>net462;net472;net48;netstandard2.1;net6.0;net8.0</LibxTargetFrameworks>
<HttpsTargetFrameworks>net472;net48;netstandard2.0;netstandard2.1;net6.0;net8.0</HttpsTargetFrameworks>
</PropertyGroup>
</When>
<!-- Visual Studio 2019, supports .NET Framework 4.8 and .NET Core 3.1 -->
Expand Down

0 comments on commit cf2e788

Please sign in to comment.