Skip to content

Commit d653ab0

Browse files
committed
Add ability to trust certificate in windows store from WSL
1 parent e40de82 commit d653ab0

File tree

2 files changed

+77
-0
lines changed

2 files changed

+77
-0
lines changed

src/Shared/CertificateGeneration/CertificateManager.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,15 @@ public sealed class CertificateManagerEventSource : EventSource
13131313

13141314
[Event(112, Level = EventLevel.Warning, Message = "Directory '{0}' may be readable by other users.")]
13151315
internal void DirectoryPermissionsNotSecure(string directoryPath) => WriteEvent(112, directoryPath);
1316+
1317+
[Event(113, Level = EventLevel.Verbose, Message = "Successfully trusted the certificate in the Windows certificate store via WSL.")]
1318+
internal void WslWindowsTrustSucceeded() => WriteEvent(113);
1319+
1320+
[Event(114, Level = EventLevel.Warning, Message = "Failed to trust the certificate in the Windows certificate store via WSL.")]
1321+
internal void WslWindowsTrustFailed() => WriteEvent(114);
1322+
1323+
[Event(115, Level = EventLevel.Warning, Message = "Failed to trust the certificate in the Windows certificate store via WSL: {0}.")]
1324+
internal void WslWindowsTrustException(string exceptionMessage) => WriteEvent(115, exceptionMessage);
13161325
}
13171326

13181327
internal sealed class UserCancelledTrustException : Exception

src/Shared/CertificateGeneration/UnixCertificateManager.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ internal sealed partial class UnixCertificateManager : CertificateManager
3232
private const string BrowserFamilyChromium = "Chromium";
3333
private const string BrowserFamilyFirefox = "Firefox";
3434

35+
private const string PowerShellCommand = "powershell.exe";
36+
private const string WslInteropPath = "/proc/sys/fs/binfmt_misc/WSLInterop";
37+
private const string WslInteropLatePath = "/proc/sys/fs/binfmt_misc/WSLInterop-late";
38+
3539
private const string OpenSslCommand = "openssl";
3640
private const string CertUtilCommand = "certutil";
3741

@@ -365,6 +369,21 @@ protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate)
365369
}
366370
}
367371

372+
// Check to see if we're running in WSL; if so, use powershell.exe to add the certificate to the Windows trust store as well
373+
if (IsRunningOnWsl())
374+
{
375+
if (TryTrustCertificateInWindowsStore(certPath))
376+
{
377+
Log.WslWindowsTrustSucceeded();
378+
sawTrustSuccess = true;
379+
}
380+
else
381+
{
382+
Log.WslWindowsTrustFailed();
383+
sawTrustFailure = true;
384+
}
385+
}
386+
368387
return sawTrustFailure
369388
? TrustLevel.Partial
370389
: TrustLevel.Full;
@@ -564,6 +583,55 @@ private static string GetCertificateNickname(X509Certificate2 certificate)
564583
return $"aspnetcore-localhost-{certificate.Thumbprint}";
565584
}
566585

586+
/// <summary>
587+
/// Detects if the current environment is Windows Subsystem for Linux (WSL).
588+
/// </summary>
589+
/// <returns>True if running on WSL; otherwise, false.</returns>
590+
private static bool IsRunningOnWsl()
591+
{
592+
// WSL exposes special files that indicate WSL interop is enabled.
593+
// Either WSLInterop or WSLInterop-late may be present depending on the WSL version and configuration.
594+
return File.Exists(WslInteropPath) || File.Exists(WslInteropLatePath);
595+
}
596+
597+
/// <summary>
598+
/// Attempts to trust the certificate in the Windows certificate store via PowerShell when running on WSL.
599+
/// </summary>
600+
/// <param name="certificatePath">The path to the certificate file (PEM format).</param>
601+
/// <returns>True if the certificate was successfully added to the Windows store; otherwise, false.</returns>
602+
private static bool TryTrustCertificateInWindowsStore(string certificatePath)
603+
{
604+
// PowerShell command to import the certificate into the CurrentUser Root store.
605+
// We use Import-Certificate which can handle PEM files on modern Windows.
606+
// The -CertStoreLocation parameter specifies the store location.
607+
var escapedPath = certificatePath.Replace("'", "''");
608+
var powershellScript = $@"
609+
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('{escapedPath}')
610+
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root', 'CurrentUser')
611+
$store.Open('ReadWrite')
612+
$store.Add($cert)
613+
$store.Close()
614+
";
615+
616+
var startInfo = new ProcessStartInfo(PowerShellCommand, $"-NoProfile -NonInteractive -Command \"{powershellScript}\"")
617+
{
618+
RedirectStandardOutput = true,
619+
RedirectStandardError = true,
620+
};
621+
622+
try
623+
{
624+
using var process = Process.Start(startInfo)!;
625+
process.WaitForExit();
626+
return process.ExitCode == 0;
627+
}
628+
catch (Exception ex)
629+
{
630+
Log.WslWindowsTrustException(ex.Message);
631+
return false;
632+
}
633+
}
634+
567635
/// <remarks>
568636
/// It is the caller's responsibility to ensure that <see cref="CertUtilCommand"/> is available.
569637
/// </remarks>

0 commit comments

Comments
 (0)