🔪

Microsoft Entra Verified ID の動きをみてみた。取消編

2023/01/14に公開

Microsoft Entra Verified ID の発行および検証の流れは以下の記事でまとめました。

Microsoft Entra Verified ID の動きをみてみた。 発行編
https://zenn.dev/nerocrux/articles/ee34b2c18c5752

Microsoft Entra Verified ID の動きをみてみた。 検証編
https://zenn.dev/nerocrux/articles/32a0b1f8d7f877

本記事では、発行済みの VC の取消(失効処理)について説明します。

概要

VC を発行する際に、Issuer が Microsoft Entra にて設定したポリシーによって、VC の有効期間を決めることが可能です。しかしいろんな事情で VC の有効期限が迎える前に、発行済みの VC を取消したい要件もあり得ます。

Microsoft Verified ID では、発行済みの VC に対して、Microsoft Entra ポータルの GUI より失効させる(revoke)機能を提供しており、Issuer が簡単に発行済みの VC を取り消すことが可能です。具体的な操作方法は、以下の Microsoft の公開資料をご参考ください。

以前に発行された検証可能な資格情報を取り消す
https://learn.microsoft.com/ja-jp/azure/active-directory/verifiable-credentials/how-to-issuer-revoke

「検証編」で説明したとおり、Microsoft Verified ID サービスを利用する場合、VC / VP の有効性検証は Microsoft Verified ID が実施するので、Verifier としては特に VC 取り消されたかどうかの検証方法を知る必要がありません。VC が Issuer によって取り消された場合、当然 VC の検証が失敗となり、Microsoft Verified ID サービスが Verifier に VC の検証が失敗したことを通知します。

ただし、もしアプリ開発者が自前で Wallet を作成することを考えている場合、あるいは Verifier は Microsoft のサービスを利用せずに、自前で VC の有効性を検証する場合では、VC の有効性管理の仕組みをちゃんと理解する必要があります。

また、VC の有効性状態は、Microsoft Verified ID 内部のデータベース上で管理されていて、Verifier としては Microsoft に問い合わせればよい、というような単純な仕組みになっているわけではありません。そこらへんの仕組みや設計思想等を理解する必要があると言えます。

VC 有効性管理の仕組み

Microsoft Verified ID の VC 有効性管理は、W3C Status List 2021 という仕様を実装しています。

この仕組みは雑にいうと、Issuer が発行したすべての VC の有効化状態一覧をバイナリー形式のリスト(Status List)で、すべての Verifier に提供するものとなります。

上図は Status List の概念図となります。Status List は(最小・既定) 16KB の bitstring となっており、131,072 個の VC のステータスを管理しています。

詳細なデータ構造は後ほど説明しますが、Verifier に提示された VC の中に、Status List から VC の有効性状態を調べるための Index 値が含まれています。例えば Index 値が 14 の VC が取り消された(revoked)場合、Status List の 14 番目の bit の値が 1 と記録されます。そうでなければ、bit 値が 0 になります。

この Status List は GZIP で圧縮され、Base 64 エンコードするため、文字列で表現できる形になっており、Verifier はこのリストを Microsoft Verified ID サービスの Rest API から取得可能となっています。

Verifier は VC を受信した後に、VC を解読して、取消状況を調べるための Index 値を取得します。そして、Microsoft から Status List を取ってきて、Index 値からその VC の取消状況を調べます。

※ 繰り返しになりますが、この作業は VC 検証時に Microsoft Verified ID サービスが内部でやってくれるので、Verifier は通常この作業を実装する必要がありません。

なぜこのような仕組みにしたか

Verifiable Credentials の発行者(Issuer)と検証者(Verifier)は通常、異なるパーティーとなっています。
Issuer は VC の有効性を管理していますが、プライバシーの配慮上、ユーザーが Issuer から取得した VC をどの Verifier に提示したかを Issuer から追跡できないようにしたいです(でなければ、中央集権式の IdP とは変わらないじゃないかという解釈になってしまいます)。

VC にユニークな識別子を付与し、Verifier が単純に VC の識別子を使って、Issuer に「この VC は取り消されているか」を問い合わせていきますことは技術的に当然実現可能ですが、Issuer は「○○ VC の有効性が調査された」を把握することができるようになってしまうため、ユーザーの行動をある程度追跡可能になってしまいます。

どの VC が利用され、その VC の有効性が調査されたかを Issuer が把握できないようにするため、Verifier が全 VC の有効性状態リストを取得して、Verifier のシステム上で VC の Index 値より VC の有効性を調べられるようにすることで、ユーザーのプライバシーがより強固に守られます。

Verifier が VC の有効性検証方法(Microsoft の実装)

Microsoft Verified ID の場合、Issuer が Authenticator に発行する VC データ(JWT 形式)をパースすると、以下のような形のデータを取得できます。
VC 検証時、Authenticator はこの VC を vp_token に含めて、Microsoft Verified ID に送信します。

