🔐

EUのCOVID-19ワクチン接種証明書はCWTを使っている

2021/07/10に公開

2021年7月1日に、EUでCOVID-19ワクチン接種のデジタル証明書(EU Digital COVID Certificate:EUDCC)の運用が正式にスタートした。このEUDCC、技術的に面白いところがあって、例えば、Base45という新たなエンコード方式を導入していたり(ビットコインのBase58を彷彿させますよね)、フォーマットとしてCWT(CBOR Web Token)を採用していたりする。CWTは、JWTのバイナリ版と言ってよいもので、比較的新しく故にマイナーな規格である。最近、個人的にCWTと戯れていることもあって、このEUDCCの規格まわりの調査と、テストデータを使った検証コードの実装を行ってみたので備忘録としてまとめておく。

なお、こうした新しい技術を取り入れた規格をこのCOVID騒動のさなか1年足らずで出せるのすごいなーということで、付録として規格化と実装の経緯も軽く調べてみている。この界隈の日本の動向には疎いのだが、1つの先行事例として参考になるかもしれない。

はじめに

EU Digital COVID Certificate (以下、EUDCC)は、欧州委員会とEU加盟国が定義したCOVID-19のワクチン接種証明の共通フォーマットである。デジタルと紙の両方で扱えるように設計されており、QRコードに入るコンパクトな表現としてCWT(CBOR Web Token)が採用されている。CWTは、ざっくり言うとJWT(JSON Web Token)のバイナリ版。JWTと比較して、コンパクトにエンコードできることに加え、JWT系規格のセキュリティ上の問題が解消されているというメリットもある。

以下、この EUDCC のあらましと技術仕様、テストデータを使った検証コードの実装と評価について書いていく。

ちなみに、CWTにフォーマットされたEUDCC自体の検証コードは Python CWT で以下のようにシンプルに書けてしまう。検証鍵のロード(1)と、検証&デコード(2)で実質2行。簡単ですね。

import cwt
from cwt import Claims, load_pem_hcert_dsc

# EUDCC発行者の証明書(検証用の公開鍵)
# A DSC(Document Signing Certificate) issued by a CSCA (Certificate Signing Certificate Authority) quoted from: https://github.com/eu-digital-green-certificates/dgc-testdata/blob/main/AT/2DCode/raw/1.json
dsc = "-----BEGIN CERTIFICATE-----\nMIIBvTCCAWOgAwIBAgIKAXk8i88OleLsuTAKBggqhkjOPQQDAjA2MRYwFAYDVQQDDA1BVCBER0MgQ1NDQSAxMQswCQYDVQQGEwJBVDEPMA0GA1UECgwGQk1TR1BLMB4XDTIxMDUwNTEyNDEwNloXDTIzMDUwNTEyNDEwNlowPTERMA8GA1UEAwwIQVQgRFNDIDExCzAJBgNVBAYTAkFUMQ8wDQYDVQQKDAZCTVNHUEsxCjAIBgNVBAUTATEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASt1Vz1rRuW1HqObUE9MDe7RzIk1gq4XW5GTyHuHTj5cFEn2Rge37+hINfCZZcozpwQKdyaporPUP1TE7UWl0F3o1IwUDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFO49y1ISb6cvXshLcp8UUp9VoGLQMB8GA1UdIwQYMBaAFP7JKEOflGEvef2iMdtopsetwGGeMAoGCCqGSM49BAMCA0gAMEUCIQDG2opotWG8tJXN84ZZqT6wUBz9KF8D+z9NukYvnUEQ3QIgdBLFSTSiDt0UJaDF6St2bkUQuVHW6fQbONd731/M4nc=\n-----END CERTIFICATE-----"

