Projekt secdb

Grundgerüst für eine abhörsichere Datenbankanwendung

Ich habe eine Datenbank für meinen Werkzeughandel. Dafür brauchte ich eine Anwendung mit der man mit der Datenbank arbeiten kann. Z.B. Bestellungen eingeben, Lagerverwaltung, Kundenverwaltung, Artikelverwaltung, Rechnungswesen, Statistik usw.
Die Anwendung sollte von verschiedenen Personen benutzt werden können und die Kommunikation über das Internet sollte verschlüsselt sein.

Die Javascript Teile des Projekts sind nur für Mozilla Firefox geschrieben. Wer den Browser nicht benutzen will, braucht gar nicht weiterzulesen.

Für die Kommunikation sorgen serverseitig das Php-Programm mydb.php und clientseitig secdb.html. Für den Firefox Browser habe ich das Add-on `secureDB` geschrieben. Dieses übergibt Daten ( ID, Passwort und Schlüssel ), die in einer bestimmten Datei auf dem Rechner gespeichert sind, an Anwendungen mit diesem Grundgrerüst.
Wo fnge ich an? Am Besten mit den Datenbank Tabellen, die für das Login zuständig sind.

Die Tabelle user speichert die ID, den Namen, das Passwort und den Schlüssel jedes Users.
create table user(
	id tinyint unsigned not null,
	name char(16) not null,
	pass char(32) not null,
	crkey varchar(256)
)
Die Tabelle hosts speichert die ID, IP, den Zeitpunkt des Einloggens und einen Sitzungsschlüssel für jeden eingeloggten User.
create table hosts(
	id tinyint unsigned not null,
	host varchar(40) not null,
	seit timestamp not null on update current_timestamp,
	session varchar(32) not null
)

Weiter geht es mit dem Php Programm mydb.php

Zuerst kommen ein paar Utility Funktionen.

Die Funktion dencode wird zum ver- und entschlüsseln verwendet.Beim Login wird der Sitzungsschlüssel erst mitgesendet, er steht daher noch nicht gleich zur Verfügung, deshalb besteht die Möglichkeit ohne ihn (einfach) zu entschlüsseln ( Argument 2 = false ).

Die Funktion ask spart eine Menge schreibarbeit. Man schreibt die SQL Anfrage in die globale Variable $sql und ruft dann ask(mode) auf. Der Rückgabewert hängt vom Argument mode ab. Bei mode=2 müssen danach die Ergebnisreihen mit $row = $res->fetch_row() noch einzeln abgerufen werden.

Die Funktion check wird zum Umwandeln von Text in Integer, Float oder Datumsformat benutzt. Ausserdem fängt sie unerlaubte Zeichen ab und schützt so gegen SQL - injection.

Zuerst wird eine Verbindung zur Datenbank hergestellt. Anschliessend werden Benutzer, die länger als eine Stunde untätig waren aus der Tabelle hosts gelöscht. Da müsste ich noch etwas ändern, damit die Benutzer, wenn sie sich wieder einloggen wollen eine Nachricht bekommen. Dann werden die binären Daten, die geposted wurden in die Variable $str eingelesen. Das erste Byte ist unverschlüsselt und beinhaltet die ID des Benutzers. Der Rest ist doppelt verschlüsselt mit dem Sitzungsschlüssel und dem Benutzerschlüssel, ausser beim Login, da ist er nur mit dem Benutzerschlüssel einfach verschlüsselt, weil ja der Sitzungsschlüssel noch nicht verfügbar ist. Jetzt haben wir also die ID und einen verschlüsselten Rest. Aus user wird das Passwort und der Benutzerschlüssel für den Benutzer mit der ID im ersten Byte geholt. Dann wird in hosts geschaut, ob der Benutzer schon eingeloggt ist. Die erste Verzweigung der if Bedingung regelt das Login, die zweite Verzweigung kommt bei bereits eigeloggten Benutzern zum Tragen. Beim Login wird nach dem ersten Byte das Passwort und direkt anschliessend der Sitzungsschlüssel gesendet, ansonsten werden die Daten als Name=Wert Paare getrennt durch & gepostet. Der Einfachheit halber, dürfen die Zeichen = und & in den Namen und den Werten nicht vorkommen, das muss ich noch ändern.

Login : 1. Verzweigung nach if( 0 == ask() )
Stimmt das Passwort überein, wird der Sitzungsschlüssel in hosts gespeichert. Dem Array t wird das Element tpl mit dem Wert login hinzugefügt. Das bedeutet, dass nacher die Datei ../includes/login.inc eingebunden wird.

Bereits eingeloggt : 2. Verzweigung nach if( 0 == ask() )
Es wird geprüft, ob die IP noch stimmt. Dann wird der Sitzungsschlüssel aus hosts geholt und die gesendeten Daten entschlüsselt. Die Daten werden im Array $t wie eben gesagt als Name=Wert Paare gespeichert. Es muss tpl=WERT dabei sein weil tpl die Templatedatei angibt, die in der vorletzten Zeile eingebunden wird.

