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

5.6k views Asked by At

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;
    }
    
1

There are 1 answers

4
Simon Taylor On BEST ANSWER

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.