Skip to content

Commit a77e68f

Browse files
authored
Kestrel Endpoints' "SslProtocols" settable via config (#22663) (#22910)
1 parent 23595db commit a77e68f

File tree

4 files changed

+248
-2
lines changed

4 files changed

+248
-2
lines changed

src/Servers/Kestrel/Core/src/Internal/ConfigurationReader.cs

+28-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Security.Authentication;
68
using Microsoft.Extensions.Configuration;
79

810
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal
@@ -12,6 +14,7 @@ internal class ConfigurationReader
1214
private const string ProtocolsKey = "Protocols";
1315
private const string CertificatesKey = "Certificates";
1416
private const string CertificateKey = "Certificate";
17+
private const string SslProtocolsKey = "SslProtocols";
1518
private const string EndpointDefaultsKey = "EndpointDefaults";
1619
private const string EndpointsKey = "Endpoints";
1720
private const string UrlKey = "Url";
@@ -49,13 +52,15 @@ private IDictionary<string, CertificateConfig> ReadCertificates()
4952

5053
// "EndpointDefaults": {
5154
// "Protocols": "Http1AndHttp2",
55+
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
5256
// }
5357
private EndpointDefaults ReadEndpointDefaults()
5458
{
5559
var configSection = _configuration.GetSection(EndpointDefaultsKey);
5660
return new EndpointDefaults
5761
{
58-
Protocols = ParseProtocols(configSection[ProtocolsKey])
62+
Protocols = ParseProtocols(configSection[ProtocolsKey]),
63+
SslProtocols = ParseSslProcotols(configSection.GetSection(SslProtocolsKey))
5964
};
6065
}
6166

@@ -69,6 +74,7 @@ private IEnumerable<EndpointConfig> ReadEndpoints()
6974
// "EndpointName": {
7075
// "Url": "https://*:5463",
7176
// "Protocols": "Http1AndHttp2",
77+
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
7278
// "Certificate": {
7379
// "Path": "testCert.pfx",
7480
// "Password": "testPassword"
@@ -88,6 +94,7 @@ private IEnumerable<EndpointConfig> ReadEndpoints()
8894
Protocols = ParseProtocols(endpointConfig[ProtocolsKey]),
8995
ConfigSection = endpointConfig,
9096
Certificate = new CertificateConfig(endpointConfig.GetSection(CertificateKey)),
97+
SslProtocols = ParseSslProcotols(endpointConfig.GetSection(SslProtocolsKey))
9198
};
9299

93100
endpoints.Add(endpoint);
@@ -105,19 +112,37 @@ private IEnumerable<EndpointConfig> ReadEndpoints()
105112

106113
return null;
107114
}
115+
116+
private static SslProtocols? ParseSslProcotols(IConfigurationSection sslProtocols)
117+
{
118+
var stringProtocols = sslProtocols.Get<string[]>();
119+
120+
return stringProtocols?.Aggregate(SslProtocols.None, (acc, current) =>
121+
{
122+
if (Enum.TryParse(current, ignoreCase: true, out SslProtocols parsed))
123+
{
124+
return acc | parsed;
125+
}
126+
127+
return acc;
128+
});
129+
}
108130
}
109131

110132
// "EndpointDefaults": {
111133
// "Protocols": "Http1AndHttp2",
134+
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
112135
// }
113136
internal class EndpointDefaults
114137
{
115138
public HttpProtocols? Protocols { get; set; }
139+
public SslProtocols? SslProtocols { get; set; }
116140
}
117141

118142
// "EndpointName": {
119143
// "Url": "https://*:5463",
120144
// "Protocols": "Http1AndHttp2",
145+
// "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
121146
// "Certificate": {
122147
// "Path": "testCert.pfx",
123148
// "Password": "testPassword"
@@ -131,6 +156,7 @@ internal class EndpointConfig
131156
public string Name { get; set; }
132157
public string Url { get; set; }
133158
public HttpProtocols? Protocols { get; set; }
159+
public SslProtocols? SslProtocols { get; set; }
134160
public CertificateConfig Certificate { get; set; }
135161

136162
// Compare config sections because it's accessible to app developers via an Action<EndpointConfiguration> callback.
@@ -154,6 +180,7 @@ obj is EndpointConfig other &&
154180
Url == other.Url &&
155181
(Protocols ?? ListenOptions.DefaultHttpProtocols) == (other.Protocols ?? ListenOptions.DefaultHttpProtocols) &&
156182
Certificate == other.Certificate &&
183+
(SslProtocols ?? System.Security.Authentication.SslProtocols.None) == (other.SslProtocols ?? System.Security.Authentication.SslProtocols.None) &&
157184
_configSectionClone == other._configSectionClone;
158185

