blog community

Welcome to blog community Sign in | Join | Help
in Search

Wouter van Vugt

This blog is no longer maintained and has moved

Signing Office Open XML documents using the Packaging API

The Open Packaging Convention allows packages to be signed with digital signatures. The OPC conforms with the W3C recommendation for digital signatures. This means that the support for signatures in the .NET framework can be used for signing documents. The OPC does add a few extra details, but these are already implemented in the Packaging API, so the .NET developers are in luck here as well. I will show you how to sign and validate documents using the Packaging API and X509 certificates.

Inside a single package all elements can be signed, including signatures themselves. Signing a document ensures that the origins and content can be verified as long as the right certificate is present on the target machine. All signatures inside a package are stored as parts inside the package. These parts are referenced from a special root part called the digital signature origin part. The origin part itself has no content and references the signatures through relationship parts. You can find the origin part using a well known relationship type. Each signature part referenced by the origin signs a specific set of parts and relationships using an X509 certificate or other type of signature. Obviously X509 is preferred for interoperability reasons. The X509 certificates can either be stored inside the signature, or stored in a separate package part referenced from the signature part.  The signature part contains standard digital signature markup defined by the W3C recommendation and provides a few extra rules you must conform to. For instance, the OPC specification only allows you to create <Reference> elements referring to resources inside the signature markup.

A signature part is allowed to sign data using application specific markup, but to ensure interoperability you will usually take the default steps and sign using well-known methods. There are two things to sign, package parts and relationship parts. Both should be transformed using the C14N canonicalization algorithm before the hash of the XML is calculated. This ensures that both the signing party and the one verifying the signature use identical XML before the XML hash value is calculated. There are only two canonicalization methods allowed, C14N and a custom transform called the relationship transform. The relationship transform is used to transform relationship parts before signing. It allows a specific subset of relationships to be signed. Adding new relationships to a package part will not break a signature if this is applied. Both C14N and the relationship transform is implemented for you, the first in the .NET Framework and the other in the Packaging API. I am not aware of other implementations.

The Packaging API provides all the means necessary to sign a package using an X509 certificate. There are a few steps to take which I’ll go through. Attached you will find a sample implementation. First of all you will need to create two lists, one with Package Parts that you want to sign, the other containing PacakgeRelationshipSelector objects which indicate relationships to sign. The sample implementation provides a helper method for this.

static void AddSignableItems(
    PackageRelationship relationship, 
    List partsToSign, 
    List relationshipsToSign)
{            
    PackageRelationshipSelector selector = 
        new PackageRelationshipSelector(
            relationship.SourceUri, 
            PackageRelationshipSelectorType.Id, 
            relationship.Id);
    relationshipsToSign.Add(selector);
    if (relationship.TargetMode == TargetMode.Internal)
    {
        PackagePart part = relationship.Package.GetPart(
            PackUriHelper.ResolvePartUri(
                relationship.SourceUri, relationship.TargetUri));
        if (partsToSign.Contains(part.Uri) == false)
        {
            partsToSign.Add(part.Uri);
            foreach (PackageRelationship childRelationship in
                part.GetRelationships())
            {
                AddSignableItems(childRelationship,
                    partsToSign, relationshipsToSign);
            }
        }
    }
}

First the code adds a relationship selector for the current relationship. Packages are allowed to have circular references in the relationship structure. so the package part that the relationship points to is only added if it has not been added before. If the part is not yet added to the list, it is added and all child relationships are handled.

The next step is to create a PackageDigitalSignatureManager. Using this class you can sign and validate a document. You provide the signature manager with a reference to the Package to sign and call ‘Sign’ to sign a package and ‘VerifySignatures’ to check the validity of the embedded signatures. When signing you provide the list of parts and relationships to sign. Besides these two elements you are allowed to store application specific objects inside the signature using another overload of the 'Sign' method.

static void SignPackage(Package package, X509Certificate2 certificate)
{
    List partsToSign = new List();
    List relationshipsToSign = 
        new List();
    List finishedItems = new List();
    foreach (PackageRelationship relationship in 
        package.GetRelationshipsByType(RT_OfficeDocument))
    {
        AddSignableItems(relationship, 
            partsToSign, relationshipsToSign);
    }
    PackageDigitalSignatureManager mgr = 
        new PackageDigitalSignatureManager(package);
    mgr.CertificateOption = CertificateEmbeddingOption.InSignaturePart;
    mgr.Sign(partsToSign, certificate,
       relationshipsToSign);
}

