using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.MxGateway.Server; namespace ZB.MOM.WW.MxGateway.Tests.Gateway; public sealed class GatewayTlsBootstrapTests { /// /// Verifies that when a Kestrel HTTPS endpoint is configured without its own certificate, /// the gateway supplies the generated self-signed certificate as the Kestrel HTTPS default. /// The host must start and bind, and the certificate served on the TLS handshake must be the /// gateway's generated cert (subject CN=MxAccessGateway Self-Signed) — not an ambient /// ASP.NET Core development certificate. On a host with no dev cert installed, starting a /// cert-less HTTPS endpoint throws "No server certificate was specified"; on a host that has a /// trusted dev cert, Kestrel would otherwise serve that dev cert (CN=localhost), so the /// subject assertion is what makes this test fail without the wiring on either kind of host. /// [Fact] public async Task Host_ServesGeneratedCertificate_WhenHttpsEndpointHasNoCertificate() { string certDir = Directory.CreateTempSubdirectory().FullName; try { Environment.SetEnvironmentVariable("Kestrel__Endpoints__Test__Url", "https://127.0.0.1:0"); Environment.SetEnvironmentVariable( "MxGateway__Tls__SelfSignedCertPath", Path.Combine(certDir, "gw.pfx")); WebApplication app = GatewayApplication.Build([]); await app.StartAsync(); try { string servedSubject = await ReadServedCertificateSubjectAsync(app); Assert.Contains("MxAccessGateway Self-Signed", servedSubject, StringComparison.Ordinal); } finally { await app.StopAsync(); await app.DisposeAsync(); } } finally { Environment.SetEnvironmentVariable("Kestrel__Endpoints__Test__Url", null); Environment.SetEnvironmentVariable("MxGateway__Tls__SelfSignedCertPath", null); Directory.Delete(certDir, recursive: true); } } private static async Task ReadServedCertificateSubjectAsync(WebApplication app) { IServerAddressesFeature addresses = app.Services.GetRequiredService().Features.Get() ?? throw new InvalidOperationException("Server addresses feature was not available."); Uri endpoint = new(addresses.Addresses.First()); using TcpClient client = new(); await client.ConnectAsync(endpoint.Host, endpoint.Port); using SslStream ssl = new( client.GetStream(), leaveInnerStreamOpen: false, userCertificateValidationCallback: (_, _, _, _) => true); await ssl.AuthenticateAsClientAsync("127.0.0.1"); return ssl.RemoteCertificate?.Subject ?? "(none)"; } }