Jetzt wird noch der Zeitstempel in hosts für den Benutzer aktualisiert, dann geschaut, ob es die Template Datei gibt, wenn ja, wird diese eingebunden. Der Code dieser Datei macht dann seine Arbeit und das Ergebnis wird in der Variable $ret gespeichert. In der letzten Zeile wird diese verschlüsselt an den Benutzer geschickt, nach den beiden Zeichen <<, die dem secdb.html anzeigen, dass danach verschlüsselte Daten kommen.

Nun kommt das Hauptprogramm secdb.html

Es handelt sich um eine `one page application`, eine Seite die nie verlassen wird und sich selbst verändert. Für die verschiedenen Aufgaben werden je nach Bedarf Module, d.h. Javascript Objekte, eingebunden und wenn sie nicht mehr benötigt werden, werden sie wieder gelöscht.
Diese Module holen sich die Daten, die sie benötigen von der Datenbank via mydb.php und erstellen dann eine Benutzeroberfläche. Das will ich aber hier nicht demonstrieren, ich zeige nur das Login und das Logout.
Das Login erfolgt automatisch mithilfe des Firefox Add-ons `secureDB`. Das macht ein oder mehrere Menü Einträge im Extras Menü, und beim Anklicken startet die jeweilige Anwendung im Browser. Die init() Funktion von secdb.html, die beim starten aufgerufen wird, feuert ein Event, das vom Add-on abgefangen wird. Daraufhin übergibt das Add-on die ID, das Passwort und den Benutzerschlüssel an secdb.html. Dann wird das Modul login geladen, das sich mit diesen Daten via mydb.php in die Datenbank einloggt und ein Menü anzeigt.

Schauen wir uns zuerst einmal die Eigenschaften der globale Variablen sd an, des Hauptobjekts. Es ist ein singleton Objekt, mit dem Vorteil, dass man einzelene Eigenschaften ganz einfach hinzufügen und wieder entfernen kann.

pfad :
z.B.: http.//beispiel.com/verwaltung/
Darin liegt mydb.php sowie die Verzeichnisse js und includes.
In js liegen die Module mit der Endung .js, die die jeweils benötigten Benutzeroberfläche, Tabellen, Buttons, Eingabefelder usw. erstellen.
In includes liegen die Template Dateien mit der Endung .inc, die mydb.php jedesmal einbindet und die die benötigten Daten von der Datenbank liefern.

xhr :
Das ist ein XMLHttpRequest Objekt, das für den Datentransport über das Inernet sorgt.

key :
Das ist der Benutzerschlüssel in Form eines Uint8Arrays. Auf dem Server, in mydb.php, ist das nur ein String, aber in PHP ist ein String nichts Anderes als ein Uint8Array.

session :
Das ist ein zusätzlicher Sitzungsschlüssel, um die Arbeit von NSA und Konsorten ein wenig zu erschweren. Er wird vom Modul login erzeugt, hat eine variable Länge und zufällige Zeichen.

modules :
Ein Array mit Objekten, die Daten über die gerade verwendeten Module beinhalten: der Name des Moduls und der Handle des zugehörigen Skripts, damit man es wieder löschen kann.

uid und pass :
Die ID und das Passwort für die Datenbank. Das Passwort wird nur einmal im Modul login gebraucht.

init ;
Diese Funktion wird nach dem Laden aufgerufen. Sie erzeugt ein Event, auf das das Firefox Add-on `secureDB` reagiert. Dann wird temporär ein DOM Objekt erzeugt mit den 3 Attributen uid, pass und key und ins DOM eingehängt. Auf dieses wird das Event abgefeuert und von dort zum Add-on Code aufsteigen lassen. Nun werden die Daten vom Add-on in den 3 Attributen gespeichert. Diese werden gesichert und das Element wird wieder gelöscht. Der Schlüssel liegt nun als String vor und wird in ein Uint8Array umgewandelt. Zum Schluss wird das Modul login eingebunden, dazu später.

post ;
Durch die post Funktion werden Daten von der Datenbank via mydb.php angefordert. Das Argument str ist der post string, mit dem man der Datenbank mitteilt, was man will. Wie schon gesagt enthält er NAME=WERT Paare die durch & voneinander getrennt sind (ausser beim Login).
Das Argument func bezeichnet die Funktion, die die Daten verarbeitet. Sie wird von post nach spliInput nach decode und schliesslich nach arrToString weitergegeben.
Der post String wird in ein Uint8Array umgewandelt und mit den Arrays key und session xor - verschlüsselt. Danach wrid als erstes Byte die Benutzer ID gesetzt. Der Session Schlüssel darf beim Login noch nicht verwendet werden, weil er erst in der Datenbank gespeichert werden muss, daher wurde er mit vier Nullen initialisiert und ist somit wirkungslos. Der load - Handler von xhr übergibt die empfangenen Daten an splitInput.

