| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- using System.Globalization;
- using System.Net;
- using System.Net.Http;
- using System.Net.Security;
- using System.Security.Authentication;
- using System.Security.Cryptography.X509Certificates;
- using System.Text;
- using Microsoft.AspNetCore.Connections.Features;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Http.Features;
- using Microsoft.AspNetCore.Server.Kestrel.Core;
- using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
- using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
- using Microsoft.AspNetCore.Server.Kestrel.Https;
- using Microsoft.AspNetCore.Server.Kestrel.Https.Internal;
- using Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.TestTransport;
- using Microsoft.AspNetCore.Testing;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
- using Microsoft.Extensions.Logging;
- using Microsoft.Extensions.Metrics;
- using Moq;
- namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests;
- public class HttpsConnectionMiddlewareTests : LoggedTest
- {
- private static readonly X509Certificate2 _x509Certificate2 = TestResources.GetTestCertificate();
- private static readonly X509Certificate2 _x509Certificate2NoExt = TestResources.GetTestCertificate("no_extensions.pfx");
- private static KestrelServerOptions CreateServerOptions()
- {
- var env = new Mock<IHostEnvironment>();
- env.SetupGet(e => e.ContentRootPath).Returns(Directory.GetCurrentDirectory());
- var serverOptions = new KestrelServerOptions();
- serverOptions.ApplicationServices = new ServiceCollection()
- .AddLogging()
- .AddSingleton<IHttpsConfigurationService, HttpsConfigurationService>()
- .AddSingleton<HttpsConfigurationService.IInitializer, HttpsConfigurationService.Initializer>()
- .AddSingleton(env.Object)
- .AddSingleton(new KestrelMetrics(new TestMeterFactory()))
- .BuildServiceProvider();
- return serverOptions;
- }
- [Fact]
- public async Task CanReadAndWriteWithHttpsConnectionMiddleware()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 });
- };
- await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/",
- new FormUrlEncodedContent(new[] {
- new KeyValuePair<string, string>("content", "Hello World?")
- }),
- validateCertificate: false);
- Assert.Equal("content=Hello+World%3F", result);
- }
- }
- [Fact]
- public async Task CanReadAndWriteWithHttpsConnectionMiddlewareWithPemCertificate()
- {
- var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string>
- {
- ["Certificates:Default:Path"] = Path.Combine("shared", "TestCertificates", "https-aspnet.crt"),
- ["Certificates:Default:KeyPath"] = Path.Combine("shared", "TestCertificates", "https-aspnet.key"),
- ["Certificates:Default:Password"] = "aspnetcore",
- }).Build();
- var options = CreateServerOptions();
- var loader = new KestrelConfigurationLoader(options, configuration, options.ApplicationServices.GetRequiredService<IHttpsConfigurationService>(), reloadOnChange: false);
- options.ConfigurationLoader = loader; // Since we're constructing it explicitly, we have to hook it up explicitly
- loader.Load();
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.KestrelServerOptions = options;
- listenOptions.UseHttps();
- };
- await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.PostAsync($"https://localhost:{server.Port}/",
- new FormUrlEncodedContent(new[] {
- new KeyValuePair<string, string>("content", "Hello World?")
- }),
- validateCertificate: false);
- Assert.Equal("content=Hello+World%3F", result);
- }
- }
- [Fact]
- public async Task SslStreamIsAvailable()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 });
- };
- await using (var server = new TestServer(context =>
- {
- var feature = context.Features.Get<ISslStreamFeature>();
- Assert.NotNull(feature);
- Assert.NotNull(feature.SslStream);
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("hello world", result);
- }
- }
- [Fact]
- public async Task HandshakeDetailsAreAvailable()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 });
- };
- await using (var server = new TestServer(context =>
- {
- var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();
- Assert.NotNull(tlsFeature);
- Assert.True(tlsFeature.Protocol > SslProtocols.None, "Protocol");
- Assert.True(tlsFeature.NegotiatedCipherSuite >= TlsCipherSuite.TLS_NULL_WITH_NULL_NULL, "NegotiatedCipherSuite");
- Assert.True(tlsFeature.CipherAlgorithm > CipherAlgorithmType.Null, "Cipher");
- Assert.True(tlsFeature.CipherStrength > 0, "CipherStrength");
- Assert.True(tlsFeature.HashAlgorithm >= HashAlgorithmType.None, "HashAlgorithm"); // May be None on Linux.
- Assert.True(tlsFeature.HashStrength >= 0, "HashStrength"); // May be 0 for some algorithms
- Assert.True(tlsFeature.KeyExchangeAlgorithm >= ExchangeAlgorithmType.None, "KeyExchangeAlgorithm"); // Maybe None on Windows 7
- Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("hello world", result);
- }
- }
- [Fact]
- public async Task HandshakeDetailsAreAvailableAfterAsyncCallback()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(async (stream, clientHelloInfo, state, cancellationToken) =>
- {
- await Task.Yield();
- return new SslServerAuthenticationOptions
- {
- ServerCertificate = _x509Certificate2,
- };
- }, state: null);
- }
- await using (var server = new TestServer(context =>
- {
- var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();
- Assert.NotNull(tlsFeature);
- Assert.True(tlsFeature.Protocol > SslProtocols.None, "Protocol");
- Assert.True(tlsFeature.CipherAlgorithm > CipherAlgorithmType.Null, "Cipher");
- Assert.True(tlsFeature.CipherStrength > 0, "CipherStrength");
- Assert.True(tlsFeature.HashAlgorithm >= HashAlgorithmType.None, "HashAlgorithm"); // May be None on Linux.
- Assert.True(tlsFeature.HashStrength >= 0, "HashStrength"); // May be 0 for some algorithms
- Assert.True(tlsFeature.KeyExchangeAlgorithm >= ExchangeAlgorithmType.None, "KeyExchangeAlgorithm"); // Maybe None on Windows 7
- Assert.True(tlsFeature.KeyExchangeStrength >= 0, "KeyExchangeStrength"); // May be 0 on mac
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("hello world", result);
- }
- }
- [Fact]
- public async Task RequireCertificateFailsWhenNoCertificate()
- {
- await using (var server = new TestServer(App, new TestServiceContext(LoggerFactory), listenOptions =>
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateMode = ClientCertificateMode.RequireCertificate
- });
- }))
- {
- await Assert.ThrowsAnyAsync<Exception>(
- () => server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/"));
- }
- }
- [Fact]
- public async Task AllowCertificateContinuesWhenNoCertificate()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateMode = ClientCertificateMode.AllowCertificate
- });
- }
- await using (var server = new TestServer(context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("hello world", result);
- }
- }
- [Fact]
- public async Task AsyncCallbackSettingClientCertificateRequiredContinuesWhenNoCertificate()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
- new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateRequired = true,
- RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
- CertificateRevocationCheckMode = X509RevocationMode.NoCheck
- }), state: null);
- }
- await using (var server = new TestServer(context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("hello world", result);
- }
- }
- [Fact]
- public void ThrowsWhenNoServerCertificateIsProvided()
- {
- Assert.Throws<ArgumentException>(() => CreateMiddleware(new HttpsConnectionAdapterOptions()));
- }
- [Fact]
- public async Task UsesProvidedServerCertificate()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 });
- };
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
- }
- }
- }
- [Fact]
- public async Task UsesProvidedServerCertificateSelector()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificateSelector = (connection, name) =>
- {
- Assert.NotNull(connection);
- Assert.NotNull(connection.Features.Get<SslStream>());
- Assert.Equal("localhost", name);
- selectorCalled++;
- return _x509Certificate2;
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
- Assert.Equal(1, selectorCalled);
- }
- }
- }
- [Fact]
- public async Task UsesProvidedAsyncCallback()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(async (stream, clientHelloInfo, state, cancellationToken) =>
- {
- await Task.Yield();
- Assert.NotNull(stream);
- Assert.Equal("localhost", clientHelloInfo.ServerName);
- selectorCalled++;
- return new SslServerAuthenticationOptions
- {
- ServerCertificate = _x509Certificate2
- };
- }, state: null);
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
- Assert.Equal(1, selectorCalled);
- }
- }
- }
- [Fact]
- public async Task UsesProvidedServerCertificateSelectorEachTime()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificateSelector = (connection, name) =>
- {
- Assert.NotNull(connection);
- Assert.NotNull(connection.Features.Get<SslStream>());
- Assert.Equal("localhost", name);
- selectorCalled++;
- if (selectorCalled == 1)
- {
- return _x509Certificate2;
- }
- return _x509Certificate2NoExt;
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
- Assert.Equal(1, selectorCalled);
- }
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2NoExt));
- Assert.Equal(2, selectorCalled);
- }
- }
- }
- [Fact]
- public async Task UsesProvidedServerCertificateSelectorValidatesEkus()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificateSelector = (features, name) =>
- {
- selectorCalled++;
- return TestResources.GetTestCertificate("eku.code_signing.pfx");
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
- await Assert.ThrowsAsync<IOException>(() =>
- stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
- #pragma warning restore SYSLIB0039
- Assert.Equal(1, selectorCalled);
- }
- }
- }
- [Fact]
- public async Task UsesProvidedServerCertificateSelectorOverridesServerCertificate()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2NoExt,
- ServerCertificateSelector = (connection, name) =>
- {
- Assert.NotNull(connection);
- Assert.NotNull(connection.Features.Get<SslStream>());
- Assert.Equal("localhost", name);
- selectorCalled++;
- return _x509Certificate2;
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- Assert.True(stream.RemoteCertificate.Equals(_x509Certificate2));
- Assert.Equal(1, selectorCalled);
- }
- }
- }
- [Fact]
- public async Task UsesProvidedServerCertificateSelectorFailsIfYouReturnNull()
- {
- var selectorCalled = 0;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificateSelector = (features, name) =>
- {
- selectorCalled++;
- return null;
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStream(connection.Stream);
- #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
- await Assert.ThrowsAsync<IOException>(() =>
- stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls12 | SslProtocols.Tls11, false));
- #pragma warning restore SYSLIB0039
- Assert.Equal(1, selectorCalled);
- }
- }
- }
- [Theory]
- [InlineData(HttpProtocols.Http1)]
- [InlineData(HttpProtocols.Http1AndHttp2)] // Make sure Http/1.1 doesn't regress with Http/2 enabled.
- public async Task CertificatePassedToHttpContext(HttpProtocols httpProtocols)
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = httpProtocols;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using (var server = new TestServer(context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.NotNull(tlsFeature.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate);
- return context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- }
- }
- [Fact]
- public async Task RenegotiateForClientCertificateOnHttp1DisabledByDefault()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.NoCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.Null(clientCert);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- [ConditionalTheory]
- [InlineData(HttpProtocols.Http1)]
- [InlineData(HttpProtocols.Http1AndHttp2)] // Make sure turning on Http/2 doesn't regress HTTP/1
- [TlsAlpnSupported]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task CanRenegotiateForClientCertificate(HttpProtocols httpProtocols)
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = httpProtocols;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.NotNull(clientCert);
- Assert.NotNull(tlsFeature.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- [ConditionalFact]
- [TlsAlpnSupported]
- [OSSkipCondition(OperatingSystems.Windows | OperatingSystems.Linux, SkipReason = "MacOS only test.")]
- public async Task CanRenegotiateForClientCertificate_MacOS_PlatformNotSupportedException()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- await Assert.ThrowsAsync<PlatformNotSupportedException>(() => context.Connection.GetClientCertificateAsync());
- var lifetimeNotificationFeature = context.Features.Get<IConnectionLifetimeNotificationFeature>();
- Assert.False(
- lifetimeNotificationFeature.ConnectionClosedRequested.IsCancellationRequested,
- "GetClientCertificateAsync shouldn't cause the connection to be closed.");
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- [Fact]
- public async Task Renegotiate_ServerOptionsSelectionCallback_NotSupported()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps((SslStream stream, SslClientHelloInfo clientHelloInfo, object state, CancellationToken cancellationToken) =>
- {
- return ValueTask.FromResult(new SslServerAuthenticationOptions()
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateRequired = false,
- RemoteCertificateValidationCallback = (_, _, _, _) => true,
- });
- }, state: null);
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.Null(clientCert);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- [ConditionalFact]
- [TlsAlpnSupported]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task CanRenegotiateForTlsCallbackOptions()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
- {
- OnConnection = context =>
- {
- context.AllowDelayedClientCertificateNegotation = true;
- return ValueTask.FromResult(new SslServerAuthenticationOptions()
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateRequired = false,
- RemoteCertificateValidationCallback = (_, _, _, _) => true,
- });
- }
- });
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.NotNull(clientCert);
- Assert.NotNull(tlsFeature.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true);
- }
- [ConditionalFact]
- [TlsAlpnSupported]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task CanRenegotiateForClientCertificateOnHttp1CanReturnNoCert()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.Null(clientCert);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = new SslStream(connection.Stream);
- var clientOptions = new SslClientAuthenticationOptions()
- {
- TargetHost = Guid.NewGuid().ToString(),
- #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
- EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12,
- #pragma warning restore SYSLIB0039
- };
- clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
- await stream.AuthenticateAsClientAsync(clientOptions);
- await AssertConnectionResult(stream, true);
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Fails on OSX.")]
- public async Task ServerCertificateChainInExtraStore()
- {
- var streams = new List<SslStream>();
- CertHelper.CleanupCertificates(nameof(ServerCertificateChainInExtraStore));
- (var clientCertificate, var clientChain) = CertHelper.GenerateCertificates(nameof(ServerCertificateChainInExtraStore), longChain: true, serverCertificate: false);
- using (var store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
- {
- // add chain certificate so we can construct chain since there is no way how to pass intermediates directly.
- store.Open(OpenFlags.ReadWrite);
- store.AddRange(clientChain);
- store.Close();
- }
- using (var chain = new X509Chain())
- {
- chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
- chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
- chain.ChainPolicy.DisableCertificateDownloads = true;
- var chainStatus = chain.Build(clientCertificate);
- }
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ServerCertificateChain = clientChain,
- OnAuthenticate = (con, so) =>
- {
- so.ClientCertificateRequired = true;
- so.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
- {
- Assert.NotEmpty(chain.ChainPolicy.ExtraStore);
- Assert.Contains(clientChain[0], chain.ChainPolicy.ExtraStore);
- return true;
- };
- so.CertificateRevocationCheckMode = X509RevocationMode.NoCheck;
- }
- });
- }
- await using (var server = new TestServer(
- context => context.Response.WriteAsync("hello world"),
- new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream, clientCertificate);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, true);
- }
- }
- CertHelper.CleanupCertificates(nameof(ServerCertificateChainInExtraStore));
- clientCertificate.Dispose();
- var list = (System.Collections.IList)clientChain;
- for (var i = 0; i < list.Count; i++)
- {
- var c = (X509Certificate)list[i];
- c.Dispose();
- }
- foreach (var s in streams)
- {
- s.Dispose();
- }
- }
- [ConditionalFact]
- // TLS 1.2 and lower have to renegotiate the whole connection to get a client cert, and if that hits an error
- // then the connection is aborted.
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [TlsAlpnSupported]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task RenegotiateForClientCertificateOnPostWithoutBufferingThrows()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- // Under 4kb can sometimes work because it fits into Kestrel's header parsing buffer.
- var expectedBody = new string('a', 1024 * 4);
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Connection.GetClientCertificateAsync());
- Assert.Equal("Client stream needs to be drained before renegotiation.", ex.Message);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true, expectedBody);
- }
- [ConditionalFact]
- [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] // HTTP/2 requires Win10
- [TlsAlpnSupported]
- public async Task ServerOptionsSelectionCallback_SetsALPN()
- {
- static void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps((_, _, _, _) =>
- ValueTask.FromResult(new SslServerAuthenticationOptions()
- {
- ServerCertificate = _x509Certificate2,
- }), state: null);
- }
- await using var server = new TestServer(context => Task.CompletedTask,
- new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
- {
- // Use a random host name to avoid the TLS session resumption cache.
- TargetHost = Guid.NewGuid().ToString(),
- ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
- });
- Assert.Equal(SslApplicationProtocol.Http2, stream.NegotiatedApplicationProtocol);
- }
- [ConditionalFact]
- [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)] // HTTP/2 requires Win10
- [TlsAlpnSupported]
- public async Task TlsHandshakeCallbackOptionsOverload_SetsALPN()
- {
- static void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
- {
- OnConnection = context =>
- {
- return ValueTask.FromResult(new SslServerAuthenticationOptions()
- {
- ServerCertificate = _x509Certificate2,
- });
- }
- });
- }
- await using var server = new TestServer(context => Task.CompletedTask,
- new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
- {
- // Use a random host name to avoid the TLS session resumption cache.
- TargetHost = Guid.NewGuid().ToString(),
- ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
- });
- Assert.Equal(SslApplicationProtocol.Http2, stream.NegotiatedApplicationProtocol);
- }
- [ConditionalFact]
- [TlsAlpnSupported]
- public async Task TlsHandshakeCallbackOptionsOverload_EmptyAlpnList_DisablesAlpn()
- {
- static void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new TlsHandshakeCallbackOptions()
- {
- OnConnection = context =>
- {
- return ValueTask.FromResult(new SslServerAuthenticationOptions()
- {
- ServerCertificate = _x509Certificate2,
- ApplicationProtocols = new(),
- });
- }
- });
- }
- await using var server = new TestServer(context => Task.CompletedTask,
- new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions()
- {
- // Use a random host name to avoid the TLS session resumption cache.
- TargetHost = Guid.NewGuid().ToString(),
- ApplicationProtocols = new() { SslApplicationProtocol.Http2, SslApplicationProtocol.Http11, },
- });
- Assert.Equal(default, stream.NegotiatedApplicationProtocol);
- }
- [ConditionalFact]
- [TlsAlpnSupported]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task CanRenegotiateForClientCertificateOnPostIfDrained()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- var expectedBody = new string('a', 1024 * 4);
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- // Read the body before requesting the client cert
- var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
- Assert.Equal(expectedBody, body);
- var clientCert = await context.Connection.GetClientCertificateAsync();
- Assert.NotNull(clientCert);
- Assert.NotNull(tlsFeature.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate);
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- await AssertConnectionResult(stream, true, expectedBody);
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing platform support.")]
- [TlsAlpnSupported]
- [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/33566#issuecomment-892031659", Queues = HelixConstants.RedhatAmd64)] // Outdated OpenSSL client
- public async Task RenegotationFailureCausesConnectionClose()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.Protocols = HttpProtocols.Http1;
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.DelayCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- var expectedBody = new string('a', 1024 * 4);
- await using var server = new TestServer(async context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.Null(tlsFeature.ClientCertificate);
- Assert.Null(context.Connection.ClientCertificate);
- // Request the client cert while there's still body data in the buffers
- var ioe = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Connection.GetClientCertificateAsync());
- Assert.Equal("Client stream needs to be drained before renegotiation.", ioe.Message);
- context.Response.ContentLength = 11;
- await context.Response.WriteAsync("hello world");
- }, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- // Use a random host name to avoid the TLS session resumption cache.
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync(Guid.NewGuid().ToString());
- var request = Encoding.UTF8.GetBytes($"POST / HTTP/1.1\r\nHost: localhost\r\nContent-Length: {expectedBody.Length}\r\n\r\n{expectedBody}");
- await stream.WriteAsync(request, 0, request.Length).DefaultTimeout();
- var reader = new StreamReader(stream);
- Assert.Equal("HTTP/1.1 200 OK", await reader.ReadLineAsync().DefaultTimeout());
- Assert.Equal("Content-Length: 11", await reader.ReadLineAsync().DefaultTimeout());
- Assert.Equal("Connection: close", await reader.ReadLineAsync().DefaultTimeout());
- Assert.StartsWith("Date: ", await reader.ReadLineAsync().DefaultTimeout());
- Assert.Equal("", await reader.ReadLineAsync().DefaultTimeout());
- Assert.Equal("hello world", await reader.ReadLineAsync().DefaultTimeout());
- Assert.Null(await reader.ReadLineAsync().DefaultTimeout());
- }
- [Fact]
- public async Task HttpsSchemePassedToRequestFeature()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions { ServerCertificate = _x509Certificate2 });
- }
- await using (var server = new TestServer(context => context.Response.WriteAsync(context.Request.Scheme), new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- var result = await server.HttpClientSlim.GetStringAsync($"https://localhost:{server.Port}/", validateCertificate: false);
- Assert.Equal("https", result);
- }
- }
- [Fact]
- public async Task Tls10CanBeDisabled()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(options =>
- {
- #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
- options.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11;
- #pragma warning restore SYSLIB0039
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- await using (var server = new TestServer(context => context.Response.WriteAsync("hello world"), new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- // SslStream is used to ensure the certificate is actually passed to the server
- // HttpClient might not send the certificate because it is invalid or it doesn't match any
- // of the certificate authorities sent by the server in the SSL handshake.
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- #pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
- var ex = await Assert.ThrowsAnyAsync<Exception>(
- async () => await stream.AuthenticateAsClientAsync("localhost", new X509CertificateCollection(), SslProtocols.Tls, false));
- #pragma warning restore SYSLIB0039
- }
- }
- }
- [Theory]
- [InlineData(ClientCertificateMode.AllowCertificate)]
- [InlineData(ClientCertificateMode.RequireCertificate)]
- public async Task ClientCertificateValidationGetsCalledWithNotNullParameters(ClientCertificateMode mode)
- {
- var clientCertificateValidationCalled = false;
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateMode = mode,
- ClientCertificateValidation = (certificate, chain, sslPolicyErrors) =>
- {
- clientCertificateValidationCalled = true;
- Assert.NotNull(certificate);
- Assert.NotNull(chain);
- return true;
- }
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, true);
- Assert.True(clientCertificateValidationCalled);
- }
- }
- }
- [ConditionalTheory]
- [InlineData(ClientCertificateMode.AllowCertificate)]
- [InlineData(ClientCertificateMode.RequireCertificate)]
- public async Task ValidationFailureRejectsConnection(ClientCertificateMode mode)
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateMode = mode,
- ClientCertificateValidation = (certificate, chain, sslPolicyErrors) => false
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, false);
- }
- }
- }
- [Theory]
- [InlineData(ClientCertificateMode.AllowCertificate)]
- [InlineData(ClientCertificateMode.RequireCertificate)]
- public async Task RejectsConnectionOnSslPolicyErrorsWhenNoValidation(ClientCertificateMode mode)
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- ClientCertificateMode = mode
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, false);
- }
- }
- }
- [Fact]
- public async Task AllowAnyCertOverridesCertificateValidation()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
- options.ClientCertificateValidation = (certificate, x509Chain, sslPolicyErrors) => false;
- options.AllowAnyClientCertificate();
- });
- }
- await using (var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, true);
- }
- }
- }
- [Fact]
- public async Task CertificatePassedToHttpContextIsNotDisposed()
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(options =>
- {
- options.ServerCertificate = _x509Certificate2;
- options.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
- options.AllowAnyClientCertificate();
- });
- }
- RequestDelegate app = context =>
- {
- var tlsFeature = context.Features.Get<ITlsConnectionFeature>();
- Assert.NotNull(tlsFeature);
- Assert.NotNull(tlsFeature.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate);
- Assert.NotNull(context.Connection.ClientCertificate.PublicKey);
- return context.Response.WriteAsync("hello world");
- };
- await using (var server = new TestServer(app, new TestServiceContext(LoggerFactory), ConfigureListenOptions))
- {
- using (var connection = server.CreateConnection())
- {
- var stream = OpenSslStreamWithCert(connection.Stream);
- await stream.AuthenticateAsClientAsync("localhost");
- await AssertConnectionResult(stream, true);
- }
- }
- }
- [Theory]
- [InlineData("no_extensions.pfx")]
- public void AcceptsCertificateWithoutExtensions(string testCertName)
- {
- var certPath = TestResources.GetCertPath(testCertName);
- TestOutputHelper.WriteLine("Loading " + certPath);
- var cert = new X509Certificate2(certPath, "testPassword");
- Assert.Empty(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
- CreateMiddleware(cert);
- }
- [Theory]
- [InlineData("eku.server.pfx")]
- [InlineData("eku.multiple_usages.pfx")]
- public void ValidatesEnhancedKeyUsageOnCertificate(string testCertName)
- {
- var certPath = TestResources.GetCertPath(testCertName);
- TestOutputHelper.WriteLine("Loading " + certPath);
- var cert = new X509Certificate2(certPath, "testPassword");
- Assert.NotEmpty(cert.Extensions);
- var eku = Assert.Single(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
- Assert.NotEmpty(eku.EnhancedKeyUsages);
- CreateMiddleware(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = cert,
- });
- }
- [Theory]
- [InlineData("eku.code_signing.pfx")]
- [InlineData("eku.client.pfx")]
- public void ThrowsForCertificatesMissingServerEku(string testCertName)
- {
- var certPath = TestResources.GetCertPath(testCertName);
- TestOutputHelper.WriteLine("Loading " + certPath);
- var cert = new X509Certificate2(certPath, "testPassword");
- Assert.NotEmpty(cert.Extensions);
- var eku = Assert.Single(cert.Extensions.OfType<X509EnhancedKeyUsageExtension>());
- Assert.NotEmpty(eku.EnhancedKeyUsages);
- var ex = Assert.Throws<InvalidOperationException>(() =>
- CreateMiddleware(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = cert,
- }));
- Assert.Equal(CoreStrings.FormatInvalidServerCertificateEku(cert.Thumbprint), ex.Message);
- }
- [ConditionalTheory]
- [InlineData(HttpProtocols.Http1)]
- [InlineData(HttpProtocols.Http2)]
- [InlineData(HttpProtocols.Http1AndHttp2)]
- [TlsAlpnSupported]
- [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
- public async Task ListenOptionsProtolsCanBeSetAfterUseHttps(HttpProtocols httpProtocols)
- {
- void ConfigureListenOptions(ListenOptions listenOptions)
- {
- listenOptions.UseHttps(_x509Certificate2);
- listenOptions.Protocols = httpProtocols;
- }
- await using var server = new TestServer(context => Task.CompletedTask, new TestServiceContext(LoggerFactory), ConfigureListenOptions);
- using var connection = server.CreateConnection();
- var sslOptions = new SslClientAuthenticationOptions
- {
- TargetHost = "localhost",
- EnabledSslProtocols = SslProtocols.None,
- ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http11, SslApplicationProtocol.Http2 },
- };
- using var stream = OpenSslStream(connection.Stream);
- await stream.AuthenticateAsClientAsync(sslOptions);
- Assert.Equal(
- httpProtocols.HasFlag(HttpProtocols.Http2) ?
- SslApplicationProtocol.Http2 :
- SslApplicationProtocol.Http11,
- stream.NegotiatedApplicationProtocol);
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Downgrade logic only applies on Windows")]
- [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
- public void Http1AndHttp2DowngradeToHttp1ForHttpsOnIncompatibleWindowsVersions()
- {
- var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http1AndHttp2
- };
- CreateMiddleware(httpConnectionAdapterOptions);
- Assert.Equal(HttpProtocols.Http1, httpConnectionAdapterOptions.HttpProtocols);
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Downgrade logic only applies on Windows")]
- [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
- public void Http1AndHttp2DoesNotDowngradeOnCompatibleWindowsVersions()
- {
- var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http1AndHttp2
- };
- CreateMiddleware(httpConnectionAdapterOptions);
- Assert.Equal(HttpProtocols.Http1AndHttp2, httpConnectionAdapterOptions.HttpProtocols);
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Error logic only applies on Windows")]
- [MaximumOSVersion(OperatingSystems.Windows, WindowsVersions.Win81)]
- public void Http2ThrowsOnIncompatibleWindowsVersions()
- {
- var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http2
- };
- Assert.Throws<NotSupportedException>(() => CreateMiddleware(httpConnectionAdapterOptions));
- }
- [ConditionalFact]
- [OSSkipCondition(OperatingSystems.MacOSX | OperatingSystems.Linux, SkipReason = "Error logic only applies on Windows")]
- [MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10)]
- public void Http2DoesNotThrowOnCompatibleWindowsVersions()
- {
- var httpConnectionAdapterOptions = new HttpsConnectionAdapterOptions
- {
- ServerCertificate = _x509Certificate2,
- HttpProtocols = HttpProtocols.Http2
- };
- // Does not throw
- CreateMiddleware(httpConnectionAdapterOptions);
- }
- private static HttpsConnectionMiddleware CreateMiddleware(X509Certificate2 serverCertificate)
- {
- return CreateMiddleware(new HttpsConnectionAdapterOptions
- {
- ServerCertificate = serverCertificate,
- });
- }
- private static HttpsConnectionMiddleware CreateMiddleware(HttpsConnectionAdapterOptions options)
- {
- return new HttpsConnectionMiddleware(context => Task.CompletedTask, options, new KestrelMetrics(new TestMeterFactory()));
- }
- private static async Task App(HttpContext httpContext)
- {
- var request = httpContext.Request;
- var response = httpContext.Response;
- while (true)
- {
- var buffer = new byte[8192];
- var count = await request.Body.ReadAsync(buffer, 0, buffer.Length);
- if (count == 0)
- {
- break;
- }
- await response.Body.WriteAsync(buffer, 0, count);
- }
- }
- private static SslStream OpenSslStream(Stream rawStream)
- {
- return new SslStream(rawStream, false, (sender, certificate, chain, errors) => true);
- }
- /// <summary>
- /// SslStream is used to ensure the certificate is actually passed to the server
- /// HttpClient might not send the certificate because it is invalid or it doesn't match any
- /// of the certificate authorities sent by the server in the SSL handshake.
- /// </summary>
- private static SslStream OpenSslStreamWithCert(Stream rawStream, X509Certificate2 clientCertificate = null)
- {
- return new SslStream(rawStream, false, (sender, certificate, chain, errors) => true,
- (sender, host, certificates, certificate, issuers) => clientCertificate ?? _x509Certificate2);
- }
- private static async Task AssertConnectionResult(SslStream stream, bool success, string body = null)
- {
- var request = body == null ? Encoding.UTF8.GetBytes("GET / HTTP/1.0\r\n\r\n")
- : Encoding.UTF8.GetBytes($"POST / HTTP/1.0\r\nContent-Length: {body.Length}\r\n\r\n{body}");
- await stream.WriteAsync(request, 0, request.Length);
- var reader = new StreamReader(stream);
- string line = null;
- if (success)
- {
- line = await reader.ReadLineAsync();
- Assert.Equal("HTTP/1.1 200 OK", line);
- }
- else
- {
- try
- {
- line = await reader.ReadLineAsync();
- }
- catch (IOException) { }
- Assert.Null(line);
- }
- }
- }
|