Udostępnij za pośrednictwem


How to: Use Transport Security

The .NET Compact Framework version 3.5 supports the use of the HTTPS transport to connect to a Windows Communication Foundation (WCF) service on the desktop. It includes support for server authentication and client authentication.

This topic provides an example of the service configuration and shows how to modify client code for mutual authentication.

Note

For server authentication only, a client certificate is not required. Message security is also supported in the .NET Compact Framework 3.5, but it is not used in this example.

To create the WCF service for the desktop

  1. Create and install a server certificate and a client certificate.

    These steps are specific to the certificate generation tool you are using (for example, Makecert.exe) and are beyond the scope of this topic. The following tasks will be necessary:

    • Create a self-signed certificate and name it (for example, use your company name: company).

    • Create a server certificate signed by company. The server certificate name must match the URL host name used to access the service

    • Create a client certificate signed by company.

    Note

    We recommend that you install the server certificate into the local machine instead of installing it to the current user. Otherwise, if the service is hosted in Internet Information Services (IIS) and you install it to the current user, it will not work.

  2. Create a new Web service project.

  3. Replace the Web.config file with the example shown in this step. Modify the following elements and attributes in the file:

    • Change the service name attribute to the new service you are using.

    • Change the behaviorConfiguration attribute to refer to the new behavior name.

    • Change the endpoint contract attribute to refer to the service interface.

    Note

    Make sure that the binding attribute value for the <endpoint> element is "basicHttpBinding". The .NET Compact Framework supports text encoding, but not binary encoding.

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <services>
          <service 
              name="CalculatorService"
              behaviorConfiguration="MyServiceTypeBehaviors">
            <endpoint address=""
                      binding="basicHttpBinding"
                      bindingConfiguration="transport"
                      contract="ICalculatorService" />
            <endpoint address="mex"
                      binding="basicHttpBinding"
                      bindingConfiguration="transport"
                      contract="IMetadataExchange" />
          </service>
        </services>
        <bindings>
          <basicHttpBinding>
            <binding name="transport">
              <security mode="Transport">
                <transport clientCredentialType="Certificate" />
              </security>
            </binding>
          </basicHttpBinding>
        </bindings>
        <!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
        <behaviors>
          <serviceBehaviors>
            <behavior name="MyServiceTypeBehaviors">
              <serviceMetadata httpsGetEnabled="True" httpsGetUrl=""/>
              <serviceDebug includeExceptionDetailInFaults="False" />
              <serviceCredentials>
                <clientCertificate>
                   <authentication trustedStoreLocation="LocalMachine"
                               revocationMode="NoCheck"
                               certificateValidationMode="ChainTrust"/>
                </clientCertificate>
              </serviceCredentials>
            </behavior>
          </serviceBehaviors>
        </behaviors>
    
      </system.serviceModel>
    
    </configuration>
    
  4. In the source code for the WCF service, remove any parameters specified in the ServiceContract and OperationContract attributes from your code.

    Note

    This sample does not implement support for parameters specified in contracts such as ServiceContract and OperationContract. If you need parameter support for these contracts, you can use the WCF .NET Compact Framework ServiceModel Utility tool (NetCFSvcUtil.exe) to generate client code. This tool builds support for many of these parameters into applications that are based on the .NET Compact Framework. NetCFSvcUtil.exe is available in the Power Toys for .NET Compact Framework. For more information, see Power Toys for .NET Compact Framework.

    The following example shows the WCF service source code for a simplified calculator application.

    <ServiceContract()>  _
    Public Interface ICalculatorService
        <OperationContract()>  _
        Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double  
        '<OperationContract()>  _ 
        Function Subtract(ByVal n1 As Double, ByVal n2 As Double) As Double 
    End Interface 
    
    
    Public Class CalculatorService
        Implements ICalculatorService
    
        Public Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Add
            Return n1 + n2
    
        End Function 
    
        Public Function Subtract(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Subtract
            Return n1 - n2
    
        End Function 
    End Class
    
    [ServiceContract()]
    public interface ICalculatorService
    {
        [OperationContract]
        double Add(double n1, double n2);
        [OperationContract]
        double Subtract(double n1, double n2);
    }
    
    public class CalculatorService : ICalculatorService
    {
        public double Add(double n1, double n2) { return n1 + n2; }
        public double Subtract(double n1, double n2) { return n1 - n2; }
    }
    
  5. Create a Web site or virtual directory and reference your Web service project. On the Web server, configure the service to require HTTPS and a client certificate.

    Note

    In IIS, you must specify the server certificate and the client certificate.

  6. Start the Web server.

    If you want to view Web Services Description Language (WSDL) output and run the service on localhost, browse to https://localhost/CalculatorService/Service.svc?wsdl. Use the same Web project name that you specified for the WCF service.

  7. Verify that you can access the directory from a desktop browser and a device browser by using HTTPS.

    You must make sure that certificates are configured correctly before you can access the service. The Web server may also have to be configured to handle requests for a WCF service.

To create the .NET Compact Framework client

  1. While the service is running, open a command line and navigate to the directory where the WCF service is located.

  2. From the command line, run the WCF ServiceModel Desktop Utility tool (SvcUtil.exe) to generate a WCF client proxy. The following example shows the command-line invocation for SvcUtil in which the service is hosted on localhost:

    svcutil.exe /language:c# https://localhost/CalculatorService/Service.svc
    
  3. Remove unsupported attributes and elements from the generated client proxy code, including the following:

    • All System.ServiceModel attributes.

    • References to the IClientChannel class.

    • References to <endpoint> configuration names.

    • Method implementations that call methods of the ServiceContract interface on the internal channel.

    For an example of this step, see How to: Use the HTTP Transport.

  4. Create a client project.

  5. Add the generated client proxy to the project.

  6. In the generated proxy code, change the fully-qualified reference to ClientBase<TChannel> to the user-defined ClientBase class.

  7. In the generated proxy code, add method implementations by invoking the Call method in the user-defined ClientBase class.

    Public Function Add(ByVal n1 As Double, ByVal n2 As Double) As Double Implements ICalculatorService.Add
        Return System.Convert.ToDouble(MyBase.Call("Add", "https://fabrikam.com/CalcService/ICalculatorService/Add", New String() {"n1", "n2"}, New Object() {n1, n2}, GetType(Double)))
    
    End Function
    
    public double Add(double n1, double n2)
    {
        return (double)base.Call("Add", "https://fabrikam.com/CalcService/ICalculatorService/Add", new string[] { "n1", "n2" }, new object[] { n1, n2 }, typeof(double));
    }
    
  8. Add the base class for the proxy to the project. This class is named ClientBase.

    Change the base class reference of your client proxy to point to your implementation of ClientBase.

    Note

    In this example, the CustomBodyWriter class in ClientBase supports only primitive types. To support non-primitive types, you have to extend the OnWriteBodyContents method. For example, you could call a custom serializer to serialize message data. In this case, you would translate code attributes in the generated client proxy to attributes that the XML serializer could consume. In this scenario, you must first add the following switch when you run SvcUtil: /serializer:xmlserializer http://endpoint.

    The following code shows an example of the ClientBase class. A ClientCredentials object is used to specify the X.509 certificate used by the client, which is named testuser in this example.

    Public Class ClientBase(Of TChannel As Class)
    
        Private requestChannel As IRequestChannel
        Private messageVersion As MessageVersion
    
    
        Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
            'this.remoteAddress = remoteAddress; 
            Me.messageVersion = binding.MessageVersion
    
            Dim parameters = New System.ServiceModel.Channels.BindingParameterCollection()
    
            ' Specifies the X.509 certificate used by the client. 
            Dim cc As New ClientCredentials()
            cc.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "testuser")
            parameters.Add(cc)
    
            Dim channelFactory As IChannelFactory(Of IRequestChannel)
            channelFactory = binding.BuildChannelFactory(Of IRequestChannel)(parameters)
            channelFactory.Open()
            Me.requestChannel = channelFactory.CreateChannel(remoteAddress)
    
        End Sub 
    
    
        Public Function [Call](ByVal op As String, ByVal action As String, ByVal varnames() As String, ByVal varvals() As Object, ByVal returntype As Type) As Object
            requestChannel.Open(TimeSpan.MaxValue)
    
            'Message msg = 
            'Message.CreateMessage(MessageVersion.<FromBinding>, 
            '      action, 
            '      new CustomBodyWriter(op, varnames, varvals, 
            '"<ns passed in from Proxy>")); 
            Dim msg As Message = Message.CreateMessage(Me.messageVersion, action, New CustomBodyWriter(op, varnames, varvals, "<ns passed in from Proxy>"))
    
            Dim reply As Message = requestChannel.Request(msg, TimeSpan.MaxValue)
            Dim reader As XmlDictionaryReader = reply.GetReaderAtBodyContents()
            reader.ReadToFollowing(op + "Result")
            Return reader.ReadElementContentAs(returntype, Nothing)
    
        End Function 
    End Class 
    
    
    Friend Class CustomBodyWriter
        Inherits BodyWriter
        Private op As String 
        Private varnames() As String 
        Private varvals() As Object 
        Private ns As String 
    
    
        Public Sub New(ByVal op As String, ByVal varnames() As String, ByVal varvals() As Object, ByVal ns As String)
            MyBase.New(True)
            Me.op = op
            Me.varnames = varnames
            Me.varvals = varvals
            Me.ns = ns
    
        End Sub 
    
    
        Protected Overrides Sub OnWriteBodyContents(ByVal writer As XmlDictionaryWriter)
            writer.WriteStartElement(op, ns)
            Dim i As Integer 
            For i = 0 To varnames.Length
                writer.WriteElementString(varnames(i), varvals(i).ToString())
            Next i
            writer.WriteEndElement()
    
        End Sub 
    End Class
    
    public class ClientBase<TChannel>
        where TChannel : class
    {
        private IRequestChannel requestChannel;
        private MessageVersion messageVersion;
    
        public ClientBase(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress)
        {
            //this.remoteAddress = remoteAddress; 
            this.messageVersion = binding.MessageVersion;
    
            BindingParameterCollection parameters = new System.ServiceModel.Channels.BindingParameterCollection();
    
            // Specifies the X.509 certificate used by the client.
            ClientCredentials cc = new ClientCredentials();
            cc.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "testuser");
            parameters.Add(cc);
    
            IChannelFactory<IRequestChannel> channelFactory = binding.BuildChannelFactory<IRequestChannel>(
                parameters);
            channelFactory.Open();
            this.requestChannel = channelFactory.CreateChannel(remoteAddress);
        }
    
        public object Call(string op, string action, string[] varnames, object[] varvals, Type returntype)
        {
            requestChannel.Open(TimeSpan.MaxValue);
    
            //Message msg = 
            //Message.CreateMessage(MessageVersion.<FromBinding>, 
            //      action, 
            //      new CustomBodyWriter(op, varnames, varvals, 
            //"<ns passed in from Proxy>"));
    
            Message msg =                   
            Message.CreateMessage(this.messageVersion, action,
                  new CustomBodyWriter(op, varnames, varvals,               
            "<ns passed in from Proxy>"));
    
            Message reply = requestChannel.Request(msg, TimeSpan.MaxValue);
            XmlDictionaryReader reader = reply.GetReaderAtBodyContents();
            reader.ReadToFollowing(op + "Result");
            return reader.ReadElementContentAs(returntype, null);
        }
    
    }
    
    internal class CustomBodyWriter : BodyWriter
    {
        private string op;
        private string[] varnames;
        private object[] varvals;
        private string ns;
    
        public CustomBodyWriter(string op, string[] varnames, object[] varvals, string ns)
            : base(true)
        {
            this.op = op;
            this.varnames = varnames;
            this.varvals = varvals;
            this.ns = ns;
        }
    
        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
        {
            writer.WriteStartElement(op, ns);
            for (int i = 0; i < varnames.Length; i+)
                writer.WriteElementString(varnames[i], varvals[i].ToString());
            writer.WriteEndElement();
        }
    }
    
  9. Add the following references to ClientBase.cs:

  10. Add a class to instantiate and use the client proxy.

    The following example uses the binding object to specify transport security over HTTPS and the use of a client certificate for authentication. It also shows code that invokes the client proxy.

    Class Program
    
        ''' <summary> 
        ''' The main entry point for the application. 
        ''' </summary>
        <MTAThread()> _
        Shared Sub Main()
    
            Dim serverAddress As String = CalculatorServiceClient.ServiceEndPoint.Uri.AbsoluteUri
    
            Dim binding As New BasicHttpBinding()
    
            ' Specifies transport security over HTTPS and the use of a 
            ' client certificate for authentication.
            binding.Security.Mode = BasicHttpSecurityMode.Transport
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate
    
            Dim proxy = New CalculatorServiceClient(binding, New EndpointAddress(serverAddress))
    
            MessageBox.Show("Add 3 + 6...")
            MessageBox.Show(proxy.Add(3, 6).ToString())
            MessageBox.Show("Subtract 8 - 3...")
            MessageBox.Show(proxy.Subtract(8, 3).ToString())
    
        End Sub 
    End Class
    
    static class Program
    {
        /// <summary> 
        /// The main entry point for the application. 
        /// </summary>
        [MTAThread]
    
        static void Main()
        {
            string serverAddress = CalculatorServiceClient.ServiceEndPoint.Uri.AbsoluteUri;
    
            BasicHttpBinding binding = new BasicHttpBinding();
    
            // Specifies transport security over HTTPS and the use of a 
            // client certificate for authentication.
            binding.Security.Mode = BasicHttpSecurityMode.Transport;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
    
            ICalculatorService proxy = new CalculatorServiceClient(binding, new EndpointAddress(serverAddress));
    
            MessageBox.Show("Add 3 + 6...");
            MessageBox.Show((proxy.Add(3, 6)).ToString());
            MessageBox.Show("Subtract 8 - 3...");        
            MessageBox.Show((proxy.Subtract(8, 3)).ToString());
    
        }
    }
    
  11. Make sure that the client certificate has been placed into the current user's certificate store on the device.

  12. Build the client application and deploy it to your device.

  13. When the WCF service is running and your device is connected to the network, start the client application on the device.

Compiling the Code

The source code for the WCF service requires references to the following namespaces:

The source code for the ClientBase class requires references to the following namespaces:

The source code for the class that contains the Main method in the client application requires references to the following namespaces:

Security

This example implements transport security based on mutual authentication. It does not implement message security.

See Also

Other Resources

Windows Communication Foundation (WCF) Development and the .NET Compact Framework