AS2 Certificate Exchange Messaging (CEM)

Requirements:
EDI Integrator

Introduction

The process of exchanging certificates with your trading partner can be cumbersome. You may need to email or otherwise transfer new certificates to your partner and wait for confirmation that they have updated their configuration. Certificate Exchange Messaging (CEM) provides a well defined messaging structure to allow certificates to be exchanged automatically.

Contents

  1. Send a CEM Request
  2. Process a CEM Request
  3. Send a CEM Response
  4. Process a CEM Response

Send a CEM Request

Sending a CEM request with AS2Sender will send a certificate (or certificates) to your partner. There are several required values in addition to the actual public certificate you wish to send.

For the purposes of this tutorial the "Client" is the entity sending the CEM request and processing the CEM response. The "Partner" is the entity receiving the CEM request and sending the CEM response.

Please see the code with comment below which steps through the process.

As2sender as2sender = new As2sender();
as2sender.AS2From = "client";
as2sender.AS2To = "partner";
as2sender.MDNTo = "test@nsoftware.com"; //Request a MDN
as2sender.LogDirectory = @"C:\cemtest\client\logs";

//To begin populate the CEMDetail with the new certificate and CEM data
as2sender.CEMDetails.Add(new CEMDetail());
as2sender.CEMDetails[0].CertStoreType = CertStoreTypes.cstPublicKeyFile;
as2sender.CEMDetails[0].CertStore = @"C:\cemtest\client\client.new.cer";
as2sender.CEMDetails[0].CertSubject = "*"; //Load the first (and only) certificate
as2sender.CEMDetails[0].CertId = "New Certificate";

//Specify a RespondByDate. If the partner does not respond before this time,
//the client may still decide to use the new certificate exclusively.
string respondByDate = DateTime.Now.AddDays(30).ToString("yyyy-MM-dd") + "T00:00:00-05:00";
as2sender.CEMDetails[0].RespondByDate = respondByDate;

//The ResponseURL is a publicly accessible webpage where the CEM response is sent back
as2sender.CEMDetails[0].ResponseURL = "http://www.client.com/client/cemresponse.aspx";

//The request may optionally be signed with the old certificate and encrypted. This is 
//just like a normal request when sending a file.
as2sender.SigningCert = new Certificate(CertStoreTypes.cstPFXFile,@"C:\cemtest\client\client.old.pfx","test","*");
as2sender.RecipientCerts.Add(new Certificate(CertStoreTypes.cstPublicKeyFile, @"C:\cemtest\client\partner.cer", "","*"));

//Your partner's URL where they can process CEM requests.
as2sender.URL = "http://www.partner.com/partner/cem.aspx";

string requestId = "cemrequest1"; //save this value to process the CEM response later.
as2sender.SendCEMRequest(requestId);

//At this point the CEM request has been sent and the MDN has been received and verified. 
//This only means that the CEM request was successfully sent. This does NOT mean that the 
//CEM operation is complete. The partner must still respond separately to the CEM 
//request and the CEM response must be processed on your server.

Process a CEM Request

On the Partner side once a CEM request has been received it will be processed by AS2Receiver similarly to a regular AS2 request. The CEMRequest event will fire when ParseRequest is called which will give you access to the CEM details.

