@@ -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