196 lines
6.4 KiB
Markdown
196 lines
6.4 KiB
Markdown
# 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);
|
||
}
|
||
```
|