Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,23 @@ internal static int BioTell(SafeBioHandle bio)
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool PushX509StackField(SafeSharedX509StackHandle stack, SafeX509Handle x509);

internal static string? GetX509RootStorePath()
internal static string? GetX509RootStorePath(out bool defaultPath)
{
return Marshal.PtrToStringAnsi(GetX509RootStorePath_private());
IntPtr ptr = GetX509RootStorePath_private(out byte usedDefault);
defaultPath = (usedDefault != 0);
return Marshal.PtrToStringAnsi(ptr);
}

internal static string? GetX509RootStoreFile()
{
return Marshal.PtrToStringAnsi(GetX509RootStoreFile_private());
return Marshal.PtrToStringAnsi(GetX509RootStoreFile_private(out _));
}

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetX509RootStorePath")]
private static extern IntPtr GetX509RootStorePath_private();
private static extern IntPtr GetX509RootStorePath_private(out byte defaultPath);

[DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetX509RootStoreFile")]
private static extern IntPtr GetX509RootStoreFile_private();
private static extern IntPtr GetX509RootStoreFile_private(out byte defaultPath);

[DllImport(Libraries.CryptoNative)]
private static extern int CryptoNative_X509StoreSetVerifyTime(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,33 @@

#include <assert.h>

const char* CryptoNative_GetX509RootStorePath()
const char* CryptoNative_GetX509RootStorePath(uint8_t* defaultPath)
{
assert(defaultPath != NULL);

const char* dir = getenv(X509_get_default_cert_dir_env());
*defaultPath = 0;

if (!dir)
{
dir = X509_get_default_cert_dir();
*defaultPath = 1;
}

return dir;
}

const char* CryptoNative_GetX509RootStoreFile()
const char* CryptoNative_GetX509RootStoreFile(uint8_t* defaultPath)
{
assert(defaultPath != NULL);

const char* file = getenv(X509_get_default_cert_file_env());
*defaultPath = 0;

if (!file)
{
file = X509_get_default_cert_file();
*defaultPath = 1;
}

return file;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
Look up the directory in which all certificate files therein are considered
trusted (root or trusted intermediate).
*/
PALEXPORT const char* CryptoNative_GetX509RootStorePath(void);
PALEXPORT const char* CryptoNative_GetX509RootStorePath(uint8_t* defaultPath);

/*
Look up the file in which all certificates are considered trusted
(root or trusted intermediate), in addition to those files in
the root store path.
*/
PALEXPORT const char* CryptoNative_GetX509RootStoreFile(void);
PALEXPORT const char* CryptoNative_GetX509RootStoreFile(uint8_t* defaultPath);
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@ internal sealed class CachedSystemStoreProvider : IStorePal
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 DirectoryInfo? s_rootStoreDirectoryInfo = SafeOpenRootDirectoryInfo();
private static bool s_defaultRootDir;
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 @@ -98,19 +97,16 @@ 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) ||
(linkInfo != null && linkInfo.Exists && linkInfo.LastWriteTimeUtc != s_linkCertsLastWrite))
(fileInfo != null && fileInfo.Exists && ContentWriteTime(fileInfo) != s_fileCertsLastWrite) ||
(dirInfo != null && dirInfo.Exists && ContentWriteTime(dirInfo) != s_directoryCertsLastWrite))
{
ret = LoadMachineStores(dirInfo, fileInfo, linkInfo);
ret = LoadMachineStores(dirInfo, fileInfo);
}
}
}
Expand All @@ -121,8 +117,7 @@ private static Tuple<SafeX509StackHandle, SafeX509StackHandle> GetCollections()

