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;