Archive: Month 10, Year 2015

Hardware-Zufallszahlengenerator des Raspberry Pi nutzen

29.10.2015 yahe administration legacy linux security

Bei Serversystemen hat man häufig das Problem, dass dem Zufallszahlengenerator nicht genug Nutzerinteraktionen zur Verfügung stehen, um ausreichend Entropie für den Zufallszahlengenerator zu sammeln. Das führt dann, je nach eingesetzter Software, zu hängenden SSH-Verbindungen, zu Timeouts bei HTTPS-Verbindungen oder anderen nicht reproduzierbaren Fehlern. Aus diesem und anderen Gründen haben CPU-Hersteller begonnen, Hardware-Zufallszahlengeneratoren direkt in ihre Prozessoren zu integrieren. Bei Intel nennt sich diese Technologie "Secure Key" (vormals "Bull Mountain"), bei VIA ist sie als "PadLock" bekannt, einige Broadcom-SOCs haben einen Hardware-Zufallszahlengenerator und auch AMD Zen unterstützt RdRand.

Auch der Broadcom-Chip des Raspberry Pi bietet einen Hardware-Zufallszahlengenerator, dessen erzeugte Zufallszahlen man in den Linux-Zufallszahlenpool einfließen lassen kann. Hierfür sind nur wenige Schritte notwendig. Als erstes muss das entsprechende Kernel-Modul geladen werden. Die Distribution Raspbian liefert zwei Broadcom-Random-Number-Generator-Module mit den Namen "bcm2708_rng" und "bcm2835_rng". Obwohl im Raspberry Pi ein Broadcom 2835 steckt, funktioniert in Raspbian nur das "bcm2708_rng"-Kernelmodul korrekt. BCM2708 ist die SOC-Familie, zu der auch der BCM2835 gehört. Überprüfen kann man das auch nochmal anhand der Ausgabe des Befehls:

cat /proc/cpuinfo

In der erscheinenden Ausgabe wird die Hardware explizit als BCM2708 benannt:

processor  : 0
model name  : ARMv6-compatible processor rev 7 (v6l)
BogoMIPS  : 2.00
Features  : half thumb fastmult vfp edsp java tls
CPU implementer  : 0x41
CPU architecture: 7
CPU variant  : 0x0
CPU part  : 0xb76
CPU revision  : 7

Hardware  : BCM2708
Revision  : 000e
Serial    : ****************

Nun heißt es, das passende Kernelmodul zu laden. Testweise könnt ihr das mit folgendem Befehl tun:

sudo modprobe bcm2708_rng

Wenn das ohne Fehler funktioniert hat, sollte nun das Character-Device-File "/dev/hwrng" existieren. Ist das der Fall, benötigt ihr noch die "rng-tools", die ihr einfach per "apt-get" installieren könnt:

sudo apt-get install rng-tools

Nach der Installation sollten diese direkt automatisch starten. Ihr könnt zudem testen, ob diese auch ihre Arbeit verrichten. Dazu ruft ihr das "rngd" Tool im Foreground-Modus auf und beendet es nach ein wenig Warten wieder per "CTRL+C":

sudo rngd -f
^C

Euch sollten nun die verschiedensten Statistiken angezeigt werden, unter anderem, wieviele FIPS 140-2 Tests erfolgreich bestanden wurden. Mit diesen wird die Qualität der generierten Zufallszahlen ermittelt:

rngd 2-unofficial-mt.14 starting up...
entropy feed to the kernel ready
stats: bits received from HRNG source: 60064
stats: bits sent to kernel pool: 512
stats: entropy added to kernel pool: 512
stats: FIPS 140-2 successes: 3
stats: FIPS 140-2 failures: 0
stats: FIPS 140-2(2001-10-10) Monobit: 0
stats: FIPS 140-2(2001-10-10) Poker: 0
stats: FIPS 140-2(2001-10-10) Runs: 0
stats: FIPS 140-2(2001-10-10) Long run: 0
stats: FIPS 140-2(2001-10-10) Continuous run: 0
stats: HRNG source speed: (min=752.012; avg=782.220; max=807.043)Kibits/s
stats: FIPS tests speed: (min=5.249; avg=5.533; max=6.076)Mibits/s
stats: Lowest ready-buffers level: 2
stats: Entropy starvations: 0
stats: Time spent starving for entropy: (min=0; avg=0.000; max=0)us
Exiting...

Wenn das alles funktioniert hat, könnt ihr die Verwendung des Kernel-Moduls permanent machen, indem ihr in die Datei "/etc/modules" eine neue Zeile mit dem Namen des Kernel-Moduls einfügt:

bcm2708_rng

Um sicherzugehen, dass die bereitgestellten Zufallszahlen wirklich eine gute Qualität haben, habe ich den vollständigen Satz an Dieharder-Tests laufen lassen. Hierzu habe ich erst einmal Dieharder selbst installiert:

sudo apt-get install dieharder

Da der Zufallszahlengenerator nicht sonderlich schnell ist, musste ich den Dieharder-Test via "nohup" starten. Dieses sorgt dafür, dass der Prozess weiterläuft, obwohl die SSH-Session, in der der Aufruf erfolgte, bereits geschlossen wurde. Der gesamte Dieharder-Testlauf dauerte etwa einen Monat:

sudo nohup sh -c "cat /dev/hwrng | dieharder -g 200 -a"

Die Ergebnisse der Dieharder-Tests sind aus meiner Sicht vielversprechend. Fast alle Testläufe wurden ohne Probleme bestanden. Lediglich zwei Testläufe zeigen ein "weak"-Ergebnis, was bei einem echten Zufallszahlengenerator durchaus mal vorkommen kann:

#=============================================================================#
#            dieharder version 3.31.1 Copyright 2003 Robert G. Brown          #
#=============================================================================#
   rng_name    |rands/second|   Seed   |
stdin_input_raw|  2.49e+04  |1848243654|
#=============================================================================#
        test_name   |ntup| tsamples |psamples|  p-value |Assessment
#=============================================================================#
   diehard_birthdays|   0|       100|     100|0.80754184|  PASSED
      diehard_operm5|   0|   1000000|     100|0.46154223|  PASSED
  diehard_rank_32x32|   0|     40000|     100|0.24741511|  PASSED
    diehard_rank_6x8|   0|    100000|     100|0.98544914|  PASSED
   diehard_bitstream|   0|   2097152|     100|0.74214657|  PASSED
        diehard_opso|   0|   2097152|     100|0.62926414|  PASSED
        diehard_oqso|   0|   2097152|     100|0.65459762|  PASSED
         diehard_dna|   0|   2097152|     100|0.05329859|  PASSED
diehard_count_1s_str|   0|    256000|     100|0.93984253|  PASSED
diehard_count_1s_byt|   0|    256000|     100|0.73264960|  PASSED
 diehard_parking_lot|   0|     12000|     100|0.82688871|  PASSED
    diehard_2dsphere|   2|      8000|     100|0.81340970|  PASSED
    diehard_3dsphere|   3|      4000|     100|0.81943245|  PASSED
     diehard_squeeze|   0|    100000|     100|0.20437793|  PASSED
        diehard_sums|   0|       100|     100|0.01259559|  PASSED
        diehard_runs|   0|    100000|     100|0.40514608|  PASSED
        diehard_runs|   0|    100000|     100|0.83339986|  PASSED
       diehard_craps|   0|    200000|     100|0.11679660|  PASSED
       diehard_craps|   0|    200000|     100|0.42675629|  PASSED
 marsaglia_tsang_gcd|   0|  10000000|     100|0.96356183|  PASSED
 marsaglia_tsang_gcd|   0|  10000000|     100|0.84443650|  PASSED
         sts_monobit|   1|    100000|     100|0.86875820|  PASSED
            sts_runs|   2|    100000|     100|0.76068017|  PASSED
          sts_serial|   1|    100000|     100|0.44501629|  PASSED
          sts_serial|   2|    100000|     100|0.08213384|  PASSED
          sts_serial|   3|    100000|     100|0.40056497|  PASSED
          sts_serial|   3|    100000|     100|0.84781124|  PASSED
          sts_serial|   4|    100000|     100|0.67540322|  PASSED
          sts_serial|   4|    100000|     100|0.67479165|  PASSED
          sts_serial|   5|    100000|     100|0.93910920|  PASSED
          sts_serial|   5|    100000|     100|0.78108786|  PASSED
          sts_serial|   6|    100000|     100|0.36072231|  PASSED
          sts_serial|   6|    100000|     100|0.10943079|  PASSED
          sts_serial|   7|    100000|     100|0.33454672|  PASSED
          sts_serial|   7|    100000|     100|0.84662732|  PASSED
          sts_serial|   8|    100000|     100|0.56239211|  PASSED
          sts_serial|   8|    100000|     100|0.85989154|  PASSED
          sts_serial|   9|    100000|     100|0.09053360|  PASSED
          sts_serial|   9|    100000|     100|0.76921152|  PASSED
          sts_serial|  10|    100000|     100|0.65164012|  PASSED
          sts_serial|  10|    100000|     100|0.98802936|  PASSED
          sts_serial|  11|    100000|     100|0.16490199|  PASSED
          sts_serial|  11|    100000|     100|0.43350451|  PASSED
          sts_serial|  12|    100000|     100|0.97347736|  PASSED
          sts_serial|  12|    100000|     100|0.35755776|  PASSED
          sts_serial|  13|    100000|     100|0.61727927|  PASSED
          sts_serial|  13|    100000|     100|0.70426071|  PASSED
          sts_serial|  14|    100000|     100|0.96653473|  PASSED
          sts_serial|  14|    100000|     100|0.72003802|  PASSED
          sts_serial|  15|    100000|     100|0.33951664|  PASSED
          sts_serial|  15|    100000|     100|0.65324653|  PASSED
          sts_serial|  16|    100000|     100|0.79539647|  PASSED
          sts_serial|  16|    100000|     100|0.29465210|  PASSED
         rgb_bitdist|   1|    100000|     100|0.29377788|  PASSED
         rgb_bitdist|   2|    100000|     100|0.26581318|  PASSED
         rgb_bitdist|   3|    100000|     100|0.68619130|  PASSED
         rgb_bitdist|   4|    100000|     100|0.65469598|  PASSED
         rgb_bitdist|   5|    100000|     100|0.94194728|  PASSED
         rgb_bitdist|   6|    100000|     100|0.06186682|  PASSED
         rgb_bitdist|   7|    100000|     100|0.99989483|   WEAK
         rgb_bitdist|   8|    100000|     100|0.94851172|  PASSED
         rgb_bitdist|   9|    100000|     100|0.55403191|  PASSED
         rgb_bitdist|  10|    100000|     100|0.42372957|  PASSED
         rgb_bitdist|  11|    100000|     100|0.69930031|  PASSED
         rgb_bitdist|  12|    100000|     100|0.10435458|  PASSED
rgb_minimum_distance|   2|     10000|    1000|0.06686667|  PASSED
rgb_minimum_distance|   3|     10000|    1000|0.73245892|  PASSED
rgb_minimum_distance|   4|     10000|    1000|0.21902555|  PASSED
rgb_minimum_distance|   5|     10000|    1000|0.98142677|  PASSED
    rgb_permutations|   2|    100000|     100|0.52202354|  PASSED
    rgb_permutations|   3|    100000|     100|0.01765637|  PASSED
    rgb_permutations|   4|    100000|     100|0.54444953|  PASSED
    rgb_permutations|   5|    100000|     100|0.98885119|  PASSED
      rgb_lagged_sum|   0|   1000000|     100|0.19012074|  PASSED
      rgb_lagged_sum|   1|   1000000|     100|0.52956823|  PASSED
      rgb_lagged_sum|   2|   1000000|     100|0.95958846|  PASSED
      rgb_lagged_sum|   3|   1000000|     100|0.95145243|  PASSED
      rgb_lagged_sum|   4|   1000000|     100|0.13284355|  PASSED
      rgb_lagged_sum|   5|   1000000|     100|0.50651321|  PASSED
      rgb_lagged_sum|   6|   1000000|     100|0.67578443|  PASSED
      rgb_lagged_sum|   7|   1000000|     100|0.28159075|  PASSED
      rgb_lagged_sum|   8|   1000000|     100|0.70439598|  PASSED
      rgb_lagged_sum|   9|   1000000|     100|0.95701984|  PASSED
      rgb_lagged_sum|  10|   1000000|     100|0.11591970|  PASSED
      rgb_lagged_sum|  11|   1000000|     100|0.79622842|  PASSED
      rgb_lagged_sum|  12|   1000000|     100|0.30363114|  PASSED
      rgb_lagged_sum|  13|   1000000|     100|0.66673746|  PASSED
      rgb_lagged_sum|  14|   1000000|     100|0.08939797|  PASSED
      rgb_lagged_sum|  15|   1000000|     100|0.15096120|  PASSED
      rgb_lagged_sum|  16|   1000000|     100|0.31977071|  PASSED
      rgb_lagged_sum|  17|   1000000|     100|0.27180216|  PASSED
      rgb_lagged_sum|  18|   1000000|     100|0.69988188|  PASSED
      rgb_lagged_sum|  19|   1000000|     100|0.47622405|  PASSED
      rgb_lagged_sum|  20|   1000000|     100|0.69420826|  PASSED
      rgb_lagged_sum|  21|   1000000|     100|0.99297760|  PASSED
      rgb_lagged_sum|  22|   1000000|     100|0.60131473|  PASSED
      rgb_lagged_sum|  23|   1000000|     100|0.62287604|  PASSED
      rgb_lagged_sum|  24|   1000000|     100|0.70013973|  PASSED
      rgb_lagged_sum|  25|   1000000|     100|0.65860222|  PASSED
      rgb_lagged_sum|  26|   1000000|     100|0.93448843|  PASSED
      rgb_lagged_sum|  27|   1000000|     100|0.99635422|   WEAK
      rgb_lagged_sum|  28|   1000000|     100|0.29584189|  PASSED
      rgb_lagged_sum|  29|   1000000|     100|0.81313054|  PASSED
      rgb_lagged_sum|  30|   1000000|     100|0.64124408|  PASSED
      rgb_lagged_sum|  31|   1000000|     100|0.94116582|  PASSED
      rgb_lagged_sum|  32|   1000000|     100|0.96364896|  PASSED
     rgb_kstest_test|   0|     10000|    1000|0.13971491|  PASSED
     dab_bytedistrib|   0|  51200000|       1|0.65378962|  PASSED
             dab_dct| 256|     50000|       1|0.86099385|  PASSED
Preparing to run test 207.  ntuple = 0
        dab_filltree|  32|  15000000|       1|0.80254877|  PASSED
        dab_filltree|  32|  15000000|       1|0.56933154|  PASSED
Preparing to run test 208.  ntuple = 0
       dab_filltree2|   0|   5000000|       1|0.71661701|  PASSED
       dab_filltree2|   1|   5000000|       1|0.34205581|  PASSED
Preparing to run test 209.  ntuple = 0
        dab_monobit2|  12|  65000000|       1|0.80589488|  PASSED

Java: Klasse mit korrektem Interface dynamisch laden

21.10.2015 yahe code java legacy

Bei größeren Anwendungen, die flexibel in verschiedenen Bereichen einsetzbar sein sollen, kommt schnell der Wunsch auf, diese erweiterbar zu gestalten, sodass andere Nutzer zur Not eigene Erweiterungen schreiben können. Java bietet hierfür mit seinen von Hause aus mitgebrachten Classloadern bereits die passende Grundlage, diese muss nur noch richtig zusammengesetzt werden.

