[ Impressum ]

LiveCode Server für HTTPS-Server mit Node.js

www.Rozek.de > LiveCode Server > HTTPS
In einer anderen Notiz wurde erklärt, wie man sich mit Node.js [1] und Express.js [2] unter Verwendung der SimpleCGI "Middleware" einen eigenen HTTP-Server bauen kann, der CGI-Anforderungen erkennt und an "LiveCode Server" [3] als CGI-Prozessor weiterleitet.

Nun sind solche CGI-Skripte bisweilen aber sicherheitskritisch, weswegen sie nicht immer offen zugänglich sein sollten. Deshalb wird hier erläutert, wie man mit derselben Technik einen Server erstellt, der auch HTTPS sowie eine Authentifizierung der anfragenden Benutzer unterstützt.

Übersicht

  • Ein Zertifikat als Grundlage für HTTPS
  • Ein HTTPS-Server mit Node.js
  • Authentifizierung von Benutzern

Ein Zertifikat als Grundlage für HTTPS

Wer einen HTTPS-Server einrichten möchte, benötigt zunächst einmal ein sogenanntes TLS/SSL-Zertifikat. Ohne allzu sehr in die Details gehen zu wollen: ein solches Zertifikat
  1. identifiziert den (zum Zertifikat gehörenden) Server - damit der Browser weiß, dass er auch wirklich den gewünschten Server vor sich hat;
  2. enthält einen öffentlichen Schlüssel, der bei der Verbindungsaufnahme eine Rolle spielt.
Zu jedem Zertifikat gehört auch ein Schlüsselpaar (bestehend aus einem öffentlichen und einem nicht-öffentlichen ("privaten") Schlüssel), das beim Verbindungsaufbau zwischen Browser und Server eine wesentliche Rolle spielt (in dieser Phase wird u.a. derjenige Schlüssel ausgehandelt, mit dem die Kommunikation über die aktuelle Verbindung tatsächlich verschlüsselt wird).

Solange es sich bei Ihrem HTTPS-Server um einen privaten Server handelt, können Sie sich ein solches (sogenanntes "selbst-signiertes") Zertifikat problemlos selbst erstellen.

Da wirklich jedermann ein solches Zertifikat generieren kann, wird man bei einem öffentlichen Server jedoch darauf achten, dass das zum Server gehörige Zertifikat von einer offiziellen Stelle ausgestellt bzw. zumindest signiert wurde. Alternativ können Sie das von Ihnen erstellte "selbst-signierte" Zertifikat aber auch vorab an alle "Kunden" Ihres Servers verteilen (die es den jeweiligen Browsern bzw. allgemeinen Clients bekannt geben müssen), wenn Sie diesen Personenkreis von vornherein kennen. Ohne eine solche vorbereitende Maßnahme ist das Zertifikat wertlos bzw. sogar gefährlich, weil es eine in der Realität gar nicht vorhandene Sicherheit vortäuscht.

Erstellen eines "selbst-signierten" Zertifikates

Das grundlegende Verfahren zur Erstellung eines selbst-signierten Zertifikates wird in der entsprechenden Node.js-Dokumentation sehr schön beschrieben:
  1. wechseln Sie in das Verzeichnis mit dem WebServer für "LiveCode Server" (auf diese Weise liegen die nachstehend zu erstellenden Dateien gleich an der richtigen Stelle)
     
  2. erzeugen Sie einen privaten Schlüssel für das spätere Zertifikat
     
    openssl genrsa -out WebServer-Key.pem 2048
     
  3. erstellen Sie ein selbst-signiertes Zertifikat für Ihren Server
     
    openssl req -new -x509 -days 365 -subj '/CN=127.0.0.1' -key WebServer-Key.pem -out WebServer-Certificate.pem
     
    das so erzeugte Zertifikat
    • gehört zum lokalen Rechner (wegen CN=127.0.0.1) und
    • gilt ein Jahr lang (wegen -days 365)
       
  4. kontrollieren Sie den Inhalt Ihres Zertifikates mit
     
    openssl x509 -text -in WebServer-Certificate.pem
Das zuletzt genannte Kommando liefert eine etwas längliche Ausgabe in folgendem Format:

