Zu kompakt: IF-Statement

Das Bestreben danach kompakten Code zu schreiben führt sehr oft im Ergebnis zu unleserlichem und verwirrendem Code dessen Sinn, und vor allem dessen Korrektheit, sich nicht auf den ersten Blick erschließt.

Hier ist ein Beispiel einer verschachtelten IF-Abfrage:

if (!((@include_once __DIR__ . '/../../../../../vendor/autoload.php') || !(@include_once __DIR__ . '/../../../../autoload.php'))) {
throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}

Nicht nur das die Zeile zu lang ist für die meisten Editoren, auch führen die Klammern zu Verwirrung. Formatiert man den Code um sieht er wie folgt aus:

if (
    !(
        (
            @include_once __DIR__ . '/../../../../../vendor/autoload.php'
        ) 
        || 
        !(
            @include_once __DIR__ . '/../../../../autoload.php'
        )
    )
) {
    throw new RuntimeException('vendor/autoload.php could not be found. Did you run `php composer.phar install`?');
}

Eine lesbarere Implementation der gleichen Logik sähe wie folgt aus:

if(file_exists(__DIR__ . '/../../../../../vendor/autoload.php')) {
    include_once __DIR__ . '/../../../../../vendor/autoload.php';
} else if(file_exists(__DIR__ . '/../../../../autoload.php')) {
    include_once __DIR__ . '/../../../../autoload.php';
} else {
    throw new RuntimeException('vendor/autoload.php could not be found. '.
        .'Did you run `php composer.phar install`?');
}

Formulare verschlüsselt übertragen mit GPG

By: Tsahi Levent-LeviCC BY 2.0[/caption]

Sicherheit im Netz

Daten wandern auf dem Weg zwischen dem eigenen Webbrowser und dem Server durch viele Leitungen und Netze. Nicht zuletzt der jetzt gerade (August 2013) in voller Blüte stehende Skandal um das Anzapfen der Glasfaserleitungen durch NSA und GCHQ macht einem das deutlich.
Wenn man nun selbst eine Webseite betreibt so ist die Absicherung des eigenen Servers gegen Eindringlinge sowie die Beachtung von Sicherheitsmaßnahmen beim erstellen der Webseite längst nicht mehr das Einzige. Denn was bringt es, wenn der eigene Server und der Rechner auf dem der Webbrowser läuft sicher sind, aber die Daten auf dem Weg dazwischen abgegriffen werden?

TLS – Transport Layer Security

Die meisten Webseiten setzen zur Absicherung von Login-Daten, Online-Banking oder Bezahlvorgängen auf die Absicherung durch TLS (Transport Layer Security), welches vielen unter der älteren Bezeichnung SSL (Secure Socket Layer) bekannt sein dürfte.

Allerdings stellt sich seit geraumer Zeit die Frage ob TLS wirklich noch ausreichende Sicherheit bietet. Eine Gefahrenquelle geht hierbei von sogenannten Man-in-the-middle-Angriffen aus. Sei es das diese dadurch möglich werden das Zertifizierungsstellen gehackt werden oder mit Organisationen im Geheimen zusammenarbeiten oder das verschiedene Angriffe möglich sind. Oder auch das Schutzmaßnahmen wie HTTP Strict Transport Security von Webseitenbetreibern nicht umgesetzt werden wie die Studie von SecureNet belegt.

Zusätzlicher Schutz durch zusäztliche Verschlüsselung

Nun sollte man keineswegs auf die Verschlüsselung durch TLS verzichten. Im Gegenteil, es sollten alle Inhalte einer Webpräsenz verschlüsselt sein (nicht nur der Login-Bereich oder ähnliches wie es heute noch meist der Fall ist). Allerdings kann man darüber hinaus über zusätzliche Maßnahmen nachdenken.

Eine Möglichkeit ist die, die Daten vor versenden an den Server zusätzlich zu TLS noch einmal zu verschlüsseln. Genau darum soll es in diesem Beitrag gehen.

Überblick

Die Idee ist, mittels einer JavaScript-Funktion die Daten vor dem Versand mittels Gnupg zu verschlüsseln und PHP mit allem notwendigen auszustatten um diese wieder entschlüsseln zu können. Hierzu sind mehrere Komponenten notwendig:

  • Installation der benötigten Softwarepakete auf dem Server
  • Erzeugen eines Schlüsselpaares
  • Installation des PECL-Moduls gnupg
  • Erstellen des benötigten JavaScript- und PHP-Codes