At this point the signatures will not validate correctly in Microsoft Office. To allow Office to validate the signature a custom object needs to be present in the signature parts. While this error should be fixed in a later service pack, you will want to add this object when using Microsoft Office to work with the documents.

The XML markup required looks like the following.

<SignatureProperties xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignatureProperty Id="idOfficeV1Details" Target="#signatureID">
    <SignatureInfoV1 xmlns="http://schemas.microsoft.com/office/2006/digsig">
      <ManifestHashAlgorithm>
        http://www.w3.org/2000/09/xmldsig#sha1
      </ManifestHashAlgorithm>
    </SignatureInfoV1>
  </SignatureProperty>
</SignatureProperties>

You can add this data to the signature using a custom DataObject and Reference when creating the signature with the 'Sign' method. 

DataObject officeObject = CreateOfficeObject(signatureID, manifestHashAlgorithm);
Reference officeObjectReference = new Reference("#" + OfficeObjectID);
mgr.Sign(partsToSign, certificate,
    relationshipsToSign, signatureID,
    new DataObject[] { officeObject },
    new Reference[] { officeObjectReference }); 

 

I’ve created a small demo application called XmlSign which signs all documents you provide on the commandline. It uses the .NET Framework X509Certificate2UI class to pick a certificate and signs all parts and relationships in the document.

 

Published Saturday, February 24, 2007 1:10 PM by wouterv

Comments

 

Doug Mahugh said:

I'm back home after a week on the road, with lots of interesting work to finish up and an overflowing
February 26, 2007 6:17 AM
 

Doug Mahugh said:

Professor Flavio Soares da Silva from the University of Sao Paulo has an interesting article on "Arguments
June 14, 2007 4:46 AM
 

Wahaj said:

Hi, It would be nice if there is a sample for verifying the XML signatures inside Office documents. Regards, Wahaj
June 25, 2007 8:17 AM
 

jekora said:

hi, the demo app worked fine on my dev machine but trying it on a less equipped workstation gave following error - what should I install to make it work..? Thanks. Unhandled Exception: System.IO.FileNotFoundException: Could not load file or ass embly 'WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad3 64e35' or one of its dependencies. The system cannot find the file specified. File name: 'WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf38 56ad364e35' at DocSign.Program.SignFile(String path, X509Certificate2 certificate) at DocSign.Program.Main(String[] args) in C:\TEMP\openxmlsign\XmlSign\Program .cs:line 37 WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\M icrosoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure lo gging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fus ion!EnableLog].
July 20, 2007 11:50 AM
 

wouterv said:

Hi Jekora,

you need to install the .NET 3.0 Framework on your workstation:

http://www.microsoft.com/downloads/details.aspx?FamilyId=10CC340B-F857-4A14-83F5-25634C3BF043

Hope it helps,

Wouter

July 20, 2007 11:59 AM
 

mszCool's thoughts and cents revealed said:

As I just answered this question for a customer: of course it is possible to digitally sign Office Open
July 31, 2007 4:26 PM
 

Noticias externas said:

As I just answered this question for a customer: of course it is possible to digitally sign Office Open
July 31, 2007 5:15 PM
 

Walter Stiers - Academic Relations Team (BeLux) said:

mszCool's thoughts and cents revealed has on his blog an entry Digitally Signing Office Open XML Packages
July 31, 2007 7:51 PM
 

Segurança na Microsoft said:

O Microsoft Office suporta a assinatura digital de documentos desde a versão Office XP de 2001. O usuário
August 3, 2007 8:44 PM
 

pattami said:

Hi Wouter van Vugt, I have a doubt .. how to select the certificate in mgr.Sign(partsToSign, certificate, relationshipsToSign) in a secure way. I dont want to use the mgr.Sign(package) over load of this function , prompting ui for certificate selection. How will i programatically select the certificate in a secure way ? Is it possible to avoid using private key or password openly inside the code for the above Thanks in advance pattami
October 18, 2007 11:22 AM
 

Jason Bie said:

Hi Wouter van Vugt, your program is very useful full me, thank you very much. By the way, I have a question, How can I sign for macros in a docm file only, not all the parts. Can you give me some tips?
October 19, 2007 10:11 AM
Anonymous comments are disabled

This Blog

Syndication

News


Add to Technorati Favorites
Powered by Community Server, by Telligent Systems