Certificate: Data:
Version: 3 (0x2)
Serial Number: 17894222022604898032 (0xf8550c1f35563ef0)
Signature Algorithm: sha1WithRSAEncryption
Issuer: CN=127.0.0.1
Validity
Not Before: Feb 8 04:53:25 2014 GMT
Not After : Feb 8 04:53:25 2015 GMT
Subject: CN=127.0.0.1
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:be:01:f9:bf:5c:66:3a:b0:58:af:69:16:b5:f6:
1e:1f:29:d7:65:62:0b:ac:21:2b:c3:71:42:d7:8c:
90:4d:3d:b4:4b:55:f9:6a:43:e9:db:17:bd:eb:4c:
1d:7e:a8:ab:a0:fc:53:0a:e6:b0:7b:25:7e:ad:fd:
ec:85:0b:c1:e9:c1:0d:20:d8:9b:be:43:fc:e5:81:
09:e0:b9:d6:9b:e5:ea:1a:56:9a:fc:bb:27:35:23:
b8:7c:6c:a9:57:17:a9:e1:60:2e:39:83:5a:fb:e6:
72:bd:43:99:bc:51:95:72:9c:81:55:48:0a:4f:82:
ac:1a:7f:12:30:fe:46:47:21:0d:3c:c7:95:65:39:
d6:da:43:3f:b8:5b:c5:ed:c6:31:2b:14:42:5d:62:
0d:91:bc:8b:5b:45:09:6b:c7:2c:1e:5e:f3:b4:91:
5a:3e:ec:89:a2:de:b8:0d:81:2b:1c:c4:f0:aa:89:
ca:bd:79:a0:e1:93:ad:0e:a0:42:d6:df:6c:00:06:
4b:01:2d:4e:92:57:8d:80:00:65:6a:e6:51:15:4e:
3e:b3:3f:26:14:9f:d4:9e:34:6c:00:62:cb:c0:78:
e4:7e:8a:e3:93:40:78:d4:d7:74:57:fb:bc:ce:1d:
18:76:24:b7:f4:a4:68:6e:4b:1f:7f:00:60:37:18:
1b:4f
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
3D:D4:5F:5E:90:E5:A9:8A:D3:A2:BB:96:A6:98:E0:BD:7C:F8:36:CF
X509v3 Authority Key Identifier:
keyid:3D:D4:5F:5E:90:E5:A9:8A:D3:A2:BB:96:A6:98:E0:BD:7C:F8:36:CF


