DIDComm-gRPC 0.0.3

DIDComm-gRPC 0.0.3

Tomislav Markovski维护。



  • 作者:
  • $(git config user.name)

DIDComm for gRPC 扩展

使用二进制协议(protobuf)和 HTTP/2 传输(gRPC)对 DIDComm 消息 v2 进行扩展的实现和消息定义。

目录


动机

DIDComm 消息 提供了一种在 DIDs 去中心化设计的基础上构建的安全、私人通信方法。该规范定义了一个使用 JSON 语法表示的具体消息定义,并允许定义其他协议。此 DIDComm 消息协议的扩展引入了 Protocol Buffers (Protobuf) 作为消息合同的语言定义。Protobuf 是一种不依赖于语言的、不依赖于平台的、可扩展的序列化结构化数据机制。此外,它还提供了一套具体的远程服务 RPC 协议栈,称为 gRPC。
Protobuf 和 gRPC 聚焦于接口定义和代码生成,以实现多平台、零代码的应用架构。DIDComm 消息可以利用这一点在不同平台之间提供可用性和互操作性。

设计方法

为了将 DIDComm 消息扩展到与其他协议一起使用,我们需要描述以下方面的方法

  • 消息合同
  • 安全层
  • 传输

本扩展中使用协议缓冲区消息描述符的消息合同。有关不同消息格式的说明,请参阅 消息结构 部分。
安全层 用 Rust 编写为一个跨平台的库,可以执行与安全性相关操作,如消息打包、签名等。该库发布了一个 C 风格的接口,用于由不同平台调用。
消息的传输 使用 gRPC 在 HTTP/1.x 和 HTTP/2 的上下文中进行描述。后者使用 protobuf 定义来标准化端点和启动实现。

DIDComm Exchange

图 1:DIDComm 交换的组件

扩展架构

推动 DIDComm 消息采用的重要因素之一是提供多种语言的库。gRPC 和 Protobuf 库已经高度可用;此扩展提供了安全层中缺失的组件,并将所有内容打包在一起。

DIDComm gRPC Extension Architecture

本机安全库

此库是用 Rust 编写的,适用于所有架构。它支持 DIDComm 消息规范中定义的加密和签名算法。此外,它还提供了用于处理 did:key 方法的基本实用方法,使用 ed25519x25519p256 密钥类型。此库通过简单的外部函数接口 (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);

其他协议

这种架构足够通用,可以轻松适应其他协议。例如,ThriftAvro 采用与接口定义和 RPC 服务支持非常相似的方法;为支持这些协议编写插件或扩展将相当简单。虽然 CBOR 是无模式的序列化格式,但它也可以与任何数据建模解决方案(如 YANG)一起使用,但这不是必需的。

数据合同定义

用于 gRPC 上 DIDComm 消息的所有数据合同分为三组:

  • 安全消息(security.proto)- 定义了运输和安全层使用的信息,例如 EncryptedMessageSignedMessage,以及支持密钥格式的相关消息
  • 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 来实现这一目的。此格式还允许在消息中附加多个签名。

预期 SignedMesageEncryptedMessage 的签名有效负载始终是一个 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" ]
    }]
}

支持的平台

参与开发