Skip to content

Commit

Permalink
Improve Linux root certificate store change detection involving symlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
wfurt authored Mar 25, 2021
1 parent 6496624 commit 400311b
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@ internal sealed class CachedSystemStoreProvider : IStorePal
// followed by a reboot for a kernel update, etc).
// Customers requested something more often than "never" and 5 minutes seems like a reasonable
// balance.
//
// Note that on Ubuntu the LastWrite test always fails, because the system default for SSL_CERT_DIR
// is a symlink, so the LastWrite value is always just when the symlink was created (and Ubuntu does
// not provide the single-file version at SSL_CERT_FILE, so the file update does not trigger) --
// meaning the "assume invalid" interval is Ubuntu's only refresh.
private static readonly TimeSpan s_lastWriteRecheckInterval = TimeSpan.FromSeconds(5);
private static readonly TimeSpan s_assumeInvalidInterval = TimeSpan.FromMinutes(5);
private static readonly Stopwatch s_recheckStopwatch = new Stopwatch();
private static readonly DirectoryInfo? s_rootStoreDirectoryInfo = SafeOpenRootDirectoryInfo();
private static readonly DirectoryInfo? s_rootLinkedStoreDirectoryInfo = SafeOpenLinkedRootDirectoryInfo();
private static readonly FileInfo? s_rootStoreFileInfo = SafeOpenRootFileInfo();

// Use non-Value-Tuple so that it's an atomic update.
private static Tuple<SafeX509StackHandle, SafeX509StackHandle>? s_nativeCollections;
private static DateTime s_directoryCertsLastWrite;
private static DateTime s_linkCertsLastWrite;
private static DateTime s_fileCertsLastWrite;

private readonly bool _isRoot;
Expand Down Expand Up @@ -101,16 +98,19 @@ private static Tuple<SafeX509StackHandle, SafeX509StackHandle> GetCollections()
{
FileInfo? fileInfo = s_rootStoreFileInfo;
DirectoryInfo? dirInfo = s_rootStoreDirectoryInfo;
DirectoryInfo? linkInfo = s_rootLinkedStoreDirectoryInfo;

fileInfo?.Refresh();
dirInfo?.Refresh();
linkInfo?.Refresh();

if (ret == null ||
elapsed > s_assumeInvalidInterval ||
(fileInfo != null && fileInfo.Exists && fileInfo.LastWriteTimeUtc != s_fileCertsLastWrite) ||
(dirInfo != null && dirInfo.Exists && dirInfo.LastWriteTimeUtc != s_directoryCertsLastWrite))
(dirInfo != null && dirInfo.Exists && dirInfo.LastWriteTimeUtc != s_directoryCertsLastWrite) ||
(linkInfo != null && linkInfo.Exists && linkInfo.LastWriteTimeUtc != s_linkCertsLastWrite))
{
ret = LoadMachineStores(dirInfo, fileInfo);
ret = LoadMachineStores(dirInfo, fileInfo, linkInfo);
}
}
}
Expand All @@ -121,7 +121,8 @@ private static Tuple<SafeX509StackHandle, SafeX509StackHandle> GetCollections()

private static Tuple<SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores(
DirectoryInfo? rootStorePath,
FileInfo? rootStoreFile)
FileInfo? rootStoreFile,
DirectoryInfo? linkedRootPath)
{
Debug.Assert(
Monitor.IsEntered(s_recheckStopwatch),
Expand All @@ -134,6 +135,7 @@ private static Tuple<SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores

DateTime newFileTime = default;
DateTime newDirTime = default;
DateTime newLinkTime = default;

var uniqueRootCerts = new HashSet<X509Certificate2>();
var uniqueIntermediateCerts = new HashSet<X509Certificate2>();
Expand All @@ -153,6 +155,11 @@ private static Tuple<SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores
}
}

if (linkedRootPath != null && linkedRootPath.Exists)
{
newLinkTime = linkedRootPath.LastWriteTimeUtc;
}

void ProcessFile(FileInfo file)
{
using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(file.FullName, "rb"))
Expand Down Expand Up @@ -248,6 +255,7 @@ void ProcessFile(FileInfo file)
Volatile.Write(ref s_nativeCollections, newCollections);
s_directoryCertsLastWrite = newDirTime;
s_fileCertsLastWrite = newFileTime;
s_linkCertsLastWrite = newLinkTime;
s_recheckStopwatch.Restart();
return newCollections;
}
Expand Down Expand Up @@ -291,5 +299,43 @@ void ProcessFile(FileInfo file)

return null;
}

private static DirectoryInfo? SafeOpenLinkedRootDirectoryInfo()
{
string? rootDirectory = Interop.Crypto.GetX509RootStorePath();

if (!string.IsNullOrEmpty(rootDirectory))
{
string? linkedDirectory = Interop.Sys.ReadLink(rootDirectory);
if (linkedDirectory == null)
{
return null;
}

if (linkedDirectory[0] == '/')
{
rootDirectory = linkedDirectory;
}
else
{
// relative link
var root = new DirectoryInfo(rootDirectory);
root = new DirectoryInfo(Path.Join(root.Parent?.FullName, linkedDirectory));
rootDirectory = root.FullName;
}

try
{
return new DirectoryInfo(rootDirectory);
}
catch (ArgumentException)
{
// If SSL_CERT_DIR is set to the empty string, or anything else which gives
// "The path is not of a legal form", then the GetX509RootStoreFile value is ignored.
}
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@
Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Permissions.cs"
Link="Common\Interop\Unix\System.Native\Interop.Permissions.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ReadLink.cs"
Link="Common\Interop\Unix\System.Native\Interop.ReadLink.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs"
Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.GetIntegerBytes.cs"
Expand Down

0 comments on commit 400311b

Please sign in to comment.