Um das Vorgehen einmal darzustellen, habe ich ein kleines Projekt aufgesetzt, das aus drei Teilen besteht:

  1. Die Klassenbibliothek "lib", in der ein Interface definiert ist: Das Interface dient als Schnittstelle zwischen der eigentlichen Anwendung und der Erweiterung. Durch Zuhilfenahme eines Interfaces müssen die späteren Methodenaufrufe des Plugins nicht über Reflection realisiert werden.
  2. Die Klassenbibliothek "plugin", in der die Erweiterung implementiert ist: Die Erweiterung ist eine Klasse, die das Interface aus der Klassenbiliothek "lib" implementiert.
  3. Die Anwendung "app", die die eigentliche Anwendungslogik implementiert: Die Anwendung ist der eigentliche Nutznießer der Erweiterbarkeit. Sie lädt das Plugin und führt den darin enthaltenen Code aus.

Fangen wir mit der Implementierung der Klassenbibliothek "lib" an. Die beschriebenen Schritte sind beispielhaft für die Entwicklungsumgebung Netbeans, sollten in anderen IDEs jedoch genauso einfach umsetzbar sein:

  1. Zuerst legen wir ein neues Projekt an und wähle als Projektart "Java Class Library" aus. Als Projektnamen vergeben wir einfach den Namen "lib".
  2. Als nächstes legen wir im Projekt eine neue Datei an und wähle als Typ das "Java Package" aus. Als Namen erhält es in diesem Beispiel "com.example.loadclass.lib".
  3. Innerhalb des Packages wiederum legen wir eine neue Datei vom Typ "Java Interface" an. Die Klasse erhält den Namen "ExampleInterface".
  4. Der Inhalt der Interface-Datei ist in diesem Beispiel minimal:
package com.example.loadclass.lib;

public interface ExampleInterface {

    public String returnValue();

}

Nun machen wir weiter mit der Implementierung des Plugins:

  1. Wir legen ein weiteres Projekt an und wähle als Projektart wieder die "Java Class Library" aus. Als Projektnamen vergeben wir dieses Mal den Namen "plugin".
  2. Mit einem Rechtsklick auf den Projekteintrag im Projektbrowser gehen wir auf "Properties" und im Konfigurationsfenster auf die Kategorie "Libraries". Hier können wir über "Add Project..." nun das vorhin angelegte Projekt "lib" als Abhängigkeit aufnehmen. Nach einem Klick auf "OK" geht es weiter.
  3. Wir legen im Projekt eine neue Datei an und wählen als Typ das "Java Package" aus. Als Namen erhält es nun "com.example.loadclass.plugin".
  4. Innerhalb des Packages legen wir eine neue Datei vom Typ "Java Class" an. Die Klasse erhält den Namen "ExamplePlugin".
  5. Die Funktion des Plugins ist entsprechend des Interfaces ebenfalls minimal:
package com.example.loadclass.plugin;

import com.example.loadclass.lib.ExampleInterface;

public class ExamplePlugin implements ExampleInterface {

    @Override
    public String returnValue() {
        return "Hello world!";
    }

}

Bis hierhin gibt es, abgesehen davon, dass wir Interface und Implementierung in eigene Projekte auslagern, wenig Neues im Vergleich zu einer einfachen Java-Anwendung. Spannend wird es in der eigentlichen Anwendung, die wir nun anlegen:

  1. Wir legen ein weiteres Projekt an und wählen nun als Typ die "Java Application" aus. Als Namen erhält das Projekt dieses Mal den Wert "app". Zudem wählen wir aus, dass eine Main-Klasse mit dem Namen "com.example.loadclass.app.Main" erstellt werden soll.
  2. Als nächsten Schritt machen wir wieder einen Rechtsklick auf den Projekteintrag im Projektbrowser, gehen auf "Properties" und im Konfigurationsfenster auf die Kategorie "Libraries". Über "Add Project..." nehmen wir das Projekt "lib" wieder als Abhängigkeit auf und klicken abschließend auf "OK".
  3. Anschließend können wir die Main-Klasse mit Leben füllen. Wir beginnen mit ein paar Definitionen, die später in den Methoden benötigt werden. Die Methode "checkName()" dient dazu, herauszufinden, ob ein angegebener Plugin-Pfad tatsächlich korrekt ist. Als Pluginpfad verwenden wir die Form "<Klassenname>@<Bibliothekspfad>". Die Methode "loadClass()" verwendet den URLClassLoader, um die angegebene Klasse zu laden. Die Methode "createObject()" erzeugt eine Instanz der geladenen Klasse. Und in der Methode "main()" wird schlussendlich alles zusammengefügt:
package com.example.loadclass.app;

import com.example.loadclass.lib.ExampleInterface;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;

public class Main {

    protected static final String LOADCLASS_CLASS_EXTENSION = ".class";
    protected static final char   LOADCLASS_CLASS_SEPARATOR = '.';
    protected static final String LOADCLASS_JAR_EXTENSION   = ".jar";
    protected static final char   LOADCLASS_PATH_SEPARATOR  = '@';
    protected static final String LOADCLASS_URL_PREFIX      = "file://";

