saloni
saloni

Reputation: 23

How to use AddDigitalSignatureOriginPart (DocumentFormat.OpenXml library) to secure an Excel file?

Need to create a digital signed excel file and then validate the signature when uploaded in C#.

Upvotes: 2

Views: 1302

Answers (1)

Thomas Barnekow
Thomas Barnekow

Reputation: 2259

On its own, the SpreadsheetDocument.AddDigitalSignatureOriginPart() method does not secure an Excel file. The same is true for the corresponding methods of the WordprocessingDocument and PresentationDocument classes. Those methods only add an empty DigitalSignatureOriginPart that serves as the origin of one or more XmlSignaturePart instances, each of which contains a ds:Signature element based on the W3C Recommendation XML Signature Syntax and Processing Version 1.1 (XMLDSIG).

To secure an Excel file, or any file based on the Open Packaging Conventions (OPC), the most straightforward approach is to use the PackageDigitalSignatureManager class, which is contained in the System.IO.Packaging namespace as provided by the WindowsBase.dll assembly. Thus, if you are targeting the full .NET Framework (e.g., net471), you can use it. However, if you are targeting .Net Core, you need to implement that functionality yourself.

The following code example shows how you can use the PackageDigitalSignatureManager class:

using System;
using System.Collections.Generic;
using System.IO.Packaging;
using System.Linq;

namespace CodeSnippets.Windows.IO.Packaging
{
    public static class DigitalSignatureManager
    {
        public static void Sign(Package package)
        {
            var dsm = new PackageDigitalSignatureManager(package)
            {
                CertificateOption = CertificateEmbeddingOption.InSignaturePart
            };

            List<Uri> parts = package.GetParts()
                .Select(part => part.Uri)
                .Concat(new[]
                {
                    // Include the DigitalSignatureOriginPart and corresponding
                    // relationship part, since those will only be added when
                    // signing.
                    dsm.SignatureOrigin,
                    PackUriHelper.GetRelationshipPartUri(dsm.SignatureOrigin)
                })
                .ToList();

            dsm.Sign(parts);
        }

        public static VerifyResult VerifySignature(Package package)
        {
            var dsm = new PackageDigitalSignatureManager(package);
            return dsm.VerifySignatures(true);
        }
    }
}

In case you need to implement that functionality yourself, it helps to make yourself familiar with a number of sources:

Based on those sources, I created a partial sample implementation that works with .Net Core. The following snippet shows the void Sign(OpenXmlPackage, X509Certificate2) method that takes an OpenXmlPackage and an X509Certificate2 and creates a valid signature:

        public static void Sign(OpenXmlPackage openXmlPackage, X509Certificate2 certificate)
        {
            if (openXmlPackage == null) throw new ArgumentNullException(nameof(openXmlPackage));
            if (certificate == null) throw new ArgumentNullException(nameof(certificate));

            RSA privateKey = certificate.GetRSAPrivateKey();
            using SHA256 hashAlgorithm = SHA256.Create();

            // Create KeyInfo.
            var keyInfo = new KeyInfo();
            keyInfo.AddClause(new KeyInfoX509Data(certificate));

            // Create a Signature XmlElement.
            var signedXml = new SignedXml { SigningKey = privateKey, KeyInfo = keyInfo };
            signedXml.Signature.Id = Constants.PackageSignatureId;
            signedXml.SignedInfo.SignatureMethod = Constants.SignatureMethod;
            signedXml.AddReference(CreatePackageObjectReference());
            signedXml.AddObject(CreatePackageObject(openXmlPackage.Package, hashAlgorithm));
            signedXml.ComputeSignature();
            XmlElement signature = signedXml.GetXml();

            // Get or create the DigitalSignatureOriginPart.
            DigitalSignatureOriginPart dsOriginPart =
                openXmlPackage.GetPartsOfType<DigitalSignatureOriginPart>().FirstOrDefault() ??
                openXmlPackage.AddNewPart<DigitalSignatureOriginPart>();

            var xmlSignaturePart = dsOriginPart.AddNewPart<XmlSignaturePart>();

            // Write the Signature XmlElement to the XmlSignaturePart.
            using Stream stream = xmlSignaturePart.GetStream(FileMode.Create, FileAccess.Write);
            using XmlWriter writer = XmlWriter.Create(stream);
            signature.WriteTo(writer);
        }

The full source code of the above void Sign(OpenXmlPackage, X509Certificate2) method can be found in my CodeSnippets GitHub repository. Look for the DigitalSignatureManager class in the CodeSnippets project.

Upvotes: 6

Related Questions