diff --git a/MetaPackages.sln b/MetaPackages.sln
index 5fbde25dd9..de2568595f 100644
--- a/MetaPackages.sln
+++ b/MetaPackages.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26419.0
+VisualStudioVersion = 15.0.26424.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}"
EndProject
@@ -24,9 +24,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{192F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{AF5BB04E-92F7-4737-8B98-F86F6244FAB2}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppSettings", "samples\AppSettings\AppSettings.csproj", "{5009D7C8-6061-49CF-9A30-23B309BBEFB0}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.FunctionalTests", "test\Microsoft.AspNetCore.Tests\Microsoft.AspNetCore.FunctionalTests.csproj", "{C72A756A-D29D-44C7-83D4-821DBE82DBCA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.FunctionalTests", "test\Microsoft.AspNetCore.FunctionalTests\Microsoft.AspNetCore.FunctionalTests.csproj", "{C72A756A-D29D-44C7-83D4-821DBE82DBCA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestSites", "TestSites", "{EC22261D-0DE1-47DE-8F7C-072675D6F5B4}"
EndProject
@@ -38,6 +40,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRequestDelegateUrlApp"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "test\TestSites\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{79CF58CE-B020-45D8-BDB5-2D8036BEAD14}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestArtifacts", "TestArtifacts", "{9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90}"
+ ProjectSection(SolutionItems) = preProject
+ test\TestArtifacts\testCert.pfx = test\TestArtifacts\testCert.pfx
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -56,6 +63,10 @@ Global
{AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5009D7C8-6061-49CF-9A30-23B309BBEFB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5009D7C8-6061-49CF-9A30-23B309BBEFB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5009D7C8-6061-49CF-9A30-23B309BBEFB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5009D7C8-6061-49CF-9A30-23B309BBEFB0}.Release|Any CPU.Build.0 = Release|Any CPU
{C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -85,11 +96,13 @@ Global
{CC8F551E-213A-45E8-AECA-507C4DB4F164} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}
{F92CB7A1-C38E-408C-A7EC-A5C040D041E1} = {97D53BEB-A511-4FBE-B784-AB407D9A219F}
{AF5BB04E-92F7-4737-8B98-F86F6244FAB2} = {192F583C-C4CA-43E5-B31C-D21B7806E274}
+ {5009D7C8-6061-49CF-9A30-23B309BBEFB0} = {192F583C-C4CA-43E5-B31C-D21B7806E274}
{C72A756A-D29D-44C7-83D4-821DBE82DBCA} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}
{EC22261D-0DE1-47DE-8F7C-072675D6F5B4} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}
{AB42054B-1801-4FEE-B5C3-8529C6D7BFDA} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
{3A85FA52-F601-422E-A42E-9F187DB28492} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
{401C741B-6C7C-4E08-9F09-C3D43D22C0DE} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
{79CF58CE-B020-45D8-BDB5-2D8036BEAD14} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4}
+ {9BBA7A0A-109A-4AC8-B6EF-A52EA7CF1D90} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}
EndGlobalSection
EndGlobal
diff --git a/samples/AppSettings/AppSettings.csproj b/samples/AppSettings/AppSettings.csproj
new file mode 100644
index 0000000000..bf6be606f3
--- /dev/null
+++ b/samples/AppSettings/AppSettings.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+ netcoreapp2.0
+ aspnetcore-MetaPackagesAppSettings-20170421155031
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/AppSettings/Program.cs b/samples/AppSettings/Program.cs
new file mode 100644
index 0000000000..5db5710d52
--- /dev/null
+++ b/samples/AppSettings/Program.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Http;
+
+namespace AppSettings
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ using (WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
+ {
+ Console.WriteLine("Running application: Press any key to shutdown...");
+ Console.ReadKey();
+ }
+ }
+ }
+}
diff --git a/samples/AppSettings/Properties/launchSettings.json b/samples/AppSettings/Properties/launchSettings.json
new file mode 100644
index 0000000000..fe806c5dc4
--- /dev/null
+++ b/samples/AppSettings/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:53434/",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "AppSettings": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:53435"
+ }
+ }
+}
diff --git a/samples/AppSettings/appsettings.json b/samples/AppSettings/appsettings.json
new file mode 100644
index 0000000000..da7253554d
--- /dev/null
+++ b/samples/AppSettings/appsettings.json
@@ -0,0 +1,63 @@
+{
+ "Kestrel": {
+ "EndPoints": {
+ "Http": {
+ "Address": "127.0.0.1",
+ "Port": 8081
+ },
+ "HttpV6": {
+ "Address": "::1",
+ "Port": 8081
+ },
+ // Add testCert.pfx to the current user's certificate store to enable this scenario.
+ //"HttpsInlineCertStore": {
+ // "Address": "127.0.0.1",
+ // "Port": 8082,
+ // "Certificate": {
+ // "Source": "Store",
+ // "Subject": "cn=localhost",
+ // "StoreName": "My",
+ // "StoreLocation": "CurrentUser",
+ // "AllowInvalid": "True"
+ // }
+ //},
+ "HttpsInlineCertFile": {
+ "Address": "127.0.0.1",
+ "Port": 8083,
+ "Certificate": {
+ "Source": "File",
+ "Path": "testCert.pfx",
+ // TODO: remove when dotnet user-secrets is working again
+ "Password": "testPassword",
+ }
+ },
+ // Add testCert.pfx to the current user's certificate store to enable this scenario.
+ //"HttpsCertStore": {
+ // "Address": "127.0.0.1",
+ // "Port": 8084,
+ // "Certificate": "TestCertInStore"
+ //},
+ "HttpsCertFile": {
+ "Address": "127.0.0.1",
+ "Port": 8085,
+ "Certificate": "TestCert"
+ }
+ }
+ },
+ "Certificates": {
+ "TestCert": {
+ "Source": "File",
+ "Path": "testCert.pfx",
+ // TODO: remove when dotnet user-secrets is working again
+ "Password": "testPassword"
+ },
+ // Add testCert.pfx to the current user's certificate store to enable this scenario.
+ //"TestCertInStore": {
+ // "Source": "Store",
+ // "Subject": "cn=localhost",
+ // "StoreName": "My",
+ // "StoreLocation": "CurrentUser",
+ // "AllowInvalid": "True"
+ //}
+ }
+}
diff --git a/samples/AppSettings/testCert.pfx b/samples/AppSettings/testCert.pfx
new file mode 100644
index 0000000000..7118908c2d
Binary files /dev/null and b/samples/AppSettings/testCert.pfx differ
diff --git a/samples/SampleApp/SampleApp.csproj b/samples/SampleApp/SampleApp.csproj
index 6f2a348a9f..37ac635939 100644
--- a/samples/SampleApp/SampleApp.csproj
+++ b/samples/SampleApp/SampleApp.csproj
@@ -1,6 +1,6 @@
-
+
netcoreapp2.0
diff --git a/src/Microsoft.AspNetCore/CertificateLoader.cs b/src/Microsoft.AspNetCore/CertificateLoader.cs
new file mode 100644
index 0000000000..95339040a1
--- /dev/null
+++ b/src/Microsoft.AspNetCore/CertificateLoader.cs
@@ -0,0 +1,173 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore
+{
+ ///
+ /// A helper class to load certificates from files and certificate stores based on data.
+ ///
+ public static class CertificateLoader
+ {
+ ///
+ /// Loads one or more certificates from a single source.
+ ///
+ /// An with information about a certificate source.
+ /// The certificate password, in case it's being loaded from a file.
+ /// The loaded certificates.
+ public static X509Certificate2 Load(IConfiguration certificateConfiguration, string password)
+ {
+ var sourceKind = certificateConfiguration.GetValue("Source");
+
+ CertificateSource certificateSource;
+ switch (sourceKind.ToLowerInvariant())
+ {
+ case "file":
+ certificateSource = new CertificateFileSource(password);
+ break;
+ case "store":
+ certificateSource = new CertificateStoreSource();
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid certificate source kind: {sourceKind}");
+ }
+
+ certificateConfiguration.Bind(certificateSource);
+ return certificateSource.Load();
+ }
+
+ ///
+ /// Loads all certificates specified in an .
+ ///
+ /// The root .
+ ///
+ /// A dictionary mapping certificate names to loaded certificates.
+ ///
+ public static Dictionary LoadAll(IConfiguration configurationRoot)
+ {
+ return configurationRoot.GetSection("Certificates").GetChildren()
+ .ToDictionary(
+ certificateSource => certificateSource.Key,
+ certificateSource => Load(certificateSource, certificateSource["Password"]));
+ }
+
+ private abstract class CertificateSource
+ {
+ public string Source { get; set; }
+
+ public abstract X509Certificate2 Load();
+ }
+
+ private class CertificateFileSource : CertificateSource
+ {
+ private readonly string _password;
+
+ public CertificateFileSource(string password)
+ {
+ _password = password;
+ }
+
+ public string Path { get; set; }
+
+ public override X509Certificate2 Load()
+ {
+ var certificate = TryLoad(X509KeyStorageFlags.DefaultKeySet, out var error)
+ ?? TryLoad(X509KeyStorageFlags.UserKeySet, out error)
+ #if NETCOREAPP2_0
+ ?? TryLoad(X509KeyStorageFlags.EphemeralKeySet, out error)
+ #endif
+ ;
+
+ if (error != null)
+ {
+ throw error;
+ }
+
+ return certificate;
+ }
+
+ private X509Certificate2 TryLoad(X509KeyStorageFlags flags, out Exception exception)
+ {
+ try
+ {
+ var loadedCertificate = new X509Certificate2(Path, _password, flags);
+ exception = null;
+ return loadedCertificate;
+ }
+ catch (Exception e)
+ {
+ exception = e;
+ return null;
+ }
+ }
+ }
+
+ private class CertificateStoreSource : CertificateSource
+ {
+ public string Subject { get; set; }
+ public string StoreName { get; set; }
+ public string StoreLocation { get; set; }
+ public bool AllowInvalid { get; set; }
+
+ public override X509Certificate2 Load()
+ {
+ if (!Enum.TryParse(StoreLocation, ignoreCase: true, result: out StoreLocation storeLocation))
+ {
+ throw new InvalidOperationException($"Invalid store location: {StoreLocation}");
+ }
+
+ using (var store = new X509Store(StoreName, storeLocation))
+ {
+ X509Certificate2Collection storeCertificates = null;
+ X509Certificate2Collection foundCertificates = null;
+ X509Certificate2 foundCertificate = null;
+
+ try
+ {
+ store.Open(OpenFlags.ReadOnly);
+ storeCertificates = store.Certificates;
+ foundCertificates = storeCertificates.Find(X509FindType.FindBySubjectDistinguishedName, Subject, validOnly: !AllowInvalid);
+ foundCertificate = foundCertificates
+ .OfType()
+ .OrderByDescending(certificate => certificate.NotAfter)
+ .FirstOrDefault();
+
+ if (foundCertificate == null)
+ {
+ throw new InvalidOperationException($"No certificate found for {Subject} in store {StoreName} in {StoreLocation}");
+ }
+
+ return foundCertificate;
+ }
+ finally
+ {
+ if (foundCertificate != null)
+ {
+ storeCertificates.Remove(foundCertificate);
+ foundCertificates.Remove(foundCertificate);
+ }
+
+ DisposeCertificates(storeCertificates);
+ DisposeCertificates(foundCertificates);
+ }
+ }
+ }
+
+ private void DisposeCertificates(X509Certificate2Collection certificates)
+ {
+ if (certificates != null)
+ {
+ foreach (var certificate in certificates)
+ {
+ certificate.Dispose();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore/KestrelServerOptionsSetup.cs b/src/Microsoft.AspNetCore/KestrelServerOptionsSetup.cs
new file mode 100644
index 0000000000..888ece3903
--- /dev/null
+++ b/src/Microsoft.AspNetCore/KestrelServerOptionsSetup.cs
@@ -0,0 +1,103 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Https;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore
+{
+ ///
+ /// Binds Kestrel configuration.
+ ///
+ public class KestrelServerOptionsSetup : IConfigureOptions
+ {
+ private readonly IConfiguration _configurationRoot;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The root .
+ public KestrelServerOptionsSetup(IConfiguration configurationRoot)
+ {
+ _configurationRoot = configurationRoot;
+ }
+
+ ///
+ /// Configures a instance.
+ ///
+ /// The to configure.
+ public void Configure(KestrelServerOptions options)
+ {
+ BindConfiguration(options);
+ }
+
+ private void BindConfiguration(KestrelServerOptions options)
+ {
+ var certificates = CertificateLoader.LoadAll(_configurationRoot);
+ var endPoints = _configurationRoot.GetSection("Kestrel:EndPoints");
+
+ foreach (var endPoint in endPoints.GetChildren())
+ {
+ BindEndPoint(options, endPoint, certificates);
+ }
+ }
+
+ private void BindEndPoint(
+ KestrelServerOptions options,
+ IConfigurationSection endPoint,
+ Dictionary certificates)
+ {
+ var addressValue = endPoint.GetValue("Address");
+ var portValue = endPoint.GetValue("Port");
+
+ IPAddress address;
+ if (!IPAddress.TryParse(addressValue, out address))
+ {
+ throw new InvalidOperationException($"Invalid IP address: {addressValue}");
+ }
+
+ int port;
+ if (!int.TryParse(portValue, out port))
+ {
+ throw new InvalidOperationException($"Invalid port: {portValue}");
+ }
+
+ options.Listen(address, port, listenOptions =>
+ {
+ var certificateName = endPoint.GetValue("Certificate");
+
+ X509Certificate2 endPointCertificate = null;
+ if (certificateName != null)
+ {
+ if (!certificates.TryGetValue(certificateName, out endPointCertificate))
+ {
+ throw new InvalidOperationException($"No certificate named {certificateName} found in configuration");
+ }
+ }
+ else
+ {
+ var certificate = endPoint.GetSection("Certificate");
+
+ if (certificate.GetChildren().Any())
+ {
+ endPointCertificate = CertificateLoader.Load(certificate, certificate["Password"]);
+ }
+ }
+
+ if (endPointCertificate != null)
+ {
+ listenOptions.UseHttps(endPointCertificate);
+ }
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj b/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj
index cae574de41..5350bf93a3 100644
--- a/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj
+++ b/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Microsoft.AspNetCore/WebHost.cs b/src/Microsoft.AspNetCore/WebHost.cs
index 5bd9d11ba9..5cc626fd5e 100644
--- a/src/Microsoft.AspNetCore/WebHost.cs
+++ b/src/Microsoft.AspNetCore/WebHost.cs
@@ -8,9 +8,11 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore
{
@@ -182,6 +184,7 @@ namespace Microsoft.AspNetCore
.ConfigureServices(services =>
{
services.AddSingleton();
+ services.AddTransient, KestrelServerOptionsSetup>();
});
return builder;
diff --git a/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.FunctionalTests.csproj b/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj
similarity index 75%
rename from test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.FunctionalTests.csproj
rename to test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj
index d29ae617ca..a4676dcc7f 100644
--- a/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.FunctionalTests.csproj
+++ b/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj
@@ -6,6 +6,10 @@
netcoreapp2.0
+
+
+
+
@@ -17,4 +21,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Tests/WebHostFunctionalTests.cs b/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs
similarity index 58%
rename from test/Microsoft.AspNetCore.Tests/WebHostFunctionalTests.cs
rename to test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs
index f925eb91f4..99485cab7d 100644
--- a/test/Microsoft.AspNetCore.Tests/WebHostFunctionalTests.cs
+++ b/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs
@@ -4,9 +4,12 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Net.Http;
-using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
@@ -74,6 +77,129 @@ namespace Microsoft.AspNetCore.Tests
}, setTestEnvVars: true);
}
+ [Theory]
+ [InlineData("127.0.0.1", "127.0.0.1")]
+ [InlineData("::1", "[::1]")]
+ public async Task BindsKestrelHttpEndPointFromConfiguration(string endPointAddress, string requestAddress)
+ {
+ try
+ {
+ File.WriteAllText("appsettings.json", @"
+{
+ ""Kestrel"": {
+ ""EndPoints"": {
+ ""EndPoint"": {
+ ""Address"": """ + endPointAddress + @""",
+ ""Port"": 0
+ }
+ }
+ }
+}
+");
+ using (var webHost = WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
+ {
+ var port = GetWebHostPort(webHost);
+
+ Assert.NotEqual(0, port);
+
+ using (var client = new HttpClient())
+ {
+ var response = await client.GetAsync($"http://{requestAddress}:{port}");
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
+ finally
+ {
+ File.Delete("appsettings.json");
+ }
+ }
+
+ [Fact]
+ public async Task BindsKestrelHttpsEndPointFromConfiguration_ReferencedCertificateFile()
+ {
+ try
+ {
+ File.WriteAllText("appsettings.json", @"
+{
+ ""Kestrel"": {
+ ""EndPoints"": {
+ ""EndPoint"": {
+ ""Address"": ""127.0.0.1"",
+ ""Port"": 0,
+ ""Certificate"": ""TestCert""
+ }
+ }
+ },
+ ""Certificates"": {
+ ""TestCert"": {
+ ""Source"": ""File"",
+ ""Path"": ""testCert.pfx"",
+ ""Password"": ""testPassword""
+ }
+ }
+}
+");
+ using (var webHost = WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
+ {
+ var port = GetWebHostPort(webHost);
+
+ Assert.NotEqual(0, port);
+
+ using (var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true }))
+ {
+ var response = await client.GetAsync($"https://127.0.0.1:{port}");
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
+ finally
+ {
+ File.Delete("appsettings.json");
+ }
+ }
+
+ [Fact]
+ public async Task BindsKestrelHttpsEndPointFromConfiguration_InlineCertificateFile()
+ {
+ try
+ {
+ File.WriteAllText("appsettings.json", @"
+{
+ ""Kestrel"": {
+ ""EndPoints"": {
+ ""EndPoint"": {
+ ""Address"": ""127.0.0.1"",
+ ""Port"": 0,
+ ""Certificate"": {
+ ""Source"": ""File"",
+ ""Path"": ""testCert.pfx"",
+ ""Password"": ""testPassword""
+ }
+ }
+ }
+ }
+}
+");
+ using (var webHost = WebHost.Start(context => context.Response.WriteAsync("Hello, World!")))
+ {
+ var port = GetWebHostPort(webHost);
+
+ Assert.NotEqual(0, port);
+
+ using (var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true }))
+ {
+ var response = await client.GetAsync($"https://127.0.0.1:{port}");
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
+ finally
+ {
+ File.Delete("appsettings.json");
+ }
+ }
+
private async Task ExecuteStartOrStartWithTest(Func> getResponse, string applicationName)
{
await ExecuteTestApp(applicationName, async (deploymentResult, logger) =>
@@ -135,5 +261,10 @@ namespace Microsoft.AspNetCore.Tests
throw new Exception($"Solution root could not be found using {applicationBasePath}");
}
+
+ private static int GetWebHostPort(IWebHost webHost)
+ => webHost.ServerFeatures.Get().Addresses
+ .Select(serverAddress => new Uri(serverAddress).Port)
+ .FirstOrDefault();
}
}
diff --git a/test/TestArtifacts/testCert.pfx b/test/TestArtifacts/testCert.pfx
new file mode 100644
index 0000000000..7118908c2d
Binary files /dev/null and b/test/TestArtifacts/testCert.pfx differ