    protected static final String APP_PLUGIN_CLASS = "com.example.loadclass.plugin.ExamplePlugin";
    protected static final String APP_PLUGIN_JAR   = "/Users/kenny/Desktop/loadClass/plugin/dist/plugin.jar";
    protected static final String APP_PLUGIN_PATH  = APP_PLUGIN_CLASS + "@" + APP_PLUGIN_JAR;

    // structure is "<class>@<path>", where "<path>" may be a folder or a *.jar file
    protected static boolean checkName(String aString) {
        boolean lResult = false;

        if (null != aString) {
            // the combinator must not be at the very beginning or at the very end
            if ((aString.indexOf(LOADCLASS_PATH_SEPARATOR) > 0) &&
                (aString.indexOf(LOADCLASS_PATH_SEPARATOR) < aString.length()-1)) {
                String lClassName = aString.substring(0, aString.indexOf(LOADCLASS_PATH_SEPARATOR));
                String lPathName  = aString.substring(aString.indexOf(LOADCLASS_PATH_SEPARATOR)+1);

                File lPath = new File(lPathName);
                if (lPath.exists()) {
                    if (lPath.isFile()) {
                        if (lPathName.endsWith(LOADCLASS_JAR_EXTENSION)) {
                            try (JarFile lJarFile = new JarFile(lPath)) {
                                if (null != lJarFile.getJarEntry(lClassName.replace(LOADCLASS_CLASS_SEPARATOR,
                                                                                    File.separatorChar) +
                                                                 LOADCLASS_CLASS_EXTENSION)) {
                                    lResult = true;
                                }
                            } catch (Exception lException) {}
                        }
                    } else {
                        if (lPath.isDirectory()) {
                            // fix path delimiter
                            if (File.separatorChar != lPathName.charAt(lPathName.length()-1)) {
                                lPathName = lPathName + File.separator;
                            }

                            // check if the class exists
                            lPath = new File(lPathName +
                                             lClassName.replace(LOADCLASS_CLASS_SEPARATOR, File.separatorChar) +
                                             LOADCLASS_CLASS_EXTENSION);
                            if (lPath.isFile()) {
                                lResult = true;
                            }
                        }
                    }
                }
            }
        }

        return lResult;
    }

    protected static Class loadClass(String aName) {
        Class lResult = null;

        if (checkName(aName)) {
            // get name parts of parameter
            String lClassName = aName.substring(0, aName.indexOf(LOADCLASS_PATH_SEPARATOR));
            String lPathName  = aName.substring(aName.indexOf(LOADCLASS_PATH_SEPARATOR)+1);

            // check that directory name ends with path separator
            if (new File(lPathName).isDirectory()) {
                if (File.separatorChar != lPathName.charAt(lPathName.length()-1)) {
                    lPathName = lPathName + File.separator;
                }
            }

            try {
                URL lURL = new URL(LOADCLASS_URL_PREFIX + lPathName);

                URLClassLoader lClassLoader = new URLClassLoader(new URL[]{lURL},
                                                                 ClassLoader.getSystemClassLoader());

                lResult = lClassLoader.loadClass(lClassName);
            } catch (Exception lException) {}
        }

        return lResult;
    }

    protected static Object createObject(String aName, Class aInterface) {
        Object lResult = null;

        if (null != aInterface) {
            Class lClass = loadClass(aName);
            if (null != lClass) {
                boolean lCorrectInterface = false;

                for (Class lInterface : lClass.getInterfaces()) {
                    if (aInterface.equals(lInterface)) {
                        lCorrectInterface = true;
                        break;
                    }
                }

                if (lCorrectInterface) {
                    try {
                        lResult = lClass.newInstance();
                    } catch (Exception lException) {}
                }
            }
        }

        return lResult;
    }

    public static void main(String[] args) {
        ExampleInterface lObject = (ExampleInterface)createObject(APP_PLUGIN_PATH,
                                                                  ExampleInterface.class);
        if (null != lObject) {
            System.out.println(lObject.returnValue());
        }
    }

}

Wenn ihr die Pakete "lib.jar", "plugin.jar" und "app.jar" alle einmal erzeugt habt, müsst ihr euch ansehen, unter welchem Dateipfad das Plugin-Paket erreichbar ist. In meinem Beispiel ist es "/Users/kenny/Desktop/loadClass/plugin/dist/plugin.jar". Diesen vollständigen Pfad müsst ihr in die Variable "APP_PLUGIN_JAR" schreiben. In einem tatsächlichen Anwendungsfall würde man solche Informationen dann wahrscheinlich eher aus einer Konfigurationsdatei auslesen.


IPFire via Mac OS X auf ALIX 2D13 installieren

01.10.2015 yahe administration legacy linux security

Lange Zeit habe ich die kleine Firewall-Distribution m0n0wall eingesetzt, um die Edge-Firewall, sprich, die äußerste Firewall zwischen dem eigenen Netz und dem Internet, in meinem heimischen Netzwerk zu betreiben. Leider wurde die Weiterentwicklung von m0n0wall vor einer Weile eingestellt. Ich musste also früher oder später einen Ersatz finden. Ich sah mich ein wenig um und fand das deutsche Projekt IPFire, einen Fork des sehr bekannten, aber nicht mehr gepflegten IPCop.