{
  "alg": "ES256K",
  "kid": "did:ion:EiALbbi7GmxxxxxxxxxxxxHI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx#792ca9888b1c4cc184fad3b63365f323vcSigningKey-b07dd",
  "typ": "JWT"
}.{
  "vc": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1"
    ],
    "type": [
      "VerifiableCredential",
      "VerifiedCredentialExpert"
    ],
    "credentialSubject": {
      "firstName": "Peichao",
      "lastName": "Yu"
    },
    "credentialStatus": {
      "id": "urn:uuid:cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c?bit-index=12",
      "type": "RevocationList2021Status",
      "statusListIndex": 12,
      "statusListCredential": "did:ion:EiALbbi7GmxxxxxxxxxxxxHI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx?service=IdentityHub&queries=W3sibWV0aG9kIjoiQ29sbGVjdGlvbnNRdWVyeSIsInNjaGVtYSI6Imh0dHBzOi8vdzNpZC5vcmcvdmMtc3RhdHVzLWxpc3QtMjAyMS92MSIsIm9iamVjdElkIjoiY2NhOGJkMGEtODNjMi00YWUxLTlmNDMtZTk5OTQzY2RmYTVjIn1d"
    },
    "exchangeService": {
      "id": "https://beta.did.msidentity.com/v1.0/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/verifiableCredential/card/exchange",
      "type": "PortableIdentityCardServiceExchange2020"
    }
  },
  "jti": "urn:pic:2cd7dd2232ae49afa0f852ceeefce60e",
  "iss": "did:ion:EiALbbi7GmxxxxxxxxxxxxHI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx",
  "sub": "did:ion:EiBtR0YwYDcxxxxxxxxxxxQH89_6Q:eyJkZWx0YSI6eyJxxxxxxxxxxxxxx",
  "iat": 1670500048,
  "exp": 1673092049
}.[Signature]

VC に credentialStatus というオブジェクトが存在していることが確認できます。そこに statusListCredential というパラメータが存在しており、このパラメータは <Issuer DID>?service=IdentityHub&queries=<...>のようなスキーマとなっています。

queries は base64 encode されている文字列で、デコードすると以下のようなデータが取れます。

[
  {
    "method":"CollectionsQuery",
    "schema":"https://w3id.org/vc-status-list-2021/v1",
    "objectId":"cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c"
  }
]

で、この queries をそのまま Microsoft Verified ID の Identity Hub と呼ばれるエンドポイントに投げますと、VC に関連する Status List を取得できます。

Identity Hub は Issuer の DID を resolve すると取れます。まぁ今のところは https://hub.did.msidentity.com/v1.0/<Tenant ID> のスキーマを持つ固定 URL になっているので、固定値をそのまま使ってもいい気がしますが。

Status List を取得するためのリクエストは以下のようなものになります。

HTTP POST
https://hub.did.msidentity.com/v1.0/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

{
  "requestId": "c5784162-84af-4aab-aff5-f1f8438dfc33",
  "target": "did:ion:EiALbbi7GmxxxxxxxxxxxxHI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx",
  "messages": [
    {
      "descriptor": {
        "method": "CollectionsQuery",
        "schema": "https://w3id.org/vc-status-list-2021/v1",
        "objectId": "cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c"
      }
    }
  ]
}

リクエストにある requestId は現時点ではおそらく UUID 形式の文字列であれば何でもよく、特に用途がないはずです。将来的にログに記録され Microsoft Entra Portal から確認できるようになるかも・・・と想像しています。

target パラメータには Issuer の DID となり、messages.descriptor には queries をそのまま記載します。

※ ちなみにここら辺の API 仕様はどこでも見当たらなかったので、自前で VC を検証しようとする人がいれば困ってしまうので整備してほしいですが、普通に Microsoft Verified ID サービスを使っていれば触らないのでちゃんと整備してくれないのも仕方がないでね…。

上記リクエストに対して、以下のようなレスポンスが返ってきます。

{
  "requestId": "c5784162-84af-4aab-aff5-f1f8438dfc33",
  "replies":
  [
    {
      "messageId": "bafkreig6d3ouzy47w346pf6rrsh7rywie47uezpp6kaeyhy5fpyocwcuwe",
      "status":
      {
        "code": 200
      },
      "entries":
      [
        {
          "descriptor":
          {
            "method": "CollectionsWrite",
            "schema": "https://w3id.org/vc-status-list-2021/v1",
            "dataFormat": "application/vc+jwt",
            "objectId": "cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c",
            "clock": 0,
            "cid": "bafkreicelhoivs5q7zti5nkqwocny3e65p6hxya52hho4ibzshrshu4wgy"
          },
          "data": "ZXlKaGJHY2lPaUpGVXpJMU5rc2lMQ0pyYVdRaU9pSmthV1E2YVc5dU9rVnBRVXhpWW1rM1IyMVVVbkJoZWxwQ05EZDNOVXBTY0daeGFuaFlWRlJtZGxoQlZWaEdVV3N6TjBoSlgzYzZaWGxLYTFwWGVEQlpVMGsyWlhsS2QxbFlVbXBoUjFaNlNXcHdZbVY1U21oWk0xSndZakkwYVU5cFNubGFXRUp6V1ZkT2JFbHBkMmxhUnpscVpGY3hiR0p1VVdsUGJuTnBZMGhXYVdKSGJHcFRNbFkxWTNsSk5sY3pjMmxoVjFGcFQybEpNMDlVU21wWlZHczBUMFJvYVUxWFRUQlpNazE0VDBSU2JWbFhVWHBaYWxsNlRYcFpNVnBxVFhsTk0xcHFWVEpzYm1KdGJIVmFNSFJzWlZNeGFVMUVaR3RhUTBselNXNUNNVmx0ZUhCWk1IUnNaVlZ3TTJGNVNUWmxlVXBxW..."
        }
      ]
    }
  ]
}

