マイナンバーカードで「本物の証明」と「簡易証明」を分けて使う — OIDC×WebAuthn ハイブリッド認証の PoC

前回、マイナンバーカードを Safari の WebAuthn 認証器にする skeleton を作りました。カードで Ed25519 鍵を生やして登録・認証ができる——のですが、この方式(CTAP2 attestation 無し設計)には構造的な限界があります。「その鍵が本物のマイナンバーカード由来である」ことを RP が検証できないし、同じ人が鍵を量産できてしまう(Sybil 耐性が無い)。投票の「一人一票」や「一人 N 個まで」を強制できません。

そこで考えたのが、OpenID Connect(デジタル認証アプリ)と WebAuthn を「分離して合成する」ハイブリッド設計です。本記事はその PoC(hyde-myna)の記録です。

2種類の「証明」を分ける

WebAuthn と OIDC が担保するものは、実は直交しています。これを混ぜずに2つの独立したプリミティブとして扱うのが設計の肝です。

プリミティブ実体何を証明するか性質
① 本物の証明
(personhood)
デジタル認証アプリ
OIDC attestation
「今この瞬間、本物のマイナンバーカード保持者である」重い・オンライン・IdP 介在・fresh
② 簡易証明
(possession)
Myna FIDO
WebAuthn assertion
「登録時に確認済みの鍵を、今も所持している」軽い・オフライン・低レイテンシ・高プライバシー

「本物かどうか」と「持っているかどうか」は別の問いです。日常のログインは②だけで十分(速くてプライベート)。一方、投票や高額操作のように「今この人が本物」が要る場面でだけ①を足す。RP は操作ごとに必要なものを合成すればよい。

ハイブリッド:登録時だけ本人性を担保し、鍵に束ねる

[登録(1回だけオンライン)]
  RP → デジタル認証アプリ(OIDC): 認可コードフロー(PKCE)
  IdP → RP: id_token { sub }          ← 本物の Myna 保持者を attest
  RP → カード: WebAuthn 登録(Ed25519 を生成)
  RP: 保存 { sub, credentialId, pubkey }  ← sub に FIDO を束ねる

[日常運用(オフライン・低レイテンシ・高プライバシー)]
  RP → カード: WebAuthn 認証(Myna FIDO 単独、IdP は介在しない)
  RP: credentialId → sub を特定 → 当人認証

IdP(デジタル認証アプリ)が登場するのは登録の 1 回だけ。日々のログインは IdP に一切見えません(純粋な OIDC ログインより桁違いにログが少ない)。これで攻略できる壁は3つ:

  • attestation:登録時に OIDC で本人性を担保 → 以後の FIDO 鍵は「確認済みの人」に束ねられている
  • Sybil 耐性:同一 RP 内では sub で名寄せ → 一人が複数登録しても束ねられ「一人 N 個」を強制できる
  • 再発行 re-binding:カード再発行で鍵が変わっても、sub が人不変なら同じ人に再束ねできる(※要検証、後述)

要求層(2段階)

2つのプリミティブは別モジュールとして実装し、RP が操作ごとに必要なレベルを宣言します。

段階要求用途
L1 所持・継続性② 簡易証明のみ日常ログイン・通常操作
L2 本人性の再確認① 本物(fresh) + ② 簡易投票・高額/不可逆操作・カード再発行後の re-binding

L2 は OIDC をその場で fresh に取り直します(max_age を短く)。「登録時の古い attestation」は流用しません。②単独では「今この人が本物」は言えない(所持の継続性だけ)ので、本人性が要る操作は必ず①を合成する、という分離がそのまま安全性の境界になります。

PoC 実装(Rust / axum)

RP サーバと、デジタル認証アプリを模した最小 OIDC IdP(mock-idp)を Rust で書きました。client_id の取得(デジタル庁への申請)前でも前進できるよう、まずモック IdP で「束ね処理」を end-to-end 検証し、後で実 IdP に差し替える 2 段構えにしています。

  • mock-idp:認可コードフロー + PKCE(S256) + RS256 の id_token + /jwks 公開。subsha256(client_id : user) の pairwise(RP ごとに固有、RP 内では安定)にして「sub は RP 単位」という前提をモデル化
  • rp:① OIDC クライアント(PKCE/state/nonce + JWKS 署名検証)、② WebAuthn を ciborium + ed25519-dalek手動検証(fmt=none / nil-aaguid の Ed25519。webauthn-rs を使わず自前で COSE 鍵を取り出す)

WebAuthn の検証は、まずカード無しで自己テスト(正規の登録・認証=成功、challenge/署名の改竄=拒否)を書いて固めてから、実カードに繋ぎました。

実カードで一気通貫

Safari + Myna 拡張 + 実マイナンバーカードで、3操作すべてが同一の sub で一貫して動きました。

  1. ① 本物の証明:OIDC で sub 取得(fresh)
  2. ② Myna FIDO 登録:カードの Ed25519 を sub に束ね(boundTo(sub) = 同 sub)
  3. ③ 簡易証明のみログイン:カード単独(OIDC 不在)で同じ sub に解決
  4. 要求層/protected L1(簡易のみ)も L2(本物 fresh + 簡易)も granted

「登録時だけ本人性を担保 → カードに束ね → 日常はカード単独で同じ人に解決」という、本物の証明と簡易証明の分離合成が、実機で通りました。

残る課題(このPoCで検証したい未確認前提)

これはあくまで PoC で、本丸はここから先です。実デジタル認証アプリに繋いで確かめるべき前提が残っています。

  • sub は本当に RP ごとに固有(pairwise)か、同一人物で安定不変か
  • マイナンバーカード再発行・電子証明書更新で sub が変わるか(re-binding 可否の核心)
  • テスト環境 / client_id 取得(private_key_jwt 鍵登録)の要件
  • 番号法・ガイドライン上の整理

コードは GitLab に置いてあります(PoC・production 非対応):Ryujiyasu/hyde-myna。WebAuthn 層は前回の myna-fido-safari を利用しています。マイナンバーカードを「本人性の重い証明」と「日常の軽い証明」の両方に、役割を分けて使えるのではないか——という方向の実験でした。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

IP: 取得中...
216.73.216.228216.73.216.228