# MCPletA2A Passkey 認証 真正の WebAuthn/FIDO2 Passkey 認証。外部 amiPro FIDO2 サーバーと連携。 ## アーキテクチャ ``` Host (Node.js) │ ├── PasskeyServer ──→ ブラウザページ起動(自己完結型フロー) │ │ │ ├── userId 入力 │ ├── FIDO2 サーバーにユーザー確認 │ ├── 未登録 → 自動で Passkey 登録(Touch ID 等) │ ├── 認証(Touch ID 等) │ └── 結果を Host に HTTP callback │ ├── PasskeyPlatformService ──→ AmiProFIDO2Client ──→ FIDO2 Server │ (統一管理: 登録/認証/セッション) (https://fido2.epk.amipro.me) │ └── PasskeyAPIServer (REST proxy, port 8443) /api/passkey/attestation/options /api/passkey/attestation/result /api/passkey/assertion/options /api/passkey/assertion/result ``` ## ファイル構成 ``` platform_impl/src/passkey/ ├── amipro-fido2-client.ts # amiPro FIDO2 サーバー REST クライアント ├── platform-service.ts # 統一 Passkey 管理サービス ├── passkey-server.ts # ブラウザ WebAuthn 式典サーバー(自己完結型ページ) ├── api-server.ts # REST API プロキシ ├── index.ts # エクスポート ├── storage.ts # IPasskeyStorage インターフェース (ローカル用、予備) ├── challenge-manager.ts # チャレンジ管理 (ローカル用、予備) ├── fido2-backend.ts # FIDO2 検証 (ローカル用、予備) ├── client.ts # ブラウザ側 WebAuthn クライアント └── mcplet-helper.ts # MCPlet ツール統合ヘルパー ``` ## 設定 ```yaml # reference_impl/config/reference.yaml passkey: mode: https # localhost | https rpId: a2a-demo.mcplet.ai # WebAuthn Relying Party ID fido2ServerUrl: https://fido2.epk.amipro.me # 外部 FIDO2 サーバー apiPort: 8443 # REST API ポート ``` ## ユーザーフロー ### 新規ユーザー(未登録) ``` ブラウザページ起動 ↓ 1. userId 入力(または自動入力) ↓ 2. FIDO2 サーバーに問い合わせ → "ユーザーが存在しません" ↓ 3. 自動で登録モードに切替 → Touch ID 等で Passkey 登録 ↓ 4. 登録成功 → 自動で認証に進む ↓ 5. Touch ID 等で認証 ↓ 6. session + username を Host に返却 ``` ### 既存ユーザー(登録済み) ``` ブラウザページ起動 ↓ 1. userId 入力(または自動入力) ↓ 2. FIDO2 サーバーに問い合わせ → OK(allowCredentials 返却) ↓ 3. Touch ID 等で認証 ↓ 4. session + username を Host に返却 ``` ## amiPro FIDO2 Server API | エンドポイント | 説明 | |--------------|------| | `POST /attestation/options` | 登録オプション取得(challenge + publicKey params) | | `POST /attestation/result` | 登録結果送信(attestationObject 検証) | | `POST /assertion/options` | 認証オプション取得(challenge + allowCredentials) | | `POST /assertion/result` | 認証結果送信(signature 検証) | | `POST /usr/validsession` | セッション検証 | | `POST /usr/delsession` | ログアウト | エンコーディング: Base64URL(dfido2-lib.js 互換) ## 開発環境セットアップ ### 1. /etc/hosts ``` 127.0.0.1 a2a-demo.mcplet.ai ``` ### 2. 自己署名証明書の生成 ```bash cd platform_impl mkdir -p certs openssl req -x509 -newkey rsa:2048 \ -keyout certs/key.pem -out certs/cert.pem \ -days 365 -nodes -subj "/CN=a2a-demo.mcplet.ai" \ -addext "subjectAltName=DNS:a2a-demo.mcplet.ai,IP:127.0.0.1" ``` ### 3. 証明書の信頼(macOS) ```bash # Admin 権限あり sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain certs/cert.pem # Admin 権限なし(Edge で有効、Chrome は完全再起動が必要) security add-trusted-cert -r trustRoot \ -k ~/Library/Keychains/login.keychain-db certs/cert.pem ``` ### 4. テスト ```bash cd platform_impl # API 接続テストのみ npx tsx test-passkey.ts # 統一フロー(自動登録+認証) npx tsx test-passkey.ts test a2a-test-user # 新規ユーザー(userId 手入力) npx tsx test-passkey.ts new ``` ## 重要な注意事項 ### WebAuthn rpId とドメインの一致 WebAuthn は rpId がブラウザページのドメインと一致することを要求する。 `http://127.0.0.1` のページでは rpId `a2a-demo.mcplet.ai` は使用不可。 必ず /etc/hosts + HTTPS(または Chrome flag)で一致させること。 ### Passkey のブラウザ間共有 macOS の Touch ID / iCloud キーチェーンで作成された Passkey はブラウザ間で共有される。 ただし Chrome の `--user-data-dir=tmp` で起動すると隔離プロファイルとなり、 プラットフォーム Passkey にアクセスできない。常にデフォルトブラウザを使用すること。 ### 本番環境 vs 開発環境 | 項目 | 開発環境 | 本番環境 | |------|---------|---------| | 証明書 | 自己署名 + キーチェーン信頼 | 正規 SSL 証明書 | | rpId | a2a-demo.mcplet.ai (hosts) | 実ドメイン (DNS) | | FIDO2 server | fido2.epk.amipro.me | 同 or 専用サーバー | | `PASSKEY_DEV_HTTP` | 設定可(Chrome flag 必要) | 不使用 | ## Platform 統合 ### base-agent.ts での Passkey 呼び出し ```typescript // action 型ツールで auth.required === 'passkey' の場合、自動で仪式を開始 if (tool.meta.mcpletType === 'action' && tool.meta.auth?.required === 'passkey') { const assertion = await this.performPasskeyCeremony(tool); if (!assertion) { return this.errorResult(toolName, 'Passkey authentication failed', 'AUTH_FAILED'); } callParams = { ...args, _mcplet_auth: assertion }; } ``` ### MCPletHost 初期化 ```typescript if (config.passkey) { this.passkeyPlatformService = new PasskeyPlatformService(rpId, fido2ServerUrl); this.passkeyAPIServer = new PasskeyAPIServer(platformService, rpId, origin); this.passkeyAPIServer.start(config.passkey.apiPort || 8443); this.passkeyServer = new PasskeyServer(rpId, fido2ServerUrl); } ```