2022年11月6日日曜日

発行済みVerifiable Credentialsの状態確認を行うStatus List 2021の仕様と動作

こんにちは、富士榮です。

今日はちょっとマニアックなところを。(完全に自分向けのメモです)


Microsoft Entra Verified IDは発行済みのVerifiable Credentialsの状態(有効・取り消し済み)を管理するためにDecentralized Identity Foundation(DIF)のStatus List 2021とIdentity Hubという仕様を使っています。

ちなみにその後Identity Hubはdecentralized web nodeという仕様になっているので、もうIdentity Hubの仕様はすでに古いものとなっています。

Decentralized Web Nodeの仕様


基本的にやりたいことはとしては、

  • 発行されたVerifiable Credentialの状態をHolderやVerifierは確認したい
  • ただ、Issuerに状態を問い合わせるのでは従来の集中モデルと変わらずプライバシー保護の観点などからも好ましくない
ということなので、Issuerに問い合わせしなくてもVerifiable Credentialの有効状態を確認できる必要があります。

ということでStatusList2021CredentialというタイプのVerifiable Credentialを発行し、IssuerのIdentity Hubに発行、他のノード等からIssuerが把握することなく閲覧が可能な状態を実現します。
なお、スケーラビリティへの考慮から発行済みVerifiable Credentialsのリストをなるべくコンパクトに保持する必要があり、ビット配列で状態を保持する形の仕様となっています。


今回はHolderやVerifier(主にはこちらが確認することが多いとは思いますが)などVerifiable Credentialを受け取ったエンティティがどのように有効性確認を行うのか?について触れたいと思います。(あくまで現時点でのMicrosoft Entra Verified IDでの実装をベースにしています)

■大まかな流れ

大まかにはこんな流れです。
  1. Verifiable Credentialを受け取る
  2. 受け取ったVC内のCredentialStatusを取得(主に以下2つの値が重要)
    • statusListIndex
    • statusListCredential
  3. Issuer DIDのDID DocumentからIdentity HubのserviceEndpointを取得
  4. 2で取得したstatusListCredentilalからIdentity HubへPOSTすべきBodyを生成、Identity Hubへ投げ込む
  5. Identity HubからstatusList2021CredentialというTypeのVCが返ってくるのでencodedListを取得
  6. 2でとっておいたstatusListIndexでencodedListのオフセットを確認し、ビットが立っていたら取り消し済み、倒れていたら有効として判断

■実際に取得してみる

まずは、VCからCredentialStatusの取得です。これは単純にJWTをパースすればOKです。
{
  "vc": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1"
    ],
    "type": [
      "VerifiableCredential",
      "hogehogeVC"
    ],
    "credentialSubject": {
      "name": "hoge taro",
      "mail": "hoge@exampple.jp"
    },
    "credentialStatus": {
      "id": "urn:uuid:6fa4e682-f427-41cf-b0e5-59179d66fea3?bit-index=5",
      "type": "RevocationList2021Status",
      "statusListIndex": 5,
      "statusListCredential": "did:web:example.jp?service=IdentityHub&queries=W3sibWV0aG9kIjoiQ29...(省略)...jZmZWEzIn1d"
    },
    (以下省略)


大事なのはstatusListIndex、statusListCredentialなかでもqueriesの値です。
queriesはbase64urlエンコードされたjsonなのでデコードすると後からIdentity Hubへ投げ込む値が出てきます。
[
	{
		"method": "CollectionsQuery",
		"schema": "https://w3id.org/vc-status-list-2021/v1",
		"objectId": "6fa4e682-f427-41cf-b0e5-59179d66fea3"
	}
]