protected void Page_Load(object sender, EventArgs e)
    {
      if (Request.HttpMethod.ToUpper() == "POST")
      { 
        string CEMRequestId = "";

        //The client has posted a CEM request to us. Begin by decrypting and verifying 
        //the request just like a normal file.
        As2receiver as2receiver = new As2receiver();
        as2receiver.LogDirectory = @"C:\cemtest\partner\logs";

        as2receiver.OnCEMRequest += delegate(object s, As2receiverCEMRequestEventArgs args)
        {
          CEMRequestId = args.RequestId; //Save this to use when sending the CEM response
        };

        //Call ReadRequest to get As2from and As2to values. In a real application
        //you would use these values to choose the right certificates. In this case
        //we will assume the right values.
        as2receiver.ReadRequest();

        //The certificate for decryption
        as2receiver.Certificate = new Certificate(CertStoreTypes.cstPFXFile, @"C:\cemtest\partner\partner.pfx", "test", "*");

        //The certificate for signature verification
        as2receiver.SignerCert = new Certificate(CertStoreTypes.cstPublicKeyFile, @"C:\cemtest\partner\client.old.cer", "", "*");

        //Set the location where the new certificates will be stored. This is optional.
        //The certificate will also be held in memory and can be accessed using
        //the CEMDetails collection.
        as2receiver.Config(@"CEMCertDir=C:\cemtest\partner\incomingcerts");

        //Process the request. When called the CEMRequest event will fire.
        as2receiver.ParseRequest();
        as2receiver.CreateMDNReceipt("", "", ""); //Empty values let the component handle MDN creation
        as2receiver.SendResponse();

        //At this point the CEM request was processed and a MDN was sent back. This 
        //only means there were no errors. This does NOT mean the CEM operation is complete.
        //A CEM response must still be sent to tell the client the partner has updated
        //the certificate on file for that client.

Send a CEM Response

Sending a CEM response is done from the Partner side to the Client side. The CEM response may be sent back immediately, or may be sent in another process at a later date. In this example sending the response takes place in the same page where the CEM request was received. This code is a continuation of the code from the previous section.

//The AS2Sender component is used to accept and send back the CEM response.
        //In this example this is done in the same process where the CEM request was 
        //received, but this is not required. The CEM response can be sent back at any 
        //time.
        As2sender as2sender = new As2sender();
        as2sender.AS2From = "partner";
        as2sender.AS2To = "client";
        as2sender.MDNTo = "test@nsoftware.com"; //Request a MDN
        as2sender.LogDirectory = @"C:\cemtest\partner\logs";

        as2sender.URL = as2receiver.CEMDetails[0].ResponseURL;

        //Copy CEM details from AS2Receiver. If the response is sent back in a separate 
        //process the CEM details should be stored previously so they can be used now.
        as2sender.CEMDetails.Add(new CEMDetail());
        as2sender.CEMDetails[0].CertId = as2receiver.CEMDetails[0].CertId;

        //Certificate information may be specified in one of two ways. You may 
        //specify the actual certificate:
        as2sender.CEMDetails[0].CertStoreType = as2receiver.CEMDetails[0].CertStoreType;
        as2sender.CEMDetails[0].CertStore = as2receiver.CEMDetails[0].CertStore;
        as2sender.CEMDetails[0].CertStorePassword = as2receiver.CEMDetails[0].CertStorePassword;
        as2sender.CEMDetails[0].CertSubject = as2receiver.CEMDetails[0].CertSubject;

        //Alternatively you may specify the CertIssuer and CertSerialNumber:
        //as2sender.CEMDetails[0].CertIssuer = as2receiver.CEMDetails[0].CertIssuer;
        //as2sender.CEMDetails[0].CertSerialNumber = as2receiver.CEMDetails[0].CertSerialNumber;

        //Accept the certificate
        as2sender.CEMDetails[0].Accepted = true;

        //If the certificate is not accepted it can be rejected.
        //as2sender.CEMDetails[0].Accepted = false;
        //as2sender.CEMDetails[0].RejectionReason = "A user defined error message.";

        //The CEM response may optionally be signed and encrypted just like a normal file
        as2sender.RecipientCerts.Add(new Certificate(CertStoreTypes.cstPublicKeyFile,@"C:\cemtest\partner\client.old.cer","","*"));
        as2sender.SigningCert = new Certificate(CertStoreTypes.cstPFXFile, @"C:\cemtest\partner\partner.pfx", "test", "*");

        as2sender.SendCEMResponse(CEMRequestId);
      
      }
    }

Process a CEM Response

The CEM response is posted back to the Client for processing. The Client will process the response and determine whether the request was accepted or rejected by the Partner.

protected void Page_Load(object sender, EventArgs e)
    {
      if (Request.HttpMethod.ToUpper() == "POST")
      {
        string CEMRequestId = "";

        //The client has posted a CEM request to us. Begin by decrypting and verifying 
        //the request just like a normal file.
        As2receiver as2receiver = new As2receiver();
        as2receiver.LogDirectory = @"C:\cemtest\client\logs";

        as2receiver.OnCEMResponse += new As2receiver.OnCEMResponseHandler(delegate(object s, As2receiverCEMResponseEventArgs args){
          CEMRequestId = args.RequestId; //Match this to request Id that was sent by as2sender.SendCEMRequest()
        });

        //Call ReadRequest to get As2from and As2to values. In a real application
        //you would use these values to choose the right certificates. In this case
        //we will assume the right values.
        as2receiver.ReadRequest();

        //The certificate for decryption
        as2receiver.Certificate = new Certificate(CertStoreTypes.cstPFXFile, @"C:\cemtest\client\client.old.pfx", "test", "*");

        //The certificate for signature verification
        as2receiver.SignerCert = new Certificate(CertStoreTypes.cstPublicKeyFile, @"C:\cemtest\client\partner.cer", "", "*");

        //Process the request. When called the CEMResponse event will fire.
        as2receiver.ParseRequest();
        as2receiver.CreateMDNReceipt("", "", ""); //Empty values let the component handle MDN creation
        as2receiver.SendResponse();

        //At this point the MDN is sent back. The CEM response has been received, but not yet inspected. 
        //Inspect the Accept field of the CEMDetails collection to verify the CEM request was 
        //successfully processed by the partner.

        for (int i = 0; i < as2receiver.CEMDetails.Count; i++)
        {
          if (!as2receiver.CEMDetails[i].Accepted)
          {
            //The partner rejected this certificate.
            string reason = as2receiver.CEMDetails[i].RejectionReason;

            //At this point a business decision must be made depending on the reason. 
            //For instance it may be necessary to contact the partner for further investigation.
          }
        }
      }
    }

We appreciate your feedback.  If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.