[Platform] Provide a better error message when the developer certificate can't be used (#16659)
Improves the error message Kestrel gives when the developer certificate key is not available for some reason.
This commit is contained in:
parent
8acff52cf0
commit
3ceca46c5b
|
|
@ -617,4 +617,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="Http2TellClientToCalmDown" xml:space="preserve">
|
<data name="Http2TellClientToCalmDown" xml:space="preserve">
|
||||||
<value>A new stream was refused because this connection has too many streams that haven't finished processing. This may happen if many streams are aborted but not yet cleaned up.</value>
|
<value>A new stream was refused because this connection has too many streams that haven't finished processing. This may happen if many streams are aborted but not yet cleaned up.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="BadDeveloperCertificateState" xml:space="preserve">
|
||||||
|
<value>The ASP.NET Core developer certificate is in an invalid state. To fix this issue, run the following commands 'dotnet dev-certs https --clean' and 'dotnet dev-certs https' to remove all existing ASP.NET Core development certificates and create a new untrusted developer certificate. On macOS or Windows, use 'dotnet dev-certs https --trust' to trust the new certificate.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
|
var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var certificateManager = new CertificateManager();
|
DefaultCertificate = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
|
||||||
DefaultCertificate = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true)
|
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
if (DefaultCertificate != null)
|
if (DefaultCertificate != null)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Security.Authentication;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Certificates.Generation;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Connections.Features;
|
using Microsoft.AspNetCore.Connections.Features;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -208,12 +209,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Https.Internal
|
||||||
await sslStream.DisposeAsync();
|
await sslStream.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException || ex is AuthenticationException)
|
catch (IOException ex)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
_logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
||||||
await sslStream.DisposeAsync();
|
await sslStream.DisposeAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
catch (AuthenticationException ex)
|
||||||
|
{
|
||||||
|
if (_serverCertificate == null ||
|
||||||
|
!CertificateManager.IsHttpsDevelopmentCertificate(_serverCertificate) ||
|
||||||
|
CertificateManager.CheckDeveloperCertificateKey(_serverCertificate))
|
||||||
|
{
|
||||||
|
_logger?.LogDebug(1, ex, CoreStrings.AuthenticationFailed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger?.LogError(2, ex, CoreStrings.BadDeveloperCertificateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sslStream.DisposeAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol;
|
feature.ApplicationProtocol = sslStream.NegotiatedApplicationProtocol.Protocol;
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
new TestServiceContext(LoggerFactory),
|
new TestServiceContext(LoggerFactory),
|
||||||
listenOptions =>
|
listenOptions =>
|
||||||
{
|
{
|
||||||
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
listenOptions.UseHttps(TestResources.GetTestCertificate("no_extensions.pfx"));
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
using (var connection = server.CreateConnection())
|
using (var connection = server.CreateConnection())
|
||||||
|
|
@ -383,6 +383,35 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
Assert.Equal(LogLevel.Debug, loggerProvider.FilterLogger.LastLogLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DevCertWithInvalidPrivateKeyProducesCustomWarning()
|
||||||
|
{
|
||||||
|
var loggerProvider = new HandshakeErrorLoggerProvider();
|
||||||
|
LoggerFactory.AddProvider(loggerProvider);
|
||||||
|
|
||||||
|
await using (var server = new TestServer(context => Task.CompletedTask,
|
||||||
|
new TestServiceContext(LoggerFactory),
|
||||||
|
listenOptions =>
|
||||||
|
{
|
||||||
|
listenOptions.UseHttps(TestResources.GetTestCertificate());
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
using (var connection = server.CreateConnection())
|
||||||
|
using (var sslStream = new SslStream(connection.Stream, true, (sender, certificate, chain, errors) => true))
|
||||||
|
{
|
||||||
|
// SslProtocols.Tls is TLS 1.0 which isn't supported by Kestrel by default.
|
||||||
|
await Assert.ThrowsAsync<IOException>(() =>
|
||||||
|
sslStream.AuthenticateAsClientAsync("127.0.0.1", clientCertificates: null,
|
||||||
|
enabledSslProtocols: SslProtocols.Tls,
|
||||||
|
checkCertificateRevocation: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await loggerProvider.FilterLogger.LogTcs.Task.DefaultTimeout();
|
||||||
|
Assert.Equal(2, loggerProvider.FilterLogger.LastEventId);
|
||||||
|
Assert.Equal(LogLevel.Error, loggerProvider.FilterLogger.LastLogLevel);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task OnAuthenticate_SeesOtherSettings()
|
public async Task OnAuthenticate_SeesOtherSettings()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,13 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
||||||
|
|
||||||
// Setting to 0 means we don't append the version byte,
|
// Setting to 0 means we don't append the version byte,
|
||||||
// which is what all machines currently have.
|
// which is what all machines currently have.
|
||||||
public int AspNetHttpsCertificateVersion { get; set; } = 1;
|
public static int AspNetHttpsCertificateVersion { get; set; } = 1;
|
||||||
|
|
||||||
public IList<X509Certificate2> ListCertificates(
|
public static bool IsHttpsDevelopmentCertificate(X509Certificate2 certificate) =>
|
||||||
|
certificate.Extensions.OfType<X509Extension>()
|
||||||
|
.Any(e => string.Equals(AspNetHttpsOid, e.Oid.Value, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
public static IList<X509Certificate2> ListCertificates(
|
||||||
CertificatePurpose purpose,
|
CertificatePurpose purpose,
|
||||||
StoreName storeName,
|
StoreName storeName,
|
||||||
StoreLocation location,
|
StoreLocation location,
|
||||||
|
|
@ -228,6 +232,33 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
||||||
return certificate;
|
return certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool CheckDeveloperCertificateKey(X509Certificate2 candidate)
|
||||||
|
{
|
||||||
|
// Tries to use the certificate key to validate it can't access it
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var rsa = candidate.GetRSAPrivateKey();
|
||||||
|
if (rsa == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypting a random value is the ultimate test for a key validity.
|
||||||
|
// Windows and Mac OS both return HasPrivateKey = true if there is (or there has been) a private key associated
|
||||||
|
// with the certificate at some point.
|
||||||
|
var value = new byte[32];
|
||||||
|
RandomNumberGenerator.Fill(value);
|
||||||
|
rsa.Decrypt(rsa.Encrypt(value, RSAEncryptionPadding.Pkcs1), RSAEncryptionPadding.Pkcs1);
|
||||||
|
|
||||||
|
// Being able to encrypt and decrypt a payload is the strongest guarantee that the key is valid.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public X509Certificate2 CreateSelfSignedCertificate(
|
public X509Certificate2 CreateSelfSignedCertificate(
|
||||||
X500DistinguishedName subject,
|
X500DistinguishedName subject,
|
||||||
IEnumerable<X509Extension> extensions,
|
IEnumerable<X509Extension> extensions,
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
Assert.NotNull(exportedCertificate);
|
Assert.NotNull(exportedCertificate);
|
||||||
Assert.False(exportedCertificate.HasPrivateKey);
|
Assert.False(exportedCertificate.HasPrivateKey);
|
||||||
|
|
||||||
var httpsCertificates = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
|
var httpsCertificates = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
|
||||||
var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
|
var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
|
||||||
Assert.True(httpsCertificate.HasPrivateKey);
|
Assert.True(httpsCertificate.HasPrivateKey);
|
||||||
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
|
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
|
||||||
|
|
@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||||
e => e.Critical == false &&
|
e => e.Critical == false &&
|
||||||
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" &&
|
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" &&
|
||||||
e.RawData[0] == _manager.AspNetHttpsCertificateVersion);
|
e.RawData[0] == CertificateManager.AspNetHttpsCertificateVersion);
|
||||||
|
|
||||||
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
|
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||||
|
|
||||||
var httpsCertificate = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
|
var httpsCertificate = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject);
|
var result = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject);
|
||||||
|
|
@ -164,9 +164,9 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||||
|
|
||||||
_manager.AspNetHttpsCertificateVersion = 2;
|
CertificateManager.AspNetHttpsCertificateVersion = 2;
|
||||||
|
|
||||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
var httpsCertificateList = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||||
Assert.Empty(httpsCertificateList);
|
Assert.Empty(httpsCertificateList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,12 +178,12 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
|
|
||||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||||
_manager.AspNetHttpsCertificateVersion = 0;
|
CertificateManager.AspNetHttpsCertificateVersion = 0;
|
||||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||||
|
|
||||||
_manager.AspNetHttpsCertificateVersion = 1;
|
CertificateManager.AspNetHttpsCertificateVersion = 1;
|
||||||
|
|
||||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
var httpsCertificateList = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||||
Assert.Empty(httpsCertificateList);
|
Assert.Empty(httpsCertificateList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,10 +195,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
|
|
||||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||||
_manager.AspNetHttpsCertificateVersion = 0;
|
CertificateManager.AspNetHttpsCertificateVersion = 0;
|
||||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||||
|
|
||||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
var httpsCertificateList = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||||
Assert.NotEmpty(httpsCertificateList);
|
Assert.NotEmpty(httpsCertificateList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,11 +210,11 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
|
|
||||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||||
_manager.AspNetHttpsCertificateVersion = 2;
|
CertificateManager.AspNetHttpsCertificateVersion = 2;
|
||||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||||
|
|
||||||
_manager.AspNetHttpsCertificateVersion = 1;
|
CertificateManager.AspNetHttpsCertificateVersion = 1;
|
||||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
var httpsCertificateList = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||||
Assert.NotEmpty(httpsCertificateList);
|
Assert.NotEmpty(httpsCertificateList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,10 +241,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||||
|
|
||||||
_manager.CleanupHttpsCertificates(TestCertificateSubject);
|
_manager.CleanupHttpsCertificates(TestCertificateSubject);
|
||||||
|
|
||||||
Assert.Empty(_manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
Assert.Empty(CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
Assert.Empty(_manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
Assert.Empty(CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
||||||
{
|
{
|
||||||
var now = DateTimeOffset.Now;
|
var now = DateTimeOffset.Now;
|
||||||
var certificateManager = new CertificateManager();
|
var certificateManager = new CertificateManager();
|
||||||
var certificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
var certificates = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||||
if (certificates.Count == 0)
|
if (certificates.Count == 0)
|
||||||
{
|
{
|
||||||
reporter.Output("No valid certificate found.");
|
reporter.Output("No valid certificate found.");
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
||||||
if (trust != null && trust.HasValue())
|
if (trust != null && trust.HasValue())
|
||||||
{
|
{
|
||||||
var store = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? StoreName.My : StoreName.Root;
|
var store = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? StoreName.My : StoreName.Root;
|
||||||
var trustedCertificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, store, StoreLocation.CurrentUser, isValid: true);
|
var trustedCertificates = CertificateManager.ListCertificates(CertificatePurpose.HTTPS, store, StoreLocation.CurrentUser, isValid: true);
|
||||||
if (!certificates.Any(c => certificateManager.IsTrusted(c)))
|
if (!certificates.Any(c => certificateManager.IsTrusted(c)))
|
||||||
{
|
{
|
||||||
reporter.Output($@"The following certificates were found, but none of them is trusted:
|
reporter.Output($@"The following certificates were found, but none of them is trusted:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue