18.5.2019 - TLS/SSL Verschlüsselung

Https (hypertext transfer protocol secure), TLS (transport layer security) und SSL (secure sockets layer) seien hier synonym verwendet, obwohl es Unterschiede gibt: SSL war die ursprüngliche Implementation, TLS ist der verbesserte und heute aktuelle Nachfolger (in Version 1.3), und Https ist einfach eine Bezeichnung für die sichere Version des Http Protokolls. Man meint damit aber außerhalb der Spezialistenkreise immer dasselbe: Einerseits eine sichere Identifikation des Servers, andererseits eine Verschlüsselung der Daten, die durch die Verbindung ausgetauscht werden.

Prinzip

Das Konzept basiert auf dem Prinzip der asymmetrischen Verschlüsselung, welches ursprünglich 1977 von Rivest, Shamir und Adleman entwickelt und nach ihren Anfangsbuchstaben RSA-Verfahren genannt wurde. Dabei hat man einen privaten Schlüssel, den man geheim hält und einen öffentlichen Schlüssel, der allgemein bekannt sein darf bzw. soll. Wenn nun Alphons eine Nachricht an Berta senden will, dann verschlüsselt er diese mit Bertas öffentlichem Schlüssel. Berta wiederum kann die Nachricht mit ihrem privaten Schlüssel (und nur mit diesem!) entziffern. Dadurch kann Alphons, ohne dass er den privaten Schlüssel von Berta kennen muss, oder ohne dass man, wie bei symmetrischen Verschlüsselungsverfahren üblich, einen Schlüssel “irgendwie” austauschen muss, sicher verschlüsselte Nachrichten senden. Und wenn Berta antworten will, verwendet sie einfach Alphons’ öffentlichen Schlüssel und dieser kann mit seinem privaten Schlüssel entschlüsseln.

Eine zweite damit zusammenhängende interessante Möglichkeit ist folgende: Alphons kann seine Nachricht vor dem Verschlüsseln “signieren”. Das bedeutet, er erstellt eine Prüfsumme über die Nachricht und verschlüsselt diese Prüfsumme mit seinem eigenen privaten Schlüssel. Dann verschlüsselt er die Nachricht zusammen mit der Signatur mit Bertas öffentlichem Schlüssel.

Berta entschlüsselt die Nachricht zunächst mit ihrem eigenen privaten Schlüssel und entschlüsselt anschliessend die Signatur mit Alphons’ öffentlichem Schlüssel. Dann bildet sie selber eine Prüfsumme über die Nachricht und kontrolliert, ob diese Prüfsumme dieselbe ist wie das, was in der Signatur steht. Wenn ja, ist die Nachricht garantiert von Alphons und wurde garantiert auch nicht verändert - zumindest wenn sicher ist, dass der zur Prüfung verwendete öffentliche Schlüssel wirklich von Alphons stammt.

Genau hier liegt eine Angreifbarkeit dieses Verfahrens: Wenn Cäsar die Nachrichten zwischen Alphons und Berta abhören will, dann kann er eine “man in the middle attack” versuchen: Er generiert ein eigenes Schlüsselpaar, und jubelt beiden Partnern jeweils seinen eigenen öffentlichen Schlüssel als den des eigentlichen Partners unter. Dann kann er Alphons’ Nachricht entschlüsseln, neu signieren und mit Bertas echtem öffentlichen Schlüssel an diese weiterleiten. Sie “meint” Alphons’ öffentlichen Schlüssel zu haben, hat aber den von Cäsar und merkt so nicht, dass die Nachrichten abgehört oder sogar gefälscht werden.

Man kann diese Gefahr begrenzen, indem man die öffentlichen Schlüssel über einen anderen Kanal selber austauscht, oder indem man eine Prüfsumme (einen sogenannten ‘key fingerprint’) über den öffentlichen Schlüssel so publiziert, dass jeder die Echtheit nachprüfen kann. Das bedeutet aber einen zusätzlichen Schritt, den jeder Anwender machen müsste, was in der Praxis unrealistisch ist - Sicherheit muss automatisch erfolgen, sonst funktioniert sie nicht auf Dauer. Daher verwendet man im Internet ein automatisiertes Authentisierungssystem, das ich im nächsten Abschnitt zeigen werde.

Implementation der TLS/SSL Verschlüsselung im Internet

Wenn ein Browser eine verschlüsselte Verbindung mit einem Webserver aufnehmen will, dann “spricht” er ihn mit https:// statt http:// an und wählt standardmässig den Port 443 statt 80. Zunächst verlangt er vom Server dessen öffentlichen Schlüssel, dann generiert er einen “session key”, einen zufälligen Schlüssel für ein symmetrisches Verfahren, verschlüsselt diesen Schlüssel mit dem eben erhaltenen öffentlichen Schlüssel des Servers und schickt ihn zurück. Von da an kennen beide den session key, und die weitere Kommunikation wird mit diesem session key symmetrisch verschlüsselt. Dies deshalb, weil symmetrische Verfahren effizienter und schneller sind, als asymmetrische Verfahren - nur der Austausch des Schlüssels ist kritisch und dieses Problem hat die asymmetrische Verschlüsselung ja gelöst.

Ich habe hier bewusst etwas vereinfacht: Client und Server müssen sich über verschiedene andere Dinge, zum Beispiel die zu verwendenden Verschlüsselungsalgorithmen, einigen. Etwas viel Wichtigeres habe ich aber auch weggelassen: Bei oben skizzierter Methode hat der Client keine Chance zu erkennen, wer der Server wirklich ist. Er könnte sich “meineBank.de” nennen, in Wirklichkeit aber zu “boeserhacker.com” gehören. Der öffentliche Schlüssel enthält per se keine nachprüfbare Identität. Dann würde die Kommunikation zwar perfekt verschlüsselt ablaufen, aber leider mit dem falschen Adressaten!

Dieses Problem wird mit Zertifikaten gelöst: Wenn eine vertrauenswürdige Stelle bestätigt, dass der öffentliche Schlüssel wirklich zu “meineBank.de” gehört, dann kann man das so weit glauben, wie man der Zertifizierungsstelle vertraut. Es gibt eine ganze Reihe solcher Zertifizierungsstellen, und die Browser- und Betriebssystemhersteller bemühen sich, deren Vertrauenswürdigkeit auf hohem Niveau zu halten. Ein Zertifikat kann zum Beispiel so aussehen (Sie erhalten es, wenn Sie im Browser bei einer https-Verbindung auf das Symbol links neben der Adresse klicken):

Hier bestätigt also die SwissSign AG, dass die Website zkb.ch der Firma ‘Zürcher Kantonalbank’ gehört, und dass der Server, mit dem ich derzeit verbunden bin, dieses Zertifikat besitzt: Der öffentliche Schlüssel, den diese https-Verbindung verwendet, wurde mit dem privaten Schlüssel von SwissSign signiert, was mein Browser wiederum mit dem öffentlichen Schlüssel von SwissSign prüfen kann (und er tut das auch jedesmal). Aber woher weiss ich, dass der öffentlich Schlüssel von SwissSign wirklich der SwissSign AG gehört? Ganz einfach: Der ist natürlich auch signiert. Von einer “höheren” Zertifizierungsstelle. Auf diese Weise kann man sich durch eine Kette von Zertifikaten (certificate chain) weiter hangeln, bis man ganz oben bei einem “root certificate” angelangt ist. Und diese root-certificates sind fest im Browser gespeichert, können einem also nicht von Bösewichtern untergejubelt werden. Natürlich ist diese Zertifikatskette trotzdem ein Schwachpunkt des Ganzen. Es wurde zum Beispiel bekannt, dass manche Browser Root-Zertifikate eingebaut hatten, die unter Kontrolle der NSA standen, so dass diese jede Verschlüsselung kompromittieren konnte, indem sie sich für irgendeinen Server ausgab und dessen Zertifikat mit der eigenen Zertifizierungsstelle signierte. Aber es ist immer noch die sicherste bekannte Möglichkeit, verschlüsselt und authentisiert zu kommunizieren.

Wichtig ist auch zu wissen, dass die Zertifizierungsstelle nicht etwa die Schlüssel herstellt. Das tut man immer auf dem eigenen Computer, und der private Schlüssel sollte diesen nie verlassen. Man schickt nur den öffentlichen Schlüssel zum Signieren an die Zertifizierungsstelle. Dazu muss man dieser in irgendeiner, je nach Zertifikatstyp mehr oder weniger aufwändigen Form beweisen, dass man wirklich der Inhaber der zu schützenden Website ist. Also auch die Zertifizierungsstelle bekommt den privaten Schlüssel nicht zu Gesicht, kann die Verschlüsselung also nicht ohne Weiteres knacken.

In manchen Fällen genügt dieses Ein-Weg-Vertrauen nicht. Manchmal muss auch der Server genauer wissen, wer der Client ist. Dann kann er auch vom Client ein Zertifikat anfordern und überprüfen. Das ist aber nur selten der Fall. Meistens wird der Client einfach über eine Passwortabfrage authentisiert, welche über die bereits mit TLS verschlüsselte Verbindung erfolgt.

Zertifikat-Sicherheit und Warnungen

Ein Sonderfall ist das sogenannte self signed certificate, also ein selbstsigniertes Zertifikat. Das ist ein Zertifikat, das sich selbst bestätigt, vertrauenswürdig zu sein. Was es natürlich idR nicht ist. Ausser, wenn wir es selbst hergestellt, aber aus praktischen oder finanziellen Gründen nicht von einer offiziellen Zertifizierungsstelle absegnen liessen.

Ein selbstsigniertes Zertifikat lässt sich zum Beispiel so herstellen:

openssl req -newkey rsa:2048 -keyout key.pem -x509 -days 365 -noenc -out cert.pem

Aus obigem sollte klar geworden sein, dass das Zertifikat nichts über die Sicherheit der Verschlüsselung aussagt, sondern nur über die Vertrauenswürdigkeit des öffentlichen Schlüssels. Das ist wichtig, um entsprechende Browser-Warnungen zu verstehen. Diese können je nach Betriebssystem und Browsertyp sehr unterschiedlich sein.

Hier beispielsweise die Warnseite von Safari unter macOS:

Um dennoch weiterzugehen, müssen Sie “Details einblenden” und dann ganz unten auf “öffne diese Website” klicken. Danach kommt noch einmal eine Warnung, die Sie mit “Website besuchen” bestätigen müssen. Und dann will macOS Ihren Fingerabdruck bzw, Ihr Admin-Passwort, um die “Einstellungen für vertrauenswürdige Zertifikate” zu ändern. Immerhin: Danach hat er sich die Einstellung gemerkt und vertraut fortan diesem Zertifikat für diese Site.

Chrome unter macOS zeigt dieses Bild:

Hier müssen Sie auf “Erweitert” klicken und dann auf “weiter zu … (unsicher)”. Dann geht es ohne weitere Nachfrage zur Site. Allerdings merkt sich Chrome die Ausnahme nicht: Bei einer nächsten Verbindung müssen Sie die Warnung erneut abnicken. Ausser, Sie haben unter macOS zuvor mit Safari die Einstellungen geändert: Ein Zertifikat, dem Safari sein Vertrauen ausgesprochen hat, wird auch von Chrome akzeptiert.

Firefox schliesslich begrüsst uns auf dem Mac so:

Hier muss man auf “Erweitert” klicken und dann auf “Risiko akzeptieren und fortfahren”. auch Firefox merkt sich die Erlaubnis zwar nicht von selbst, respektiert aber die persistierte Erlaubnis von Safari (resp. der System-Schlüsselbundverwaltung, die das im Hintergrund erledigt).

Vertrauenswürdige Zertifikate

Wie oben gezeigt, kann man relativ leicht eine verschlüsselte Kommunikation einrichten.

Trotzdem ist es natürlich unschön, wenn wir jedesmal, wenn wir auf unseren eigenen Server zugreifen, den Browser beruhigen und überreden müssen, uns durchzulassen. Um dieses Problem zu umgehen, gibt es verschiedene Möglichkeiten, die allerdings leider sehr verschieden je nach Betriebssystem und Browser sind. Einige Hinweise:

Selbstsigniertem Zertifikat das Vertrauen aussprechen

  • Manche Browser erlauben, Sicherheitsausnahmen dauerhaft zu speichern, und dann für dasselbe Zertifikat keine Warnung mehr auszugeben.

  • Bei manchen Betriebssystemen können Sie manuell das Vertrauen zu einem Zertifikat erklären (bei macOS zum Beispiel mit der Schlüsselverwaltung des Systems)

  • Bei manchen Betriebssystemem können Sie auch manuell Root-Zertifikate installieren, mit denen Sie eigene Zertifikate signieren und damit das Vertrauen erklären können. Bei Ubuntu zum Beispiel müssen Sie dazu nur das eigene root-Zertifikat nach /usr/local/share/ca_certificates kopieren und dann eingeben sudo update-ca-certificates.

  • Etwas ausführlichere Erläuterungen zu diesem Problemkreis finden Sie z.B. hier: https://tarunlalwani.com/post/self-signed-certificates-trusting-them/

Offizielle Zertifikate erwerben/beziehen

Man kann natürlich auch den “offiziellen” Weg gehen. Und der sieht vor, dass man sich die Echtheit eines selbst erstellten Schlüssels von einer derjenigen Stellen zertifizieren lässt, die bei den Browsern bereits als vertrauenswürdig eingebaut sind, oder die selbst von solchen “root-certificates” zertifiziert sind. Bis vor wenigen Jahren musste man dazu in die Tasche greifen: Zertifikate waren nur im Jahresabo zu haben und kosteten je nach Sicherheitsstufe von einer handvoll Dollars bis einigen hundert Dollars pro Jahr. Mit “Sicherheitsstufe” ist dabei gemeint, wie genau die Zertifizierungsstelle die Echtheit überprüft. Der Browser zeigt die Sicherheitsstufe eines Zertifikats durch die Art des Symbols in der Titelzeile an (von einem schlichten grauen Schloss bis zu einem breiten grünen Balken). Wobei noch einmal wiederholt werden muss: Diese Sicherheitsstufe hat nichts mit der Sicherheit der Verschlüsselung zu tun, sondern nur mit der Sicherheit der Identität. In der niedrigsten Stufe muss man nur beweisen, dass man Administratorzugriff auf die Website hat, die man sichern will, in der höchsten Stufe muss man der Zertifizierungsstelle z.B. einen Ausweis oder einen Handelsregisterauszug vorweisen.

Seit einigen Jahren gibt es mit Let’s Encrypt eine Möglichkeit, kostenlos Zertifikate zu beziehen. Man muss dazu nur beweisen, dass man die Website. die man sichern will, manipulieren kann, Das lässt sich ohne weiteres automatisiert machen. Es gibt fertige Docker-Lösungen, die Let’s Encrypt Zertifikate vollautomatisch anfordern, verwalten und regelmässig erneuern können (Let’s Encrypt Zertifikate sind immer nur 3 Monate lang gültig).

4.2.2018 - MacBook pro vs. Tuxedo

In diesem Post vergleiche ich zwei Computer, die man eigentlich nicht vergleichen kann: Ein 2013er MacBook pro 15 ‘’ Retina Display und ein 2018er Tuxedo Notebook mit 14’’ Bildschitm und Ubuntu Linux 16.04.

Anlass dieses Vergleichs war, dass mein auch unterwegs vielgenutztes MacBook sich immer öfter unvermittelt ausschaltete, wenn es etwas mehr Leistung erbringen musste, auch wenn die Akkuanzeige noch bei 60% oder mehr stand. Als Ursache kristallisierte sich schon bald der fast 5 Jahre alte Akku mit fast tausend Ladezyklen heraus. Er hatte offensichtlich nicht nur an Kapazität verloren, sondern er schaffte es auch nicht mehr, dem Betriebssystem rechtzeitig mitzuteilen, wenn sein Saft dem Ende zuging.

Wer Apple kennt, der weiss: Selbermachen ist schwierig. Mit meinen zwei linken Händen wollte ich diesem teuren Teil nicht an die Eingeweide, Also fragte ich den freundlichen Apple Reseller, der einen Kostenvoranschlag von 300.- machte und eine Reparaturzeit von 3 Tagen in Aussicht stellte. Nun bin ich, was Computerfirmen betrifft, ein etwas gebranntes Kind und wusste, dass drei Tage sich in dieser Branche oft als drei Wochen oder mehr entpuppen können, und da ich den Laptop oft benötige, musste ein Ersatz für die Reparaturzeit her.

Ich bin zwar kein Windows-Hasser, aber mit Windows 10 kann ich absolut nichts anfangen. Ein MacBook wiederum ist als Notbehelf einfach zu teuer. Ein Linux-Laptop sollte es sein. Linux selbst auf einem Standard-Laptop zu installieren, ist allerdings immer ein Abenteuer mit ungewissem Ausgang: Oft gehen die Stromsparmechanismen nicht korrekt, der Lüfter dreht in den höchsten Tönen, oder die speziellen Tasten etwa zur Bildschirmhelligkeits-Regelung gehen nicht, oder er schläft beim Zuklappen nicht ein, oder erwacht beim Aufklappen nicht mehr.

Da trifft es sich gut, dass es (mindestens) eine Firma gibt, die sich auf Linux-Computer und Laptops spezialisiert hat: Tuxedo Computer in Deutschland.

Da mir niedriges Gewicht und lange Akkuleistung wichtiger sind, als Spieletauglichkeit, wählte ich das 14 Zoll-Modell aus der Book-BU Serie. Tuxedo Notebooks kann man sehr weitgehend Konfigurieren. Ich entschied mich für eine eher preiswerte Konfiguration mit 16GB RAM, einer 250GB SSD als einzigem Laufwerk und eine Core-i5-CPU.

Die Lieferung erfolgte sehr schnell, nach 4 Tagen war es da. Mitgeliefert hat Tuxedo ein paar Gadgets wie Kugelschreiber und Sticker, sowie einen Zugang für die “Tuxedo-Cloud”, der allerdings bei mir nicht funktionierte. Egal, brauche ich eh nicht. Zeit für einen Vergleich. Vorauszuschicken ist, dass das MacBook 2013 stolze 2700.- gekostet hat, das Tuxedo-Book nun knapp 900.-.

Äusserlichkeiten

Die Eleganz und Verarbeitungsqualität des MacBook sind legendär. Aber das Tuxedo macht gar keine schlechte Figur daneben. Es ist ebenfalls sehr schlank und schick geformt. Dass es aus Plastik ist, bewirkt gegenüber dem Aluminium-Macbook natürlich eine weniger edle Anmutung, dafür ist es leichter. Die Verarbeitungsqualität erhält das Prädikat “brauchbar”. Manche Teile stehen fühlbar ein wenig über. Die Tastatur ist angenehm gross, aber im Gegensatz zu der des Mac nicht beleuchtet. Kleiner Minuspunkt bei schlechten Lichtverhältnissen.

Ich würde sagen: Äusseres: 10:6 für das Macbook.

Schauen, Hören und Fühlen

Der Bildschirm des Macbook ist über jeden Zweifel erhaben - solange das Licht nicht von hintern kommt. Dann kann man es als Rasierspiegel benutzen, aber nicht mehr zum Arbeiten. Der Tuxedo glänzt mit einem matten Display, allerdings nicht mit Retina-Auflösung.

Das Tippgefühl ist beim Mac besser. Beim Tuxedo ist es zwar auch nicht schlecht, die Tasten haben einen angenehmen Hub und deutlichen Druckpunkt, aber mich störte, dass die Leertaste etwas störrisch war und manchmal mit Nachdruck bedient werden wollte. Vermutlich könnte man das sogar als Garantiefall reklamieren, aber so schlimm war es dann doch wieder nicht. Raffiniert finde ich, dass der Deckel so gestaltet ist, das er das Notebook hinten ein wenig anhebt, wenn man ihn aufklappt, so dass die Tastatur ergonomisch optimal leicht geneigt steht.

Worin die beiden sich aber wirklich um Längen unterscheiden, ist das Touchpad. Das des Macbook ist gross, präzise und empfindlich, trotzdem aber praktisch immun gegen ungewollte Bewegungen und Berührungen. In all den Jahren hatte ich so gut wie nie das Bedürfnis, eine Maus anzuschliessen. Beim Tuxedo kam dieses Bedürfnis sehr schnell. Natürlich kann man es mit dem Touchpad bedienen, aber wie gesagt: Hier geht es um Welten, wenn man es mit dem MacBook vergleicht.

Das MacBook hört man fast nie. Wenn man doch einmal den Lüfter hört, dann in einer wenig störenden Tonlage. Beim Tuxedo schaltet sich der etwas höher sirrende Lüfter schon beim Compilieren oder Video schneiden hörbar ein. Allerdings nur in sehr ruhiger Umgebung störend.

Meine Wertung: 10:5 fürs MacBook in dieser Kategorie.

Leistung

Beide Computer fühlen sich subjektiv sehr schnell an. Störende Wartezeiten gibt es nie, Programmstarts sind praktisch augenblicklich. Aber auch objektiv geben sich beide keine Blösse. Obwohl das Tuxedo “nur” einen i5 Prozessor hat, gegenüber dem i7 des Mac (allerdings einer älteren Generation), kann es problemlos mithalten. Als Benchmark zum Beispiel das Compilieren von elexis-3-base:

  • MacBook pro 16GB, SSD, Core i7-3740: 3’ 13”.
  • Tuxedo BU1407 16GB, SSD, Core i5-8250: 2’ 57”

Auch Geekbench sieht die beiden dicht beieinander:

  • MacBook: Single-Core: 3484, Multi-Core: 13484 Punkte.
  • Tuxedo: Single-Core: 3308, Multi-Core: 12632 Punkte.

Hier kann die Wertung nur ausgeglichen sein: 10:10 Punkte.

Batterielaufzeit

Naja, die Batterie des MacBook war ja am Ende. Am Anfang hielt sie rund 10 Stunden durch, und erst nach etwa 3 Jahren bemerkte ich eine gewisse Verschlechterung.

Die Batterie des Tuxedo hielt gut 8 Stunden durch, über Langzeiterfahrungen kann ich naturgemäss noch nichts berichten. Jedenfalls ist die Laufzeit auch für längere Bahnfahrten und Sitzungen gut ausreichend.

10:9 fürs MacBook.

Konfigurierbarkeit

Ein Blick auf die Website zeigt: Bei Tuxedo kann man sich den Laptop bis ins letzte Detail zusammenstellen. Mehrere unterschiedliche Laufwerke, verschiedene Prozessoren usw. Beim Apple gibt es nur eine Hand voll Konfigurationen.

7:10 für Tuxedo

Wartbarkeit und Erweiterbarkeit

Das Macbook musste ich für einen Akkuwechsel zum Händler bringen. Bei Tuxedo ist das Wechseln des Akku eine Sache von zwei Schiebern und einmal Einrasten. Ein Ersatzakku kostet bei Tuxedo weniger als ein Drittel der Reparatur des Mac. Natürlich weiss ich nicht, ob es diesen Akku noch zu kaufen gibt, wenn ich ihn mal brauche. Diese Langzeit-Sicherheit ist bei Apple sicher grösser.

Auch sonst ist der Tuxedo weit offen: Man kann Laufwerke und Speicher problemlos selbst austauschen bzw. erweitern, und man kann für alles günstige Standardteile verwenden.

3:10 für Tuxedo.

Systemintegration und Betriebssystem

Kein Zweifel: Beim Macbook sind Hardware und Betriebssystem optimal aufeinander abgestimmt. Das ist eine grosse Stärke der Apple-Computer. Aber auch Tuxedo ist die Integration recht gut gelungen. Die Laptop-spezifischen Knöpfe tun, was sie sollen, die Stromsparmodi funktionieren, man kann einstellen, dass er beim Zuklappen einschlafen soll, und er erwacht (meistens) auch wieder, wenn man ihn aufklappt. Meistens, nicht immer. Beim Aktualisieren des Betriebssystems nach dem ersten Aufstarten tat er ein wenig zickig, aber dann lief alles rund.

Beide Betriebssysteme sind recht offen. Beim MacBook kann man via Homebrew praktisch alle verfügbaren Linux-Programme laufen lassen, und der Darwin-Systemkern ist ebenso OpenSource wie der Kern von Linux. Apple macht es einem aber in den letzten Versionen ein wenig arg schwer, Fremdsoftware laufen zu lassen, scheint sich also von der Offenheit wegbewegen zu wollen.

Unterm Strich haben beide Konzepte ihre Vor- und Nachteile. Mit Linux kann an “alles” machen, aber man muss manchmal doch lange suchen und googeln, bevor man herausgefunden hat, wie es geht. Standardaufgaben wie Drucker anschliessen, löst Ubuntu Linux inzwischen aber genauso mühelos wie Windows oder MacOS: Anschliessen, geht. Zumindest, wenn es kein Exot ist.

Zusammengefasst schenken sich die Kontrahenten hier nichts, auch wenn ihre Stärken und Schwächen unterschiedlich verteilt sind.

8:8

Fazit

Wenn man einfach nur die Punkte zusammenzählt, liegt Tuxedo mit 58:58 Punkten gleich auf mit dem dreimal so teuren Mac. Das hat es allerdings vor allem seiner leichten Reparierbarkeit zu verdanken. Und natürlich wird man nie alle Bereiche gleich werten.

Und noch ein PS

Meine Befürchtung wegen der Reparaturzeit hat sich übrigens nicht bewahrheitet. Nach knapp einer Woche konnte ich das Macbook wieder abholen, und der Kostenvoranschlag war eingehalten.

28.3.2017 - Garagentor-Fernbedienung, Zweiter Teil

Zum ersten Versuch

Der zweite Entwurf

Nachdem ich sowohl den Finger, als auch den Transistor mit dem Lötkolben verbrannt hatte, stellte sich heraus: Ich bin elektrotechnisch eben doch ein Warmduscher, und so kaufte ich anstelle der Eigenbau-Relaisplatine ein PiFace Digital 2. Das kann man einfach auf den Raspberry draufstecken, und es bietet nicht nur zwei Relais, sondern auch 8 Aus- und 8 Eingänge. Ein wenig Overkill für unser Projekt, aber zumindest einen der Eingänge können wir noch sinnvoll nutzen. Die Relais können bis zu 20V/5A schalten, mehr als genug für den GaragentorTaster, der mit 12V und wenigen mA betrieben wird.

Der Hardware-Aufbau sieht so aus:

  • Eines der PiFace-relais wird parallel zum Garagentor-Taster geschaltet
  • Ein Mikroschalter wird so befestigt, dass er bei geöffnetem Garagentor betätigt wird. Dieser Schalter wird zwischen Inputpin 0 und GND des PiFace geschaltet. Damit “weiss” die Garagentor-Fernbedienung, wenn das Tor offen steht.

Ein weiteres Problem mit dem ersten Versuch war, dass das Einbuchen ins häusliche WLAN zu lange dauert. Wenn man vor der Garage steht, mag man nicht eine Minute warten, bis das Handy Kontakt hat. Also wird der Software-Teil so umgebaut, dass man das Tor übers Internet bedienen kann. Es wird somit, modern ausgedrückt, Teil des IoT.

Damit entwickeln sich eine Reihe zusätzlicher Anforderungen für den Garagentor-Server:

  1. Es muss eine DynDNS-Umleitung eingerichtet werden, damit man die Garage mit einem sprechenden Namen (some.where.ch) anstatt einer kryptischen Internetadresse (85.34.223.12) erreichen kann (welche obendrein bei den meisten Providern fast täglich ändert).
  2. Der Raspberry muss so konfiguriert werden, dass er den DynDNS Provider mit der jeweils aktuellen IP versorgt.
  3. Der häusliche Router muss Zugriffe auf den Garagengtorserver an diesen durchreichen.
  4. Der Kontakt zum Server muss verschlüsselt erfolgen, damit Passwoörter nicht abgehört werden können.
  5. Hackversuche müssen unterbunden werden.
  6. Zugriffe müssen geloggt werden, damit Angriffe erkannt und analysiert werden können.

Somit wird aus dem kleinen Projekt des ersten Teils ein etwas grösseres Projekt, welches auch ganz gut als allgemeines NodeJS/Express-Server Beispiel dienen kann. Ich führe daher im Folgenden alles ein wenig deutlicher aus. Ganz am Ende findet sich dann der komplette Quellcode.

1. Dynamische DNS

Ein Teilnehmer im Internet ist durch seine IP-Adresse eindeutig indentifiziert. Es gibt zwei varianten: Das ältere IPV4, das aus 4 Gruppen von Zahlen zwischen 0 und 255 zusammengesetzt ist, etwa so: 85.198.16.82. Und das neuere IPV6, das aus 8 Gruppen von maximal 4-stelligen hexadezimalen Zahlen besteht, etwa so: 2f45:b8:8256:1fd:0:0:2345:ff1a. Die meisten Provider setzen derzeit noch auf IPV4 und erteilen eine IP je Router. Diese IP wird dynamisch erteilt, also meistens einmal pro Tag (und bei jedem neuen Verbindungsaufbau) neu vergeben. Fixe IPs kann man zwar von manchem Providern bekommen, aber meist nur für recht viel Geld. Um einen privaten Server von aussen zu erreichen, muss man also irgendwie herausfinden, welche IP er gerade hat.

Die Lösung hierfür heisst DynDNS: Unser Server teilt einem speziellen Internet-Service regelmässig mit, welche IP er gerade hat, und dieser publiziert diese IP unter einem symbolischen Namen. Man kann also zum Beispiel “mein.garagentor.ch” reservieren, Falls das noch nicht vergeben ist, und einen DynDNS Service einrichten, der jeden Internet-Zugriff auf diesen Namen auf unseren Server leitet.

www.dyndns.com ist der Pionier solcher Services, aber es gibt auch etliche Andere. ich verwende ZoneEdit, weil ich dort ohnehin einige Domains gehostet habe, und der DynDNS Service nichts extra kostet.

2. Raspberry anweisen, mit DynDNS zu kooperieren

Dafür gibt es fixfertige Lösungen:

sudo apt-get install ddclient

Den ddclient muss man für dne gewählten DynDNS-Diens konfigurieren, dann wird er fortan immer die aktuelle IP-Adresse an den Dienst melden-

3. Router konfigurieren

Da die vom Provider erteilte IP pro Router gilt, haben alle Computer “hinter” dem Router gegen aussen dieselbe IP. Der Router hat die Aufgabe, Zugriffe auf die inneren Computer zu verteilen, für die er ein internes Netzwerk mit eigenen IP-Nummern (z.B. 192.168.1.5) aufbaut. Wenn ein Browser jetzt auf https://meingaragentor.ch:2017 zugreift, dann wird er melden: “Diese Website ist nicht erreichbar”. Er weiss ja nicht, wo der Server lauert, der diesen Dienst bereitstellt. Wir müssen also den Route anweisen, den Port 2017 zu unserem Raspberry weiterzuleiten.

Das dafür nötige Vorgehen ist bei jedem Router anders. Das entsprechende Menü heisst oft “NAT” oder “Dienst freigeben”.

4. Kontakt zum Server verschlüsseln

Dies geschieht, indem man das https:// anstatt dem http:// Protokoll verwendet. Dies muss auf dem Server eingerichtet werden. Bisher hatten wir:

    app.listen(2017,function(){
      console.log("Garagenserver läuft an port 2017")
    })

Was einen HTTP-Server erstellt. Neu schreiben wir nun:

    https.createServer({
      key: fs.readFileSync('key.pem'),
      cert: fs.readFileSync('cert.pem')
    }, app).listen(2017)

Wobei “key” der private Schlüssel des Servers und “cert” das Zertifikat ist, das die Identität des Servers bestätigt. Bis vor kurzem war ein solches Zertifikat eine teure Sache. Man musste sich bei einer Zertifizierungsstelle als Inhaber der Website ausweisen und dann einen jährlichen Betrag bezahlen. Heute gibt es mit Letsencrypt eine kostenlose Variante.

So oder so kann man auch einfach ein selbstsigniertes Zertifikat benutzen. Dafür genügt ein simples:

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes

Damit erstellt man einen Schlüssel und bestätigt sich selber, wer man ist. Natürlich mekren alle Browser diese “Schummelei” und warnen vor einer nicht-sicheren Verbindung. Das ist aber nicht ganz korrekt: Die Verschlüsselung ist absolut gleichwertig mit der, die man mit einem “offiziellen” Zertifikat erhält. Nur der Identitätsnachweis des Serverbetreibers ist nicht gesichert. Aber das kann uns egal sein, da wir ja selber der Serverbetreiber sind. Also wenn man mit der hässlichen Browserwarnung leben kann, dann ist ein selbstsigniertes Zertifikat der bequemste Weg.

5. Hackversuche unterbinden

Hier geht es nicht einmal so sehr um den Einbrecher, der durch die garage ins Haus eindringen will. Für den ist es allemal einfacher, ein Fenster einzuschlagen, als Passwörter zu hacken. Aber wenn man vom Internet aus erreichbar ist, dann ist man auch Hackern ausgesetzt, die einfach zum Spass irgendwo Schaden anrichten wollen, und auch leistungsfähigen automatischen Hackprogrammen, die Computer erobern wollen, um sie dann zum Beispiel als Spamschleudern oder als Selver für illegale Inhalte zu missbrauchen. Daher:

  • Auf dem Raspberry läuft nur, was für den Garagenserver notwenidig ist. Was nicht vorhanden ist, kann auch nicht gehackt werden. Also kein VNC Zugang, keine Windows-Dateifreigabe etc.
  • Der Router gibt nur den einen Port weiter, der benötigt wird, also in unserem Beispiel 2017. Alle anderen Ports werden abgewiesen.
  • Der Server hat nur passwortgeschützte Funktionen. Ohne Passwort kann man gar nichts machen.
  • Wenn jemand ein falsches Passwort eingibt, wird eine Zwangspause eingelegt. Dies stört Programme, die vollautomatisch hunderte von Passwörtern pro Sekunde ausprobieren können. Wir verwenden folgende Methode: Beim ersten Fehlversuch beträgt die Pause 3 Sekunden, bei jedem weiteren Fehlversuch verdoppelt sie sich. Wer also fünf Mal ein falsches Passwort eingibt, muss sich vor dem nächsten Versuch eineinhalb Minuten gedulden, nach zehn Mal schon fast eine Stunde.

6. Zugriffe loggen

Es könnte immer noch sein, dass ein raffinierter Hacker einen Weg findet, an den wir nicht gedacht haben. Also muss der Server alles, was er tut in ein Logfile schreiben. Wenn man das ab und zu durchsieht, kann man verdächtige Aktionen bemerken.

Das Programm

Vorbereitung

Listings

/**
 *  Garagentor-Fernbedienung mit Raspberry Pi
 */

"use strict"

// Damit wir das Programm auf einem normalen PC ohne GPIO testen können. Wenn es auf dem echten Pi läuft, true setzen
const realpi = false
// Pin des piface für den output. pin 1 ist das linke Relais.
const output_pin = 1
// pin für den Schalter, der feststellt, ob das Garagentor offen ist
const input_pin = 0
// Dauer des simulierten Tastendrucks in Millisekunden
const time_to_push = 1200
// Dauer des Öffnungs/Schliessvorgangs des Tors
const time_to_run = 4000
// Aussperren bei falscher Passworteingabe
const lock_time = 3000

const fs = require('fs')
const https = require('https')
const express = require('express')
const nconf = require('nconf')
const hash = require('crypto-js/sha256')
const path = require('path')
const bodyParser = require('body-parser');
const salt = "um Hackern mit 'rainbow tables' die Suppe zu versalzen"
const favicon = require('serve-favicon');

