Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore name mismatch when IgnoreInvalidName is set #73745

Merged
merged 8 commits into from
Aug 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
145 changes: 145 additions & 0 deletions src/libraries/Common/tests/System/Net/Configuration.Certificates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.X509Certificates.Tests.Common;
using System.Threading;
using Test.Cryptography;
using Xunit;
Expand All @@ -16,6 +18,30 @@ public static partial class Configuration
{
public static partial class Certificates
{
private static readonly X509KeyUsageExtension s_eeKeyUsage =
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DataEncipherment,
critical: false);
wfurt marked this conversation as resolved.
Show resolved Hide resolved

private static readonly X509EnhancedKeyUsageExtension s_tlsServerEku =
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1", null)
},
false);

private static readonly X509EnhancedKeyUsageExtension s_tlsClientEku =
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.2", null)
},
false);

private static readonly X509BasicConstraintsExtension s_eeConstraints =
new X509BasicConstraintsExtension(false, false, 0, false);

private const string CertificatePassword = "PLACEHOLDER";
private const string TestDataFolder = "TestDataCertificates";
private const int MutexTimeoutMs = 120_000;
Expand All @@ -26,6 +52,8 @@ public static partial class Certificates
private static readonly X509Certificate2 s_selfSignedServerCertificate;
private static readonly X509Certificate2 s_selfSignedClientCertificate;
private static X509Certificate2 s_selfSigned13ServerCertificate;
private static X509Certificate2 s_dynamicServerCertificate;
private static X509Certificate2Collection s_dynamicCaCertificates;

static Certificates()
{
Expand Down Expand Up @@ -53,6 +81,8 @@ static Certificates()
s_noEKUCertificate = new X509Certificate2(noEKUCertificateBytes, CertificatePassword, X509KeyStorageFlags.Exportable);
s_selfSignedServerCertificate = new X509Certificate2(selfSignedServerCertificateBytes, CertificatePassword, X509KeyStorageFlags.Exportable);
s_selfSignedClientCertificate = new X509Certificate2(selfSignedClientCertificateBytes, CertificatePassword, X509KeyStorageFlags.Exportable);
CleanupCertificates();
(s_dynamicServerCertificate, s_dynamicCaCertificates) = GenerateCertificates("localhost", nameof(Configuration)+nameof(Certificates));
}
finally { mutex?.ReleaseMutex(); }
}
Expand All @@ -73,6 +103,12 @@ static Certificates()
public static X509Certificate2 GetSelfSignedServerCertificate() => new X509Certificate2(s_selfSignedServerCertificate);
public static X509Certificate2 GetSelfSignedClientCertificate() => new X509Certificate2(s_selfSignedClientCertificate);

public static X509Certificate2 GetDynamicServerCerttificate(X509Certificate2Collection? chainCertificates)
{
chainCertificates?.AddRange(s_dynamicCaCertificates);
return new X509Certificate2(s_dynamicServerCertificate);
}

public static X509Certificate2 GetSelfSigned13ServerCertificate()
{
if (s_selfSigned13ServerCertificate == null)
Expand Down Expand Up @@ -110,6 +146,115 @@ public static X509Certificate2 GetSelfSigned13ServerCertificate()

return new X509Certificate2(s_selfSigned13ServerCertificate);
}

