前回、マイナンバーカードを 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公開。subはsha256(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 で一貫して動きました。
- ① 本物の証明:OIDC で
sub取得(fresh) - ② Myna FIDO 登録:カードの Ed25519 を
subに束ね(boundTo(sub)= 同 sub) - ③ 簡易証明のみログイン:カード単独(OIDC 不在)で同じ
subに解決 - 要求層:
/protectedL1(簡易のみ)も 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 を利用しています。マイナンバーカードを「本人性の重い証明」と「日常の軽い証明」の両方に、役割を分けて使えるのではないか——という方向の実験でした。

コメントを残す