Probleme mit note und Crypt::PWSafe3 1.08

Mein Perlmodul Crypt::PWSafe3 ist ein Interface zur Datenbank des Passwortmanagers Password Safe, das ich vor geraumer Zeit entwickelt habe. Das Notizverwaltungsscript note - ebenfalls von mir - hat dafür ein Backendmodul bekommen, so dass man mit note auf PasswordSafe Dateien zugreifen kann.

Mit Version 1.08 des Crypt::PWSafe3 Moduls gab es aber ein Problem, wenn man note gestartet hatte, erschien nach der Passworteingabe diese Meldung:

notedb seems to be encrypted!

Ursache dafür war ein Bug in Crypt::PWSafe 1.08, eigentlich sogar auch noch einer im note. Der Bug ist - vorsichtig ausgedrückt - eklig. Eigentlich handelt es sich bei dem Modul um eine Portierung eines Pythonmoduls. Die Verschlüsselung der PasswordSafe Datenbank ist nicht gerade unkompliziert und anhand des vorhandenen Pythonmoduls hatte ich eine Vorlage, wie man das implementieren kann.

Natürlich gibt es diverse Unterschiede zwischen Python und Perl, einer davon sind Formatstrings, die von den pack() und unpack() Routinen akzeptiert werden. Diese Routinen kann man verwenden, um zum Beispiel aus binären Daten typisierte Werte zu extrahieren (z.b. aus einer Bytefolge eine Fliesspunktzahl). Das Datenbankformat von PasswordSafe schreibt nun für alle Felder Little Endian Bytereihenfolge vor, was beim Ent- und Verschlüsseln natürlich berücksichtigt werden muss.

Die Formatstrings von pack() und unpack() in Python bieten samt und sonders entsprechende Flags an, z.b. "L<4" extrahiert eine 4-Byte-Long-Zahl im little endian Bytereihenfolge. In der alten Version meines Moduls (1.04) hatte ich diese Flags noch nicht verwendet. Das war für mich auch kein Problem, da ich nur little endian Maschinen zur Verfügung hatte, also funktionierte alles. Portabel ist aber was anders. Also habe ich das bei 1.08 geändert und die endian Flags in den entsprechenden Routinen eingebaut.

Und hier haben wir nun das Problem: Der Formatstring für Hexwerte "H*" bietet in Python ebenfalls den little endian Flag, in Perl aber nicht! Ich stand also vor einem Problem, das ich gelöst habe, indem ich einfach "H*" durch "L<4" ersetzt habe. Die Ent- und Verschlüsselung funktionierte immer noch wie vorher und mit der Lösung war das Modul nun endlich vollständig portabel.

Der Haken an der Sache war, dass man natürlich von "L<4" einen Wert in der korrekten Grösse (16 Byte) zurück bekommt, aber keinen Hexstring, sondern eine Zahl. Modulintern war das völlig redundant, da an der Stelle sowieso nur mit Zahlen hantiert wird. Das war auch der Grund, weswegen ich das - wohlwissend um den Umstand - so gelassen habe.

Zum Problem wurde das ganze erst im Zusammenspiel mit note. Note ist sehr alt. 15 Jahre werden das jetzt schon sein, seit es das gibt, oder mehr. Müsste ich mal nachschauen. Jedenfalls hat jeder Eintrag im Note eine ID, d.h. eine Nummer. Über diese ID wird der Eintrag adressiert. Also mit dem Befehl "e 18" bearbeitet man den Eintrag 18. Diesen Umstand berücksichtigen sämtliche Speicherbackends von Note, von denen es mittlerweile eine Reihe gibt, die aber samt und sonders proprietär sind. Das PasswordSafe-Backend ist das erste und einzige offene Backend und dessen Format ist vorgegeben. Und dieses Format kennt eine solche Nomenklatur mit numerierten IDs pro Eintrag nicht. Statt dessen werden in PasswordSafe die Einträge mit einer UUID adressiert. Das ist sehr elegant aber für mich als Noteautor wenig hilfreich.

