【C#, Azure Function】How to Sign RosettaNet Message (using SMIME / PKCS#7)

Sponsored Links

RossetaNet message consists of 1. Preamble 2. Service Header 3. Delivery Header and 4. Service Contents.
When signature is required for EDI, signature data is included for preamble, service header, delivery header and service contents.
Signature is to make sure that these headers and contents are not modified after the signature is created and the message is sent by trusted partner. ( For B2B EDI, public certificate should be exchanged ahead before the actual message is transmitted.)
If any of headers or contents need to be modified, the signature is also recomputed accordingly.

RosettaNet signature is using SMIME and PKCS#7. (SMIME and MIME are different!)
In order to make SMIME and PKCS#7, MimeKit can be used to recompute signature data.

For RosettaNet, preamble, service header, delivery header and service contents should be included as an attachment and for those attachment, the digital signature can be recomputed.

Below code is sample code for Azure Function App (for Windows.) (I’m not certain where it is, but the code cannot run under Linux environment on Azure Function)

    public class FunctionName
    {
        private readonly ILogger<FunctionName> _logger;
        
        public FunctionName(ILogger<FunctionName> log)
        {
            _logger = log;
        }

        [FunctionName("FunctionName")]
        [OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
        [OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
        [OpenApiParameter(name: "rosettaBody", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
        [OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ExecutionContext context)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            string preamble = "preamble xml string";
            string delivery = "delivery header xml string";
            string service = "service header xml string";
            string contents = "service contents xml string";

            var signedData = GetSignature(context, preamble, delivery, service, contents).Result;
            return new OkObjectResult(Convert.ToBase64String(Encoding.UTF8.GetBytes(signedData)));

        }


        private async Task<string> GetSignature(ExecutionContext context, string preamble, string delivery, string service, string contents)
        {
            try
            {
                string filename = Path.Combine(context.FunctionAppDirectory, "cer (forlder name in project. Blank if pfx exists directly under project folder)", @"PFX file name");

                var attachmentPreamble = new MimePart("application", "xml")
                {
                    Content = new MimeContent(new MemoryStream(new System.Text.UTF8Encoding().GetBytes(preamble)), ContentEncoding.Default),
                    ContentTransferEncoding = ContentEncoding.Base64,
                    ContentDescription = "Preamble_MP",
                    ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                    FileName = Path.GetFileName(@"Preamble"),
                    ContentLocation = new Uri("RN-Preamble", UriKind.Relative)
                };
                var attachmentDelivery = new MimePart("application", "xml")
                {
                    Content = new MimeContent(new MemoryStream(new System.Text.UTF8Encoding().GetBytes(delivery)), ContentEncoding.Default),
                    ContentTransferEncoding = ContentEncoding.Base64,
                    ContentDescription = "DeliveryHeader_MP",
                    ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                    FileName = Path.GetFileName(@"Delivery"),
                    ContentLocation = new Uri("RN-Delivery-Header", UriKind.Relative)
                };
                var attachmentService = new MimePart("application", "xml")
                {
                    Content = new MimeContent(new MemoryStream(new System.Text.UTF8Encoding().GetBytes(service)), ContentEncoding.Default),
                    ContentTransferEncoding = ContentEncoding.Base64,
                    ContentDescription = "RN-Service-Header",
                    ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                    FileName = Path.GetFileName(@"Service"),
                    ContentLocation = new Uri("RN-Service-Header", UriKind.Relative)
                };
                var attachmentContents = new MimePart("application", "xml")
                {
                    Content = new MimeContent(new MemoryStream(new System.Text.UTF8Encoding().GetBytes(contents)), ContentEncoding.Default),
                    ContentTransferEncoding = ContentEncoding.Base64,
                    ContentDescription = "ServiceContent_MP",
                    ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                    FileName = Path.GetFileName(@"ServiceContents"),
                    ContentLocation = new Uri("RN-Service-Content", UriKind.Relative)
                };

                var multipart = new Multipart("related")
                {
                };
                multipart.Add(attachmentPreamble);
                multipart.Add(attachmentDelivery);
                multipart.Add(attachmentService);
                multipart.Add(attachmentContents);

                MimeMessage message = new MimeMessage
                {
                };
                message.Body = multipart;

                MimeKit.Cryptography.CmsSigner signer = new MimeKit.Cryptography.CmsSigner(filename, "PFX Password")
                {
                    DigestAlgorithm = DigestAlgorithm.Sha256
                };
                BouncyCastleSecureMimeContext ctx = new TemporarySecureMimeContext();
                message.Body = MultipartSigned.Create(ctx, signer, message.Body);

                byte[] singedData;
                using (var memory = new MemoryStream())
                {
                    message.WriteTo(memory);
                    singedData = memory.ToArray();
                }
                var signedData = Convert.ToBase64String(singedData);
                byte[] decodedBytes = Convert.FromBase64String(signedData);
                return Encoding.UTF8.GetString(decodedBytes);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.ToString());
            }
        }
    }

Headers (preamble, service and delivery) and service contents data should be created by EDI system (such as Azure Logic App) and “string preamble, delivery, service and contents” should be from RosettaNet message.
If the application is not run in Azure Function app, there is no need to use “ExecutionContext’.
“GetSignature” function recomputes the signature data based on headers and service contents. First headers and service contents are assigned as attachment.
*I suppose PFX (certificate file with private key) is included in C# project. This can be retrieved from Azure Key Vault as well.
*Code above is snapshot from my project, “public async Task Run” may not work well, but recomputing signature data “private async Task GetSignature” function should work.

using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Pkcs;
using MimeKit.Cryptography;
using MimeKit;
using System.Linq;
using System.Text.RegularExpressions;

Hope this article helps someone!

IT
Sponsored Links
Sponsored Links
Sponsored Links
ようさんチョットでぶ
Copied title and URL
Bitnami