# 検証対象のEUDCC
# An EUDCC (EU Digital COVID Certificate) quoted from: https://github.com/eu-digital-green-certificates/dgc-testdata/blob/main/AT/2DCode/raw/1.json
eudcc = bytes.fromhex("d2844da20448d919375fc1e7b6b20126a0590133a4041a61817ca0061a60942ea001624154390103a101a4617681aa62646e01626d616d4f52472d3130303033303231356276706a313131393334393030376264746a323032312d30322d313862636f624154626369783155524e3a555643493a30313a41543a31303830373834334639344145453045453530393346424332353442443831332342626d706c45552f312f32302f31353238626973781b4d696e6973747279206f66204865616c74682c20417573747269616273640262746769383430353339303036636e616da463666e74754d5553544552465241553c474f455353494e47455262666e754d7573746572667261752d47c3b6c39f696e67657263676e74684741425249454c4562676e684761627269656c656376657265312e302e3063646f626a313939382d30322d323658405812fce67cb84c3911d78e3f61f890d0c80eb9675806aebed66aa2d0d0c91d1fc98d7bcb80bf00e181806a9502e11b071325901bd0d2c1b6438747b8cc50f521")

# 1. EUDCCの発行者の証明書を、検証用の公開鍵としてロード
public_key = load_pem_hcert_dsc(dsc)

# 2. 検証&デコード
decoded = cwt.decode(eudcc, keys=[public_key])

# 3. EUCCの中身(ペイロード)の取得。ペイロード部分は以下の通りJSON形式である
claims = Claims.new(decoded)
# claims.hcert[1] == decoded[-260][1] ==
# {
#     'v': [
#         {
#             'dn': 1,
#             'ma': 'ORG-100030215',
#             'vp': '1119349007',
#             'dt': '2021-02-18',
#             'co': 'AT',
#             'ci': 'URN:UVCI:01:AT:10807843F94AEE0EE5093FBC254BD813#B',
#             'mp': 'EU/1/20/1528',
#             'is': 'Ministry of Health, Austria',
#             'sd': 2,
#             'tg': '840539006',
#         }
#     ],
#     'nam': {
#         'fnt': 'MUSTERFRAU<GOESSINGER',
#         'fn': 'Musterfrau-Gößinger',
#         'gnt': 'GABRIELE',
#         'gn': 'Gabriele',
#     },
#     'ver': '1.0.0',
#     'dob': '1998-02-26',
# }

EU Digital COVID Certificate とは

EUDCC (EU Digital COVID Certificate) は、欧州委員会とEU加盟国が定義したCOVID-19のワクチン接種証明書の共通フォーマットである。正確にいうと「ワクチン接種済(Vaccination Certificate)」「罹患したが回復(Recovery Certificate)」「検査結果が陰性(Certificate for Test Results)」の3種類の証明書をまとめて EUDCC と呼ぶ。EU Digital Green Certificate(DGC) とも呼ばれている(元はこちらが正式名で、後述する仕様群も"DGC"を使っているものが多い。一方で"DCC"に表記を更新している仕様もあり現状は混在状態。同じものと思っておけばよい)。EU域内での安全な移動を実現することを目的としており、EU加盟国間での相互運用性、不正・偽造防止ためのセキュリティが必須要件である。当然ながら個人情報にも配慮されており、個人情報が(移動先の)別の国に渡ることがないように設計されている。

EUDCC example of digital format
※画像引用元:covid-certificate_paper_guidelines_en.pdf

EUDCCはデジタルと紙で扱えること、QRコードで表現できることを要件としており、このためにコンパクトなエンコード方式を採用している。詳しくは後述するとして、(Vaccination Certificateの場合)ある個人がワクチン接種済みであることを示す必要最低限の情報が証明書に記載され、そこに信頼できる発行機関の電子署名が付与されている。この電子署名の検証鍵は、検証アプリ向けに公開されており、これを使って正規の証明書であることを確認することができる。こうしたフォーマットやトラストフレームワークなどの技術仕様は、eHealth Network が発行・公開している。リファレンス実装やテストデータも、オープンソースとしてGitHubにて公開されている。このあたりも今どきと言えば今どきですが偉いですね。

技術仕様の概要

