Source code for the AS2 Client C# example program posted at EDIINT AS2. A detailed explanation of the algorithm can be read at "Creating an AS2 Client in Stages".
private void cmdSend_Click(object sender, EventArgs e) { Cursor = Cursors.WaitCursor; int nMdnResult = -1; string sErrorMsg = ""; txtNotification.Text = ""; txtNotification.Refresh(); try { // Get internet mail document object mailDocument oMailDocument = oEdiDoc.GetMailDocument(); //Setting so that syntax for the field "Disposition-Notification-To" is not validated, but treated as a text field oMailDocument.AddDefinedHeader("Disposition-Notification-To", MailMessageHeaderTypeConstants.HeaderType_FieldText, RequirementTypeConstants.Requirement_Optional); mailMessage oSubjMsg = oMailDocument.GetMessageContent(); Label1.Text = "importing EDI..."; Label1.Refresh(); // For speed, import the EDI file instead of processing. if (oSubjMsg.Import(sPath + cmbFile.Text) != 1) { MessageBox.Show("Failed to load document"); } else { //specify content type of mesage oSubjMsg.set_HeaderFieldValue("Content-Type", txtContentType.Text); // Configure the security of this message. ediSecurity oSecurity = oSubjMsg.GetSecurity(); // Specify the type of encryption and signing algorithm to use oSecurity.EncryptionAlgorithm = sEncryptAlgorithm; oSecurity.AssuranceAlgorithm = sSigningHash; // Recipient's certificate to encrypt message. oSecurity.SetCertSubjectNameByLocation(sRecCertSubjName, sRecCertStoreLocation, sRecCertStoreName, sRecServiceProvider); // Sender's certificate to sign message. oSecurity.SetCertSignerNameByLocation(sSignCertSubjName, sSignCertStoreLocation, sSignCertStoreName, sSignServiceProvider); if (ckSendEncrypted.Checked == true) { oSecurity.EnableEncryption = true; } else { oSecurity.EnableEncryption = false; } if (ckSendSigned.Checked == true) { oSecurity.EnableAssurance = true; } else { oSecurity.EnableAssurance = false; } if (ckCompression.Checked == true) { oSecurity.EnableCompression = true; } else { oSecurity.EnableCompression = false; } //This is how the content of the file looks like before it is prepared with all //the security configuration. This is also how your trading partner would see //the content of your file after he decrypts the AS2 message you send him. //(This is the garbled section of the "Sent_SampleEdiX12_850.x12)" oMailDocument.Save(sPath + "Output\\MessageContent.txt"); // Calculate the MIC value from the original message. // string sSubjMicValue; if (oSecurity.EnableEncryption || oSecurity.EnableAssurance) { //RFC4130: For any signed messages, the MIC to be returned is calculated //on the RFC1767/RFC3023 MIME header and content. //Canonicalization on the MIME headers MUST be performed before //the MIC is calculated, since the sender requesting the signed //receipt was also REQUIRED to canonicalize. //RFC4130: For encrypted, unsigned messages, the MIC to be returned is //calculated on the decrypted RFC 1767/RFC3023 MIME header and //content. The content after decryption MUST be canonicalized //before the MIC is calculated. sSubjMicValue = oSubjMsg.GenerateDigest(MailMessagePartTypeConstants.Message_All, EncodingMechanismTypeConstants.EncodeType_Base64); } else { //'RFC4130: For unsigned, unencrypted messages, the MIC MUST be calculated //'over the message contents without the MIME or any other RFC //'2822 headers, since these are sometimes altered or reordered by //'Mail Transport Agents (MTAs). sSubjMicValue = oSubjMsg.GenerateDigest(MailMessagePartTypeConstants.Message_Body, EncodingMechanismTypeConstants.EncodeType_Base64); } txtMIC.Text = sSubjMicValue; txtMIC.Refresh(); // Prepare the message with the imported EDI file so that the security configurations // can be applied. if (oSubjMsg.Prepare() != 1) { MessageBox.Show("Failed to prepare Message"); } else { if (ckBase64.Checked == true) { oMailDocument.Save(sPath + "Output\\After_PrepBeforeBase64.txt"); oSubjMsg.set_HeaderFieldValue("Content-Transfer-Encoding", "base64"); oSubjMsg.Prepare(); } // Put globally unique ID as Message-ID. oSubjMsg.set_HeaderFieldValue("Message-ID", "1234567890@evalusercompany.com"); //txtMessageID.Text // Put AS2 version. oSubjMsg.set_HeaderFieldValue("AS2-Version", "1.0"); // Put AS2-To header value. oSubjMsg.set_HeaderFieldValue("AS2-To", txtAs2To.Text); // Put AS2-From header value. oSubjMsg.set_HeaderFieldValue("AS2-From", txtAS2From.Text); if (ckAsynchronous.Checked == true) { // For asynchronous, must set a syntactically correct URI at 'Receipt-delivery-option'. oSubjMsg.set_HeaderFieldValue("Receipt-delivery-option", txtReceiptDeliveryOption.Text); } if (ckReceiveMdn.Checked == true) { // Request Acknowledgment by adding "Disposition-Notification-To" header. // Value must be present but holds no meaning in an AS2 environment. oSubjMsg.set_HeaderFieldValue("Disposition-Notification-To", txtDispositionNotificationTo.Text); if (ckReceiveSigned.Checked == true) { // Request the MDN to be signed. string sDnOpts; sDnOpts = "signed-receipt-protocol=optional,pkcs7-signature;"; sDnOpts = sDnOpts + "signed-receipt-micalg=optional,sha1"; oSubjMsg.set_HeaderFieldValue("Disposition-Notification-Options", sDnOpts); } } //ckReceiveMdn.Value = 1 //' Save the document before sending. if (oMailDocument.Save(sPath + "Output\\Sent_File.txt") != 1) { MessageBox.Show("Failed to save message"); } else { // Get the transports object for internet mail documents. ediTransports oTransports = oMailDocument.GetTransports(); // Create a transport token, and set the information of the // HTTP server where to send the file. ediTransport oTransport = oTransports.CreateTransport(); oTransport.SetHTTP(); // *** Note that FREDI requires the forward-slash (/) at the end of the HTTP URI for posting oTransport.InternetUrl = cmbUrl.Text; oTransport.TimeOut = 7200; // Configure the ISAPI information in the HTTP config object. These settings are specific // only to the FREDI ISAPI extension and the ActiveX components that it drives. ediHttpCfg oHttpCfg = oTransport.GetHttpCfg(); oHttpCfg.SendVerb = "POST"; // If there are no errors received from the HTTP server and data is // received, we want that data to be processed. oHttpCfg.EnableProcessResponse = true; // Specify the file name to save the received raw data from the server. // The raw data is saved only if the transfer is successful. oHttpCfg.ResponseOutput = sPath + "output\\Receive.Mime.Txt"; // Send the file to the server. If it's a synchronous connection, then // wait for incoming data from the server. // Execution on the server side may take several minutes // which means that the Send() statement will be blocked for the // same period until processing the file has been completed and // then received, or if an error has occurred. Label1.Text = "sending..."; Label1.Refresh(); oTransport.Send(cmbFile.Text); if (ckReceiveMdn.Checked == true && ckAsynchronous.Checked == false ) //synchronous { // If the send is successful, we expect an MDN to be received from the server // during the Send call. This is because this session is a synchronous session // and the MDN must be received in the same HTTP connection that the message // itself was sent. Since the MDN is already received, get the MDN message and save // the MDN to an external file. mailMessage oMDN = oMailDocument.GetMDN(); txtNotification.Text = oMDN.GetMsgString(); txtNotification.Refresh(); mailMessages oMdnMsgs = oMDN.GetMessages(); //Reading the MDN for (int ii=1; ii <= oMdnMsgs.Count; ii++) { mailMessage oMdnMsg = oMdnMsgs.GetMessageContent(ii); mailContentType oContentType = oMdnMsg.GetContentType(); if (oContentType.MediaType == "message" && oContentType.SubType == "disposition-notification") { mailMessages oSubMessages = oMdnMsg.GetMessages(); mailMessage oDisposition = oSubMessages.GetMessageContent(1); if (oDisposition != null) { mailHeaders oHeaders = oDisposition.GetHeaders(); string sHeaders = ""; sErrorMsg = ""; mailHeader oHeader = null; for (int i = 1; i <= oHeaders.Count; i++) { mailHeader.Set(ref oHeader, oHeaders.GetHeaderByIndex(i)); //oHeader = oHeaders.GetHeaderByIndex(i) sHeaders = sHeaders + oHeader.Name + " : " + oHeader.Value + "\r\n"; if (oHeader.Name.ToLower() == "disposition" ) { nMdnResult = 0; } else if ( oHeader.Name.ToLower() == "warning") { nMdnResult = 1; sErrorMsg = sErrorMsg + oHeader.Value + "\r\n"; } else if (oHeader.Name.ToLower() == "error") { nMdnResult = 2; sErrorMsg = sErrorMsg + oHeader.Value + "\r\n"; } } //Each oHeader In oHeaders // Verify MIC from the receiver. mailHeader.Set(ref oHeader, oHeaders.GetHeaderByName("Received-Content-MIC")); if (oHeader != null) { string sRecvMicFldValue; sRecvMicFldValue = oHeader.Value; int lPos; lPos = sRecvMicFldValue.IndexOf(","); if (lPos == 0 && sRecvMicFldValue.Length > 0) { MessageBox.Show("Invalid Received-Content-MIC field value"); } else { string sMicValue; string sMicAlg; sMicValue = sRecvMicFldValue.Substring(0, lPos); sMicAlg = sRecvMicFldValue.Substring(lPos); // Compare the MIC of the subject document to MIC received by received. if (sMicValue != sSubjMicValue ) { MessageBox.Show("Recipient received invalid document. Subject MIC=" + sSubjMicValue + ", Recv MIC=" + sMicValue); } } //lPos == 0 && Len(sRecvMicFldValue) > 0 oHeader.Dispose(); } //oHeader != null oHeaders.Dispose(); } //oDisposition != null oDisposition.Dispose(); oSubMessages.Dispose(); } //oContentType.MediaType oContentType.Dispose(); oMdnMsg.Dispose(); } // for ii oMDN.Save(sPath + "Output\\Received_MDN.Txt"); oMdnMsgs.Dispose(); oMDN.Dispose(); } //ckReceiveMdn.Value = 1 And ckAsynchronous.Value = 0 Label1.Text = ""; Label1.Refresh(); if (nMdnResult == 0 ) { MessageBox.Show("Successfully sent."); } else if ( nMdnResult == 1 ) { MessageBox.Show("The message was successfully sent but there were warnings."); MessageBox.Show(sErrorMsg, "MDN Warning"); } else if (nMdnResult == 2 ) { MessageBox.Show("An error occurred during transmission"); MessageBox.Show(sErrorMsg, "MDN Error"); } oHttpCfg.Dispose(); oTransport.Dispose(); oTransports.Clear(); oTransports.Dispose(); } //oMailDocument.Save(sPath + "Output\Sent_File.txt") != 1 } //oSubjMsg.Prepare != 1 } //oSubjMsg.Import(sPath + cmbFile.Text) != 1 oSubjMsg.Dispose(); oMailDocument.Dispose(); oEdiDoc.Close(); Cursor = Cursors.Default; } catch( System.Exception ex) { txtNotification.Text = txtNotification.Text + "\r\n" + "Severe error. " + ex.Message; txtNotification.Refresh(); } Cursor = Cursors.Default; }