SslStream.DomainValidation Event

This event is raised when the domain validation check is performed during the SSL/TLS handshake.

public event System.EventHandler DomainValidation;

This event allows you to customize the domain validation process. By default, .NET validates the server's certificate against the trusted root certificates on the system. You can hook into this event to implement custom validation logic, such as checking against a specific list of approved domains or performing more in-depth certificate checks.

Event Data

The event is raised with an instance of System.Net.Security.SslPolicyErrorsEventArgs, which provides the following information:

  • SslPolicyErrors: A bitwise combination of enumeration values that indicate the reasons the server certificate validation failed. If the certificate is valid, this will be SslPolicyErrors.None.
  • ServerCertificate: The System.Security.Cryptography.X509Certificates.X509Certificate2 object representing the server's certificate.

Requirements

Namespace: System.Net.Security

Assembly: System.Net.Security.dll

Platform: Windows, Linux, macOS

Example

The following C# code demonstrates how to subscribe to the DomainValidation event and implement custom validation logic.


using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

public class CustomSslValidator
{
    public static async Task ConnectWithCustomValidation(string host, int port)
    {
        try
        {
            TcpClient client = new TcpClient();
            await client.ConnectAsync(host, port);

            using (SslStream sslStream = new SslStream(client.GetStream(), false,
                new RemoteCertificateValidationCallback(ValidateServerCertificate)))
            {
                await sslStream.AuthenticateAsClientAsync(host);

                // Perform custom validation via event if needed (though callback is more direct)
                // If you prefer using the event for a different approach:
                // sslStream.DomainValidation += (sender, e) => {
                //     Console.WriteLine($"Domain Validation Event Triggered. Errors: {e.SslPolicyErrors}");
                //     // Add custom logic here if the callback needs augmentation
                // };
                // Note: The RemoteCertificateValidationCallback is the primary mechanism for validation.
                // The DomainValidation event is typically observed if the callback handler doesn't fully resolve validation.

                Console.WriteLine($"Successfully connected to {host}:{port} with custom validation.");
                // ... send/receive data ...
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Connection failed: {ex.Message}");
        }
    }

    public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        Console.WriteLine($"Certificate validation initiated. Errors: {sslPolicyErrors}");

        // If there are no errors, the certificate is considered valid.
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            Console.WriteLine("Certificate is valid.");
            return true;
        }

        // Custom validation logic:
        // For demonstration, let's allow self-signed certificates if they are for our internal server.
        // In a real application, you'd have more robust checks.

        Console.WriteLine("Certificate has errors. Attempting custom validation.");

        X509Certificate2 cert2 = certificate as X509Certificate2;
        if (cert2 != null)
        {
            Console.WriteLine($"Issuer: {cert2.Issuer}");
            Console.WriteLine($"Subject: {cert2.Subject}");

            // Example: Allow if it's a specific internal domain (replace with your domain)
            // if (cert2.Subject.Contains("CN=my.internal.server.local"))
            // {
            //     Console.WriteLine("Allowing internal server certificate.");
            //     return true;
            // }

            // Example: For self-signed certs, we might need to add it to trusted store or check thumbprint
            if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors) ||
                sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch) ||
                sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
            {
                Console.WriteLine("Certificate has validation issues, but attempting to proceed with custom logic.");
                // Add your specific validation logic here. For example:
                // - Check if the certificate's thumbprint is in a known list.
                // - Check if the certificate is trusted by a specific root not in the system store.
                // - Check for specific subject alternative names.

                // For this example, let's pretend we trust any certificate from a specific issuer name (for demo only)
                // if (cert2.Issuer.Contains("My Custom CA"))
                // {
                //     Console.WriteLine("Trusted by custom CA.");
                //     return true;
                // }
            }
        }

        Console.WriteLine("Custom validation failed. Certificate is not trusted.");
        return false; // Deny the connection if validation fails
    }

    // Example of how to call this method:
    // public static async Task Main(string[] args)
    // {
    //     await ConnectWithCustomValidation("example.com", 443);
    // }
}