EUDCC関連の技術仕様は、複数のドキュメントにまたがって構成されている。証明書のフォーマットにとどまらず、EU加盟各国間をまたがった検証を実現するためのゲートウェイ(DGCG。以下の概要図参照)の仕様や、このDGCGと連携する各国のバックエンドサーバが持つ機能の仕様、発行・検証のためのアプリの仕様までこの中に含まれる。

DGCG Overview

本記事では、全体像を紹介したあと、広範な技術仕様の中からCWTまわり(EUDCCのフォーマット)の技術仕様にフォーカスして解説する。

仕様一覧

EUDCCの一連の技術仕様の主なものは、eHealth Networkが「Technical Specifications for Digital Green Certificates」(以下の5部構成)として策定・公開している。それぞれの概要は以下のとおり。

  • Volume 1 (Electronic Health Certificate Specification)
    • 基本仕様書であり、EUDCCの汎用データ構造やトラストフレームワークを定義している。紙やデジタル媒体でQRコードとして表現するためのエンコード方法についても規定している。この文書の最新版は、Electronic Health Certificate Specificationとして、Githubプロジェクトで管理されている。
  • Volume 2: European Digital Green Certificate Gateway
    • EU加盟各国をまたがったEUDCCの相互検証のためのトラストフレームワークを支える Digital Green Certificate Gateway(DGCG)のアーキテクチャ、技術仕様を規定している。上記の概要図をみると、なんらかのPub/Sub基盤をイメージしてしまうが、実体はEUDCCの検証用の証明書(DSC: Document Signing Certificate)やそのCA証明書のレジストリであり、これらをCRUDできるREST APIサーバである。API仕様は、 いまどきな Open API SpecでDigital Green Certificate Gateway として、実装は Github - dgc-gateway として公開されている。
  • Volume 3: Interoperable 2D Code
    • Volume 1に記載されているEUDCCの汎用データ構造やエンコード方法を具体的に規定したもの。この記事で主に扱うのはこの内容。Volume 1と重複している内容が多いが、コンフリクトがある場合には、Volume 1が優先される。
  • Volume 4: European Digital Green Certificate Applications
    • EU全体でのEUDCC検証に必要な、発行者アプリ、ウォレットアプリ、検証アプリ、加盟各国のバックエンドサービスのコア機能について解説している。iOSやAndroidアプリのリファレンス実装は、Github - eu-digital-green-certificatesで公開されている。
  • Volume 5: Public Key Certificate Governance
    • DGCGとこれを利用するEU各国のバックエンドサービスの間では、幾つかのタイプの証明書が利用されるが、このPKIのガバナンスについて規定している。具体的には、それぞれの証明書のテンプレート、有効期間、失効を含むライフサイクル管理について詳しく記載されている。これも Github - Digital Green Certificate – Public Key Certificate Governance としてGithub上で管理されている。

この5つで仕様が完結するわけではない。上記のリスト中で取り上げているAPI仕様もそうだが、他にも重要な関連文書・ガイドライン・仕様がある。以下、いくつかピックアップしておく。

さておき、この記事ではCWT周辺にフォーカスしたいのでVolume 3を中心に見ていく。DGCG含むPKI周辺も面白いので、興味のある方はVolume 2, 5あたりを読んでみるのもおすすめ。

データ構造・フォーマット

EUDCCは、2次元コード(QRコード)のフットプリントをできる限り小さくするため、エンコード方式して CBOR (Concise Binary Object Representation) を、データの完全性を保証するために、COSE (CBOR Object Signature and Encryption)を採用している。このCOSE/CBORをベースにEUDCCのクレームを表現する方法が CWT (CBOR Web Token) である。

ちなみにCBORは、JSONのバイナリ版的なもので、可読性は無いがデータをコンパクトに表現できる。現状ではマイナーな規格だが、WebAuthn でアテステーション情報のエンコード方式として採用されているので、Web界隈でも認知度は上がっている。一方のCOSEは、JSONにおけるJOSE(JSON Object Signing and Encryption)のCBOR版。JOSEの後発であることもあって、JOSE のセキュリティ上の問題が解消されている等、コンパクトさ以外にセキュリティ上のメリットもある。