private static Tuple<SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores(
DirectoryInfo? rootStorePath,
FileInfo? rootStoreFile,
DirectoryInfo? linkedRootPath)
FileInfo? rootStoreFile)
{
Debug.Assert(
Monitor.IsEntered(s_recheckStopwatch),
Expand All @@ -135,40 +130,65 @@ 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>();
bool firstLoad = (s_nativeCollections == null);

if (rootStoreFile != null && rootStoreFile.Exists)
{
newFileTime = rootStoreFile.LastWriteTimeUtc;
newFileTime = ContentWriteTime(rootStoreFile);
ProcessFile(rootStoreFile);
}

bool hasStoreData = false;

if (rootStorePath != null && rootStorePath.Exists)
{
newDirTime = rootStorePath.LastWriteTimeUtc;
foreach (FileInfo file in rootStorePath.EnumerateFiles())
newDirTime = ContentWriteTime(rootStorePath);
hasStoreData = ProcessDir(rootStorePath);
}

if (firstLoad && !hasStoreData && s_defaultRootDir)
{
DirectoryInfo etcSslCerts = new DirectoryInfo("/etc/ssl/certs");

if (etcSslCerts.Exists)
{
ProcessFile(file);
DateTime tmpTime = ContentWriteTime(etcSslCerts);
hasStoreData = ProcessDir(etcSslCerts);

if (hasStoreData)
{
newDirTime = tmpTime;
s_rootStoreDirectoryInfo = etcSslCerts;
}
}
}

if (linkedRootPath != null && linkedRootPath.Exists)
bool ProcessDir(DirectoryInfo dir)
{
newLinkTime = linkedRootPath.LastWriteTimeUtc;
bool hasStoreData = false;

foreach (FileInfo file in dir.EnumerateFiles())
{
hasStoreData |= ProcessFile(file);
}

return hasStoreData;
}

void ProcessFile(FileInfo file)
bool ProcessFile(FileInfo file)
{
bool readData = false;

using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(file.FullName, "rb"))
{
// The handle may be invalid, for example when we don't have read permission for the file.
if (fileBio.IsInvalid)
{
Interop.Crypto.ErrClearError();
return;
return false;
}

// Some distros ship with two variants of the same certificate.
Expand All @@ -181,6 +201,7 @@ void ProcessFile(FileInfo file)
while (OpenSslX509CertificateReader.TryReadX509PemNoAux(fileBio, out pal) ||
OpenSslX509CertificateReader.TryReadX509Der(fileBio, out pal))
{
readData = true;
X509Certificate2 cert = new X509Certificate2(pal);

// The HashSets are just used for uniqueness filters, they do not survive this method.
Expand Down Expand Up @@ -226,6 +247,8 @@ void ProcessFile(FileInfo file)
cert.Dispose();
}
}

return readData;
}

foreach (X509Certificate2 cert in uniqueRootCerts)
Expand Down Expand Up @@ -255,7 +278,6 @@ 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 All @@ -282,7 +304,7 @@ void ProcessFile(FileInfo file)

private static DirectoryInfo? SafeOpenRootDirectoryInfo()
{
string? rootDirectory = Interop.Crypto.GetX509RootStorePath();
string? rootDirectory = Interop.Crypto.GetX509RootStorePath(out s_defaultRootDir);

if (!string.IsNullOrEmpty(rootDirectory))
{
Expand All @@ -300,42 +322,68 @@ void ProcessFile(FileInfo file)
return null;
}

private static DirectoryInfo? SafeOpenLinkedRootDirectoryInfo()
private static DateTime ContentWriteTime(FileInfo info)
{
string? rootDirectory = Interop.Crypto.GetX509RootStorePath();
string path = info.FullName;
string? target = Interop.Sys.ReadLink(path);

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

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;
}
if (target[0] != '/')
{
target = Path.Join(info.Directory?.FullName, target);
}

try
try
{
var targetInfo = new FileInfo(target);

if (targetInfo.Exists)
{
return new DirectoryInfo(rootDirectory);
return targetInfo.LastWriteTimeUtc;
}
catch (ArgumentException)
}
catch (ArgumentException)
{
// If we can't load information about the link path, just treat it as not a link.
}

return info.LastWriteTimeUtc;
}

private static DateTime ContentWriteTime(DirectoryInfo info)
{
string path = info.FullName;
string? target = Interop.Sys.ReadLink(path);

if (string.IsNullOrEmpty(target))
{
return info.LastWriteTimeUtc;
}

if (target[0] != '/')
{
target = Path.Join(info.Parent?.FullName, target);
}

try
{
var targetInfo = new DirectoryInfo(target);

if (targetInfo.Exists)
{
// 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 targetInfo.LastWriteTimeUtc;
}
}
catch (ArgumentException)
{
// If we can't load information about the link path, just treat it as not a link.
}

return null;
return info.LastWriteTimeUtc;
}
}
}