Skip to content

Commit

Permalink
ignore name mismatch when IgnoreInvalidName is set (#73745)
Browse files Browse the repository at this point in the history
* ignore name mismatch when IgnoreInvalidName is set

* remove dead code

* feedback from review

* fix build

* split Configuration.Certificates

* disable win7

* update test

* skip on browser
  • Loading branch information
wfurt committed Aug 13, 2022
1 parent 4c26d98 commit d54486e
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

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 Test.Cryptography;

namespace System.Net.Test.Common
{
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);

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 static X509Certificate2 s_dynamicServerCertificate;
private static X509Certificate2Collection s_dynamicCaCertificates;
private static object certLock = new object();


// These Get* methods make a copy of the certificates so that consumers own the lifetime of the
// certificates handed back. Consumers are expected to dispose of their certs when done with them.

public static X509Certificate2 GetDynamicServerCerttificate(X509Certificate2Collection? chainCertificates)
{
lock (certLock)
{
if (s_dynamicServerCertificate == null)
{
CleanupCertificates();
(s_dynamicServerCertificate, s_dynamicCaCertificates) = GenerateCertificates("localhost", nameof(Configuration) + nameof(Certificates));
}

chainCertificates?.AddRange(s_dynamicCaCertificates);
return new X509Certificate2(s_dynamicServerCertificate);
}
}

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)
{
if (cert.Subject.Contains(caName))
{
store.Remove(cert);
}
cert.Dispose();
}
}
}
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);
}
cert.Dispose();
}
}
}
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;
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 @@ -14,7 +14,6 @@
<ItemGroup>
<Compile Include="$(CommonTestPath)System\Net\Capability.Security.cs" Link="Common\System\Net\Capability.Security.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.cs" Link="Common\System\Net\Configuration.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Certificates.cs" Link="Common\System\Net\Configuration.Certificates.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Http.cs" Link="Common\System\Net\Configuration.Http.cs" />
<Compile Include="$(CommonTestPath)System\Net\Configuration.Security.cs" Link="Common\System\Net\Configuration.Security.cs" />
<Compile Include="$(CommonTestPath)System\Net\Http\HttpMessageHandlerLoopbackServer.cs" Link="Common\System\Net\Http\HttpMessageHandlerLoopbackServer.cs" />
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,137 @@ public SocketsHttpHandler_RequestContentLengthMismatchTest_Http3(ITestOutputHelp
protected override Version UseVersion => HttpVersion.Version30;
}

[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
public abstract class SocketsHttpHandler_SecurityTest : HttpClientHandlerTestBase
{
public SocketsHttpHandler_SecurityTest(ITestOutputHelper output) : base(output) { }

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))]
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;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion, VersionPolicy = HttpVersionPolicy.RequestVersionExact };
// This will drive SNI and name verification
request.Headers.Host = "localhost";
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead);
Assert.Equal("foo", await response.Content.ReadAsStringAsync());
},
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_SecurityTest_Http11 : SocketsHttpHandler_SecurityTest
{
public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http11(ITestOutputHelper output) : base(output) { }
protected override Version UseVersion => HttpVersion.Version11;
}

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

[ConditionalClass(typeof(HttpClientHandlerTestBase), nameof(IsQuicSupported))]
public sealed class SocketsHttpHandler_SocketsHttpHandler_SecurityTest_Http3 : SocketsHttpHandler_SecurityTest
{
public SocketsHttpHandler_SocketsHttpHandler_SecurityTest_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,12 @@
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\Net\Configuration.Certificates.Dynamic.cs"
Link="Common\System\Net\Configuration.Certificates.Dynamic.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

0 comments on commit d54486e

Please sign in to comment.