Inspiriert wurde ich vom Tutorial TSL/SSL für Heimwerker von Oliver Sperke und vieles ist auch daraus übernommen.

Der Server

Für meine Beispiel-Installation verwende ich eine virtuelle Maschine auf der CentOS 6.4 als 64 bit Version installiert ist. PHP wird als PHP-FPM verwendet und liegt in der Version 5.5.1 vor.

Installation der benötigten Softwarepakete auf dem Server

Abhängig von der eigenen Installation werden evtl. weitere Pakete benötigt. Hier jedoch diese, welche ich noch nachinstallieren musste:

yum install gnupg2 php55w-devel php55w-pear gcc make gpgme gpgme-devel

Eine Besonderheit ist hier die Installation von ‚php55w-devel‘ und ‚php55w-pear‘ welche auf das Webtatic-Repository verweist. Grundsätzlich sollte die Installation, im Standard-Fall, auch mit php-devel bzw. php-pear ausreichend sein.

Erzeugen eines Schlüsselpaares

Auf dem Server sollte nun ein Schlüsselpaar erzeugt werden. Hierzu, wenn möglich, die folgenden Schritte als Benutzer ausführen, welcher auch den Webserver bzw. PHP ausführt. Ausserdem sollte für den Schlüssel kein Passwort vergeben werden da ab gnupg version 2 keine Passwörter mehr im Klartext verwendet werden können in PHP.

gpg --gen-key

Als Typ habe ich RSA ausgewählt (Standard) und einen Schlüssel mit 4096 bit erzeugen lassen.

Sollte man sich per SSH auf dem Server befinden und das Problem haben das dort nicht genügend Entropie erzeugt wird, so kann man mittels eines zweiten Terminals mit folgendem Befehl für etwas Action auf dem Server sorgen:

find /var/ /usr /lib /srv -type f -print0 | xargs -0 cat > /dev/null

Sollte dies nicht den nötigen Erfolg zeigen so kann durch die Verwendung der rng-tools Abhilfe geschaffen werden. Dies habe im Beitrag Ausreichende Entropie in virtuellen Maschinen erzeugen beschrieben.

Ist der Schlüssel erzeugt gibt gpg das Ergebnis aus und teilt darin die ID des Schlüssels sowie den Fingerabdruck mit. Beides benötigen wir später noch können es uns daher gleich notieren:

gpg: "Trust-DB" wird überprüft
gpg: 3 marginal-needed, 1 complete-needed, PGP Vertrauensmodell
gpg: Tiefe: 0  gültig:   1  unterschrieben:   0  Vertrauen: 0-, 0q, 0n, 0m, 0f, 1u
pub   4096R/2F58D75F 2013-08-09
  Schl.-Fingerabdruck = 14B7 D86B 90FB 41AA 654B  8F0A F027 ED08 2F58 D75F
uid                  Robert Hennig (Used for php and javascript based encryption with pecl gnupg and javascript encryption) <info@robcoding.de>
sub   4096R/40AA7939 2013-08-09

ID und Fingerabruck befinden sich in den zwei Zeilen

pub   4096R/2F58D75F 2013-08-09
  Schl.-Fingerabdruck = 14B7 D86B 90FB 41AA 654B  8F0A F027 ED08 2F58 D75F

Und lauten somit:
ID = 2F58D75F
Fingerabdruck = 14B7 D86B 90FB 41AA 654B 8F0A F027 ED08 2F58 D75F

Um nicht auf das Home-Verzeichnis des Benutzers angewiesen zu sein exportieren wir sowohl den erzeugten privaten Schlüssel als auch den öffentlichen Schlüssel in jeweilige Dateien:

gpg --export-secret-key -a > secret.key
gpg -a --export 09C9929C > key.pub

Installation des PECL-Moduls gnupg

Das PECL-Modul gnupg wird mittels pecl wie folgt installiert:

pecl install gnupg

Sollten im Betriebssystem noch Pakete fehlen, diese einfach mit Yum nachinstallieren.

Ist die Installation abgeschlossen muss das Modul noch in PHP einigebunden werden. Hierzu habe ich die Datei /etc/php.d/gnupg.ini angelegt und wie folgt befüllt:

; Enable the gnupg extension
extension=gnupg.so

Danach den Neustart von PHP-FPM nicht vergessen.
Ob die Installation gelungen ist lässt sich überprüfen in dem man in der Liste der PHP-Module nach gnupg sucht. An der Kommandozeile geht dies wie folgt:

php -m