Als Hardware sollte mein bereits vorhandenes ALIX 2D13 von PC Engines zum Einsatz kommen. Dabei handelt es sich um ein Embedded-Board mit CF-Kartenleser, serieller Schnittstelle und einer 500MHz AMD Geode LX800 CPU. Von seinem Bruder, dem ALIX 2D3, unterscheidet es sich eigentlich nur durch die Pufferbatterie für die BIOS-Uhrzeit.

Um mit der seriellen Schnittstelle kommunizieren zu können, braucht man einen USB-zu-Seriell-Adapter, den man problemlos bei Amazon finden kann. Diese enthalten typischerweise den Prolific PL2303 Chipsatz, für den man die passenden Treiber beim Hersteller finden kann. Vergesst nicht das Nullmodem-Kabel, das ihr ebenfalls brauchen werdet. Auch das findet man bei Amazon. Um die CF-Karte beschreiben zu können, braucht man weiterhin einen CF-Kartenleser, den man ebenfalls bei Amazon findet. Abschließend braucht man noch eine ordentliche Terminalemulation. Hier habe ich dieses Mal ZOC ausprobiert, das man 30 Tage lang kostenlos testen kann.

Bevor wir mit dem Installieren von IPFire anfangen können, müssen wir wahrscheinlich erst einmal die Firmware des ALIX-Boards aktualisieren. Alle wichtigen Informationen zum ALIX-Board findet man auf der dazugehörigen Support-Seite, inklusive der Firmware-Version 0.99m. Mit dieser allein können wir jedoch noch nichts anfangen. Wir benötigen noch ein Betriebssystem, das wir auf dem ALIX-Board booten können, um das Firmware-Image flashen zu können. Auf der Webseite von PC Engines sind mehrere Möglichkeiten beschrieben, wie man das mit einem FreeDOS-Image lösen kann. Die aus meiner Sicht einfachste Variante ist es, das fertige Image herunterladen, dieses auf eine CF-Karte flashen, die Dateien aus der ebenfalls heruntergeladenen Firmware-Version 0.99m mit auf die CF-Karte kopieren und das ganze Paket anschließend booten und verwenden:

  1. Wenn wir das FreeDOS-Image entpackt haben, haben wir eine Datei namens "./freedos_alixupdate_0.99.img".
  2. Um die Datei zu flashen, stecken wir die CF-Karte in den CF-Kartenleser.
  3. Wir öffnen die Terminal-Anwendung von macOS und wechseln in den Ordner, in dem das FreeDOS-Image liegt.
  4. Nun lassen wir uns mit dem Befehl "diskutil list" die Liste der gemounteten Speichermedien anzeigen.
  5. In dieser Liste müssen wir den Namen der CF-Karte ausfindig machen, der die Form "/dev/disk<Zahl>" haben sollte. Man kann sich dabei z.B. an der Größe der CF-Karte orientieren.
  6. Wenn wir den Namen der CF-Karte ausfindig gemacht haben, können wir die entsprechende Disk mit dem Befehl "diskutil unmountDisk /dev/disk<Zahl>" unmounten.
  7. Nun sind wir soweit, dass wir das FreeDOS-Image mit dem Befehl "sudo dd if=./freedos_alixupdate_0.99.img of=/dev/disk<Zahl>" auf die CF-Karte flashen können.
  8. Das Flashen sollte relativ schnell gehen. Danach sollte macOS die Karte wieder erkennen und euch ermöglichen, die Dateien darauf zu bearbeiten.
  9. Nun könnt ihr die Dateien aus dem entpackten ALIX-Firmware-Download auf die CF-Karte kopieren ("./alix1.bin", "./ALIX1.IMG", "./alix2.bin", "./ALIX2.IMG", "./alix5.bin", "./ALIX5.IMG", "./alixbios.txt" und "./sb.com").
  10. Abschließend könnt ihr die CF-Karte mit dem Befehl "diskutil eject /dev/disk<Zahl>" auswerfen.

Die so vorbereitete Karte könnt ihr nun verwenden, um die Firmware eures ALIX-Boards zu aktualisieren. Das geht leider nicht vollkommen automatisch. Deshalb müssen wir nun alles vorbereiten, um die serielle Konsole des ALIX-Boards verwenden zu können. Hierfür solltet ihr als erstes mal die Treiber eures USB-zu-Seriell-Adapters installieren. Bei mir musste das Systems dabei neu gestartet werden. Nach der Installation sollten zwei neue Devices im System erscheinen ("/dev/cu.usbserial" und "/dev/tty.usbserial"). Wenn das der Fall ist, könnt ihr nun euren Terminalemulator starten. Hier müsst ihr nun konfigurieren, dass sich der Emulator mit der seriellen Schnittstelle des ALIX-Boards verbinden kann. Hier Schritt für Schritt das Vorgehen für ZOC:

  1. Verbindet den USB-Stecker eures USB-zu-Seriell-Adapters mit eurem Mac.
  2. Öffnet ZOC und startet eine neue Verbindung.
  3. Im sich neu öffnenden Einstellungsfenster gebt ihr ein, dass ihr das Verbindungsprofil "Standard.zoc" verwenden wollt, als Verbindungstyp setzt ihr "Serial/Direct" und als Emulation nehmt ihr "Xterm".
  4. Nun klickt ihr auf den "Bearbeiten..."-Button neben dem Verbindungsprofil, wodurch sich ein weiteres Fenster öffnet.
  5. Im neuen Fenster geht ihr links in der Liste auf "Verbindungstyp" und wählt "Serial/Direct" aus. Beim COM-Port drückt ihr nun auf "Scan..." und solltet einen Eintrag in der Form "/dev/cu.usbserial" angezeigt bekommen, den ihr auswählt. Weiterhin wählt ihr als Baud-Geschwindigkeit den Wert "38400". Das ist der Standardwert für ALIX-2-Boards gemäß des Handbuchs.
  6. Wenn ihr das soweit habt, könnt ihr nun auf "Speichern" klicken und im vorherigen Dialog auf "Verbinden" gehen. Euch sollte nun eine Fehlermeldung angezeigt werden, die ihr einfach mit "Abbruch" wegklicken könnt.