159186
public override int GetHashCode() => HashCode.Combine(Name, Url, Protocols ?? ListenOptions.DefaultHttpProtocols, Certificate, _configSectionClone);

src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Linq;
88
using System.Net;
9+
using System.Security.Authentication;
910
using System.Security.Cryptography;
1011
using System.Security.Cryptography.X509Certificates;
1112
using Microsoft.AspNetCore.Certificates.Generation;
@@ -279,9 +280,16 @@ public void Load()
279280
var httpsOptions = new HttpsConnectionAdapterOptions();
280281
if (https)
281282
{
283+
httpsOptions.SslProtocols = ConfigurationReader.EndpointDefaults.SslProtocols ?? SslProtocols.None;
284+
282285
// Defaults
283286
Options.ApplyHttpsDefaults(httpsOptions);
284287

288+
if (endpoint.SslProtocols.HasValue)
289+
{
290+
httpsOptions.SslProtocols = endpoint.SslProtocols.Value;
291+
}
292+
285293
// Specified
286294
httpsOptions.ServerCertificate = LoadCertificate(endpoint.Certificate, endpoint.Name)
287295
?? httpsOptions.ServerCertificate;

src/Servers/Kestrel/Kestrel/test/ConfigurationReaderTests.cs

+80
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Security.Authentication;
78
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
89
using Microsoft.Extensions.Configuration;
910
using Xunit;
@@ -173,5 +174,84 @@ public void ReadEndpointsSection_ReturnsCollection()
173174
Assert.Equal("cetlocation", cert4.Location);
174175
Assert.True(cert4.AllowInvalid);
175176
}
177+
178+
[Fact]
179+
public void ReadEndpointWithSingleSslProtocolSet_ReturnsCorrectValue()
180+
{
181+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
182+
{
183+
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
184+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
185+
}).Build();
186+
var reader = new ConfigurationReader(config);
187+
188+
var endpoint = reader.Endpoints.First();
189+
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
190+
}
191+
192+
[Fact]
193+
public void ReadEndpointWithMultipleSslProtocolsSet_ReturnsCorrectValue()
194+
{
195+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
196+
{
197+
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
198+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
199+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:1", "Tls12"),
200+
}).Build();
201+
var reader = new ConfigurationReader(config);
202+
203+
var endpoint = reader.Endpoints.First();
204+
Assert.Equal(SslProtocols.Tls11|SslProtocols.Tls12, endpoint.SslProtocols);
205+
}
206+
207+
[Fact]
208+
public void ReadEndpointWithSslProtocolSet_ReadsCaseInsensitive()
209+
{
210+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
211+
{
212+
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
213+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "TLS11"),
214+
}).Build();
215+
var reader = new ConfigurationReader(config);
216+
217+
var endpoint = reader.Endpoints.First();
218+
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
219+
}
220+
221+
[Fact]
222+
public void ReadEndpointWithNoSslProtocolSettings_ReturnsNull()
223+
{
224+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
225+
{
226+
new KeyValuePair<string, string>("Endpoints:End1:Url", "http://*:5001"),
227+
}).Build();
228+
var reader = new ConfigurationReader(config);
229+
230+
var endpoint = reader.Endpoints.First();
231+
Assert.Null(endpoint.SslProtocols);
232+
}
233+
234+
[Fact]
235+
public void ReadEndpointDefaultsWithSingleSslProtocolSet_ReturnsCorrectValue()
236+
{
237+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
238+
{
239+
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
240+
}).Build();
241+
var reader = new ConfigurationReader(config);
242+
243+
var endpoint = reader.EndpointDefaults;
244+
Assert.Equal(SslProtocols.Tls11, endpoint.SslProtocols);
245+
}
246+
247+
[Fact]
248+
public void ReadEndpointDefaultsWithNoSslProtocolSettings_ReturnsCorrectValue()
249+
{
250+
var config = new ConfigurationBuilder().Build();
251+
var reader = new ConfigurationReader(config);
252+
253+
var endpoint = reader.EndpointDefaults;
254+
Assert.Null(endpoint.SslProtocols);
255+
}
176256
}
177257
}

src/Servers/Kestrel/Kestrel/test/KestrelConfigurationBuilderTests.cs renamed to src/Servers/Kestrel/Kestrel/test/KestrelConfigurationLoaderTests.cs

+132-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using System.Linq;
8+
using System.Security.Authentication;
89
using System.Security.Cryptography.X509Certificates;
910
using Microsoft.AspNetCore.Hosting;
1011
using Microsoft.AspNetCore.Server.Kestrel.Core;
@@ -18,7 +19,7 @@
1819

