Identity-Hub
1. 背景
在DID的生态体系中,需要有一个服务用于保存和管理用户的数据,这就是Identity Hub。Identity Hub的实现满足几个要求:
- 用户可控。Identity Hub可以由用户选择部署于任何地方,包括用户自己的手机、PC等。
- 用户数据加密保存。
- Identity Hub不保存任何私钥。
- 用户数据的访问需要认证。
- 经过用户授权后可允许第三方访问用户数据。
2. 应用场景
Claim 相关内容参考:verifiable-claim
- Claim 生产方:权威机构,也有自己的 DID
- Claim 申请方:DID 用户
- Claim 使用方:第三方应用
2.1. Claim 的申请与签发
- DID 用户(申请者)向权威机构申请相关认证的 claim
- 权威机构验证通过后,用自己的 DID 签发claim 并存储在 Identity hub 中
- 权威机构授权申请者访问claim 数据,这里权威机构自己也可以访问该数据
- 申请者获取 claim,之后可以存储在本地或者自己的 hub 中
2.2. 授权第三方获取 Claim
- 用户使用自己 DID 的私钥和第三方应用 DID 的公钥生成重加密密钥
- 向 Identity hub 授权第三方应用 DID 访问,附加重加密密钥
- Identity hub 用重加密密钥对claim 密文和密钥密文重加密
- Identity hub 生成 endpoint 给被授权的第三方 DID 访问
对以上的场景进行分析,Identity hub主要需要提供以下的几种功能:
- 存储:存储 DID 相关的 claim 密文和密钥密文
- 鉴权:访问数据需要权限认证
- 授权:添加、删除授权给第三方访问 claim
- 同步: Identity hub可以由用户选择部署于任何地方,包括用户自己的手机、PC等 Identity hub 之间同步 数据访问不绑定在特定在Identity hub实例上
3. 架构
4. 详细介绍
4.1. 数据存储
Identity hub 中的数据以 commit 的形式存储,每个 commit 表示对数据的一次操作,将所有的 commits 合并就可以得到数据的现有状态。采用这种形式犹如下的好处:
- 方便不同的 Identity hub 实例间同步数据
- 能够对数据的操作历史进行审计(每个 commit 需要由 DID 签名)
- 它简化了需要脱机数据修改并需要解决冲突的用例的实现
需要注意的是这里的数据包括 Claim 数据以及 Permission 数据,他们都以 commit 形式存在。对于 commit 的操作,包括三种:写入数据、读取 commit 和读取完整的 object。
每一个 commit 都是一个 JWT 结构的数据(如下),其中 Payload 为要发送的原始数据。
type Header struct {
CommittedAt string `json:"committedAt"` // commit created time
Iss string `json:"iss"` // ctrator DID
Kid string `json:"kid"` // key used to sign the commit
Sub string `json:"sub"` // owner DID
Interface string `json:"interface"` // Profile, Actions, Stores, Collections, Services(data type)
Context string `json:"context"` // schema context, "http://schema.org"
Type string `json:"type"` // WriteRequest, CommitQueryRequest, ObjectQueryRequest
Operation string `json:"operation"` // create, update, delete
CommitStrategy string `json:"commitStrategy"` // "basic"
ObjectId string `json:"objectId,omitempty"` // optional
Revision string `json:"revision"` // commit hash
Meta interface{} `json:"meta,omitempty"` // application-specific metadata
}
type Commit struct {
Protected Header `json:"protected"`
Unprotected interface{} `json:"header"`
Payload string `json:"payload"` // encrypted data
Signature string `json:"signature"`
}
严格来说,hub 中存储的是 Commit 序列化后的二进制数据。
对于 Claim 数据的存储,我们将加密后的Claim 密文放在 payload中,而加密后的密钥密文放在 UnprotectedHeader 的 Meta 中。这样做的考虑是授权第三方访问数据时,服务端要对密钥密文进行重加密计算,而 payload 信息是受 signature 保护,修改后无法保证数据的真实性。
这里采用 secp256k1 形式的椭圆曲线密钥,对称加密采用 A128GCM
,非对称加密采用 ES256K
。
4.1.1. 写入数据
当我们发送写入数据的请求时,需要构造一个 JWT 格式的请求数据WriteRequest。WriteRequest的结构如下:
type WriteRequest struct {
Context string `json:"@context` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "WriteRequest"
Iss string `json:"iss"` // client DID, sender
Sub string `json:"sub"` // target DID, data owner
Aud string `json:"aud"` // hub DID, receiver
CommitData SearlizedCommit `json:"commit"` // searlized Commit object
}
不管是写入数据还是下面的读取数据,都是以 HTTP POST 请求发送,序列化后的 request 数据放在 body 中,依靠 Type 字段来说识别当前是什么操作。
当 hub 收到写入 commits 请求后,按照以下逻辑执行:
- 权限验证、授权验证(owner 写入)
- 根据 objectId 在数据库中查找相关 commits
- 如果已有同样的 object,返回错误
- 否则向数据库中写入 commit
- 将该 commit 的revisions(hash) 打包,签名加密并发送回复
如果是更新数据,等同于写入一个新的 commit,但是会返回 之前所有 commits的 revisions 信息。
4.1.2. 获取数据
4.1.2.1. 获取 commits
同一个数据的一系列 commits 以 objectId 关联起来,当 hub 收到获取 commits 的请求后,按照以下逻辑执行:
- 权限验证、授权验证
- 根据 objectId 在数据库中查找所有 commits
- 如果找不到相应的 object,返回错误
- 验证所有 commits 的签名
- 打包返回数据,签名加密并发送回复
4.1.2.2. 获取 object
所有 commits以一定的策略合并得到完整的 object,这里采用 basic 策略,也即每个 commit 都有一个 object 的完整形态,取最新的 commit 最为 object ,执行逻辑如下:
- 权限验证、授权验证
- 根据 objectId 在数据库中查找所有满足 basic 策略的 commits
- 如果找不到相应的 object,返回错误
- 验证所有 commits 的签名
- 取第一个和最后一个 commit,元信息中的创建时间等字段取自于第一个 commit,而 meta 和 payload 信息取自于最后一个 commit(最新状态)
- 打包返回数据,签名加密并发送回复
响应数据的结构可以参考第5节。
4.2. 鉴权
Identity hub 对访问请求的鉴权遵循 DIF/W3C DID Auth schemes,同样采用挑战-响应的机制,认证机制主要实现以下两个任务:
- 在客户端和 Hub 之间利用各自的 DID 和密钥相互认证。
- 加密所有Hub请求和响应,使其内容仅对消息交换中涉及的DID密钥的持有者可用。
也即存储到 hub 中的数据不仅是加密存储的,也是加密传输的。而加密传输的过程首先需要构造认证数据结构。
假如我们有上述 WriteRequest
形式的数据,还需要构造如下的 AuthEnvelope
认证结构。
type AuthHeader struct {
Alg string `json:"alg"`
Kid string `json:"kid"` // sender's did publickey descriptor
DIDRequestNonce string `json:"did-requester-nonce"`
DIDAccessToken string `json:"did-access-token"`
}
type AuthEnvlope struct {
Header AuthHeader `json:"header"`
Payload string `json:"payload"` //data content
Signature string `json:"signature"`
}
其中 Payload 为 WriteRequest
序列化后的数据。
其中 header 必须是以下的几个字段。
字段 | 描述 |
---|---|
alg | 标准JWT头。表示用于签署JWT的算法。 |
kid | 标准JWT头。该值应采用该形式{did}#{key-id}。另一个应用程序可以获取此值,解析DID,并找到可用于提交的签名验证的指示公钥。 |
did-requester-nonce | 随机生成的字符串,必须在客户端缓存。此字符串将用于在以下部分中验证来自Hub的响应。 |
did-access-token | 应在客户端缓存并包含在发送到集线器的每个请求中的令牌。由于我们还没有访问令牌,因此请在初始请求中保留此属性。 |
之后通过 JSON Web Encryption (JWE) standard用 hub 的公钥加密请求。
发送请求时需要did-access-token这个字段,但是初次发送请求时客户端是没有这个 token 的,所以先发送一个不带 token 的请求,hub 会拒绝这个请求,但是在回复中会包含这个 token(JWE 形式,hub解密 request JWE后提取发送者的公钥来加密,需要获取 DID Document)。客户端提取这个 token (完成挑战)之后就可以放在后续的请求中。token 的过期时间会放在exp字段中,token 过期后继续发送请求,hub 会返回错误,此时需要重复上面的方法获取新的 token。
token 本身也是一个 JWT 结构,did-access-token字段就是 token 序列化后的内容。
假设我们已有请求数据内容JWT,例如下文的 WriteRequest(其本身就是一个 JWT,其 payload也是一个序列化后的 JWT),先构造成认证请求 JWT,进行签名序列化,再加密成 JWE,然后加密发送给 hub,整个流程如下图所示:
4.3. 授权分享
授权数据在 hub 中以 PermissionGrant 的形式存储。
type PermissionGrant struct {
Owner string `json:"owner"` // Owner DID granting permission
Grantee string `json:"grantee"` // DID the permission is granted to
Context string `json:"context"` // the data schema context
ObjectId string `json:"objectId"`
Allow string `json:"allow"` // Permissioned allowed in the form of a "CRUD" string, or "----" for no permission
Rk string `json:"key"` // proxy re-encryption key
Exp Int `json:"expiration"` //expiration time for permission
}
目前支持的操作为创建授权、读取授权、修改授权和删除授权。
4.3.1. 创建授权
创建授权的过程也就是写入数据的过程,其中PermissionGrant作为 Commit 的 payload。其执行逻辑如下:
- 根据被授权的 DID 从 resolver 中查找其公钥
- 用自己 DID 的私钥和被授权 DID 的公钥生成重加密密钥
- 填写 PermissionGrant 作为 Commit 的payload 写入 hub
4.3.2. 验证授权
当 hub 收到客户端发来的请求时,进行如下的验证:
- 从请求中获取 schema、操作、客户端 DID、objectId
- 查询所有的PermissionGrants,找到objectId匹配 且 DID 匹配的授权
- 查看操作是否允许
- 返回授权成功/失败
如果请求 JWT 中的 iss(请求发送者) 和 sub(数据所有者)一致,说明是 hub owner 发来的请求(还要进行验签),能够访问任意数据。
由于权限验证是利用Auth 层的 kid 来进行验证的,所以这里还要验证 kid 与 iss 为同一 DID 所有者才能证明是 hub owner 发来的请求。
通过授权后,hub 在返回数据时会附加 PermissionGrant 中的 key(重加密密钥),hub 客户端收到数据后,先对密钥密文进行重加密计算,然后再解密真实数据。
这里没有将重加密过程放在创建授权时,是因为存储在 hub 中的数据可能会有多个版本,我们希望授权给第三方 DID 后,第三方 DID 在授权有效期内总能获得最新数据,这样每次有数据更新,都要另外创建一份授权重加密数据,该过程比较麻烦,不适用。
而将重加密过程放在服务端,在被授权方 DID 获取数据时,返回最新数据的重加密版本,可以保证被授权方每次都获取最新数据。
4.4.3. 撤销授权
撤销授权需要 hub owner 修改PermissionGrant的权限,也即写入一个新的PermissionGrant,将其对应的权限置为空就可以。
4.4. 同步
Identity hub 只要遵循统一的标准,可以由不同的语言和数据库来实现。一个用户可以在多个不同的设备上部署自己的 Identity hub,例如可以在手机端部署Java/Swift 版本,在云端部署 Node 版本,在音乐、游戏设备上部署.Net版本。属于同一个账户的 hub 实例需要互相同步数据和请求以保证一致性。
这里可以采用 couchdb 的同步机制来实现,couchdb 可以实现对等节点的同步,而不是主从复制的模式,并且可以自解决冲突,适合多个 hub 实例数据的同步。而且在移动端使用 punchdb 也可以和 couchdb 进行同步。
同步逻辑为:
1. hub 定时从 DID resolver 中获取 owner 的 DID document
2. 从 DID document 中的 service 字段解析所有 hub 实例地址
3. 向其他实例地址查询使用的 couchdb 地址
4. 其他实例根据鉴权结果检测请求方的 DID 是否和自己一样,如果一致,则返回 couchdb 地址,否则返回错误
5. 收到couchdb 地址后,与对方的数据库进行同步
5. 主要数据结构
type Header struct {
CommittedAt string `json:"committedAt"` // commit created time
Iss string `json:"iss"` // ctrator DID
Kid string `json:"kid"` // key used to sign the commit
Sub string `json:"sub"` // owner DID
Interface string `json:"interface"` // Profile, Actions, Stores, Collections, Services(data type)
Context string `json:"context"` // schema context, "http://schema.org"
Type string `json:"type"` // WriteRequest, CommitQueryRequest, ObjectQueryRequest
Operation string `json:"operation"` // create, update, delete
CommitStrategy string `json:"commitStrategy"` // "basic"
ObjectId string `json:"objectId,omitempty"` // optional
Revision string `json:"revision"` // commit hash
Meta interface{} `json:"meta,omitempty"` //application-specific metadata
}
type Commit struct {
Protected Header `json:"protected"`
Unprotected interface{} `json:"header"`
Payload string `json:"payload"` // encrypted data
Signature string `json:"signature"`
}
type SearlizedCommit struct {
Protected string `json:"protected"`
Payload string `json:"payload"`
Header interface{} `json:"header"`
Signature string `json:"signature"`
}
type WriteRequest struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "WriteRequest"
Iss string `json:"iss"` // client DID, sender
Sub string `json:"sub"` // target DID, data owner
Aud string `json:"aud"` // hub DID, receiver
CommitData SearlizedCommit `json:"commit"` // searlized Commit object
}
type WriteResponse struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "WriteResponse"
Message string `json:"message"` // data
Revisions []string `json:"revisions"`
}
type CommitQuery struct {
ObjectId []string `json:"objectId"`
}
type CommitQueryRequest struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "CommitQueryRequest"
Iss string `json:"iss"` // client DID, sender
Sub string `json:"sub"` // target DID, data owner
Aud string `json:"aud"` // hub DID, receiver
Query CommitQuery `json:"query"`
}
type CommitQueryResponse struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "CommitQueryResponse"
Message string `json:"message"` // responsed data, such as "completely optional"
Commits []Commit `json:"commits"`
}
type ObjectQuery struct {
Interface string `json:"interface"` // Profile, Actions, Stores, Collections, Services(data interface type)
Context string `json:"context,omitempty"` // schema context, "http://schema.org"
Type string `json:"type,omitempty"` // detailed data type
ObjectId []string `json:"objectId"`
}
type ObjectQueryRequest struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "ObjectQueryRequest"
Iss string `json:"iss"` // client DID, sender
Sub string `json:"sub"` // target DID, data owner
Aud string `json:"aud"` // hub DID, receiver
Query ObjectQuery `json:"query"`
}
type ObjectMetaData struct {
Interface string `json:"interface"` // Profile, Actions, Stores, Collections, Services(data interface type)
Context string `json:"context,omitempty"` // schema context, "http://schema.org"
Type string `json:"type,omitempty"` // detailed data type
ObjectId string `json:"objectId"`
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
Sub string `json:"sub"` // target DID, data owner
CommitStrategy string `json:"commitStrategy"` //"basic"
Meta interface{} `json:"meta,omitempty"`
}
type CommitObjectResponse struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "CommitQueryResponse"
Message string `json:"message"` // responsed data, such as "completely optional"
Commits []Commit `json:"commits"`
}
type HubError struct {
Context string `json:"@context"` // "https://schema.identity.foundation/0.1"
Type string `json:"@type"` // "ErrorResponse"
Message string `json:"message"` // data
Code string `json:"code"` // status code
}
type AuthHeader struct {
Alg string `json:"alg"`
Kid string `json:"kid"` // sender's did publickey descriptor
DIDRequestNonce string `json:"did-requester-nonce"`
DIDAccessToken string `json:"did-access-token"`
}
type AuthEnvlope struct {
Header AuthHeader `json:"header"`
Payload string `json:"payload"` //data content
Signature string `json:"signature"`
}
type PermissionGrant struct {
Owner string `json:"owner"` // Owner DID granting permission
Grantee string `json:"grantee"` // DID the permission is granted to
Context string `json:"context"` // the data schema context
ObjectId string `json:"objectId"`
Allow string `json:"allow"` // Permissioned allowed in the form of a "CRUD" string, or "----" for no permission
Rk string `json:"key"` // proxy re-encryption key
Exp Int `json:"expiration"` //expiration time for permission
}
6. RoadMap
本项目基于微软的 Identity hub 开发,补充完整了一些新功能,具体计划如下:
2019.10.15
1. 主体框架逻辑
2. did-auth-jose 添加 secp256k1支持
3. 小程序对接
4. 数据的基本增删改查
5. 添加数据授权
6. 代理重加密
2019.12.31
1. hub 与 DIF resolver 对接
2. 提供与 hub 交互 sdk
3. 部署 hub 流程
4. 整理代码提到 github 与百度开源仓库
新添加的主要特性包括: 1. 对 secp256k1密钥的支持 2. 授权模型扩展,支持对单个 object 的授权 3. 数据加密存储,通过代理重加密分享 4. 删除 object 的逻辑优化 5. 对百度 DID resolver 的适配 6. sdk 适配 web 和小程序
7. Reference
- 微软数字身份
- 微软数字身份开源项目 ION
- identity-hub:Storage and compute nodes for decentralized identity data and interactions
- DID Auth
- Hub Authentication
- Data authorization in Identity hub
- hub-reference:相关仓库和资料说明
- [Interface] Node.js implementation of the Identity Hub interfaces, business logic, and replication protocol.
- [SDK] JavaScript SDK for interacting with Identity Hubs
- [SDK] Common interfaces for working with Identity Hubs in JavaScript/TypeScript
- https://forum.blockstack.org/t/syncing-data-among-identity-hub-instances/505
- https://github.com/decentralized-identity/identity-hub/issues/4
- https://pouchdb.com/guides/replication.html