Add UUID and metadata support for PDF signing
Enhanced PDF signing with UUID and metadata handling: - Added `UUID` and `OriginalDocumentSha256` to `PdfSignOptionsDto` and `PdfSignatureCheckItemDto`. - Introduced `BusinessReason` to separate metadata from signature reasons. - Updated `PdfDigitalSignatureService` to embed and parse metadata in signature reasons. - Improved error handling to include metadata in validation results. - Updated `PdfSignatureController` to handle `UUID` in signing requests. Refactored PKCS#11 configuration: - Replaced `LibraryPath` with `AssetsRootPath` for asset resolution. - Added `ProxySocket` and `DefaultApplicationName` options. - Clarified runtime behavior for asset resolution in `README.md`. Updated documentation and examples to reflect these changes.
This commit is contained in:
@@ -16,6 +16,8 @@ namespace EnvelopeGenerator.Infrastructure.Crypt.Services;
|
||||
|
||||
public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options) : IPdfDigitalSignatureService
|
||||
{
|
||||
private static readonly Regex SignatureReasonMetaRegex = new(@"\|\s*SFMETA:v1;doc=(?<doc>[0-9a-fA-F-]{36});sha256=(?<sha>[A-Fa-f0-9]{64})$", RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
||||
|
||||
public Task<byte[]> SignPdfAsync(byte[] pdfBytes, PdfSignOptionsDto? optionsModel = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(pdfBytes);
|
||||
@@ -27,6 +29,9 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
|
||||
var digestCalculator = new DigestCalculator(HashAlgorithmType.SHA256);
|
||||
var signerInfo = new ExternalSignerInfo(PdfSignatureType.Pkcs7, 32768, digestCalculator);
|
||||
var sourceHashHex = Convert.ToHexString(SHA256.HashData(pdfBytes));
|
||||
var uuid = ResolveUuid(request.UUID);
|
||||
var reasonWithMeta = BuildReasonWithMeta(request.Reason ?? pkcs11Options.DefaultReason, uuid, sourceHashHex);
|
||||
var signatureFieldInfo = new PdfSignatureFieldInfo(1)
|
||||
{
|
||||
Name = $"Sig_{DateTimeOffset.UtcNow:yyyyMMddHHmmss}"
|
||||
@@ -35,7 +40,7 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
var builder = new PdfDeferredSignatureBuilder(signerInfo, signatureFieldInfo)
|
||||
{
|
||||
Name = request.Name ?? pkcs11Options.CertificateLabel,
|
||||
Reason = request.Reason ?? pkcs11Options.DefaultReason,
|
||||
Reason = reasonWithMeta,
|
||||
Location = request.Location,
|
||||
ContactInfo = request.ContactInfo,
|
||||
SigningTime = request.SigningTime ?? (pkcs11Options.UseUtcSigningTime ? DateTimeOffset.UtcNow : DateTimeOffset.Now),
|
||||
@@ -102,10 +107,12 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
var certificate = pkcs7.GetSignatureCertificate();
|
||||
var issuedTo = ParsePrincipal(certificate?.Subject);
|
||||
var issuedBy = ParsePrincipal(certificate?.Issuer);
|
||||
var reasonMeta = ParseReasonMeta(signature.Reason);
|
||||
|
||||
items.Add(new PdfSignatureCheckItemDto(
|
||||
CoversWholeDoc: coversWholeDocument,
|
||||
Reason: signature.Reason,
|
||||
BusinessReason: reasonMeta.BusinessReason,
|
||||
Location: signature.Location,
|
||||
DeclaredName: signature.SignerName,
|
||||
ContactInfo: signature.ContactInfo,
|
||||
@@ -125,14 +132,19 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
Filter: signature.Filter,
|
||||
SubFilter: signature.SubFilter,
|
||||
ApplicationName: null,
|
||||
UUID: reasonMeta.UUID,
|
||||
OriginalDocumentSha256: reasonMeta.OriginalDocumentSha256,
|
||||
MessageDigestHex: TryExtractMessageDigestHex(signature.Contents),
|
||||
Error: null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var reasonMeta = ParseReasonMeta(signature.Reason);
|
||||
|
||||
items.Add(new PdfSignatureCheckItemDto(
|
||||
CoversWholeDoc: false,
|
||||
Reason: signature.Reason,
|
||||
BusinessReason: reasonMeta.BusinessReason,
|
||||
Location: signature.Location,
|
||||
DeclaredName: signature.SignerName,
|
||||
ContactInfo: signature.ContactInfo,
|
||||
@@ -152,6 +164,8 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
Filter: signature.Filter,
|
||||
SubFilter: signature.SubFilter,
|
||||
ApplicationName: null,
|
||||
UUID: reasonMeta.UUID,
|
||||
OriginalDocumentSha256: reasonMeta.OriginalDocumentSha256,
|
||||
MessageDigestHex: null,
|
||||
Error: ex.Message));
|
||||
}
|
||||
@@ -434,4 +448,37 @@ public sealed class PdfDigitalSignatureService(IOptions<Pkcs11Options> options)
|
||||
|
||||
return start + length;
|
||||
}
|
||||
|
||||
private static string ResolveUuid(string? uuid)
|
||||
{
|
||||
return Guid.TryParse(uuid, out var parsed)
|
||||
? parsed.ToString()
|
||||
: Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private static string BuildReasonWithMeta(string? businessReason, string uuid, string sourceHashHex)
|
||||
{
|
||||
var trimmedReason = string.IsNullOrWhiteSpace(businessReason) ? null : businessReason.Trim();
|
||||
var prefix = string.IsNullOrWhiteSpace(trimmedReason) ? string.Empty : trimmedReason + " ";
|
||||
return $"{prefix}| SFMETA:v1;doc={uuid};sha256={sourceHashHex}";
|
||||
}
|
||||
|
||||
private static (string? BusinessReason, string? UUID, string? OriginalDocumentSha256) ParseReasonMeta(string? reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
return (reason, null, null);
|
||||
|
||||
var match = SignatureReasonMetaRegex.Match(reason);
|
||||
if (!match.Success)
|
||||
return (reason, null, null);
|
||||
|
||||
var businessReason = reason[..match.Index].Trim();
|
||||
if (string.IsNullOrWhiteSpace(businessReason))
|
||||
businessReason = null;
|
||||
|
||||
return (
|
||||
businessReason,
|
||||
match.Groups["doc"].Value,
|
||||
match.Groups["sha"].Value.ToUpperInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user