1920
namespace Microsoft.AspNetCore.Server.Kestrel.Tests
2021
{
21-
public class KestrelConfigurationBuilderTests
22+
public class KestrelConfigurationLoaderTests
2223
{
2324
private KestrelServerOptions CreateServerOptions()
2425
{
@@ -456,6 +457,136 @@ private void EndpointConfigSectionCanSetProtocols(string input, HttpProtocols ex
456457
Assert.True(ran3);
457458
}
458459

460+
[Fact]
461+
public void EndpointConfigureSection_CanSetSslProtocol()
462+
{
463+
var serverOptions = CreateServerOptions();
464+
var ranDefault = false;
465+
466+
serverOptions.ConfigureHttpsDefaults(opt =>
467+
{
468+
opt.ServerCertificate = TestResources.GetTestCertificate();
469+
470+
// Kestrel default
471+
Assert.Equal(SslProtocols.None, opt.SslProtocols);
472+
ranDefault = true;
473+
});
474+
475+
var ran1 = false;
476+
var ran2 = false;
477+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
478+
{
479+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
480+
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
481+
}).Build();
482+
serverOptions.Configure(config)
483+
.Endpoint("End1", opt =>
484+
{
485+
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
486+
ran1 = true;
487+
})
488+
.Load();
489+
serverOptions.ListenAnyIP(0, opt =>
490+
{
491+
opt.UseHttps(httpsOptions =>
492+
{
493+
// Kestrel default.
494+
Assert.Equal(SslProtocols.None, httpsOptions.SslProtocols);
495+
ran2 = true;
496+
});
497+
});
498+
499+
Assert.True(ranDefault);
500+
Assert.True(ran1);
501+
Assert.True(ran2);
502+
}
503+
504+
[Fact]
505+
public void EndpointConfigureSection_CanOverrideSslProtocolsFromConfigureHttpsDefaults()
506+
{
507+
var serverOptions = CreateServerOptions();
508+
509+
serverOptions.ConfigureHttpsDefaults(opt =>
510+
{
511+
opt.ServerCertificate = TestResources.GetTestCertificate();
512+
opt.SslProtocols = SslProtocols.Tls12;
513+
});
514+
515+
var ran1 = false;
516+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
517+
{
518+
new KeyValuePair<string, string>("Endpoints:End1:SslProtocols:0", "Tls11"),
519+
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
520+
}).Build();
521+
serverOptions.Configure(config)
522+
.Endpoint("End1", opt =>
523+
{
524+
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
525+
ran1 = true;
526+
})
527+
.Load();
528+
529+
Assert.True(ran1);
530+
}
531+
532+
[Fact]
533+
public void DefaultEndpointConfigureSection_CanSetSslProtocols()
534+
{
535+
var serverOptions = CreateServerOptions();
536+
537+
serverOptions.ConfigureHttpsDefaults(opt =>
538+
{
539+
opt.ServerCertificate = TestResources.GetTestCertificate();
540+
});
541+
542+
var ran1 = false;
543+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
544+
{
545+
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
546+
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
547+
}).Build();
548+
serverOptions.Configure(config)
549+
.Endpoint("End1", opt =>
550+
{
551+
Assert.Equal(SslProtocols.Tls11, opt.HttpsOptions.SslProtocols);
552+
ran1 = true;
553+
})
554+
.Load();
555+
556+
Assert.True(ran1);
557+
}
558+
559+
560+
[Fact]
561+
public void DefaultEndpointConfigureSection_ConfigureHttpsDefaultsCanOverrideSslProtocols()
562+
{
563+
var serverOptions = CreateServerOptions();
564+
565+
serverOptions.ConfigureHttpsDefaults(opt =>
566+
{
567+
opt.ServerCertificate = TestResources.GetTestCertificate();
568+
569+
Assert.Equal(SslProtocols.Tls11, opt.SslProtocols);
570+
opt.SslProtocols = SslProtocols.Tls12;
571+
});
572+
573+
var ran1 = false;
574+
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
575+
{
576+
new KeyValuePair<string, string>("EndpointDefaults:SslProtocols:0", "Tls11"),
577+
new KeyValuePair<string, string>("Endpoints:End1:Url", "https://*:5001"),
578+
}).Build();
579+
serverOptions.Configure(config)
580+
.Endpoint("End1", opt =>
581+
{
582+
Assert.Equal(SslProtocols.Tls12, opt.HttpsOptions.SslProtocols);
583+
ran1 = true;
584+
})
585+
.Load();
586+
587+
Assert.True(ran1);
588+
}
589+
459590
[Fact]
460591
public void Latin1RequestHeadersReadFromConfig()
461592
{

0 commit comments

Comments
 (0)