public static void CleanupCertificates([CallerMemberName] string? testName = null, StoreName storeName = StoreName.CertificateAuthority)
{
string caName = $"O={testName}";
try
{
using (X509Store store = new X509Store(storeName, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
foreach (X509Certificate2 cert in store.Certificates)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these need to be disposed of, right? Same for the iteration below.

{
if (cert.Subject.Contains(caName))
{
store.Remove(cert);
}
}
}
}
catch { };

try
{
using (X509Store store = new X509Store(storeName, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite);
foreach (X509Certificate2 cert in store.Certificates)
{
if (cert.Subject.Contains(caName))
{
store.Remove(cert);
}
}
}
}
catch { };
}

private static X509ExtensionCollection BuildTlsServerCertExtensions(string serverName)
{
return BuildTlsCertExtensions(serverName, true);
}

private static X509ExtensionCollection BuildTlsCertExtensions(string targetName, bool serverCertificate)
{
X509ExtensionCollection extensions = new X509ExtensionCollection();

SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder();
builder.AddDnsName(targetName);
builder.AddIpAddress(IPAddress.Loopback);
builder.AddIpAddress(IPAddress.IPv6Loopback);
extensions.Add(builder.Build());
extensions.Add(s_eeConstraints);
extensions.Add(s_eeKeyUsage);
extensions.Add(serverCertificate ? s_tlsServerEku : s_tlsClientEku);

return extensions;
}

public static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true)
{
const int keySize = 2048;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
if (PlatformDetection.IsWindows && testName != null)
{
CleanupCertificates(testName);
}

X509Certificate2Collection chain = new X509Certificate2Collection();
X509ExtensionCollection extensions = BuildTlsCertExtensions(targetName, serverCertificate);

CertificateAuthority.BuildPrivatePki(
PkiOptions.IssuerRevocationViaCrl,
out RevocationResponder responder,
out CertificateAuthority root,
out CertificateAuthority[] intermediates,
out X509Certificate2 endEntity,
intermediateAuthorityCount: longChain ? 3 : 1,
subjectName: targetName,
testName: testName,
keySize: keySize,
extensions: extensions);

// Walk the intermediates backwards so we build the chain collection as
// Issuer3
// Issuer2
// Issuer1
// Root
for (int i = intermediates.Length - 1; i >= 0; i--)
{
CertificateAuthority authority = intermediates[i];

chain.Add(authority.CloneIssuerCert());
authority.Dispose();
}

chain.Add(root.CloneIssuerCert());

responder.Dispose();
root.Dispose();

if (PlatformDetection.IsWindows)
{
X509Certificate2 ephemeral = endEntity;
endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx));
ephemeral.Dispose();
}

return (endEntity, chain);
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static async Task<Http2LoopbackConnection> CreateAsync(SocketWrapper sock
{
var sslStream = new SslStream(stream, false, delegate { return true; });

using (X509Certificate2 cert = Configuration.Certificates.GetServerCertificate())
using (X509Certificate2 cert = httpOptions.Certificate ?? Configuration.Certificates.GetServerCertificate())
{
#if !NETFRAMEWORK
SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ private static Http2Options CreateOptions(GenericLoopbackOptions options)
{
http2Options.Address = options.Address;
http2Options.UseSsl = options.UseSsl;
http2Options.Certificate = options.Certificate;
http2Options.SslProtocols = options.SslProtocols;
http2Options.ListenBacklog = options.ListenBacklog;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public Http3LoopbackServer(Http3Options options = null)
{
options ??= new Http3Options();

_cert = Configuration.Certificates.GetServerCertificate();
_cert = options.Certificate ?? Configuration.Certificates.GetServerCertificate();

var listenerOptions = new QuicListenerOptions()
{
Expand Down Expand Up @@ -130,6 +130,7 @@ private static Http3Options CreateOptions(GenericLoopbackOptions options)
{
http3Options.Address = options.Address;
http3Options.UseSsl = options.UseSsl;
http3Options.Certificate = options.Certificate;
http3Options.SslProtocols = options.SslProtocols;
http3Options.ListenBacklog = options.ListenBacklog;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ private static HttpAgnosticOptions CreateOptions(GenericLoopbackOptions options)
if (options != null)
{
httpOptions.Address = options.Address;
httpOptions.Certificate = options.Certificate;
httpOptions.UseSsl = options.UseSsl;
httpOptions.SslProtocols = options.SslProtocols;
httpOptions.ListenBacklog = options.ListenBacklog;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,7 @@ private static LoopbackServer.Options CreateOptions(GenericLoopbackOptions optio
{
newOptions.Address = options.Address;
newOptions.UseSsl = options.UseSsl;
newOptions.Certificate = options.Certificate;
newOptions.SslProtocols = options.SslProtocols;
newOptions.ListenBacklog = options.ListenBacklog;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

namespace System.Net.Http.Functional.Tests
{
using Configuration = System.Net.Test.Common.Configuration;

[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
{
Expand Down Expand Up @@ -3728,6 +3730,132 @@ public SocketsHttpHandler_RequestContentLengthMismatchTest_Http3(ITestOutputHelp
protected override Version UseVersion => HttpVersion.Version30;
}

public abstract class SocketsHttpHandler_SslTest : HttpClientHandlerTestBase
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: SslTest => SslOptionsTest? (If you change it, also change the derived type names)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not sure. The intention is to test security related functions e.g. SslStream, Quic & certificate handling...

{
public SocketsHttpHandler_SslTest(ITestOutputHelper output) : base(output) { }

[Fact]
public async Task SslOptions_CustomTrust_Ok()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts);

GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate };
await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
using HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: false);
var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);

var policy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.NoCheck,
TrustMode = X509ChainTrustMode.CustomRootTrust,
};

policy.ExtraStore.AddRange(caCerts);
policy.CustomTrustStore.Add(caCerts[caCerts.Count - 1]);
socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy };
using HttpClient client = CreateHttpClient(handler);
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
Assert.Equal("foo", await client.GetStringAsync(uri));
},
async server =>
{
await server.AcceptConnectionSendResponseAndCloseAsync(content: "foo");
}, options: options);
}

[Fact]
public async Task SslOptions_InvalidName_Throws()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
using X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts);

GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate };
await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
using HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: false);
var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);
using HttpClient client = CreateHttpClient(handler);
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact };
// This will drive SNI and name verification
request.Headers.Host = Guid.NewGuid().ToString("N");