X509v3 Basic Constraints:
CA:TRUE
Signature Algorithm: sha1WithRSAEncryption
23:4c:18:6a:6b:9a:e0:88:61:9f:2c:77:48:5b:26:6d:c5:c8:
b1:b7:0d:1b:ec:dd:29:a5:26:27:aa:58:f8:25:e4:e6:e4:16:
ba:b1:df:b6:8a:43:d3:37:ec:fc:04:4d:9e:0c:dd:92:54:80:
17:34:e1:4d:61:49:a0:b6:05:5c:27:af:93:b7:38:97:12:d6:
00:82:dd:45:26:cd:54:d0:2f:99:cc:08:a6:58:86:8a:3f:6b:
a0:73:d9:3a:3d:4b:cb:4d:47:8a:fb:31:b3:46:08:45:0c:35:
80:8a:5f:b4:6f:2e:f3:82:d6:c7:63:29:2e:47:59:1e:af:ac:
99:cc:92:fb:6e:53:f1:6a:0d:29:a5:60:b4:1b:06:0c:45:4a:
f4:99:ae:11:a7:9b:6f:97:de:38:11:91:e0:36:ce:20:24:66:
b3:1b:1b:1e:78:8f:51:5a:d6:1f:96:17:85:27:8b:c3:b3:2e:
b0:33:08:17:e9:e2:fd:74:9f:8c:99:61:d8:b1:3a:79:2f:34:
b5:40:93:29:8f:1f:a6:58:88:4f:bd:96:9e:38:df:9a:8b:30:
e2:84:5e:5a:0f:67:5f:82:e6:ab:5d:8a:57:dc:3d:78:c0:ee:
54:15:b5:31:dc:c3:6b:3d:99:9f:3c:89:0a:4e:47:3f:54:2a:
c8:4f:09:38
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIJAPhVDB81Vj7wMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV
BAMMCTEyNy4wLjAuMTAeFw0xNDAyMDgwNDUzMjVaFw0xNTAyMDgwNDUzMjVaMBQx
EjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL4B+b9cZjqwWK9pFrX2Hh8p12ViC6whK8NxQteMkE09tEtV+WpD6dsXvetM
HX6oq6D8UwrmsHslfq397IULwenBDSDYm75D/OWBCeC51pvl6hpWmvy7JzUjuHxs
qVcXqeFgLjmDWvvmcr1DmbxRlXKcgVVICk+CrBp/EjD+RkchDTzHlWU51tpDP7hb
xe3GMSsUQl1iDZG8i1tFCWvHLB5e87SRWj7siaLeuA2BKxzE8KqJyr15oOGTrQ6g
QtbfbAAGSwEtTpJXjYAAZWrmURVOPrM/JhSf1J40bABiy8B45H6K45NAeNTXdFf7
vM4dGHYkt/SkaG5LH38AYDcYG08CAwEAAaNQME4wHQYDVR0OBBYEFD3UX16Q5amK
06K7lqaY4L18+DbPMB8GA1UdIwQYMBaAFD3UX16Q5amK06K7lqaY4L18+DbPMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBACNMGGprmuCIYZ8sd0hbJm3F
yLG3DRvs3SmlJieqWPgl5ObkFrqx37aKQ9M37PwETZ4M3ZJUgBc04U1hSaC2BVwn
r5O3OJcS1gCC3UUmzVTQL5nMCKZYhoo/a6Bz2To9S8tNR4r7MbNGCEUMNYCKX7Rv
LvOC1sdjKS5HWR6vrJnMkvtuU/FqDSmlYLQbBgxFSvSZrhGnm2+X3jgRkeA2ziAk
ZrMbGx54j1Fa1h+WF4Uni8OzLrAzCBfp4v10n4yZYdixOnkvNLVAkymPH6ZYiE+9
lp4435qLMOKEXloPZ1+C5qtdilfcPXjA7lQVtTHcw2s9mZ88iQpORz9UKshPCTg=
-----END CERTIFICATE-----

Die (für unsere Zwecke) wichtigsten Informationen stehen jedoch gleich am Anfang dieser Ausgabe:

Issuer: CN=127.0.0.1
Validity
Not Before: Feb 8 04:53:25 2014 GMT
Not After : Feb 8 04:53:25 2015 GMT
Subject: CN=127.0.0.1

Der "Issuer" deutet an, dass der Schlüssel vom eigenen Rechner ausgestellt wurde. Die "Validity" beschreibt den Gültigkeitszeitraum für das Zertifikat und das "Subject" enthält die IP-Adresse desjenigen Rechners, für den das Zertifikat ausgestellt wurde.

Damit wären alle Vorbereitungen getroffen und wir können uns an den eigentlichen Server heranwagen.

Erstellen eines "offiziellen" Zertifikates

Wenn Sie ein Zertifikat benötigen, welches von einer offiziellen Stelle ausgestellt bzw. signiert wurde, wenden Sie sich bitte an den Service Provider, der Ihnen Ihren Server bereitstellt.

Ein HTTPS-Server mit Node.js

Der Quelltext für einen HTTP-Server mit "LiveCode Server" als CGI-Prozessor wurde bereits gezeigt. Eine Variante, die auch HTTPS unterstützt, sieht sehr ähnlich aus:

#!/usr/bin/env node
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');

var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Speichern Sie dieses Skript wie zuvor in der Datei WebServer und starten Sie den Server neu. Als Funktionstest öffnen Sie mit Ihrem Browser die Seite

https://127.0.0.1:8081/00_SmokeTest.lc

Achten Sie dabei insbesondere auf das neue Protokoll (https) und die neue Port-Nummer (8081)!

Ihr Browser sollte Sie bei der ersten Kontaktaufnahme mit Ihrem Server in irgendeiner Form darauf hinweisen, dass er die Identität des Rechners mit der IP-Adresse 127.0.0.1 nicht verifizieren kann - dies ist ein klares Indiz für ein nicht offiziell signiertes Zertifikat. Im allgemeinen Fall sollten Sie jetzt erst einmal sehr genau prüfen, ob Sie dem Server wirklich vertrauen wollen - in unserem Testfall ist das Zertifikat jedoch vertrauenswürdig und darf akzeptiert werden.