nconf.file('users.json')
const app = express()
// Dieses Flag nutzen wir später, um den Server temporär inaktiv zu schalten.
let disabled = false;
// Dieses Flag zeigt an, dass das Garagentor gerade fährt
let running = false
// Hier sammeln wir schiefgegangene Login-Versuche
const failures = {}

app.set('view-cache', true)
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));

/*
 HTTPS-Server erstellen, damit Usernamen und Passwörter verschlüsselt übermittelt werden.
 Als Zertifikat kann man entweder ein self-signed certificate verwenden
 (openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes).
 Dann muss man allerdings damit leben, dass der Browser eine "Sicherheitswarnung" ausgibt.

 Oder man erstellt ein Zertifikat via letsencrypt, mit "sudo certbot certonly --manual" und kopiert
 "privkey.pem" nach "key.pem" und "fullchain.pem" nach "cert.pem".
 Dann muss man allerdings, das Zertifikat alle drei Monate erneuern, weil Letsencrypt keine länger gültigen
 Zertifikate ausstellt.

 Oder man kauft irgendwo ein kostenpflichtiges Zertifikat. Aber das scheint mir für einen Garagentorantrieb
 eigentlich zu aufwändig.
 */
https.createServer({
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem')
}, app).listen(2017)

// Auf einem echten Pi ist pfio auf das piface (https://www.npmjs.com/package/piface) gesetzt
// Auf einem anderen PC wird es einfach mit leeren Funktionen simuliert.
let pfio
if (realpi) {
  pfio = require('piface')
  pfio.init()
} else {
  let pinstate = 1
  pfio = {
    digital_write: function () {
    },
    digital_read: function (pin) {
      pinstate = pinstate ? 0 : 1
      return pinstate
    }
  }
}

/**
 * Expressjs sagen, dass die Views im Verzeichnis "views" zu finden sind, und dass
 * pug benötigt wird, um sie nach HTML zu konvertieren.
 */
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

/**
 * Expressjs mitteilen, dass wir json- und urlencoded Parameter im request body erwarten
 */
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));

/**
 * Endpoint für https://adresse:2017/
 *  Login-Screen anzeigen
 */
app.get("/", function (request, response) {
  response.render("garage")
})

/**
 Check ob der Server inaktiv geschaltet ist, oder das Garagentor gerade läuft.
 Wird vor jeden POST-Request ("/*") geschaltet.
 */
app.post("/*", function (req, resp, next) {
  if (disabled) {
    resp.render("answer", {
      message: "Der Server ist derzeit inaktiv geschaltet."
    })
  } else if (running) {
    resp.render("answer", {
      message: "Das Garagentor fährt gerade. Bitte warten."
    })
  } else {
    next()
  }
})

/**
 Check, ob der aktuelle Anwender gesperrt ist
 */
function isLocked(lockinfo) {
  if (lockinfo) {
    let now = new Date().getTime()
    let locked_until = lockinfo.time + (Math.pow(2, lockinfo.attempt) * lock_time)
    if (now < locked_until) {
      return true;
    }
  }
  return false;
}

/**
 * Aktuellen User sperren. Wenn er schon gesperrt ist, Sperrzeit verlängern
 * @param user user
 * @returns {number} Zahl an Sekunden der aktuellen Sperrzeit
 */
function setLock(user) {
  let now = new Date().getTime()
  let lockinf = failures[user] ? failures[user] : {"attempt": 0}
  lockinf["attempt"] += 1
  lockinf["time"] = now
  failures[user] = lockinf
  return Math.round((Math.pow(2, lockinf.attempt) * lock_time) / 1000)
}

/**
 * Zugriffstest;
 * wird vor alle https://server:2017/garage/... Anfragen POST requessts geschaltet
 * Wenn ein user gesperrt ist, dann prüfe, ob die Sperre abgelaufen ist. Wenn nein, abweisen
 * Sonst:
 * Wenn das Passwort korrekt ist, allfällige Sperren löschen
 * Wenn das Passwort falsch ist, Sperre erneut setzen, Dauer erhöhen (2^attempt*lock_time)
 */
app.post("/garage/*", function (request, response, next) {
  let user = request.body.username.toLocaleLowerCase()
  if (isLocked(failures[user])) {
    response.render("answer", {message: "Sperre wegen falscher Passworteingabe. Bitte etwas später nochmal versuchen."})
  } else {
    let password = JSON.stringify(hash(request.body.password + salt))
    let valid = nconf.get(user)
    if (valid && valid === password) {
      delete failures[user]
      next()
    } else {
      console.log("Loginfehler mit Name " + user + ", " + new Date())
      let secs = setLock(user)
      response.render("answer", {
        message: "Wer bist denn du??? Sperre " + secs + " Sekunden."
      })
    }
  }
})

/**
 * Zugriffstest für Admin-Funktionen.
 * Wird vor alle https://server:2017/adm/... GET requests geschaltet.
 * Gemeinsame Syntax: /adm/masterpassword/funktion/parameter.
 * Bei falschem Masterpasswort: Sperre setzen bzw. verlängern.
 */
app.get("/adm/:master/*", function (req, resp, next) {
  if (isLocked(failures['admin'])) {
    resp.render("answer", {message: "Sperre wegen falscher Passworteingabe. Bitte etwas später nochmal versuchen."})
  } else {
    let master = JSON.stringify(hash(req.params.master + salt))
    let stored = nconf.get("admin")
    if (!stored) {
      nconf.set("admin", master)
      stored = master
    }
    if (stored === master) {
      delete failures['admin']
      next()
    } else {
      console.log("Admin-Fehler" + req.params.username + ", " + new Date())
      let secs = setLock("admin")
      resp.render("answer", {
        message: "Insufficient rights. Wait " + secs + " seconds."
      })
    }
  }
})

/*
 Nach dem Login-Screen und erfolgreicher Passworteingabe: Aktuellen Zustand des Tors anzeigen.
 */
app.post("/garage/login", function (request, response) {
  let state = pfio.digital_read(input_pin)
  let action = state === 1 ? "Schliessen" : "Öffnen"
  response.render("confirm", {
    name: request.body.username,
    pwd: request.body.password,
    status: state === 1 ? "offen" : "geschlossen",
    action: action
  })

})

/*
 "Taste drücken".  Kontakt wird für time_to_push Millisekunden geschlossen. Für time_to_run Millisekunden werden
 keine weiteren Kommandos entgegengenommen, um dem Tor Zeit zu geben, ganz hoch oder runter zu fahren.
 */
app.post("/garage/action", function (request, response) {
  console.log("Garage " + request.body.action + ", " + new Date())
  running=true
  pfio.digital_write(output_pin, 1)
  setTimeout(function () {
    pfio.digital_write(output_pin, 0)
  }, time_to_push);
  setTimeout(function(){
    running=false
  },time_to_run)
  response.render("answer", {
    message: "Auftrag ausgeführt, " + request.body.username
  })
})

/**
 * Einen neuen User eintragen.
 */
app.get("/adm/:master/add/:username/:password", function (req, resp) {
  var user = req.params.username.toLocaleLowerCase()
  var password = JSON.stringify(hash(req.params['password'] + salt))
  nconf.set(user, password)
  nconf.save()
  resp.render("answer", {
    message: "Ok"
  })

})

/**
 * Einen User löschen.
 */
