Thursday, June 12, 2014

eMedNy Web Services in .NET (Guest Post)

@YaronNaveh

This is a guest post by Isaac Kleinman about web services interoperability with eMedNy

I was recently tasked with building a SOAP Client to consume some services provided by eMedNy, New York State Dept. of Health's electronic Medicaid system. While eMedNy provides a number of web services for providers such as medical and prescription history etc., my project focuses on their subscriber (patient) eligibility service. Once you manage to successfully communicate with the service, the actual exchange consists of submitting an X12-formatted 270 file which is an eligibility request and receiving a 271 (eligibility response) in return.

Working with this arcane format is challenging in its own right and is more than worthy of its own post. However, configuring the certificates and structuring the SOAP security message headers proved to be even more challenging. I still haven't completely gotten my mind around the concept, but apparently the service was written in Java and uses WS-Security to define its security policy. It's possible that building a client in Java is a smooth process, but doing so in .NET proved to be a quite complicated ordeal. While I can't fully explain all the details of the issues at hand, I can, at least, describe the steps I took to get my project up and running. Yaron Naveh was extremely helpful at each step of the way; hat-tip to him.

Officially, Microsofts's `svcutil` utility was supposed to do all the magic for me: just provide the WSDL URL and the appropriate proxy and config files get generated and you're good to go. This was far from the case. Here's what happened when I ran `svcutil`:


The generated `output.config` file contains a similar error:


Now, I haven't even gotten clarity yet as to what exactly this error message means or what is causing it, but as you can see, due to the error, not much configuration is happening here at all. Thus, I don't include this file in my project.

As an aside, the proxy file, `MHService.cs`, contains a whopping 55K loc. Most of this is not relevant to the services I'm using. (I suspect I only need about 30 of those lines, but I haven't gotten to sifting it yet.)

Let's begin with the steps toward putting together a functional outbound SOAP request message.

Here's a sample request message provided by eMedNy:


While it's true that a message doesn't have to conform entirely to this sample, it gives us something to work towards.Here is an outline of the architecture we can derive from this sample:


  • A random temporary session key is generated.
  • Message body is encrypted (Triple-Des CBC) with this key.
  • The temporary key gets encrypted using eMedNy's server certificate.
  • The encrypted key is included in the request header.
  • (Encrypted) Message body gets canonicalized (C14N).
  • Digest (SHA1) is produced from canonicalized message.
  • A digest (SHA1) of all digests (only message body here) is produced.
  • This final digest is encrypted with client's private key.
  • The hash and signature are included in the request header.
  • Username token: includes `username`, `password`, `nonce` and `timestamp` (I've found that the request is accpeted just fine even without the `nonce` and `timestamp`)

  • So here's a first draft of my code which generates a working request (based on Yaron's gist):


    And here what the generated message looks like:


    As you can see, the message I generate differs slightly from the sample they provide.
    In particular, my version does not have:

    1. the `nonce` and `timestamp`
    2. the server certificate ( it only gets referenced by `Subject Key Identifier`

    but.. it works.

    Now, let's proceed to dealing with the response message.

    If you try running your program using the above code, you'll get the following error:


    The issue here is that eMedNy's server is presenting a certificate which is not valid according to the client's trust chain. Yaron addresses this issue here. You incorporate his code as follows:

    1. add the following callback to the program:
    static bool OnValidationCallback( object sender, X509Certificate cert,
    X509Chain chain, SslPolicyErrors errors)
    {
    return true;
    }


    2. and, in your configuration code, perform the following assignment:
    ServicePointManager.ServerCertificateValidationCallback = new
    RemoteCertificateValidationCallback(OnValidationCallback);
    After making that change, running the program should give you this error:

    Unhandled Exception: System.ServiceModel.Security.MessageSecurityException: The incoming message was signed with a token which was different from what used to encrypt the body. This was not expected.

    For some reason, WCF is not properly identifying the server certificate token. Hard as I tried, I have not (yet) been able to figure out how to tweak the configuration to overcome this issue. As a last resort, I had no choice but to roll my own custom encoder. The code is based on the examples provided on MSDN, but I've tried to remove a lot of the parts that are not needed for our case, so that it's somewhat more obvious what the code does.

    So here is the CustomTextMessageBindingElement class:


    here is the CustomTextMessageEncoderFactory class:


    and, finally, here is the actual CustomTextMessageEncoder class. The `ReadMessage` method is where the decryption takes place. I intend to make the method a bit neater using the `EncryptedXml` class, but here it is for now:


    At this point, we can go back to the configuration code and replace the use of the default encoder:

    binding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));

    with our custom encoder as follows:

    binding.Elements.Add(new CustomTextMessageBindingElement());


    As a relative n00b, getting this to work was quite challenging, so I hope this can help some others in the same situation.

    @YaronNaveh

    What's next? get this blog rss updates or register for mail updates!

    10 comments:

    Jean said...

    Very good your post! Helped me a lot.

    Unknown said...

    Hello -
    tried your client code however got an aditional tag in the header:

    Any idea why?
    Thank you, Yvona

    Yaron Naveh (MVP) said...

    Yvona - what tag did you got? XML is not clear in the comments, maybe use stars instead of angle brackets.

    Jyotindra said...

    Yaron thanks for your wonderful post, I implemented the same code as you suggested but it give error Private Key Not found
    Can you please help me out what could be the potential reason behind it.

    Yaron Naveh (MVP) said...

    this means your client certificate is the wrong one - it does not have a private key. maybe by mistake you replaces client and server certificates.

    Jyotindra said...

    Thanks Yaron!!!

    Kiquenet said...

    Great simple about WCF.

    Only better with full source code sample and the configuration files (*.config) about service.model.

    Unknown said...

    Thanks for a very nice post.. This helped me along my way a lot! I am trying to make a client for a service for energy, meter readings, in NL. They also have a response that is not encrypted with my signing certificate.

    There are, of course, some differences... The response looks different and a general tip for people who want to use this is to capture a response (using the WCF tools or by just putting a break point in the custom MessageEncoders ReadMessage method and examine the content of the "doc" variable.

    In my case there were namespace differences and my service also returned a KeyIdentifier, along with the CipherValue, in the EncryptedKey tag. So I had to use "CipherValue[0]" = Key and "CipherValue[1]" = Message.

    Once again, thanks for a great post !!

    Hi said...

    Hi thanlks a lot for the post.
    from server I get this response
    env:ClientHash values do not match. (from client)

    Alex said...

    You can send the complete code please, thank you