Implementation of C# XMLRPC.NET client and server over HTTPS
Asked Answered
C

3

7

It's pretty hard to find information about XMLRPC.net library used with https.

The only documentation where an "https" URL can be set is here : http://xml-rpc.net/faq/xmlrpcnetfaq-2-5-0.html#2.3 but yet it does not explain exactly how can one setup correctly.

Experimenting on the base of samples provided in the downloads http://xmlrpcnet.googlecode.com/files/xml-rpc.net.2.5.0.zip I tried this :

Changes in the client.cs file of StateNameServer solution :

IStateName svr = (IStateName)Activator.GetObject(
typeof(IStateName), "https://localhost:5678/statename.rem");

What the server code looks like

    IDictionary props = new Hashtable();
    props["name"] = "MyHttpChannel";
    props["port"] = 5678;
    HttpChannel channel = new HttpChannel(
    props,
    null,
    new XmlRpcServerFormatterSinkProvider()
    );

    ChannelServices.RegisterChannel(channel, false);

    RemotingConfiguration.RegisterWellKnownServiceType(
    typeof(StateNameServer),
    "statename.rem",
    WellKnownObjectMode.Singleton);

The client obviously drops an exception when trying to contact the server using HTTPS because I don't know how to configure it. Could someone help in anyway please ? What kind of stuff should I look for ?

Thanks a lot !

Curiosity answered 26/10, 2011 at 15:0 Comment(0)
C
4

First, I would like to thank warmly Charles Cook for his help on this problem and for developing XMLRPC.NET.

Second, this sample is based on the XMLRPC.NET StateNameServer sample available for download here : http://xml-rpc.net/download.html

So here is the solution :

1. Generate or get a [self-signed] certificate (using makecert.exe for example)

2. Add this certificate to your server configuration and specify the port you want to use with your XMLRPC.NET server (in this case 5678) using httpcfg.exe or another tool like HttpSysConfig (Open Source)

3. Implement your XMLRPC.NET server using the following code :

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

using CookComputing.XmlRpc;

using System.Net;
using System.IO;

public class _
{
    static void Main(string[] args)
    {
        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("https://127.0.0.1:5678/");
        listener.Start();
        while (true)
        {
            HttpListenerContext context = listener.GetContext();
            ListenerService svc = new StateNameService();
            svc.ProcessRequest(context);
        }

        Console.WriteLine("Press <ENTER> to shutdown");
        Console.ReadLine();
    }
}

public class StateNameService : ListenerService
{
    [XmlRpcMethod("examples.getStateName")]
    public string GetStateName(int stateNumber)
    {
        if (stateNumber < 1 || stateNumber > m_stateNames.Length)
            throw new XmlRpcFaultException(1, "Invalid state number");
        return m_stateNames[stateNumber - 1];
    }

    string[] m_stateNames
      = { "Alabama", "Alaska", "Arizona", "Arkansas",
        "California", "Colorado", "Connecticut", "Delaware", "Florida",
        "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", 
        "Kansas", "Kentucky", "Lousiana", "Maine", "Maryland", "Massachusetts",
        "Michigan", "Minnesota", "Mississipi", "Missouri", "Montana",
        "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", 
        "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma",
        "Oregon", "Pennsylviania", "Rhose Island", "South Carolina", 
        "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 
        "Washington", "West Virginia", "Wisconsin", "Wyoming" };
}

public abstract class ListenerService : XmlRpcHttpServerProtocol
{
    public virtual void ProcessRequest(HttpListenerContext RequestContext)
    {
        try
        {
            IHttpRequest req = new ListenerRequest(RequestContext.Request);
            IHttpResponse resp = new ListenerResponse(RequestContext.Response);
            HandleHttpRequest(req, resp);
            RequestContext.Response.OutputStream.Close();
        }
        catch (Exception ex)
        {
            // "Internal server error"
            RequestContext.Response.StatusCode = 500;
            RequestContext.Response.StatusDescription = ex.Message;
        }
    }
}

public class ListenerRequest : CookComputing.XmlRpc.IHttpRequest
{
    public ListenerRequest(HttpListenerRequest request)
    {
        this.request = request;
    }

    public Stream InputStream
    {
        get { return request.InputStream; }
    }

    public string HttpMethod
    {
        get { return request.HttpMethod; }
    }

    private HttpListenerRequest request;
}

public class ListenerResponse : CookComputing.XmlRpc.IHttpResponse
{
    public ListenerResponse(HttpListenerResponse response)
    {
        this.response = response;
    }

    string IHttpResponse.ContentType
    {
        get { return response.ContentType; }
        set { response.ContentType = value; }
    }

    TextWriter IHttpResponse.Output
    {
        get { return new StreamWriter(response.OutputStream); }
    }

    Stream IHttpResponse.OutputStream
    {
        get { return response.OutputStream; }
    }

    int IHttpResponse.StatusCode
    {
        get { return response.StatusCode; }
        set { response.StatusCode = value; }
    }

    string IHttpResponse.StatusDescription
    {
        get { return response.StatusDescription; }
        set { response.StatusDescription = value; }
    }