では、このデータをIdentity Hubに投げ込んでいきます。
まずはIdentity Hubのエンドポイントを取得する必要があります。statusListCredentialを見ると当該のDIDのserviceの種類がIdentityHubとなっているところにqueriesの値を投げ込むべし、ということが書いてあり、MSの仕様ではIdentityHubへ聞きに行く必要があるということが指し示されているからです。
ということでIssuer DIDをResolveします。これまでもこのBlogで触れてきたuniversal resolverを使ってもいいですし、Microsoftが用意しているdiscoveryエンドポイントを使ってもいいでしょう。今回はMicrosoftのものを使いました。
https://discover.did.msidentity.com/1.0/identifiers/{IssuerのDID}
でGETするとDID Documentが返ってきます。
{
  "id": "did:web:example.jp",
  "@context": [
    "https://www.w3.org/ns/did/v1",
    {
      "@base": "did:web:example.jp"
    }
  ],
  "service": [
    {
      "id": "#linkeddomains",
      "type": "LinkedDomains",
      "serviceEndpoint": {
        "origins": [
          "https://example.jp/"
        ]
      }
    },
    {
      "id": "#hub",
      "type": "IdentityHub",
      "serviceEndpoint": {
        "instances": [
          "https://hub.did.msidentity.com/v1.0/{tenant id}"
        ]
      }
    }
  ],
  (以下省略)

この中のIdentityHubのserviceEndpointの値を取得します。Entra Verified IDの場合、https://hub.did.msidentity.com/v1.0/{Azure ADのテナントID}という構造になっていますので、あらかじめわかっている場合はわざわざresolveしなくてもいいかもしれません。

このエンドポイントに先ほど取得したqueriesの値を含むjsonをPOSTすることでstatusList2021Credentialが取得できます。

まずはPOSTデータの作成です。
構造としてはこんな感じになっています。
{
  "requestId": "c5784162-84af-4aab-aff5-f1f8438dfc33",
  "target": "did:web:example.jp",
  "messages": [
    {
      "descriptor": {
        "method": "CollectionsQuery",
        "schema": "https://w3id.org/vc-status-list-2021/v1",
        "objectId": "6fa4e682-f427-41cf-b0e5-59179d66fea3"
      }
    }
  ]
}

requestIdはuuid-v4形式の値を指定します。単なるリクエストとレスポンスを紐づけるための識別子なので値はなんでもOKです。
targetはissuerのDIDを指定します。
messagesの中のdescriptorに先ほど取得したqueriesの値をセットします。

ということでPOSTすると以下のレスポンスが返ってきます。
{
    "requestId": "c5784162-84af-4aab-aff5-f1f8438dfc33",
    "replies": [
        {
            "messageId": "bafkreicksslcxa7xraqg6luqqfsezkpfalpqdqk6jzealwqxrkv7svu5vy",
            "status": {
                "code": 200
            },
            "entries": [
                {
                    "descriptor": {
                        "method": "CollectionsWrite",
                        "schema": "https://w3id.org/vc-status-list-2021/v1",
                        "dataFormat": "application/vc+jwt",
                        "objectId": "6fa4e682-f427-41cf-b0e5-59179d66fea3",
                        "clock": 0,
                        "cid": "bafkreib5cnsi7kw5ya6otq2lybvwnqlh2ga2gmud7x7t46f4zkkcca6s5y"
                    },
                    "data": "ZXlKaGJHY2lPaUpG...(省略)...IydDNXaWhR"
                }
            ]
        }
    ]
}

大事なのはdataの部分です。
この値はbase64エンコードされたVerifiable CredentialsなのでデコードするといつものeyJ...が出てきます。もちろんこのjwtの署名検証はするとして、中身を確認していきます。
{
  "vc": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://w3id.org/vc-status-list-2021/v1"
    ],
    "type": [
      "VerifiableCredential",
      "StatusList2021Credential"
    ],
    "credentialSubject": {
      "id": "urn:uuid:6fa4e682-f427-41cf-b0e5-59179d66fea3",
      "type": "RevocationList2021",
      "encodedList": "H4sIAAAAAAAAA-3BAQEAAAgCoPw_qmsOEfgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAhAJmaUnkqGEAAA"
    }
  },
  "jti": "did:web:example.jp?service=IdentityHub&queries=W3sibWV0aG9kIjo3...(省略)...zlkNjZmZWEzIn1d",
  "iss": "did:web:example.jp",
  "sub": "urn:uuid:6fa4e682-f427-41cf-b0e5-59179d66fea3",
  "iat": 1667651604
}

大事なのはencodedListです。
この値はbase64でエンコードされgzipされたビット配列なのでデコードし、確認したいVCのstatusListIndexの値のオフセットの部分のビットの状態を確認します。

例えば、以下のような状態になっている場合は上から下に向けて1-2-4-8-...という形で桁が上がっていくのでこの例だと7番目だけがActive、あとは取り消し済みなのでencodedListは2進数でいうと「...110111111」という形で対応するIndexにあたるビットが立ちます。

ここまでわかればあとは簡単ですね。
statusListIndexに指定された値(上の例だと5)に該当するオフセットの部分をmaskしてANDをとれば当該ビットが立っているのかどうかがわかり、当該のVCの状態が把握できます。


ということで今回はStatus List 2021の仕様とMSの実装について触れました。
(実際はMSのAPIの中でよろしくやってくれるので自前でコードを書く必要はないんですけどね)






0 件のコメント:

コメントを投稿