await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(request, HttpCompletionOption.ResponseContentRead));
},
async server =>
{
try
{
await server.AcceptConnectionSendResponseAndCloseAsync(content: "foo");
}
catch { };
}, options: options);
}

[Fact]
public async Task SslOptions_CustomPolicy_IgnoresNameMismatch()
{
X509Certificate2Collection caCerts = new X509Certificate2Collection();
X509Certificate2 certificate = Configuration.Certificates.GetDynamicServerCerttificate(caCerts);

GenericLoopbackOptions options = new GenericLoopbackOptions() { UseSsl = true, Certificate = certificate };
await LoopbackServerFactory.CreateClientAndServerAsync(
async uri =>
{
using HttpClientHandler handler = CreateHttpClientHandler(allowAllCertificates: false);
var socketsHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);

var policy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.NoCheck,
TrustMode = X509ChainTrustMode.CustomRootTrust,
VerificationFlags = X509VerificationFlags.IgnoreInvalidName,
};

policy.ExtraStore.AddRange(caCerts);
policy.CustomTrustStore.Add(caCerts[caCerts.Count -1]);
socketsHandler.SslOptions = new SslClientAuthenticationOptions() { CertificateChainPolicy = policy };

using HttpClient client = CreateHttpClient(handler);
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact };
// This will drive SNI and name verification
request.Headers.Host = Guid.NewGuid().ToString("N");

HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
Assert.Equal("foo", await response.Content.ReadAsStringAsync());
},
async server =>
{
await server.AcceptConnectionSendResponseAndCloseAsync(content: "foo");
}, options: options);
}
}

public sealed class SocketsHttpHandler_SocketsHttpHandler_SslTest_Http11 : SocketsHttpHandler_SslTest
{
public SocketsHttpHandler_SocketsHttpHandler_SslTest_Http11(ITestOutputHelper output) : base(output) { }
protected override Version UseVersion => HttpVersion.Version11;
}

[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
public sealed class SocketsHttpHandler_SocketsHttpHandler_SslTest_Http2 : SocketsHttpHandler_SslTest
{
public SocketsHttpHandler_SocketsHttpHandler_SslTest_Http2(ITestOutputHelper output) : base(output) { }
protected override Version UseVersion => HttpVersion.Version20;
}

[ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))]
public sealed class SocketsHttpHandler_SocketsHttpHandler_SslTest_Http3 : SocketsHttpHandler_SslTest
{
public SocketsHttpHandler_SocketsHttpHandler_SslTest_Http3(ITestOutputHelper output) : base(output) { }
protected override Version UseVersion => HttpVersion.Version30;
}

public class MySsl : SslStream
{
public MySsl(Stream stream) : base(stream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
Link="CommonTest\System\Security\Cryptography\PlatformSupport.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Certificates.cs"
Link="Common\System\Net\Configuration.Certificates.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\CertificateAuthority.cs"
Link="CommonTest\System\Security\Cryptography\X509Certificates\CertificateAuthority.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\X509Certificates\RevocationResponder.cs"
Link="CommonTest\System\Security\Cryptography\X509Certificates\RevocationResponder.cs" />
<Compile Include="$(CommonTestPath)System\Net\EventSourceTestLogging.cs"
Link="Common\System\Net\EventSourceTestLogging.cs" />
<Compile Include="$(CommonTestPath)System\Net\HttpsTestServer.cs"
Expand Down
Loading