Search code examples
c#wcfssl-certificatewcf-bindingwcf-client

Calling a WCF service with certificate from a client application without having the contract interface


My particular problem is something like this:

  • I am able to invoke WCF service dynamically, but i need to provide certificate.
  • We don't want to use app.config.
  • Major issue is that WCF service is a 3rd party url.
  • Actually, i am getting an exception while invoke instance that certificate is not provided.
  • My code is something like this:

        try
        {
            // Define the metadata address, contract name, operation name, 
            // and parameters. 
            // You can choose between MEX endpoint and HTTP GET by 
            // changing the address and enum value.
            Uri mexAddress = new Uri("http://Some3rdPartyURL.svc?wsdl");//Some 3rd party url
            // For MEX endpoints use a MEX address and a 
            // mexMode of .MetadataExchange
    
    
            MetadataExchangeClientMode mexMode = MetadataExchangeClientMode.HttpGet;
    
            string contractName = "IService";//"";//3rd party service name
            string operationName = "SendMethod";//3rd party method name
            object[] args = new object[] { "", "", "0" };//3rd party required parameters
            //object[] operationParameters = new object[] { /*1, 2*/args };
    
            // Get the metadata file from the service.
            MetadataExchangeClient mexClient =
            new MetadataExchangeClient(mexAddress, mexMode);
            mexClient.ResolveMetadataReferences = true;
            MetadataSet metaSet = mexClient.GetMetadata();
    
            // Import all contracts and endpoints
            WsdlImporter importer = new WsdlImporter(metaSet);
    
            Collection<ContractDescription> contracts =
                   importer.ImportAllContracts();
            ServiceEndpointCollection allEndpoints = importer.ImportAllEndpoints();
    
            // Generate type information for each contract
            ServiceContractGenerator generator = new ServiceContractGenerator();
    
            var endpointsForContracts = new Dictionary<string, IEnumerable<ServiceEndpoint>>();
    
            foreach (ContractDescription contract in contracts)
            {
                generator.GenerateServiceContractType(contract);
                // Keep a list of each contract's endpoints
                endpointsForContracts[contract.Name] = allEndpoints.Where(
                  se => se.Contract.Name == contract.Name).ToList();
            }
    
            if (generator.Errors.Count != 0)
                throw new Exception("There were errors during code compilation.");
    
            // Generate a code file for the contracts 
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("C#");
    
            // Compile the code file to an in-memory assembly
            // Don't forget to add all WCF-related assemblies as references
            CompilerParameters compilerParameters = new CompilerParameters(
                 new string[] { 
                     "System.dll", "System.ServiceModel.dll", 
                    "System.Runtime.Serialization.dll"});
            compilerParameters.GenerateInMemory = true;
    
            CompilerResults results = codeDomProvider.CompileAssemblyFromDom(
                compilerParameters, generator.TargetCompileUnit);
    
            if (results.Errors.Count > 0)
            {
                throw new Exception("There were errors during generated code compilation");
            }
            else
            {
                // Find the proxy type that was generated for the specified contract
                // (identified by a class that implements 
                // the contract and ICommunicationbject)
                Type clientProxyType = results.CompiledAssembly.GetTypes().First(
                    t => t.IsClass &&
                       t.GetInterface(contractName) != null &&
                        t.GetInterface(typeof(ICommunicationObject).Name) != null);
    
    
                // Get the first service endpoint for the contract
                ServiceEndpoint se = endpointsForContracts[contractName].First();
                //se = endpointsForContracts[contractName].First();
    
                WSHttpBinding wsBinding = this.fillWsHttpBinding();                 
                string encodeValue = "MIIFazCCBFO****"; // here we should use the encoded certificate value generated by svcutil.exe.
                X509Certificate2Collection supportingCertificates = new X509Certificate2Collection();
                supportingCertificates.Import(Convert.FromBase64String(encodeValue));
                X509Certificate2 primaryCertificate = supportingCertificates[0];
                supportingCertificates.RemoveAt(0);
                EndpointIdentity.CreateX509CertificateIdentity(primaryCertificate, supportingCertificates);
                EndpointIdentity identity = EndpointIdentity.CreateX509CertificateIdentity(primaryCertificate);
                var endpoint = new EndpointAddress(mexAddress,  identity);
    
                se.Binding = wsBinding;
                se.Address = endpoint;
    
                // Create an instance of the proxy
                // Pass the endpoint's binding and address as parameters
                // to the ctor                  
                object instance = results.CompiledAssembly.CreateInstance(
                     clientProxyType.Name,
                   false,
                    System.Reflection.BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance,
                       null,
                    new object[] { se.Binding, se.Address }, CultureInfo.CurrentCulture, null);
    
                var methodInfo = instance.GetType().GetMethod(operationName);
                var methodParams = methodInfo.GetParameters();
                int count = args.Count();
                object[] args2 = new object[count];
                int i = 0;
                foreach (var service1 in methodParams)
                {
                    args2[i] = Convert.ChangeType(args[i], Type.GetType("System." + service1.ParameterType.Name));
                    i += 1;
                }
                Object retVal = instance.GetType().GetMethod(operationName).Invoke(instance, args2);/*Getting Error that certificate not provided???*/
            }
        }
        catch (Exception ex)
        {
            string error = ex.ToString();
            MessageBox.Show("Error Invoking Method: " + ex.Message);
        }
    

    private WSHttpBinding fillWsHttpBinding() {

        WSHttpBinding wsBinding = new WSHttpBinding();
        wsBinding.CloseTimeout = new TimeSpan(0, 1, 0);
        wsBinding.OpenTimeout = new TimeSpan(0, 10, 0);
        wsBinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
        wsBinding.SendTimeout = new TimeSpan(0, 5, 30);
        wsBinding.BypassProxyOnLocal = false;
        wsBinding.TransactionFlow = false;
        wsBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
        wsBinding.MaxBufferPoolSize = 524288L;
        wsBinding.MaxReceivedMessageSize = 10485760L;
        wsBinding.MessageEncoding = WSMessageEncoding.Text;
        wsBinding.TextEncoding = Encoding.UTF8;
        wsBinding.UseDefaultWebProxy = true;
        wsBinding.AllowCookies = false;
    
        wsBinding.ReaderQuotas.MaxDepth = 32;
        wsBinding.ReaderQuotas.MaxStringContentLength = 8192;
        wsBinding.ReaderQuotas.MaxArrayLength = 10485760;
        wsBinding.ReaderQuotas.MaxNameTableCharCount = 16384;
    
        wsBinding.ReliableSession.Ordered = true;
        wsBinding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(10);
        wsBinding.ReliableSession.Enabled = false;
    
        wsBinding.Security.Mode = SecurityMode.Message;
        wsBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
        wsBinding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
        wsBinding.Security.Transport.Realm = "";
    
        wsBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
        wsBinding.Security.Message.NegotiateServiceCredential = true;
        wsBinding.Security.Message.AlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Basic256;
    
        return wsBinding;
    }
    

Solution

  • Where you are setting the certificate on the EndpointIdentity causes it to be used to validate the service's identity. You haven't set the client's credentials using instance.ClientCredentials.ClientCertificate.SetCertificate(...). You'll have to get the ClientCredentials from the ClientBase<> using reflection. See http://msdn.microsoft.com/en-us/library/ms732391%28v=vs.110%29.aspx.

    var credentialProperty = instance.GetType().GetProperty("ClientCredentials");
    var credentials = (ClientCredentials)credentialProperty.GetValue(instance, null);
    credentials.ClientCertificate.SetCertficate(...);
    

    You've also got two calls to EndpointIdentity.CreateX509CertificateIdentity, ignoring the output of one of them.