splitInput :
Die Daten vor den beiden Zeichen `<<` sind unverschlüsselt, das sind Warnungen vom Server. Diese werden an showWarning weitergegeben. Nach `<<` kommen verschlüsselte Bytes, sie werden an decode weitergeleitet.

decode und arrToString:
In decode werden die Bytes decodiert und anschliessend in arrToString in einen String umgewandelt und an die Funktion übergeben, die die Daten verarbeiten soll.

use-, add- und delModule :
Die Funktion useModule löscht zuerst alle Module und ruft dann addModule auf.
AddModule lädt mithilfe von xhr eine Javascript-Datei aus dem pfad/js Verzeichnis des Servers. Es wird ein script-Element erzeugt und ins HTML-DOM eingehängt. Der Inhalt der Datei stellt eine Eigenschaft der Hauptvariablen sd dar und wird ins script-Element übertragen, Daraufhin wird die init() Funktion des Skripts aufgerufen. DelModule ruft erst den Destruktor des Moduls auf, falls der vorhanden ist. Diese Funktion destruct soll die Benutzeroberfläche des Moduls löschen. Danach entfernt delModule das script Element aus dem DOM und löscht das Modul.

Natürlich ist secdb.html damit noch nicht fertig. Man braucht noch einige utility Functionen, die von den einzelnen Modulen immer wieder gebraucht werden.
Aber das ist einfache Javascript/DOM Programmierung, ab jetzt braucht man sich nicht mehr um die eingebauten Javascript Objekte wie `Blob`, `Uint8Array`, `FileReader`, `XMLHttpRequest` usw. zu kümmern.
Jetzt kommen die Module.

login.js
Die init() Funktion erstellt den Sitzungsschlüssel. Dieser wird zusammen mit dem Passwort via der post() Funktion an mydb.php gesendet.
Nach erfolgreichem Login, sendet mydb.php die Daten für das Menü. Diese Daten sollen von der Funktion setup() des Moduls verarbeitet werden. Deshalb ist das 2. Argument für die post() Funktion `sd.login.setup`.
Die setup() Funktion erhält die Daten von mydb.php als String, über die Kette : load handler von post() -> splitInput() -> decode() -> arrToString() .
Die Daten werden so verarbeitet, dass ein Menü angezeigt wird.

login.inc
Die Datei login.inc wird von mydb.php nach erfolgreichem Login eingebunden. Sie übergibt einen JSON String an sd.login.setup(). Der JSON String stellt ein Array mit Objekten dar. Die Eigenschaften der Objekte sind `n` = die Überschriften der Sektionen und `a` = ein weiteres Array mit Objekten. Diese Objekte haben die Eigenschaften `s' = Modulname und `d` = Menütext.

logout.js
Die init() Funktion fordert Daten von logout.inc an und diese sollen in der Funktion setup() verarbeitet werden.

logout.inc
Hier wird der user aus der Tabelle hosts gelöscht und ist somit abgemeldet. Dann wird der Name des Benutzers zurückgegeben.

Das Modul logout zeigt, wie man Daten von mydb.php über eine include Datei anfordert.
Hier noch ein Beispiel : sd.post("tpl=test&sql=select id from kunde where name=\"ABC GmbH & Co. KG\"", sd.xmod.yfunc);
Das funktioniert nicht weil die Zeichen = und & nur als Separatoren vorkommen dürfen. Ich mache das so, dass ich anstatt = ~ und anstatt & # verwende und diese Zeichen in der .inc Datei wieder zurückverwandle.
Jetzt ein Blick auf das Firefox Add-on 'secureDB'.

Das Overlay
Hier wird der Menüeintrag `secureDB`in das Extras.Menü des Browsers hinzugefügt. Das dazugehörige Untermenü ist noch leer.

Das Javascrip
Die Funktion init(), die beim Laden von Firefox ausgeführt wird, ruft die Funktion loadProfiles() auf. Diese ladet die Datei secdb.json aus dem extensions Verzeichnis des Firefox Profil Verzeichnisses und ruft dann die Funktion setupMenu auf.
SetupMenu erstellt das Untermenü, das bis jetzt noch leer war.
Klickt man auf einen Untermenü Eintrag, wird die entsprechende HTML Datei geladen. Diese muss dann ein `secdbEvent` auslösen und die Funktion listener() übergibt dann die ID, das Passwort und den Schlüssel an die Seite.

secdb.json
Diese Datei stellt ein Array mit Objekten dar. Wahrscheinlich ist im Array nur ein Objekt. Dieses hat folgende Eigenschaften :
name = Der Text des Menüeintrags
url = Die Adresse der Dantenbank Anwendung
uid = Die Benutzer ID für die Datenbank
pass = Das Passwort
key = der Benutzerschlüssel

Diese Datei sollten vom Datenbank Administrator, also wahrscheinlich von dir, auf sicherem Weg den jeweiligen Benutzern übermittelt werden. Der Benutzer muss sie dann im Firefox Profil Verzeichnis im Unterverzeichnis extensions speichern.

Viel Spass beim Programmieren