    private HttpListenerResponse response;
}

public class StateNameServer : MarshalByRefObject, IStateName
{
  public string GetStateName(int stateNumber)
  {
    if (stateNumber < 1 || stateNumber > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "Invalid state number");
    return m_stateNames[stateNumber-1]; 
  }

  public string GetStateNames(StateStructRequest request)
  {
    if (request.state1 < 1 || request.state1 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    if (request.state2 < 1 || request.state2 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    if (request.state3 < 1 || request.state3 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    string ret = m_stateNames[request.state1-1] + " "
      + m_stateNames[request.state2-1] + " " 
      + m_stateNames[request.state3-1];
    return ret;
  }

  string[] m_stateNames 
    = { "Alabama", "Alaska", "Arizona", "Arkansas",
        "California", "Colorado", "Connecticut", "Delaware", "Florida",
        "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", 
        "Kansas", "Kentucky", "Lousiana", "Maine", "Maryland", "Massachusetts",
        "Michigan", "Minnesota", "Mississipi", "Missouri", "Montana",
        "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", 
        "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma",
        "Oregon", "Pennsylviania", "Rhose Island", "South Carolina", 
        "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 
        "Washington", "West Virginia", "Wisconsin", "Wyoming" };
}

4. Implement your XMLRPC.NET client using the following code (the code also creates a new X509 client certificate)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

using CookComputing.XmlRpc;
using System.Net;
using System.Security.Cryptography.X509Certificates;

class _
{
    public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
    {
        public TrustAllCertificatePolicy() { }
        public bool CheckValidationResult(ServicePoint sp,
           X509Certificate cert,
           WebRequest req,
           int problem)
        {
            return true;
        }
    }
    static void Main(string[] args)
    {
        System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
        IStateName proxy = XmlRpcProxyGen.Create<IStateName>();
        XmlRpcClientProtocol cp = (XmlRpcClientProtocol)proxy;
        cp.Url = "https://127.0.0.1:5678/";
        cp.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(@"C:\path\to\your\certificate\file\my.cer"));
        cp.KeepAlive = false;
        //cp.Expect100Continue = false;
        //cp.NonStandard = XmlRpcNonStandard.All;

        string stateName = ((IStateName)cp).GetStateName(13);
    }
}

Of course, I don't give here the interface implementation for the ServerStateName but you'll find it in the sample files using the download link at the top.

Remark :

System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy(); will allow the server implementation to accept the self-signed certificate you generated by yourself. I think this is not necessary with certificates issued by certification authorities.

If you find anything that could be improved and is incorrect it will be much appreciated.

Curiosity answered 26/10, 2011 at 23:1 Comment(1)
After testing, I can confirm that the implementation works on two separate Windows machines.Curiosity
H
2

Create a client proxy using XmlRpcProxyGen.Create, specifying the https url (your interface should derive from IXmlRpcProxy). If you need to supply client certificate(s) the proxy has a ClientCertificates property which can be used in the same way as the corresponding property on the System.Net.HttpWebRequest class.

I don't think Remoting can support HTTPS. You can use HttpListener as described in the XML-RPC.NET FAQ but you will need to configure a certificate, for example as described in this blog

Herold answered 26/10, 2011 at 16:38 Comment(3)
Thank you Charles. I tried using XmlRpcProxyGen but the client continues on dropping exception when trying to send data. Don't the server need some kind of certificate too ?Curiosity
Added paragraph on implementing a server.Herold
Thanks for your help Charles, your link helped me to find out the solution. I added the certificate to the server (my local computer in fact)... the rest of the solution below in my response.Curiosity
G
1

Great article! Helps me a lot. But item 1 and 2 took me a day to figure out. So here is my experience:

  1. To generate self-signed certificate I used openssl tool. Just follow the instruction in the link. This certificate I needed for client application.
  2. For server application I needed another certificate. Server code uses HttpListener class which has no Certificates property. There is no way how to apply specific certificate to instance of HttpListener class. There is another strategy:
    • Create new certificate in local certificate storage. To do this type 'mmc' in cmd->File->Add/Remove Snap-in->Certificates->Add->Computer account->Local computer->OK. Go to Personal->Certificates->Right click->All tasks->Request new certificate. Next->Next->select Web Server->click the blue link->basically you need to feel only Common Name here (put desired certificate name). Ok->Enroll. Now you have yours certificate in the local storage.
    • Bind created certificate to specific ip/port pair. To do this run following string in cmd: netsh http add sslcert ipport=192.168.111.195:4022 certhash=c8973a86141a7a564a6f509d1ecfea326a1852a2 appid={0a582a74-fc2d-476c-9281-c73b2e4bfb26}, where 'ipport' is ip/port pair which will be used for ssl connection; 'certhash' is a certificate hash (open certificate that you created in previous step->go to Details->look for Thumbprint); 'appid' could be any.

If you you specify 'https' in your HttpListener url this class will automatically look for binded certificates.

Godrich answered 25/2, 2013 at 8:5 Comment(1)
Glat it helped ! :) I will have a try to your notes.Curiosity

© 2022 - 2024 — McMap. All rights reserved.