True passkey implement

This commit is contained in:
qingjie.du
2026-03-31 15:59:59 +09:00
parent 73ce0ef5a2
commit ec5eb0a447
13 changed files with 1195 additions and 412 deletions

195
PASSKEY.md Normal file
View File

@@ -0,0 +1,195 @@
# 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 サーバーに問い合わせ → OKallowCredentials 返却)
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` | ログアウト |
エンコーディング: Base64URLdfido2-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);
}
```