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.
Posted
24-02-2007 13:10
by
Anonymous