File: Tcp\ClientCredentialTypeCertificateCanonicalNameTests.4.1.0.cs
Web Access
Project: src\src\System.Private.ServiceModel\tests\Scenarios\Security\TransportSecurity\Security.TransportSecurity.IntegrationTests.csproj (Security.TransportSecurity.IntegrationTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using Infrastructure.Common;
using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Security;
using Xunit;
using System.Collections.Generic;
using System.Text;
 
public class Tcp_ClientCredentialTypeCertificateCanonicalNameTests : ConditionalWcfTest
{
    // We set up three endpoints on the WCF service (server) side, each with a different certificate: 
    // Tcp_ClientCredentialType_Certificate_With_CanonicalName_Localhost_Address - is bound to a cert where CN=localhost
    // Tcp_ClientCredentialType_Certificate_With_CanonicalName_DomainName_Address - is bound to a cert where CN=domainname
    // Tcp_ClientCredentialType_Certificate_With_CanonicalName_Fqdn_Address - is bound to a cert with a fqdn, e.g., CN=domainname.example.com
    //
    // When tests are run, a /p:ServiceHost=<name> is specified; if none is specified, then "localhost" is used
    // Hence, we are only able to determine at runtime whether a particular endpoint presented by the WCF Service is going 
    // to pass a variation or fail a variation. 
 
    [WcfFact]
    [Condition(nameof(Root_Certificate_Installed))]
    [OuterLoop]
    public static void Certificate_With_CanonicalName_Localhost_Address_EchoString()
    {
        var localhostEndpointUri = new Uri(Endpoints.Tcp_ClientCredentialType_Certificate_With_CanonicalName_Localhost_Address);
        var endpointAddress = new EndpointAddress(localhostEndpointUri);
        bool shouldCallSucceed = string.Compare(localhostEndpointUri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0;
 
        string testString = "Hello";
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        try
        {
            // *** SETUP *** \\
            NetTcpBinding binding = new NetTcpBinding(SecurityMode.Transport);
            binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
 
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
 
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            string result = serviceProxy.Echo(testString);
 
            // *** VALIDATE *** \\
            Assert.Equal(testString, result);
 
            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy).Close();
            factory.Close();
 
            var errorBuilder = new StringBuilder();
            errorBuilder.AppendFormat("The call to '{0}' should have failed but succeeded. ", localhostEndpointUri.Host);
            errorBuilder.AppendFormat("This means that the certificate validation passed when it should have failed. ");
            errorBuilder.AppendFormat("Check the certificate returned by the endpoint at '{0}' ", localhostEndpointUri);
            errorBuilder.AppendFormat("to see that it is correct; if it is, there is likely an issue with the identity checking logic.");
 
            Assert.True(shouldCallSucceed, errorBuilder.ToString());
        }
        catch (Exception exception) when (exception is CommunicationException || exception is MessageSecurityException)
        {
            if ((exception is MessageSecurityException) || (exception is CommunicationException) && !string.Equals(exception.InnerException.GetType().ToString(), "System.ServiceModel.Security.MessageSecurityException"))
            {
                // If there's a MessageSecurityException, we assume that the cert validation failed. Unfortunately checking for the 
                // message is really brittle and we can't account for loc and how .NET Native will display the exceptions
                // The exception message should look like:
                //
                // System.ServiceModel.Security.MessageSecurityException : Identity check failed for outgoing message. The expected
                // DNS identity of the remote endpoint was 'localhost' but the remote endpoint provided DNS claim 'example.com'.If this
                // is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity 'example.com' as
                // the Identity property of EndpointAddress when creating channel proxy.
 
                var errorBuilder = new StringBuilder();
                errorBuilder.AppendFormat("The call to '{0}' should have been successful but failed with a MessageSecurityException. ", localhostEndpointUri.Host);
                errorBuilder.AppendFormat("This usually means that the certificate validation failed when it should have passed. ");
                errorBuilder.AppendFormat("When connecting to host '{0}', the expectation is that the DNSClaim will be for the same hostname. ", localhostEndpointUri.Host);
                errorBuilder.AppendFormat("Exception message: {0}{1}", Environment.NewLine, exception.Message);
 
                Assert.True(!shouldCallSucceed, errorBuilder.ToString());
            }
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
 
    [WcfFact]
    [Condition(nameof(Root_Certificate_Installed))]
    [OuterLoop]
    public static void Certificate_With_CanonicalName_DomainName_Address_EchoString()
    {
        var domainNameEndpointUri = new Uri(Endpoints.Tcp_ClientCredentialType_Certificate_With_CanonicalName_DomainName_Address);
        var endpointAddress = new EndpointAddress(domainNameEndpointUri);
 
        // We check that: 
        // 1. The WCF service's reported hostname does not contain a '.' (which means we're hitting the FQDN)
        // 2. The WCF service's reported hostname is not "localhost" (which means we're hitting localhost)
        // If both these conditions are true, expect the test to pass. Otherwise, it should fail
        bool shouldCallSucceed = domainNameEndpointUri.Host.IndexOf('.') == -1 && string.Compare(domainNameEndpointUri.Host, "localhost", StringComparison.OrdinalIgnoreCase) != 0;
 
        string testString = "Hello";
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        try
        {
            // *** SETUP *** \\
            NetTcpBinding binding = new NetTcpBinding(SecurityMode.Transport);
            binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
 
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
 
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            string result = serviceProxy.Echo(testString);
 
            // *** VALIDATE *** \\
            Assert.Equal(testString, result);
 
            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy).Close();
            factory.Close();
 
            var errorBuilder = new StringBuilder();
            errorBuilder.AppendFormat("The call to '{0}' should have failed but succeeded. ", domainNameEndpointUri.Host);
            errorBuilder.AppendFormat("This means that the certificate validation passed when it should have failed. ");
            errorBuilder.AppendFormat("Check the certificate returned by the endpoint at '{0}' ", domainNameEndpointUri);
            errorBuilder.AppendFormat("to see that it is correct; if it is, there is likely an issue with the identity checking logic.");
 
            Assert.True(shouldCallSucceed, errorBuilder.ToString());
        }
        catch (Exception exception) when (exception is CommunicationException || exception is MessageSecurityException)
        {
            if ((exception is MessageSecurityException) || (exception is CommunicationException) && !string.Equals(exception.InnerException.GetType().ToString(), "System.ServiceModel.Security.MessageSecurityException"))
            {
                // If there's a MessageSecurityException, we assume that the cert validation failed. Unfortunately checking for the 
                // message is really brittle and we can't account for loc and how .NET Native will display the exceptions
                // The exception message should look like:
                //
                // System.ServiceModel.Security.MessageSecurityException : Identity check failed for outgoing message. The expected
                // DNS identity of the remote endpoint was 'localhost' but the remote endpoint provided DNS claim 'example.com'.If this
                // is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity 'example.com' as
                // the Identity property of EndpointAddress when creating channel proxy.
 
                var errorBuilder = new StringBuilder();
                errorBuilder.AppendFormat("The call to '{0}' should have been successful but failed with a MessageSecurityException. ", domainNameEndpointUri.Host);
                errorBuilder.AppendFormat("This usually means that the certificate validation failed when it should have passed. ");
                errorBuilder.AppendFormat("When connecting to host '{0}', the expectation is that the DNSClaim will be for the same hostname. ", domainNameEndpointUri.Host);
                errorBuilder.AppendFormat("Exception message: {0}{1}", Environment.NewLine, exception.Message);
 
                Assert.True(!shouldCallSucceed, errorBuilder.ToString());
            }
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
 
    [WcfFact]
    [Issue(3572, OS = OSID.OSX)]
    [Condition(nameof(Root_Certificate_Installed))]
    [OuterLoop]
    public static void Certificate_With_CanonicalName_Fqdn_Address_EchoString()
    {
        bool shouldCallSucceed = false;
        var domainNameEndpointUri = new Uri(Endpoints.Tcp_ClientCredentialType_Certificate_With_CanonicalName_DomainName_Address);
        // Get just the hostname part of the domainName Uri
        var domainNameHost = domainNameEndpointUri.Host.Split('.')[0];
 
        var fqdnEndpointUri = new Uri(Endpoints.Tcp_ClientCredentialType_Certificate_With_CanonicalName_Fqdn_Address);
        var endpointAddress = new EndpointAddress(fqdnEndpointUri);
 
        // If the WCF service's reported FQDN is the same as the services's reported hostname, 
        // it means that there the WCF service is set up on a network where FQDNs aren't used, only hostnames.
        // Since our pass/fail detection logic on whether or not this is an FQDN depends on whether the host name has a '.', we don't test this case
        if (string.Compare(domainNameHost, fqdnEndpointUri.Host, StringComparison.OrdinalIgnoreCase) != 0)
        {
            shouldCallSucceed = fqdnEndpointUri.Host.IndexOf('.') > -1;
        }
 
 
        string testString = "Hello";
        ChannelFactory<IWcfService> factory = null;
        IWcfService serviceProxy = null;
 
        try
        {
            // *** SETUP *** \\
            NetTcpBinding binding = new NetTcpBinding(SecurityMode.Transport);
            binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
 
            factory = new ChannelFactory<IWcfService>(binding, endpointAddress);
            factory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
            factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
 
            serviceProxy = factory.CreateChannel();
 
            // *** EXECUTE *** \\
            string result = serviceProxy.Echo(testString);
 
            // *** VALIDATE *** \\
            Assert.Equal(testString, result);
 
            // *** CLEANUP *** \\
            ((ICommunicationObject)serviceProxy).Close();
            factory.Close();
 
            var errorBuilder = new StringBuilder();
            errorBuilder.AppendFormat("The call to '{0}' should have failed but succeeded. ", fqdnEndpointUri.Host);
            errorBuilder.AppendFormat("This means that the certificate validation passed when it should have failed. ");
            errorBuilder.AppendFormat("Check the certificate returned by the endpoint at '{0}' ", fqdnEndpointUri);
            errorBuilder.AppendFormat("to see that it is correct; if it is, there is likely an issue with the identity checking logic.");
 
            Assert.True(shouldCallSucceed, errorBuilder.ToString());
        }
        catch (MessageSecurityException exception)
        {
            // If there's a MessageSecurityException, we assume that the cert validation failed. Unfortunately checking for the 
            // message is really brittle and we can't account for loc and how .NET Native will display the exceptions
            // The exception message should look like:
            //
            // System.ServiceModel.Security.MessageSecurityException : Identity check failed for outgoing message. The expected
            // DNS identity of the remote endpoint was 'localhost' but the remote endpoint provided DNS claim 'example.com'.If this
            // is a legitimate remote endpoint, you can fix the problem by explicitly specifying DNS identity 'example.com' as
            // the Identity property of EndpointAddress when creating channel proxy.
 
            var errorBuilder = new StringBuilder();
            errorBuilder.AppendFormat("The call to '{0}' should have been successful but failed with a MessageSecurityException. ", fqdnEndpointUri.Host);
            errorBuilder.AppendFormat("This usually means that the certificate validation failed when it should have passed. ");
            errorBuilder.AppendFormat("When connecting to host '{0}', the expectation is that the DNSClaim will be for the same hostname. ", fqdnEndpointUri.Host);
            errorBuilder.AppendFormat("Exception message: {0}{1}", Environment.NewLine, exception.Message);
 
            Assert.True(!shouldCallSucceed, errorBuilder.ToString());
        }
        finally
        {
            // *** ENSURE CLEANUP *** \\
            ScenarioTestHelpers.CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
        }
    }
}