ZOC Serial Konfiguration ZOC Serial Konfiguration ZOC Serial Konfiguration

Euch sollte nun angezeigt werden, dass die Verbindung aufgebaut werden konnte. Daher könnt ihr nun das ALIX-Board vorbereiten. Steckt die vorbereitete CF-Karte in den dafür vorgesehenen Schacht, verbindet das Nullmodem-Kabel mit dem ALIX-Board und mit dem USB-zu-Seriell-Adapter, der bereits mit eurem Mac verbunden ist. Verbindet nun abschließend das ALIX-Board mit seinem Netzteil. Das ALIX-Board sollte nun eine Speicherprüfung machen und anschließend FreeDOS booten. Das sieht im Terminalemulator in etwa so aus:

PC Engines ALIX.2 v0.99h
640 KB Base Memory
261120 KB Extended Memory

01F0 Master 848A TS4GCF133
Phys C/H/S 7769/16/63 Log C/H/S 971/128/63
FreeDOS kernel build 2036 cvs [version Aug 18 2006 compiled Aug 18 2006]...................123
Kernel compatibility 7.10 - WATCOMC - 80386 CPU required - FAT32 support

(C) Copyright 1995-2006 Pasquale J. Villani and The FreeDOS Project.
All Rights Reserved. This is free software and comes with ABSOLUTELY NO
WARRANTY; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation;
either version 2, or (at your option) any later version.
 - InitDiskWARNING: using suspect partition Pri:1 FS 06: with calculated values    3-113-49 instead of    1-242-49
C: HD1, Pri[ 1], CHS=    0-1-1, start=     0 MB, size=    15 MB
FreeDOS HIMEM64 3.26 [Aug 25 2006] (c) 1995, Till Gerken 2001-2006 tom ehlert
HIMEM - Always On A20 method used
Kernel: allocated 43 Diskbuffers = 22876 Bytes in HMA

FreeCom version 0.84-pre2 XMS_Swap [Aug 28 2006 00:29:00]
C:\>

Startet nun das Programm "sb.com" aus, das sich um das Firmware-Update kümmert:

C:\>sb.com
ALIX flash update (C)2007-2011 PC Engines GmbH
Flash ID = 2 FFFF GPI = FF
Flash ID = 1 FFFF GPI = FF
Flash ID = 0 9D37 GPI = 01
Reading 512KB flash image alix1.bin................
Compare Erase Program Verify - update OK.
C:\>

Weiterhin könnt ihr nun schonmal die Baud-Rate des ALIX-Boards neu setzen, damit ihr später IPFire problemlos über die serielle Konsole verwenden könnt. Dazu rebootet ihr das Board (z.B. indem ihr es kurz stromlos schaltet). Während beim Hochfahren noch der Speichertest des ALIX-Boards läuft, drückt ihr schnell die "S"-Taste, woraufhin das ALIX-Board zur BIOS-Konfiguration wechseln sollte, was in etwa so aussieht:

PC Engines ALIX.2 v0.99h
640 KB Base Memory
261120 KB Extended Memory

01F0 Master 848A TS4GCF133
Phys C/H/S 7769/16/63 Log C/H/S 971/128/63

BIOS setup:

(9) 9600 baud (2) 19200 baud *3* 38400 baud (5) 57600 baud (1) 115200 baud
*C* CHS mode (L) LBA mode (W) HDD wait (V) HDD slave (U) UDMA enable
(E) PXE boot enable
*M* Memory test
(P) late PCI init
*R* Serial console enable
(X) Xmodem upload
(Q) Quit

Hier gebt ihr nun die "1"-Taste ein, denn IPFire wird später für die serielle Ausgabe die Geschwindigkeit von 115200 Baud verwenden. So könnt ihr dann den Bootvorgang des ALIX-Boards und die Ausgabe von IPFire ohne Probleme verfolgen. Nach Eingabe der Taste wechselt das ALIX-Board in eine andere Ansicht, in der ihr mit der "Q"-Taste und anschließendem Druck auf die "Y"-Taste die Änderung bestätigen müsst:

(9) 9600 baud (2) 19200 baud (3) 38400 baud (5) 57600 baud *1* 115200 baud
*C* CHS mode (L) LBA mode (W) HDD wait (V) HDD slave (U) UDMA enable
(E) PXE boot enable
*M* Memory test
(P) late PCI init
*R* Serial console enable
(X) Xmodem upload
(Q) Quit

Save changes Y/N ?
Writing setup to flash... OK