Gelöst habe ich das, indem ich im Backendmodul eine Routine eingebaut habe, die aus den UUIDs einigermaßen kleine Zahlen generiert. Der Algorithmus ist primitiv, aber er erzeugt für die gleiche UUID immer die gleiche Zahl. Somit sind Note-Einträge eindeutig adressierbar und die Inkompatibilität war gelöst. Allerdings basiert die Routine darauf, dass die UUID als Hexstring vorliegt. Jedoch war das bei Crypt::PWSafe3 ab 1.08 nicht mehr der Fall, hatte ich dort ja für Hexstrings auf "L<4" umgestellt.

Die Routine hat daher nicht mehr funktioniert und immer IDs erzeugt, die bei 1 anfingen und einfach fortliefen. Hier kommt der zweite Bug zu Tage. An sich wäre das kein Problem: solange die IDs reproduzierbar und eindeutig sind, ist ja alles gut. Aber im Note ist eine Abfrage eingebaut, die prüft, ob auf eine verschlüsselte Datei zugegriffen wurde und die Entschlüsselung funktioniert hat, bzw. ob dies bei abgeschalteter Verschlüsselung geschah. Dazu nimmt sich Note den Eintrag mit der ID 1 und guckt beim Datum, ob es mit einer zweistelligen Zahl beginnt.

Das funktioniert bei allen Backends, nicht aber mit Crypt::PWSafe3. Dazu muss man wissen, dass die Passwortabfrage aus Implementierungsgründen im Backend eingebaut ist und bei dem Backend also nicht die Abfrage aus Note selbst verwendet wird. Daher muss man in der Config bei Verwendung dieses Backends die Verschlüsselung abschalten, damit sich beide Abfragen nicht in die Quere kommen. Dadurch wird aber die erwähnte Prüfroutine aktiviert. Die oben zitierte Fehlermeldung kommt von da.

Bis Crypt::PWSafe3 1.08 war das nie ein Problem, weil die Routine, die aus den UUIDs die Note-IDs erzeugt hat, niemals eine ID 1 errechnet hatte. Das war nicht beabsichtigt, aber so wie ich den Algorithmus gestaltet hatte, wird da wohl nie eine 1 herauskommen. Daher kam die Prüffunktion im Note nie zum Zug, weil es keinen Eintrag mit der ID 1 kam, was Note dazu veranlasst hatte, weiterzumachen (in der Annahme, die Datenbank sei leer). Mit dem geänderten unpack() Format für Hexstrings auf "L<4" kam aber nun auf einmal eine ID 1 heraus. Und hier kommt der nächste Bug: Im Backendmodul NOTEDB::pwsafe3 wiederum konvertiere ich die verschiedenen Felder der Datenbank in Note-kompatible Werte. Das Datumsfeld habe ich aber falsch konvertiert, da wurde ein einfacher localtime() String zurück gegeben, der mit dem Tag begann (wie in "Mi 19 Jun 2013 19:16:22 CEST"). Da das Datum nicht mit einer zweistelligen Zahl begann, hat die Noteroutine den erwähnten Fehler geworfen, davon ausgehend, dass auf eine verschlüsselte Datenbank zugegriffen wurde, die nicht entschlüsselt wurde.

Ich habe daher nun in Version 1.10 von Crypt::PWSafe3 wieder auf "H*" umgestellt. Das wird zwar eventuell Schwierigkeiten auf big endian Architekturen geben (Solaris z.b.), aber sollte es dazu kommen, werde ich den Bugreport an die Perlentwickler weiterleiten, weil das dann in dem Fall nicht mein Fehler ist. Gleichzeitig habe ich auch noch das Backendmodul für Note upgedatet, welches jetzt korrekte Datumswerte zurückliefert (für den Fall, dass doch mal eine ID 1 heraus kommt).

In der neuen Note-Version (1.16) wird nun auch nicht mehr länger der Eintrag mit der ID 1 geprüft, sondern einfach der erste Eintrag, welche ID auch immer der haben mag.

#source

↷ 19.06.2013