COSEヘッダ

CWTクレームをペイロードとして運ぶCOSEのヘッダには、以下の2つを指定する。いずれも署名で保護される領域(Protected Heder)に指定する。

  • alg: 署名アルゴリズム。JWTでもおなじみのES256PS256の2つが利用できる。前者がプライマリアルゴリズム、後者がセカンダリアルゴリズムとして指定されている。セカンダリは、プライマリが規制などで使えない場合のみ利用される。
  • kid: EUDCCへの署名に使った公開鍵(DSC: Document Signing Certificate)を識別するID。kidは本来保護する必要は無く、Unprotected Headerに指定するのが一般的だと思われるが、EUDCCでは Protected Headerに指定することとしている。なお、kidは、EUDCC仕様の規定として、DSCのSHA256ハッシュ値の先頭8byteを利用する。衝突の可能性があるため、検証アプリは、EUDCCに含まれるkidで、すべてのDSCをチェックしなければならない。

CWTクレーム

次にCOSEで運ばれるペイロード(CWTクレーム)について。CWTの仕様(RFC8392)は、クレームの基本情報(証明書の発行者や有効期限などの基本情報)を定義しているが必須項目は定めていない。「どの項目を用いるか」といった運用は、利用する側にゆだねられている。Volume 3では、以下の4項目を共通データセットとして定めている。

  • iss: 発行者情報。ISO 3166-1 alpha-2の国名コード(DE等)が入る。
  • iat: 発行日時。DSCの有効期間より前に設定してはならない。
  • exp: 失効日時。DSCの有効期間を超えてはならない。
  • hcert: ワクチン接種証明情報など証明書タイプ毎のペイロード情報。この hcertクレームは、Electronic Health Certificate Specification (= Volume 1) で定義され、これ自体はEUDCC以外の健康証明書に利用できる汎用的なものになっている。EUDCCの場合、キーに1(EUDCCであることを表す)が指定され、バリューに上記の Technical Specifications for EUDCCs - JSON Schema Specification で定義されたJSONデータが設定される。

伝送エンコード方式

上記のCWTをそのままRawデータとして伝送してもよいが、QRコードで表現する場合には、よりコンパクトにする処理を必須としている。具体的には、下図に書いているように、CWTを作った後で以下の2つの処理をおこなう。

  • ZLIB圧縮
  • Base45エンコード

Serialization Process

Base45エンコードは、おそらくこのEUDCCのために開発されたエンコード方式で、IETFに The Base45 Data Encoding として提案されている(2021-07-10時点では、まだ個人ドラフトのステータス)。QRコードは、バイナリモードでもデータをUTF-8文字列として処理しようとするため、任意のバイト列を直接エンコードできず、一旦Base64等にエンコードしてQRコード化することが一般的である。Base45は、Base64/32/16(RFC4648)と比べて、よりコンパクトなQRコードが作れるとのこと。

検証方法

EUDCCが正規の機関から発行された有効な証明書であることを検証する方法は、基本的にはCOSEの仕様にしたがう。EUDCCならではのチェックポイントとして、上記のexpiatの値とDSCの有効期間の齟齬のチェックと、hcertのバリデーションがあるが、それ以外はCOSEの検証ステップのままである。

問題は、検証に必要なEUDCCの発行者の証明書(DSC:Document Signing Certificate)をどう取得するかだが、DSCリストの提供方法はEU加盟各国にゆだねられている。Volume 1には、一例として JWTでおなじみの JWK set format(RFC7517 section 5) に署名する例が挙げられている。スウェーデンがこの方式を採用しており、Document Signer Certificate (DSC) Trust List - Format Specification として仕様を公開している(公開エンドポイントのURLを含めて ここに情報がまとまっている)。