Wenn ihr das geschafft habt, könnt ihr das ALIX-Board erst einmal wieder vom Strom trennen und die CF-Karte entfernen. Auf diese werden wir als nächstes das IPFire-Image installieren, das man von deren Webseite herunterladen kann. Die CF-Karte, die für IPFire verwendet wird, muss mindestens 1GB groß sein. Größere Karten sind in Ordnung. IPFire wird bei der Installation dafür sorgen, dass der größere Speicherplatz mit verwendet wird. Beim Flashen des IPFire-Images gehen wir im Grunde genauso vor, wie auch vorhin schon:

  1. Wenn wir das IPFire-Image entpackt haben, haben wir eine Datei namens "./ipfire-2.21.2gb-ext4.i586-full-core124.img".
  2. Um die Datei zu flashen, stecken wir die CF-Karte in den CF-Kartenleser.
  3. Wir öffnen die Terminal-Anwendung von macOS und wechseln in den Ordner, in dem das IPFire-Image liegt.
  4. Nun lassen wir uns mit dem Befehl "diskutil list" die Liste der gemounteten Speichermedien anzeigen.
  5. In dieser Liste müssen wir den Namen der CF-Karte ausfindig machen, der die Form "/dev/disk<Zahl>" haben sollte. Man kann sich dabei z.B. an der Größe der CF-Karte orientieren.
  6. Wenn wir den Namen der CF-Karte ausfindig gemacht haben, können wir die entsprechende Disk mit dem Befehl "diskutil unmountDisk /dev/disk<Zahl>" unmounten.
  7. Nun sind wir soweit, dass wir das IPFire-Image mit dem Befehl "sudo dd if=./ipfire-2.21.2gb-ext4.i586-full-core124.img of=/dev/disk<Zahl>" auf die CF-Karte flashen können.
  8. Das Flashen sollte dieses Mal etwas mehr Zeit in Anspruch nehmen, da es mit 1GB doch recht groß ist, habt also Geduld. macOS wird den Inhalt der CF-Karte nach dem Flash-Vorgang wahrscheinlich nicht erkennen, aber das ist okay.
  9. Wir werfen die CF-Karte einfach mit dem Befehl "diskutil eject /dev/disk<Zahl>" aus.

Bevor wir weitermachen können, müssen wir noch den Terminalemulator umkonfigurieren, damit er die Geschwindigkeit von 115200 Baud verwendet. Bei ZOC gehen wir wieder so vor, dass wir das Programm starten, eine neue Verbindung aufbauen und im Einstellungsdialog neben dem Verbindungsprofil auf "Bearbeiten..." klicken. Im neuen Dialog gehen wir wieder auf "Verbindungstyp", danach auf "Serial/Direct" und wählen die Geschwindigkeit 115200 Baud aus. Abschließend klicken wir auf "Speichern" und klönnen nunim vorherigen Dialog auf "Verbinden" gehen.

ZOC Serial Konfiguration

Nun können wir die neu geflashte CF-Karte wieder in den CF-Kartenslot des ALIX-Boards schieben und dieses mit dem Strom verbinden. Nach dem obligatorischen Speichercheck sollte nun der Bootloader GRUB geladen werden, in dem IPFire standardmäßig nach wenigen Sekunden gebootet wird. Beim ersten Hochfahren reformatiert IPFire die CF-Karte automatisch und startet dann direkt wieder neu. Beim zweiten Hochfahren sollte dann eine pseudografische Oberfläche gestartet werden, die ihr mit der Tastatur steuern könnt. Dort werden Dinge wie das Root-Passwort der seriellen Konsole und des SSH-Zugangs, das Passwort des "admin"-Accounts der Weboberfläche, sowie der Netzwerktyp und die zugehörigen Netzwerkadressen erfragt. Das initiale Setup ist im Wiki von IPFire noch ausführlicher beschrieben.

Wenn ihr das initiale Setup erfolgreich absolviert habt, könnt ihr euren Mac mit einem Ethernet-Kabel mit dem von euch konfigurierten "grünen" LAN-Anschluss (also dem Anschluss, dem ihr das interne Netzwerk zugeordnet habt) verbinden und anschließend die webbasierte Konfigurationsoberfläche von IPFire aufrufen. Dazu müsst ihr euch erinnern, welche IP-Adresse ihr dem internen Netzwerkanschluss der Firewall gegebenen habt (typischerweise sowas wie "192.168.0.1") und könnt anschließend im Browser die URL "https://<ip>:444" aufrufen. Ihr solltet per HTTP-Basic-Authentication nach den Zugangsdaten des "admin"-Account gefragt werden und anschließend auf der Startseite der Weboberfläche landen.

IPFire Weboberfläche IPFire Weboberfläche

Ist das der Fall? Dann herzlichen Glückwunsch! Ihr habt soeben die Firmware eures ALIX-Boards aktualisiert und anschließend erfolgreich IPFire darauf installiert. Die weitere Konfiguration lässt sich durch die moderne Weboberfläche ziemlich schnell erledigen.


Search

Links

RSS feed

Categories

administration (40)
arduino (12)
calcpw (2)
code (33)
hardware (16)
java (2)
legacy (113)
linux (27)
publicity (6)
review (2)
security (58)
thoughts (21)
windows (17)
wordpress (19)