开放打包约定的数字签名框架

 

大卫·梅尔泽和安德烈·舒尔
Microsoft Corporation

2006 年 9 月

适用于:
   OPC 数字签名框架
   W3C XML 数字签名标准
   Microsoft .NET 3.0 Framework

摘要:讨论 OPC 数字签名框架,概述包组件和支持服务,以及签名策略及其实现的示例。 ) (12 个打印页

目录

简介
OPC 数字签名框架的组件
   XML 数字签名标准
   在包中表示数字签名
   签名部件和关系
包签名的编程支持
   为包部件和关系签名
   验证证书和签名
应用程序签名策略
   XPS 文档
   对 XPS 签名的编程支持
参考

简介

由开放打包约定 (OPC) 指定的打包模型描述了包、部件和关系。 包保存部分,用于保存内容和资源。 定义关系以将包连接到部件,以及连接包中的各个部件。

本文讨论 OPC 数字签名框架,提供包组件和支持服务的概述,以及签名策略及其实现的示例。

签名框架包括用于表示数字签名的基础结构,以及用于创建和验证签名的服务。 签名框架允许将 W3C XML 数字签名标准应用于包部件和关系。

使用签名框架,基于包的格式的所有者定义并实现特定于其格式的“签名策略”。 这些策略指定如何对特定格式的整型内容进行签名和验证,并体现签名如何用于不同的工作流。 事实上,对于单一格式,可以定义多个策略,以便在文档生命周期的不同阶段使用。

基于包的格式的签名策略以签名部件和关系以及可能的其他文档特征 (表示,例如验证预期显示设备、颜色深度或应用程序版本) 。 签名策略指定要签名的文档组件和保留未签名的文档组件(如果有)。 例如,可以实施签名策略以允许将新部件和关系添加到包中,或者如果向包添加新部件或签名,则策略可能导致签名失效。

本文假定你熟悉 开放打包约定规范 和 W3C 建议 XML 签名语法和处理

OPC 数字签名框架的组件

XML 数字签名标准

包的签名框架使用 XML 数字签名标准,如 W3C 建议 XML 签名语法和处理中所述。 此建议指定用于生成和存储数字签名的 XML 语法和处理规则。

该标准定义了对任何类型的数字资源进行签名和验证的 XML 签名元素类型、架构和一致性要求。 架构还定义用于引用资源和指定签名相关算法的元素。

数字签名的功能

数字签名可用于确定签名内容自签名后是否已更改。 签名包含根据已知算法进行哈希处理的内容清单,并在创建时存储在签名中。 若要确定内容是否已更改,请重新创建已签名内容的哈希,并将其与签名中存储的哈希进行比较。

数字签名还可用于标识内容的签名者。 签名者的标识由与签名关联的证书表示。 证书可以嵌入到签名中,也可以在其他位置使用。

数字签名不会“锁定”文档或使其 (加密,尽管它可能已) 加密。 文档内容在签名后保持不变。 数字签名不会阻止意外的使用者查看已签名的内容。

在包中表示数字签名

应用程序使用部件和关系的指定配置将数字签名合并到包中。

签名框架使用 XML 数字签名标准允许的打包命名空间中的元素和属性。 打包命名空间中定义的签名元素支持包特定的功能,这些功能可增强标准,而不会与标准相矛盾。 有关新增内容摘要,请参阅 OPC 规范的“对 XML 数字签名规范的修改”部分。

为签名框架定义的包部件包括源部分、XML 签名部分和证书部分。 每个都有一个定义完善的内容类型。 定义完善的关系类型用于连接包中的签名部分,如 OPC 规范的附录 H“标准命名空间和内容类型”中所述。

数字签名源部件

数字签名源部分是浏览包中的签名的起点。 数字签名源部分使用 数字签名 源关系从包根目录定位。 多个签名部件可能以源部件为目标。 如果包中没有签名,则源部件将不存在。

数字签名 XML 签名部件

数字签名 XML 签名部分包含 W3C 数字签名标准以及打包命名空间中定义的标记。 这些部件的目标来自具有 数字签名关系的数字签名 源部件 XML。

数字签名证书部件

标识签名者所需的 X.509 证书(如果放置在包中)可以嵌入在 XML 签名部件中,或存储在单独的证书部件中。 可选的证书部件面向具有 数字签名证书 关系的 XML 签名部件。 证书部件可以在多个签名部件之间共享。

自定义签名部件

签名框架允许但不处理特定于应用程序的自定义 () 签名部件。 保存 XML 签名以外的签名形式的签名部件必须由自定义内容类型标识。 此外,必须使用具有自定义关系类型的关系来面向数字签名源部件中的部件。

签名部件和关系

XML 数字签名标准允许对可寻址资源进行签名,对于包来说,这些资源是一部分。 签名框架支持对部件进行签名。 包中存储的关系(存储在关系部件中)可以一次性全部签名,也可以指定一部分关系进行签名。

部件的内容类型与部件内容一起签名,以确保有效签名的包中的部件按预期使用或呈现。 由于内容类型不是可寻址的资源,因此采用特定于包的方法对内容类型值进行签名。 对包进行签名后,每个已签名部件的内容类型将存储在引用已签名部分的 URI 的查询组件中。 使用包时,OPC 数字签名框架使用内容类型值来确保部件的内容类型自部件签名后未发生更改。

当关系部分作为一个整体进行签名时,在该部件中定义的所有关系都会被签名。 为了支持允许更改包的某些内容的签名策略而不使签名失效,签名框架提供了一种用于对指定关系进行签名的机制。 若要对指定关系进行签名,签名框架使用特殊转换,关系转换 (请参阅 转换算法) 。

关系转换创建仅包含指定关系集的关系部分。 获取的关系部分用于签名和签名验证期间。

包签名的编程支持

若要对签名进行签名和验证,应用程序可以使用 .NET 3.0 类 PackageDigitalSignatureManagerSystem.IO.Packaging 命名空间中定义的包特定类基于在System.Security.Cryptography.Xml 命名空间中定义的 Microsoft .NET 3.0 Framework 的数字签名类。

PackageDigitalSignatureManager 类用于创建和验证签名,以及将签名基础结构置于包中。 签名由基于 PackageDigitalSignature 类的对象表示。

为包部件和关系签名

应用程序根据其签名策略定义要签名的部分和关系的列表。 然后,应用程序调用 PackageDigitalSignatureManager.Sign () 方法来创建签名并将签名基础结构添加到包。

下面的示例代码演示如何对包中的所有部件(关系部分除外)进行签名,对源自包根的所有现有关系进行签名,以及将用于签名的证书嵌入到 XML 签名部件中。 示例代码假定包中一开始没有签名,并且验证之前只应用了一个签名。

开始签名过程

若要开始使用包中的签名,请先创建 PackageDigitalSignatureManager,如下所示。

    // Open the package.
    Package package = Package.Open(filename);

    // Create the PackageDigitalSignatureManager
      PackageDigitalSignatureManager dsm =
        new PackageDigitalSignatureManager(package);

证书嵌入选项

证书可以表示为嵌入在签名本身中的字符串、作为包中的单独部分,或表示为包外部的资源。 如果要将证书放置在包中,应用程序将使用 PackageDigitalSignature.CertificateOption 属性的嵌入选项指定证书的持久保存方式。 创建 PackageDigitalSignatureManager 类后,将设置证书的嵌入选项,如下面的示例代码所示。

    //Specify that the certificate is embedded in the signature held
    //in the XML Signature part.

    //Certificate embedding options include:
    // InSignaturePart – Certificate is embedded in the signature.
    // InCertificatePart – Certificate is embedded in a 
    //                     separate certificate part

    dsm.CertificateOption =
        CertificateEmbeddingOption.InSignaturePart;

已签名部件列表

要签名的部件列表是使用寻址部件的 URI 指定的。 在此示例中,包中的所有部分都将签名,关系部分除外,这些部分使用 PackUriHelper.IsRelationshipPartUri () 方法筛选掉。

    //Initialize a list to hold the part URIs to sign.

    System.Collections.Generic.List<Uri> partsToSign =
        new System.Collections.Generic.List<Uri>();

    //Add each part to the list, except relationships parts.
    foreach (PackagePart packagePart in package.GetParts())
    {
        if (!PackUriHelper.IsRelationshipPartUri(packagePart.Uri))
      partsToSign.Add(packagePart.Uri);
  }

已签名关系列表

使用关系转换对各个关系进行签名。 以这种方式对关系进行签名允许将新关系添加到包中,而不会使签名失效。

通过创建将在签名时使用的 PackageRelationshipSelector 对象的列表来选择关系进行签名。 PackageRelationshipSelector 对象可以按开放打包约定) “标准命名空间和内容类型”部分中定义的关系类型 (创建为组,也可以通过指定关系 ID 单独创建,如以下示例所示。

     //Create list of selectors for the list of relationships

     List<PackageRelationshipSelector> relationshipSelectors = 
          new List<PackageRelationshipSelector>();

     //Create one selector for each package-level relationship, based on id

  foreach (PackageRelationship relationship in package.GetRelationships())
            {
                relationshipSelectors.Add(new
                    PackageRelationshipSelector(relationship.sourceUri, 
                    PackageRelationshipSelectorType.Id, relationship.Id));
            }

使用 PackageRelationshipSelectorType.Id 创建 PackageRelationshipSelector 时,将选择一个具有指定唯一 ID 的关系进行签名。 使用 PackageRelationshipSelectorType.Type 创建选择器时,将选择具有指定类型的所有关系进行签名。 如果以后将同一类型的关系添加到包中,签名将失效。

创建证书对象

在签名之前,可以通过实例化 类型 System.Security.Cryptography.X509Certificates.X509Certificate2 的对象来获取有效的 X.509 证书。 此对象在签名时传递给 PackageDigitalSignatureManager.Sign () 方法。 有关创建证书对象的详细信息,请参阅 System.Security.Cryptography.X509Certificates 命名空间。

应用签名

创建要签名的部分和关系列表并获取证书对象后,应用程序将调用 PackageDigitalSignatureManager.Sign () 方法。

     //Sign package using components created above

     PackageDigitalSignature signature = dsm.Sign(partsToSign, 
          x509Certificate, relationshipSelectors);

     //After signing, close the package.
     //The signature will be persisted in the package.
     package.Close();

调用 Sign () 方法时,将生成哈希并将其存储在签名清单中,并创建签名部分。 如果包中已存在签名基础结构,则将 (添加新签名部件(如果允许) )。 如果包中尚不存在基础结构, 则 Sign () 方法将创建基础结构并将其置于包中。

验证证书和签名

应用程序可以验证证书或签名。 在验证签名之前,应验证证书。 表示包中签名的 对象 PackageDigitalSignature 具有属性“Signer”,该属性返回用于创建该签名的证书(如果位于包中)。 如果证书未嵌入到包中,则应用程序将从应用程序已知的位置获取证书。

PackageDigitalSignatureManager.VerifyCertificate () 方法用于验证获取的证书,检查证书结构、到期日期和链状态。 有关链状态的详细信息,请参阅 .NET Framework 类库中的 X509ChainStatusFlag 枚举

应用程序开发人员可以使用证书状态来支持其签名策略。 例如,应用程序可以指定仅接受在特定日期之后颁发的证书。

PackageDigitalSignatureManager.VerifySignatures () 方法用于验证包中的所有签名。 此方法仅验证签名,而不验证与签名关联的证书。

下面的示例代码可用于验证签名示例中的包中放置的证书和签名。 示例代码假定未将其他签名添加到包中。

    // Open the package.

    Package package = Package.Open(filename);

    // Create the PackageDigitalSignatureManager

    PackageDigitalSignatureManager dsm =
        new PackageDigitalSignatureManager(package);

    // Verify the collection of certificates in the package (one, in this case)

        foreach(PackageDigitalSignature signature in pdsm.Signatures)
        {
        if(PackageDigitalSignatureManager.VerifyCertificate(signature.Signer)
            != X509ChainStatusFlags.NoError)
              {
                // Application-specific code for error handling 
                // or certificate validation 
              }
        }
 
   // For this example, if all certificates are valid,
   // verify all signatures in the package.
 
    VerifyResult vResult = dsm.VerifySignatures(false);
    Console.WriteLine("Result " + vResult.ToString());

    // Close the package.

    package.Close();

应用程序签名策略

使用基于包的格式的应用程序将自己的策略定义为签名框架的一部分。 策略由格式的元素类型和工作流要求决定。 在本部分中,将针对一种基于 Microsoft 包的格式(XPS 文档格式)介绍签名策略。

XPS 文档

XPS 文档格式基于 XML 纸张规范中指定的开放打包约定。 XML 纸张规范定义用于对 XPS 文档进行签名的策略。 在该策略中,有可用于支持应用程序功能或工作流的签名选项。

XPS 文档包的签名策略

XPS 文档的签名策略描述了一组必须签名才能验证内容的部分和关系。 作为此策略的一部分,应用程序可以创建一个签名,该签名可以选择包含内容附属的特定部分的组合,例如 CoreProperties 部件。 对这些部分进行签名将阻止更改它们,而不会使签名失效。 此外,应用程序可以选择在签名中对附加到数字签名源部件的关系部分进行签名,从而禁止在不使签名失效的情况下将新签名添加到文档中。

为了使签名有效,XPS 文档签名策略要求签名中包含某些部分和关系。 无法对无法识别的部分或关系进行签名。 验证签名时,应用程序必须确认所有必需的部件和关系都已签名。

要签名的 XPS 文档部件

下表包含必须在所有 XPS 文档中签名的部件列表,以及可选签名的部件的列表。 对于关系,XPS 签名策略指定所需的关系 (面向所需部分的关系) 始终使用 OPC 定义的关系转换进行签名。 如果部件已签名,则还必须对面向它的关系进行签名。

部件类型 策略
FixedDocumentSequence 部件 必须签名
FixedDocument 部件 必须签名
DocumentStructure 部件 必须签名
SignatureDefinitions 部件 必须签名
FixedPage 部件 必须签名
字体、图像) 等所需资源部件 ( 必须签名
StoryFragments 部件 必须签名
缩略图部件 必须签名
CoreProperties 部分 (可选)签名
数字签名源部分 (可选)签名
数字签名证书部分 (可选)签名
PrintTicket 部件 (可选)签名
DiscardControl 部件 (可选)签名

有关 XPS 签名策略的详细信息,请参阅 XML 纸张规范中的“XPS 文档包功能:数字签名:签名规则”部分。

标记兼容性签名策略

XML 纸张规范介绍了在 XPS 文档中包括备用内容的方法:标记兼容性。 备用内容放置在标记兼容性命名空间中的元素内。 根据策略,无法对具有标记兼容性元素和属性的 XPS 文档进行有效签名,除非签名应用程序将所有内容替代项识别为等效项。 只能对包含可识别的元素和属性的 XPS 文档进行签名或验证。

副署

可以将多个签名应用于 XPS 文档的内容。 例如,表示法律合同的内容可能需要多人应用其签名,以指示已签名的内容和签名者的标识。

添加新签名始终会在附加到数字签名源部件的关系部件中创建一个新关系。 若要在不使现有签名失效的情况下将新签名添加到 XPS 文档,此关系部分必须保持未签名 (尽管关系子集可能) 签名。 如果关系部分包含在签名中,则该签名将被随后应用的所有签名失效。

验证 XPS 签名

除了定义签名创建之外,XPS 签名策略还指定了如何验证签名是否有效。 该策略定义签名的有效状态,包括不合规、损坏、可疑和有效,如下表所示。

签名状态 所有必需的部件和关系都已签名? 签名仅包括已识别的内容? 已签名内容的哈希是否已验证? 已识别签名标记兼容性内容? 证书是否有效?
不符合

YES

不适用

不适用

不适用

不适用

不适用

不适用

不适用

已损坏 YES YES 不适用 不适用
可疑 YES

YES

YES

YES

YES

YES

YES

不适用

有效 YES YES YES YES YES

XPS 查看器显示有问题的和损坏的 XPS 签名,以及有效的 XPS 签名。 不枚举不符合的签名。

对 XPS 签名的编程支持

使用 XPS 文档时,应用程序可以使用 XpsDigitalSignature 类中的 方法。 此类基于 PackageDigitalSignature 类,包括遵循 XPS 数字签名规范中指定的算法和要求的方法。 签名和验证方法验证 XPS 文档的所有必需部件和关系是否已签名。

对 XPS 文档进行签名

XpsDocument.SignDigitally () 方法用于对 XPS 文档进行签名。 在调用 方法之前,应用程序必须具有 X.509 证书,可以使用对象 System.Security.Cryptography.X509Certificates.X509Certificate2 获取该证书。

     // Open the XPS Document

     XpsDocument document = new XpsDocument(dstContainer,
          FileAccess.ReadWrite);

     // Obtain the certificate object from a file

     X509Certificate certificate =
          509Certificate.CreateFromCertFile(certFilename);

     // Create the signature and add it to the document using
     // the OPC Signing Framework

     document.SignDigitally(certificate, true, 
          XpsDigSigPartAlteringRestrictions.None);

XpsDigSigPartAlteringRestrictions 可用于根据签名策略为签名指定其他限制。 此参数指定是否从签名中排除 CoreMetadata 和/或 SignatureOrigin 部件。 然后,可以稍后更改排除的部分,而不会使签名失效。 例如,从签名中排除 CoreMetadata 部件可使应用程序更改某些文档属性,而不会使签名失效。

PrintTicketDiscardControl 部件从 SignDigitally () 方法创建的签名中排除,但这些部件可以选择以特定于应用程序的方式进行签名。

验证 XPS 文档签名

一个或多个签名可以与 XPS 文档一起存储。 可以从 XpsDocument.Signatures 属性获取签名。 每个签名都由 XpsDigitalSignature 对象的实例表示。

在下面的示例中,仅验证集合中的第一个签名。

     // Open the XPS Document.

     // Obtain the first enumerated signature.

     foreach (XpsDigitalSignature digitalSignature in
              document.Signatures)
     { 
          // Verify the signature object, if present.

          if (digitalSignature.Verify() ==
     System.IO.Packaging.PackageDigitalSignature.VerifyResult.Success)
          {
         //Signature is valid
          }
     }

参考