一方で、Githubで公開されている検証アプリのリファレンス実装は、Volume 4 で定義されているVerifier APIを利用してEU加盟各国のバックエンドサービス(下図の dgca-verifier-service)からDSCリストを取得するつくりになっており、これが事実上のスタンダードといえるかもしれない(各国の検証アプリ実装の実態までは追えていないので、あくまで推測)。このVerifier APIは、 Digital Green Certificate Verifier Service としてWeb APIの定義が公開されている。

DGCA architecture overview

たとえばこのVerifier APIを用いる場合、EUDCCの検証は以下の手順でおこなえばよいはず。

  1. 検証アプリは、定期的に(例えば1日1回)Verifier APIを呼び出し、DSCリストを更新する。
    • GET /signercertificateUpdateをアップデートが無くなるまでくりかえし呼び出し、DSCリストを更新する。
  2. 検証の際、検証アプリは、検証対象のEUDCCのkidが、GET /sigercertificateStatusの結果と照らして有効であることを確認する。
    • GET /signerceriticateStatusを検証のたびに呼び出すかは微妙。少なくとも、ステップ1よりは高頻度で呼び出すのが期待された使い方な気がする。
  3. 検証アプリは、kidに該当するDSCを読み込み、これを検証鍵としてEUDCCの署名検証を行う。この際、DSCの有効期限とissexpの齟齬がないことも確認する。

以上、主にCWTまわりのフォーマットと検証方法にフォーカスして、EUDCCを支える技術を概観した。・・真面目に読むと実はいろいろと微妙な仕様なのだが、これは 評価・考察として後述する。

検証コードの実装

これまでにも書いてきた通り、EUDCCをとりまくシステムコンポーネント群はだいたいオープンソースで公開されている。わざわざ実装してみるまでもないのだが、汎用のCWT/COSEライブラリ Python CWT を用いて、一連の検証コードを書いてみた。実際に動くコードは ここ に置いた。以下の引用コードは、ここからの抜粋である。

まず、前述のVerifier API からEUDCCの検証用の公開鍵(DSC: Document Signing Certificate)を取得する処理は以下のようになる(DSCを1つずつしか取得できない謎仕様なので、何とも冗長・・)。ポイントは、後半で load_pem_hcert_dscPython CWTがEUDCC向けに用意しているヘルパー関数)を呼び出し、PEM形式の DSC を EUDCC の検証にそのまま使える COSE Key オブジェクトに変換し、リスト化( self._dscs)している部分である。

from cwt import load_pem_hcert_dsc

# 中略

def refresh_trustlist(self):
    status = 200
    headers = None

    # Get new DSCs
    x_resume_token = (
        self._trustlist[len(self._trustlist) - 1]["x_resume_token"]
        if self._trustlist
        else ""
    )
    while status == 200:
        if x_resume_token:
            headers = {"X-RESUME-TOKEN": x_resume_token}
        r = requests.get(
            self._base_url + "/signercertificateUpdate", headers=headers
        )
        status = r.status_code
        if status == 204:
            break
        if status != 200:
            raise Exception(f"Received {status} from signercertificateUpdate")

        x_resume_token = r.headers["X-RESUME-TOKEN"]
        self._trustlist.append(
            {
                "x_kid": r.headers["X-KID"],
                "x_resume_token": x_resume_token,
                "dsc": r.text,
            }
        )

    # Filter expired/revoked DSCs
    r = requests.get(self._base_url + "/signercertificateStatus")
    if r.status_code != 200:
        raise Exception(f"Received {r.status_code} from signercertificateStatus")
    active_kids = r.json()
    self._dscs = []
    for v in self._trustlist:
        if v["x_kid"] not in active_kids:
            continue
        dsc = f"-----BEGIN CERTIFICATE-----\n{v['dsc']}\n-----END CERTIFICATE-----"
        self._dscs.append(load_pem_hcert_dsc(dsc))

    # Update trustlist store.
    with open(self._trustlist_store_path, "w") as f:
        json.dump(
            [v for v in self._trustlist if v["x_kid"] in active_kids], f, indent=4
        )
    return

