IT security, FreeBSD, Linux, mail server hardening, post-quantum crypto, DNS, retro computing & hands-on hardware hacks. Privater Tech-Blog seit 2003.

Kategorie: Kernel-Error-Blog (Seite 1 von 49)

Persönlicher Tech-Blog von Sebastian van de Meer — Beiträge zu IT-Security, Netzwerken, FreeBSD, Linux, Elektronik und Maker-Projekten.

BIOS erklärt, Teil 4: AES-NI, SGX, TME und ein Schalter der nichts bewirkt

In der letzten Folge ging es um die obere Hälfte der CPU-Seite, also Kerne, Threads und Prefetcher. Die untere Hälfte ist die spannendere, weil dort keine Leistungsschalter mehr stehen, sondern Sicherheitsfunktionen. Und weil mindestens einer davon eine richtig gute Geschichte hat.

Zur Erinnerung für alle die neu dazustossen: ich gehe hier das BIOS meines Supermicro-Serverboards durch, Themenblock für Themenblock, alle Folgen liegen unter BIOS & Firmware.

AES-NI, oder: der Grund warum es eine Karte gab

Aptio Setup, CPU Configuration, mit Hyper-Threading, den fünf Prefetchern und AES-NI
AES-NI                          [Enable]

Ein Schalter und dahinter steckt eine ganze Ära. AES-NI sind sechs zusätzliche Prozessorbefehle, die AES in Hardware rechnen statt in Software. Der Unterschied ist keine Feinheit, er liegt grob bei einer Grössenordnung, und obendrein sind die Hardwarebefehle immun gegen die Cache-Timing-Seitenkanäle, mit denen man Software-AES angreifen kann. Aber vor allem is et dann ers rischtig flott mit der Crypto.

Ob die CPU es kann und darf, sieht man sofort im OS mit:

grep -o ' aes ' /proc/cpuinfo | head -1
#    aes

Warum es diesen Schalter überhaupt gibt, ist eine berechtigte Frage. Man schaltet AES-NI ja nicht ab, weil man gern langsam verschlüsselt. Die ehrliche Antwort ist vermutlich, dass er aus einer Zeit stammt, in der AES-NI ein Unterscheidungsmerkmal zwischen Produktlinien war, und niemand hat ihn seitdem entfernt. Oder hat jemand eine bessere Erklärung?

Über genau diese Zeit habe ich hier schon geschrieben. In meinem Beitrag zur Intel QuickAssist 8950-SCCP steckt eine Karte, die es nur deshalb gab, weil manche CPUs kein AES-NI hatten. Wer 2013 ein NAS mit einer Atom-CPU verschlüsseln wollte, brauchte Hilfe. Heute ist der Schalter eine Formalie, damals war er die Grenze zwischen „geht“ und „geht nicht“.

Virtualisierung und ein Schalter der Vertrauen heisst

Intel Virtualization Technology [Enable]
Enable SMX                      [Disable]

Der erste ist VT-x, im Handbuch etwas altmodisch VMX und in einer noch älteren Fassung sogar Vanderpool Technology genannt, nach dem Codenamen von 2003. Ohne ihn läuft keine Hardware-Virtualisierung, also weder KVM noch VirtualBox mit brauchbarer Geschwindigkeit. Bleibt selbstverständlich an.

Der zweite ist interessanter. SMX steht für Safer Mode Extensions und ist die Grundlage für Intel TXT, Trusted Execution Technology. Vereinfacht gesagt geht es darum, dass eine Maschine beim Start beweisen kann, dass sie genau die Software geladen hat, die sie laden sollte. Ein gemessener Start, verankert in der Hardware, ähnlich wie Measured Boot mit einem TPM, nur eine Ebene tiefer.

Dabei ist mir etwas aufgefallen, das ich nicht restlos erklären kann. Im Setup steht SMX auf Disable. In /proc/cpuinfo steht das Flag smx trotzdem drin. Die naheliegende Deutung ist, dass das Flag die Fähigkeit der CPU beschreibt und der BIOS-Schalter nur die Freigabe, dass CPUID also weiterhin „kann ich“ meldet, während die Firmware „darfst du nicht“ sagt. Belegen kann ich das nicht, ich habe es nur beobachtet. Wer da genauer Bescheid weiss, immer her damit. Ich bin ja auch nur doof.

Speicherverschlüsselung, und warum SGX daran hängt

Total Memory Encryption (TME)   [Disabled]

TME verschlüsselt den kompletten Arbeitsspeicher. Der Schlüssel wird bei jedem Start neu erzeugt, liegt im Speichercontroller und verlässt die CPU nie. Für das Betriebssystem ist das unsichtbar, es merkt schlicht nichts davon.

Wogegen hilft das? Gegen Angriffe, die physisch am Speicher ansetzen. Der Klassiker ist der Cold-Boot-Angriff, bei dem man die Riegel kühlt, aus der laufenden Maschine reisst und in einem anderen Gerät ausliest, weil DRAM seinen Inhalt für Sekunden bis Minuten behält. Dazu kommt alles, was per DMA am Speicher horcht. Bei einem Server im fremden Rechenzentrum ist das kein theoretisches Szenario. Sucht das mal auf YouTube ist so, das sieht immer krass aus. Vielleicht kann ich so etwas selbst irgendwann mal probieren.

Bei mir steht es auf Disabled, und das ist der Werksdefault. Meine Maschine steht in meiner Wohnung, die Systemplatte ist ohnehin mit LUKS verschlüsselt, und das Angriffsmodell, gegen das TME hilft, setzt jemanden voraus der hier steht während die Kiste läuft. Falls das passiert, habe ich grössere Probleme. Reizvoll finde ich es trotzdem, denn die Kosten sind gering: die Verschlüsselung sitzt im Speichercontroller und macht sich in Messungen kaum bemerkbar.

Ein Detail, über das ich beim Lesen des Handbuchs gestolpert bin, erklärt den Aufbau des Menüs:

*If the feature above is set to Enabled, the next five features are displayed*

Erst wenn TME an ist, erscheinen die fünf Zeilen darunter, und dazu gehört auch SGX. Die Enklaven-Technik hängt hier also an der Speicherverschlüsselung. Das ergibt Sinn, denn der geschützte Speicherbereich einer Enklave muss ja gegen genau die Zugriffe abgesichert sein, die TME abdeckt. Im Setup sieht man davon nur eine flache Liste, im Handbuch die Abhängigkeit.

SGX, und wie Intel die UHD-Blu-ray auf dem PC beerdigt hat

SW Guard Extensions (SGX)       [Disabled]
SGX Factory Reset               [Disabled]
SGX Package Info In-Band Access [Disabled]

SGX ist die Idee, dass ein Programm einen Speicherbereich anlegen kann, in den niemand hineinsehen darf. Nicht andere Programme, nicht der Kernel, nicht der Hypervisor, nicht einmal jemand mit Root. Die CPU verschlüsselt den Bereich und gibt ihn nur dem Code frei, der ihn angelegt hat. Diese Enklaven waren als Fundament für vertrauliche Berechnungen in fremden Rechenzentren gedacht.

Die Geschichte drumherum ist allerdings die bessere. SGX war jahrelang in normalen Desktop-Prozessoren drin, und die prominenteste Anwendung war nicht vertrauliches Rechnen, sondern Kopierschutz. Die Wiedergabe einer Ultra-HD-Blu-ray auf dem PC verlangte SGX, weil der Schlüsselaustausch in einer Enklave passierte. Dann hat Intel SGX ab der elften Generation aus den Consumer-CPUs entfernt, und damit war die UHD-Blu-ray-Wiedergabe auf neuen PCs vorbei. Wer eine Scheibe abspielen wollte, brauchte plötzlich einen älteren Prozessor.

Ich finde das aus mehreren Gründen bemerkenswert. Erstens ist es ein schönes Beispiel dafür, wie eine Sicherheitstechnik zweckentfremdet wird und dann an einer Produktentscheidung stirbt, die mit ihrem eigentlichen Zweck nichts zu tun hat. Zweitens hat es Leute getroffen, die für ihre legal gekauften Scheiben plötzlich keinen legalen Abspielweg mehr hatten, während der inoffizielle Weg weiterhin problemlos funktionierte. Kopierschutz eben.

Im Xeon lebt SGX weiter, dort war es nie der Kopierschutz sondern das Verkaufsargument. Bei mir steht es trotzdem aus, weil ich keine Software habe die Enklaven nutzt, und weil es ohnehin nur zusammen mit TME ginge. Der Vollständigkeit halber, im laufenden System taucht folgerichtig nichts davon auf:

grep -c ' sgx' /proc/cpuinfo    # 0
ls /sys/devices/system/cpu/ | grep -c sgx    # 0

46 Bit, wegen Hyper-V

Limit CPU PA to 46 bits         [Enable]

PA steht für Physical Address. Der Schalter begrenzt die Breite der physischen Adressen auf 46 Bit, was 64 Terabyte adressierbarem Speicher entspricht. Bei 256 GB im Rechner ist das reichlich weit weg von jeder Grenze. 46 Bit und 64 Bit, da muss ich direkt an fe80 bei IPv6 denken und das man die Luft gelassen hat, weil MAC Adressen auch irgendwann zu wenige sind. Ok da sind es 48 Bit aber denken muss ich daran.

Die Begründung im Handbuch ist knapp und ziemlich entlarvend:

Use this feature to limit the CPU physical address to 46 bits to support older hyper-v.

Ältere Hyper-V-Versionen kamen mit breiteren Adressen nicht klar, also gibt es im BIOS eines Serverboards von 2021 einen Schalter, der die CPU künstlich einschränkt, damit eine bestimmte Microsoft-Software läuft. Das ist Abwärtskompatibilität in ihrer reinsten Form. Der Effekt lässt sich direkt nachlesen:

grep -m1 'address sizes' /proc/cpuinfo
#   address sizes : 46 bits physical, 57 bits virtual

46 physisch, wie eingestellt. Die 57 virtuellen Bit sind übrigens eine ganz andere Baustelle, das ist Five-Level Paging, ebenfalls neu mit Ice Lake.

Eine Seriennummer im Prozessor

Aptio Setup, untere Hälfte der CPU Configuration, mit TME, SGX, PPIN Control und Extended APIC
PPIN Control                    [Lock/Disable]

PPIN steht für Protected Processor Inventory Number, eine eindeutige Nummer pro physischer CPU. Gedacht ist sie für die Fehleranalyse in grossen Flotten: wenn ein Prozessor Speicherfehler meldet, will man wissen welcher es war, und zwar auch nachdem er längst ausgebaut wurde.

Eine eindeutige, unveränderliche Hardwarenummer ist allerdings genau die Sorte Datenpunkt, an der sich vor Jahren schon einmal die Gemüter erhitzt haben. Der Pentium III hatte eine Seriennummer, der Aufschrei war so gross, dass Intel sie wieder entfernt hat. PPIN ist die gleiche Idee, nur diesmal mit einem Schalter davor und beschränkt auf Serverprozessoren.

Hier ist mir eine Abweichung aufgefallen. Das Handbuch nennt als mögliche Werte:

The options are Unlock/Disable and Unlock/Enable.

Im Setup steht aber Lock/Disable, ein Wert den das Handbuch gar nicht kennt. Passend dazu fehlt im laufenden System das Flag:

grep -c intel_ppin /proc/cpuinfo    # 0

Meine Lesart: Lock bedeutet, dass das Register gesperrt ist und die Nummer nicht ausgelesen werden kann, und das Handbuch ist an dieser Stelle schlicht nicht auf dem Stand der Firmware. Für mich ist das der Wunschzustand, ich brauche keine Seriennummer und niemand sonst braucht sie von mir. Nur schön dokumentiert ist es eben nicht.

Und zum Schluss der Schalter, der nichts bewirkt

Extended APIC                   [Disable]

Das ist mein Lieblingsfund auf dieser Seite, weil er aussieht wie eine vergessene Optimierung. Der APIC ist der Interrupt-Controller. x2APIC ist seine erweiterte Fassung, die mehr adressierbare Prozessoren und schnelleren Zugriff über Register statt über den Speicher erlaubt. Auf Disable denkt man reflexhaft: aha, hier liegt Leistung brach, das schalte ich mal ein.

Nur ist die Funktion längst aktiv. Aus dem Kernel-Log meiner laufenden Maschine:

DMAR-IR: Queued invalidation will be enabled to support x2apic and Intr-remapping.
DMAR-IR: Enabled IRQ remapping in x2apic mode
x2apic enabled
APIC: Switched APIC routing to: cluster x2apic

Der Grund ist eine Abhängigkeit, die man kennen muss: Interrupt-Remapping über VT-d verlangt x2APIC. Sobald der Kernel die IOMMU mit Interrupt-Remapping hochfährt, schaltet er x2APIC selbst ein, unabhängig davon was die Firmware vorher getan hat. Der BIOS-Schalter steuert nur, ob die Firmware das schon vorab macht. B.T.W.: habe ihr euch MMU beim Commodore C128 mal angeschaut?

Ich lasse ihn deshalb bewusst in Ruhe, und ich schreibe das hier so ausführlich auf, weil er genau die Art Schalter ist, an dem man beim nächsten Setup-Besuch wieder hängenbleibt und denkt, man hätte etwas übersehen. Hat man nicht.

Nächste Folge: Speicher. ECC, Patrol Scrub, und die Frage warum mein DDR4-3200 mit 2933 läuft und das völlig in Ordnung ist.

Siehe auch

Weiss jemand genauer, warum das smx-Flag in /proc/cpuinfo auftaucht obwohl der BIOS-Schalter auf Disable steht? Ich habe dazu keine belastbare Quelle gefunden und würde es gern richtig verstehen. Ihr dürft mich jederzeit fragen.

Kein SMART für SD-Karten: sieben Linux-Werkzeuge, die trotzdem verraten, wie gesund eine Karte ist

Transcend-8-GB-microSD-Karte im SD-Adapter und USB-Kartenleser neben Linux-Werkzeugen zur Prüfung von Kapazität, Datenintegrität und Schreibleistung ohne SMART.

Eine alte 8-GB-microSD-Karte lag noch in der Schublade, entbehrlich genug für einen Härtetest. Die Ausgangsfrage war simpel: ist die Karte noch gut, oder produziert sie im Hintergrund längst stille Fehler? Bei einer SSD würde ich smartctl -a tippen und hätte binnen Sekunden Health-Prozent, Reallocated Sectors und Power-On-Hours auf dem Schirm. Bei einer SD-Karte geht genau das nicht, und der Grund dafür ist interessanter als die fehlende Zahl selbst.

Warum SMART hier nicht funktioniert

ATA- und NVMe-Laufwerke haben eine standardisierte SMART-Schnittstelle: fest definierte Attribute, die die Firmware selbst pflegt und die jedes Betriebssystem auf die gleiche Weise abfragen kann. SD-Karten haben nichts Vergleichbares. Der Flash-Controller auf der Karte übernimmt zwar Wear-Leveling und Bad-Block-Mapping, aber wie er das im Detail tut und was er darüber nach außen preisgibt, ist herstellerspezifisch und komplett verschlossen. Es gibt zwar eine SD Health Status Extension, CMD56-basiert, die unterstützt aber so gut wie kein Consumer-Werkzeug, und sie setzt einen nativen MMC-Host voraus, keinen USB-Kartenleser.

Ein Wort noch zu TRIM, bevor der Eindruck entsteht, SD-Karten hätten damit überhaupt nichts zu tun: der Linux-MMC-Stack unterstützt discard-artige Operationen für native mmcblk-Geräte durchaus. Nur bringt das über einen USB-Kartenleser nichts, weil die USB-Massenspeicher-Übersetzung diesen Pfad gar nicht durchreicht, das habe ich in diesem Test empirisch bestätigt. Und selbst auf einem nativen Host wäre das nur ein Erase-Hinweis an den Controller, keine standardisierte Health-Rückmeldung wie bei SSD-TRIM zusammen mit SMART.

Testkandidat und Aufbau

Für den Test kam eine Transcend Premium 8GB microSDHC zum Einsatz, Class 10, UHS-Speed-Class U1, in einem Samsung-Adapter auf volle SD-Größe gesteckt und über einen UGREEN-USB-Kartenleser ausgelesen. Der Leser meldet sich per USB als Genesys-Logic-Chip (05e3:0748), ein generischer Combo-Reader, wie er auch in vielen Laptops und günstigen USB-Dongles verbaut ist. Host war ein Linux Mint 22.3 mit Kernel 7.0.0-28-generic, Ubuntu-24.04-Unterbau, alle Befehle liefen als root.

MicroSD-Karte im Samsung-Adapter, gesteckt in einen schwarzen USB-Kartenleser auf einer Küchenarbeitsplatte
So kam die Karte beim Testsystem an: microSD im Samsung-Adapter, gesteckt in den UGREEN-USB-Kartenleser.

Werkzeuge installieren

Bis auf badblocks, smartctl, hdparm und dd, die auf den meisten Systemen ohnehin vorhanden sind, kommt der Rest aus den Standard-Paketquellen, keine Drittanbieter-PPA nötig:

apt-get install -y f3 mmc-utils sdparm flashbench
apt-get install -y fio   # nachinstalliert, sobald der Schritt feststand

Installiert wurden f3 8.0, mmc-utils als Git-Snapshot von 2022, sdparm 1.12, flashbench 62 und fio 3.36, alles aus dem Ubuntu-Noble-Universe-Repository. Getestet auf Linux Mint 22.3, aber auf einem reinen Ubuntu 24.04 sollten Paketnamen und Versionen identisch sein.

Schritt 1: Identifikation, warum SMART eine Sackgasse ist

$ smartctl -a /dev/sdc
/dev/sdc: Unknown USB bridge [0x05e3:0x0748 (0x1209)]
Please specify device type with the -d option.

$ smartctl -a -d sat /dev/sdc
Read Device Identity failed: scsi error unsupported scsi opcode
A mandatory SMART command failed: exiting.

$ smartctl --scan
/dev/nvme0 -d nvme
/dev/nvme1 -d nvme
# sdc taucht gar nicht erst auf, smartd hält es für keinen unterstützten Gerätetyp

$ sdparm -a /dev/sdc
MODE SENSE(10): Malformed SCSI command
    /dev/sdc: Generic   MassStorageClass  1209

$ sdparm -i /dev/sdc
    /dev/sdc: Generic   MassStorageClass  1209
Device identification VPD page:
  Addressed logical unit:
    designator type: T10 vendor identification,  code set: ASCII
      vendor id: Generic
      vendor specific: STORAGE DEVICE

smartctl erkennt die Genesys-Logic-USB-Bridge gar nicht erst als unterstützten Gerätetyp. Erzwingt man SAT, also ATA-Kommandos über SCSI getunnelt, scheitert das vollständig, weil die Bridge-Firmware das Kommando schlicht nicht implementiert. Selbst der Geräte-Scan von smartd listet /dev/sdc gar nicht auf. sdparm kommt einen Schritt weiter, eine simple SCSI-INQUIRY funktioniert, aber die gemeldete Identität bleibt vollständig generisch: „Generic MassStorageClass“, „STORAGE DEVICE“. Weder Hersteller-ID noch Seriennummer noch irgendeine Form von Wear- oder Health-Daten sind über diesen Pfad erreichbar. Das ist der konkrete, reproduzierbare Beleg dafür, dass es für SD-Karten hinter einem USB-Leser kein SMART-Äquivalent gibt.

$ lsusb -v -d 05e3:0748
Bus 002 Device 007: ID 05e3:0748 Genesys Logic, Inc. All-in-One Cardreader
  bcdUSB               3.10
  idVendor           0x05e3 Genesys Logic, Inc.
  idProduct          0x0748 All-in-One Cardreader
  iSerial                 5 000000001209
  bInterfaceClass         8 Mass Storage
  bInterfaceSubClass      6 SCSI
  bInterfaceProtocol     80 Bulk-Only
  SuperSpeed USB Device Capability:
    wSpeedsSupported   0x000e  (Full/High/SuperSpeed, bis zu 5Gbps Link)

Der Leser meldet sich als „All-in-One Cardreader“ von Genesys Logic, ein verbreiteter generischer Combo-Chip, USB-3.1-fähig, spricht auf der SCSI-Ebene aber Bulk-Only Transport (BOT), nicht UAS. Wichtig für die Kausalität: BOT statt UAS ist für sich genommen nicht der Grund, warum SAT-Passthrough scheitert. Es gibt genug BOT-Bridges, die SAT unterstützen, und genug, die es nicht tun, unabhängig vom Transportprotokoll. Belegt ist hier nur, dass ausgerechnet diese Genesys-Bridge keine ATA- beziehungsweise SAT-Kommandos durchreicht, nicht dass USB-Bulk-Only-Bridges das grundsätzlich nicht könnten.

Transcend Premium 8GB microSDHC Class 10 U1 im Samsung-Adapter, liegend auf einem UGREEN-USB-Kartenleser mit blau leuchtender Status-LED
Der Aufdruck verrät mehr als jedes Software-Tool: Transcend Premium, 8GB, Class 10, UHS-Speed-Class U1.

Schritt 2: Durchsatz mit dd und hdparm

hdparm -t allein ergab rund 90 MB/s gepuffertes Lesen, plausibel, aber es lohnt sich, mit echtem O_DIRECT-I/O gegenzuprüfen, damit keine Bridge- oder Seiten-Cache-Effekte den Wert verfälschen.

$ hdparm -Tt /dev/sdc
 Timing cached reads:   24182 MB in  2.00 seconds = 12113.48 MB/sec
 Timing buffered disk reads: 272 MB in  3.00 seconds =  90.66 MB/sec

# 512MB Zufallsnutzlast zuerst im tmpfs erzeugt, damit die CPU-Last von
# /dev/urandom die eigentliche Messung nicht verfälscht
$ dd if=/dev/urandom of=/root/sdtest/payload.bin bs=1M count=512
536870912 Bytes (537 MB, 512 MiB) kopiert, 1,74754 s, 307 MB/s

# SCHREIBEN: Direct I/O direkt auf das Rohgerät, kein Seiten-Cache beteiligt
$ dd if=/root/sdtest/payload.bin of=/dev/sdc bs=1M count=512 oflag=direct,sync
536870912 Bytes (537 MB, 512 MiB) kopiert, 22,8654 s, 23,5 MB/s

# LESEN: erst flushen, dann Direct I/O der gleichen 512MB zurück
$ blockdev --flushbufs /dev/sdc
$ dd if=/dev/sdc of=/root/sdtest/readback.bin bs=1M count=512 iflag=direct
536870912 Bytes (537 MB, 512 MiB) kopiert, 6,28156 s, 85,5 MB/s

# Integritätscheck
$ cmp /root/sdtest/payload.bin /root/sdtest/readback.bin
MATCH: identical

Ergebnis: rund 23,5 MB/s sequenzielles Schreiben, rund 85,5 MB/s sequenzielles Lesen, mit byteidentischem Rücklese-Ergebnis. Die Asymmetrie zwischen Schreiben und Lesen ist normal und erwartbar, Schreiben braucht auf Flash-Ebene ein Erase-before-Program, Lesen nicht. 23,5 MB/s reißt die Class-10- und U1-Mindestangabe von 10 MB/s locker, für U3/V30 mit 30 MB/s Minimum würde es nicht reichen, was exakt zum aufgedruckten Rating der Karte passt: Class 10, U1, keine U3- oder V30-Kennzeichnung.

Schritt 3: f3probe, ist die Kapazität echt?

