DIDComm for gRPC 扩展
使用二进制协议(protobuf)和 HTTP/2 传输(gRPC)对 DIDComm 消息 v2 进行扩展的实现和消息定义。
目录
- 动机
- 扩展架构
- 接口定义
- 端点和传输
- 支持的平台
- Rust
- .NET
- NET Core
- Xamarin
- WebAssembly
- Objective C
- Swift
- WebAssembly
- Node
- 浏览器
- Python
- Go
- 计划中
- Java
- Kotlin
- Android
- React Native
- Dart
- Java
- 许可证
- 参与其中
🙌
动机
DIDComm 消息 提供了一种在 DIDs 去中心化设计的基础上构建的安全、私人通信方法。该规范定义了一个使用 JSON 语法表示的具体消息定义,并允许定义其他协议。此 DIDComm 消息协议的扩展引入了 Protocol Buffers (Protobuf) 作为消息合同的语言定义。Protobuf 是一种不依赖于语言的、不依赖于平台的、可扩展的序列化结构化数据机制。此外,它还提供了一套具体的远程服务 RPC 协议栈,称为 gRPC。
Protobuf 和 gRPC 聚焦于接口定义和代码生成,以实现多平台、零代码的应用架构。DIDComm 消息可以利用这一点在不同平台之间提供可用性和互操作性。
设计方法
为了将 DIDComm 消息扩展到与其他协议一起使用,我们需要描述以下方面的方法
- 消息合同
- 安全层
- 传输
本扩展中使用协议缓冲区消息描述符的消息合同。有关不同消息格式的说明,请参阅 消息结构 部分。
安全层 用 Rust 编写为一个跨平台的库,可以执行与安全性相关操作,如消息打包、签名等。该库发布了一个 C 风格的接口,用于由不同平台调用。
消息的传输 使用 gRPC 在 HTTP/1.x 和 HTTP/2 的上下文中进行描述。后者使用 protobuf 定义来标准化端点和启动实现。
图 1:DIDComm 交换的组件
扩展架构
推动 DIDComm 消息采用的重要因素之一是提供多种语言的库。gRPC 和 Protobuf 库已经高度可用;此扩展提供了安全层中缺失的组件,并将所有内容打包在一起。
本机安全库
此库是用 Rust 编写的,适用于所有架构。它支持 DIDComm 消息规范中定义的加密和签名算法。此外,它还提供了用于处理 did:key
方法的基本实用方法,使用 ed25519
、x25519
和 p256
密钥类型。此库通过简单的外部函数接口 (FFI) 公开了所有功能。公开的所有函数都具有相同的方法签名。
pub extern "C" fn didcomm_pack(request: ByteBuffer,
response: &mut ByteBuffer,
err: &mut ExternError) -> i32 { }
pub extern "C" fn didcomm_sign(request: ByteBuffer,
response: &mut ByteBuffer,
err: &mut ExternError) -> i32 { }
这种方式允许我们将 protobuf 编码的有效载荷用作每个接口的请求和响应。按照惯例,请求和响应消息类型以函数命名。例如,函数 didcomm_generate_key
使用 GenerateKeyRequest
作为输入信息,GenerateKeyResponse
作为输出消息。相应的 protobuf 消息定义如下:
message GenerateKeyRequest {
bytes seed = 1;
KeyType key_type = 2;
}
message GenerateKeyResponse {
Key key = 1;
}
所有 FFI 消息都保存在 api.proto 文件中。
这种方法使创建和维护平台特定包装器变得容易。由于 gRPC 库的代码生成能力,这些 API 合同以语言惯用的方式立即对我们可用。
// JavaScript
let request = {
keyType: dm.KeyType.x25519,
};
var response = generateKey(request);
// Swift
var request = Didcomm_Messaging_GenerateKeyRequest()
request.keyType = .ed25519
let response = try DIDCommGrpc.generateKey(request: request)
// C#
var request = new GenerateKeyRequest { KeyType = KeyType.P256 };
var response = DIDComm.GenerateKey(request);
其他协议
这种架构足够通用,可以轻松适应其他协议。例如,Thrift 和 Avro 采用与接口定义和 RPC 服务支持非常相似的方法;为支持这些协议编写插件或扩展将相当简单。虽然 CBOR 是无模式的序列化格式,但它也可以与任何数据建模解决方案(如 YANG)一起使用,但这不是必需的。
数据合同定义
用于 gRPC 上 DIDComm 消息的所有数据合同分为三组:
- 安全消息(security.proto)- 定义了运输和安全层使用的信息,例如
EncryptedMessage
、SignedMessage
,以及支持密钥格式的相关消息 - API/FFI消息(api.proto)- 定义了由本机库 FFI 层使用的消息。这些信息是 DIDComm 规范的一部分,用作与安全 API 交互的通用格式。
- DIDComm 消息和服务(didcomm.proto)- 定义了常见的 DIDComm 消息和一组用于 DIDComm 运输的 RPC 服务
消息结构
DIDComm 规范使用 JWM 消息类型来定义消息结构。JWM 消息允许在任何 body
属性中编码任何一组属性。
{
"id": "1234567890",
"type": "<message-type-uri>",
"from": "did:example:alice",
"to": ["did:example:bob"],
"created_time": 1516269022,
"expires_time": 1516385931,
"body": {
"messagespecificattribute": "and it's value"
}
}
JWM 可以用 proto 语法表示为
message CoreMessage {
string id = 1;
string type = 2;
bytes body = 3;
repeated string to = 4;
string from = 5;
int64 created = 7 [json_name="created_time"]; // custom name mapping
int64 expires = 8 [json_name="expires_time"];
}
类似于 JSON 格式,自定义消息将以字节字符串的形式编码在 body
字段中。例如,考虑以下自定义消息
message CustomMessage {
string messagespecificattribute = 1;
}
该消息可以像这样嵌入到 JWM protobuf 消息中:
var message = new CoreMessage
{
Id = "1234567890",
Type = "https://didcomm.org/custom/1.0/custom_message",
Body = new CustomMessage { Messagespecificattribute = "and it's value" }.ToByteString()
}
JSON 在 protobuf 语法中是第一公民,它允许将属性直接映射到其 JSON 字段。大多数 protobuf 库支持轻松在 JSON 和 Protobuf 之间进行转换。这可以在支持多个 DIDComm 格式时提供一些灵活性。
在需要序列化 JOSE 格式以对有效负载进行签名或加密的情况下,通常将其序列化到 base64 或 UTF8 字节格式,protobuf 简单地使用 bytes
类型。默认的二进制序列化足以避免需要额外的编码。
消息加密
核心 DIDComm 规范定义 JWE 作为加密有效负载的消息格式。本库支持使用 256 位密钥加密算法的 XChaCha20Poly1305 和 AES-GCM。
与 JWM 类似,JWE 可以表示为一系列消息组合。
message EncryptedMessage {
bytes iv = 1 [json_name="iv"];
bytes aad = 2 [json_name="aad"];
bytes ciphertext = 3 [json_name="ciphertext"];
bytes tag = 4;
repeated EncryptionRecipient recipients = 5;
}
message EncryptionHeader {
EncryptionMode mode = 1 [json_name="enc"];
EncryptionAlgorithm algorithm = 2 [json_name="alg"];
string key_id = 3 [json_name="kid"];
string sender_key_id = 4 [json_name="skid"];
}
message EncryptionRecipient {
EncryptionHeader header = 1 [json_name="unprotected"];
bytes content_encryption_key = 2 [json_name="cek"];
}
enum EncryptionMode {
direct = 0;
content_encryption_key = 1;
}
enum EncryptionAlgorithm {
xchacha20poly1305 = 0;
aes_gcm = 1;
}
这种语法并不完全匹配 JWE 语法,其意图也不是实现直接兼容性。这是一种略微简化的格式,它考虑到规范的要求,以提供一个可以使用作为 DID URL 的密钥进行身份验证加密的封装,同时遵循既定的 JWE 规范模式。
虽然可以提供兼容的格式,即使用 JOSE 库进行 JWE 加密并使用 gRPC 扩展进行解密,但这并非这项工作的目标。我们认为这种方法限制了实现始终符合 JOSE 格式,并限制了在标准化 JSON 结构之外进行创新的能力。
消息签名
DIDComm 规范定义了用于消息签名的 JWS 格式。该消息可以使用 proto 语法进行描述。
message SignedMessage {
bytes payload = 1;
repeated Signature signatures = 2;
}
message Signature {
bytes header = 1 [json_name="protected"];
bytes signature = 3;
}
message SignatureHeader {
string algorithm = 1;
string key_id = 2;
}
类似于 JWS,在此格式中,签名是通过连接 SignedMessage.payload
字段和相应的 Signature.header
产生的。字段 header
的类型是 bytes
,它表示经过编码的 SignatureHeader
。这样做的原因是为了保证签名验证时的相同表示。JWS 使用字段 protected
来实现这一目的。此格式还允许在消息中附加多个签名。
预期
SignedMesage
和EncryptedMessage
的签名有效负载始终是一个DCMessage
(或本例中的 JWM)。消息也可以相互嵌套。
端点和传输
HTTP/1.x
POST /agent HTTP/1.1
HOST: api.example.com
Content-Type: application/didcomm-encrypted+protobuf
<protobuf encoded payload>
HTTP/2 with gRPC
使用 HTTP/2 有两种可能的场景。使用 gRPC,我们可以利用 HTTP/2 中的流控能力,在需要时执行双向通信。Proto 服务定义需要指定消息类型。这使我们能够避免在请求中手动指定内容类型。默认情况下,gRPC 实现将内容类型设置为 application/grpc
。尽管这不是必须的,但这也可能被更改。
使用 protobuf 消息的 gRPC 服务
这个定义使用核心 DIDComm 消息作为其端点定义。
service DIDCommPlain {
rpc Unary(CoreMessage) returns (CoreMessage);
rpc ServerStreaming(CoreMessage) returns (stream CoreMessage);
rpc ClientStreaming(stream CoreMessage) returns (CoreMessage);
rpc BidirectionalStreaming(stream CoreMessage) returns (stream CoreMessage);
}
同样,我们可以定义一个使用 EncryptedMessage
的服务定义。
service DIDCommEncrypted {
rpc Unary(EncryptedMessage) returns (EncryptedMessage);
rpc ServerStreaming(EncryptedMessage) returns (stream EncryptedMessage);
rpc ClientStreaming(stream EncryptedMessage) returns (EncryptedMessage);
rpc BidirectionalStreaming(stream EncryptedMessage) returns (stream EncryptedMessage);
}
实施者可以混合使用这些端点,以匹配其使用情况。也可以定义一个不返回结果的端点,以匹配 DC 消息的异步特性。
service DIDCommSimple {
rpc Unary(EncryptedMessage) returns (NoOp);
}
message NoOp {}
使用请求/响应 REST 风格 API
还可以在 gRPC 之外使用 HTTP/2。今天,许多平台都支持 HTTP/2,尽管它仅作为简单的请求/响应。在这种情况下,我们应该指定消息的内容类型。
POST /agent HTTP/2
HOST: api.example.com
Content-Type: application/didcomm-encrypted+protobuf
<protobuf encoded payload>
DID 文档中的服务
这是一个非规范性条目,可能需要对所有 DIDComm 服务进行标准化。
{
"service": [{
"id": "did:example:123456789abcdefghi#messages",
"type": "DIDCommService",
"serviceEndpoint": "https://example.com/agent",
"contentTypes": [
"application/didcomm-encrypted+protobuf",
"application/didcomm-encrypted+json" ]
}, {
"id": "did:example:123456789abcdefghi#rpc",
"type": "DIDCommService",
"serviceEndpoint": "https://example.com/DIDCommEncrypted",
"contentTypes": ["application/grpc" ],
"grpcEndpoints": [ "Unary", "ServerStreaming" ]
}]
}
支持的平台
- Rust
- .NET
- NET Core
- Xamarin
- WebAssembly
- Objective C
- Swift
- WebAssembly
- Node
- 浏览器
- Python
- Go
- 计划中
- Java
- Kotlin
- Android
- React Native
- Dart
- Java