Die eigentliche Ausgabe des SmokeTest ist dieselbe wie auch unter HTTP.

Wenn Sie sich als nächstes mit

https://127.0.0.1:8081/01_EnvironmentVariables.lc

die CGI-Umgebungsvariablen ansehen, werden Sie entdecken, dass die Variable SERVER_PROTOCOL jetzt HTTPS/1.1 anzeigt - auf diese Weise können Sie in einem "LiveCode Server"-Skript sichere Verbindungen erkennen.

Authentifizierung von Benutzern

HTTPS ist die Grundlage für einen abgesicherten Zugang zu vertraulichen oder gar potentiell gefährlichen Dateien - noch kann aber weiterhin jedermann frei auf den Server zugreifen. Was jetzt noch fehlt, ist die Authentifizierung der anfragenden Benutzer.

Beschränkt man sich auf einen einzigen Login, ist die Erweiterung des Servers denkbar einfach:

#!/usr/bin/env node
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');

var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, express.basicAuth(function(UserName,Password){
return (UserName === 'user') && (Password === 'password');
}), SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Auch hier gilt wieder: bitte speichern Sie dieses Skript in der Datei WebServer und starten Sie den Server neu.

Wenn Sie jetzt erneut auf eine Seite (mit einem "LiveCode Server"-Skript) zugreifen wollen, werden Sie von Ihrem Browser nach Benutzername und Passwort gefragt - und erst nach Eingabe von user und password erhalten Sie Zugriff auf das gewünschte Skript.

Natürlich hat dieser Server eine Reihe von Nachteilen:
  1. es wird nur ein einziger Benutzer unterstützt
    dieses Problem kann z.B. durch Erweitern des Prüfausdruckes umgangen werden;
  2. die Liste der Benutzer ist fest im Server verankert
    dieses Problem lässt sich lösen, indem man die Liste der zugelassenen Benutzer aus einer Datei einliest;
  3. die Passworte liegen im Klartext vor
    sicherheitstechnisch ist dieses Manko eine Katastrophe - hier sollte sofort z.B. auf MD5 Hashes umgestellt werden
Das folgende Skript löst alle diese Probleme und liest die Benutzerdaten aus einer Datei namens WebServer-UserList.txt:

#!/usr/bin/env node
var crypto = require('crypto');
var express = require('express');
var fs = require('fs');
var http = require('http');
var https = require('https');
var SimpleCGI = require('simplecgi');
var oneDay = 24*60*60*1000;

var WebServer = express();
WebServer.use(express.compress());
WebServer.use(express.staticCache());

WebServer.all(/^.+[.]lc$/, express.basicAuth(function(UserName,Password, next){
UserName = UserName.toLowerCase();

fs.readFile(
__dirname + '/WebServer-UserList.txt', function (Error, FileContent) {
if (Error) {
console.log('Internal Error during Authentication: ', Error);
return next(null, false);
};

var LineList = FileContent.toString().split('\n');
for (var i = 0; i < LineList.length; i++) {
if (LineList[i].replace(/ *:.*$/,'').replace(/^ */,'').toLowerCase() === UserName) {
var PasswordHash = crypto.createHash('md5').update(Password).digest('hex');
return next(null, (LineList[i].replace(/^.*: */,'').replace(/ *$/,'') === PasswordHash));
};
};

return next(null, false);
}
);
}), SimpleCGI(
'/usr/local/bin/livecode-server', __dirname + '/www', /^.+[.]lc$/
));

WebServer.use(express.static(__dirname + '/www', { maxAge:oneDay }));

WebServer.use(express.errorHandler());
http.createServer(WebServer).listen(8080); // actually starts HTTP server
https.createServer({ // dto. for HTTPS
key: fs.readFileSync(__dirname + '/WebServer-Key.pem'),
cert: fs.readFileSync(__dirname + '/WebServer-Certificate.pem')
}, WebServer).listen(8081);

Selbst dieses Skript ist noch recht übersichtlich. Speichern Sie es wie üblich in der Datei WebServer und starten Sie den Server neu.

