Skip to content

Commit

Permalink
Fix a problem when a new private key can not be reloaded in virtualiz…
Browse files Browse the repository at this point in the history
…ed environments.
  • Loading branch information
mregen committed Jan 21, 2022
1 parent 27caf0c commit ae9ecac
Showing 1 changed file with 100 additions and 54 deletions.
154 changes: 100 additions & 54 deletions Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.Security.Certificates;

Expand Down Expand Up @@ -286,74 +287,117 @@ public X509Certificate2 LoadPrivateKey(string thumbprint, string subjectName, st
return null;
}

foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der"))
// on some sytems, specifically in virtualized environments,
// reloading a previously saved private key may fail on the first attempt.
int retryCounter = 3;
while (retryCounter-- > 0)
{
try
bool certificateFound = false;
Exception importException = null;
foreach (FileInfo file in m_certificateSubdir.GetFiles("*.der"))
{
X509Certificate2 certificate = new X509Certificate2(file.FullName);

if (!String.IsNullOrEmpty(thumbprint))
try
{
if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
}
X509Certificate2 certificate = new X509Certificate2(file.FullName);

if (!String.IsNullOrEmpty(subjectName))
{
if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject))
if (!String.IsNullOrEmpty(thumbprint))
{
if (subjectName.Contains('='))
if (!string.Equals(certificate.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase))
{
continue;
}
}

if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.OrdinalIgnoreCase)))
if (!String.IsNullOrEmpty(subjectName))
{
if (!X509Utils.CompareDistinguishedName(subjectName, certificate.Subject))
{
continue;
}
if (subjectName.Contains('='))
{
continue;
}

if (!X509Utils.ParseDistinguishedName(certificate.Subject).Any(s => s.Equals("CN=" + subjectName, StringComparison.OrdinalIgnoreCase)))
{
continue;
}

}
}
}

string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length);
string fileRoot = file.Name.Substring(0, file.Name.Length - file.Extension.Length);

StringBuilder filePath = new StringBuilder()
.Append(m_privateKeySubdir.FullName)
.Append(Path.DirectorySeparatorChar)
.Append(fileRoot);
StringBuilder filePath = new StringBuilder()
.Append(m_privateKeySubdir.FullName)
.Append(Path.DirectorySeparatorChar)
.Append(fileRoot);

X509KeyStorageFlags[] storageFlags = {
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet,
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet
};
X509KeyStorageFlags[] storageFlags = {
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet,
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.UserKeySet
};

FileInfo privateKeyFile = new FileInfo(filePath.ToString() + ".pfx");
password = password ?? String.Empty;
foreach (var flag in storageFlags)
{
try
FileInfo privateKeyFile = new FileInfo(filePath.ToString() + ".pfx");
if (!privateKeyFile.Exists)
{
certificate = new X509Certificate2(
privateKeyFile.FullName,
password,
flag);
if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true))
{
return certificate;
}
Utils.Trace(Utils.TraceMasks.Security, "A private key for the certificate with thumbprint [{0}] does not exist.", certificate.Thumbprint);
continue;
}
catch (Exception)
certificateFound = true;
password = password ?? String.Empty;
foreach (var flag in storageFlags)
{
certificate?.Dispose();
certificate = null;
try
{
certificate = new X509Certificate2(
privateKeyFile.FullName,
password,
flag);
if (X509Utils.VerifyRSAKeyPair(certificate, certificate, true))
{
Utils.Trace(Utils.TraceMasks.Security, "Imported the private key for [{0}].", certificate.Thumbprint);
return certificate;
}
}
catch (Exception ex)
{
importException = ex;
certificate?.Dispose();
certificate = null;
}
}
}
catch (Exception e)
{
Utils.Trace(e, "Could not load private key for certificate {0}", subjectName);
}
}
catch (Exception e)

// found a certificate, but some error occurred
if (certificateFound)
{
Utils.Trace(e, "Could not load private key for certificate " + subjectName);
Utils.Trace(Utils.TraceMasks.Security, "The private key for the certificate with subject {0} failed to import.", subjectName);
if (importException != null)
{
Utils.Trace(importException, "Certificate import error: {0}", importException.Message);
}
}
else
{
if (!String.IsNullOrEmpty(thumbprint))
{
Utils.Trace(Utils.TraceMasks.Security, "A Private key for the certificate with thumbpint {0} was not found.", thumbprint);
}
// if no certificate was found, no need to retry
break;
}

// retry within a few ms
if (retryCounter > 0)
{
const int retryDelay = 100;
Utils.Trace(Utils.TraceMasks.Security, "Retry to import private key after {0} ms.", retryDelay);
Thread.Sleep(retryDelay);
}
}

Expand Down Expand Up @@ -795,16 +839,18 @@ private void WriteFile(byte[] data, string fileName, bool includePrivateKey)
}

// write file.
BinaryWriter writer = new BinaryWriter(fileInfo.Open(FileMode.Create));

try
using (FileStream fileStream = fileInfo.Open(FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
writer.Write(data);
}
finally
{
writer.Flush();
writer.Dispose();
try
{
writer.Write(data);
}
finally
{
writer.Flush();
fileStream.Flush();
}
}

m_certificateSubdir.Refresh();
Expand Down

0 comments on commit ae9ecac

Please sign in to comment.