Bevor der große, langsame Test kommt, lohnt sich der schnelle: f3 (Fight Flash Fraud) bringt mit f3probe einen Kapazitätsbetrugs-Check, der binnen weniger Minuten läuft, statt die ganze Karte zu beschreiben.

$ f3probe --time-ops /dev/sdc

Probe finished, recovering blocks... Done

Good news: The device `/dev/sdc' is the real thing

Device geometry:
	         *Usable* size: 7.31 GB (15333376 blocks)
	        Announced size: 7.31 GB (15333376 blocks)
	                Module: 8.00 GB (2^33 Bytes)
	Approximate cache size: 0.00 Byte (0 blocks), need-reset=no
	   Physical block size: 512.00 Byte (2^9 Bytes)

Probe time: 1'46"
 Operation: total time / count = avg time
      Read: 29.32s / 4197116 = 6us
     Write: 1'15" / 4192321 = 18us
     Reset: 0us / 1 = 0us

Verdikt: eine echte Karte, keine Fälschung. Die angekündigte Größe, 8-GB-Modul mit 7,31 GB nutzbar, deckt sich mit der tatsächlich nutzbaren Größe, die f3probe per binärer Suche gefunden hat, also dort, wo Schreibvorgänge aufhören, korrekt anzukommen. Approximate cache size: 0.00 Byte ist ebenfalls ein gutes Zeichen: manche gefälschten Karten täuschen ihre Kapazität vor, indem sie eine kleine Menge echten Flashs als Write-Back-Cache benutzen, der Schreibvorgänge über die tatsächliche Kapazität hinaus vorübergehend „schluckt“ und damit naive Benchmarks täuscht. Diese Karte zeigt diesen Trick nicht. f3probe hat die für die Prüfung benutzten Blöcke danach wiederhergestellt, ohne -n gestartet, die Karte blieb also in ihrem vorherigen, leeren Zustand. Reine Probe-Zeit rund 1:46 Minuten, mit Block-Wiederherstellung insgesamt rund 3 Minuten, deutlich schneller als ein vollständiger Schreib-Lese-Durchlauf, weil f3probe gezielt sucht statt jeden Block anzufassen.

Schritt 4: f3write und f3read, der Haupttest

$ wipefs -a /dev/sdc
$ parted -s /dev/sdc mklabel gpt mkpart primary ext4 0% 100%
$ mkfs.ext4 -F -L SDTEST /dev/sdc1
$ mount /dev/sdc1 /mnt/sdtest
$ df -h /mnt/sdtest
/dev/sdc1       7,2G     24K  6,8G    1% /mnt/sdtest

$ cd /mnt/sdtest && f3write .
Free space: 7.10 GB
Creating file 1.h2w ... OK!
...
Creating file 8.h2w ... OK!
Free space: 16.46 MB
Average writing speed: 22.78 MB/s

$ f3read .
                  SECTORS      ok/corrupted/changed/overwritten
Validating file 1.h2w ... 2097152/        0/      0/      0
...
Validating file 8.h2w ...  176128/        0/      0/      0

  Data OK: 7.08 GB (14856192 sectors)
Data LOST: 0.00 Byte (0 sectors)
	       Corrupted: 0.00 Byte (0 sectors)
	Slightly changed: 0.00 Byte (0 sectors)
	     Overwritten: 0.00 Byte (0 sectors)
Average reading speed: 90.32 MB/s

Verdikt: sauber. Jedes Byte jeder der 8 Testdateien, 7,08 GB insgesamt, bis auf rund 16 MB Restplatz gefüllt, kam exakt so zurück, wie es geschrieben wurde: 0 korrupte, 0 veränderte, 0 überschriebene Sektoren. Die gemessenen Geschwindigkeiten, 22,78 MB/s Schreiben und 90,32 MB/s Lesen, decken sich eng mit dem rohen dd-Direct-I/O-Benchmark aus Schritt 2, eine gute Gegenprobe, dass die Zahlen echt sind und kein Artefakt einer einzelnen Methode.

Schritt 5: badblocks, die Musterprüfung

$ umount /mnt/sdtest
$ badblocks -wsv /dev/sdc

Es wird nach defekten Blöcken gesucht (Lesen+Schreiben-Modus)
Von Block 0 bis 7666687
Es wird getestet Mit Muster 0xaa: ... 100% erledigt
Es wird getestet Mit Muster 0x55: ... 100% erledigt
Es wird getestet Mit Muster 0xff: ... 100% erledigt
Es wird getestet Mit Muster 0x00: ... 100% erledigt
Durchgang beendet, 0 defekte Blöcke gefunden. (0/0/0 Fehler)

Verdikt: 0 defekte Blöcke, über alle 4 Standard-Testmuster hinweg (0xAA/01010101, 0x55/10101010, 0xFF/lauter Einsen, 0x00/lauter Nullen, gewählt, um Stuck-at-0- und Stuck-at-1-Zellfehler ebenso wie Kopplungsfehler zwischen Nachbarbits zu erwischen). Gesamtlaufzeit für den kompletten Schreib-Lese-Vergleichs-Zyklus über alle 4 Muster: rund 26 Minuten 47 Sekunden auf dieser 7,31-GiB-Karte, was zur Schätzung von rund 27 Minuten aus den früheren Durchsatzwerten passt, 4-mal Schreibdurchlauf plus Lesedurchlauf bei rund 23,5/85 MB/s.

badblocks -w ist ein linearer, positionsbasierter Test, Teil von e2fsprogs. In einem Durchgang schreibt er dasselbe Muster auf jeden logischen Block und liest es zurück. Genau das ist der strukturelle Unterschied zu f3write/f3read, und der Grund, warum badblocks kein Ersatz für f3 bei der Kapazitätsbetrugsfrage ist: eine Karte, die Schreibvorgänge still auf einen kleineren physischen Bereich zurückführt, könnte in einem badblocks-Durchgang trotzdem das „richtige“ Muster zurückliefern, weil ohnehin jeder logische Block identischen Inhalt bekommt, das Aliasing bliebe unsichtbar. f3write vermeidet das, indem es positionsabhängige Pseudozufallsdaten schreibt, sodass Wraparound oder Aliasing als Mismatch auffällt. badblocks beantwortet dafür eine engere, ergänzende Frage: versagen bestimmte Blöcke oder Zellen dabei, irgendein Muster zuverlässig zu speichern, was der einmalige, pseudozufällige Schreibdurchgang von f3 pro Karte nicht so systematisch prüft, kein 0xAA/0x55/0xFF/0x00-Stuck-Bit-Sweep.

Schritt 6: flashbench, ein Blick unter die Haube

$ flashbench -f -o /root/sdtest/flashbench.out /dev/sdc
$ cat /root/sdtest/flashbench.out

4MiB    28.5M/s  28.3M/s  26.7M/s  27.6M/s  23.4M/s  28M/s
2MiB    26.6M/s  28.2M/s  26.7M/s  28.1M/s  26.8M/s  28.1M/s
1MiB    26.6M/s  28.8M/s  26.4M/s  28.4M/s  26.6M/s  27.3M/s
512KiB  26.7M/s  28.2M/s  26.9M/s  28.9M/s  26.8M/s  28.3M/s
256KiB  26.7M/s  28.2M/s  26.6M/s  27.8M/s  27M/s    28.8M/s
128KiB  26M/s    28.5M/s  26.7M/s  28.3M/s  26.8M/s  28.3M/s
64KiB   26.9M/s  28.8M/s  26.8M/s  28.3M/s  26.7M/s  27.9M/s
32KiB   12.6M/s  12.7M/s  13.1M/s  12.6M/s  12.9M/s  12.8M/s
16KiB   5.78M/s  5.91M/s  5.87M/s  5.82M/s  5.82M/s  5.86M/s

flashbench -f (find-fat) liest am Ende der ersten paar Erase-Blöcke zunehmend kleinere Häppchen und misst die Zeit dafür, auf der Suche nach dem Punkt, an dem die Lesegeschwindigkeit plötzlich einbricht. Die Grundidee: das deutet auf die interne Lese- beziehungsweise Page-Granularität des Flashs hin, weil das Lesen eines Teils einer internen Page oder eines Blocks genauso viel kosten kann wie das Lesen der ganzen Einheit, sub-granulare Reads verschwenden dann Bandbreite. Hier hält sich das Plateau, rund 26 bis 29 MB/s, passend zum rohen sequenziellen Lese-Benchmark, stabil bis hinunter zu 64 KiB, bricht dann bei 32 KiB auf weniger als die Hälfte ein (rund 12,6 bis 13,1 MB/s) und nochmal auf etwa ein Fünftel bei 16 KiB (rund 5,8 bis 5,9 MB/s). Ein sauberes, reproduzierbares Signal, 6 Wiederholungen pro Zeile, alle konsistent.

Die Schlussfolgerung bleibt bewusst vorsichtig formuliert: das ist ein Performance-Knick bei 64 KiB auf diesem konkreten Reader-Controller-Pfad, konsistent mit einer größeren internen Leseeinheit oder FTL-Gruppierung, aber keine direkte, verifizierte Messung der tatsächlichen physischen NAND-Page-Größe der Karte (typischerweise 8 bis 16 KiB, dafür bräuchte es Herstellerdokumentation oder eine andere Messmethode). Die Daten sind mit einer bestimmten Erklärung konsistent, sie beweisen sie nicht.

Schritt 7: fio, bleibt die Schreibrate konstant?

dd und f3write liefern Durchschnittswerte. fio mit einem Bandbreiten-Log pro Sekunde zeigt dagegen die Form des Schreibvorgangs über die vollen 6 GB, genau das will man sehen, wenn man einen Pseudo-SLC-Cache-Absturz sucht: viele Karten schreiben schnell in einen kleinen SLC-Modus-Puffer, bis der voll ist, und fallen danach auf eine deutlich niedrigere TLC- oder QLC-Dauerschreibrate ab.

$ fio --name=sdcard-write --filename=/dev/sdc --rw=write --bs=1M --size=6G --direct=1 --ioengine=psync --iodepth=1 --write_bw_log=/root/sdtest/fio_write --log_avg_msec=1000 --group_reporting

write: IOPS=26, BW=26.0MiB/s (27.3MB/s)(6144MiB/236179msec)
bw (KiB/s): min=24576, max=27675, per=100.00%, avg=26645.27, stdev=457.38, samples=236

Der Blick ins rohe Sekunden-Log lohnt sich, nicht nur die Zusammenfassung: 236 Einsekunden-Stichproben über den kompletten 6-GB-Schreibvorgang, alle im Bereich von 24576 bis 27675 KiB/s, also rund 24,0 bis 27,0 MB/s. Erste und letzte Stichprobe liegen im selben schmalen Band, kein erhöhter Burst am Anfang, kein Absturz später.

Verdikt: flach, kein erkennbarer SLC-Cache-Absturz auf dieser Karte. Auch hier lohnt sich die vorsichtige Formulierung: das beweist nicht, dass die Karte null Schreibpufferung hat, nur dass ein eventueller Puffer beziehungsweise Absturz innerhalb dieses 6-GB-Testfensters auf einer Karte mit nur rund 7,3 GB nutzbarer Gesamtkapazität nicht sichtbar wurde. Es könnte schlicht nicht genug Karte nach dem Puffer übrig sein, um einen Absturz zu zeigen, oder, wahrscheinlicher bei einer reinen Class-10/U1-Budgetkarte ohne A1/A2- oder „Extreme/Pro“-Einstufung, sie implementiert gar kein dynamisches SLC-Caching. So oder so steht das praktische Ergebnis: die Schreibleistung war über die gesamte Kapazität konsistent und vorhersagbar, nicht nach vorne verlagert.

Was für eMMC gilt, aber nicht für SD

Ein paar Dinge, die in diesem Testlauf nicht zum Einsatz kamen, aber der Vollständigkeit halber dazugehören. mmc extcsd read und seine Lebensdauer-Schätzfelder, das Nächste an einer echten Health-Prozentangabe in diesem Umfeld, sind eine eMMC-Eigenschaft. Eine wechselbare SD- oder microSD-Karte ist kein eMMC, dieser Pfad stand hier ohnehin nicht zur Verfügung, USB-Leser statt nativer MMC-Host. Ein nativer MMC-Host-Slot, etwa der eingebaute SD-Slot eines Laptops, würde dagegen mehr Identitätsdaten offenlegen als ein USB-Leser: /sys/block/mmcblkX/device/ mit cid, csd, scr, serial, manfid, name, oemid, preferred_erase_size und date, der Kernel parst die Identitätsregister der Karte direkt in sysfs, ganz ohne Zusatzwerkzeug. Wer also einen echten SD/MMC-Controller statt einer USB-SCSI-Bridge als Leser hat, bekommt das gratis dazu, in diesem Test war davon nichts erreichbar.

Und noch ein Missverständnis vorweg: der „SD Card Formatter“ der SD Association ist ein Formatierungs- und Reset-Werkzeug, das die werkseitige Partitionierung wiederherstellt, nachdem andere Betriebssysteme sie durcheinandergebracht haben, kein Health-Check-Werkzeug.

Die Reihenfolge, wenn du das nachmachen willst

Vom schnellsten und harmlosesten zum langsamsten und gründlichsten, ungefähr so, wie dieser Test auch abgelaufen ist:

  1. lsusb -v, udevadm info, dmesg. Leser und Karte identifizieren, Schreibschutz-Status vorab prüfen.
  2. smartctl -a -d sat und sdparm -a. Schneller, harmloser Versuch an Health- und Identitätsdaten, meist scheitert das über einen USB-Leser, und genau dieses Scheitern ist der Punkt, den man erklären sollte.
  3. f3probe. Schneller Kapazitätsbetrugs-Check, für 8 GB etwa 2 bis 3 Minuten, minimal destruktiv, stellt die benutzten Blöcke standardmäßig wieder her.
  4. f3write plus f3read. Der Haupttest, vollständige Schreib-Lese-Integritätsprüfung über die gesamte Kapazität, braucht ein Dateisystem.
  5. badblocks -w. Destruktive Vier-Muster-Prüfung auf defekte Blöcke, Rohgerät, kein Dateisystem nötig. Fängt Dinge, die f3 strukturell nicht kann, siehe Schritt 5.
  6. dd mit oflag/iflag=direct und/oder fio mit Bandbreiten-Log. Durchsatzzahlen und, mit fio, die Form der Schreibrate über die Zeit.
  7. flashbench -f -o DATEI. Optional, für die Neugier, Hinweise auf die interne Lese-Granularität.

Fazit

Für diese konkrete Karte: gesund, echt, keine Defekte gefunden, bei jedem Test, der überhaupt in der Lage gewesen wäre, welche zu finden. Keine SMART-Werte erreichbar über den USB-Leser, echte Kapazität ohne Cache-Trickserei, null Datenkorruption über 7,08 GB, null defekte Blöcke über alle 4 Muster, rund 23 bis 27 MB/s Schreiben und rund 85 bis 90 MB/s Lesen, konsistent über vier unabhängige Messmethoden, kein SLC-Cache-Absturz über die volle Kapazität. Die Karte reißt ihre aufgedruckte Class-10/U1-Angabe locker, für U3/V30 würde es nicht reichen, was sie auch gar nicht behauptet.

Der eigentliche Punkt reicht über diese eine Karte hinaus: ohne SMART bleibt nur Verhaltenstestung statt Register-Abfrage. Schreiben, lesen, vergleichen, und der Karte dabei zusehen, statt sie nach einer Zahl zu fragen, die sie gar nicht hat.

Siehe auch

Falls jemand einen zuverlässigeren Weg zur Health-Einschätzung einer SD-Karte unter Linux kennt, immer her damit. Und falls ihr selbst öfter mit fragwürdigen Billigkarten zu tun habt, dürft ihr mich zu dem Thema sehr gerne fragen.

ECH aktivieren: Encrypted Client Hello in nginx mit OpenSSL 4.0, sechs Domains und ein gemeinsamer Deckname

Der Servername im TLS-Handshake steht bis heute im Klartext im Netz, unabhängig davon, wie gut die DNS-Anfrage davor verschlüsselt war. Wer DNS over HTTPS oder DNS over TLS einsetzt, verbirgt den Lookup selbst, aber sobald der Browser die eigentliche Verbindung aufbaut, nennt das Client Hello die besuchte Domain im Server Name Indication Feld offen. Jeder Netzwerkbeobachter zwischen Client und Server sieht damit weiterhin, welche Website gerade aufgerufen wird, egal wie sauber DNS-Ebene und Transportebene sonst verschlüsselt sind.

Illustration zu Encrypted Client Hello (ECH): Ein sichtbarer ClientHelloOuter mit dem gemeinsamen Decknamen ech.kernel-error.de schützt die verschlüsselten Hostnamen mehrerer Domains auf einem nginx-Server mit OpenSSL 4.0.

Encrypted Client Hello schließt genau diese Lücke, indem der eigentliche Servername in einen zweiten, verschlüsselten Client Hello wandert. Der folgende Beitrag beschreibt, wie ECH auf dieser Infrastruktur aktiviert wurde: mit OpenSSL 4.0 als Voraussetzung, nginx als Ports-Build und sechs Domains, die sich einen gemeinsamen Deckname teilen. Alle Befehle, Konfigurationsausschnitte und Testausgaben stammen aus dem produktiven Rollout und sind gegengeprüft, aktuell laufen alle sechs sauber verifiziert.

Was Encrypted Client Hello eigentlich verbirgt

Ein ECH-Verbindungsaufbau enthält zwei Client Hellos statt einem. Das äußere ist wie gewohnt unverschlüsselt, trägt aber nicht die echte Domain, sondern einen Deckname. Das innere steckt verschlüsselt im äußeren und enthält die tatsächlich angefragte Domain:

ClientHelloOuter: unverschlüsselt sichtbar, enthält public_name
ClientHelloInner: verschlüsselt, enthält die tatsächlich besuchte Domain

Der Server entschlüsselt das innere Client Hello mit einem Schlüssel, den er vorher über DNS veröffentlicht hat, wählt anhand der darin enthaltenen echten SNI den passenden vHost aus und antwortet als wäre nie ein Deckname im Spiel gewesen. Für den Browser läuft das komplett automatisch ab, sobald der Server ECH per DNS anbietet.

Voraussetzungen, bevor es losgeht

  • OpenSSL 4.0 oder neuer (ECH ist seit dem stabilen 4.0.0-Release vom 14. April 2026 Bestandteil, im Alpha-/Dev-Branch bereits ab 10./11. März 2026 verfügbar), alternativ BoringSSL
  • nginx 1.29.4 oder neuer, mit der Direktive ssl_ech_file (hier im Einsatz: 1.30.4)
  • eine DNSSEC-signierte Zone ist sinnvoll, aber keine normative ECH-Voraussetzung, dazu weiter unten mehr

Auch der erste Punkt ist keine Formalität. Das Lighttpd-Wiki führt ECH aktuell weiterhin als EXPERIMENTAL und nennt dieselbe Voraussetzung, OpenSSL 4.0 oder BoringSSL. ECH ist über die Server-Landschaft hinweg gesehen noch frisch, entsprechend lohnt vor dem eigenen Rollout ein Blick auf die aktuell installierte Softwareversion, nicht auf das, was vor einem halben Jahr noch galt.

Der Umstieg auf OpenSSL 4.0 als Ausgangspunkt

nginx läuft in dieser Jail als Ports-Build, nicht als Repo-Paket, weil das Paket Brotli und headers_more deaktiviert ausliefert. Damit lässt sich die verwendete OpenSSL-Hauptversion über die SSL-Flavor-Einstellung in /etc/make.conf steuern und nginx anschließend dagegen neu bauen:

jexec nginx pkg unlock -y nginx
# /etc/make.conf: DEFAULT_VERSIONS+=ssl=openssl40 (vorher openssl35)
jexec nginx make -C /usr/ports/www/nginx missing
jexec nginx make -C /usr/ports/www/nginx -DBATCH build
jexec nginx make -C /usr/ports/www/nginx -DBATCH deinstall install
jexec nginx pkg lock -y nginx
jexec nginx service nginx restart

Ein reload reicht an dieser Stelle ausdrücklich nicht. Die neuen Shared Libraries werden erst beim vollständigen Neustart der Worker-Prozesse eingebunden, ein reload behält die alten libssl-Bindings der laufenden Prozesse bei. Die neue Version zeigt sich danach direkt im Server-Build:

$ nginx -V
built with OpenSSL 4.0.1 9 Jun 2026

public_name ist die eigentliche Entscheidung

ssl_ech_file ist keine Bastellösung und kein Fork, sondern eine echte, in nginx-Mainline gemergte Direktive, sichtbar direkt im Quellcode unter src/http/modules/ngx_http_ssl_module.c und src/event/ngx_event_openssl.c. Technisch nutzt sie die OSSL_ECHSTORE-API, die mit OpenSSL 4.0 dazugekommen ist.

Der eigentlich entscheidende Wert ist public_name, der Name, der im unverschlüsselten ClientHelloOuter sichtbar bleibt. Ein Netzwerkbeobachter sieht ihn ohnehin, ECH hin oder her. Setzt man ihn auf die eigene, echte Domain, bringt die ganze Übung keinerlei Verschleierung: der Beobachter sieht exakt den Namen, den er auch ohne ECH gesehen hätte, nur mit einem zusätzlichen Handshake-Schritt drumherum.

Echten Schutz gibt es nur mit einem gemeinsamen public_name über mehrere unabhängige Domains hinweg, dem Anonymitätsset-Prinzip. Genau so macht es Cloudflare mit cloudflare-ech.com als Deckname für Millionen Kundendomains: wer nur sieht, dass jemand eine Verbindung zu cloudflare-ech.com aufbaut, weiß damit nichts über die konkret besuchte Seite unter den Millionen dahinter.

Ein gemeinsamer public_name reicht dafür allein aber noch nicht. Im ClientHelloOuter steht neben dem public_name auch die config_id unverschlüsselt, ein einzelnes Byte, das die verwendete ECHConfig identifiziert. Nutzt jede Domain einen eigenen ECH-Schlüssel, hat jede Domain automatisch auch eine eigene config_id, und ein Beobachter kann die Domains trotz identischem Deckname allein anhand dieses Bytes unterscheiden. RFC 9849 bringt es auf den Punkt: verwendet ein Server für verschiedene Servernamen unterschiedliche ECHConfig-Werte, hat jedes Anonymitätsset effektiv Größe 1, also gar keinen Effekt. Die Konsequenz für den eigenen Aufbau: alle teilnehmenden Domains müssen denselben Schlüssel und dieselbe ECHConfig nutzen, nicht nur denselben Deckname.

Diese eine nginx-Instanz hostet bereits rund zwanzig unabhängige Domains auf derselben IP, darunter dieser Blog, eine Therapiepraxis-Website, eine Fotogalerie, private Familienseiten, Webmail und ein URL-Shortener. Die Voraussetzung für ein eigenes, kleines Anonymitätsset war also schon da, es musste nur genutzt werden.

Umgesetzt wurde ein dedizierter Deckname, ech.kernel-error.de, ohne eigenen Inhalt. Sechs Domains teilen sich einen gemeinsamen ECH-Schlüssel und damit dieselbe ECHConfig, denselben public_name und dieselbe config_id: www.kernel-error.de, die Apex-Domain kernel-error.de, webmail.kernel-error.de, bilder.kernel-error.de und kernel-error.org als eigene, ebenfalls DNSSEC-signierte Zone. Die TLS-Zertifikats-Schlüssel der einzelnen Domains bleiben davon unberührt getrennt, das ist ein anderes Schlüsselpaar als der ECH-HPKE-Schlüssel und für die Anonymitätsset-Eigenschaft ohne Belang. stammbaum.kernel-error.de, ein CNAME auf www, erbt die ECH-Konfiguration automatisch per DNS-CNAME-Chasing, ganz ohne eigenen Eintrag.

Schlüssel erzeugen

Für die Schlüsselerzeugung braucht es das Port-OpenSSL unter /usr/local/bin/openssl, nicht das Base-System-OpenSSL von FreeBSD, dem fehlt der ech-Subcommand komplett:

openssl ech -public_name ech.kernel-error.de -out /usr/local/etc/nginx/ssl/ech/shared.kernel-error.pem

Das Ergebnis ist eine PEM-Datei mit zwei Blöcken, dem privaten Schlüssel und der öffentlichen ECH-Konfiguration:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
-----BEGIN ECHCONFIG-----
AEb+DQBC8AAgACDKMT4bTYtGnUXKvOkeuYEWDaByMjijSRINkLQxIEHQQQAEAAEAAQATZWNoLmtlcm5lbC1lcnJvci5kZQAA
-----END ECHCONFIG-----

Der Base64-Block zwischen den ECHCONFIG-Markern ist exakt der Wert, der später als ech=-Parameter in den DNS-Eintrag wandert. Das PEM-Format selbst folgt keinem nginx- oder OpenSSL-eigenen Standard, sondern ist mittlerweile als RFC 9934 festgeschrieben, hervorgegangen aus dem Internet-Draft draft-farrell-tls-pemesni, den auch das DEfO-Projekt in seinen ech-dev-utils dokumentiert.

Ein gemeinsamer Schlüsselspeicher für alle Domains

Bei mehreren Domains mit gemeinsamem public_name gehört ssl_ech_file auf die http{}-Ebene in der nginx.conf, noch vor die vHost-Includes, nicht in die einzelnen vHost-Dateien:

ssl_ech_file /usr/local/etc/nginx/ssl/ech/shared.kernel-error.pem;

Der Grund liegt in der Reihenfolge des Handshakes. Die ECH-Entschlüsselung passiert, bevor nginx anhand der noch verschlüsselten echten SNI überhaupt den passenden vHost auswählen kann. In diesem Moment entscheiden die äußere SNI, also der gemeinsame public_name, und die vom Client mitgeschickte config_id, welcher Schlüssel überhaupt zum Entschlüsseln probiert wird. Ein Store auf http{}-Ebene stellt sicher, dass genau dieser eine, gemeinsame Schlüssel unabhängig vom initial per SNI getroffenen Serverblock verfügbar ist, egal welche der sechs Domains die Verbindung eigentlich betrifft.

Ein eigenes Zertifikat für den Deckname

Der Deckname selbst braucht ein eigenes Zertifikat mit ausschließlich diesem einen SAN-Eintrag, kein Wildcard-Zertifikat, das nebenbei auch die echten Domains auflistet. Sonst verrät schon das äußere Zertifikat die Domain-Familie, selbst wenn die konkrete Domain im Handshake verschlüsselt bleibt:

certbot certonly --nginx -d ech.kernel-error.de --key-type ecdsa
X509v3 Subject Alternative Name:
    DNS:ech.kernel-error.de

Der nginx-vHost für den Deckname bleibt inhaltsleer und erbt den Schlüsselspeicher automatisch von der http{}-Ebene:

server {
    listen      [::]:443 ssl;
    http2 on;
    server_name ech.kernel-error.de;
    ssl_certificate     /usr/local/etc/letsencrypt/live/ech.kernel-error.de/fullchain.pem;
    ssl_certificate_key /usr/local/etc/letsencrypt/live/ech.kernel-error.de/privkey.pem;
    include              /usr/local/etc/nginx/tls-default.conf;
    location / { return 204; }
}

Der DNS-Eintrag

Die ECH-Konfiguration wird nicht als eigener Record-Typ verteilt, sondern als zusätzlicher ech=-SvcParam an der ohnehin schon vorhandenen HTTPS-Resource-Record angehängt, und zwar identisch auf allen sechs teilnehmenden Domains. Der Bootstrap-Mechanismus dafür steht in RFC 9848.

www  IN HTTPS  1 www.kernel-error.de. ( alpn="h3,h2" ipv4hint=148.251.30.200
     ipv6hint=2a01:4f8:262:4716::443
     ech="AEb+DQBC8AAgACDKMT4bTYtGnUXKvOkeuYEWDaByMjijSRINkLQxIEHQQQAEAAEAAQATZWNoLmtlcm5lbC1lcnJvci5kZQAA" )

Eine DNSSEC-signierte Zone ist dafür nicht normativ vorgeschrieben, aber sinnvoll: ohne Signatur kann ein Man in the Middle den ech=-Parameter einfach aus der Antwort entfernen, ein klassischer Downgrade auf Klartext-SNI, den ein validierender Resolver mit DNSSEC erkennt. Vor jeder Form von Downgrade schützt das trotzdem nicht: ein Angreifer kann die gesamte HTTPS/SVCB-Auflösung unterdrücken statt sie zu manipulieren, und gegen dieses Denial hilft DNSSEC allein nicht.

Verifikation nach dem Rollout

Der naheliegende erste Test ist openssl s_client mit dem passenden ECH-Config-List-Wert. Ohne ein gesetztes CA-Bundle lassen sich ein echter ECH-Fehler und ein simpler Zertifikatsfehler dabei nicht unterscheiden, -CAfile ist deshalb Pflicht, nicht optional:

openssl s_client -connect 148.251.30.200:443 -servername www.kernel-error.de -ech_config_list "AEb+DQBC8AAgACDKMT4bTYtGnUXKvOkeuYEWDaByMjijSRINkLQxIEHQQQAEAAEAAQATZWNoLmtlcm5lbC1lcnJvci5kZQAA" -CAfile /etc/ssl/cert.pem
Verify return code: 0 (ok)
ECH: success: 1
ECH: inner: www.kernel-error.de
ECH: outer: ech.kernel-error.de

Derselbe Test läuft identisch, mit demselben Base64-Wert und demselben Erfolg, auch für kernel-error.de, webmail.kernel-error.de, bilder.kernel-error.de und kernel-error.org, alle fünf gegen dieselbe ECHConfig verifiziert.

Für eine unabhängige Zweitmeinung von außen eignet sich das externe Tool echcheck. Es prüft nicht nur, ob der Handshake gelingt, sondern auch DNS-Veröffentlichung, verwendete Kryptografie, das innere Zertifikat und die Retry-Konfiguration einzeln:

ECHConfig aus dem DNS: ✓
finale ECH-Version 0xfe0d: ✓
X25519, HKDF-SHA256, AES-128-GCM: ✓
öffentlicher Name ech.kernel-error.de: ✓
echter ECH-Handshake: Accepted (TLS 1.3): ✓
gültiges inneres Zertifikat: ✓
gültige Retry-Konfiguration, Wiederholung erfolgreich: ✓
SNI Leakage: ✓ (Einzel-SAN-Zertifikat für den Deckname)
Certificate (outer): ✓ (Einzel-SAN-Zertifikat für den Deckname)

In allen neun geprüften Kategorien kommt ein Haken zurück, auch bei den beiden Punkten, die erst nach dem Einzel-SAN-Zertifikat für den Deckname sauber wurden. Ein einmaliger CLI-Test sagt aber nichts darüber, ob echte Clients die Funktion später auch tatsächlich nutzen. Dafür lohnt sich ein erweitertes Log-Format, das den ECH-Status jeder einzelnen Verbindung mitschreibt:

log_format tls_extended
  '$remote_addr - $remote_user [$time_local] '
  '"$request" $status $body_bytes_sent '
  '"$http_referer" "$http_user_agent" '
  'proto=$ssl_protocol cipher=$ssl_cipher kx_curve=$ssl_curve '
  'alpn=$ssl_alpn_protocol reused=$ssl_session_reused sni=$ssl_server_name '
  'ech_status=$ssl_ech_status ech_outer_sni=$ssl_ech_outer_server_name';

Im laufenden Traffic zeigen sich damit bereits echte ech_status=SUCCESS-Verbindungen, neben NOT_TRIED für Clients ohne ECH-Unterstützung und GREASE für Clients, die absichtlich eine leere ECH-Erweiterung mitschicken, um Middleboxen an das Feld zu gewöhnen. Genau dieser Log-Eintrag ist der eigentliche Erfolgsnachweis, nicht der einzelne CLI-Test.

Während dieser TLS-Arbeiten am selben Server ist außerdem noch ein kleinerer Nebenfund aufgetaucht: ein externer TLS-Scan meldete, dass der Server unter TLS 1.2 weiterhin SHA-224 als Hash-Funktion für die Signatur beim Schlüsselaustausch akzeptierte, obwohl die konfigurierten Cipher-Suiten ohnehin ausschließlich ECDSA nutzen. RFC 9847 stuft SHA-224 in der TLS-HashAlgorithm-Registry inzwischen als „Discouraged“ ein, während SHA-256, SHA-384 und SHA-512 dort als „Recommended“ geführt werden. Ein guter Grund, es trotzdem weiter anzubieten, findet sich aber nicht, wenn SHA-256 und höher ohnehin zur Verfügung stehen. Unter TLS 1.3 stellt sich die Frage gar nicht erst, dort ist für SHA-224 kein Signature Scheme registriert, die Hash-Funktion lässt sich strukturell nicht wählen.

Cipher-Suite und Signaturalgorithmus fürs Handshake sind zwei getrennte Stellschrauben, eine sauber konfigurierte Cipher-Suite sagt nichts über die andere aus. Ohne explizite Vorgabe verwendet OpenSSL seine Default-Liste der SignatureAlgorithms, und die enthält SHA-224 weiterhin aus Kompatibilitätsgründen:

openssl s_client -connect 148.251.30.200:443 -servername www.kernel-error.de -tls1_2 -sigalgs "ECDSA+SHA224"
Peer signing digest: SHA224
New, TLSv1.2, Cipher is ECDHE-ECDSA-CHACHA20-POLY1305

Der Fix ist eine Zeile in der gemeinsamen TLS-Konfigurationsdatei, damit gilt sie automatisch für alle vHosts, nur ECDSA-Varianten gelistet, passend zum bestehenden reinen ECDSA-Cipher-Setup:

ssl_conf_command SignatureAlgorithms ECDSA+SHA384:ECDSA+SHA256:ECDSA+SHA512;

Auch hier reichte ein reload nicht, erst ein vollständiger Neustart übernahm die geänderte Liste, aus demselben Grund wie beim OpenSSL-Umstieg weiter oben. Danach lehnt der Server SHA-224 ab, SHA-256 funktioniert unverändert:

SHA224: error:...:SSL routines:ssl3_read_bytes:tls alert handshake failure:...
SHA256: Peer signing digest: SHA256, New, TLSv1.2, Cipher is ECDHE-ECDSA-CHACHA20-POLY1305

Fazit

OpenSSL 4.0 war die technische Voraussetzung für diesen Umstieg, die eigentliche Entscheidung fiel aber woanders. Ein ECH-Setup mit dem eigenen Domainnamen als public_name wäre in wenigen Minuten fertig gewesen und hätte am Ende nichts verschleiert. Der Wert liegt allein in einem gemeinsamen Deckname über mehrere unabhängige Domains, und den gab es hier schon, bevor überhaupt eine Zeile Konfiguration geschrieben wurde.

Firefox unterstützt ECH seit Version 118, standardmäßig aktiv ist es seit Version 119, jeweils automatisch sobald ein Server es per DNS anbietet. Bei Chromium-basierten Browsern lässt sich keine ebenso feste Versionsgrenze nennen, der Rollout läuft dort gestaffelt über Channel und Policy. Der Nutzer muss in beiden Fällen nichts einstellen, die Aushandlung läuft beim ersten Verbindungsaufbau im Hintergrund. Wie schnell sich diese Versionsstände weiterentwickeln, lässt sich hier nicht dauerhaft verlässlich festschreiben, ein aktueller Blick in die jeweilige Browser-Dokumentation lohnt bei Zweifeln also immer.

Wer selbst mehrere unabhängige Domains auf derselben IP betreibt, hat die Grundvoraussetzung für ein eigenes Anonymitätsset damit häufig schon, ganz ohne Cloudflare oder einen anderen großen Anbieter dazwischen.

Siehe auch

Bei Fragen zum Setup, zur Wahl des Decknamens oder zum eigenen Anonymitätsset dürft ihr mich sehr gerne fragen.

OpenPGP-Karte eingerichtet: Curve 25519 scheitert am Kartenlimit, die Unterschlüssel landen trotzdem in Hardware

Moin. Der letzte Beitrag endete mit einem Satz, der mir seitdem im Kopf herumspukt: „Nichts hier braucht eine Smartcard, und nichts hier braucht root.“ Das stand in der Restrisiken-Liste des Ed25519-Beitrags, gleich neben dem Eingeständnis, dass eine Smartcard das größte verbleibende Risiko zwar verkleinern, aber nicht auflösen würde. Genau diese Lücke wollte ich mir ansehen: taugt eine OpenPGP-Karte dafür, den Primärschlüssel offline vorzuhalten, statt ihn nur auf einem Cold-Storage-Stick liegen zu lassen?

OpenPGP-Smartcard mit drei hardwaregebundenen Unterschlüsseln für Signatur, Verschlüsselung und Authentifizierung; der Primärschlüssel bleibt separat offline gespeichert.

Die kurze Antwort: nein, technisch geht das nicht. Eine OpenPGP-Karte kennt genau drei Schlüsselslots, Signatur, Verschlüsselung und Authentifizierung. Für den Zertifizierungsschlüssel, also den, der die Unterschlüssel überhaupt erst beglaubigt, gibt es keinen Slot. Der Primärschlüssel mit der Fähigkeit [C] passt schlicht nicht drauf.

Die längere Antwort ist der eigentliche Grund für diesen Beitrag. Die Karte macht etwas anderes, mindestens genauso viel wert. Sie nimmt die drei Unterschlüssel auf, die du im Alltag tatsächlich benutzt, und macht sie hardwaregebunden. Nicht kopierbar, nicht extrahierbar, während der Primärschlüssel unverändert offline liegen bleibt. Diese Erwartungskorrektur gehört an den Anfang und nicht als Fußnote irgendwo im Text, weil sie der Grund ist, warum dieser Beitrag neben dem Ed25519-Beitrag überhaupt eine eigene Daseinsberechtigung hat.

Weißer Versandumschlag mit Pinguin-Sticker, darunter die Chipkarte mit sichtbarem Goldchip.
So kommt eine OpenPGP-Karte an, hier eine blanko Smart Card V3.4 vom FLOSS-Shop. Der Chip schimmert schon durch den Umschlag.

Das Testsetup

Bevor irgendein echter Schlüssel in die Nähe der Karte kommt, brauchte es eine Umgebung, in der ich ohne Risiko herumprobieren kann. Die Eckdaten:

KarteOpenPGP Smart Card V3.4, ID-1 blanko, FLOSS-Shop, Serie 0000D961, Chip von ZeitControl
ReaderREINER SCT cyberJack pinpad(a), Baujahr 2008
Demo-Schlüsselisoliertes GNUPGHOME, UID „OpenPGP Card Demo card-demo@example.invalid“
Warum isoliertScreenshots dürfen ungeschwärzt raus, und ich wollte mit factory-reset und wiederholten Fehlversuchen experimentieren können, ohne das eigentliche Setup zu gefährden

Der Reader ist derselbe cyberJack pinpad(a), den ich mir vor einer Weile aus Neugier auseinandergenommen hatte. Für diesen Beitrag zählt an ihm eigentlich nur eine Eigenschaft: er hat eine eigene Zifferntastatur samt Display, PINs gehen also nie über die Tastatur deines Rechners und damit auch nie durch dessen Speicher.

Reader-Setup: zwei Fallstricke, bevor überhaupt eine Karte drinsteckt

Der erste Stolperstein kam, bevor ich überhaupt eine Karte eingesteckt hatte. scdaemon, der Smartcard-Daemon von GnuPG, versucht standardmäßig, den internen CCID-Treiber zu benutzen. Der cyberJack spricht aber ein eigenes Vendor-Protokoll über PC-SC, kein CCID, und scdaemon quittiert das mit einem schlichten „No such device“. Die Lösung steht in der Konfigurationsdatei, nicht auf der Kommandozeile:

disable-ccid

Die Zeile gehört in scdaemon.conf. Danach findet gpg den Reader über den PC-SC-Layer, und pcscd übernimmt.

Der zweite Stolperstein trat immer wieder auf, sobald ich scdaemon während der Arbeit neu gestartet oder abgeschossen habe. Der Reader blieb dann als „busy“ hängen, pcscd quittierte jeden Zugriffsversuch mit pcsc_connect: sharing violation. Ein gpgconf --kill scdaemon allein hat das nie behoben. Zuverlässig geholfen hat nur ein Neustart des Dienstes selbst:

systemctl restart pcscd

Beide Punkte sind auf jeden anderen PC-SC-Reader mit eigenem Vendor-Protokoll übertragbar, nicht nur auf den cyberJack, deswegen stehen sie hier als eigener Abschnitt.

REINER SCT cyberJack pinpad(a) Kartenleser mit Zifferntastatur, LEDs und eingesteckter OpenPGP-Karte.
Der cyberJack pinpad(a) mit eingesteckter Karte. Alles, was danach an PIN eingegeben wird, läuft über die Tasten hier und nicht über die Rechnertastatur.

Erster Schritt: die Standard-PINs ändern

Der Karte liegt eine kleine Karteikarte bei, und die erklärt in aller Ruhe, warum der allererste Schritt kein optionaler ist:

Beiliegende Karteikarte mit Standard-PIN 123456 und Admin-PIN 12345678 sowie dem Hinweis auf die Sperrfolgen.
Standard-PIN 123456, Admin-PIN 12345678, bei jeder blanko Karte identisch. Drei falsche PIN-Versuche sperren die Karte, drei falsche Admin-PIN-Versuche löschen die Daten.

Standard-PIN und Admin-PIN sind bei jeder blanko Karte identisch, stehen also öffentlich in jedem Handbuch. Wer sie nicht ändert, hat effektiv gar keinen PIN-Schutz. In gpg --card-edit geht das über admin, gefolgt von passwd:

Pinentry-Dialog mit dem Hinweis, den Admin-PIN und den neuen Admin-PIN über das Pinpad des Kartenlesers einzugeben.
Pinentry zeigt nur den Hinweis, das Pinpad des Readers zu benutzen. Der Admin-PIN selbst taucht auf dem Bildschirm nie auf.
Pinentry-Dialog mit dem Hinweis, den PIN und den neuen PIN über das Pinpad des Kartenlesers einzugeben.
Derselbe Dialog für den normalen PIN. Der Retry-Zähler danach steht bei drei von drei, unverändert gegenüber vorher.

Wichtig an beiden Dialogen ist derselbe Punkt: der eigentliche PIN geht nie über den Rechner, weder alt noch neu. Pinentry zeigt nur einen Hinweis an, die Eingabe passiert komplett am Pinpad. Genau dafür gibt man das Geld für einen Reader mit eigener Tastatur aus, sonst könnte ein kompromittierter Rechner den PIN einfach mitschneiden.

Das erste Kartenlimit: Curve 25519 scheitert

Im jungfräulichen Zustand meldet die Karte für alle drei Slots rsa2048, keine Schlüssel gesetzt:

Terminalausgabe von gpg --card-status im jungfräulichen Zustand, Schlüsselattribute überall rsa2048, keine Schlüssel gesetzt.
Der Zustand direkt nach dem Auspacken. rsa2048 überall, keine Schlüssel, die Referenz für alles, was danach kommt.

Weil der Ed25519-Beitrag genau davon handelte, war meine erste Idee naheliegend: die Kartenattribute per key-attr auf Curve 25519 umstellen, dieselbe Kurve wie beim Primärschlüssel. Curve 25519 steht im Menü sogar als Default. Die Karte lehnt sie trotzdem ab, mit Statuswort 6A80, sowohl für den Signatur- als auch für den Verschlüsselungsslot:

Terminalausgabe von gpg card-edit key-attr, Auswahl von Curve 25519 als Default endet mit Card error.
key-attr bietet Curve 25519 als Default an, aber die Karte quittiert die Umstellung mit Card error.

Tückisch daran: scdaemon meldet bei den ersten beiden Versuchen im Terminal nicht einmal einen klaren Fehler, sondern läuft einfach in die nächste Abfrage weiter. Wer sich auf den Bildschirmtext verlässt statt hinterher mit card-status nachzuschauen, merkt das Scheitern leicht gar nicht. Das FLOSS-Shop-Datenblatt zur Karte bestätigt im Nachhinein, warum: gelistet sind nur „NIST/ANSI“ und „Brainpool“, Curve 25519 taucht in der Aufzählung schlicht nicht auf. Funktioniert haben stattdessen NIST P-384 und Brainpool P-256:

Terminalausgabe von gpg card-edit key-attr, Auswahl von Brainpool P-256 für Signatur- und Verschlüsselungsschlüssel läuft sauber durch, daneben der Pinentry-Dialog für den Admin-PIN.
Brainpool P-256 für Signatur- und Verschlüsselungsslot läuft sauber durch, jede Umstellung verlangt erneut den Admin-PIN am Pinpad.

Für Signatur und Verschlüsselung bin ich bei Brainpool P-256 geblieben, den Authentifizierungsslot habe ich später aus einem eigenen Grund auf NIST P-384 umgestellt. Dazu gleich mehr, erst kommt aber ein zweites Kartenlimit, das mit Kurven gar nichts zu tun hat.

Der Admin-PIN-Timeout: kein Zufall, sondern ein hartes Zeitlimit

Bei den ersten Versuchen mit key-attr schlug die Admin-PIN-Eingabe am Pinpad immer wieder mit Statuswort 6400 fehl, ohne dass der PIN-Retry-Zähler sich bewegte. Mein erster Verdacht war eine Race Condition zwischen scdaemon und dem Reader. Also habe ich mitgestoppt, wie lange die Eingabe am Pinpad tatsächlich dauert, bis das OK gedrückt ist:

EingabedauerErgebnisFälle
3 bis 5 SekundenSW 9000, Erfolg8 von 8
7 bis 16 SekundenSW 6400, Fehler14 von 14

Keine Race Condition also, sondern ein hartes Zeitlimit von etwa fünf bis sechs Sekunden für die PIN-Eingabe am Pinpad selbst. Der PIN-Retry-Zähler bleibt bei einem Timeout unberührt, du verlierst also keinen Versuch, nur Zeit. Einen Konfigurationsschalter dagegen habe ich nicht gefunden, ich habe die komplette Flag-Liste der cyberjack.conf durchsucht. Die Option enable-pinpad-varlen in scdaemon.conf senkt die Fehlerquote spürbar, weil sie variable PIN-Längen am Pinpad erlaubt statt auf eine feste Länge zu warten, behebt das Problem aber nicht vollständig. Praktisch heißt das: PIN vorher im Kopf bereithalten, zügig eintippen, und bei einem Fehlschlag sofort denselben Schritt wiederholen statt lange zu überlegen.

Den Demo-Schlüsselsatz bauen

Mit den bekannten Kartenattributen im Kopf habe ich einen Demo-Schlüsselsatz gebaut, passend zugeschnitten: ein Ed25519-Primärschlüssel mit ausschließlich der Zertifizierungsfähigkeit [C], dazu drei Unterschlüssel in brainpoolP256r1 für Signatur und Verschlüsselung. Der naheliegende Befehl dafür scheitert allerdings:

gpg --quick-add-key 7C7A529A359FA9F8A13DF0868AEA158652F8C926 brainpoolP256r1 sign
gpg: Wrong key usage

Bei NIST- und Brainpool-Kurven kennt der Quick-Befehl offenbar nur ECDH als Default-Verwendungszweck, eine Signaturfähigkeit lehnt er direkt ab. Der Umweg über den ausführlichen Editiermodus funktioniert dagegen anstandslos, dort lässt sich die Kurve explizit wählen und die Fähigkeit im Nachhinein umschalten:

gpg --expert --edit-key 7C7A529A359FA9F8A13DF0868AEA158652F8C926
gpg> addkey
Please select what kind of key you want:
   (11) Existing key
Your selection? 11
Please select which elliptic curve you want:
   (6) Brainpool P-256
Your selection? 6
Possible actions for this ECDH key: Sign Encrypt
Current allowed actions: Encrypt
   (S) Toggle the sign capability
   (Q) Finished
Your selection? S
Your selection? Q

So entstanden, in Reihenfolge, der Verschlüsselungs-, der Signatur- und vorläufig auch der Authentifizierungs-Unterschlüssel, alle drei in Brainpool P-256. Der fertige Satz vor dem eigentlichen Übertragen auf die Karte:

Terminalausgabe von gpg list-secret-keys with-fingerprint, Ed25519-Hauptschlüssel plus drei Brainpool-P-256-Unterschlüssel für Verschlüsselung, Signatur und Authentifizierung.
Ed25519-Primärschlüssel mit reiner Zertifizierungsfähigkeit, darunter drei brainpoolP256r1-Unterschlüssel. Noch alle im lokalen Schlüsselbund, noch keiner auf der Karte.

keytocard für alle drei Unterschlüssel

Das eigentliche Übertragen läuft für jeden Unterschlüssel einzeln über keytocard, mit den üblichen Wiederholungen, wenn der Admin-PIN-Timeout wieder einmal zuschlägt:

gpg --edit-key 7C7A529A359FA9F8A13DF0868AEA158652F8C926
gpg> key 1
gpg> keytocard
Please select where to store the key:
   (2) Encryption key
Your selection? 2
gpg> key 1
gpg> key 2
gpg> keytocard
Please select where to store the key:
   (1) Signature key
Your selection? 1

Verifiziert wird das Ergebnis über gpg --card-status. Alle drei Fingerabdrücke auf der Karte stimmen mit dem lokalen Schlüsselbund überein, und dort steht jetzt ssb> statt nur ssb, also der Hinweis, dass der private Schlüsselteil nicht mehr lokal liegt, sondern nur noch als Verweis auf die Karte:

Terminalausgabe von gpg --card-status mit allen drei Unterschlüsseln auf der Karte, dazu der Schlüsselring-Abgleich mit ssb-Markierung für kartengebundene Schlüssel.
Alle drei Slots besetzt, alle drei Fingerabdrücke stimmen, und ssb> markiert im Schlüsselbund, dass der private Teil nur noch auf der Karte existiert.

Der SSH-Nachtrag: Brainpool kennt SSH nicht

Den Authentifizierungs-Unterschlüssel wollte ich zusätzlich als SSH-Schlüssel benutzen, dafür exportiert gpg-agent ihn über gpg --export-ssh-key. Für den Brainpool-Auth-Unterschlüssel scheitert das mit „Unknown elliptic curve“. Der Grund liegt nicht bei GnuPG: RFC 5656, der SSH-Standard für elliptische Kurven, kennt Brainpool schlicht nicht, nur nistp256, nistp384 und nistp521. Also habe ich den Authentifizierungsslot noch einmal neu erzeugt, diesmal mit NIST P-384, während Signatur und Verschlüsselung bei Brainpool P-256 geblieben sind, und den neuen Unterschlüssel erneut per keytocard übertragen.

Dabei sind mir zwei weitere Kleinigkeiten begegnet, beide nicht offensichtlich:

  • Meine eigene ~/.ssh/config mit einem globalen Host * / IdentitiesOnly yes blendet Card-Keys komplett aus, unabhängig davon, ob man den Agenten zusätzlich per -o IdentityAgent=... explizit angibt. Für einen sauberen Test half nur -F /dev/null, um die eigene Konfiguration ganz zu umgehen.
  • gpg-agent verlangt den Keygrip des Auth-Unterschlüssels explizit in sshcontrol, sonst kommt „agent refused operation“, und zwar noch bevor überhaupt ein Pinpad-Prompt erscheint. Kein PIN-Fehler, sondern eine reine Allowlist-Sache, die sich leicht mit einem PIN-Problem verwechseln lässt.

Drei Live-Tests, alle drei erfolgreich

Zum Schluss die eigentliche Nagelprobe, alle drei Unterschlüssel einzeln gegen die Karte getestet, mit dem Pinpad als einzigem Ort für die PIN-Eingabe.

Signatur. Eine Testnachricht clearsignen und gleich wieder verifizieren:

Terminalausgabe eines clearsign- und verify-Durchlaufs mit der Karte, Ergebnis Good signature im ultimate Vertrauensstatus.
clearsign fragt den PIN am Pinpad ab, verify bestätigt danach eine gute Signatur mit dem ECDSA-Schlüssel der Karte.

Verschlüsselung. Ein vollständiger Roundtrip aus Verschlüsseln und Entschlüsseln, wieder mit PIN-Abfrage am Pinpad beim Entschlüsseln, der Klartext kommt danach unverändert zurück:

echo "Test von der OpenPGP-Karte" | gpg --encrypt --recipient card-demo@example.invalid | gpg --decrypt
gpg: encrypted with brainpoolP256r1 key, ID ..., created ...
      "OpenPGP Card Demo (throwaway key for blog/card demo, not for real use) <card-demo@example.invalid>"
Test von der OpenPGP-Karte

SSH. Ein Login gegen den eigenen Rechner über den Auth-Unterschlüssel, mit einem nur für den Test temporär ergänzten und danach wieder entfernten Eintrag in authorized_keys:

ssh-add -L
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... cardno:0005 0000D961
ssh -F /dev/null -o IdentityAgent=$(gpgconf --list-dirs agent-ssh-socket) kernel@localhost

Der Login fragt am Pinpad nach dem PIN und ist danach ein ganz normales SSH-Login, der einzige Unterschied gegenüber einem Schlüssel auf der Festplatte ist der Prompt am Reader.

Fazit: was die Karte wirklich bringt

Zurück zur Ausgangsfrage. Nein, eine OpenPGP-Karte hält deinen Primärschlüssel nicht offline vor, weil sie keinen Zertifizierungsslot hat und ihn deswegen gar nicht aufnehmen kann. Wer genau das sucht, bleibt bei einem verschlüsselten Cold-Storage-Medium, wie im Ed25519-Beitrag beschrieben.

Was die Karte stattdessen bringt, ist aus meiner Sicht mindestens genauso viel wert: die drei Unterschlüssel, mit denen du tatsächlich täglich arbeitest, Signatur, Verschlüsselung und Authentifizierung, wandern hardwaregebunden auf ein Stück Plastik. Kein keytocard lässt sich rückgängig machen, kein Angreifer mit Zugriff auf deine Festplatte bekommt das Schlüsselmaterial zu fassen, denn es liegt dort gar nicht mehr. Der Primärschlüssel bleibt davon komplett unberührt und weiterhin offline, genau da, wo er laut Restrisiken-Liste vom letzten Mal ohnehin hingehört. Die Smartcard aus dem Satz „Nichts hier braucht eine Smartcard“ wollte etwas anderes lösen, als eine Karte lösen kann. Für das, was sie tatsächlich kann, würde ich sie nach diesem Test jederzeit empfehlen, nur eben mit einem echten statt einem Wegwerf-Demo-Schlüssel und mit etwas Geduld für den Admin-PIN-Timeout.

Siehe auch

Wenn du selbst gerade zwischen Karten oder Kurven schwankst, oder wenn dir am Kartenlimit oder am PIN-Timeout noch ein Detail auffällt, das ich übersehen habe, dann dürft ihr mich sehr gerne fragen.

Teil 2: Funktastatur unter Linux „hacken“: AES-Key aus dem Pairing ableiten und Tastendrücke entschlüsseln

„Im nächsten Teil wird nicht nur erklärt, sondern gemacht. Mit einem nRF52840-Dongle und Logitacker schneide ich den Kopplungsvorgang einer Unifying-Tastatur mit, leite daraus den Schlüssel ab und lese die Eingaben mit.“ Das habe ich am Ende des letzten Beitrags geschrieben. Jetzt löse ich es ein.

Kurz zur Erinnerung, falls du den ersten Teil nicht gelesen hast: Logitech-Funktastaturen mit Unifying-Empfänger verschlüsseln die Übertragung mit AES, aber die Kopplung selbst, also der kurze Moment, in dem sich Tastatur und Empfänger gegenseitig kennenlernen, hat eine nie gepatchte Design-Lücke. Wer genau in diesem Moment mithört, kann daraus den Schlüssel ableiten. Das ist CVE-2019-13052, seit 2019 öffentlich, von Logitech nie gefixt.

Logitech MX Keys mit Unifying-Empfänger und nRF52840-Dongle beim Mitschneiden einer Funk-Kopplung mit LOGITacker

Im ersten Teil stand das als Zeile in einer CVE-Datenbank und als Absatz in einer Offenlegung von Marcus Mengs. Diesmal nicht. Diesmal sitze ich mit einem Dongle für unter 30 Euro vor meiner eigenen MX Keys, schneide die Kopplung mit, und am Ende tippe ich auf der Tastatur, während eine Konsole live mitliest, was ich gerade drücke.

Vorweg die Grenze des Beitrags, damit du nicht am Ende danach suchst: den abgeleiteten AES-Schlüssel zeige ich nirgends. Warum, erkläre ich weiter unten. Die Kurzform: das war zum Zeitpunkt der Aufnahme ein aktives, funktionierendes Credential für meine echte Tastatur, kein historisches Artefakt.

Der Dongle, und welches Werkzeug ich benutze

Im ersten Teil hatte ich einen GeeekPi-Dongle über einen Amazon-Affiliate-Link verlinkt, als Empfehlung für genau dieses Vorhaben. Ehrlich gesagt bin ich am Ende nicht mit dem losgezogen. Zwischen dem Schreiben des ersten Beitrags und diesem Versuch lag noch eine Bestellung woanders, und am Ende kam das Board, das gerade greifbar war, nicht das verlinkte. In der Hand hatte ich stattdessen ein makerdiary nRF52840-MDK USB Dongle, anderer Hersteller, anderes Layout, aber derselbe Chip: ein Nordic nRF52840, auf dem Board direkt aufgedruckt mit N52840 QIAADO 2330FM. Kein Beinbruch, eher ein guter Aufhänger für den Bezugsquellen-Block weiter unten, den ich mir sonst hätte sparen können.

Verpackung des makerdiary nRF52840-MDK USB Dongle mit aufgedrucktem Board-Layout und Pinbelegung.
Die Verpackung des makerdiary nRF52840-MDK USB Dongle, mit aufgedrucktem Board und den Pin-Bezeichnungen.

Werkzeug ist LOGITacker, ursprünglich von mame82, also Marcus Mengs selbst, gebaut und heute unter RoganDawes auf GitHub aktiv gepflegt. Anders als etwa jackit läuft hier keine Angriffslogik auf dem Laptop, LOGITacker ist ein Standalone-Gerät. Die komplette Software steckt auf dem Dongle, bedient wird sie über eine serielle Konsole per USB, der Laptop selbst sieht am Ende nur ein weiteres USB-Gerät mit ein paar zusätzlichen Schnittstellen. Vier Boards werden offiziell unterstützt: der Nordic pca10059, zwei makerdiary-Boards, darunter genau das hier verwendete MDK Dongle, und ein Board von April Brother. Kein Zufallstreffer also. Das Board war von Anfang an ein sinnvoller Kauf für dieses Vorhaben, auch wenn es nicht das verlinkte war, und ich musste vor dem Kauf nicht einmal lange suchen, die Liste der unterstützten Boards steht direkt im Repository.

Geflasht habe ich logitacker_mdk_dongle.hex aus Release v0.2.3-beta, Stand 17. Januar 2020 und bis heute die aktuellste verfügbare Version. Kein Neubau aus dem Quellcode nötig, an der zugrundeliegenden Lücke hat sich seit 2020 nichts geändert. Pfostenstifte liegen dem Board bei, werden für dieses Experiment aber nicht gebraucht. Flashen und Betrieb laufen komplett über USB, kein Lötkolben in Sicht.

Das makerdiary nRF52840-MDK USB Dongle neben den beiliegenden, losen Pfostenstiftleisten. Auf dem Chip steht der Aufdruck N52840 2330FM.
Das Board selbst, mit lesbarem Chip-Aufdruck N52840 2330FM. Die Pfostenstifte liegen bei, werden für dieses Experiment aber nicht gebraucht.

Flashen, und wie ich den Bootloader falsch eingeschätzt habe

Ich bin mit der Annahme reingegangen dass so ein Nordic-Board ein serielles DFU spricht, das man mit nrfutil bedient. Falsch gedacht. Hält man beim Einstecken den Knopf auf dem Board, meldet es sich stattdessen als schnödes USB-Massenspeichergerät:

$ lsblk -o NAME,SIZE,LABEL,FSTYPE,MOUNTPOINT
sda   32,1M UF2BOOT vfat /media/kernel/UF2BOOT

Ein UF2-Bootloader, keine Nordic-eigene serielle Schnittstelle. LOGITackers Release liefert für dieses Board aber nur eine .hex-Datei, keine fertige .uf2. Also musste ich die Datei erst mit Microsofts eigenem uf2conv.py umwandeln, und zwar mit der richtigen Familien-ID, die ich mir nicht ausgedacht, sondern gegen die mitgelieferte uf2families.json geprüft habe:

$ grep -A2 NRF52840 uf2families.json
    "id": "0xada52840",
    "short_name": "NRF52840",

$ python3 uf2conv.py -f 0xADA52840 -c -o logitacker_mdk_dongle.uf2 logitacker_mdk_dongle.hex
Converted to uf2, output size: 470528, start address: 0x1000
Wrote 470528 bytes to logitacker_mdk_dongle.uf2

$ cp logitacker_mdk_dongle.uf2 /media/kernel/UF2BOOT/

Der Bootloader hat die Datei angenommen und das Laufwerk danach getrennt, das ist normales Verhalten beim Flashen. Was nicht normal lief: das Board hat sich anschließend nicht von selbst mit der neuen Firmware gemeldet. Erst ein einmaliges Aus- und wieder Einstecken hat es zurückgebracht, und dann korrekt:

$ lsusb
Bus 001 Device 021: ID 1915:520c Nordic Semiconductor ASA Logitacker by MaMe82

Vier neue Schnittstellen kamen dazu: seriell, Maus, Tastatur und HID-Rohdaten. Ein nettes Detail am Rand, die USB-Seriennummer trägt die Firmware-Version gleich mit, v0.2.3-beta.

Das nRF52840-MDK-Dongle im USB-Port eines Notebooks, mit durchgehend grün leuchtenden LEDs im Bootloader-Modus.
Im UF2-Bootloader-Modus leuchten die LEDs des Dongles durchgehend grün.

Erstkontakt, und die Adresse stand quasi auf dem Karton

Serielle Konsole auf, 115200 Baud, fertig. Der Standardmodus von Logitacker heißt discover, eine Art passiver Dauerscan, und der bringt schon etwas, bevor ich überhaupt ein Kommando eingetippt habe:

<info> LOGITACKER_PROCESSOR_DISCOVER: DISCOVERY: received valid ESB frame (addr C3:1F:D0:D1:07, len: 15, ch idx 0, raw ch 5, rssi 47)
<info> LOGITACKER_PROCESSOR_DISCOVER: discovered device is Logitech

C3:1F:D0:D1:07. Das ist keine zufällige Adresse. Die USB-Seriennummer meines Tastatur-Empfängers, die sowohl solaar show meldet als auch auf dem Gehäuse aufgedruckt steht, lautet C31FD0D1. Exakt dieselbe Bytefolge, als Präfix der Funkadresse. Ohne jeden Angriff, nur weil das Gerät eingeschaltet ist, lässt sich die Funkadresse eines Unifying-Empfängers aus der Seriennummer ablesen, die außen auf dem Gehäuse steht.

Ich habe kurz gebraucht, um das wirklich zu glauben, und den Empfänger deshalb aus dem Rechner gezogen und die Seriennummer noch einmal mit der Lupe der Handykamera abfotografiert, im Vergleich zur Konsolenausgabe daneben. Kein Tippfehler, keine Verwechslung, dieselben acht Zeichen. Für einen Angriff braucht es das ohnehin nicht, discover läuft passiv und komplett ohne dass ich irgendetwas an Tastatur oder Empfänger anfassen müsste.

Drei Versuche, die nichts brachten, und der eine, der klappte

Diagramm: zwei Kopplungsversuche über die Easy-Switch-Taste blieben ohne Funkanfrage beim Dongle, der dritte Versuch per Aus- und Wiedereinschalten der Tastatur war beim ersten Mal erfolgreich.
Zwei Versuche über die Easy-Switch-Taste brachten nichts, der dritte Versuch nach der dokumentierten Methode klappte sofort.

Um einen echten Kopplungsvorgang mitzuschneiden, muss erst einer stattfinden. Also den Mitschnitt-Modus starten und danach die schon gekoppelte MX Keys trennen, damit sie sich während laufendem Mitschnitt neu koppeln kann:

LOGITacker (discover) $ pair sniff run
<info> LOGITACKER_RADIO: Channel hopping stopped
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Sniff pairing on address BB:0A:DC:A5:75
<info> ESB_ILLEGALMOD: Using channel table 'Unifying pairing'
<info> ESB_ILLEGALMOD: New channel table with length 11
$ solaar unpair 1DA452CF
Unpaired 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

$ solaar pair C31FD0D1
Pairing: turn your new device on (timing out in 30 seconds).

Erster Versuch: Easy-Switch-Taste „1“ gehalten, laut solaar show genau der Kanal, der für diesen Rechner reserviert ist. Die LED blinkte brav weiter. Beim Dongle kam in den vollen 30 Sekunden, die solaar pair öffnet, nichts an:

<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: dongle on channel 44
... (many more channel hops, receiver beacon tracked continuously) ...
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Lost dongle in pairing mode, restart channel hopping

Zweiter Versuch, gleiche Methode, gleiches Ergebnis. solaar pair lief diesmal direkt in einen Timeout:

$ solaar pair C31FD0D1
solaar: error: Exception: pairing failed: device timeout

An der Stelle dachte ich kurz, das Board oder die Firmware hätten ein Problem, und habe testweise sogar den Kanal manuell auf den Standardwert zurückgesetzt und noch einmal von vorne begonnen. Half nichts. Meine Deutung dazu, ausdrücklich eine Deutung und kein gemessener Fakt: das Dauerblinken der Easy-Switch-LED zeigt vermutlich nur an, dass dieser Kanal aktuell keinen Empfänger kennt, keine aktive Funksuche. Analysiert habe ich das interne Verhalten der Tastatur nicht, dafür fehlt mir der Einblick, und ich will hier nichts behaupten, was ich nicht geprüft habe.

Dritter Versuch, diesmal nach der Methode, die die Logitacker-Dokumentation für Unifying-Geräte tatsächlich vorschreibt und die mit der Easy-Switch-Taste nichts zu tun hat: Tastatur ausschalten, Kopplungsfenster am Empfänger öffnen, Tastatur wieder einschalten. Erster Versuch nach dieser Methode, sofort erfolgreich:

<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: PAIR SNIFF data received on channel 5
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: PAIR SNIFF assigned C3:1F:D0:D1:08 as new sniffing address
<info> LOGITACKER_PAIRING_PARSER: Device name: MX Keys
<info> LOGITACKER_PAIRING_PARSER: Device RF address: C3:1F:D0:D1:08
<info> LOGITACKER_PAIRING_PARSER: Device serial: 1D:A4:52:CF
<info> LOGITACKER_PAIRING_PARSER: Device WPID: 0x408A
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: device automatically stored to flash
<info> LOGITACKER_PROCESSOR_PAIR_SNIFF: Sniffed full pairing, moving on with passive enumeration for C3:1F:D0:D1:08

solaar show bestätigt unabhängig dieselbe Seriennummer 1DA452CF und denselben WPID 408A für dieselbe Tastatur, wobei die neu ersniffte Funkadresse C3:1F:D0:D1:08 denselben Präfix trägt wie die passive Sichtung von vorhin, nur eine Adresse höher.

Was ich da wirklich in der Hand hatte, und was ich bewusst nicht zeige

Ein erfolgreich mitgeschnittener Kopplungsvorgang liefert bei Logitacker fünf Dinge: Gerätename, Funkadresse, Seriennummer, WPID, und einen 16 Byte langen AES-Schlüssel. Die ersten vier stehen oben, alle unabhängig gegen Solaar geprüft. Der Schlüssel steht hier nicht. Kein einziges Byte davon, auch keine erfundenen Beispielwerte, die nur wie ein Schlüssel aussehen würden.

Der Unterschied zur EK-Zertifikat-Entscheidung im TPM-Beitrag lässt sich in einem Satz sagen: dort ging es um eine dauerhafte Geräte-Kennung, eindeutig, aber für sich genommen nicht ausnutzbar. Hier ging es um ein Credential, das im Moment der Aufnahme aktiv gültig war und echten Zugriff auf meine echte, gerade auf meinem Schreibtisch liegende Tastatur bedeutet hätte. Direkt im Anschluss an den Mitschnitt aus dem nächsten Abschnitt habe ich die Kopplung deshalb noch einmal neu gemacht, um den mitgeschnittenen Schlüssel ungültig zu machen:

$ solaar unpair 1DA452CF
Unpaired 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

$ solaar pair C31FD0D1
Pairing: turn your new device on (timing out in 30 seconds).
Paired device 1: MX Keys Keyboard (MX Keys) [408A:1DA452CF]

Wieder die Aus-und-Einschalt-Methode, wieder auf Anhieb erfolgreich. Der Schlüssel aus dem Mitschnitt ist damit nicht mehr der Schlüssel, der heute im Einsatz ist. Falls du dich fragst, ob das den Beweis im nächsten Abschnitt irgendwie entwertet: nein, der wurde vorher aufgezeichnet, die Rotation kam erst danach.

Der Beweis: Tastenanschläge in Echtzeit entschlüsselt

Diagramm: vom Pairing-Handshake über den mitgeschnittenen und abgeleiteten AES-Schlüssel bis zur Live-Entschlüsselung der Tastenanschläge, wobei der Schlüssel selbst nicht gezeigt wird.
Der Ablauf vom mitgeschnittenen Handshake bis zum entschlüsselten Klartext. Der Schlüssel selbst taucht in diesem Beitrag nirgends auf.

Nach dem erfolgreichen Mitschnitt wechselt Logitacker automatisch in einen passiven Mithör-Modus. Ich habe angefangen, auf der frisch wieder gekoppelten MX Keys zu tippen, und die Konsole hat live mitgeschrieben. Vier Tastendrücke aus derselben Sitzung, jeder mit eigenem Rohframe und eigenem entschlüsseltem Ergebnis:

ZählerVerschlüsselter RohframeEntschlüsselter Klartext-ReportErkannte Taste
4693281200 D3 45 B3 46 90 36 E6 B0 93 46 93 28 12 00 00 00 00 00 00 00 ED00 13 00 00 00 00 00 C9P
4693281400 D3 82 5E 0C 2F 89 D7 B2 86 46 93 28 14 00 00 00 00 00 00 00 6500 17 00 00 00 00 00 C9T
4693281600 D3 C7 B6 18 D3 34 82 1E 3D 46 93 28 16 00 00 00 00 00 00 00 9D00 37 00 00 00 00 00 C9.
4693281800 D3 68 E2 F1 7D 3C 6B D6 3D 46 93 28 18 00 00 00 00 00 00 00 A200 28 00 00 00 00 00 C9ENTER

Nacheinander gelesen: P, T, ., ENTER. Kein vollständiges Wort, sondern das Ende eines Satzes, den ich in dem Moment tatsächlich getippt habe, plus Enter. Genau das steht hier auch so, nicht schöngeredet zu mehr, als es ist.

Die Rohframes in der Tabelle sind Chiffretext, unbedenklich zum Zeigen, reines Rauschen ohne den Schlüssel. Was daraus die Klartext-Reports macht, ist der Schlüssel aus dem vorigen Abschnitt, und der taucht hier nicht auf. Ich habe dabei ziemlich bewusst langsam getippt, fast schon ein bisschen albern vor dem eigenen Bildschirm, nur um in der Konsole klar zuordnen zu können, welche Zeile zu welchem Tastendruck gehört. Es hat trotzdem gedauert, bis ich wirklich geglaubt habe, was da steht.

Was das nicht war

Eine kurze, ehrliche Einordnung. Dieser Versuch endet beim Mitlesen. Ich habe keine Einschleusung versucht, obwohl derselbe bekannte Schlüssel das technisch auch für gefälschte, eingeschleuste Tastenanschläge ermöglicht hätte, also für aktiven Angriff statt reinem Zuhören. Das ist die andere Hälfte von CVE-2019-13052, und die habe ich bewusst nicht angefasst. Nicht aus technischer Unfähigkeit, Logitacker kann das nachweislich, sondern weil eine aktive Einschleusung gegen die eigene Tastatur eine andere Kategorie Experiment ist als reines Zuhören, und weil das für den Beleg der Lücke in diesem Beitrag nicht nötig war.

Was das für die Empfehlung aus Beitrag 1 bedeutet

Im ersten Teil stand am Ende eine Rangfolge: Kabel für wirklich Geheimes, Logi Bolt oder sauberes Bluetooth fürs Büro, vorhandenes Unifying behalten und pflegen, No-Name-Funktastaturen ersetzen. An dieser Rangfolge ändert dieser Beitrag nichts. Was sich ändert, ist der Status des Arguments dahinter. Die Kopplungs-Lücke ist jetzt kein Datenbankeintrag mehr, sondern ein Ablauf, den ich mit handelsüblicher Hardware für unter 30 Euro selbst gegen mein eigenes Gerät durchgeführt habe, an einem gewöhnlichen Nachmittag, ohne Speziallabor und ohne Vorwissen, das ich mir nicht selbst aus der Dokumentation hätte holen können. Das praktische Risiko bleibt so klein wie im ersten Teil beschrieben, gebunden an den kurzen, seltenen Moment der Kopplung. Aber „das ist doch nur graue Theorie“ trägt als Einwand jetzt nicht mehr, und genau das war für mich der eigentliche Grund, diesen zweiten Teil überhaupt zu schreiben.

Was offen bleibt

  • Warum die Easy-Switch-Taste keine Kopplungsanfrage auslöst, bleibt meine Deutung des Dauerblinkens, keine bestätigte Analyse des Tastatur-internen Verhaltens. Sicher war ich einfach nur zu dumm die Tasten in der richtigen Reihenfolge zu drücken. gefettfingert
  • Die aktive Einschleusungshälfte von CVE-2019-13052 habe ich mit dem bekannten Schlüssel nicht ausprobiert. Ob und wann daraus noch ein dritter Teil wird, ist offen. Das hängt nun also an euch. Wenn ich sehen wollte, wie ich einfach Text oder Kommandos einschleuse, sagt es mir 🙂
  • Ob sich derselbe Ablauf gegen andere Unifying-Tastaturen als meine MX Keys genauso zuverlässig reproduzieren lässt, habe ich nicht geprüft. Aber hey, wir sind uns nun wohl alle sicher, dass es klappen wird, oder?
  • Die genaue Ursache der beiden ersten Kopplungsversuche, ob Timing, Tastatur-Firmware-Logik oder etwas Drittes, habe ich nicht bis auf die Funkebene nachverfolgt. gefettfingert

Bezugsquellen

Der folgende Link ist ein Affiliate-Link (Werbung). Kaufst du darüber, bekomme ich eine kleine Provision, für dich ändert sich am Preis nichts.

Der Dongle in diesem Beitrag war tatsächlich ein makerdiary nRF52840-MDK USB Dongle, über einen anderen Kanal bezogen. Der verlinkte Dongle trägt denselben Nordic-nRF52840-Chip, ist aber ein anderes Board von einem anderen Hersteller. Ob Logitacker dieses konkrete Modell offiziell unterstützt, habe ich nicht geprüft. Offiziell gelistet sind der Nordic pca10059, zwei makerdiary-Boards und ein Dongle von April Brother.

Siehe auch

Wenn ich irgendwo danebenliege, korrigiert mich gerne, dann lerne ich selbst etwas. Und die Frage an euch zum Schluss: Würdet ihr für ein bisschen mehr Sicherheit auf Logi Bolt oder Kabel umsteigen, oder ist euch das Restrisiko bei einer gepflegten Unifying-Tastatur egal? Wenn ihr mögt, dürft ihr mich dazu sehr gerne fragen.

sipgate unter Linux: ein Softphone, das Kontakte und Kalender aus der eigenen Nextcloud kennt

Vor zwölf Jahren habe ich hier beschrieben, wie der Familienkalender meiner Frau in die ownCloud gewandert ist. Vorher gab es ein Büchlein in ihrer Tasche und einen großen Kalender an der Wand, und beide waren selten einer Meinung. Seitdem liegen Termine und Kontakte unter eigener Kontrolle, und alles Mögliche greift darauf zu. Nur ein Gerät hat es nie getan: das Telefon am Schreibtisch.

Zwei Karten nebeneinander: links ein Anruf als nackte Rufnummer ohne Adressbuch, rechts derselbe Anruf mit aufgelöstem Namen aus der eigenen Nextcloud.

Das hat mich lange nicht gestört, bis ich es einmal ausprobiert habe. Ein Anruf kommt rein, auf dem Bildschirm steht eine nackte Rufnummer, und ich sitze vor einem Rechner, auf dem 144 Kontakte mit genau dieser Nummer liegen. Das ist albern.

Mein sipgate-Konto ist alt. Wie alt genau, wusste ich selbst nicht mehr. In diesem Blog taucht sipgate erstmals im August 2009 auf, und 2015 habe ich beim Abschalten meiner alten 01801-Nummer geschrieben, ich hätte sie „vor ? 13 ? Jahren“ bekommen. Die Fragezeichen waren schon damals meine. Also habe ich einfach den Support gefragt, ausdrücklich nur aus Neugier. Die Antwort kam am nächsten Morgen: angemeldet am 30. April 2005. Da sipgate 2004 mit den Basis-Accounts gestartet ist, bin ich also aus dem ersten Jahr dabei, gut zwanzig Jahre. Dass sich jemand die Mühe macht, so eine reine Neugier-Frage überhaupt zu beantworten, finde ich bemerkenswert.

Was ich wollte, war jedenfalls nichts Exotisches: ein Softphone unter Linux, das an diesem Konto hängt und beim Klingeln in mein eigenes Adressbuch schaut statt in gar keines. Dazu am besten noch die Kalender, damit ich beim Telefonieren sehe, ob der Termin, über den gerade gesprochen wird, überhaupt frei ist.

Der Weg dahin war überraschend kurz, aber die naheliegenden Kandidaten führen alle in die Irre. Deshalb schreibe ich beides auf.

Warum der naheliegende Weg nicht funktioniert

Der erste Reflex ist, das Softphone des Anbieters zu nehmen. Bei sipgate steht dazu im eigenen Hilfecenter, dass es unter Linux nicht installiert, sondern nur ausgeführt wird, dass es kein 64 Bit kann, also weder auf x86_64 noch auf ARM läuft, und dass der Support in Kürze eingestellt wird und keine Updates mehr kommen. Das ist eine erfrischend ehrliche Ansage, hilft aber nicht weiter.

Der zweite Reflex heißt Linphone, und das ist die Falle, in der ich am längsten gesteckt habe. Linphone kann CardDAV wirklich, die Funktionen stecken vollständig in der Bibliothek liblinphone. Nur hat die Oberfläche der paketierten Version keinen einzigen Schalter dafür, dort gibt es bei den Adressbuchquellen ausschließlich LDAP. In die Oberfläche kam CardDAV erst mit Version 6.0, und die liegt in keinem einzigen Linux-Distributionsrepo. Ein Blick auf Repology zeigt für linphone-desktop gerade einmal AUR bei 6.1.2, alles andere hängt bei 4.x oder 5.x, Debian unstable bei 5.2.6. Der Hersteller selbst liefert für Linux ausschließlich AppImages aus, ohne veröffentlichte Prüfsumme und ohne eingebettete Update-Information. Wer sich nicht um Aktualisierungen kümmern will, ist damit an der falschen Adresse.

Blink fällt aus, weil es zwar ein ausgereifter SIP-Client ist, beim Adressbuch aber nur Google Contacts kennt. GNOME Calls wäre technisch ein Weg, ist aber für Telefone gebaut und als Desktop-Softphone dünn.

Nebenbei: die Frage wird gestellt. Im Nextcloud-Forum steht ein Thread vom August 2025 mit genau diesem Wunsch, und der Fragesteller verweist darin auf einen älteren Thread zum selben Thema, der ohne Ergebnis blieb. Die Antworten waren eine kommerzielle Lösung und ein Umweg über die FRITZ!Box. Was ich jetzt benutze, kam in keinem der beiden vor.

Die Kette, die tatsächlich funktioniert

Der Trick besteht darin, dass das Softphone gar nicht mit der Nextcloud sprechen muss. Unter Linux gibt es dafür längst eine Zwischenschicht, und die heißt evolution-data-server. Sie ist auf so gut wie jedem Desktop installiert, weil Kalender- und Kontaktanwendungen darauf aufsetzen. Und sie spricht CalDAV und CardDAV von Haus aus.

Nextcloud
  |
  +-- GNOME Online Accounts     Provider owncloud, CalDAV und CardDAV
       |
       +-- evolution-data-server
            |
            +-- Softphone (GOnnect, Adressbuchquelle "EDS")

Das Softphone ist damit austauschbar, und die Zugangsdaten der Cloud liegen genau an einer Stelle statt in jeder Anwendung noch einmal. Als Softphone nehme ich GOnnect von der GONICUS GmbH, einem Open-Source-UC-Client, den es als Flatpak auf Flathub gibt. Er kann als Adressbuchquellen LDAP, CardDAV, CSV und eben evolution-data-server.

Dass er über Flathub kommt, war für mich das Ausschlusskriterium gegen alles andere. Ein AppImage müsste ich von Hand nachziehen, ein Flatpak läuft mit flatpak update einfach mit.

Schritt 1: Die Nextcloud in die Online-Konten hängen

Unter Linux Mint findest du das unter „Online-Konten“, dahinter steckt gnome-online-accounts-gtk. Dort legst du ein Konto vom Typ Nextcloud an, trägst die Adresse deiner Instanz, deinen Benutzernamen und ein App-Passwort ein und hakst Kalender und Kontakte an.

Der Dialog Online-Konten unter Linux Mint mit einem eingerichteten Nextcloud-Konto, bei dem Kalender, Kontakte und Dateien eingeschaltet sind.
Ein Konto vom Typ Nextcloud in den Online-Konten, Kalender und Kontakte angehakt. Ab hier kennt der ganze Rechner die Daten, nicht nur ein Programm.

Nimm dafür wirklich ein App-Passwort aus den Sicherheitseinstellungen deiner Nextcloud und nicht dein normales. Falls du später den Zugriff eines einzelnen Rechners zurückziehen willst, geht das damit mit einem Klick, ohne dass du überall sonst neue Zugangsdaten eintragen musst.

Wenn es geklappt hat, sieht die Konfiguration hinterher so aus:

[Account account_1735125820_0]
Provider=owncloud
Uri=https://cloud.example.org/remote.php/webdav
CalendarEnabled=true
CalDavUri=https://cloud.example.org/remote.php/dav
ContactsEnabled=true
CardDavUri=https://cloud.example.org/remote.php/dav
FilesEnabled=true
AcceptSslErrors=false

Auf AcceptSslErrors=false lohnt ein Blick. Steht dort true, akzeptiert die Verbindung kaputte Zertifikate, und dann kannst du dir den ganzen Rest sparen.

Ab hier haben alle Programme auf dem Rechner Zugriff, die evolution-data-server nutzen. Das Softphone ist nur eines davon.

Schritt 2: GOnnect installieren

flatpak install flathub de.gonicus.gonnect

Das war der ganze Schritt. Rund 310 MB, und beim ersten Start liest GOnnect die Kontakte und Kalender aus evolution-data-server ein, ohne dass du irgendwo CardDAV konfigurieren müsstest. Bei mir standen im Protokoll direkt beim ersten Start:

gonnect.app.addressbook: Found 1 active configurations for address book plugin "EDS"
gonnect.app.feeder.EDSAddressBookFeeder: Loaded 144 contact(s) of source "Kontakte"

Ja, und die Kalender ebenfalls, inklusive der Familienkalender aus dem Beitrag von 2014. Die stehen dann rechts im Fenster, unter den Favoriten und neben der Anrufliste.

Das Hauptfenster von GOnnect mit der Anrufliste auf der linken Seite, den Favoriten rechts oben und den Terminen aus der Nextcloud rechts unten.
Links die Anrufliste, rechts die Favoriten und darunter die Termine aus dem Familienkalender. Namen, Nummern und Kontaktbilder sind unkenntlich gemacht.

Und die Suche oben im Fenster greift auf dasselbe Adressbuch zu. Ein paar Buchstaben genügen, dann steht der Treffer da, und darüber die Quelle, aus der er kommt: eds-contacts. Genau darum ging es. Kein Zwischenschritt, kein Export, keine zweite Kontaktverwaltung im Softphone.

Die Kontaktsuche in GOnnect zeigt einen gefundenen Kontakt, darüber steht die Quelle eds-contacts.
Ein paar Buchstaben genügen. Über dem Treffer steht die Quelle: eds-contacts, also das Adressbuch aus der eigenen Nextcloud.

Schritt 3: Das sipgate-Konto eintragen

Hier kommt die Eigenheit von GOnnect, an der man sich einmal stoßen muss: es gibt keinen Einrichtungsassistenten und keinen Einstellungsdialog für das SIP-Konto. Der Client ist dafür gebaut, in Firmen ausgerollt zu werden, und erwartet deshalb eine fertige Konfigurationsdatei. Die liegt hier:

~/.var/app/de.gonicus.gonnect/config/gonnect/99-user.conf

Unter der Haube arbeitet PJSIP, entsprechend sehen die Schlüssel aus. Das hier ist die vollständige Konfiguration, die bei mir mit einem sipgate-Basis-Konto läuft:

[account0]
userUri=sip:1234567e0@sipgate.de
registrarUri=sip:sipgate.de
proxies=sip:sip.sipgate.de:5061;transport=tls
auth=auth0
transport=tls
srtpUse=mandatory
srtpSecureSignaling=1
verifyServer=true
caListFile=/etc/ssl/certs/ca-certificates.crt
contactRewriteMethod=always-update

[auth0]
scheme=Digest
username=1234567e0
realm=sipgate.de
type=digest
data=HIER_DER_MD5_HASH

1234567e0 ist deine SIP-ID aus dem sipgate-Konto, nicht deine Rufnummer.

Wichtig ist die Zeile type=digest. Du kannst dort auch dein Passwort im Klartext hinterlegen, aber das musst du nicht. SIP authentifiziert sich per Digest, und der Hash dafür ist schlicht MD5(Benutzer:Realm:Passwort). Den rechnest du dir selbst aus:

printf '%s' '1234567e0:sipgate.de:DEIN_SIP_PASSWORT' | md5sum

Das Ergebnis kommt hinter data=. Danach setzt du die Datei noch auf chmod 600.

Damit steht dein Passwort nicht mehr wörtlich in einer Konfigurationsdatei. Ehrlich bleiben muss man trotzdem: der Hash ist für diesen Realm genauso viel wert wie das Passwort selbst, wer ihn hat, kann sich anmelden. Der Gewinn ist ein anderer. Falls du dieses Passwort irgendwo sonst auch verwendest, liegt es hier nicht lesbar herum.

Ein Detail, das mich beim Ändern der Datei erwischt hat: GOnnect muss dabei beendet sein. Der Client schreibt die Datei beim Beenden aus dem Speicher zurück und überschreibt deine Änderungen sonst kommentarlos.

Was der Desktop davon merkt

Zwei Dinge sind mir erst im Betrieb aufgefallen, und beide gehören zu der Sorte, die man nicht vermisst, solange man sie nicht kennt.

Wenn ein Anruf reinkommt oder du selbst einen startest, pausiert die laufende Medienwiedergabe von selbst. Das YouTube-Video im Firefox hält an, der Musikplayer ebenso, und nach dem Auflegen läuft beides weiter. Dahinter steckt MPRIS, die Schnittstelle, über die sich Medienplayer unter Linux fernsteuern lassen. Für mich war das der Moment, in dem sich das Ding nicht mehr nach Fremdkörper angefühlt hat, sondern nach Teil des Desktops.

Das zweite: sip:-Links auf Webseiten funktionieren. Ein Klick darauf öffnet die Anwendung und wählt sofort. Der Client trägt sich beim Installieren als Handler für dieses Schema ein, du musst dafür nichts konfigurieren.

Verschlüsselung, und warum eine Zeile davon die wichtigste ist

sipgate kann das seit über zwanzig Jahren. Heise hat am 1. Februar 2006 über „sipgate-Crypto“ berichtet, damals schon mit TLS für die Signalisierung und SRTP für die Sprache bis zum Festnetz-Gateway. Das Problem war seinerzeit, dass kaum ein Endgerät mitspielte, weshalb sipgate passende Hardware gleich mit anbot. Zwanzig Jahre später kann es jedes Gerät, und trotzdem steht es in kaum einer Anleitung.

Eingeschaltet ist es nämlich nicht von allein. Die Standardkonfiguration läuft über UDP und unverschlüsselt, du musst zwei Dinge selbst ändern: sip.sipgate.de als Proxy eintragen und die Signalisierung von UDP auf TLS umstellen.

Interessant ist, was danach passiert. In der sipgate-Dokumentation steht dieser Satz:

Bei verschlüsselter Signalisierung erfordern wir ebenfalls, dass die Sprachdaten verschlüsselt werden. Ist diese Einstellung falsch, so werden Anrufe mit Fehler „488 No Acceptable here“ abgewiesen.

Sobald die Signalisierung verschlüsselt ist, erzwingt sipgate also auch die Sprachverschlüsselung. Das ist die angenehmste Sorte Sicherheitsentscheidung, weil sie einem die Wahl abnimmt. srtpUse=mandatory ist damit nicht meine Strenge, sondern schlicht das, was die Gegenseite ohnehin verlangt. Ein optional würde dir nichts retten, der Anruf käme trotzdem nicht zustande, nur mit einer verwirrenderen Fehlermeldung.

Kleine Randbemerkung, weil es mich gefreut hat: gefunden habe ich diesen Satz nicht durch Klicken im Hilfecenter, sondern hier:

curl https://help.sipgate.de/llms-full.txt

Das ist das komplette Hilfecenter als eine Textdatei, rund 350 KB, gedacht als maschinenlesbare Fassung für Sprachmodelle. Zum Durchsuchen mit grep ist das erheblich angenehmer als jede Suchmaske, und es beantwortet Fragen, die im Menü drei Ebenen tief liegen. Ich habe so etwas seit einer Weile selbst auf diesem Blog liegen, und es ist schön zu sehen, dass es sich langsam herumspricht.

Die eigentlich wichtige Zeile ist eine andere, und die steht in keinem Beispiel, das ich gefunden habe:

verifyServer=true
caListFile=/etc/ssl/certs/ca-certificates.crt

Die Voreinstellung von PJSIP ist verifyServer=false. Übersetzt heißt das: die Verbindung wird verschlüsselt, aber es wird nicht geprüft, mit wem. Jedes beliebige Zertifikat wird angenommen. Gegen jemanden, der auf dem Weg sitzt und die Verbindung übernimmt, ist so eine Verschlüsselung wertlos, weil er einfach sein eigenes Zertifikat vorzeigt. Erst mit diesen zwei Zeilen wird geprüft, und zwar gegen den Zertifikatsspeicher deines Systems.

Nachmessen statt glauben

Dass die Registrierung mit code=200 klappt, sagt über die Verschlüsselung nichts. Also nachsehen. Der einfachste Test zeigt, wohin die Verbindung überhaupt geht:

ss -tnp | grep 5061
ESTAB  [2001:db8::23]:49705 -> [2001:ab7::18]:5061  users:(("gonnect",pid=48721))
ESTAB  [2001:db8::23]:38171 -> [2001:ab7::1a]:5061  users:(("gonnect",pid=48721))

Port 5061, und auf Port 5060 liegt nichts mehr, es läuft also kein Klartext-SIP nebenher. Meine eigene Adresse habe ich ersetzt, die Gegenstellen sind echt. Ein whois auf 2001:ab7::/36 nennt die netzquadrat GmbH, und sip.sipgate.de löst genau auf diese Adressen auf. Das Zertifikat dazu:

openssl s_client -connect sip.sipgate.de:5061 -servername sip.sipgate.de </dev/null | openssl x509 -noout -subject -issuer
subject=CN = sip.sipgate.de
issuer=C = US, O = DigiCert Inc, CN = GeoTrust TLS RSA CA G1

Wenn du schon dabei bist, kannst du auch gleich nachsehen, was die Gegenstelle überhaupt anbietet. Das geht wie bei jedem anderen TLS-Dienst:

nmap -Pn -p 5061 --script ssl-enum-ciphers sip.sipgate.de
TLSv1.2:
  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
  TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
  ...
TLSv1.3:
  TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
  TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
  TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
cipher preference: server
least strength: A

Das Ergebnis ist erfreulich unaufgeregt. Es gibt nur TLS 1.2 und 1.3, die alten Versionen habe ich gegengeprüft und beide werden abgelehnt. Alles bewertet mit A, der Server bestimmt die Reihenfolge, und das Zertifikat ist RSA mit 4096 Bit. Ausgehandelt wird in der Praxis TLS 1.3 mit TLS_AES_256_GCM_SHA384 über X25519.

Ein Schönheitsfehler bleibt: unter TLS 1.2 stehen auch noch reine TLS_RSA_WITH_*-Suiten in der Liste, also solche ohne Forward Secrecy. Weil der Server die Reihenfolge vorgibt und ECDHE oben steht, bekommt sie in der Praxis niemand, sie sind der Rückfall für Uraltgeräte. Schöner wäre es trotzdem ohne.

Bleibt die Sprache. Während des Gesprächs zeigt GOnnect oben links ein grünes Schild, das ist die Verschlüsselungsanzeige.

Ein laufendes Gespräch in GOnnect, oben links ein grünes Schild als Anzeige für die Verschlüsselung.
Das grüne Schild oben links ist die Verschlüsselungsanzeige. Was dahinter wirklich passiert, steht erst im Protokoll.

Ein Symbol ist aber nur ein Symbol. Was dahinter passiert, protokolliert der Client erst, wenn du ihn darum bittest:

[logging]
level=6

Danach einmal telefonieren und ins Protokoll schauen. Was du sehen willst, sind zwei Dinge. Erstens die Aushandlung, in der beide Seiten RTP/SAVP sprechen statt RTP/AVP:

m=audio 4000 RTP/SAVP 96 97 98 99 3 0 8 9 100 102 103 101 104 105 106
a=crypto:1 AES_256_CM_HMAC_SHA1_80 inline:S3ybiZ92TCP4JuPe48V...
a=crypto:3 AES_CM_128_HMAC_SHA1_80 inline:r2RXvwQ31ewSR0xEvuZX...

Und zweitens die Bestätigung, welche der angebotenen Suiten die Gegenstelle genommen hat:

srtp0x...  SRTP started, keying=SDES, crypto=AES_256_CM_HMAC_SHA1_80
           SRTP status: Active  Crypto-suite: AES_256_CM_HMAC_SHA1_80

sipgate hat also die stärkste der vier angebotenen Varianten gewählt. Erst damit weiß ich, dass srtpUse=mandatory wirklich gegriffen hat und nicht still auf „aus“ stand. Nebenbei: mehr als diese vier hat der Client gar nicht im Angebot, alle mit AES im Counter-Modus und HMAC-SHA1. AES-GCM kennt diese PJSIP-Variante nicht, an der Stelle ist also nichts zu optimieren.

Und dann dreh den Loglevel wieder runter. In den Zeilen oben stehen die SRTP-Schlüssel im Klartext, die haben in einer Protokolldatei nichts verloren.

Was dabei nicht verschlüsselt ist

Hier muss ich die gute Laune etwas bremsen, denn „alles verschlüsselt“ wäre falsch.

Verschlüsselt ist die Strecke zwischen deinem Rechner und sipgate. Das ist viel wert, es schützt gegen dein WLAN, gegen fremde Netze und gegen deinen Provider, und zwar sowohl den Gesprächsinhalt als auch die Information, wen du wann anrufst.

Es endet aber bei sipgate. Rufst du ein Mobiltelefon an, läuft der Anruf ab dort durch das normale Telefonnetz, und dort gibt es keine Verschlüsselung. Dazu kommt, dass SDES als Schlüsselaustausch bedeutet, dass die Schlüssel im Signalisierungsweg stehen, den sipgate liest. Der ist zwar durch TLS gegen Dritte geschützt, aber sipgate kennt deine Schlüssel und könnte mithören. Etwas anderes steht auch gar nicht zur Wahl: ZRTP und DTLS-SRTP kommen in der gesamten sipgate-Dokumentation kein einziges Mal vor.

Echte Ende-zu-Ende-Verschlüsselung bekommst du nur zwischen zwei Clients, die direkt miteinander eines von beidem sprechen. Mit einem Anbieter dazwischen ist das, was hier läuft, die Obergrenze. Das ist kein Grund, es zu lassen, man sollte es nur nicht mit etwas verwechseln, das es nicht ist.

Kleinkram, der auffällt

Zwei Meldungen im Protokoll haben mich kurz beunruhigt und sind harmlos. Failed to connect to pipewire instance erscheint, weil das Flatpak den PulseAudio-Socket hat und auf pipewire-pulse zurückfällt. Die Geräte werden danach sauber gesetzt, der Ton funktioniert. Die Fehler des JitsiConnector betreffen die Videokonferenz-Anbindung und nicht das Telefonieren.

Was tatsächlich nicht geht: globale Tastenkürzel. Cinnamon bringt das Portal org.freedesktop.portal.GlobalShortcuts nicht mit, Anrufe per Tastendruck annehmen fällt damit aus. Alles andere, auch das Symbol im Systemabschnitt der Kontrollleiste, funktioniert.

Fazit

Der Teil, den ich für den schwierigen hielt, war der einfachste. Ich musste am Softphone überhaupt kein CardDAV einrichten, weil evolution-data-server das längst erledigt hatte. Ein Konto in den Online-Konten, und die Kontakte und Kalender stehen jedem Programm auf dem Rechner zur Verfügung.

Aufwendig war stattdessen, überhaupt einen Client zu finden, der unter Linux gepflegt wird und den man ohne AppImage-Gefummel aktuell hält. Dass ausgerechnet ein Client, der eigentlich für den Rollout in Firmen gedacht ist und deshalb keinen Einrichtungsdialog hat, für einen einzelnen Basis-Account die beste Wahl ist, hätte ich vorher nicht erwartet.

Und der Familienkalender, der 2014 in die ownCloud gewandert ist, steht jetzt neben dem Telefon. Nur zwölf Jahre später.

Siehe auch:

Falls ihr das nachbaut und irgendwo hängen bleibt, oder falls ihr einen besseren Weg kennt, dann dürft ihr mich sehr gerne fragen.

BIOS erklärt, Teil 3: acht Kerne, sechzehn Threads und fünf Prefetcher

Ein Detail, das mich an dieser Maschine bis heute amüsiert: die Kiste, die hier steht, hat weniger Kerne als die, die sie ersetzt hat. Vorher waren es zwei Xeon E5-2687W v3, zusammen 20 Kerne und 40 Threads. Jetzt ist es ein einzelner Xeon Gold 5315Y mit 8 Kernen und 16 Threads. Auf dem Papier ein deutlicher Rückschritt, in der Praxis für fast alles was ich mache ein Fortschritt, weil jeder einzelne Kern erheblich mehr leistet und weil PCIe 4.0 und 256 GB Speicher am Ende mehr wert sind als zwölf zusätzliche langsame Kerne.

Womit wir bei der Seite wären, um die es heute geht. Das hier ist die dritte Folge meiner Reise durch das BIOS dieses Supermicro-Boards, alle Teile stehen gesammelt unter BIOS & Firmware. Dran ist Advanced > CPU Configuration, und zwar die obere Hälfte. Die untere mit AES-NI, SGX und den Sicherheitsfunktionen bekommt die nächste Folge, sonst wird es zu lang.

Der Teil zum Anschauen

Aptio Setup, Processor Configuration, mit Prozessorsignatur, Takt, Mikrocode-Stand und den Cache-Grössen

Oben auf der Seite steht ein Block, an dem man nichts einstellen kann. Trotzdem lohnt sich der Blick, weil dort Dinge stehen, die man sonst zusammensuchen müsste:

Processor BSP Revision          606A6 - ICX M1
Processor ID                    000606A6
Processor Frequency             3.200GHz
Processor Max Ratio             20H
Processor Min Ratio             08H
Microcode Revision              0D000421
L1 Cache RAM(Per Core)          80KB
L2 Cache RAM(Per Core)          1280KB
L3 Cache RAM(Per Package)       12288KB

606A6 ist die Prozessorsignatur, aufgedröselt: Familie 6, Modell 6A, Stepping 6. ICX M1 ist Intels interne Bezeichnung dafür, ICX steht für Ice Lake. Die beiden Ratios sind Multiplikatoren in hexadezimal, 08H sind 8 und 20H sind 32. Mit dem üblichen Referenztakt von 100 MHz ergibt das 800 MHz im Leerlauf und 3,2 GHz als Basistakt. Genau diese Spanne meldet auch Linux:

lscpu | grep MHz
#   CPU max MHz:  3600,0000
#   CPU min MHz:   800,0000

Die 3600 oben drüber sind der Turbo, der geht über den Basisratio hinaus. Dazu mehr, sobald ich das Power-Management-Menü nachgereicht bekomme, das fehlt mir in meiner Aufzeichnung noch.

Am interessantesten finde ich die Cache-Zeilen. 1280 KB L2 pro Kern klingt viel, und das ist es auch. Bei Ice Lake hat Intel den L2 gegenüber der Vorgängergeneration deutlich aufgebohrt und im Gegenzug den gemeinsamen L3 kleiner gemacht. 12 MB L3 für acht Kerne sind für einen Xeon nicht üppig. Der Gedanke dahinter ist, dass Daten näher am Kern liegen und seltener über den Ring wandern müssen. Ob das für die eigene Last aufgeht, merkt man erst beim Messen, aber die Zahlen im Setup erzählen die Designentscheidung ziemlich direkt.

Das Microcode Revision 0D000421 ist übrigens der Wert, den auch Linux meldet:

grep -m1 microcode /proc/cpuinfo
#   microcode : 0xd000421

Das BIOS bringt also bereits den Mikrocode mit, den auch das Betriebssystem laden würde. Wer sich fragt, ob ein BIOS-Update wegen Mikrocode nötig wäre: hier eindeutig nein.

Kerne abschalten, in hexadezimal

Aptio Setup, CPU1 Core Disable Bitmap, mit dem Hinweis dass mindestens ein Kern aktiv bleiben muss
> CPU1 Core Disable Bitmap

Ein eigenes Untermenü mit genau einem Eintrag, und dieser Eintrag ist herrlich unhöflich. Das Handbuch beschreibt ihn so:

Select 0 to enable all cores or FFFFFFFFFFF to disable all cores. One core must be enabled.

Man gibt also eine Bitmaske in hexadezimal ein, in der jedes gesetzte Bit einen Kern abschaltet. Keine Auswahlliste, keine Zahl „wie viele Kerne hätten Sie gern“, eine Bitmaske. Und dann der wunderbare Nachsatz, dass mindestens ein Kern übrig bleiben muss, was man vermutlich nicht schreiben würde, wenn es nicht mal jemand ausprobiert hätte.

Wozu macht man so etwas? Der häufigste Grund ist Lizenzkosten. Es gibt genug Software, die pro Kern abgerechnet wird, und wenn eine Maschine dafür zu viele hat, schaltet man welche ab. Der zweite Grund ist Testen: Verhalten einer Anwendung auf weniger Kernen prüfen, ohne die Hardware zu tauschen. Bei mir steht das erwartungsgemäss auf 0, alle 16 logischen Prozessoren sind da:

cat /sys/devices/system/cpu/online     # 0-15
cat /sys/devices/system/cpu/offline    # leer

Wichtig zu verstehen ist der Unterschied zum Abschalten unter Linux. Wenn ich dort einen Kern offline nehme, existiert er weiter, er bekommt nur keine Arbeit mehr. Die Bitmaske im BIOS blendet ihn aus, bevor das Betriebssystem überhaupt startet. Für eine Lizenzzählung, die sich ansieht was die Maschine meldet, ist das ein Unterschied.

Hyper-Threading

Hyper-Threading [ALL]           [Enable]

Der bekannteste Schalter der Seite. Aus acht physischen Kernen werden sechzehn logische, weil jeder Kern zwei Befehlsströme gleichzeitig verwaltet und die Recheneinheiten teilt, die gerade nichts zu tun haben. Der Gewinn liegt je nach Last irgendwo zwischen nichts und dreissig Prozent.

Es gibt drei gute Gründe, das abzuschalten, und keiner davon trifft auf mich zu. Der erste sind Seitenkanalangriffe: mehrere der Spectre-Nachfolger nutzen aus, dass sich zwei Threads einen Kern teilen, und wer fremden Code auf seiner Maschine ausführen lässt, fährt ohne Hyper-Threading sicherer. Der zweite ist Latenz: bei harten Echtzeitanforderungen ist ein Kern, der sich mit niemandem abstimmen muss, berechenbarer. Der dritte sind wieder Lizenzen.

Für eine Workstation, auf der ich weiss was läuft, überwiegt der Durchsatz. Bleibt an.

Die fünf Prefetcher

Aptio Setup, CPU Configuration, mit Hyper-Threading, den fünf Prefetchern und AES-NI

Und jetzt der Teil, der wirklich interessant ist:

Hardware Prefetcher             [Enable]
Adjacent Cache Prefetch         [Enable]
DCU Streamer Prefetcher         [Enable]
DCU IP Prefetcher               [Enable]
LLC Prefetch                    [Enable]

Fünf Zeilen, die aussehen wie eine Liste von Marketingbegriffen und in Wirklichkeit fünf verschiedene Stücke Hardware beschreiben. Alle haben dieselbe Aufgabe: raten, welche Daten die CPU als nächstes braucht, und sie schon mal holen. Speicher ist quälend langsam gegenüber dem Prozessor, ein Zugriff der bis zum RAM durchschlägt kostet ein Vielfaches dessen, was aus dem Cache kommt. Jeder erfolgreiche Rateversuch spart diese Wartezeit.

Sie raten nur unterschiedlich:

  • Hardware Prefetcher ist der Streaming-Prefetcher für den L2. Er erkennt, dass ein Programm Speicher der Reihe nach durchläuft, und holt vorausschauend nach. Der klassische Fall ist eine Schleife über ein grosses Array.
  • Adjacent Cache Prefetch ist der simpelste von allen. Er holt zu jeder angeforderten 64-Byte-Cachezeile gleich die benachbarte mit, arbeitet also faktisch in 128-Byte-Blöcken. Die Wette lautet: wer diese Zeile braucht, braucht gleich auch die daneben.
  • DCU Streamer Prefetcher macht dasselbe wie der erste, aber eine Ebene näher am Kern, für den L1-Datencache. DCU steht für Data Cache Unit.
  • DCU IP Prefetcher ist der cleverste. IP steht hier für Instruction Pointer. Er merkt sich pro Befehl, welche Zugriffsmuster dieser Befehl in der Vergangenheit erzeugt hat, und extrapoliert daraus. Damit erwischt er auch Muster mit konstantem Abstand, etwa jedes achte Element einer Struktur, an denen ein reiner Streaming-Prefetcher scheitert.
  • LLC Prefetch zieht Daten in den gemeinsamen L3, den Last Level Cache.

Das Handbuch beschreibt den DCU IP Prefetcher übrigens als etwas, das „IP addresses“ vorlädt und damit „network connectivity“ verbessert. Das ist schlicht falsch, da hat jemand Instruction Pointer mit Internet Protocol verwechselt und den Satz nie wieder gelesen. Solche Sätze stehen in erstaunlich vielen Handbüchern, und sie sind eine gute Erinnerung daran, dass auch offizielle Dokumentation nur von Menschen geschrieben wird.

Die Gegenprobe: was davon kommt wirklich an

Das Schöne an diesen fünf Schaltern ist, dass man sie nicht glauben muss. Sie landen in einem Prozessorregister, und das kann man auslesen. MSR 0x1A4 heisst Prefetch Control, und die unteren vier Bits schalten die Prefetcher ab, wenn sie gesetzt sind:

modprobe msr
rdmsr -0 0x1a4
#   0000000000000000

Null. Kein Bit gesetzt, also kein Prefetcher abgeschaltet. Das deckt sich exakt mit dem, was das Setup anzeigt. Die Zuordnung ist Bit 0 für den Hardware Prefetcher, Bit 1 für Adjacent Cache Line, Bit 2 für den DCU Streamer und Bit 3 für den DCU IP Prefetcher. Man beachte die umgekehrte Logik: gesetztes Bit bedeutet aus.

Ich mag solche Gegenproben. Ein Screenshot zeigt, was das Setup behauptet. Das Register zeigt, was die CPU tatsächlich macht. Bei Firmware ist das nicht immer dasselbe.

Wann man daran drehen würde

Fast nie, ehrlich gesagt. Die Prefetcher sind für allgemeine Lasten deutlich im Plus, sonst hätte Intel sie nicht eingebaut. Es gibt aber Fälle, in denen sie schaden:

Bei Datenbanken und Graphen-Workloads mit stark zufälligem Zugriffsmuster liegt der Prefetcher regelmässig daneben, holt Daten die keiner braucht, und verdrängt dabei Daten die jemand gebraucht hätte. Bei Echtzeitanwendungen sind sie eine Jitter-Quelle, weil sie Speicherbandbreite zu unvorhersehbaren Zeitpunkten verbrauchen. Und bei manchen HPC-Codes, die ihre Zugriffe selbst sehr genau steuern, stört der Prefetcher mehr als er hilft.

Der entscheidende Punkt: das misst man, das rät man nicht. Und der grosse Vorteil des MSR ist, dass man dafür gar nicht ins BIOS muss. Man kann die Bits im laufenden System setzen, messen, und wieder zurücksetzen. Wer das ernsthaft ausprobiert, sollte allerdings wissen, dass wrmsr genau so gefährlich ist wie es klingt, und dass die Einstellung pro logischem Prozessor gilt.

Nächste Folge: die untere Hälfte derselben Seite. AES-NI, TME, SGX und ein Schalter namens Extended APIC, der aussieht wie eine vergessene Optimierung und keine ist.

Siehe auch

Hat jemand von euch schon mal Prefetcher gezielt abgeschaltet und dabei etwas gemessen, das sich gelohnt hat? Das würde mich wirklich interessieren, ihr dürft mich jederzeit fragen.

BIOS erklärt, Teil 2: Boot Feature, oder was ein Interrupt von 1981 hier noch tut

Der erste Start der neuen Maschine war laut. Nicht akustisch, sondern optisch: eine Wand aus weissem Text auf schwarzem Grund, Speichertest, Controller die sich melden, Netzwerkkarten die ihre MAC-Adressen ausspucken, und das alles über mehrere Bildschirmseiten. Bei einem Desktop-Board wäre da ein Herstellerlogo gewesen und darunter vielleicht ein dezenter Fortschrittsbalken. Ich habe die Textwand gelassen wie sie ist, und der Grund dafür steht in den ersten beiden Zeilen des Menüs, um das es heute geht.

Das hier ist Teil 2 der Serie, in der ich das BIOS meines Supermicro-Boards Stück für Stück durchgehe. Die bisherigen Folgen findest du gesammelt in der Kategorie BIOS & Firmware. Heute: Boot Feature, die erste Seite unter Advanced, neun Einstellungen rund um das Einschalten.

Die Anzeige, oder: warum ich kein Logo will

Aptio Setup, Seite Boot Feature, mit Quiet Boot, Wait for F1, INT19 Trap Response, Watchdog und Restore on AC Power Loss
Quiet Boot                        [Disabled]
Option ROM Messages               [Force BIOS]
Bootup NumLock State              [On]

Quiet Boot ist der Schalter für genau die Textwand von oben. Auf Enabled zeigt das Board beim Start ein Logo, auf Disabled die POST-Meldungen. Das Logo ist hübscher, aber es versteckt genau die Informationen, die man braucht wenn etwas nicht stimmt. Auf einer Maschine mit vier Netzwerkports, einem Beschleuniger und einer NVMe an einem Breakout-Kabel möchte ich beim Booten sehen, wer sich meldet und wer nicht. Das kostet mich drei Sekunden Wartezeit und hat mir schon zweimal eine Fehlersuche erspart.

Option ROM Messages ist der kleine Bruder davon. Steckkarten bringen eigene Firmware mit, das Option ROM, und die möchte beim Start etwas ausgeben. Force BIOS heisst: das BIOS bestimmt, wie diese Ausgaben aussehen und dass sie überhaupt erscheinen. Keep Current überlässt das der Karte. Auch hier gilt, ich möchte die Meldungen sehen.

Bootup NumLock State ist der harmloseste Schalter des ganzen Setups und trotzdem einer, über den sich Menschen erstaunlich zuverlässig streiten. Er legt fest, ob der Ziffernblock nach dem Einschalten an ist. Bei mir: an.

Wait For „F1“ If Error

Wait For "F1" If Error            [Disabled]

Das ist der Klassiker unter den Server-Fallstricken. Steht der Schalter auf Enabled und beim Start tritt irgendein Fehler auf, dann bleibt die Maschine stehen und wartet darauf, dass jemand F1 drückt. Bei einem Rechner unter dem Schreibtisch ist das ärgerlich. Bei einem Server im Rechenzentrum ist es der Grund, warum man nachts hinfährt.

Der Fehler muss dabei nicht dramatisch sein. Eine leere CMOS-Batterie reicht, oder eine Konfigurationsänderung, die das BIOS für erwähnenswert hält. Auf Disabled protokolliert das Board den Fehler und bootet trotzdem weiter. Für alles ohne Tastatur davor ist das die einzig sinnvolle Einstellung. Wer den Rechner täglich vor sich hat, kann anderer Meinung sein, dann merkt man wenigstens sofort dass etwas war.

Der Interrupt aus dem Jahr 1981

INT19 Trap Response               [Immediate]

Und hier wird es schön. Das Handbuch erklärt den Schalter so:

Interrupt 19 is the software interrupt that handles the boot disk function.

Interrupt 19h, genauer INT 19h, ist der Bootstrap Loader aus dem BIOS des originalen IBM PC von 1981. Der Ablauf war denkbar einfach: das BIOS beendet seinen Selbsttest und ruft INT 19h auf. Was dahinter liegt, lädt den ersten Sektor eines Laufwerks und übergibt die Kontrolle dorthin. Das war der komplette Bootvorgang.

Interessant wird es durch das, was Steckkarten daraus gemacht haben. Ein SCSI-Controller mit eigenem Option ROM konnte sich in INT 19h einhängen, den Aufruf abfangen und stattdessen von seiner eigenen Platte booten. Genau so wurden Karten bootfähig, ohne dass das Mainboard-BIOS je von ihnen gehört hatte. Immediate heisst, die Karte darf sich den Interrupt sofort greifen. Postponed heisst, sie muss warten bis das System-BIOS fertig ist.

Meine Maschine bootet im reinen UEFI-Modus, ohne CSM, und lädt einen EFI-Bootloader von der NVMe. INT 19h spielt dabei überhaupt keine Rolle mehr. Der Schalter ist trotzdem noch da, weil irgendwo auf der Welt noch eine Karte steckt, die ihn braucht. Ich mag solche Fundstücke. Man scrollt durch ein Setup von 2025 und stolpert über einen Mechanismus, der älter ist als die meisten Leute, die ihn konfigurieren.

Neustart bei Fehlschlag, und der Watchdog

Re-try Boot                       [Disabled]
Watch Dog Function                [Disabled]

Re-try Boot startet die Maschine automatisch neu, wenn der erste Bootversuch fehlschlägt. Klingt hilfreich, ist es in einer Endlosschleife aber nicht. Wenn die Bootplatte weg ist, dann ist sie auch beim vierzigsten Versuch weg, und ich bekomme statt einer klaren Fehlermeldung eine Maschine die im Sekundentakt neu startet. Auf Disabled bleibt sie stehen und sagt mir was los ist. Wer eine Maschine an einem Ort betreibt, an den er schlecht hinkommt, wägt das anders ab.

Der Watchdog ist ein anderes Kaliber. Laut Handbuch löst er nach fünf Minuten aus und macht dann wahlweise einen Reset oder einen NMI, also einen nicht maskierbaren Interrupt. Der Sinn dahinter: eine Software auf dem laufenden System muss den Timer regelmässig zurücksetzen. Passiert das nicht mehr, weil das System hängt, greift der Watchdog ein.

Der Haken ist der zweite Halbsatz. Es braucht diese Software. Ohne einen Dienst, der den Timer füttert, hat man einen Timer der nach fünf Minuten unangekündigt die Reset-Leitung zieht, und das ist keine Verfügbarkeitsmassnahme sondern eine Zeitbombe. Unter Linux gäbe es dafür watchdogd, und wenn ich das eines Tages sauber einrichte, schalte ich den Schalter an. Vorher nicht.

Zwei Schalter, die ich gar nicht sehe

Im Handbuch stehen an dieser Stelle noch Front USB Port(s) und Rear USB Port(s), mit denen sich die USB-Anschlüsse einzeln abschalten lassen. Auf einem Rechner an einem öffentlich zugänglichen Ort ist das eine ernstzunehmende Massnahme. In meinem Setup tauchen die beiden Zeilen nicht auf, und das Handbuch sagt auch warum:

The next two features are available for configuration if the SFT-DCMS-SINGLE license is installed.

Wer Teil 1 gelesen hat, kennt die Lizenz schon. Sie ist der Grund, warum ich meine BIOS-Konfiguration nicht maschinell auslesen kann. Dass sie zusätzlich zwei Sicherheitseinstellungen im Setup versteckt, hatte ich nicht erwartet. Das ist kein Management-Feature für Flottenbetreiber mehr, das ist eine Funktion des Boards, das ich gekauft habe, hinter einer Bezahlschranke im eigenen Setup.

Nach dem Stromausfall bleibt sie aus

Restore on AC Power Loss          [Stay Off]
Power Button Function             [4 Seconds Override]

Drei Möglichkeiten gibt es: Stay Off, Power On und Last State. Der Werksdefault ist Last State, also der Zustand von vor dem Stromausfall. Bei mir steht es auf Stay Off, und das ist eine bewusste Entscheidung gegen den naheliegenden Reflex.

Der Reflex sagt: klar soll die Maschine wieder hochkommen, sonst steht sie nach einem Stromausfall im Urlaub zwei Wochen tot herum. Für einen Server stimmt das auch. Für meine Workstation nicht. Wenn hier der Strom weg war, dann ist etwas passiert, und ich möchte selbst entscheiden wann und ob die Kiste wieder angeht. Dazu kommt der Fall, den man leicht übersieht: ein flatternder Strom, der mehrfach hintereinander kommt und geht. Mit Power On fährt das Board dann jedes Mal wieder an, mitten in den nächsten Ausfall hinein. Ein Rechner der aus ist, kann nicht kaputtgehen.

Beim Power Button Function habe ich 4 Seconds Override gelassen. Kurz drücken meldet dem Betriebssystem einen Shutdown-Wunsch, den es sauber abarbeitet. Vier Sekunden halten schneidet den Strom hart ab. Die Alternative Instant Off macht das sofort, ohne Rückfrage, und dafür sehe ich keinen Grund. Ein versehentlicher Stups ans Gehäuse soll keine ungespeicherte Arbeit kosten.

Fazit

Neun Schalter, davon acht auf Default und einer bewusst geändert. Das ist ungefähr die Quote, die sich durch das ganze Setup zieht, und sie ist auch in Ordnung so. Interessant sind trotzdem alle neun, weil hinter jedem eine Entscheidung steckt, die jemand mal getroffen hat.

Falls du beim Watchdog oder bei INT 19h anderer Meinung bist oder ich etwas verkürzt dargestellt habe: immer her damit. Besonders bei INT 19h würde mich interessieren, ob jemand noch aktiv Hardware betreibt, bei der Postponed einen Unterschied macht. Ich habe keine gefunden.

Nächste Folge: die CPU. Kerne, Threads und die fünf Prefetcher, von denen die Hälfte Namen trägt, die nach Marketing klingen und es nicht sind.

Siehe auch

Wie steht bei dir Restore on AC Power Loss, und aus welchem Grund? Schreib es mir gerne, ihr dürft mich jederzeit fragen.

Bald ist wieder FrOSCon: warum ich seit zwanzig Jahren hinfahre und wen ich dort treffen möchte

Moin.

Konferenz-Namensschild an einem grünen Schlüsselband, Aufschrift FrOSCon 2026, Sankt Augustin, darunter der Name Kernel-Error und ein Stempel mit dabei seit 2007

Am kommenden Wochenende ist wieder FrOSCon. Samstag der 15. und Sonntag der 16. August, wie immer an der Hochschule Bonn-Rhein-Sieg in Sankt Augustin, Grantham-Allee 20. Der Eintritt ist frei, es laufen sieben Vortragsschienen und zwei Workshop-Räume parallel, samstagabend gibt es das Social Event. Das Programm steht online.

Es ist die 21. Ausgabe. Die erste war 2006, die Konferenz wird also gerade zwanzig. Ich bin an beiden Tagen dort und würde mich sehr freuen, wenn wir uns treffen und ein bisschen quatschen. Also: ist noch jemand von euch da? Sag mir gerne vorher Bescheid, dann verabreden wir uns, oder sprich mich einfach an, wenn du mich irgendwo im Foyer siehst.

Und falls du noch nie dort warst: fahr hin. Der Eintritt kostet nichts, das Programm ist gut, und diese Mischung aus Vorträgen, Ständen und Leuten, die dir ungefragt ihr Projekt erklären wollen, gibt es in der Form nicht oft.

Warum es nicht jedes Jahr klappt

Ich versuche eigentlich jedes Jahr, auf die FrOSCon zu kommen. Das scheitert regelmäßig daran, dass meine Kinder rund um dieses Wochenende Geburtstag haben, und das geht dann selbstverständlich vor. Dieses Jahr passt es, deshalb schreibe ich das hier überhaupt.

Beim Zusammensuchen der alten Beiträge habe ich dann etwas gefunden, das mich kurz sehr still gemacht hat. In meinen Entwürfen liegt ein Beitrag vom 9. August 2013, der nie veröffentlicht wurde. Darin steht:

Ich habe gerade die Bestätigung meines FrOSCon Tickets bekommen 🙂 Die FrOSCon ist dieses Jahr vom 24.08-25.08.2013 in Sankt Augustin. Ich schaffe es nicht immer zur FrOSCon denn meine Kinder haben immer um diesen Dreh Geburtstag. Das geht dann vor! In diesem Jahr klappt es genau und dieses freut mich natürlich umso mehr! Wir sehen uns also auf der FrOSCon….

Ich, in einem Entwurf vom 9. August 2013, den ich nie veröffentlicht habe

Dreizehn Jahre später sitze ich hier und schreibe im Grunde denselben Absatz noch einmal. Gleiche Konferenz, gleicher Ort, gleiche Terminkollision, gleiche Freude, dass es diesmal passt. Nur die Kinder sind inzwischen deutlich größer. Den Beitrag von 2013 lasse ich übrigens in der Schublade, der hat sich seinen Platz dort verdient.

Und die Kinder?

Möglicherweise dreht sich die Sache dieses Jahr sogar. Teckids e.V. macht an beiden Tagen die Mini-FrogLabs, das Jugendprogramm der Konferenz. Samstag geht es um Luanti, also eigene Mods und Kreaturen in 3D, Sonntag um Spieleprogrammierung mit Python. Gedacht ist das etwa ab der vierten Klasse bis sechzehn, Vorkenntnisse braucht niemand, bezahlt wird so viel man mag, und man meldet sich für jeden Tag einzeln an. Das schaue ich mir für meine Kinder auf jeden Fall an. Nach all den Jahren, in denen die Kinder der Grund waren, nicht hinzufahren, wäre das eine schöne Wendung.

Wie das bei mir angefangen hat

Wann ich zum ersten Mal dort war, kriege ich nicht mehr sicher zusammen. Mein Gefühl sagt 2006, belegen kann ich 2007. Es gibt Bilder aus dem Jahr in meiner Galerie, in einem Beitrag von 2013 hängt ein Schlüsselband von der FrOSCon 2007 an meinem Schlüsselbund, und der Ausweis hängt bis heute bei mir im Arbeitszimmer an der Wand.

Drei Konferenzausweise an bunten Schlüsselbändern an einer Wand. Vorne der Ausweis der FrOSCon 2007 mit handgeschriebenem Namen und dem senkrechten Aufdruck Aussteller, darunter ein hellblaues Schild der FrOSCon 2013 mit Frosch-Logo, rechts ein Besucherausweis der OpenRheinRuhr 2010.
Der Ausweis von 2007 hängt noch im Arbeitszimmer, rechts senkrecht steht Aussteller. Darunter das hellblaue Schild von 2013, das belegt, dass ich in dem Jahr tatsächlich dort war, auch wenn der Beitrag dazu nie erschienen ist. Rechts daneben der Besucherausweis der OpenRheinRuhr 2010.

Damals habe ich am Stand von CAcert mitgeholfen, einer von der Gemeinschaft betriebenen Zertifizierungsstelle, die kostenlos Zertifikate ausgestellt hat, lange bevor das normal war. Das Vertrauen kam dort nicht von einem Audit, sondern von Menschen: Punkte gab es, wenn man einem Assurer seinen Ausweis zeigte. Genau das haben wir den ganzen Tag gemacht, und ich fand das damals ganz große Klasse.

Geworden ist daraus nichts. Der Antrag auf Aufnahme in den Truststore von Mozilla wurde noch im selben Jahr zurückgezogen, und bis heute vertraut kein Browser dieser Wurzel. Ein curl auf die Seite bricht immer noch mit self-signed certificate in certificate chain ab, gerade nachgesehen. Gelöst hat das Problem später Let’s Encrypt, mit einem Protokoll statt mit Menschen und Ausweisen. Technisch die bessere Antwort, ein bisschen wehmütig macht es mich trotzdem.

Hängengeblieben ist trotzdem etwas. Mein Interesse an Zertifikaten, Schlüsseln und dem ganzen Vertrauenskram hat genau dort angefangen, und das hat mich seitdem nicht mehr losgelassen.

Die Keysigning-Party

Zum selben Themenkreis gehört das andere, wozu ich früher immer gegangen bin, wenn es sich einrichten ließ: die GPG-Keysigning-Party. Das Prinzip war schön unaufgeregt. Vorher schickt jeder seinen Schlüssel-Fingerprint ein, vor Ort steht man in einer Reihe, vergleicht die Zeile auf dem ausgedruckten Zettel mit dem, was die andere Person vorliest, sieht sich einen Ausweis an, hakt ab. Zu Hause signiert man dann die Schlüssel, deren Zeile gestimmt hat. Eine Stunde Anstehen, ein Blatt Papier, und danach hängt das eigene Schlüsselbund ein Stück fester im Netz.

Im offiziellen Zeitplan für dieses Jahr finde ich keine Keysigning-Party. Das muss nichts heißen, solche Runden organisieren sich oft nebenbei und stehen dann in keinem Programmheft. Falls sich jemand findet, bin ich sofort dabei. Und ganz unabhängig davon, weil es der einfachere Weg ist: wenn wir uns auf der FrOSCon über den Weg laufen, würde ich mich sehr über ein Cross-Signing meines neuen Schlüssels freuen. Der ist frisch, trägt die Kennung 0x893DE0CDDE986DEB, den Fingerprint bringe ich ausgedruckt mit und den Personalausweis sowieso. Wie ich ihn gebaut und woran ich ihn beim Härten kaputt gemacht habe, steht hier im Blog. Die Rolle, die früher der Ausweis am Klapptisch hatte, übernehmen bei mir mittlerweile nachprüfbare Identity-Claims. Funktioniert gut, ersetzt aber das Anstehen nicht.

Ein Gruß an Jens Link, und eine offene Frage

Aus den frühen Jahren ist mir ein Vortrag bis heute im Kopf geblieben, und zwar einer über IPv6 von Jens Link. Der Mann hat es geschafft, mich mit Adressarchitektur und Autokonfiguration zum Lachen zu bringen und mir im selben Vortrag klarzumachen, dass da erheblich mehr dahintersteckt als eine Pflichtaufgabe, die man irgendwann mal erledigen muss. Das können nicht viele. Ich bin danach an dem Thema hängengeblieben und habe es nie wieder losgelassen.

Fair muss ich dazusagen, dass ich nicht durch ihn angefangen habe. Mein ältester Beitrag zu IPv6 hier im Blog ist vom März 2003, vier Jahre vor diesem Wochenende, es ging um Adressarchitektur und radvd unter Linux. Dass daraus vierunddreißig Beiträge geworden sind, verteilt über die Jahre bis 2021, dafür trägt dieser Vortrag aber einen guten Teil der Schuld. Wenn man einmal gesehen hat, wie jemand über so ein Thema begeistert und komisch reden kann, schaut man danach anders hin. Einen Gruß nach Berlin also, und ein Dankeschön obendrauf.

Genau erinnern kann ich mich nach knapp zwanzig Jahren natürlich nicht mehr, weder an das Jahr noch an die Folien. Der Eindruck ist geblieben, das reicht mir.

Und eine Frage, bei der ihr mir helfen könnt. In meiner Erinnerung wollte er damals ein Buch über IPv6 schreiben. Ich habe es nie gesehen, was aber genauso gut daran liegen kann, dass es an mir vorbeigegangen ist. Auf seiner Seite finde ich keins, und sein Vortragsarchiv reicht nicht so weit zurück, dass ich damit etwas belegen könnte. Weiß es jemand? Ist das Buch erschienen, ist es liegen geblieben, oder habe ich mir das über die Jahre selbst zusammengebaut? Im Fediverse ist er als @quux unterwegs.

Worauf ich schauen will

Festlegen will ich mich nicht, das wird bei mir sowieso spontan. Auf dem Zettel stehen aber diese acht:

Wenn ich die Liste so ansehe, ist das Muster ziemlich eindeutig. Sechs von acht liegen in der Security-Schiene, die anderen zwei kommen aus dem Themenblock zum Recht auf Reparatur, und mehrfach geht es um Haftung und Regulierung statt um Technik. Das ist neu für mich. Drei davon laufen am Samstag im selben Raum, zwei sogar direkt hintereinander, das kommt mir sehr entgegen. Ob ich es aus dem CrypTool-Workshop rechtzeitig rausschaffe, steht auf einem anderen Blatt, der Zeitplan verrät keine Endzeiten.

Die Stände, an denen ich hängen bleiben werde

Auf meiner Runde stehen das CrypTool Projekt, ElectroBSD, Freie Software und Bildung, illumos, Matrix, The PHP Foundation, XMPP und der Datenburg e.V. Bei illumos werde ich vermutlich am längsten stehen, aus reiner Nostalgie, mein Solaris-Archiv hier im Blog hat immerhin achtunddreißig Beiträge. Bei The PHP Foundation habe ich sogar eine konkrete Frage im Gepäck, nachdem ich mich neulich durch einen PHP-Absturz bis in den Laufzeitlinker von FreeBSD gegraben habe.

Auf der Ausstellerliste steht übrigens ein Name, bei dem ich kurz grinsen musste: CAcert ist dieses Jahr wieder mit einem Stand da, neunzehn Jahre später, im selben Gebäude. Die Wurzel liegt weiterhin in keinem Truststore, die Leute stehen trotzdem wieder hinter dem Tisch. Da gehe ich vorbei und sage einmal Danke.

Zwanzig Jahre

Was mich beim Schreiben am meisten getroffen hat, ist nicht die CAcert-Geschichte, sondern die Rechnung dahinter. Von meinem belegten ersten Besuch bis heute sind es neunzehn Jahre. Rechne ich mein Gefühl mit, dass es 2006 war, sind es zwanzig. Die Konferenz gibt es genauso lange. Zwischen dem Klapptisch mit den Assurance-Formularen und dem Beitrag, den ich neulich über einen TPM-Chip geschrieben habe, liegt also ziemlich genau mein halbes Leben mit diesem Hobby. Ich habe mich beim Nachrechnen ehrlich alt gefühlt.

Andererseits: das Wochenende ist immer noch dasselbe Wochenende. Gleiche Hochschule, freier Eintritt, Leute, die einem an einem Stand irgendetwas erklären, wovon sie viel zu begeistert sind. Das ist eine ziemlich gute Konstante.

Siehe auch:

Wenn du auch auf der FrOSCon bist, sag gerne Bescheid, ich freue mich über jeden Kaffee im Stehen und über jede Signatur. Und wenn du die Sache mit dem IPv6-Buch aufklären kannst, oder sonst etwas dazu wissen möchtest, dann darfst du mich sehr gerne fragen.

Die Maus funkt im Klartext: wie sicher Funktastatur und Funkmaus unter Linux wirklich sind

Vor Kurzem saß ich in einer Arztpraxis im Wartezimmer, und weil ich so bin wie ich bin, habe ich statt der Zeitschriften die Rechner am Empfang angeschaut. An jedem hing eine Funkmaus und eine Funktastatur, dazu der kleine Adapter im USB-Port. Von einem Hersteller, den ich nicht kannte, der Eindruck war sehr günstiges No-Name-Gerät. So etwas nenne ich selbst gern etwas abfällig „Chinaprodukt“, was eigentlich unfair ist, denn die können das inzwischen richtig gut.

Funkmaus und Funktastatur neben einem Linux-Rechner mit 2,4-GHz-Funkanalyse. Die Tastaturverbindung ist verschlüsselt, die Maus überträgt unverschlüsselt.

Im Gespräch mit dem Arzt kam heraus, dass ihm ein Punkt völlig neu war: dass die Tastatureingaben als Funk durch die Luft gehen und dass man Funk mithören kann. An seinem Platz werden Diagnosen, Namen und Medikamente getippt, und die Frage, ob das jemand aus dem Nachbarzimmer mitlesen könnte, hatte sich vorher schlicht nie gestellt. Aus genau dieser Unterhaltung ist dieser Beitrag entstanden, und er wollte ihn selbst lesen. Also, hallo an dieser Stelle, danke für den Anstoß.

Ich will die Praxis dabei nicht an den Pranger stellen. Über die Sicherheit genau dieses No-Name-Geräts kann ich nichts sagen, ich habe es nie in der Hand gehabt. Der Punkt ist ein anderer: fast jeder benutzt so einen Adapter, und kaum jemand hat sich die Frage je gestellt. Bei einer Mausbewegung ist das auch egal. Bei einem Passwort, einer PIN oder einem Login nicht. Also drei Fragen, die den Beitrag tragen. Kann man das wirklich abhören? Kann man sogar eigene Tasten einschleusen? Und wie sieht man, ob das eigene Gerät geschützt ist?

Ich mache das am Beispiel von Logitechs Unifying-Adapter, dem mit dem kleinen orangen Stern, weil ich davon zwei Stück im Rechner habe und weil Logitech einer der Hersteller ist, bei dem man den Unterschied zwischen „kein Krypto“ und „verschlüsselt“ schön sehen kann. Alles, was ich hier auf dem Rechner prüfe, zeige ich mit dem Befehl und der echten Ausgabe. Und wo etwas unklar bleibt, sage ich das auch.

Durch die Luft, und schon sind es zwei Probleme

Bevor es um Logitech geht, muss eine Sache sauber getrennt sein, denn der ganze Beitrag hängt daran. Wer an einem Funkgerät lauscht, will eines von zwei Dingen, und die sind unterschiedlich gefährlich.

  • Mitlesen, passiv. Der Angreifer hört den Funk nur mit. Das ist gefährlich bei allem, was die Tastatur sendet, also Passwörter, PINs, Nachrichten. Bei der Maus dagegen gehen nur Bewegungen und Klicks über die Luft, da ist Mitlesen fast folgenlos.
  • Einschleusen, aktiv. Der Angreifer sendet selbst und gibt sich als euer Gerät aus. Damit tippt er auf eurem Rechner, öffnet ein Terminal, lädt etwas nach. Das ist der unangenehmere Fall, weil er nicht davon abhängt, dass ihr gerade etwas tippt. Der Rechner steht am Empfang, niemand sitzt davor, und trotzdem passiert etwas.

Daraus ergeben sich drei Schutzstufen, und die Reihenfolge ist wichtig.

  1. Kein Schutz. Klartext. Jeder in Funkreichweite liest mit und kann einschleusen.
  2. Verschlüsselung. Der Funk ist chiffriert. Wer mithört, bekommt nur Kauderwelsch. Logitech nimmt dafür bei Tastaturen AES-128.
  3. Authentisierung. Der Empfänger prüft, ob ein Paket wirklich vom gekoppelten Gerät stammt. Erst das schließt das Einschleusen zuverlässig, denn Verschlüsselung allein sagt nur, dass keiner mitliest, nicht, dass keiner sich einschleicht.

Und hier kommt die Design-Entscheidung, die uns später um die Ohren fliegt. Eine Tastatur braucht zwingend Verschlüsselung, weil sie Geheimes sendet. Eine Maus bekam bei Unifying historisch keine, weil sie ja nichts Geheimes sendet. Das klingt vernünftig, und für sich genommen ist es das auch. Zum Einfallstor wurde es trotzdem, weil ein Empfänger, der offene Maus-Pakete akzeptiert, sich dazu bringen ließ, auch gefälschte Tastatur-Pakete zu schlucken. Genau das war MouseJack. Dazu gleich mehr.

Diagramm des Funkwegs: Tastatur MX Keys funkt AES-128-verschlüsselt, Maus M705 im Klartext, beide über den Unifying-Empfänger zum Rechner, ein Angreifer mit Antenne hört mit.
Der Funkweg. Die Tastatur funkt AES-128-verschlüsselt, die Maus im Klartext. Ein Angreifer mit Antenne hört beide Strecken, von der Tastatur bekommt er aber nur Kauderwelsch.

KeySniffer, so sieht es aus, wenn gar nichts da ist

Damit klar wird, wovon wir reden, erst das schlechte Beispiel. 2016 zeigte die Firma Bastille unter dem Namen KeySniffer, dass Funktastaturen von acht Herstellern die Tastenanschläge komplett im Klartext funken, ohne jede Verschlüsselung. Betroffen waren Anker, EagleTec, General Electric, Hewlett-Packard, Insignia, Kensington, Radio Shack und Toshiba. Mitlesbar aus rund 75 Metern, mit Hardware für unter hundert Dollar. Man tippt sein Passwort, und einer im selben Gebäude schreibt es Zeichen für Zeichen mit.

Zwei Dinge daran sind wichtig. Erstens ist das kein „schwaches Krypto“, das ist „kein Krypto“. Diesen Unterschied wirft man gern in einen Topf, aber er ist der ganze Punkt. Zweitens kann man diese Tastaturen nicht per Update retten. Da hilft nur wegwerfen und ersetzen. Und genau das ist der Unterschied zu Logitech, wo ein Empfänger-Update wirklich etwas ändert, wie wir noch sehen werden. Bluetooth-Tastaturen und die höherwertigen Geräte von Logitech, Dell und Lenovo waren von KeySniffer übrigens nicht betroffen. Das ist die Brücke zu Logitech.

Ein Stern, sechs Geräte: wie Unifying funktioniert

Unifying ist der kleine USB-Adapter mit dem orangen Stern, an den sich bis zu sechs Geräte koppeln lassen. Eine Maus, eine Tastatur, vielleicht ein Ziffernblock, alles über einen Stick. Technisch funkt das bei 2,4 GHz auf Nordics nRF24-Technik, nicht Bluetooth. Das ist ein wichtiger Punkt, den ich später beim Abhören noch brauche. Tastaturen werden mit AES-128 verschlüsselt, der Schlüssel wird beim Koppeln festgelegt. Mäuse funken offen.

MouseJack, 2016, und die offene Maus als Hebel

Ebenfalls 2016, wieder Bastille, diesmal Marc Newlin. Über die offene, nicht authentifizierte Maus-Strecke ließen sich gefälschte Tastatur-Pakete in viele Empfänger einschleusen, dazu erzwungenes Koppeln. Betroffen waren Geräte vieler Hersteller, nicht nur Logitech. Der Angreifer musste nicht warten, bis jemand tippt. Er gab sich als neue Tastatur aus und schrieb selbst.

Logitech reagierte mit mehreren Firmware-Ständen für die Empfänger. Nach Logitechs eigenen Release-Notes sind die Kern-MouseJack-Punkte, also erzwungenes Koppeln, Tastatur-Injektion und gefälschte Maus, ab RQR12.05 beziehungsweise RQR24.03 geschlossen. Eine später gefundene Schwäche, die verschlüsselte Injektion (Bastille-Punkt #13, als CVE-2016-10761 geführt), folgte erst ab RQR12.08 und RQR24.06. Diese zwei Generationsnummern solltet ihr euch merken, sie kommen beim Testgerät gleich wieder.

Die vier Lücken von 2019, und die eine, die blieb

2019 nahm sich Marcus Mengs, im Netz MaMe82, Unifying noch einmal vor. Vier CVEs, und der Unterschied zwischen ihnen ist der eigentliche Lehrwert.

CVEWasFix
CVE-2019-13052Aus dem Mitschnitt der Kopplung den AES-Schlüssel ableiten, danach mitlesen und einschleusenkein Patch, Logitech lehnte ab
CVE-2019-13053Einschleusen in die verschlüsselte Strecke, setzt einmaligen physischen Zugriff vorauskein Patch für die erweiterte Form
CVE-2019-13054Schlüssel aus R500- und Spotlight-Presentern auslesen, physischer ZugriffFix für August 2019 angekündigt
CVE-2019-13055Alle gekoppelten Schlüssel aus einem Empfänger in unter einer Sekunde, physischer ZugriffFix für August 2019 angekündigt

Die spektakuläre Fernübernahme, MouseJack, ist an einem aktuellen Empfänger zu. Was bleibt, ist die erste Zeile: CVE-2019-13052. Wer den Moment der Kopplung mitschneidet, kann daraus den AES-Schlüssel ableiten und danach mitlesen und einschleusen. Diese Lücke wurde nie geschlossen. Logitech hat erklärt, dafür keinen Patch zu liefern, sie steckt im Design.

Das praktische Risiko ist gering, weil man dafür genau dann mithören muss wenn ihr ein Gerät neu koppelt, und weil der Schlüssel danach nicht neu ausgehandelt wird. Koppeln passiert selten und ist schnell vorbei. Aber der Satz „Firmware aktuell, also alles gut“ wäre trotzdem falsch, und genau diese Ehrlichkeit macht für mich den Reiz an der Sache aus. Diese Kopplungs-Lücke ist übrigens auch der Aufhänger für den zweiten Teil.

Zeitleiste: 2016 MouseJack, Firmware-Fix ab RQR12.05 und RQR24.03, 2019 die vier Mengs-CVEs mit der nie gepatchten CVE-2019-13052, und 2021 Logi Bolt mit BLE Security Level 4.
Die Angriffe über die Zeit. MouseJack wurde per Firmware geschlossen, aus der 2019er-Serie blieb CVE-2019-13052 offen, und Logi Bolt zieht 2021 die Konsequenz.

Logi Bolt, der Nachfolger, der es richtig macht

2021 brachte Logitech mit Logi Bolt einen neuen Empfänger heraus, der Sicherheit von Anfang an mitdenkt. Die Kernpunkte, alle belegt:

  • Bolt setzt auf Bluetooth Low Energy im Security Mode 1, Level 4, also Secure Connections Only. Das heißt verschlüsselt und authentifiziert. Genau der Schutz gegen Einschleusen, der Unifying von Haus aus fehlte.
  • Es ist ein geschlossenes System, ein Bolt-Empfänger spricht nur mit Bolt-Geräten.
  • Pro Kopplung eine andere Bluetooth-Adresse und eigene Schlüssel.
  • Koppeln geht nur nach einem ausdrücklichen, langen Knopfdruck von einigen Sekunden. Das schließt das erzwungene Koppeln.
  • Firmware ist zentral ausrollbar, mit Schutz gegen das Zurückrollen auf alte Stände.

Ein Missverständnis will ich gleich einfangen. Bolt ist Bluetooth-basiert, aber es ist ein geschlossenes, gehärtetes System, kein normales Bluetooth-Pairing, wie ihr es vom Kopfhörer kennt. Verwechselt das nicht, das eine ist gehärtet, das andere ist ein bunter Haufen.

Zwei Empfänger, eine Maus, eine Tastatur

Genug Geschichte, jetzt der eigene Rechner. Auf dem Testgerät, einem Notebook mit Linux Mint 22.3, stecken zwei Unifying-Empfänger gleichzeitig. Am einen hängt die Maus, eine Logitech Marathon Mouse M705, am anderen die Tastatur, eine MX Keys. Beide Sticks tragen denselben orangen Stern, und wie sich gleich zeigt, tragen sie trotzdem zwei verschiedene Sicherheitsgeschichten mit sich herum.

Die Logitech Marathon Mouse M705 und ein Unifying-Empfänger mit orangem Stern auf einer Tischplatte.
Die Maus M705 und ein Unifying-Empfänger mit dem orangen Stern.
Nahaufnahme eines Logitech-Unifying-Empfängers, der orange Unifying-Stern ist deutlich lesbar.
Der orange Stern ist das Erkennungszeichen von Unifying.
Die Logitech MX Keys Tastatur in der Gesamtansicht.
Die Tastatur, eine Logitech MX Keys.

Erst einmal schauen, ganz ohne Spezialwerkzeug

Man muss nicht sofort ein Paket installieren, um zu verstehen, was da steckt. Das Betriebssystem verrät schon eine ganze Menge. Erst schaue ich, welche USB-Geräte hängen. Mit lsusb sehe ich beide Empfänger, und interessant ist, dass sich beide als exakt dieselbe Kennung melden.

$ lsusb | grep -i logitech
Bus 001 Device 011: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 012: ID 046d:c52b Logitech, Inc. Unifying Receiver

046d ist Logitech, c52b ist der Unifying-Empfänger. Zwei gleiche Kennungen, zwei physische Sticks. Als Nächstes die Kernelmodule, die diese Sticks bedienen.

$ lsmod | grep -i logi
hid_logitech_hidpp     69632  0
hid_logitech_dj        36864  0
usbhid                 77824  2 hid_logitech_dj,hid_logitech_hidpp
hid                   282624 10 usbhid,...,hid_logitech_dj,hid_logitech_hidpp

Zwei Module machen die Arbeit, und die Aufgabenteilung lohnt sich zu verstehen. hid_logitech_dj kümmert sich um das Multiplexing des Unifying-Empfängers, also darum, dass ein einziger Stick mehrere Geräte tragen kann. hid_logitech_hidpp spricht das Feature-Protokoll HID++, über das gleich auch Solaar redet. Und über die Geräteknoten /dev/hidraw* reden die Werkzeuge am Ende mit den Sticks. So versteht man die Schichten, bevor überhaupt ein Zusatzprogramm im Spiel ist.

Solaar, das eine Werkzeug, das man wirklich braucht

Solaar ist die grafische Oberfläche und zugleich ein Kommandozeilen-Werkzeug für Unifying und HID++. Damit koppelt man Geräte, sieht den Akkustand, den Verschlüsselungsstatus und kann Tasten umbelegen. Auf dem Testgerät läuft Version 1.1.11. Der eine Befehl, der alles auf einmal ausgibt, ist solaar show. Für den Empfänger der Maus, gekürzt auf das Wesentliche:

Unifying Receiver
  USB id       : 046d:C52B
  Serial       : 3CE0986A
    Firmware   : 12.11.B0032
  Has 1 paired device(s) out of a maximum of 6.

  1: Marathon Mouse M705 (M-R0073)
     Kind         : mouse
     Protocol     : HID++ 4.5
     Battery: 50%, discharging, next level 20%.
Solaar-Empfängeransicht der Maus: USB-ID 046d:C52B, Seriennummer 3CE0986A, Firmware 12.11.B0032, ein gekoppeltes Gerät.
Solaar zeigt den Empfänger der Maus. USB-ID 046d:C52B, Firmware 12.11.B0032, ein gekoppeltes Gerät.

Und für den Empfänger der Tastatur:

Unifying Receiver
  USB id       : 046d:C52B
  Serial       : C31FD0D1
    Firmware   : 24.11.B0036
  Has 1 paired device(s) out of a maximum of 6.

  1: MX Keys Keyboard
     Kind         : keyboard
     Protocol     : HID++ 4.5
Solaar-Empfängeransicht der Tastatur: USB-ID 046d:C52B, Firmware 24.11.B0036, MX Keys gekoppelt.
Der zweite Empfänger, der für die Tastatur. Dieselbe USB-Kennung, aber Firmware 24.11.B0036.

Hier fällt schon auf, was ich gleich groß mache. Zwei Unifying-Sticks, aber zwei Firmware-Linien: RQR12 für die ältere Maus-Generation, RQR24 für die neuere Tastatur. Beide tragen denselben orangen Stern, dahinter stecken aber zwei Baujahre und zwei Geschichten.

Zwei Sterne, ein Unterschied

Das ist das Herzstück, und es ist ein einziger Blick in Solaar. In der Geräteansicht steht bei jedem gekoppelten Gerät eine Zeile „Drahtlose Verbindung“. Für die Maus M705 meldet Solaar dort „nicht verschlüsselt“, mit einem roten Schild. Für die Tastatur MX Keys meldet dieselbe Zeile „verschlüsselt“, mit einem geschlossenen Schloss.

Solaar-Geräteansicht der Marathon Mouse M705: Drahtlose Verbindung nicht verschlüsselt, mit rotem Schild.
Solaar meldet für die Maus M705 eine nicht verschlüsselte Funkverbindung, mit rotem Schild.
Solaar-Geräteansicht der MX Keys: Drahtlose Verbindung verschlüsselt, mit geschlossenem Schloss.
Für die Tastatur MX Keys meldet Solaar dieselbe Übersicht als verschlüsselt, mit geschlossenem Schloss.

Was das heißt, und was nicht. Die Tastatur ist mit AES-128 verschlüsselt, der Schlüssel wurde beim Koppeln festgelegt. Wer die Funkstrecke mithört, bekommt Kauderwelsch. Die Maus funkt offen, was für Bewegungen und Klicks kein Problem ist. Und dann die ehrliche Einschränkung, die uns direkt zum Angriff führt: dass die Maus offen funkt, war historisch genau der Hebel für das Einschleusen, bis die Firmware das abgestellt hat.

Ein Gedanke, der jetzt naheliegt, führt in die Irre: dann schalte ich die Verschlüsselung der Tastatur eben ab und schaue, was durchgeht. Geht nicht. Die AES-Verschlüsselung der Tastatur hat keinen Ausschalter. Sie steckt fest im Unifying-Protokoll, Solaar hat dafür keinen Schalter, das alte ltunify auch nicht. Wer die Klartext-Eingaben der Tastatur sehen will, muss das AES brechen, nicht abschalten.

Was man installieren sollte, und was nicht

Für die reine Frage „bin ich sicher“ braucht man erstaunlich wenig. Zwei Pakete reichen, und beide waren auf dem Testgerät sowieso schon da.

PaketWofür
solaarKopplung, Akku, Verschlüsselungsstatus, Tastenbelegung
fwupdFirmware-Updates über LVFS

Ein paar Werkzeuge, über die man im Netz stolpert, braucht man hier ausdrücklich nicht, und ich schreibe dazu, warum.

PaketWarum nicht
ltunifyältere Unifying-Kommandozeile, von Solaar überholt
libratbag / piperDPI- und Tastenkonfiguration für Gaming-Mäuse, für die M705 unnötig, Solaar deckt sie ab
jackit, nrfutil, dfu-toolAngriffs- und Flash-Werkzeuge für nRF-Hardware, hier nicht installiert. jackit gehört zum Crazyradio-am-Laptop-Weg, in diesem Aufbau macht die Funkarbeit stattdessen der Flipper

Bin ich sicher? Zwei Blicke genügen

Der erste Blick ist der Verschlüsselungsstatus, den Solaar meldet. Tastatur verschlüsselt, Maus nicht. Das ist bei Unifying normal und in Ordnung, solange man weiß, dass die Maus offen funkt und dort nichts Geheimes durchgeht.

Der zweite Blick ist die Firmware. fwupd kennt beide Empfänger über LVFS, und fwupdmgr get-updates sagt, ob etwas ansteht.

$ fwupdmgr get-updates --no-unreported-check
Devices with the latest available firmware version:
 • Unifying Receiver
 • Unifying Receiver

Beide sind auf dem neuesten Stand, den LVFS anbietet, das ist RQR12.11 und RQR24.11. Und jetzt die Verbindung zur Geschichte, die den Abschnitt wertvoll macht. Die MouseJack-Fixes stecken in den Firmware-Generationen ab RQR12.05 beziehungsweise RQR24.03 für die Kern-Punkte, und ab RQR12.08 beziehungsweise RQR24.06 für die verschlüsselte Injektion. Beide Empfänger liegen mit 12.11 und 24.11 deutlich darüber. Die Fernübernahme von 2016 ist an diesem Gerät also zu. Das ehrliche Ergebnis dieses Abschnitts ist „hier ist nichts zu tun“, und das darf man auch so hinschreiben.

Trotzdem der Handgriff für den Fall, dass doch einmal ein Update ansteht, als Verfahren, nicht als Messung. Auf dem Testgerät gab es nichts zu aktualisieren, ich habe die letzte Zeile hier also nicht wirklich ausgeführt.

fwupdmgr refresh
fwupdmgr get-updates
fwupdmgr update

Wie würde ein Angreifer das überhaupt abhören?

Jetzt die andere Seite. Unifying funkt bei 2,4 GHz auf nRF24-Technik mit einer eigenen Paketstruktur, Enhanced ShockBurst nennt Nordic das. Normale SDR-Empfänger und die üblichen Sub-Gigahertz-Geräte sehen das schlicht nicht. Man braucht ein nRF24-Radio, das in einen Mithör-Modus versetzt wird. Und genau hier wird es interessant, was ein Flipper Zero kann und was nicht.

Der Flipper Zero, und warum er allein nicht reicht

Das eingebaute Funkteil des Flipper ist ein CC1101. Das arbeitet unter einem Gigahertz, grob 300 bis 928 MHz. Es kommt physikalisch nicht an die 2,4 GHz von Unifying heran. Aus der Schachtel heraus kann der Flipper diesen Angriff also gar nicht, egal welche Firmware drauf ist.

Diagramm: der Sub-GHz-CC1101 des Flippers sieht die 2,4-GHz-Strecke nicht, ein nRF24-Modul erkennt sie und kann MouseJack, Crazyradio oder nRF52840 mit Logitacker können zusätzlich Schlüssel ableiten.
Welches Radio an 2,4 GHz herankommt. Der eingebaute CC1101 des Flippers arbeitet unter einem Gigahertz und sieht die Strecke nicht. Erst ein nRF24-Radio kommt heran.

Auf meinem Aufbau steckt der Flipper mit der Momentum-Firmware und einem Zusatzmodul, einem AIO Board. Auf dem Board steht die Bestückung selbst drauf: RED NRF24, GREEN ESP32, BLUE CC1101, dazu ein EBYTE-Modul und externe Antennen. Das entscheidende Teil für uns ist das nRF24-Radio. Genau das, was dem Flipper von Haus aus fehlt. Damit werden die Apps NRF24: Sniffer und NRF24: Mouse Jacker nutzbar, beide aus dem Projekt von mothball187 und in Momentum enthalten.

Flipper Zero mit aufgestecktem AIO Board V1.4 und drei Antennen, betriebsbereit. Das Board trägt neben ESP32 und CC1101 das nRF24-Radio.
Flipper Zero mit aufgestecktem AIO Board V1.4. Das Board liefert das nRF24-Radio für 2,4 GHz, neben ESP32 und CC1101, das dem Flipper allein fehlt.

Der Ablauf ist in zwei Schritten schnell erzählt. Erst sucht der Sniffer die Kanäle ab und findet die Funkadresse des Empfängers. Dann bekommt der Mouse Jacker diese Adresse und ein Tastatur-Skript und versucht, Tasten einzuschleusen. Klingt einfach. Der erste Schritt ist es aber schon nicht.

Bildschirm des Flipper-NRF24-Sniffers: eine gefundene Adresse auf einem 2,4-GHz-Kanal.
Der NRF24-Sniffer meldet eine gefundene Adresse auf einem 2,4-GHz-Kanal.

Der nRF24-Treiber der Flipper-App gilt selbst als funktionierend, aber unfertig, und das Sniffen im 2,4-GHz-Band rastet gern auf zufälliges Rauschen ein und meldet Adressen, die es gar nicht gibt. Drei Läufe brachten bei mir drei verschiedene Adressen, einmal sogar mit „Unique 0“, also ohne einen einzigen wiederholten Treffer. Eine echte Logitech-Adresse taucht über mehrere Läufe immer wieder auf, das ist das Erkennungszeichen. Mit etwas Geduld, Maus dabei bewegen und jeden Durchlauf sauber mit OK beenden, damit die App überhaupt speichert, hat sich am Ende die Adresse 086A98E03C als die stabile, echte herauskristallisiert. Das Erkennen klappt also, aber es ist selbst schon eine Hürde und nicht der Sofort-Erfolg, den man sich vorstellt.

Die offene Maus lässt sich mit demselben Radio mitschnüffeln, man sieht rohe Pakete. Passwörter sind da natürlich keine drin, nur Bewegung und Klicks. Genau das ist der greifbare Beleg für „die Maus funkt im Klartext“.

Und jetzt der eigentliche Versuch. Ich habe einen einfachen Texteditor auf dem Notebook geöffnet und ihm den Fokus gegeben. Der Mouse Jacker bekam die ersniffte Adresse 086A98E03C und ein harmloses Skript, das nur den Text MOUSEJACK-TEST-0810 tippen sollte, sonst nichts. Der Flipper zeigte „Running duckyscript“.

Bildschirm des Flipper-Mouse-Jackers mit der Meldung Running duckyscript während des Injection-Versuchs.
Mouse Jacker feuert das Skript gegen die Adresse. Auf dem gepatchten Empfänger landet keine einzige Taste.

Im Editor erschien nichts. Keine einzige Taste kam durch. Das ist kein Misserfolg des Versuchs, sondern das erwartete Verhalten an einem Empfänger, der über dem MouseJack-Fix liegt.

Hier bleibe ich bei der Ursache ehrlich, das ist mir wichtig. „Nichts passiert“ allein trennt nicht sauber, ob die Firmware das Paket abgewiesen hat oder ob die ersniffte Adresse nicht exakt der Empfänger war. Um das isoliert zu beweisen, müsste ein bewusst verwundbarer Vergleichs-Empfänger danebenstehen, an dem derselbe Angriff gelingt, und der stand hier nicht daneben. Sicher ist: die klassische MouseJack-Übernahme aus der Ferne, ohne Anfassen, gelang an diesem aktuellen Setup mit RQR12.11 nicht, und das passt dazu, dass fwupd die Firmware als aktuell und über dem Fix meldet. Mehr behaupte ich an dieser Stelle nicht. Das ist gemessen, die Deutung der Ursache ist plausibel, aber nicht bewiesen.

Nur eine von mehreren Möglichkeiten

Der Flipper ist nur ein Weg, und ehrlich gesagt der bequeme für unterwegs. Damit klar ist, was sonst noch geht:

  • Crazyradio PA, ein nRF24-USB-Stick mit der Bastille-Firmware plus jackit. Das klassische MouseJack-Werkzeug, läuft am Laptop und kann mitschreiben.
  • nRF52840-Dongle mit Logitacker von Marcus Mengs. Das moderne Werkzeug, das auch die 2019er-Lücken kann, inklusive der Kopplungs-Lücke CVE-2019-13052. Genau dieses Gespann kommt im zweiten Teil zum Einsatz.
  • Raspberry Pi mit nRF24-Modul. Möglich, aber deutlich fummeliger, weil das Timing schlechter ist als bei einer dedizierten Crazyradio.

Die Grenze des Flippers ist dabei klar. Er ist gut zum Erkennen und für den MouseJack-Versuch, aber er leitet keine Schlüssel ab und entschlüsselt keine Tastatur. Dafür braucht es Logitacker auf dem nRF52840, und das ist der zweite Teil.

Was ich für sichere Umgebungen empfehle

Kein Wischiwaschi, eine klare Rangfolge.

  1. Kabel. Eine USB-Tastatur funkt gar nicht. Für alles, wo wirklich Geheimes getippt wird, ist das die ruhigste Lösung.
  2. Logi Bolt oder eine sauber gekoppelte Bluetooth-Verbindung mit Secure Connections. Verschlüsselt und authentifiziert, also auch gegen Einschleusen geschützt.
  3. Aktuelles Unifying. Völlig brauchbar, wenn die Empfänger-Firmware gepflegt ist. Die Tastatur ist verschlüsselt, die Fernübernahme ist zu. Das Restrisiko ist die Kopplungs-Lücke.
  4. Alles ohne Verschlüsselung, KeySniffer-Klasse, gehört ersetzt. Da hilft kein Update.

Dazu zwei Handgriffe, die jeder machen kann. Die Firmware der Empfänger aktuell halten, mit fwupdmgr, das ist eine Minute Arbeit. Und Geräte nur in vertrauenswürdiger Umgebung koppeln, nicht im Café und nicht im vollen Großraumbüro, wegen genau dieser Kopplungs-Lücke CVE-2019-13052.

Entscheidungshilfe: für wirklich Geheimes Kabel, fürs Büro Logi Bolt oder BLE Secure Connections, vorhandenes Unifying behalten und die Firmware pflegen, No-Name-Funktastaturen ersetzen.
Eine kurze Entscheidungshilfe für das eigene Funk-Setup.

Was offen bleibt

  • Warum das Einschleusen genau scheiterte. Der Durchlauf ist gemessen, der Sniffer findet 086A98E03C und die Injektion landet nichts, aber ob die Firmware abwies oder die ersniffte Adresse nicht exakt saß, ist ohne einen verwundbaren Vergleichs-Empfänger nicht isoliert nachgewiesen.
  • Die Kopplungs-Lücke CVE-2019-13052 ist beschrieben, aber nicht vorgeführt. Das ist der Kern des zweiten Teils.
  • Ob sich auf genau diesem nRF24-Modul die Adresse zuverlässig mitschneiden lässt, hängt von Kanal und Timing ab und ist nicht erschöpfend getestet.
  • Ob die MX Keys selbst per fwupd ein Firmware-Update bekommen könnte, habe ich nicht geprüft, nur die beiden Empfänger.
  • Reichweite und Störumgebung. Alle Aussagen zum Mithören beziehen sich auf die Laborsituation direkt am Gerät.

Wie es weitergeht

Im nächsten Teil wird nicht nur erklärt, sondern gemacht. Mit einem nRF52840-Dongle und Logitacker schneide ich den Kopplungsvorgang einer Unifying-Tastatur mit, leite daraus den Schlüssel ab und lese die Eingaben mit. Das ist die Design-Lücke CVE-2019-13052, und sie betrifft auch die aktuelle Firmware, an der der Flipper heute noch abgeprallt ist. Aus „die Tastatur ist verschlüsselt“ wird dann „und trotzdem lesbar, wenn ich im richtigen Moment dabei war“. Das ist der interessante Teil, und er kommt.

Bezugsquellen

Die folgenden Links sind Affiliate-Links (Werbung). Kaufst du darüber, bekomme ich eine kleine Provision, für dich ändert sich am Preis nichts.

Siehe auch

Wenn ich irgendwo danebenliege, korrigiert mich gerne, dann lerne ich selbst etwas. Ich bin ja auch nur doof ;-). Und die Frage an euch zum Schluss: Nutzt ihr Funk oder Kabel, und habt ihr je nachgesehen, ob eure Tastatur überhaupt verschlüsselt funkt? Wie das geht, wisst ihr jetzt. Wenn ihr mögt, dürft ihr mich dazu sehr gerne fragen.

« Ältere Beiträge

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