Sie können es zwar bereits ausprobieren (Sie sollten es sogar!), da die Datei WebServer-UserList.txt bislang aber noch fehlt, werden Sie sich nicht anmelden können, sondern bei jedem Versuch eine entsprechende Fehlermeldung erhalten (diese wird in das Terminalfenster mit dem Server-Kommando ausgegeben).

Die Datei WebServer-UserList.txt muss je einen Benutzer pro Zeile enthalten, und zwar in folgendem Format:

<username>:<password-hash>

D.h., in jeder Zeile stehen
  1. der Benutzername (die Groß-/Kleinschreibung ist dabei irrelevant),
  2. ein Doppelpunkt als Trennzeichen sowie
  3. der MD5-Hash des Benutzerpasswortes
Solche Zeilen lassen sich problemlos mithilfe der folgenden kleinen LiveCode-Funktion erzeugen:
function PasswordEntry UserName, UserPass
local Outcome
get binaryDecode("H*", md5digest(UserPass), Outcome)
return lower(UserName) & ":" & Outcome
end PasswordEntry

Der Aufruf

put PasswordEntry("user","password")

aus der "Message Box" der LiveCode Entwicklungsumgebung heraus liefert z.B. die Ausgabe

user:5f4dcc3b5aa765d61d8327deb882cf99

Wenn Sie diese Zeile in eine Datei namens WebServer-UserList.txt (im selben Verzeichnis wie Ihr WebServer) eintragen, können Sie sich wieder bei Ihrem Server anmelden.

Die Anmeldung kann dabei sowohl über Ihren Browser geschehen (der Ihnen ein entsprechendes Dialogfenster anbietet) oder über eine URL der Form

https://user:password@127.0.0.1:8081/01_EnvironmentVariables.lc

In der Anzeige der CGI-Umgebungsvariablen werden Sie erkennen, dass neben der Variablen SERVER_PROTOCOL (die immer noch HTTPS/1.1 enthält) jetzt auch die Variable REMOTE_USER gesetzt ist: sie enthält den Namen des angemeldeten Benutzers (in unserem Fall also "user") - auf diese Weise können Sie in einem "LiveCode Server"-Skript sowohl sichere Verbindungen als auch erfolgreich angemeldete Benutzer erkennen.

Nota bene: das Berechnen der Passwort-Hashes mittels LiveCode ist zwar komfortabel, aber alles andere als sicher [4] - wem die Sicherheit seiner Benutzer am Herzen liegt, sollte deshalb besser auf PBKDF2 umsteigen (auch wenn die Hashes dann ebenfalls mit Node.js gebildet werden müssen).

                       
Viel Spaß mit "LiveCode Server" und diesem HTTPS-Server! Creative Commons Lizenzvertrag

Literaturhinweise

[1]
Joyent Inc.
node.js
Node.js ist zunächst einmal eine Plattform für in JavaScript geschriebene und dennoch äußerst leistungsfähige Netzwerk-Anwendungen. In Verbindung mit weiteren Technologien (wie z.B. Node-WebKit) kann Node.js aber auch für mehr als nur HTTP-Server eingesetzt werden.
[2]
Tj Holowaychuk
Express - node.js web application framework
Express ist ein schlankes Web Application Framework für Node.js. Dank seines "Baukastensystems" ermöglicht Express eine zügige Entwicklung von HTTP-Servern auf Basis von Node.js.
[3]
(RunRev Ltd.)
LiveCode | LiveCode Server Guide
Der LiveCode Server ist ein Interpreter für LiveCode-Skripte, der von der Kommandozeile aus gestartet wird (ohne grafische Benutzeroberfläche auskommt) und vor allem als CGI-Prozessor gedacht ist (auf diese Weise können Web-Seiten mit LiveCode bearbeitet werden - man muß also nicht mehr unbedingt PHP lernen).
[4]
(Defuse Security)
Secure Salted Password Hashing - How to do it Properly
Wer einen Server betreibt, der von seinen Benutzern eine Anmeldung verlangt, muss diese Sicherheits-relevanten Daten irgendwie persistieren. Diese Web-Seite beschreibt die potentiellen Probleme und bietet entsprechende Lösungen.