Erstellen des benötigten JavaScript- und PHP-Codes

Um PHP-Verschlüsselung mittels JavaScript durchführen zu können verwende ich die von Dr. Herbert Hanewinkel entwickelten JavaScript Bibliotheken OpenPGP Message Encryption in JavaScript. Der Einfachheit halber in einer Version von Oliver Sperke der diese leicht angepasst hat, so das sie mittels google closure compiliert und zusammengefasst werden konnten. Die dabei entstandene JavaScript-Datei gnupg.js steht unter der WTFPL.

Der Ablauf ist so, das mittels PHP eine Webseite ausgegeben wird welche das gewünschte Formular enthält sowie den benötigten Javascript-Code. Der Public-Key zur Verschlüsselung wird hierbei als Base64 encodierter String in die JavaScript-Funktion geschrieben.

Zuerst erstelle ich das benötigte Formular:

<form method="post" onsubmit="encrypt_form(this)">
    <label for="user">Benutzer</label>
    <input id="user" name="user" type="text" value="name@domain.com" /><br />
    <label for="pass">Passwort</label>
    <input id="pass" name="pass" type="password" value="test123" />
    <input type="submit" value="Login" />
</form>


Dieses Formular definiert zwei Eingabefelder für Benutzername und Passwort sowie einen Knopf zum Absenden des Formulars. Die einzige Besondertheit stellt das Fehlen eines ‚action‘ tags dar, der durch einen onSubmit handler ersetzt wurde.

Die durch den onSubmit handler aufgerufene JavaScript-Funktion encrypt_form wird wie folgt realisiert:

<script type="text/javascript" src="gnupg.js"></script>
<script type="text/javascript">
function encrypt_form(form) {
	var fields=form.getElementsByTagName("input");
	var data = '';
	for(i=0;i<fields.length;i++)
	{
		if(fields[i].name!='')
		{
			data+=fields[i].name+'='+encodeURIComponent(fields[i].value)+"&";
			fields[i].value="";
		}
	}
	var pu=new getPublicKey(r2s("<?php echo base64_encode(file_get_contents('./key.pub')); ?>"));
	var gpg=document.createElement('input');
	gpg.setAttribute('name', 'gpg');
	gpg.setAttribute('type', 'hidden');
	gpg.setAttribute('value', doEncrypt(pu.keyid,0,pu.pkey.replace(/\n/g,''),data));
	form.appendChild(gpg);
}
</script>


Die JavaScript-Funktion ermittelt die in das Formular eingegebenen Werte, verschlüsselt sie, und fügt dem Formular ein verstecktes Feld hinzu welches die Daten in verschlüsselter Form enthält.

Um darauf zu reagieren wenn die Formulardaten auf dem Server eingehen kommt noch folgender Code hinzu:

if(isset($_POST) && !empty($_POST)) {
    // Gnupg initialisieren
    $gnupg = new gnupg();
    if(!$gnupg) {
        die("Initialisierung von gnupg ist fehlgeschlagen.");
    }
    
    // Error mode setzen, so das Fehler als Exception ausgegeben werden
    $gnupg->seterrormode(gnupg::ERROR_EXCEPTION);
    $gnupg->seterrormode(gnupg::ERROR_WARNING);
    
    // Den Fingerabdruck des zu verwendenden Schlüssels (ohne Leerzeichen) sowie
    // das Passwort angeben
    $gnupg->adddecryptkey('F4703CE8D704A7989813CE1F179FCED28E8B919E', null);
    
    echo "<h3>Verschlüsselt:</h3>".PHP_EOL;
    echo $_POST['gpg'];
    // Decrypt the contents into the $post variable
    parse_str(trim($gnupg->decrypt($_POST['gpg'])), $post);
    
    echo '<h3>Entschlüsselt</h3>'.PHP_EOL;
    print_r($post);
}


Wie man erkennen kann überprüft dieser ob Daten per Post-Request übergeben wurden. Ist dies der Fall wird der zu verwendende Schlüssel definiert und im Anschluss die Daten entschlüsselt. Als Ausgabe erhält man einmal die verschlüsselten Daten und einmal die Daten nach der Entschlüsselung.

Ausreichende Entropie in virtuellen Maschinen erzeugen

In der Zeit von Cloud-Computing und Virtualisierung entsteht immer wieder das Problem das im System nicht in ausreichendem Maß Entropie erzeugt wird. Dies wird dann zum Problem wenn z.Bsp. ein PGP-Schlüssel erzeugt werden soll. Aber auch andere Software welche in irgendeiner Form Schlüssel basierend auf Zufallszahlen erzeugt wird langsamer reagieren oder gar die Funktion einstellen.

