本规范描述了使用密码学(特别是数字签名和相关数学证明)确保可验证凭证及类似受限数字文档的真实性和完整性的机制。

工作组正在积极寻求对本规范的实施反馈。为了退出候选推荐阶段,工作组要求至少有两个独立的实现支持规范中的每个强制性功能。有关一致性测试过程的详细信息,请参阅实施报告中列出的测试套件。

介绍

本规范描述了使用密码学(特别是数字签名和相关数学证明)确保可验证凭证及类似受限数字文档的真实性和完整性的机制。密码学证明为分布式系统的实现者提供了有用的功能。例如,证明可以用于:

工作原理

数据完整性的操作在概念上很简单。创建密码学证明时,执行以下步骤:1)转换,2)哈希,3)生成证明。


图示显示了创建密码学证明涉及的三个步骤。图示从左到右排列,最左侧是一个标记为“数据”的蓝色框。蓝色框从左到右经过三个后续的黄色箭头,分别标记为“转换数据”、“哈希数据”和“生成证明”。最右侧的蓝色框标记为“带有证明的数据”。
创建密码学证明时,数据会被转换、哈希并通过密码学保护。

转换是由转换算法描述的过程,该算法接收输入数据并为哈希过程做好准备。一个可能的转换示例是获取参加会议的人员姓名记录,按个人姓氏的字母顺序对列表进行排序,并将姓名按顺序逐行写在纸上。转换的示例包括规范化二进制到文本编码

哈希是由哈希算法描述的过程,该算法使用密码学哈希函数计算转换数据的标识符。这个过程在概念上类似于电话地址簿的功能,其中一个人获取某人的姓名(输入数据)并将该姓名映射到该人的电话号码(哈希)。密码学哈希函数的示例包括SHA-3BLAKE-3

生成证明是由证明序列化算法描述的过程,该算法计算一个值以保护输入数据的完整性免受修改或证明某个期望的信任阈值。这个过程在概念上类似于使用蜡封在信封上以建立对发送者的信任并表明信件在传输过程中未被篡改。证明序列化函数的示例包括数字签名权益证明知识证明

验证密码学证明时,执行以下步骤:1)转换,2)哈希,3)验证证明。


图示显示了验证密码学证明涉及的三个步骤。图示从左到右排列,最左侧是一个标记为“带有证明的数据”的蓝色框。蓝色框从左到右经过三个后续的黄色箭头,分别标记为“转换数据”、“哈希数据”和“验证证明”。最右侧的蓝色框标记为“带有证明的数据”。
验证密码学证明时,数据会被转换、哈希并检查其正确性。

在验证过程中,[=转换=]和[=哈希=]步骤在概念上与上述描述相同。

验证证明是由证明验证算法描述的过程,该算法应用密码学证明验证函数以查看输入数据是否可以被信任。可能的证明验证函数包括数字签名权益证明知识证明

本规范详细说明了密码学软件架构师和实现者如何将这些过程打包成称为[=密码套件=]的东西,并将其提供给应用程序开发人员,以保护应用程序数据在传输和存储中的完整性。

设计目标和基本原理

本规范针对以下设计目标进行了优化:

简单性
该技术旨在使应用程序开发人员易于使用,而无需对密码学进行大量培训。它优先考虑以下群体的需求:应用程序开发人员优先于密码学套件实现者,密码学套件实现者优先于密码学套件设计者,密码学套件设计者优先于密码学算法规范作者。该解决方案专注于合理的默认值,以防止选择无效的保护机制。有关更多详细信息,请参阅第[[[#protecting-application-developers]]]节和第[[[#versioning-cryptography-suites]]]节。
可组合性
许多历史上的数字签名机制具有单一化的设计,将数据转换、语法、数字签名和序列化结合到单一规范中,从而限制了用例。本规范将每个组件分层,以支持更广泛的用例,包括通用的选择性披露和与序列化无关的签名。有关更多基本原理,请参阅第[[[#transformations]]]节、第[[[#data-opacity]]]节和第[[[#versioning-cryptography-suites]]]节。
弹性
由于技术进步可能会在没有警告的情况下破坏数字证明机制,因此[=密码套件=]提供多层保护并能够快速升级是很重要的。本规范同时提供了算法灵活性和密码学分层,同时仍然保持数字证明格式易于开发人员理解和使用。有关详细信息,请参阅第[[[#agility-and-layering]]]节。
渐进式可扩展性
创建和部署新的密码学保护机制被设计为一个有计划的、迭代的和谨慎的过程,承认扩展是从实验到实现再到标准化的分阶段过程。本规范努力在密码学创新速度的提高与稳定的生产级密码学套件需求之间取得平衡。有关建立新类型密码学证明的说明,请参阅第[[[#cryptographic-suites]]]节。
序列化灵活性
密码学证明可以以多种不同但等效的方式序列化,并且通常与原始文档语法紧密绑定。本规范允许创建不绑定于原始文档语法的密码学证明,从而支持更高级的用例,例如能够在各种序列化语法(如 JSON 和 CBOR)中使用单一数字签名,而无需重新生成密码学证明。有关此方法的好处,请参阅第[[[#transformations]]]节。

虽然本规范主要关注可验证凭证,但该技术的设计是通用的,因此可以用于其他用例。在这些情况下,实施者应自行尽职调查并进行专家审查,以确定该技术是否适用于其用例。

一个符合规范的安全文档是任何可以转换为 JSON 文档字节序列,并符合第[[[#proofs]]]节、第[[[#proof-purposes]]]节、第[[[#resource-integrity]]]节、第[[[#contexts-and-vocabularies]]]节和第[[[#dataintegrityproof]]]节中的相关规范性要求。

一个符合规范的密码学套件规范是任何符合第[[[#cryptographic-suites]]]节中相关规范性要求的规范。

一个符合规范的处理器是任何以软件和/或硬件实现的算法,该算法根据第[[[#algorithms]]]节中的相关规范性声明生成和/或消费[=符合规范的安全文档=]。符合规范的处理器在消费不符合规范的文档时必须产生错误。

术语

本节定义了本规范中使用的术语。这些术语在本规范中出现时会附带链接。

数据完整性证明
一组表示数字证明及验证该证明所需参数的属性。数字签名是一种数据完整性证明。
公钥
可用于验证使用对应[=私钥=]创建的数字证明的密码学材料。
私钥
有时称为私有密钥,是一种不应与任何人共享的密码学材料,用于生成数字证明和/或数字签名。
证明目的
证明的具体意图;实体创建它的原因。受保护的声明充当了一种保护措施,以防止证明被用于其原本未打算的用途。
密码套件
定义使用特定密码学原语以实现特定安全目标的规范。这些文档通常用于指定[=验证方法=]、数字签名类型、它们的标识符以及其他相关属性。有关更多详细信息,请参阅第[[[#cryptographic-suites]]]节。
受控标识符文档
包含密码学公共材料的文档,如[[[CID]]]规范中定义的那样。
验证者
一个实体执行的角色,通过接收包含一个或多个[=数据完整性证明=]的数据,然后确定证明是否合法。
验证方法

一组参数,可与一个过程一起使用以独立验证证明。例如,密码学[=公钥=]可用作数字签名的验证方法;在这种用法中,它验证签名者拥有相关的密码学[=私钥=]。

在此定义中,“验证”和“证明”旨在广泛适用。例如,在 Diffie-Hellman 密钥交换期间,密码学公钥可能用于协商对称加密密钥,从而保证密钥协商过程的完整性。因此,这也是一种验证方法,即使过程描述可能未使用“验证”或“证明”一词。

验证关系

表达主体与[=验证方法=]之间关系的一种方式。验证关系的一个示例是 身份验证

数据模型

本节规定了用于表达[=数据完整性证明=]及相关资源完整性的数据模型。

本规范中的所有数据模型属性和类型都映射到 URL。这些 URL 所在的词汇表定义在[[[?SECURITY-VOCABULARY]]]中。在受保护文档中执行此映射的显式机制是 `@context` 属性。

映射机制由[[[JSON-LD11]]]定义。为了确保文档可以在不使用 JSON-LD 库的情况下进行互操作处理,文档作者被建议确保领域专家已完成以下工作:1)指定与 `@context` 属性相关的所有值的预期顺序,2)发布每个 `@context` 文件的密码学哈希,3)确认每个 `@context` 文件的内容适合预期的使用场景。

当文档由不使用 JSON-LD 库的处理器处理时,如果需要使用与 JSON-LD 环境中相同的语义,建议实现者:1)强制执行 `@context` 属性中的预期顺序和值,2)确保每个 `@context` 文件与其已知的密码学哈希匹配。

结合 JSON Schema 使用带有已发布密码学哈希的静态版本化 `@context` 文件,是实现上述机制的一种可接受方法。这种方法确保在不使用 JSON-LD 库的处理器中,正确的术语识别、类型和顺序得以实现。有关更多详细信息,请参阅[[[?VC-DATA-MODEL-2.0]]]中的 类型特定处理部分。

证明

[=数据完整性证明=] 提供了关于证明机制、验证该证明所需的参数以及证明值本身的信息。所有这些信息都使用诸如[[[?SECURITY-VOCABULARY]]]之类的链接数据词汇表提供。

在对象上表达[=数据完整性证明=]时,必须使用`proof`属性。可验证凭证中的`proof`属性是一个[=命名图=]。如果存在,其值必须是以下属性中描述的单个对象或无序对象集。

id
一个可选的证明标识符,必须是一个 URL [[URL]],例如作为 URN 的 UUID(`urn:uuid:6a1676b8-b51f-11ed-937b-d76685a20ff5`)。此属性的使用在第[[[#proof-chains]]]节中进一步解释。
type
必须指定证明的具体类型,作为映射到 URL [[URL]]的字符串。证明类型的示例包括`DataIntegrityProof`和`Ed25519Signature2020`。证明类型决定了保护和验证证明所需的其他字段。
proofPurpose
必须指定创建证明的原因,作为映射到 URL [[URL]]的字符串。证明目的充当一种保护措施,防止证明被用于其原本未打算的用途。例如,如果没有此值,证明的创建者可能会被诱导在登录过程中使用通常用于创建可验证凭证(`assertionMethod`)的密码学材料(`authentication`),从而导致创建了他们从未打算创建的可验证凭证,而不是预期的登录操作。
verificationMethod
验证方法是验证证明所需的手段和信息。如果包含,该值必须是映射到[[URL]]的字符串。包含`verificationMethod`是可选的,但如果未包含,其他属性(如`cryptosuite`)可能提供获取验证证明所需信息的机制。请注意,当在[=数据完整性证明=]中表达`verificationMethod`时,该值指向数据的实际位置;也就是说,`verificationMethod`通过 URL 引用可以用来验证证明的[=公钥=]的位置。此[=公钥=]数据存储在[=受控标识符文档=]中,其中包含验证方法的完整描述。
cryptosuite
可以用来验证证明的密码学套件的标识符。有关更多信息,请参阅[[[#cryptographic-suites]]]。如果证明`type`是`DataIntegrityProof`,则必须指定`cryptosuite`;否则,可以选择指定`cryptosuite`。如果指定,其值必须是字符串。
created
证明创建的日期和时间是可选的,如果包含,必须指定为[[XMLSCHEMA11-2]] `dateTimeStamp`字符串,使用协调世界时(UTC),以 Z 结尾,或带有相对于 UTC 的时区偏移量。[=符合规范的处理器=]可以选择处理未正确序列化且没有偏移量的时间值。未正确序列化且没有偏移量的时间值应被解释为 UTC。
expires
`expires`属性是可选的,如果存在,指定证明的过期时间。如果存在,必须是[[XMLSCHEMA11-2]] `dateTimeStamp`字符串,使用协调世界时(UTC),以 Z 结尾,或带有相对于 UTC 的时区偏移量。[=符合规范的处理器=]可以选择处理未正确序列化且没有偏移量的时间值。未正确序列化且没有偏移量的时间值应被解释为 UTC。
domain
`domain`属性是可选的。它传达了证明旨在使用的一个或多个安全域。如果指定,关联的值必须是字符串或无序字符串集。验证者应使用该值确保证明旨在验证者操作的安全域中使用。`domain`参数的规范在挑战-响应协议中很有用,其中验证者在证明创建者已知的安全域中操作。域值的示例包括:`domain.example`(DNS 域)、`https://domain.example:8443`(Web 源)、`mycorp-intranet`(定制文本字符串)和`b31d37d4-dd59-47d3-9dd8-c973da43b63a`(UUID)。
challenge
如果指定了`domain`,则应在证明中包含一个字符串值。该值在特定域和时间窗口内使用一次。此值用于减轻重放攻击。挑战值的示例包括:`1235abcd6789`、`79d34551-ae81-44ae-823b-6dadbab9ebd4`和`ruby`。
proofValue
一个字符串值,表示使用指定的`verificationMethod`验证数字证明所需的基编码二进制数据。该值必须使用[[[CID]]]规范的第2.4 Multibase节中描述的标头和编码来表达二进制数据。此值的内容由特定的密码学套件确定,并设置为该密码学套件的添加证明算法生成的证明值。可以使用由密码学套件指定的其他属性(具有不同的编码)代替此属性来编码验证数字证明所需的数据。
previousProof
`previousProof`属性是可选的。如果存在,必须是字符串值或无序字符串值列表。每个值标识另一个[=数据完整性证明=],所有这些证明也必须验证当前证明才能被视为已验证。此属性在第[[[#proof-chains]]]节中使用。
nonce
由证明创建者提供的可选字符串值。此字段的一个用途是通过减少由确定性生成的签名导致的可链接性来增加隐私。

可以像以下示例一样将证明添加到 JSON 文档中:

{
  "myWebsite": "https://hello.world.example/"
};
        

以下证明使用`eddsa-jcs-2022`密码套件[[?DI-EDDSA]]保护上述文档,该套件通过使用 JSON 规范化方案(JCS)[[?RFC8785]]转换输入数据并使用 Edwards 数字签名算法(EdDSA)对其进行数字签名来生成可验证的数字证明。

{
  "myWebsite": "https://hello.world.example/",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "created": "2023-03-05T19:23:24Z",
    "verificationMethod": "https://di.example/issuer#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG",
    "proofPurpose": "assertionMethod",
    "proofValue": "zQeVbY4oey5q2M3XKaxup3tmzN4DRFTLVqpLMweBrSxMY2xHX5XTYV8nQApmEcqaqA3Q1gVHMrXFkXJeV6doDwLWx"
  }
}
        

同样,可以将证明添加到如下的 JSON-LD 数据文档中:

{
  "@context": {"myWebsite": "https://vocabulary.example/myWebsite"},
  "myWebsite": "https://hello.world.example/"
};
        

以下证明使用`ecdsa-rdfc-2019`密码套件[[?DI-ECDSA]]保护上述文档,该套件通过使用 RDF 数据集规范化方案[[?RDF-CANON]]转换输入数据并使用椭圆曲线数字签名算法(ECDSA)对其进行数字签名来生成可验证的数字证明。

{
  "@context": [
    {"myWebsite": "https://vocabulary.example/myWebsite"},
    "https://w3id.org/security/data-integrity/v2"
  ],
  "myWebsite": "https://hello.world.example/",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "ecdsa-rdfc-2019",
    "created": "2020-06-11T19:14:04Z",
    "verificationMethod": "https://ldi.example/issuer#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP",
    "proofPurpose": "assertionMethod",
    "proofValue": "zXb23ZkdakfJNUhiTEdwyE598X7RLrkjnXEADLQZ7vZyUGXX8cyJZRBkNw813SGsJHWrcpo4Y8hRJ7adYn35Eetq"
  }
}
        

本规范支持表达日期和时间,例如通过`created`和`expires`属性。如果处理证明时检测到超出允许的时间范围,这些信息可能会间接暴露给个人。在显示与密码学证明有效性相关的日期和时间值时,建议实现者尊重个人的语言环境和本地日历偏好[[?LTLI]]。将时间戳转换为本地时间值时,预计会考虑个人的时区期望。有关向个人表示时间值的更多详细信息,请参阅

{
  "@context": [
    {"myWebsite": "https://vocabulary.example/myWebsite"},
    "https://w3id.org/security/data-integrity/v2"
  ],
  "myWebsite": "https://hello.world.example/",
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "ecdsa-rdfc-2019",
    "created": "2020-06-11T19:14:04Z",
    // 该证明在创建一个月后过期
    "expires": "2020-07-11T19:14:04Z",
    "verificationMethod": "https://ldi.example/issuer#zDnaepBuvsQ8cpsWrVKw8fbpGpvPeNSjVPTWoq6cRqaYzBKVP",
    "proofPurpose": "assertionMethod",
    "proofValue": "z98X7RLrkjnXEADJNUhiTEdwyE5GXX8cyJZRLQZ7vZyUXb23ZkdakfRJ7adYY8hn35EetqBkNw813SGsJHWrcpo4"
  }
}
        

数据完整性规范支持在单个文档中包含多个证明的概念。识别了两种多证明方法:证明集(无序)和证明链(有序)。

证明集

证明集在需要由多个实体保护相同数据但证明顺序无关紧要的情况下很有用,例如合同上的一组签名。证明集没有顺序,通过将一组证明与文档中的`proof`键关联来表示。

{
  "@context": [
    {"myWebsite": "https://vocabulary.example/myWebsite"},
    "https://w3id.org/security/data-integrity/v2"
  ],
  "myWebsite": "https://hello.world.example/",
  "proof": [{
    // 这是集合中的一个证明
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-rdfc-2022",
    "created": "2020-11-05T19:23:24Z",
    "verificationMethod": "https://ldi.example/issuer/1#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG",
    "proofPurpose": "assertionMethod",
    "proofValue": "z4oey5q2M3XKaxup3tmzN4DRFTLVqpLMweBrSxMY2xHX5XTYVQeVbY8nQAVHMrXFkXJpmEcqdoDwLWxaqA3Q1geV6"
  }, {
    // 这是集合中的另一个证明
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-rdfc-2022",
    "created": "2020-11-05T13:08:49Z",
    "verificationMethod": "https://pfps.example/issuer/2#z6MkGskxnGjLrk3gKS2mesDpuwRBokeWcmrgHxUXfnncxiZP",
    "proofPurpose": "assertionMethod",
    "proofValue": "z5QLBrp19KiWXerb8ByPnAZ9wujVFN8PDsxxXeMoyvDqhZ6Qnzr5CG9876zNht8BpStWi8H2Mi7XCY3inbLrZrm95"
  }]
}
        

证明链

证明链在需要对相同数据进行多方签名且签名顺序重要的情况下非常有用,例如公证人在文档上对已创建的证明进行副签的情况。证明链需要保留证明的顺序,通过为至少一个证明提供一个`id`(例如 UUID [[?RFC9562]])以及另一个包含`previousProof`值的证明来标识前一个证明。

     {
       "@context": [
         {"myWebsite": "https://vocabulary.example/myWebsite"},
         "https://w3id.org/security/data-integrity/v2"
     ],
       "myWebsite": "https://hello.world.example/",
       "proof": [{
         // 'id' 值标识此特定证明
         "id": "urn:uuid:60102d04-b51e-11ed-acfe-2fcd717666a7",
         "type": "DataIntegrityProof",
         "cryptosuite": "eddsa-rdfc-2022",
         "created": "2020-11-05T19:23:42Z",
         "verificationMethod": "https://ldi.example/issuer/1#z6MkjLrk3gKS2nnkeWcmcxiZPGskmesDpuwRBorgHxUXfxnG",
         "proofPurpose": "assertionMethod",
         "proofValue": "zVbY8nQAVHMrXFkXJpmEcqdoDwLWxaqA3Q1geV64oey5q2M3XKaxup3tmzN4DRFTLVqpLMweBrSxMY2xHX5XTYVQe"
       }, {
         "type": "DataIntegrityProof",
         "cryptosuite": "eddsa-rdfc-2022",
         "created": "2020-11-05T21:28:14Z",
         "verificationMethod": "https://pfps.example/issuer/2#z6MkGskxnGjLrk3gKS2mesDpuwRBokeWcmrgHxUXfnncxiZP",
         "proofPurpose": "assertionMethod",
         "proofValue": "z6Qnzr5CG9876zNht8BpStWi8H2Mi7XCY3inbLrZrm955QLBrp19KiWXerb8ByPnAZ9wujVFN8PDsxxXeMoyvDqhZ",
         // 'previousProof' 值标识在此之前验证的证明
         "previousProof": "urn:uuid:60102d04-b51e-11ed-acfe-2fcd717666a7"
       }]
     }
             

证明图

在保护文档中的数据时,明确划分被保护的数据非常重要,这包括文档中表达的所有图,除了包含与保护机制相关的数据的图,该图被称为证明图。创建这种分离使处理算法能够确定性地保护和验证受保护的文档。

在添加[=数据完整性证明=]之前,输入文档中包含的信息以一个或多个图的形式表达。为了确保来自不同[=数据完整性证明=]的信息不会意外混合,使用[=证明图=]的概念来封装每个[=数据完整性证明=]。文档中与`proof`属性相关联的每个值标识一个单独的图,有时称为命名图,类型为ProofGraph,其中包含一个[=数据完整性证明=]。

使用这些图在执行 JSON-LD 处理时具有具体效果,因为这能正确地将一个图中表达的语句与另一个图中的语句分开。限制其处理为其他媒体类型(如 JSON、YAML 或 CBOR)的实现者需要注意,如果将一个文档中的数据与另一个文档中的数据合并(例如两个文档中`id`值字符串相同),可能会出现问题。重要的是不要合并看似具有相似属性的对象,除非这些对象具有`id`属性和/或使用全局标识符类型(如 URL),否则无法判断两个对象是否表达了同一实体的信息。

证明目的

描述其目的的证明有助于防止被误用于其他目的。[=证明目的=]使[=验证者=]能够了解证明创建者的意图,从而防止消息被意外滥用于其他目的。例如,一个仅用于断言(可能意图广泛共享)的签名消息被滥用为用于身份验证服务或执行某些操作(例如调用某种能力)的消息。

需要注意的是,[=证明目的=]与[[[?RFC7517]]]中的`key_ops`限制、[[[?WEBCRYPTOAPI]]]中的`KeyUsage`限制以及[[[?RFC5280]]]中的限制是不同的机制。[=证明目的=]表达了为什么创建[=证明=]及其预期的使用领域,而上述提到的其他机制旨在限制私钥的用途。[=证明目的=]会随[=证明=]一起“传递”,而密钥限制则不会。

以下是常用[=证明目的=]值的列表。

authentication
表示给定的证明仅用于身份验证协议的目的。
assertionMethod
表示证明只能用于进行断言,例如签署可验证凭证
keyAgreement
表示证明用于密钥协商协议,例如流行加密库中使用的椭圆曲线 Diffie-Hellman 密钥协商。
capabilityDelegation
表示证明只能用于委派能力。有关更多详细信息,请参阅授权能力[[?ZCAP]]规范。
capabilityInvocation
表示证明只能用于调用能力。有关更多详细信息,请参阅授权能力[[?ZCAP]]规范。

资源完整性

当[=符合规范的安全文档=]中包含指向外部资源的链接时,了解自证明创建以来所标识的资源是否已更改是很有必要的。这适用于远程检索的外部资源以及[=验证者=]可能拥有的资源本地缓存副本。

为了确认[=符合规范的安全文档=]引用的资源自文档被保护以来未发生更改,实现者可以在包含`id`属性的任何对象中包含一个名为`digestMultibase`的属性。如果存在,`digestMultibase`值必须是单个字符串值,或字符串值的列表,其中每个值都是Multibase编码的Multihash值。

JSON-LD 上下文作者应将`digestMultibase`添加到将用于引用其他资源的文档的上下文中,并包含相关的密码学摘要。例如,[[[?VC-DATA-MODEL-2.0]]]基础上下文(`https://www.w3.org/ns/credentials/v2`)包含`digestMultibase`属性。

以下是一个受资源完整性保护的对象示例:

   {
     ...
     "image": {
       "id": "https://university.example.org/images/58473",
       "digestMultibase": "zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
     },
     ...
   }
           

建议实现者参考适当的资源,例如 FIPS 180-4 安全哈希标准 商业国家安全算法套件 2.0,以确保选择适合其用例的哈希算法。

上下文和词汇表

执行 JSON-LD 处理的实现必须将以下 JSON-LD 上下文 URL 视为已解析,其中解析后的文档与下方对应的哈希值匹配:

上下文 URL 和哈希值
URL: https://w3id.org/security/data-integrity/v2
SHA2-256 摘要:
URL: https://w3id.org/security/multikey/v1
SHA2-256 摘要:
URL: https://w3id.org/security/jwk/v1
SHA2-256 摘要:

可以通过运行以下命令(将 `<DOCUMENT_URL>` 替换为适当的值)在现代类 UNIX 操作系统的命令行界面中确认上述密码学摘要:`curl -sL -H "Accept: application/ld+json" <DOCUMENT_URL> | openssl dgst -sha256`

上述 JSON-LD 上下文解析到的安全词汇表术语位于 https://w3id.org/security# 命名空间中。也就是说,该词汇表中的所有安全术语的形式为 `https://w3id.org/security#TERM`,其中 `TERM` 是术语的名称。

执行 RDF 处理的实现必须将词汇表 URL 的 JSON-LD 序列化视为已解引用,其中解引用的文档与下方对应的哈希值匹配。

除了本规范定义的安全术语外,https://w3id.org/security# 命名空间还包括 [[[CID]]] [[CID]] 规范中定义的术语,并在上述上下文文件中进行了相应的映射。

在解引用 https://w3id.org/security# URL 时,返回数据的媒体类型取决于 HTTP 内容协商。具体如下:

媒体类型 描述和哈希值
application/ld+json JSON-LD 格式的词汇表 [[?JSON-LD11]]。

SHA2-256 摘要:
text/turtle Turtle 格式的词汇表 [[?TURTLE]]。

SHA2-256 摘要:
text/html HTML+RDFa 格式的词汇表 [[?HTML-RDFA]]。

SHA2-256 摘要:

可以通过运行以下命令(将 `<MEDIA_TYPE>` 和 `<DOCUMENT_URL>` 替换为适当的值)在现代类 UNIX 操作系统的命令行界面中确认上述密码学摘要:`curl -sL -H "Accept: <MEDIA_TYPE>" <DOCUMENT_URL> | openssl dgst -sha256`

特定应用词汇表和规范的作者应确保其 JSON-LD 上下文和词汇表文件是永久可缓存的,可以使用上述缓存方法或功能等效的机制。

实现者可以在开发过程中从网络加载特定应用的 JSON-LD 上下文文件,但在生产环境中,应该永久缓存用于[=符合规范的安全文档=]的 JSON-LD 上下文文件,以提高其安全性和隐私特性。可以通过上述缓存方法或功能等效的机制实现处理速度目标。

某些应用程序(如数字钱包)能够保存任意可验证凭证或其他受数据完整性保护的文档,这些文档可能来自任何发行者并使用任何上下文。在生产环境中,这些应用可能需要加载外部链接资源(如 JSON-LD 上下文文件)。这预计会随着时间的推移增加用户选择、可扩展性和生态系统的去中心化升级。此类应用的作者被建议阅读本文档的安全性和隐私部分以获取进一步的考虑。

有关处理 JSON-LD 上下文和词汇表的更多信息,请参阅可验证凭证 v2.0:基础上下文可验证凭证 v2.0:词汇表

验证上下文

确保消费应用明确批准其将处理的输入文档的类型及其语义是必要的。不检查 JSON-LD 上下文值是否与已知的良好值匹配可能会导致安全漏洞,因为它们传递的语义可能存在差异。应用程序必须使用第 [[[#context-validation]]] 节中的算法,或实现等效保护的算法,以验证[=符合规范的安全文档=]中的上下文。上下文验证必须在运行第 [[[#verify-proof]]] 节或第 [[[#verify-proof-sets-and-chains]]] 节中的适用算法之后运行。

虽然第 [[[#context-validation]]] 节中描述的算法提供了一种检查上下文值的方法,以及一种安全处理未知上下文值的可选方法,但实现者可以使用替代方法或不同的步骤顺序,只要它们提供相同的保护。

例如,如果不需要进行 JSON-LD 处理,则应用程序可以遵循任何可信文档中提供的指导,而不是执行此检查,以正确理解该类型文档的语义。

另一种方法是配置应用程序使用 JSON-LD 上下文加载器(有时称为文档加载器),仅使用已批准上下文文件的本地副本。这将保证上下文文件及其密码学哈希永远不会更改,实际上与第 [[[#context-validation]]] 节中的算法结果相同。

另一种等效于第 [[[#context-validation]]] 节算法的替代方法是,应用程序维护一个已知上下文 URL 及其关联的批准密码学哈希的列表,而不存储每个上下文文件的本地副本。这将允许从网络安全加载这些上下文,而不会影响应用程序的安全预期。

另一种有效的方法是,发送应用程序通过协议(如请求可验证展示)将文档“压缩”为接收应用程序请求的内容,省略用于保护原始文档的额外发送方特定上下文值。只要密码学套件的验证算法提供成功的验证结果,这种转换是有效的,并将导致先前由省略的上下文压缩的术语扩展为完整的 URL。例如,先前基于发送方提供的上下文压缩为 `foo` 的术语(如 `https://ontology.example/v1`)将被“扩展”为类似 `https://ontology.example#foo` 的 URL,然后在接收应用程序应用 JSON-LD 压缩算法时再次“压缩”为相同的 URL。

上下文注入

`@context` 属性用于确保在处理本规范中的术语时,所有实现都使用相同的语义。例如,当处理诸如 `type` 这样的属性及其值(如 `DataIntegrityProof`)时,这一点尤为重要。

当应用程序保护文档时,如果文档中未提供 `@context` 属性,或者文档中使用的数据完整性术语未通过现有的 `@context` 属性值进行映射,实现应注入或追加一个 `@context` 属性,其值为 `https://w3id.org/security/data-integrity/v2`,或包含至少相同声明的一个或多个上下文,例如可验证凭证数据模型 v2.0 上下文(`https://www.w3.org/ns/credentials/v2`)。

不打算使用 JSON-LD 处理的实现可以选择不在文档的顶层包含 `@context` 声明。然而,如果未包含 `@context` 声明,则不得进行与本规范或相应密码套件相关的扩展(如添加新属性)。

无损保护数据

HTML 处理器在检测到可恢复的错误时会继续处理。JSON-LD 处理器以类似方式运行。这种设计理念旨在确保开发人员可以仅使用 JSON-LD 语言中对其有用的部分,而不会因处理器对开发者可能不重要的内容抛出错误。这种设计导致 JSON-LD 处理器在遇到未定义的术语等情况时不会抛出错误,而是警告开发人员。

当从 JSON-LD 转换为 RDF 数据集(例如在规范化文档时 [[?RDF-CANON]]),未定义的术语和相对 URL 可能会被静默丢弃。当值被丢弃时,它们不会受到数字证明的保护。这会导致预期的不匹配,开发人员可能误以为某些数据已被保护,但实际上并未保护,因为未抛出错误。本规范要求在执行 JSON-LD 转换时,任何可恢复的数据丢失都必须导致错误,以避免开发人员的安全预期不匹配。

使用 JSON-LD 处理的实现(例如 RDF 数据集规范化 [[?RDF-CANON]])在 JSON-LD 处理器丢弃数据时(例如在输入文档中检测到未定义的术语时),必须抛出错误,错误类型应为 `DATA_LOSS_DETECTION_ERROR`。

同样,由于[=符合规范的安全文档=]可以从一个安全域传输到另一个安全域,处理[=符合规范的安全文档=]的[=符合规范的处理器=]不能假定文档的任何特定基础 URL。在反序列化为 RDF 时,实现必须确保基础 URL 设置为 null。

数据类型

本节定义了本规范使用的数据类型。

`cryptosuiteString` 数据类型

本规范将密码套件标识符编码为可枚举的字符串,这在需要高效编码此类字符串的过程中(如压缩算法)非常有用。在支持字符串值数据类型的环境中(如 RDF [[?RDF-CONCEPTS]]),密码标识符内容通过数据类型设置为 `https://w3id.org/security#cryptosuiteString` 的文字值表示。

`cryptosuiteString` 数据类型定义如下:

表示此数据类型的 URL
`https://w3id.org/security#cryptosuiteString`
词汇空间
所有密码套件字符串的集合,这些字符串使用美国信息交换标准代码 [[ASCII]] 表示,并由所有数据完整性密码套件规范定义。
值空间
通过 `cryptosuite` 属性(在第 [[[#dataintegrityproof]]] 节中定义)表示的所有密码套件类型的集合。
词汇到值的映射
词汇空间中的任何元素都映射为解析为内部表示的结果,该表示唯一标识所有可能的密码套件类型中的一种。
规范映射
值空间中的任何元素都映射为词汇空间中的相应字符串。

与关联数据的关系

“关联数据”一词用于描述一种推荐的最佳实践,用于通过使用标准(如 URL)标识事物及其属性,在 Web 上公开、共享和连接信息。当信息以关联数据的形式呈现时,可以轻松发现其他相关信息,并轻松将新信息与之关联。关联数据以去中心化的方式具有可扩展性,从而大大降低了大规模集成的障碍。

随着关联数据在各种应用中的使用增加,需要能够验证关联数据文档的真实性和完整性。本规范通过使用数学证明为数据文档添加了身份验证和完整性保护,同时不牺牲关联数据的特性,如可扩展性和可组合性。

虽然本规范提供了对关联数据进行数字签名的机制,但使用关联数据并不是获得本规范所提供某些优势的必要条件。

与可验证凭证的关系

实现本规范的密码套件可用于保护可验证凭证可验证展示。针对这些用例的实现者需要注意,在处理这些类型的文档时,可能需要进行额外的检查。

在某些用例中,确保证明中使用的[=验证方法=]与`issuer`(发行者)或`holder`(持有者)相关联是很重要的。这种关联可以通过验证证明的[=验证方法=]的`controller`属性值是否与标识`issuer``holder`的 URL 值匹配来检查,并确保验证方法在符合证明目的的验证关系下表达。这种关联表明`issuer``holder`是用于验证证明的[=验证方法=]的控制者。

文档作者和实现者应了解证明的有效期(通过`created``expires`属性表示)与凭证的有效期(通过`validFrom``validUntil`属性表示)之间的区别。这些属性有时可能表示相同的有效期,但在其他情况下可能不一致。在验证证明时,确保感兴趣的时间(可能是当前时间或其他时间)在证明的有效期内(即在`created``expires`之间)是很重要的。在验证可验证凭证时,确保感兴趣的时间在凭证的有效期内(即在`validFrom``validUntil`之间)也很重要。未能验证证明凭证的有效期可能导致接受本应被拒绝的数据。

最后,实施者还应了解可验证凭证的吊销信息与[=验证方法=]的吊销过期时间之间的区别。[=验证方法=]的吊销过期时间通过`revocation`和`expires`属性表示,与诸如[=私钥=]被泄露或过期等事件相关,并可能提供时间信息,这些信息可能揭示控制者的细节,例如其安全实践或可能被泄露的时间。可验证凭证的吊销信息通过`credentialStatus`属性表示,与个人失去可验证凭证授予的特权等事件相关,并且不会提供时间信息,从而增强隐私性。

密码套件

[=数据完整性证明=] 旨在让开发者易于使用,因此尽量减少生成证明时需要记住的信息量。通常,开发者只需提供 [=密码套件=] 的名称(例如 `eddsa-rdfc-2022`)即可开始创建证明。这些 [=密码套件=] 通常由具备必要密码学培训的人创建和审核,以确保使用安全的密码学原语组合。本节规定了编写密码套件规范的要求。

所有数据完整性密码套件规范的要求如下:

[=密码套件实例=] 使用 [=密码套件实例化算法=] 实例化,并以实现特定的方式提供给算法。实现可以使用 [[[?VC-EXTENSIONS]]] 文档来发现已知的 [=密码套件实例化算法=]。

数据完整性证明

许多 [=密码套件=] 在表达 [=数据完整性证明=] 时遵循相同的基本模式。本节规定了这种通用设计模式,一种称为 `DataIntegrityProof` 的 [=密码套件=] 类型,通过重用设计原语和源代码减少了编写和实现 [=密码套件=] 的负担。

在指定使用此设计模式的 [=密码套件=] 时,`proof` 值采用以下形式:

type
`type` 属性必须包含 字符串 `DataIntegrityProof`。
cryptosuite
`cryptosuite` 属性的值必须是标识 [=密码套件=] 的 字符串。如果处理环境支持 字符串 子类型,则 `cryptosuite` 值的子类型必须是 `https://w3id.org/security#cryptosuiteString`。
proofValue
`proofValue` 属性必须按照第 [[[#proofs]]] 节的规定使用。

[=密码套件=] 设计者必须使用第 [[[#proofs]]] 节中定义的强制性 `proof` 值属性,并可以定义特定于其密码套件的其他属性。

从 2012 年到 2020 年,数据完整性密码套件中常见的一种设计模式是使用 `type` 属性为密码套件建立特定类型; Ed25519Signature2020 密码套件 就是其中一个规范。这种模式增加了密码套件实现的负担,每个新密码套件都需要指定一个新的 JSON-LD 上下文,从而导致开发者体验不佳。 2020 年出现了一种简化的设计模式,开发者只需包含一个 JSON-LD 上下文即可支持所有现代密码套件。这鼓励了更多现代密码套件(例如 EdDSA 密码套件 [[?DI-EDDSA]] 和 ECDSA 密码套件 [[?DI-ECDSA]])基于本节描述的简化模式构建。

为了改善开发者体验,创建新数据完整性密码套件规范的作者应使用现代模式——其中 `type` 设置为 `DataIntegrityProof`;`cryptosuite` 属性携带密码套件的标识符;任何特定于密码套件的密码学数据都被封装(即,不直接暴露为应用层数据)在 `proofValue` 中。已知遵循此模式的密码套件规范列表可在 可验证凭证扩展的安全机制部分 文档中找到。

算法

以下定义的算法适用于表示为JSON 对象的文档。本规范遵循[[[JSON-LD11-API]]]规范,将 JSON 对象表示为映射。一个不安全数据文档是一个映射,其中不包含任何证明值。一个输入文档是尚未添加当前证明的映射,但它可能包含由先前过程添加的证明值。一个安全数据文档是一个包含一个或多个证明值的映射

实现者可以在以下算法的基础上实现合理的默认值和保护措施,以帮助减轻开发者错误、过度的资源消耗、新发现的攻击模型(针对这些模型有特定保护)以及其他改进。以下提供的算法是实现互操作性的最低要求,建议开发者加入其他措施,以构建更安全、更高效的生态系统。

处理模型

本节描述了[=符合规范的处理器=]及其特定于应用程序的软件所使用的处理模型。当软件需要确保信息具有防篡改性时,它会执行以下步骤:

  1. 软件将信息组织成一个文档,例如 JSON 或 JSON-LD 文档。
  2. 如果文档是 JSON-LD 文档,软件会选择一个或多个 JSON-LD 上下文,并通过 `@context` 属性表达它们。
  3. 软件选择一个或多个满足用例需求的密码学套件,例如提供完整、选择性或不可链接披露的套件,并使用可接受的密码学密钥材料。
  4. 软件使用第 [[[#add-proof]]] 节或第 [[[#add-proof-set-chain]]] 节中提供的适用算法,添加一个或多个证明。

当软件需要使用通过本规范描述的机制传输给它的信息时,它会执行以下步骤:

  1. 软件将接收到的数据转换为一个可以被第 [[[#verify-proof]]] 节或第 [[[#verify-proof-sets-and-chains]]] 节中提供的适用算法理解的文档。
  2. 软件使用 JSON Schema 或等效机制验证接收到的文档是否遵循应用程序使用的预期模式。
  3. 软件使用第 [[[#verify-proof]]] 节或第 [[[#verify-proof-sets-and-chains]]] 节中提供的适用算法,验证接收到文档的完整性。
  4. 如果文档是 JSON-LD 文档,软件使用第 [[[#context-validation]]] 节中提供的算法或提供等效保护的算法,验证文档中使用的所有 JSON-LD 上下文值。

添加证明

以下算法指定了如何将数字证明添加到[=输入文档=]中,然后可以用来验证输出文档的真实性和完整性。所需输入包括[=输入文档=](映射 |inputDocument|)、[=密码套件实例=](结构体 |cryptosuite|)以及一组选项(映射 |options|)。输出是[=符合规范的安全文档=](映射)或错误。每当此算法对字符串进行编码时,必须使用 UTF-8 编码。

  1. 令 |proof| 为调用 |cryptosuite| 中指定的 [=密码套件实例/createProof=] 算法的结果,传入 |inputDocument| 和 |options| 作为参数。如果算法产生错误,则必须传播该错误,并应传递错误类型。
  2. 如果 |proof| 的 |type|、|verificationMethod| 和 |proofPurpose| 中的一个或多个值未设置,则必须引发错误,并应传递错误类型为 PROOF_GENERATION_ERROR
  3. 如果 |options| 中的 |domain| 结构项 非空,则其值必须等于 |proof| 的 |domain|,否则必须引发错误,并应传递错误类型为 PROOF_GENERATION_ERROR
  4. 如果 |options| 中的 |challenge| 结构项 非空,则其值必须等于 |proof| 的 |challenge|,否则必须引发错误,并应传递错误类型为 PROOF_GENERATION_ERROR
  5. 令 |securedDataDocument| 为 |inputDocument| 的副本。
  6. 将 |securedDataDocument| 的 |proof| 设置为 |proof| 的值。
  7. 返回 |securedDataDocument| 作为[=符合规范的安全文档=]。

添加证明集/链

以下算法指定了如何从包含证明或证明集/链的受保护文档开始,逐步向证明集或证明链中添加证明。所需输入包括[=符合规范的安全文档=](映射 |securedDocument|)、[=密码套件=]([=密码套件实例=] |suite|)以及一组选项(映射 |options|)。输出是新的[=符合规范的安全文档=](映射)。每当此算法对字符串进行编码时,必须使用 UTF-8 编码。

  1. 令 |proof| 设置为 |securedDocument| 的 |proof|。令 |allProofs| 为一个空列表。如果 |proof| 是一个列表,则将 |proof| 的所有元素复制到 |allProofs|。如果 |proof| 是一个对象,则将该对象的副本添加到 |allProofs|。
  2. 令 |inputDocument| 为 |securedDocument| 的副本,并移除 |proof| 属性。令 |output| 为 |inputDocument| 的副本。
  3. 令 |matchingProofs| 为一个空列表。
  4. 如果 |options| 中的 `previousProof` 结构项 是字符串,则将 |allProofs| 中 `id` 属性与 `previousProof` 匹配的元素添加到 |matchingProofs|。如果 |allProofs| 中不存在 `id` 等于 `previousProof` 的证明,则必须引发错误,并应传递错误类型为 PROOF_GENERATION_ERROR
  5. 如果 |options| 中的 `previousProof` 结构项 是数组,则将 |allProofs| 中 `id` 属性与数组中任一元素匹配的元素添加到 |matchingProofs|。如果 `previousProof` 列表 中的任何元素的 `id` 属性与 |allProofs| 中的任何元素的 `id` 属性不匹配,则必须引发错误,并应传递错误类型为 PROOF_GENERATION_ERROR
  6. 将 |inputDocument| 的 |proof| 设置为 |matchingProofs|。

    此步骤添加对[=命名图=]的引用,并添加[=证明图=]中包含的所有声明的副本。此步骤至关重要,因为它在应用当前证明之前将任何匹配的证明“绑定”到文档。文档的 |proof| 值将在此算法的后续步骤中更新。

  7. 运行第 [[[#add-proof]]] 节算法的第 1 至 6 步,传入 |inputDocument|、|suite| 和 |options|。如果未引发异常,则将生成的 |proof| 值追加到 |allProofs|;否则,引发异常。
  8. 将 |output| 的 |proof| 设置为 |allProofs| 的值。
  9. 返回 |output| 作为新的[=符合规范的安全文档=]。

验证证明

以下算法指定了如何通过验证数字证明来检查[=符合规范的安全文档=]的真实性和完整性。该算法的输入包括:

|mediaType|
一个 [=MIME type|media type=],定义于 [[MIMESNIFF]]
|documentBytes|
一个 字节序列,其媒体类型为 |mediaType|
|cryptosuite|
一个 [=密码套件=]实例
|expectedProofPurpose|
一个可选的 字符串,用于确保 |proof| 是由证明创建者为验证者预期的原因生成的。常见值请参阅 [[[#proof-purposes]]]
|domain|
一个可选的 字符串集合,由证明创建者用于将证明锁定到特定的安全域,并由验证者用于确保证明未跨不同的安全域使用
|challenge|
一个可选的 字符串 [=挑战=],由验证者用于确保攻击者未重放先前创建的证明

该算法返回一个 验证结果,即一个 结构体,其结构项包括:

verified
`true` 或 `false`
verifiedDocument
如果 [=verification result/verified=] 为 `false`,则为 Null;否则为一个 [=输入文档=]
mediaType
如果 [=verification result/verified=] 为 `false`,则为 Null;否则为一个 [=MIME type|media type=],可能包含 [=MIME type/parameters=]
warnings
一个 列表,包含 [=问题详情=],默认为空 列表
errors
一个 列表,包含 [=问题详情=],默认为空 列表

当某一步骤说明“必须引发错误”时,这意味着必须返回一个 [=验证结果=],其 [=verification result/verified=] 值为 `false`,且 [=verification result/errors=] 列表非空。

  1. 令 |securedDocument:map| 为运行将 JSON 字节解析为 Infra 值 的结果,输入为 |documentBytes|。
  2. 如果 |securedDocument| 不是 映射 或 |securedDocument|.|proof| 不是 映射,则必须引发错误,并应传递错误类型为 PARSING_ERROR
  3. 令 |proof:map| 为 |securedDocument|.|proof|。
  4. 如果 |proof|.|type|、|proof|.|verificationMethod| 和 |proof|.|proofPurpose| 中的一个或多个值不存在,则必须引发错误,并应传递错误类型为 PROOF_VERIFICATION_ERROR
  5. 如果提供了 |expectedProofPurpose|,且其值与 |proof|.|proofPurpose| 不匹配,则必须引发错误,并应传递错误类型为 PROOF_VERIFICATION_ERROR
  6. 如果提供了 |domain|,且其值不包含与 |proof|.|domain| 相同的 字符串(将单个 字符串 视为仅包含该 字符串集合),则必须引发错误,并应传递错误类型为 INVALID_DOMAIN_ERROR
  7. 如果提供了 |challenge|,且其值与 |proof|.|challenge| 不匹配,则必须引发错误,并应传递错误类型为 INVALID_CHALLENGE_ERROR
  8. 令 |cryptosuiteVerificationResult| 为运行 |cryptosuite|.[=密码套件实例/verifyProof=] 算法的结果,输入为 |securedDocument|。
  9. 返回一个 [=验证结果=],其 结构项 包括:
    [=verified=]
    |cryptosuiteVerificationResult|.|verified|
    [=verifiedDocument=]
    |cryptosuiteVerificationResult|.|verifiedDocument|
    [=mediaType=]
    |mediaType|

验证证明集和链

在[=证明集=]或[=证明链=]中,[=符合规范的安全文档=]具有一个 `proof` 属性,其中包含[=证明=]的列表 (|allProofs|)。以下算法提供了一种方法,用于通过验证 |allProofs| 中的每个证明来检查[=符合规范的安全文档=]的真实性和完整性。如果只需要验证 |allProofs| 中的部分证明,也可以采用其他方法。但如果选择验证部分证明,需要注意,任何包含 `previousProof` 的证明只有在其引用的证明也被验证通过时,才能被视为已验证。

所需输入是一个[=符合规范的安全文档=] (|securedDocument|)。生成一个与 |allProofs| 中每个证明对应的[=验证结果=]列表,并返回一个单一的综合[=验证结果=]作为输出。实现可以选择返回其他[=验证结果=]和/或任何其他元数据,作为综合[=验证结果=]的补充。

  1. 将 |allProofs| 设置为 |securedDocument|.|proof|。
  2. 将 |verificationResults| 设置为空列表。
  3. 对 |allProofs| 中的每个 |proof|,执行以下步骤:
    1. 将 |matchingProofs| 设置为空列表。
    2. 如果 |proof| 包含 `previousProof` 属性且其值是字符串,则将 |allProofs| 中 `id` 属性值与 `previousProof` 值匹配的元素添加到 |matchingProofs|。如果 |allProofs| 中不存在 `id` 值等于 `previousProof` 的证明,则必须引发错误,并应传递错误类型为 PROOF_VERIFICATION_ERROR。如果 `previousProof` 属性是列表,则将 |allProofs| 中 `id` 属性值与列表中任一元素匹配的元素添加到 |matchingProofs|。如果 `previousProof` 列表 中的任何元素的 `id` 属性值与 |allProofs| 中的任何元素的 `id` 属性值不匹配,则必须引发错误,并应传递错误类型为 PROOF_VERIFICATION_ERROR
    3. 将 |inputDocument| 设置为 |securedDocument| 的副本,并移除 `proof` 值,然后将 |inputDocument|.|proof| 设置为 |matchingProofs|。

      请参阅第 [[[#add-proof-set-chain]]] 节第 6 步中的注释,了解此步骤保护的文档属性和先前的证明。

    4. 运行第 [[[#verify-proof]]] 节算法的第 4 至 8 步,输入为 |inputDocument|;如果未引发异常,则将 |cryptosuiteVerificationResult| 追加到 |verificationResults|。
  4. 将 |successfulVerificationResults| 设置为空列表。
  5. 将 |combinedVerificationResult| 设置为空结构体。将 |combinedVerificationResult|.|status| 设置为 `true`,|combinedVerificationResult|.|document| 设置为 `null`,|combinedVerificationResult|.|mediaType| 设置为 `null`。
  6. 对 |verificationResults| 中的每个 |cryptosuiteVerificationResult|:
    1. 如果 |cryptosuiteVerificationResult|.|verified| 为 `false`,将 |combinedVerificationResult|.|verified| 设置为 `false`。
    2. 否则,将 |combinedVerificationResult|.|document| 设置为 |cryptosuiteVerificationResult|.|verifiedDocument|,将 |combinedVerificationResult|.|mediaType| 设置为 |cryptosuiteVerificationResult|.|mediaType|,并将 |cryptosuiteVerificationResult| 追加到 |successfulVerificationResults|。
  7. 如果 |combinedVerificationResult|.|status| 为 `false`,将 |combinedVerificationResult|.|document| 设置为 `null`,将 |combinedVerificationResult|.|mediaType| 设置为 `null`。
  8. 返回 |combinedVerificationResult| 和 |successfulVerificationResults|。

上下文验证

以下算法提供了一种机制,用于确保应用程序在执行文档中输入的业务规则之前,理解与文档关联的上下文。有关此算法的更多原理,请参阅第 [[[#validating-contexts]]] 节。此算法的输入包括文档(映射 |inputDocument|)、一组已知的 JSON-LD 上下文(列表 |knownContext|)以及在检测到未知上下文时是否重新压缩的布尔值(布尔值 |recompact|)。

此算法返回一个 上下文验证结果,即一个 结构体,其结构项包括:

validated
`true` 或 `false`
validatedDocument
如果 [=context validation result/validated=] 为 `false`,则为 Null;否则为一个 [=输入文档=]
warnings
一个 列表,包含 [=问题详情=],默认为空 列表
errors
一个 列表,包含 [=问题详情=],默认为空 列表

上下文验证算法如下:

  1. 将 |result|.|validated| 设置为 `false`,|result|.|warnings| 设置为空列表,|result|.|errors| 设置为空列表,|compactionContext| 设置为空列表;并克隆 |inputDocument| 到 |result|.|validatedDocument|。
  2. 令 |contextValue| 为 |result|.|validatedDocument| 的 `@context` 属性的值,该值可能未定义。
  3. 如果 |contextValue| 不深度等于 |knownContext|,|result|.|validatedDocument| 中的任何子树包含 `@context` 属性,或 |contextValue| 中的任何 URI 解引用到的 JSON-LD 上下文文件与已知的良好值或密码学哈希不匹配,则执行以下适用操作:
    1. 如果 |recompact| 为 `true`,将 |result|.|validatedDocument| 设置为运行 JSON-LD 压缩算法 的结果,输入为 |inputDocument| 和 |knownContext|。如果压缩失败,向 |result|.|errors| 添加至少一个错误。
    2. 如果 |recompact| 不为 `true`,向 |result|.|errors| 添加至少一个错误。
  4. 如果 |result|.|errors| 为空,则将 |result|.|validated| 设置为 `true`;否则,将 |result|.|validated| 设置为 `false`,并从 |result| 中移除 |document| 属性。
  5. 返回 |result| 的值。

实现可以包括其他警告或错误,以强制执行特定于实现或特定用例的进一步验证规则。

处理错误

本规范中描述的算法以及各种密码学套件规范中描述的算法会抛出特定类型的错误。实现者可能会发现将这些错误传递给其他库或软件系统很有用。本节提供了错误的特定 URL 和描述,以便实现本规范所描述技术的生态系统在发生错误时能够更有效地互操作。

当通过 HTTP 接口公开这些错误时,实施者应使用 [[RFC9457]] 将错误数据结构编码为 问题详情 映射。如果使用 [[RFC9457]]:

PROOF_GENERATION_ERROR
生成证明的请求失败。请参阅第 [[[#add-proof]]] 节和第 [[[#add-proof-set-chain]]] 节。
PROOF_VERIFICATION_ERROR
在验证证明时遇到错误。请参阅第 [[[#verify-proof]]] 节。
PROOF_TRANSFORMATION_ERROR
在转换过程中遇到错误。
INVALID_DOMAIN_ERROR
证明中的 `domain` 值与预期值不匹配。请参阅第 [[[#verify-proof]]] 节。
INVALID_CHALLENGE_ERROR
证明中的 `challenge` 值与预期值不匹配。请参阅第 [[[#verify-proof]]] 节。

安全注意事项

以下部分描述了开发人员在实现本规范时需要注意的安全事项,以创建安全的软件。

密码套件的版本控制

密码学通过使用密钥来保护信息。掌握必要的密钥可以轻松地访问某些信息。同样的信息也可以通过计算上困难的暴力破解尝试来访问。所有现代密码学都要求这种计算上困难的方法在时间上保持困难,但科学和数学的突破可能会改变这一点。也就是说,密码学是有时效性的

本规范通过假设当前使用的任何密码学方法在未来很可能会被破解,来规划所有密码学方法的过时性。软件系统必须能够随着时间的推移更改所使用的密码学方法,以继续保护信息安全。这些更改可能涉及增加所需密钥的大小或修改所使用的密码学原语。然而,一些密码学参数的组合可能实际上会降低安全性。基于这些假设,系统需要能够区分不同的安全密码学参数组合,也称为密码套件。在标识或版本化密码套件时,可以采用以下几种方法:参数、数字和日期。

参数化版本控制指定了密码套件中使用的特定密码学参数。例如,可以使用类似于 `RSASSA-PKCS1-v1_5-SHA1` 的标识符。这种方案的好处是,受过良好训练的密码学家可以通过标识符确定所有使用的参数。然而,这种方案的缺点是,大多数使用这些标识符的人并未受过良好训练,因此无法理解上述标识符代表的是一个不再安全的密码套件。此外,这种知识的缺乏可能会导致软件开发人员泛化密码套件标识符的解析,使得任何密码学原语的组合都变得可接受,从而降低安全性。理想情况下,密码套件应在软件中实现为特定的、可接受的密码学参数配置文件。

数字版本控制可能会指定主版本号和次版本号,例如 `1.0` 或 `2.1`。数字版本控制传达了一个特定的顺序,并暗示较高的版本号比较低的版本号更强大。这种方法的好处是,它消除了不太专业的开发人员可能无法理解的复杂参数,提供了一个更简单的模型,表明可能需要升级。然而,这种方法的缺点是,升级是否必要并不明确,因为软件版本号的增加通常不需要升级软件即可继续运行。这可能导致开发人员认为他们使用的特定版本是安全的,而实际上并非如此。理想情况下,使用密码套件的开发人员应定期审查这些套件以确保其持续安全。

基于日期的版本控制为特定密码套件指定了一个发布日期。日期(例如年份)的好处是,开发人员可以立即判断日期是相对较旧还是较新。看到一个旧日期可能会促使开发人员寻找更新的密码套件,而参数化或基于数字的版本控制方案可能不会。然而,基于日期的版本控制的缺点是,一些密码套件可能在 5-10 年内不会过期,这可能会促使开发人员寻找更新的密码套件,但却找不到更新的版本。尽管这可能会带来不便,但这种不便会导致更安全的生态系统行为。

保护应用程序开发人员

现代密码学算法提供了许多可调参数和选项,以确保算法能够满足不同用例的多样化需求。例如,嵌入式系统的处理能力和内存环境有限,可能无法生成给定算法的最强数字签名。其他环境(如金融交易系统)可能只需要在交易期间保护数据一天,而其他环境可能需要保护数据数十年。为了满足这些需求,密码学算法设计者通常会提供多种配置密码学算法的方法。

密码学库的实现者通常会根据密码学算法设计者和规范作者创建的规范进行实现,使所有选项都可供使用其库的应用程序开发人员使用。这可能是因为他们不知道特定应用程序开发人员在特定密码学部署中可能需要哪些功能组合。因此,所有选项通常都会暴露给应用程序开发人员。

使用密码学库的应用程序开发人员通常没有必要的密码学专业知识和知识,无法为特定应用程序适当地选择密码学参数和选项。这种缺乏专业知识可能会导致为特定应用程序选择了不适当的密码学参数和选项。

本规范将优先级设置为保护应用程序开发人员优先于密码学库实现者,优先于密码学规范作者,优先于密码学算法设计者。基于这些优先级,提出以下建议:

上述指导旨在确保在架构的较低层提供有用的密码学选项和参数,同时不向可能无法完全理解每个选项的权衡利弊的应用程序开发人员暴露这些选项和参数。

密码套件命名约定

第 [[[#versioning-cryptography-suites]]] 节强调了提供易于理解的密码套件时效性信息的重要性,而第 [[[#protecting-application-developers]]] 节进一步强调了减少需要指定的选项数量的必要性。实际上,第 [[[#cryptographic-suites]]] 节列出了密码套件的要求,包括算法、转换、哈希和序列化的详细规范。因此,密码套件的名称不需要包含所有这些细节,这意味着第 [[[#versioning-cryptography-suites]]] 节中提到的参数化版本控制既不必要也不可取。

推荐的密码套件命名约定是一个字符串,由签名算法标识符组成,与选项标识符(如果密码套件支持不兼容的实现选项)用连字符分隔,最后加上套件提出的大致年份。

例如,[[?DI-EDDSA]] 基于 EdDSA 数字签名,支持两种基于规范化方法的不兼容选项,并大约在 2022 年提出,因此它有两个不同的密码套件名称:eddsa-rdfc-2022eddsa-jcs-2022

尽管 [[?DI-ECDSA]] 基于 ECDSA 数字签名,支持与 [[?DI-EDDSA]] 相同的两种不兼容规范化方法,并通过两组不同的椭圆曲线和哈希支持两种不同的安全级别(128 位和 192 位),但它只有两个密码套件名称:ecdsa-rdfc-2019ecdsa-jcs-2019。安全级别和相应的曲线及哈希由验证中使用的公钥的多密钥格式决定。

灵活性与分层

密码学灵活性 是一种设计频繁连接的信息安全系统以支持在多种密码学原语和/或算法之间切换的实践。密码学灵活性的主要目标是使系统能够快速适应新的密码学原语和算法,而无需对系统基础设施进行破坏性更改。因此,当某个特定的密码学原语(例如 SHA-1 算法)被认为不再安全时,可以通过简单的配置文件更改将系统重新配置为使用更新的原语。

当信息安全系统中的客户端和服务器定期联系时,密码学灵活性最为有效。然而,当特定密码学算法保护的消息是长期存在的(例如 可验证凭证),并且/或者客户端(持有者)可能无法轻松地重新联系服务器(发行者)时,密码学灵活性无法提供所需的保护。

密码学分层 是一种设计很少连接的信息安全系统以同时使用多种原语和/或算法的实践。密码学分层的主要目标是使系统能够在一个或多个密码学算法或原语失效的情况下,仍然保持对有效载荷的密码学保护。例如,使用 RSA、ECDSA 和 Falcon 算法并行对单个信息进行数字签名,可以提供一种机制,即使这三种数字签名算法中的两种失效,系统仍然可以利用未失效的密码学保护继续保护信息。开发人员被建议对所有可能需要保护一年或更长时间的签名内容利用此功能。

本规范同时支持这两种灵活性形式。它支持密码学灵活性,允许轻松地从一种算法切换到另一种算法。它还支持密码学分层,允许同时使用多种密码学算法,通常是并行的,这样可以在不依赖或要求其他算法的情况下使用任何一种算法来保护信息,同时仍然保持数字证明格式对开发人员的易用性。

更安全的抽象

一个 [=证明=] 包含一个 `proofValue`,其中可以嵌入与密码学证明相关的多个参数。例如,[[[?VC-DI-ECDSA]]] 规范中的选择性披露算法在 `proofValue` 中包含多个密码学签名,每个可选择披露的项目都有一个签名。这是为了使技术更易于应用程序开发人员使用并更安全。

本规范敦促规范作者使用单一值来抽象应用层很少需要的信息。类似于表达图像的 `data:` URL 将许多图像渲染参数封装为单一值,`proofValue` 属性(以及其他类似属性)将对应用程序开发人员无用的信息抽象出来,以简化对对应用程序重要的字段的识别。以这种方式抽象信息可以使数据结构更易于开发人员使用,同时减少例如由于编程错误添加或删除关键属性而意外破坏密码学层的可能性。

本规范中的数据完整性设计将通常仅对密码学层有用的信息抽象为单一属性,以减轻应用程序开发人员的负担并增强系统的安全性。

转换

在密码保护过程中,有时对被保护的数据进行转换是有益的。这种“内联”转换可以使特定类型的密码保护与其承载的数据格式无关。例如,一些数据完整性密码套件利用 RDF 数据集规范化 [[?RDF-CANON]],将初始表示形式转换为规范化形式 [[?N-QUADS]],然后进行序列化、哈希和数字签名。只要表达被保护数据的任何语法都可以转换为这种规范化形式,数字签名就可以被验证。这使得相同的数字签名可以在 JSON、CBOR、YAML 和其他兼容语法中表达,而无需为每种语法创建密码证明。

能够在多种语法中表达相同的数字签名是有益的,因为系统通常具有其操作的本地数据格式。例如,一些系统基于 JSON 数据编写,而另一些系统基于 CBOR 数据编写。如果没有转换,内部以 CBOR 处理数据的系统需要将数字签名的数据结构存储为 JSON(或反之亦然)。这会导致数据的双重存储,并可能增加安全攻击面,如果存储在数据库中的未签名表示意外偏离了签名表示。通过使用转换,数字证明可以以本地数据格式存在,从而有助于防止数据库随时间发生不可检测的漂移。

本规范旨在通过利用“内联”数据转换避免对签名信息的重复存储。建议应用程序开发人员在其应用程序的本地数据格式中处理密码保护数据,而不是将密码证明与被保护数据分开存储。同时建议开发人员定期确认密码保护数据在写入和从应用程序存储中读取时未被篡改。

一些转换(例如 RDF 数据集规范化 [[?RDF-CANON]])具有针对攻击者可能利用的输入数据集的缓解措施,这些输入数据集可能会消耗过多的处理周期。这类攻击被称为 数据集投毒,所有现代 RDF 数据集规范化工具都需要检测此类恶意输入并停止处理。RDF 数据集规范化的测试套件包括此类投毒数据集,以确保所有符合规范的实现都具备这些缓解措施。一般来说,使用转换的密码套件规范需要缓解此类攻击,实施者应确保其使用的软件库强制执行这些缓解措施。这些攻击与任何资源耗尽攻击(例如故意减慢连接的 HTTP 客户端,从而耗尽服务器连接)属于同一类别。实施者在实施防御性安全策略时应考虑此类攻击。

受保护的信息

任何 [=数据完整性证明=] 所保护的数据是 [=转换|转换后的数据=]。[=转换|转换后的数据=] 是由特定 [=密码套件=] 指定的 [=转换算法=] 生成的。这种保护机制不同于一些不对输入数据进行任何 [=转换=] 的传统数字签名机制。[=转换=] 的好处详见第 [[[#transformations]]] 节。

例如,[=密码套件=] 如 ecdsa-jcs-2019eddsa-jcs-2022 使用 [[[?RFC8785]]] 将数据 [=转换|转换=] 为规范化 JSON,然后进行密码哈希和数字签名。这种方法的一个好处是,添加或删除不影响所签名信息含义的格式字符(例如空格、制表符和换行符)不会使数字签名失效。传统的数字签名机制不具备此功能。

其他 [=密码套件=] 如 ecdsa-rdfc-2019eddsa-rdfc-2022 使用 [[[?RDF-CANON]]] 将数据 [=转换|转换=] 为规范化 N-Quads [[?N-QUADS]],然后进行密码哈希和数字签名。这种方法的一个好处是,密码签名可以在多种不同的语法(如 JSON、YAML 和 CBOR)中移植,而不会使签名失效。传统的密码签名机制不具备此功能。

实施者和开发人员被建议不要信任包含 [=数据完整性证明=] 的信息,除非该证明已被 [=验证=],并且验证后的数据由软件库返回值提供,该库已确认所有返回的数据均已成功受到保护。

数据不透明性

应用数据的可检查性会影响系统效率和开发人员的生产力。当密码学保护的应用数据(如基于编码的二进制数据)无法被应用子系统(如数据库)轻松处理时,处理这些密码学保护信息的工作量会增加。例如,可以被数据库本地存储和索引的密码学保护负载将简化系统设计,使其:

同样,可以被多个上游网络系统处理的密码学保护负载提高了正确分层安全架构的能力。例如,如果上游系统不需要反复解码传入的负载,系统就能通过专门化上游子系统来分配处理负载,从而积极应对攻击。尽管在采取实质性行动之前始终需要检查数字签名,但其他上游检查可以在透明负载上执行——例如基于标识符的速率限制、签名过期检查或随机数/挑战检查——以拒绝明显的恶意请求。

此外,如果开发人员无法轻松查看系统中的数据,审计或调试系统正确性的能力将受到阻碍。例如,要求应用程序开发人员复制粘贴基于编码的应用数据会使开发变得更加困难,并增加明显错误被忽略的可能性,因为每条消息都需要通过手动操作的基于解码工具。

然而,有时正确的设计决策是使数据不透明。无需被其他应用子系统处理的数据,以及无需被应用开发人员修改或访问的数据,可以序列化为不透明格式。例如,数字签名值、密码学密钥参数以及其他仅需由密码学库访问而无需应用开发人员修改的数据字段。还有一些例子表明,当底层子系统不向应用开发人员暴露不透明数据的复杂性时,数据不透明性是合适的,例如执行静态加密的数据库。在这些情况下,应用开发人员继续使用透明的应用数据格式进行开发,而数据库负责管理将应用数据加密和解密到长期存储的复杂性。

本规范力求提供一种架构,其中应用数据保持其原生格式而不变得不透明,而其他密码学数据(如数字签名)则保持其不透明的二进制编码形式。密码套件的实现者被敦促在设计其套件时适当地使用数据不透明性,并在使应用数据不透明与在应用层提供密码学数据访问之间权衡设计取舍。

验证方法绑定

实现者通过从[=验证方法=]的定义到[=受控标识符文档=],并确保[=受控标识符文档=]也包含对[=验证方法=]的引用,来确保[=验证方法=]绑定到特定的控制器。此过程在 检索验证方法的算法中进行了描述。

验证关系验证

当实现正在验证证明时,必须验证生成证明所用的[=验证方法=]不仅列在[=受控标识符文档=]中,还必须验证其确实用于生成正在验证的证明。此过程称为“验证关系验证”。

验证关系的过程在[[[CID]]]规范的 3.3 检索验证方法节中概述。

此过程用于确保密码学材料(如私有密码学密钥)不会被应用于非预期用途。例如,如果一个本应用于颁发可验证凭证的私有密码学密钥被用于登录网站(即用于身份验证),这就是密码学材料的误用。不检查验证关系是危险的,因为某些密码学材料的限制和保护配置文件可能由其预期用途决定。例如,一些应用程序可能被信任仅将密码学材料用于一个目的,或者某些密码学材料可能受到更高的保护,例如存储在数据中心的硬件安全模块中,而不是作为笔记本电脑上的未加密文件。

证明目的验证

当实现正在验证证明时,必须验证[=证明目的=]是否与预期用途匹配。

此过程用于确保证明不会被应用程序误用于非预期目的,因为这对证明创建者来说是危险的。例如,如果一个证明声明其目的是用于保护可验证凭证中的断言,但却被用于[=身份验证=]以登录网站,这就是一种误用。在这种情况下,证明创建者将证明附加到他们期望分发给无限数量其他方的可验证凭证中。如果网站错误地接受此类证明作为[=身份验证=]而非其预期用途,则这些方中的任何一方都可以冒充证明创建者登录网站。

规范化方法的安全性

转换(如规范化)的执行方式会影响系统的安全特性。选择最佳的规范化机制取决于具体的用例。通常,满足所需安全要求的最简单机制是最佳选择。本节尝试为实现者在本规范中提到的两种主要规范化机制之间提供简单的指导,即 JSON 规范化方案 [[RFC8785]] 和 RDF 数据集规范化 [[RDF-CANON]]。

如果应用程序仅使用 JSON 且不依赖任何形式的 RDF 语义,那么使用基于 JSON 规范化方案 [[RFC8785]] 的密码套件是一个有吸引力的选择。

如果应用程序使用 JSON-LD 并需要保护文档的语义,那么使用基于 RDF 数据集规范化 [[RDF-CANON]] 的密码套件是一个有吸引力的选择。

实现者还被建议可以使用其他不执行任何转换的机制,这些机制通过将数据封装在密码学信封中而不是将证明嵌入数据中来保护数据,例如 JWTs [[?RFC7519]] 和 CWTs [[?RFC8392]]。这些方法在某些用例中具有简化的优势,但会牺牲本规范中详细描述的方法所提供的一些好处。

规范化方法的正确性

本规范使用的算法过程之一是规范化,这是一种[=转换=]。规范化是将可能以多种语义等价方式表达的信息作为输入,并以一种称为“规范形式”的单一方式表达所有输出的过程。

使用规范化的[=数据完整性证明=]的安全性高度依赖于算法的正确性。例如,如果规范化算法将具有不同含义的两个输入转换为相同的输出,则作者的意图可能会被[=验证者=]误解。这可能被对手用作攻击向量。

此外,如果输入中的语义相关信息未出现在输出中,则攻击者可能会将此类信息插入消息中,而不会导致证明验证失败。这类似于密码学签名消息时常用的另一种转换:密码学哈希。如果攻击者能够从不同的输入生成相同的密码学哈希,则该密码学哈希算法被认为是不安全的。

强烈建议实现者确保对任何用于[=转换=]输入到[=哈希=]过程的规范化算法进行适当的审查。适当的审查至少包括与算法正确性的同行评审数学证明相关联;多个实现和标准制定组织的专家审查是首选。强烈建议实现者不要发明或使用新的机制,除非他们在信息规范化方面接受过正式培训和/或能够获得领域专家的帮助,这些专家能够提供算法正确性的同行评审数学证明。

网络请求

本规范的设计方式确保在[=符合规范的安全文档=]上验证证明时不需要网络请求。然而,读者可能会注意到,JSON-LD 上下文和[=验证方法=]可能包含可以通过网络连接检索的 URL。这种担忧适用于验证期间或之后可能从网络加载的任何 URL。

在可能的情况下,建议实现者永久或积极地缓存此类信息,以减少需要通过网络获取此类 URL 的实现的攻击面。例如,JSON-LD 上下文的缓存技术在[[[#contexts-and-vocabularies]]]节中进行了描述,而某些[=验证方法=](如 `did:key` [[?DID-KEY]])根本不需要从网络中获取。

当无法使用缓存信息时,例如首次遇到基于特定 HTTP URL 的[=验证方法=]实例时,建议实现者采取防御性措施,以减轻在任何可能从网络获取资源的过程中拒绝服务攻击的风险。

其他安全注意事项

由于本规范描述的用于保护文档的技术具有通用性,其使用的安全影响可能不会立即显现给读者。为了了解在完整的软件系统中可能需要考虑的安全问题,建议实现者阅读有关此技术在可验证凭证生态系统中的使用方式 [[?VC-DATA-MODEL-2.0]];有关更多信息,请参阅可验证凭证安全注意事项部分。

隐私注意事项

以下部分描述了开发人员在实现本规范时需要注意的隐私事项,以创建增强隐私的软件。

不可关联性

当一个数字签名的有效负载包含被多个验证者看到的数据时,它会成为一个关联点。例如,购物会员卡号就是一个例子。可关联的数据可能会被验证者用于跟踪目的,这有时会违反隐私预期。一些数据可以被用于跟踪的事实可能并不立即显现。可关联数据的示例包括但不限于静态数字签名或图像的密码学哈希值。

可以创建一个数字签名的有效负载,该负载不包含任何可关联的跟踪数据,同时也能在特定交互中提供一定程度的可信性。这种特性被称为不可关联性,它确保数字签名的有效负载中不使用任何可关联数据,同时提供一定程度的信任,其充分性需要由每个验证者决定。

需要理解的是,并非所有用例都需要或允许不可关联性。在某些情况下,由于法规或安全原因,关联性和相关性是必要的,例如关联运输和存储危险材料的组织和个人。当某次交互中存在隐私预期时,不可关联性是有用的。

至少有两种机制可以提供一定程度的不可关联性。第一种方法是确保消息中使用的数据值永远不会在未来的消息中重复。第二种方法是确保任何重复的数据值提供足够的群体隐私,以使得在实践中几乎不可能关联期望在交互中保持一定隐私的实体。

可以使用多种方法来实现不可关联性。这些方法包括确保消息是一个一次性使用的持有者令牌,且不包含任何可用于关联的信息,使用确保足够群体隐私的属性,以及使用密码套件使消息的呈现实体能够重新生成新签名,同时不影响消息的可信性。

选择性披露

选择性披露是一种技术,它使得先前签名消息的接收者(即由其创建者签名的消息)能够仅披露消息的部分内容,而不影响这些部分的可验证性。例如,某人可能会选择性地披露数字驾驶执照以租车。这可能涉及仅披露执照的签发机构、执照号码、出生日期和授权的机动车类别。请注意,在这种情况下,执照号码是可关联的信息,但由于未共享驾驶员的全名和地址,因此保留了一定程度的隐私。

并非所有软件或密码套件都能够提供选择性披露。如果消息的作者希望其接收者能够选择性披露消息,则需要在特定消息上启用选择性披露,并且双方都需要使用支持该功能的密码套件。作者还可能要求强制披露消息的某些部分。希望选择性披露消息部分内容的接收者需要使用能够执行该技术的软件。支持选择性披露的密码套件的一个示例是`bbs-2023`。

可以以不保留不可关联性的方式选择性披露信息。例如,某人可能希望披露与某次运输相关的检查结果,其中包括运输标识符或批号,这可能由于法规要求而必须是可关联的。然而,可能不需要披露整个检查结果,仅选择性披露通过/未通过状态可能被认为是足够的。有关在保留隐私的同时披露信息的更多信息,请参阅第[[[#unlinkability]]]节。

先前的证明

当使用[[[#proof-chains]]]中定义的`previousProof`功能时,实施者需要对一个或多个先前的证明进行数字签名,以将其包含在受保护的有效负载中。这不可避免地会暴露与每个添加先前证明的实体相关的信息。

至少,先前证明的[=验证方法=](例如公钥)会被证明链中下一个证明的创建者看到。如果先前证明的创建者并不打算被包含在证明链中,这可能会引发隐私问题,但这是在任何类型的文档中添加不可否认的数字签名时不可避免的结果。

可以使用更高级的密码学机制,例如群签名,来隐藏消息签名者的身份,同时数据完整性密码套件也可以缓解这一隐私问题。

网络请求的指纹识别

验证证明期间或之后,任何可能从网络加载的URL都存在指纹识别问题。本规范的设计方式确保在[=符合规范的安全文档=]上验证证明时不需要网络请求。然而,读者可能会注意到,JSON-LD上下文和[=验证方法=]可能包含可以通过网络连接检索的URL,这会引发指纹识别问题。

例如,[=符合规范的安全文档=]的创建者可能会为JSON-LD上下文和[=验证方法=]创建唯一的每文档URL。在验证此类文档时,验证者从网络获取该信息会向文档的创建者暴露其对[=符合规范的安全文档=]的兴趣,这可能导致与任何非文档创建者的隐私预期不匹配。

建议实施者遵循第[[[#network-requests]]]节中关于URL缓存和防御性实现的指导。鼓励使用诸如Oblivious HTTP之类的技术,从网络中检索资源而不暴露发出请求的客户端。此外,可以使用启发式方法来确定[=符合规范的安全文档=]的创建者是否以可能违反隐私预期的方式使用指纹识别URL。这些启发式方法可以用于向可能处理包含可疑指纹识别URL的文档的实体显示警告。

规范化方法的隐私

执行转换(即规范化)的方式会影响系统的隐私特性。选择最佳的规范化机制取决于具体的用例。本节尝试为实施者在本规范中提到的两种主要规范化机制之间提供简单的指导,即JSON规范化方案[[RFC8785]]和RDF数据集规范化[[RDF-CANON]]。

如果应用程序不需要对受保护文档中的信息执行选择性披露,也不使用JSON-LD,那么基于JSON规范化方案[[RFC8785]]的密码套件是一个有吸引力的选择。

如果应用程序使用JSON-LD并可能需要对受保护文档中的信息执行选择性披露,那么使用基于RDF数据集规范化[[RDF-CANON]]的密码套件是一个有吸引力的选择。

实施者还被建议可以使用其他不执行任何转换的选择性披露机制,这些机制通过将数据封装在密码学信封中而不是将证明嵌入数据中来保护数据,例如SD-JWTs[[?SD-JWT]]。这些方法在某些用例中具有简化的优势,但会牺牲本规范中详细描述的方法所提供的一些好处。

其他隐私注意事项

由于本规范描述的用于保护文档的技术具有通用性,其使用的隐私影响可能不会立即显现给读者。为了了解在完整的软件系统中可能需要考虑的隐私问题,建议实施者阅读有关此技术在可验证凭证生态系统中的使用方式[[?VC-DATA-MODEL-2.0]];有关更多信息,请参阅可验证凭证隐私注意事项部分。

无障碍性注意事项

以下部分描述了开发人员在实现本规范时需要考虑的无障碍性注意事项,以确保其软件能够被具有不同认知、运动和视觉需求的人使用。通常情况下,本规范由系统软件使用,并不会直接向个人暴露需要考虑无障碍性的相关信息。然而,在某些情况下,个人可能会间接接触到由本规范表达的信息,因此针对这些情况提供了以下指导。

时间值的呈现

本规范支持表达与密码学证明有效期相关的日期和时间。如果处理某个证明时发现其超出了允许的时间范围,这些信息可能会间接暴露给个人。在向个人展示这些日期和时间时,建议实施者考虑 文化规范和地区习惯,以适应显示软件。此外,建议以减轻接收信息的个人认知负担的方式呈现时间值。

例如,在传达某组数字签名信息的过期时间时,建议实施者使用更易理解的语言,而不是以优化准确性为目标的语言。将过期时间呈现为“此票据已于三天前过期。”优于“此票据已于2023年7月25日下午3:43过期。”前者提供了更易理解的相对时间,而后者需要个人在脑海中进行计算,并假设他们能够完成这样的计算。

理解证明集和证明链

第[[[#proof-sets]]]节和第[[[#proof-chains]]]节描述了如何在[=安全数据文档=]中表达多个证明;也就是说,除了在[=安全数据文档=]中包含单个[=证明=]外,还可以将多个证明表达为列表,如[[[#example-a-proof-set-in-a-data-document]]]和[[[#example-a-proof-chain-in-a-data-document]]]中所示。该列表的元素是[=证明集=]的成员,并且可以选择性地成为[=证明链=]的成员。本节的目的是解释每种功能的预期用途,特别是它们不同的安全属性。这些不同的安全属性导致了第[[[#add-proof-set-chain]]]节中处理方式的差异。

本节以简化的方式表示[=安全数据文档=]及其证明,以便观察重要的安全属性。

考虑一个有三个签署人的场景:CEO、CFO 和工程副总裁(VP of Engineering)。每个人都需要拥有一对用于签署文档的公钥和私钥。我们分别用secretCEO/publicCEOsecretCFO/publicCFOsecretVPE/publicVPE表示这些签署人的私钥/公钥。

在构建一个[=证明集=]时,每个签署人对|inputDocument|进行签名而无需担忧,我们可以象征性地构建一个证明如下:

   {
     "type": "DataIntegrityProof",
     "cryptosuite": "eddsa-jcs-2022",
     "created": "2023-03-05T19:23:24Z",
     "proofPurpose": "assertionMethod",
     "verificationMethod": publicCEO,
     "proofValue": signature(secretCEO, inputDocument)
   }
         

其中publicCEO用作解析为CEO公钥的占位符,signature(`secretKey`, `inputDocument`)表示使用特定数据完整性密码套件,通过特定私钥对特定文档进行数字签名的计算。`type`、`cryptosuite`、`created`和`proofPurpose`属性不影响我们的讨论,因此将省略它们。特别是,下面展示了在一个文档上由工程副总裁、CFO 和 CEO 签署的[=证明集=]中的所有证明:

   {
     // 受保护的数据文档的其余部分未显示(如上)
     "proof": [{
       "verificationMethod": publicVPE,
       "proofValue": signature(secretVPE, inputDocument)
     }, {
       "verificationMethod": publicCFO,
       "proofValue": signature(secretCFO, inputDocument)
     }, {
       "verificationMethod": publicCEO,
       "proofValue": signature(secretCEO, inputDocument)
     }]
   }
         

持有者或任何其他中间方在接收到包含[=证明集=]的[=安全数据文档=]时,可以在将其传递给其他实体之前移除`proof`集合中的任何值,并且[=安全数据文档=]仍然可以验证。这可能符合或不符合意图。例如,对于签署人发送生日贺卡给一位重要员工,使用[=证明集=]可能是可以的。但如果我们试图模拟一个公司层级审批的业务流程,这就不理想了,因为任何中间方都可以从[=证明集=]中移除签名,且仍然可以验证;例如,在下面的示例中,看起来 CFO 和 CEO 批准了某些内容,而没有工程副总裁的同意。

   {
     // 受保护的数据文档的其余部分未显示(如上)
     "proof": [{
       "verificationMethod": publicCFO,
       "proofValue": signature(secretCFO, inputDocument)
     }, {
       "verificationMethod": publicCEO,
       "proofValue": signature(secretCEO, inputDocument)
     }]
   }
         

可以通过为每个证明设置`id`属性,使另一个证明能够引用它,从而在[=证明集=]中的[=证明=]之间引入依赖关系。换句话说,依赖证明将通过使用`previousProof`属性被其他依赖证明引用。这种依赖链可以具有任意深度。使用这种[=证明链=]的意图是模拟业务流程中的审批链或公证人见证模拟签名。

以下示例展示了如何构建[=证明链=]:首先工程副总裁在文档上签字;基于工程副总裁的签名和审查,CFO 随后在文档上签字;最后,基于之前的签名和审查,CEO 在文档上签字。由于其他人将引用工程副总裁的签名,我们需要为证明添加一个`id`。首先,工程副总裁在[=输入文档=]上签字:

   {
     // 受保护的数据文档的其余部分未显示(如上)
     "proof": {
       "id": "urn:proof-1",
       "verificationMethod": publicVPE,
       "proofValue": signature(secretVPE, inputDocument)
     }
   }
         

接下来,CFO 收到文档,验证工程副总裁已签署,并基于审查和工程副总裁的签名签署文档。为此,我们需要通过指示文档中刚接收到的证明的依赖关系来设置[=证明链=]。我们通过将第二个证明的`previousProof`属性设置为值`urn:proof-1`来实现,这将第二个证明“绑定”到第一个证明,然后签署。以下示例展示了如何创建对第一个证明的依赖关系:

   {
     // 受保护的数据文档的其余部分未显示(如上)
     "proof": [{
       "id": "urn:proof-1",
       "verificationMethod": publicVPE,
       "proofValue": signature(secretVPE, inputDocument)
     }, {
       "id": "urn:proof-2",
       "verificationMethod": publicCFO,
       "previousProof": "urn:proof-1",
       "proofValue": signature(secretCFO, inputDocumentWithProof1)
     }]
   }
         

现在,当 CEO 验证包含上述[=证明链=]的[=安全数据文档=]时,他们将检查 CFO 的签名是否基于工程副总裁的签名。首先,他们将根据工程副总裁的公钥检查`id`属性值为`urn:proof-1`的证明。请注意,此证明是针对原始文档的。

接下来,CEO 将根据 CFO 的公钥检查`id`属性值为`urn:proof-2`的证明。然而,为了确保 CFO 签署的文档包含工程副总裁已签署的证明,我们将验证此证明是否覆盖了文档和`urn:proof-1`的组合。如果验证成功,CEO 将签署文档,生成一个覆盖包含`urn:proof-1`和`urn:proof-2`的文档的证明。最终的[=证明链=]如下所示:

   {
     // 受保护的数据文档的其余部分未显示(如上)
     "proof": [{
       "id": "urn:proof-1",
       "verificationMethod": publicVPE,
       "proofValue": signature(secretVPE, inputDocument)
     }, {
       "id": "urn:proof-2",
       "verificationMethod": publicCFO,
       "previousProof": "urn:proof-1",
       "proofValue": signature(secretCFO, inputDocumentWithProof1)
     }, {
       "id": "urn:proof-3",
       "verificationMethod": publicCEO,
       "previousProof": "urn:proof-2",
       "proofValue": signature(secretCEO, inputDocumentWithProof2)
     }]
   }
         

此[=安全数据文档=]的接收者随后以类似的方式验证它,检查链中的每个证明。

修订历史

本节包含了此规范随时间推移所做的实质性更改。

第二版候选推荐标准以来的更改:

第一版候选推荐标准以来的更改:

第一版公开工作草案以来的更改:

致谢

本规范的工作得到了“重启信任网络”(Rebooting the Web of Trust)社区的支持,该社区由 Christopher Allen、Shannon Appelcline、Kiara Robles、Brian Weller、Betty Dhamers、Kaliya Young、Manu Sporny、Drummond Reed、Joe Andrieu、Heather Vescent、Kim Hamilton Duffy、Samantha Chase、Andrew Hughes、Will Abramson、Erica Connell 和 Eric Schuh 共同推动。由 Phil Windley、Kaliya Young、Doc Searls 和 Heidi Nobantu Saul 主持的互联网身份研讨会(Internet Identity Workshop)的参与者也通过多次旨在教育、讨论和改进本规范的工作会议支持了本规范的完善。

工作组还感谢我们的主席 Brent Zundel、前主席 Kristina Yasuda,以及 W3C 工作人员联络人 Ivan Herman,他们在 W3C 标准化过程中提供了专业管理和稳定指导。

本规范的部分工作得到了美国国土安全部科学与技术局的资助,合同编号为 70RSAT20T00000029、70RSAT21T00000016、70RSAT23T00000005、70RSAT20T00000010/P00001、70RSAT20T00000029、70RSAT21T00000016/P00001、70RSAT23T00000005、70RSAT23C00000030、70RSAT23R00000006、70RSAT24T00000011,以及国家科学基金会(NSF 22-572)的资助。本规范的内容不一定反映美国政府的立场或政策,也不应推断为官方认可。

工作组还要感谢以下个人对本规范的审阅和反馈(按姓氏字母顺序排列,或在未提供姓名的情况下按其 GitHub 用户名排列):

Will Abramson, Mahmoud Alkhraishi, Christopher Allen, Joe Andrieu, Bohdan Andriyiv, George Aristy, Anthony, Greg Bernstein, Bob420, Sarven Capadisli, Melvin Carvalho, David Chadwick, Gabe Cohen, Matt Collier, Sebastian Crane, Kim Hamilton Duffy, Snorre Lothar von Gohren Edwin, Veikko Eeva, Eric Elliott, Raphael Flechtner, Julien Fraichot, Benjamin Goering, Kyle Den Hartog, Joseph Heenan, Helge Krueger, Ivan Herman, Michael Herman, Alen Horvat, Anil John, Andrew Jones, Michael B. Jones, Rieks Joosten, Gregory K., Gregg Kellogg, Filip Kolarik, David I. Lehn, Charles E. Lehner, Christine Lemmer-Webber, Eric Lim, Dave Longley, Tobias Looker, Jer Miller, nightpool, Bert Van Nuffelen, Luis Osta, Nate Otto, George J. Padayatti, Addison Phillips, Mike Prorock, Brian Richter, Anders Rundgren, Eugeniu Rusu, Markus Sabadello, silverpill, Wesley Smith, Manu Sporny, Orie Steele, Patrick St-Louis, Henry Story, Oliver Terbu, Ted Thibodeau Jr., John Toohey, Mike Varley, Jeffrey Yasskin, Kristina Yasuda, Benjamin Young, Dmitri Zagidulin, and Brent Zundel.