以下がメインの検証処理である。HC1: プレフィックスが付いている場合は、QRコード向けにZLIB圧縮/Base45エンコードがなされているものとみなして、対応する解凍・デコード処理を入れている。CWTの検証自体は、最後のcwt.decode()の一行で行われる。検証対象のEUDCC(CWT)と、上記の refresh_trustlist()で作成したCOSE Keyリスト(self._dscs)渡しているだけである。内部で kidの照合も含めたバリデーションが実行される。(JWTのように受け入れ可能なアルゴリズム一覧を渡す必要もなく)すっきり書ける。

import cwt

# 中略

def verify_and_decode(self, eudcc: bytes) -> bytes:
    if eudcc.startswith(b"HC1:"):
        # Decode Base45 data.
        eudcc = b45decode(eudcc[4:])
        # Decompress with zlib.
        eudcc = zlib.decompress(eudcc)
    # Verify and decode CWT.
    return cwt.decode(eudcc, keys=self._dscs)

最後に、上記の2関数を含むサンプルのVerifierクラスを使った一連の検証処理を貼っておく。

# An endpoint of Digital Green Certificate Verifier Service compliant with:
# https://eu-digital-green-certificates.github.io/dgca-verifier-service/
BASE_URL = os.environ["CWT_SAMPLES_EUDCC_BASE_URL"]
# e.g., "./dscs.json"
TRUSTLIST_STORE_PATH = os.environ["CWT_SAMPLES_EUDCC_TRUSTLIST_STORE_PATH"]
# quoted from https://github.com/eu-digital-green-certificates/dgc-testdata/blob/main/AT/2DCode/raw/1.json
BASE45_FORMATTED_EUDCC = b"HC1:NCFOXN%TS3DH3ZSUZK+.V0ETD%65NL-AH-R6IOOK.IR9B+9G4G50PHZF0AT4V22F/8X*G3M9JUPY0BX/KR96R/S09T./0LWTKD33236J3TA3M*4VV2 73-E3GG396B-43O058YIB73A*G3W19UEBY5:PI0EGSP4*2DN43U*0CEBQ/GXQFY73CIBC:G 7376BXBJBAJ UNFMJCRN0H3PQN*E33H3OA70M3FMJIJN523.K5QZ4A+2XEN QT QTHC31M3+E32R44$28A9H0D3ZCL4JMYAZ+S-A5$XKX6T2YC 35H/ITX8GL2-LH/CJTK96L6SR9MU9RFGJA6Q3QR$P2OIC0JVLA8J3ET3:H3A+2+33U SAAUOT3TPTO4UBZIC0JKQTL*QDKBO.AI9BVYTOCFOPS4IJCOT0$89NT2V457U8+9W2KQ-7LF9-DF07U$B97JJ1D7WKP/HLIJL8JF8JFHJP7NVDEBU1J*Z222E.GJ457661CFFTWM-8P2IUE7K*SSW613:9/:TT5IYQBTBU16R4I1A/9VRPJ-TS.7ZEM7MSVOCD4RG2L-TQJROXL2J:52J7F0Q10SMAP3CG3KHF0DWIH"
# RAW_EUDCC = bytes.fromhex("d2844da20448d919375fc1e7b6b20126a0590133a4041a61817ca0061a60942ea001624154390103a101a4617681aa62646e01626d616d4f52472d3130303033303231356276706a313131393334393030376264746a323032312d30322d313862636f624154626369783155524e3a555643493a30313a41543a31303830373834334639344145453045453530393346424332353442443831332342626d706c45552f312f32302f31353238626973781b4d696e6973747279206f66204865616c74682c20417573747269616273640262746769383430353339303036636e616da463666e74754d5553544552465241553c474f455353494e47455262666e754d7573746572667261752d47c3b6c39f696e67657263676e74684741425249454c4562676e684761627269656c656376657265312e302e3063646f626a313939382d30322d323658405812fce67cb84c3911d78e3f61f890d0c80eb9675806aebed66aa2d0d0c91d1fc98d7bcb80bf00e181806a9502e11b071325901bd0d2c1b6438747b8cc50f521")