Eine Möglichkeit für Abhilfe zu sorgen sind die rng-tools. Diese füttern Zufallsdaten aus der Hardware in das Kerneleigene Device.

Die Einrichtung und Verwendung der rng-tools schildere ich hier am Beispiel einer auf Vmware-Fusion basierenden virtuellen Maschine mit CentOS 6.4 64 bit.

Installation rng-tools

Da die rng-tools nicht im Standard-Repository enthalten sind muss zuerst ein passendes Repository installiert werden. Ich verwende hierzu das RPMforge bzw. RepoForge Repository für welches im CentOS-Wiki eine gute Installationsnleitung existiert.

Nun können mittels:

yum install rng-tools


die notwendigen Pakete installiert werden.

rng-tools als Dienst

Soll die Entropie dauerhaft erhöht werden so sollte man die rng-tools als Dienst im Hintergrund laufen haben. Praktischerweise wird während der Installation bereits das passende Start-Skript angelegt (/etc/init.d/rngd). Allerdings muss die Konfiguration noch angepasst werden.

Hierzu in der Datei /etc/sysconfig/rngd den Parameter EXTRAOPTIONS wie folgt befüllen:

EXTRAOPTIONS="-r /dev/urandom"

Danach kann der Dienst mittels chkconfig konfiguriert werden um beim Systemstart ebenfalls gestartet zu werden:

chkconfig rngd on

rng-tools temporär starten

Wird nur kurzfristig eine höhere Entropie benötigt so können die rng-tools an der Kommandozeile wie folgt verwendet werden:

rngd -r /dev/urandom -o /dev/random -f -t 1

Überprüfen des Entropie-Wertes

Unter '/proc/sys/kernel/random/entropy_avail' kann der jeweilige Entropiewert eingesehen werden. Zum Beispiel wie folgt:

cat /proc/sys/kernel/random/entropy_avail

Auf dem laufenden bleiben kann man in dem man mittels des Programms watch den jeweils aktuellen Wert anzeigen lässt:

watch -n 1 cat /proc/sys/kernel/random/entropy_avail

SplClassLoader

spl_autoload_extensions & spl_autoload_register

Seit PHP Version 5.1.2 gibt es zwei neue Funktionen die einem dabei  helfen sollen Dateien automatisch zu laden: spl_autoload_extensions und spl_autoload_register.

In der Theorie ersparen einem diese beiden Funktionen das Schreiben einer eigenen Autoload-Funktion da sie nicht nur mit Namensräumen (Namespaces) zurecht komment sondern auch noch schneller sind als selbst geschriebene Funktionen ähnlicher Art. So sollte ein Zweizeiler ausreichen:

spl_autoload_extensions('.php');
spl_autoload_register();

Leider bleibt dies graue Theorie, da diese Funktionen fehlerhaft sind. So sind diese bisher nur beschränkt einsetzbar, da alle Klassennamen und Namensräume zur Suche in Kleinbuchstaben umgewandelt werden (PHP-Bug 53065). Schlimmer noch ist das diese Funktionen bisher nur unter Windows funktionieren (PHP-Bug 51991).

SplClassLoader

Da die oben beschriebenen Funktionen eben fehlerhaft sind und darüberhinaus noch nicht alles wünschenswerte abdecken gibt es für diese schon längst überfällige Funktionalität einen RFC bei PHP zur Umsetzung. Dieser enthält auch bereits eine Beispiel-Implementation als PHP-Klasse. Dieser Standard ist auch Teil der von der FIG (PHP Framework Interop Group) definierten Standards und firmiert dort unter Autoloading Standard PSR-0.

Ich verwende allerdings im Moment noch eine andere Implementation von Jonathan H. Wage und anderen welche auf dem gleichen RFC basiert und bei mir einwandfrei funktioniert.
Er hat diese hier zur verfügung gestellt.

Verwenden lässt sich diese Klasse ganz einfach:

// Gewünschten Pfad zum Include-Pfad hinzufügen
set_include_path("<mein Pfad>".PATH_SEPARATOR.get_include_path());
// SplClassLoader instanziieren und registrieren
$meinSplClassLoader = new SplClassLoader();
$meinSplClassLoader->register(true);

Und schon kann man Komfortabel mittels Autoload und Namensräumen arbeiten:

namespace Test;
use Produktiv\Klasse1;

$k = new Klasse1();