【C#, Azure Function】SMIME / PKCS#7を使用してメッセージに電子署名をつける(RosettaNet)

Sponsored Links

RosettaNetでは、ヘッダ情報(Preamble、Service Header、Delivery Header)と指示データなどが入っているService Contentsから成る。
電子署名を付ける際は、ヘッダ情報、Service Contentsに対し、電子署名データを作成する。電子署名の目的としては、メッセージボディー(この場合、ヘッダ情報とSertvice Contents)が改竄された形跡がないか、信頼できるパートナーから送信されたメッセージかを証明するために必要とされる。
電子署名が追加メッセージのボディー情報を変更する必要がある場合は、ボディー情報を変更後、電子署名データも変更しないと、受信側で証明書エラーが起きる。
RosettaNetの電子署名はSMIME / PKCS#7により、電子署名データが作成される。
C#で電子署名データを作成する際には、MimeKitが使用できる。
下記のコードは、Azure Function Appを使用したときの抜粋で、Windows環境のFunction Appで使用できる。(LinuxベースのFunction Appの場合、何かのライブラリが使用できない模様)

    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());
            }
        }
    }

string preamble、delivery、service、contents部分は、EDIツールなどで作成されるデータを使用し、これらの変数に入れる。
“GetSignature”ファンクションの中で、電子署名データを作成する。
Function App配下での実行でなかったり、Pfx証明書をプロジェクトファイルから取得しない場合は”ExecutionContext”のArgumentは不要と成る。
まず、”GetSignature”ファンクションでは、ヘッダ情報や、Service ContentsをAttachmentとして付ける。
このAttachmentをmessage.bodyとして、その後、電子署名データが作成される。
必要な箇所のみ切り出したため、”public async Task Run”は動かないかもしれないが、要のファンクション”GetSignature”は動作するはず。

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;

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