feat(2.9.2): Nur geprueft in Hardware entstandene Schluessel als zweiter Faktor
Befund aus dem Betrieb: Ein Passkey aus 1Password liess sich als Hardwarekey registrieren. Fuer das Deploymentcenter hinnehmbar, fuer ein Protokoll, das ausdruecklich Hardware fordert, nicht. Zwei Dinge, die wie eine Pruefung aussahen und keine waren - beide seit 2.9.0 gesetzt: authenticatorAttachment "cross-platform" steuert, was der Browser anbietet, und ein Passwortverwalter meldet sich ebenso. Und response.authenticatorAttachment setzt der Client, also genau die Instanz, gegen die sich nichts beweisen laesst. Was traegt, ist Basic Attestation. Neu geprueft wird die Kette bis zu einer hinterlegten Herstellerwurzel; die drei FIDO-Wurzeln von Yubico liegen bei. Ein x5c allein genuegt ausdruecklich nicht: Der Fall "eigenes Zertifikat, Name Yubico, AAGUID eines echten YubiKey 5, gueltige Unterschrift" wurde nachgebaut und wird abgewiesen - an der Kette, nicht an der Signatur. Drei Stufen statt eines Schalters, weil alle bisher registrierten Schluessel als nicht nachgewiesen gelten - auch echte Yubikeys. Bei attestation "none" ersetzt der Browser die Geraetekennung durch Nullbytes, bevor der Server sie sieht; es gab also nie eine Herkunft. Ein einzelner Schalter haette beim Umlegen den eigenen Zugang gesperrt. Der Wechsel nach "strict" wird zusaetzlich abgelehnt, solange das eigene Konto keinen nachgewiesenen Schluessel hat. - Attestation.php: packed und fido-u2f, Kettenpruefung gegen eigene Wurzeln statt gegen den Speicher des Betriebssystems, Gueltigkeit, Gegenprobe der AAGUID aus der Zertifikatserweiterung - HardwarePolicy.php: off / register / strict, optionale Modellliste, Wurzeln aus der Konfiguration erweiterbar - Migration 021: attested und drei Anzeigespalten, Vorgabe 0 - WebUI: Herkunft je Schluessel, Regel vor der Beruehrung angesagt - testctl clear_credentials, damit ein Testlauf den naechsten nicht aussperrt - Testreihe "hardware", 5 Faelle, ohne Krypto-Bibliothek 184 von 184 Testfaellen gruen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8f514eb17a
commit
073d52ab7d
+111
@@ -674,3 +674,114 @@ alten Namens werden dabei aufgeräumt.
|
||||
- [ ] Roadmap → 🧭 Tafel zeigt Spalten, Spuren und Kärtchen
|
||||
- [ ] Ein Klick auf ein Kärtchen öffnet das Panel, Kommentieren funktioniert
|
||||
- [ ] `python tests/e2e/run.py` gegen die Testinstanz: alles grün
|
||||
|
||||
---
|
||||
|
||||
## 18. Umstellung auf 2.9.2 — nur echte Hardware als zweiter Faktor
|
||||
|
||||
### 18.1 Worum es geht
|
||||
|
||||
Seit 2.9.0 verlangt die Anmeldung einen Hardwarekey. Was sie *nicht* verlangte,
|
||||
war, dass es wirklich Hardware ist: Ein Passkey aus einem Passwortverwalter
|
||||
(1Password, Bitwarden, iCloud, Google) ließ sich ebenso registrieren wie ein
|
||||
Yubikey. Für das Deploymentcenter selbst ist das hinnehmbar. Wo ein
|
||||
Sicherheitsprotokoll ausdrücklich Hardware fordert, ist es der Unterschied
|
||||
zwischen einer Regel und einem Wunsch.
|
||||
|
||||
Zwei Dinge, die *wie* eine Prüfung aussehen und keine sind — beide waren in
|
||||
2.9.0 bereits gesetzt:
|
||||
|
||||
- `authenticatorAttachment: "cross-platform"` steuert, was der Browser
|
||||
**anbietet**. Ein Passwortverwalter meldet sich ebenfalls als
|
||||
cross-platform.
|
||||
- `response.authenticatorAttachment` setzt der **Client**, und der Client ist
|
||||
genau die Instanz, gegen die sich hier nichts beweisen lässt.
|
||||
|
||||
Was trägt, ist die Attestation: Der Authenticator unterschreibt bei der
|
||||
Aufnahme mit einem Schlüssel, der ab Werk in seinem Sicherheitselement liegt
|
||||
und es nie verlässt. Das zugehörige Zertifikat muss auf eine hinterlegte
|
||||
Herstellerwurzel ketten.
|
||||
|
||||
**Ein Zertifikat allein genügt nicht.** Jeder kann sich eines ausstellen, es
|
||||
„Yubico U2F EE“ nennen, die AAGUID eines echten YubiKey 5 eintragen und sauber
|
||||
unterschreiben. Genau dieser Fall wurde nachgebaut und gegen den Server
|
||||
gefahren: Er wird abgewiesen — an der **Kette**, nicht an der Signatur. Was
|
||||
Software nicht herstellen kann, ist eine Kette, die bei Yubicos Wurzel endet.
|
||||
|
||||
### 18.2 Migration
|
||||
|
||||
```bash
|
||||
php cli/migrate.php # oder System → 🗄️ DB Migration im WebUI
|
||||
```
|
||||
|
||||
Migration 021 ergänzt `dc_user_credentials` um `attested`, `attestation_fmt`,
|
||||
`attestation_ca` und `attestation_root`. Sie setzt **keine** Regel — ohne
|
||||
Umstellen bleibt das Verhalten von 2.9.1.
|
||||
|
||||
### 18.3 Die drei Stufen
|
||||
|
||||
| Stufe | Aufnahme neuer Schlüssel | Anmeldung mit vorhandenen |
|
||||
|---|---|---|
|
||||
| `off` | jeder Authenticator | alle |
|
||||
| `register` | nur mit Herstellernachweis | alle |
|
||||
| `strict` | nur mit Herstellernachweis | nur nachgewiesene |
|
||||
|
||||
Einzustellen unter **System → 👤 Benutzer → 🛡️ Welche Schlüssel zugelassen
|
||||
sind**.
|
||||
|
||||
### 18.4 Der Weg nach `strict` — in dieser Reihenfolge
|
||||
|
||||
> **Alle vor 2.9.2 registrierten Schlüssel gelten als nicht nachgewiesen —
|
||||
> auch echte Yubikeys.** Das ist keine Vorsicht, sondern eine Tatsache des
|
||||
> Standards: Bei `attestation: "none"` ersetzt der **Browser** die
|
||||
> Gerätekennung durch Nullbytes, bevor der Server sie zu sehen bekommt. Es gab
|
||||
> also nie eine Herkunft, nicht einmal eine ungeprüfte.
|
||||
|
||||
1. Stufe auf **`register`** stellen.
|
||||
2. Den Hardwarekey **einmal neu aufnehmen**. In der Liste muss danach
|
||||
*🔒 Hardware* stehen, mit der erreichten Wurzel.
|
||||
3. Den alten, nicht nachgewiesenen Eintrag entfernen.
|
||||
4. Erst jetzt auf **`strict`**.
|
||||
|
||||
Schritt 4 wird abgelehnt, solange das eigene Konto keinen nachgewiesenen
|
||||
Schlüssel hat — eine Einstellung, die sich selbst aussperrt, ist keine
|
||||
Einstellung, sondern eine Falle.
|
||||
|
||||
### 18.5 Wenn doch etwas klemmt
|
||||
|
||||
- **Der Browser fragt nach Marke und Modell.** Das ist der sichtbare Preis von
|
||||
`attestation: "direct"` und kein Fehler. Wer ablehnt, liefert keine
|
||||
Attestation und wird abgewiesen.
|
||||
- **Ausgesperrt.** Der Notfalltoken funktioniert unabhängig von der Regel.
|
||||
Danach unter System → Benutzer die Stufe zurückstellen.
|
||||
- **Ganz ohne WebUI:**
|
||||
```sql
|
||||
DELETE FROM dc_settings WHERE skey = 'webauthn_hardware_policy';
|
||||
```
|
||||
Fehlt der Eintrag, gilt wieder `off`.
|
||||
|
||||
### 18.6 Andere Hersteller als Yubico
|
||||
|
||||
Mitgeliefert sind die drei FIDO-Wurzeln von Yubico
|
||||
(`src/Core/Webauthn/roots/yubico.pem`). Weitere Wurzeln in der `config.php`:
|
||||
|
||||
```php
|
||||
'security' => [
|
||||
'webauthn_root_files' => ['/etc/dc/solokeys.pem'],
|
||||
],
|
||||
```
|
||||
|
||||
Bewusst dort und nicht im WebUI: Eine Vertrauenswurzel über dieselbe
|
||||
Oberfläche austauschbar zu machen, die sie schützt, hieße die Prüfung dort
|
||||
aushebelbar zu machen.
|
||||
|
||||
### 18.7 Prüfen, ob alles läuft
|
||||
|
||||
- [ ] Migration 021 ist durch (Spalte `attested` in `dc_user_credentials`)
|
||||
- [ ] Stufe `register`: Der Yubikey wird aufgenommen und zeigt *🔒 Hardware*
|
||||
- [ ] Stufe `register`: Ein Passwortverwalter wird mit einer Meldung abgewiesen,
|
||||
die den Grund nennt
|
||||
- [ ] `strict` lässt sich **nicht** einschalten, solange kein nachgewiesener
|
||||
Schlüssel am eigenen Konto hängt
|
||||
- [ ] Nach `strict`: Anmeldung mit dem Yubikey funktioniert weiter
|
||||
- [ ] `python tests/e2e/run.py --gruppe hardware`: alles grün
|
||||
|
||||
Reference in New Issue
Block a user