レスポンスの replies.entries.data の部分は、Base64 Encode されている JWT となっているので、これを Base 64 Decode > JWT Decode すると、以下の JSON を取得できます。

{
  "vc": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://w3id.org/vc-status-list-2021/v1"
    ],
    "type": [
      "VerifiableCredential",
      "StatusList2021Credential"
    ],
    "credentialSubject": {
      "id": "urn:uuid:cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c",
      "type": "RevocationList2021",
      "encodedList": "H4sIAAAAAAAAA-3BAQ0AAAzDoOb-RV_IgLoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWPE1h7uqoYQAA"
    }
  },
  "jti": "did:ion:EiALbbi7GmTRpazZB47w5JRpfqjxXTTfvXAUXFQk37HI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx?service=IdentityHub&queries=W3sibWV0aG9kIjoiQ29sbGVjdGlvbnNRdWVyeSIsInNjaGVtYSI6Imh0dHBzOi8vdzNpZC5vcmcvdmMtc3RhdHVzLWxpc3QtMjAyMS92MSIsIm9iamVjdElkIjoiY2NhOGJkMGEtODNjMi00YWUxLTlmNDMtZTk5OTQzY2RmYTVjIn1d",
  "iss": "did:ion:EiALbbi7GmTRpazZB47w5JRpfqjxXTTfvXAUXFQk37HI_w:eyJkZWx0YSI6eyxxxxxxxxxxxxxxxxxxx",
  "sub": "urn:uuid:cca8bd0a-83c2-4ae1-9f43-e99943cdfa5c",
  "iat": 1672060313
}

vc.credentialSubject.encodedList というパラメータに含まれている文字列は、(エンコードされている)Status List となります。この encodedList は、bitstring を GZIP で圧縮した上で、Base 64 エンコードしたものになります。

The encodedList property of the credential subject MUST be the GZIP-compressed [RFC1952], base-64 encoded [RFC4648] bitstring values for the associated range of verifiable credential status values. The uncompressed bitstring MUST be at least 16KB in size.

つまり、Base 64 Decode して GZIP 解凍すれば、もとの Status List の bitstring を取得可能となります。

また、VC の credentialStatus オブジェクトの statusListIndex に、該当 VC の Index の値が記載されています。上記例では、statusListIndex の値は 12 となります。なので、デコード後の encodedList の 12 番目の bit は 0 か 1 かを調べることで、VC が有効か取り消されたかを調べることができます。

(雑に動かしてみたときのコード)

import { inflate, deflate } from "pako"

const encodedList = "H4sIAAAAAAAAA-3BMQEAAAzDoFzzL3lCCtQFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw4AEEi4LOqGEAAA";
const statusListIndex = parseInt(12, 10);

// decode & decompress
const decoded = Buffer.from(encodedList, 'base64');
const buf = Buffer.from(inflate(decoded));

// find bit value
const byte = statusListIndex >> 3;
const offset = statusListIndex & 0b111;
const shift = 7 - offset;
const bit = (buf[byte] >> shift) & 1;

// if bit is 1, revoked / suspended; otherwise (0), valid. 
const isRevoked = bit === 1;

Microsoft Verified ID を利用して VC 検証を行う場合、Microsoft 側が Status List から VC が Revoke されているかどうかを検証します。VC が取り消されている場合、Microsoft Verified ID から Authenticator に以下のようなレスポンスを返しています。

{
  "requestId": "fbe421957c177b6f057cbc6f46f1517f",
  "date": "Thu, 12 Jan 2023 11:46:08 GMT",
  "mscv": "yP9ZsI88TvKwGQVAPu74Mw.5.5",
  "error": {
    "code": "badRequest",
    "message": "The request is invalid.",
    "innererror": {
      "code": "tokenError",
      "message": "The presented verifiable credential with jti 'urn:pic:c2b845bfbcb24f7095079bebea358889' is revoked."
    }
  }
}

そうすると Authenticator の画面上に、以下のような(何の情報量もない)エラー画面が表示されます。「詳細の表示」をタップすると一応それっぽいエラーメッセージが確認できますが、これもちょっと改善してほしい気持ちがありますね(「取消」は一応想定内の動作なので、エラーぽく見せるのはどうかなと)。

終わりに

これで多分 Microsoft Verified ID の機能を一通り触ってなんとなく理解したと思います。そこそこ長い旅でしたが、Microsoft の Verifiable Credentials の実装はこれからも多分いろいろ進化していくと思います。

Microsoft それなりに真面目に取り組んでいると思いますので、これをきっかけに Verifiable Credentials が流行るといいですね。

Discussion