app.get("/adm/:master/remove/:username", function (req, resp) {
  nconf.set(req.params.username, undefined)
  nconf.save()
  resp.render("answer", {
    message: "ok"
  })
})

/**
 * Server inaktiv schalten.
 */
app.get("/adm/:master/disable", function (req, resp) {
  disabled = true
  resp.render("answer", {
    message: "disabled"
  })
})

/**
 * Server aktiv schalten.
 */
app.get("/adm/:master/enable", function (req, resp) {
  disabled = false
  resp.render("answer", {
    message: "enabled"
  })
})

/**
 * Passwort ändern
 */
app.post("/garage/chpwd",function(req,resp) {
  let npwd=req.body.npwd
  if(npwd && npwd.length>4 && /\d/.test(npwd) && /[a-zA-Z]/.test(npwd)) {
    nconf.set(req.body.username, JSON.stringify(hash(req.body.npwd + salt)))
    nconf.save()
    console.log(req.body.username + " changed password, " + new Date())
    resp.render("answer", {message: "Ab sofort gilt das neue Passwort"})
  }else{
    resp.render("answer",{message:"Das neue Passwort muss mindestens 5 Zeichen lang sein und sowohl Zahlen als auch Buchstaben enthalten."})
  }
})

/**
 * Logfile auslesen
 */
app.get("/adm/:master/log",function(req,resp){
  fs.readFile("../forever.log",function(err,data){
    if(err){
      resp.render("answer",{message:err})
    }else{
      var lines=data.toString().split("\n")
      resp.render("answer",{message:"<p>"+lines.join("<br>")+"</p>"})
    }
  })
})

Die Views

Views sind das, was der Server an den Browser zurückliefert, also das, was man letztlich zu Gesicht bekommt. In diesem Beispiel sind alle Views in der Beschreibungssprache “pug” verfasst, die vor der Ausgabe vom Server nach HTML umgewandelt werden.

layout.pug

Das ist der gemeinsame Rahmen um alle Views. Die jeweils spezifische View wird bei “content” eingesetzt. Hier wird das “Materialize”- Styling geladen und festgelegt, dass die Seite auf kleinen Bildschirmen vergrössert dargestellt werden soll. Ausserdem deklarieren wir, wo sich die Icons für iOS (apple-touch-icon) un Android (manifest.json) befinden, falls der User eine Verknüpfung auf dem Handy-Desktop anlegen will.