if __name__ == "__main__":
    v = Verifier.new(BASE_URL, TRUSTLIST_STORE_PATH)
    v.refresh_trustlist()
    try:
        res = v.verify_and_decode(BASE45_FORMATTED_EUDCC)  # or RAW_EUDCC
    except Exception as err:
        print("Verification failed: %s" % err)
        exit(1)
    hcert = res[-260][1]
    print(hcert)
# {
#     'v': [
#         {
#             'dn': 1,
#             'ma': 'ORG-100030215',
#             'vp': '1119349007',
#             'dt': '2021-02-18',
#             'co': 'AT',
#             'ci': 'URN:UVCI:01:AT:10807843F94AEE0EE5093FBC254BD813#B',
#             'mp': 'EU/1/20/1528',
#             'is': 'Ministry of Health, Austria',
#             'sd': 2,
#             'tg': '840539006',
#         }
#     ],
#     'nam': {
#         'fnt': 'MUSTERFRAU<GOESSINGER',
#         'fn': 'Musterfrau-Gößinger',
#         'gnt': 'GABRIELE',
#         'gn': 'Gabriele',
#     },
#     'ver': '1.0.0',
#     'dob': '1998-02-26',
# }    
    exit(0)

評価・考察

評価・考察というか所感。

EUDCC関連仕様について

個人的には色々アラが目についてしまった。CWTを利用する規格として、もっと厳密に運用規定を定めてもよかったのではないかと思う。例えば、 issexpiathcertのクレームを使うとしているが、明にMUSTとは言っていない。CWTの構造も COSE_Sign1に絞ってしまえばいいのに、COSE_Signも暗に許容されている(一方で、テストベクターは COSE_Sign1しかない)。まあ、この辺り、リファレンス実装がベースとしてあるわけだし、緩くてもいいというポリシーは理解できなくはないが、私としては、仕様レベルでインターオペラビリティが厳密には保証されていない感じに違和感を感じてしまう。

もっとイマイチだと思うのは、 上記の Verifier API (Digital Green Certificate Verifier Service) の仕様。そもそも kid が重複し得る前提なので、 実は/signercertificateStatus は厳密にはkidの有効性の検証には利用できないはず(ある kid をもつDSCが失効した後に、同じ kid のDSCが発行されてしまうと、本来失効しているはずのDSCが有効と誤判定され得る)。とすると、 /signercertificateUpdate で、すべての有効なDSCを取得しなおすつくりにせざるを得ないが、この場合、DSCを1つずつ取得しなければならず、著しく非効率である。実際、上記のサンプルコードをテスト用に公開されているエンドポイントで試したところ、refresh_trustlist は、110ものDSCを取得するはめになり、2分以上を要した。なぜ比較的素直な スウェーデン方式 をリファレンスに出来なかったのか・・。

今から日本で基盤整備するなら

日本の状況はどうなっているのか全然知らないですが、今から整備するなら EUDCC のオープンな資産(主要なものは ライセンスがApache 2.0)をガンガン流用してしまえば、結構低コストで構築できるんじゃないですかね(上記の問題はありますが目をつぶれなくもないし)。

まあ実際の問題は、開発以外の部分が大きいとは思いますが。

おわりに

お気づきかもしれませんが、ちょっとステマのフレーバーが入った記事でした。はじめて Zen を使ってみたけどなかなか良いですね。これからもたまに興味にまかせて技術記事を書いてみたいなと思いました。この記事を書く過程で EUDCC に多少詳しくなってしまったので、質問があればお気軽に。

付録:規格化および実装の経緯

付録として、EUDCCがどのような流れで規格化され、基盤整備がおこなわれているのか公開情報からざっと辿ってみる。

規格化・普及のプロセス