doctype html
html
  head
    title= "Unsere Garage"
    link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css")
    link(href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet")
    meta(content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=2, user-scalable=0" name="viewport")
    link(rel="apple-touch-icon" href="/garage-57x57.png")
    link(rel="manifest" href="/manifest.json")
  body
    block content
    script(src="simple.js")

garage.pug

Das ist die Einstiegsseite, die auf den Aufruf von “/” gezeigt wird.

extends layout.pug
block content
  .container
    .row
      h1.col.s12 Garage
      form.col.s12(method="post" action="/garage/login")
        .row
          .col.s12
            .input-field.col.s12
              i.material-icons.prefix account_circle
              input(type="text" name="username" placeholder="Username" id="uname")
            .input-field.col.s8
              i.material-icons.prefix vpn_key
              input(type="password" name="password" placeholder="Passwort" id="pwd")
            .btn.btn-flat.col.s4(onClick="enterPwd()") ändern...

            button.btn.waves-effect.waves-light.col.s12(type="submit" name="action") Senden
              i.material-icons.right send

answer.pug

Mit dieser Seite gibt der Server seine Antworten zurück. In der Variablen “message” befindet sich der Text der Antwort.

extends layout.pug
block content
  .container
    .row
      p.col.s12(style="font-size:large") Die Garage sagt: !{message}
      a.col.s3.btn.waves-effect.waves-light(href="/") Zurück
      a.col.s3.offset-s1.btn.waves-effect.waves-light(href="http://www.google.ch") Irgendwo anders hin

confirm.pug

Hier wird dem user nach erfolgtem Login die Möglichkeit gegeben, das Tor zu öffnen oder zu schliessen. In der Variable “Status” steht, ob die garage derzeit offen oder zu ist, in “action” steht, was der User jetzt tun kann (schliessen oder öffnen).

extends layout.pug
block content
  .container
    .row
      h3(style="color:blue") Willkommen, #{name}
      p Die Garage ist <b>#{status}</b>
      form.col.s12(method="post" action="/garage/action")
        input(type="hidden" name="username" value=name)
        input(type="hidden" name="password" value=pwd)
        input(type="hidden" name="action" value=action)
        button.col.s3.btn.waves-effect.waves-light(type="submit" name="action") #{action}
        a.col.s3.offset-s1.btn.waves-effect.waves-li

16.3.2017 - Garagentor-Fernbedienung

Zur zweiten Version

Eine defekte Garagentor-Fernbedienung gab den Ausschlag: Da unser Torantrieb schon das biblische-Alter von 15 Jahren hat, war ein Ersatzteil natürlich nicht mehr zu bekommen. Es gab nur noch Universalfernbedienungen für um die 100.-. Da kommt man ins Grübeln. Der Materialwert einer solchen Fernbedieung dürfte unter 10.- sein.

Ausserdem gibt es noch ein zweites, davon unabhängiges Problem: Wir haben nie genug Schlüssel, und die Jungs vergessen ihren manchmal, wenn sie weg gehen. Aber was wir alle immer dabei haben, ist das Smartphone. Nun denn, die Idee, das Garagentor mit dem Smartphone zu öffnen ist, zugegeben, nicht ganz neu, aber es geht viel billiger und einfacher, als gedacht:

Innen hat der Torantrieb einen Taster, der einfach einen Kontakt schliesst und den Torantrieb so einschaltet. Mit diesem Taster kann man das Tor von Hand bedienen, wenn man sich in der Garage befindet. Das Tor fährt bei jedem Druck auf den Taster abwechselnd runter oder hoch, bzw. bleibt stehen, wenn es gerade am Fahren ist.

Natürlich kann man so einen Kontakt auch mit einem Relais, statt mit einem Handtaster schliessen.

Und ein Relais kann man mit einer beliebigen Elektronikschaltung steuern. Oder mit einem GPIO Ausgang eines Raspberry Pi. Und der Raspberry Pi wiederum kann via Netzwerk oder via Bluethooth mit der Umwelt drahtlos in Kontakt treten.

Also ist die Idee sehr einfach: Auf dem Raspi läuft ein REST-Server. Sobald man im häuslichen WLAN eingebucht ist, kann man mit dem Browser des Handys auf diesen REST-Server zugreifen, und einen GPIO-Pin schalten, der wiederum ein Relais ansteuert, welches den Kontakt schliesst, der das Garagentor losfahren oder stoppen lässt. Da das WLAN mit WPA2 gesichert ist, und da der REST-Server nur vom WLAN aus erreichbar ist, braucht man sich um die Absicherung nicht allzu viele Gedanken zu machen. Eine einfache Passwortabfrage genügt. Und es wird dann geradezu trivial, Besuchern einen temporären Schlüssel zu geben: Man braucht sie nur auf dem Server einzutragen und am Ende wieder zu löschen.

Software

Da ich eher ein Software-Mensch bin, mache ich mich zuerst an den Server:

  • Einen Raspi aus dem keller holen, es ist ein B+. Zwar langsamer, als die aktuellen 2er und 3er Versionen, aber für unsere Zwecke sollte es genügen.
  • NOOBS herunterladen und auf die SD-Karte aufspielen, für silentinstall konfigurieren, da ich keine Lust habe, den Raspi an Tastatur und Bildschirm anzuschliessen.
  • per ssh mit dem Raspi verbinden, und das Passwort ändern. Dafür sorgen, dass er beim Start nicht in den grafischen Modus hochfährt, um nicht noch mehr von der spärlichen Leistung zu verbraten.
  • Wlan-Stick anstecken und konfigurieren.
  • NodeJS und NPM installieren (was weniger einfach ist, als gedacht, weil die in Raspbian mitgelieferte NodeJS-Version hoffnungslos veraltet ist.)

Einschub: NodeJS Version 7.x auf einem Raspberry Pi mit ARM6 installieren:

  sudo apt-get remove nodejs
  cd
  mkdir apps
  cd apps
  wget https://nodejs.org/dist/v7.7.3/node-v7.7.3-linux-armv6l.tar.xz
  tar -xf node-v7.7.3-linux-armv6l.tar.xz
  mv node-v7.7.3-linux-armv6l node7
  sudo ln -s /home/pi/apps/node7/bin/node /usr/bin/node
  sudo ln -s /home/pi/apps/node7/bin/npm /usr/bin/npm
  echo export PATH=$PATH:/home/pi/apps/node7/bin >>../.profile
  • Ein Projekt aufsetzen:

Im Terminal:

  mkdir garage
  cd garage
  npm init
  npm install
  npm install --save express nconf crypto-js

Dann kann man einen trivialen REST-Server erstellen:

    // garage.js
    var express=require('express')
    var nconf=require('nconf')
    var hash=require('crypto-js/sha256')
    nconf.file('users.json')
    var salt="um Hackern mit 'rainbow tables' die Suppe zu versalzen"

    var app=express()

    app.get("/garage/:user/:password",function(request,response){
      var user=JSON.stringify(hash(request.params['user']+salt))
      var password=JSON.stringify(hash(request.params['password']+salt))
      var valid=nconf.get(user)
      if(valid && valid == password){
        console.log("Die Garage öffnet sich!")
        response.send("Willkommen, "+request.params['user'])
      }else{
        response.send("Wer bist denn du???")
      }
    })

    app.listen(3000,function(){
      console.log("Garagenserver läuft an port 3000")
    })

Mit node garage.js kann man das Ding starten. Dann können wir mit http://raspi:3000/garage/hans/peter zugreifen und erhalten immer “Wer bist denn du???” zurück, da ja noch kein User ‘hans’ mit Paswort ‘peter’ bekannt ist.

Das wollen wir nun ändern:

Damit keine Usernamen und Passwörter im Klartext in users.json stehen, werden sie gehasht. Allerdings ist es dann nicht ganz einfach, sie in die Konfiguration zu kriegen. Da dies ein kleines Projekt mit einer absehbaren Zahl von Usern ist, machen wir uns das sehr einfach: Es gibt einen zweiten REST-Endpoint zum Eintragen von Usern, den wir vor der app.listen()-Zeile einfügen:

    app.get("/adduser/:username/:password",function(req,resp){
      var user=JSON.stringify(hash(req.params['username']+salt))
      var password=JSON.stringify(hash(req.params['password']+salt))
        nconf.set(user,password)
        nconf.save()
        resp.send("Ok")
    })

Jetzt können wir höchst simpel mit http://raspi:3000/adduser/hans/peter unseren ersten User eintragen. Danach sollte http://raspi:3000/garage/hans/peter eine freundliche Willkommensmeldung zurückliefern. (Über die Tatsache, dass die Nachricht “Die Garage öffnet sich!” eine klassische Fake-News ist, wollen wir jetzt mal gnädig hinwegsehen.)

Natürlich wäre das nun doch allzu unsicher: So könnte sich ja jeder selbst als zugelassener User eintragen :) Der Endpunkt “adduser” wird deshalb im Normalbetrieb auskommentiert und nur dann “scharf” geschaltet, wenn der Admin einen neuen User zufügen will. Wie gesagt, wir sprechen von einem System mit einer sehr überschaubaren Zahl von Usern. Wem das zu unbequem ist, der kann ja einen Admin-Zugang erstellen, um User zu verwalten.

Über die Klartextübermittlung von Usernamen und Passwörtern braucht man sich übrigens keine Gedanken zu machen, sofern man selbst die Kontrolle über den Router hat: Das mit WPA2 gesicherte WLAN verschlüsselt alle übertragenen Daten.

Damit ist der Software-Teil des Garagenentoröffners fertig.

Hardware

Für den Hardware-Teil muss man wissen:

Die GPIOs des Raspberry bringen grade mal 3.3 Volt mit äusserst spärlichen Milliampères auf die Beine. Wenn man da herzlos ein handelsübliches Relais anschliesst, brennt der raspi glatt durch. Oder das Relais reagiert nicht. Oder was auch immer (ich bin ja kein Elektroniker). Jedenfalls wird es nicht klappen.

Man braucht also eine Steuerelektronik. Hinter diesem hochtrabenden Namen verbirgt sich ein Transistor, eine Diode und ein Widerstand. Für jemandem mit zwei linken Händen wie mich kann das schon limitierend sein. Aber viel kann ja nicht passieren. Schlimmstenfalls verbrennt man beim Löten den Transistor oder die Finger. Natürlich kann man auch fertige Relaismodule für den Raspberry kaufen, aber wir sind ja keine Warmduscher.

Hier kann man zum Beispiel nachlesen, wie man so etwas aufbaut.

Zusammenbringen

Nun weiss unser Server allerdings noch nicht, wie er den passenden GPIO Pin schalten kann. Wie fast immer bei NodeJS ist guter Rat billig: npm install --save rpio.

Dann fügen wir im Kopf von ‘garage.js’ folgende Zeilen hinzu:

var rpio=require('rpio')
rpio.open(12,rpio.OUTPUT,rpio.LOW)  // definierten Ausgangszustand erstellen

Und ändern dann den Erfolg-Teil des garage-Endpoints wie folgt:

    if(valid && valid == password){
      console.log("Das Garagentor tut etwas!")
      rpio.write(12,rpio.HIGH)
      rpio.sleep(1)
      rpio.write(12,rpio.LOW)
      response.send("Auftrag ausgeführt, "+request.params['user'])
    }

Also: Wir “drücken den Knopf” eine Sekunde lang und lassen ihn dann wieder los. Wenn das Garagentor sich zuletzt geöffnet hat, dann schliesst es sich jetzt, wenn es sich gerade bewegt, dann bleibt es stehen, und wenn es sich zuletzt geschlossen hatte, dann geht es jetzt auf.

Ziemlich einfach, nicht wahr? Und die Kosten? Ein Raspberry Pi B+ kostet um die 35.-, Gehäuse 15.-, Netzteil 15.-, das Relais 5.- und die restlichen Bauteile sind im Rappenbereich. Aber natürlich darf man die Zeit nicht als Arbeitszeit nicht rechnen :)

Natürlich kann und sollte man am Programm, speziell am UI, noch einiges verfeinern, aber das sei als Übung der interessierten Leserschaft überlassen :)