まず規格化と普及に向けたプロセスの大まかな時系列は以下のとおり。

  • 2020-11
    • 欧州委員会とEU加盟国がeHealth NetworkにてEUDCCの相互接続性の要件議論を開始。
  • 2021-01-27
    • 策定された相互接続性のガイドラインがeHealth Networkにて承認。
  • 2021-03-17
    • 欧州委員会が、EUDCCの共通のフレームワークを確立するための立法文書を提案。
  • 2021-04-14
    • 欧州理事会が、この提案について欧州議会と交渉を開始する指令を採択。
  • 2021-04-22
    • eHealth Networkに参加している加盟国の代表者が、システム導入のための主要な技術仕様を記述したガイドラインに合意。
  • 2021-05-07
    • 欧州委員会は、EUDCCの認証を容易にするEU相互運用基盤(EUゲートウェイ)のパイロットテストを開始。
  • 2021-05-20
    • 欧州議会と欧州委員会が、EUDCCに合意。
  • 2021-06-01
    • EUゲートウェイ運用開始。
  • ~ 2021-06-30
    • ウォームアップ期間:EU加盟国は、証明書の発行と検証の準備が整い、必要な法的基盤が整備されていれば、自主的に証明書を発行することができる。
  • 2021-07-01
    • EUDCCが欧州全域で適用。
  • 2021-07-01 ~ 2021-08-12
    • 段階的導入期間:加盟国が国民に新しい証明書を発行する準備ができていない場合、他のフォーマットを使用することができ、他の加盟国で受け入れられる必要がある。

以上をみると、この記事の本編で技術仕様の発行元として名前を出したeHealth Networkという組織が重要な役割を果たしていることが分かる。eHealth Networkは、e-Healthの普及とEU加盟国間の協力を促進することを目的として、欧州委員会の健康・食品安全総局に設置されているネットワークである。ここには、デジタルヘルスケアを管轄する全EU加盟国の当局が自発的に集まっており、各国のデジタルヘルスシステム間の相互運用性の課題を解決する中心的な役割を果たしているようだ[25]。

実装

一方の実装サイドの動向も、オープンソースのアクティビティ、コントリビュータに着目して見てみる。

まず、EUDCCに関連するGithub Organizationは、以下の2つ。

いずれのOrganizationに含まれるリポジトリも、今年の3月後半以降に作成されている。公開前に水面下で開発は進められていたかもしれないが、少なくともGithub上では、3月後半以降、主に4月に入ってからリポジトリが作成され、そこから一気に開発が行われたように見える。

詳しく追っていないが、欧州では感染者接触検知アプリのためのEUワイドのゲートウェイサービス(EU Federation Gateway Service)を昨夏に整備しており、EU加盟各国内のバックエンドサービスと連携する構造は、EUDCCのDGCGと類似している。接触検知システムの資産をある程度活用していると思われる。

コントリビュータに視点を移すと、ドイツテレコム系のソフトウェアエンジニアが目立つ(Deutsche Telekom、T-System、Detecon)。特にシステムの核であるDGCGは、T-System所属のエンジニアが中心となって作っているようだ。スマホアプリの方も、Volume 4: European Digital Green Certificate Applications の中に、"Although TSI/SAP will provide reference implementations, ..." との記載があることから、ドイツ企業の T-Systems International と SAP がリファレンス実装開発の中心となっているようである。とはいえ、各種アプリやライブラリのリポジトリを個別にみていくと、欧州全域からエンジニアが開発に参加していることがわかる。大手(IBMやMicrosoft)所属もいれば、セキュリティ系のベンチャー、コンサルティング会社の名前も目につく。見た限りでは、過去の活動を追うと各国の接触検知アプリ開発を行っていたメンバなどが今回も引き続き貢献している流れがあるようだ。

軸となるシステム設計・開発・構築(・運用)できる企業をおきつつ、モノは早々にオープンソース化して、市井のプログラマの協力を得るというスタイルは、現状の1つのベストプラクティスかなと思います。

References

Discussion