Training - Beratung - Projektarbeiten

www.David-Tielke.de

Mobile Developer Conference 2012 in Hamburg

Am 13.2 und 14.2 fand, wie jedes Jahr, die Mobile Developer Conference 2012 statt. An zwei Tagen wurden in jeweils drei Tracks aktuelle T20120213_MDevCon_Tielke_0010hemen rund um iOS, Android, Windows Phone 7 und natürlich auch Trends präsentiert und mit den Teilnehmern diskutiert. Die sehr gut besuchte Konferenz fand im Radisson Blue auf dem Hamburger Messegelände statt und bot, mit großen und gut ausgestatteten Konferenzräumen die idealen Rahmenbedingungen für eine großartige Konferenz. Zum ersten Mal war auch ich mit einem Vortrag dabei. Da die Entwicklung von mobilen Anwendungen auf immer mehr Plattformen publiziert werden muss und es dabei wichtig ist, die Kosten in einem erträglichen Rahmen zu halten und eine gut erweiterbare und wartbare Software zu schaffen, wählte ich als Thema “Unite the Worlds – .NET als Backend”. In dem 60-minütigen Vortrag ging es neben der Frage “Warum überhaupt ein einheitliches Backend bei mobilen Anwendungen?” vor allem um die konkrete Umsetzung und warum Microsoft .NET dafür eine ideale Plattform ist. Der komplett gefüllte Raum bot eine super Atmosphäre und spannende Fragen und Diskussionen mit den Teilnehmern und hat wirklich enorm viel Spaß gemacht. Nach dem Vortrag entstand eine rege Diskussion mit den Teilnehmern in der Lounge, bei der sich viele als überrascht zeigten, wie einfach ein Backend mit .NET zu realisieren ist und wie viele Probleme damit vermieden werden können.

Zusätzlich führte der Chefredakteur Markus Stäuble mit mir ein Interview zu meiner Markteinschätzung von Windows Phone 7:

Ich möchte mich auf diesem Wege noch einmal bei allen Teilnehmern für die rege Teilnahme und das Feedback bedanken und hoffe mit meinem Vortrag den ein oder anderen entscheidenden Impuls für Ihre mobilen Projekte gegeben zu haben. Natürlich möchte ich mich auch bei der Neuen Mediengesellschaft Ulm für die Einladung zu dieser Konferenz bedanken und hoffe beim nächsten Mal wieder dabei sein zu können.

Links:
Mobile Developer Conference 2012
Fotos auf Facebook

Ist der .NET-Entwickler der neue VB6-Entwickler?

Etwas mehr als 12 Stunden sind seit der BUILD vergangen… Die Konferenz die endlich Klarheit bringen sollte, die uns Entwicklern die Marschrichtung für die Zukunft aufzeigen sollte. Das Gefühl war schon irgendwie beängstigend am Vormittag, die Nervosität bei vielen Leuten spürbar. Durch die Verschwiegenheit von Microsoft im Vorfeld wurden die wildesten Gerüchte gestreut: Wird HTML5&JS die neue primäre Sprache für Windowsentwicklung? Wird es .NET weiterhin in seiner jetzigen Form geben? Haben wir viele Jahre eine Technologie gelernt, die nun veraltet ist?

Die Keynote begann, Windows 8 wurde gezeigt und dann war es raus: Es wird eine neue Laufzeitumgebung geben, genannt Windows RT, welche für die Entwicklung von Windows 8 Apps genutzt wird. Diese Runtime kann mit diversen Sprachen genutzt werden, neben HTML/JS und C++ glücklicherweise auch C#… Das HTML gesetzt war, vermuteten viele bereits, aber eine wirkliche Bekenntnis zu C# und zu .NET gab Microsoft bis dato nicht. Aber ist jetzt alles gut für das .NET Framework? Nein – ich denke nicht!

Wirklich viele Informationen über die nächste .NET Version gab es ja nicht. Bei der Sessionlist der BUILD ist mir folgende Session aufgefallen, auf die ich meine Argumentation stütze:

image

Das .NET-Framework wird also neue Erweiterungen bekommen, um Anwendungen für Windows 8 zu schreiben, bzw. die Windows RT aus .NET heraus nutzen zu können. Das man neue Erweiterungen für ein Betriebssystem bereitstellt, kennen wir bereits von Windows 7. Allerdings wurde das damals als “Windows 7 API Code Pack” bereitgestellt und nicht als Bestandteil einer neuen Version von .NET zu Verfügung gestellt.

Das Konzept von Microsoft war es (einmal!?), ein Framework für alle Windowsplattformen zur Verfügung zu stellen. Damit war sichergestellt, das Anwendungen, die auf einer Frameworkversion auf Betriebssystem X laufen, auch auf einem Betriebssystem Y läuft, vorausgesetzt es existiert eine Runtime dazu. Sollte Microsoft also wirklich die API für Windows 8 in .NET integrieren, wäre diese Plattformunabhängigkeit Geschichte. Macht man sich da das eigene Konzept kaputt?

Schaut man sich die Sessions zur BUILD an, wird eines deutlich: Der Kernpunkt dieser Konferenz ist neben Windows 8 vor allem HTML5 und JavaScript.

  • 9 Sessions zu .NET
  • 7 Sessions zu ASP.NET
  • 7 Sessions zu C#
  • 40 (!!!) Sessions zu HTML5

Merkwürdig das HTML5 als Technologie die langjährig gefeierte Technologie .NET mit einer gigantischen Entwicklergemeinde zu überholen scheint. Schaut man sich die Sessions im Bereich .NET, ASP.NET und C# an, mangelt es an wirklichen Innovationen und es erweckt den Eindruck, das diese Frameworkversion einzig und allein für die Kompatibilität mit Windows 8 entwickelt wurde und um die Brücke für ASP.NET Entwickler hin zu HTML5 Metro UI zu schlagen.

Wir sind bald im zehnten Jahr des .NET-Zeitalters angekommen. In dieser Zeit hat sich viel Verändert, das Framework wurde massiv erweitert und um coole und innovative Features ergänzt, wie z.B. LINQ. Wir haben mit WPF eine UI-Technologie die in der Lage ist, hardwarebeschleunigte Oberflächen zu entwickeln die schick und modern aussehen. Nun hat Microsoft mit Windows 8 ihr Flaggschiff zurück auf 0 gestellt (mehr oder weniger), und so viele Bereiche neu entwickelt wie bei keinem Betriebssystem vorher. Aber womit wurden die Oberfläche von Windows entwickelt? Mit HTML5 und JavaScript! Wurde das Betriebssystem auf Basis von managed Code geschrieben? Nein, aber warum? Das Forschungsbetriebssytem Singularity von Microsoft Research hat doch gezeigt, das ein performantes managed OS möglich ist und zahlreiche Vorteile hat. Welche großen Microsoftprodukte gibt es den, die mit .NET umgesetzt wurden, wenn wir Sharepoint einmal außen vor lassen? Keins!

Diese Tatsache zeigt mir, das Microsoft selbst nicht mehr an eine Zukunft von .NET glaubt und diese Version 4.5 des Frameworks untermauert das Ganze noch einmal. Egal mit wem man im Vorfeld gesprochen hat und über ein mögliches AUS von .NET diskutierte, jeder entgegnete mit der selben Antwort “Microsoft wird doch keine Technologie sterben lassen, in die sie so viele Jahre so viel Geld investiert haben”. Aber haben die VB6 Entwickler das nicht früher vielleicht auch behauptet bevor .NET veröffentlicht wurde? Ich befürchte die .NET Entwickler werden die neuen VB6 Entwickler…

WCF und Username & Password Security: HowTo

Die Entwicklung von Serviceanwendungen mit der WCF ist in simplen HelloWorld-Beispielen sehr komfortabel. Möchte man allerdings erweiterte Funktionalitäten wie Sicherheit nutzen, verzweifelt man sehr schnell an den umfangreichen Einstellungsmöglichkeiten und der daraus resultierenden Konfigurationsdateien. Deshalb möchte an dieser Stelle zeigen, wie man es zum Laufen bekommt und wie das alles funktioniert, nachdem ich in den letzten Stunden selbst erfahren musste, das es doch nicht so einfach ist, wie man zu Anfang glaubt.

Ausgangslage

Wenn man auf der grünen Wiese beginnt, sollte man zunächst immer Client und Service ohne Sicherheitsmerkmale entwickeln und sicherstellen, dass dieser Teil bereits funktioniert, bevor man sich an Zertifikate & Co. heranwagt. Da ich nicht der Freund von generierten Clientproxies bin, erstelle ich den Dienst und den Client aus dem ServiceContract, der in einem shared Project liegt. Aus diesem Grund erstellen wir drei Projekte:

image

Abbildung 1: Die drei Projekte unserer Beispielanwendung

Da das Absichern eines Dienstes durch die WCF (fast) vollständig unabhängig von deren angebotenen Operationen ist, definieren wir einen möglichst einfachen ServiceContract:

namespace SharedContracts
{
    [ServiceContract]
    public interface IHelloService
    {
        [OperationContract]
        string Echo(string message);
    }
}

Listing 1: Der ServiceContract aus der Projektmappe SharedContracts

Der Contract wird von dem Dienst in der Projektmappe Service implementiert:

namespace Service
{
    class HelloService : IHelloService
    {
        public string Echo(string message)
        {
            return message;
        }
    }
}

Listing 2: Der Dienst in der Projektmappe Services

Durch die bereits erwähnte Abneigung zu generierten Proxies, fällt die Implementierung des Channels etwas komplizierter als normal aus:

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new ChannelFactory("HelloService");
            proxy.Open();
            IHelloService client = proxy.CreateChannel();
            string response = client.Echo("Huhu");
            Console.WriteLine(response);
            Console.ReadKey();
        }
    }
}

Listing 3: Der Client aus der Projektmappe Client

Authentifizierungsmodi

Die Windows Communication Foundation unterstützt diverse Möglichkeiten einen Client bei einem Service zu authentifizieren. Neben der Möglichkeit bestehende Windows-Authentifizierung (Kerberos / NTLM) oder Zertifikate zu nutzen, ist für einfachere Szenarien vor allem die Authentifizierung durch einen Benutzernamen und ein Passwort interessant. Was recht einfach klingt, entpuppt sich bei der Umsetzung durch WCF als durchaus kompliziert.

Zertifikate

Eigentlich könnte man denken, durch Angabe von Benutzername und Passwort im Client und einer entsprechenden Einstellung im Service, sollte die Anforderung erfüllt sein.Dem ist allerdings nicht so: Würde der Client seine Benutzerdaten im Klartext übertragen, wäre das sicherheitstechnisch im höchsten Maße kritisch, da Dritte diese ohne weiteres mitlesen könnten. Deshalb erfordert WCF unbedingt die Übertragung dieser Daten mit einer, durch ein Zertifikat verschlüsselten Verbindung.

Um ein solches Zertifikat zu bekommen, gibt es diverse Möglichkeiten. Eine wäre es ein solches signiertes Zertifikat bei einer vertrauenswürdigen Stelle ausstellen zu lassen, was zwar dem ganzen Szenario mehr Sicherheit verleihen würde, allerdings auch mit einem erhöhten Preis einher geht. Deshalb entscheiden wir uns in diesem Beispiel für die Möglichkeit ein solches Zertifikat selbst zu erstellen. Im SDK von Visual Studio existiert dazu das konsolenbasierte Tool makecert.exe. Da dieses Tool am Anfang durchaus recht kompliziert sein kann, empfehle ich an dieser Stelle das Tool Self-Cert, welches das Erstellen mit einer normalen Windowsoberfläche ermöglicht:

image

Abbildung 2: Das Tool Self-Cert zum Erstellen von Zertifikaten

Dabei gibt es, wie auch bei makecert.exe, zwei Möglichkeiten die Zertifikate generieren zu lassen. Zum einen kann ein generiertes Zertifikat als Datei exportiert werden und darüber hinaus direkt in den Zertifikatsspeicher von Windows gespeichert werden. Ich wähle als Namen david-tielke.de und speichere das Zertifikat direkt in meinem Zertifikatsspeicher. Wichtig dabei ist das die Option "Exportale private key…" aktiviert ist.

Um sicherzustellen, dass wir alles richtig gemacht haben, schauen wir uns den Zertifikatsspeicher im Windows einmal genauer an: (Start –> Ausführen (Oder WIN + R) –> mmc.exe –> Datei –> Snapin hinzufügen –> Zertifikate –> hinzufügen –> OK). Wenn das Zertifikat im Speicher gefunden wurde, war der Vorgang erfolgreich.

image

Abbildung 3: Das generierte Zertifikat wurde im Ordner Eigene Zertifikate abgelegt.

Wie für die sichere Kommunikation mit Zertifikaten erforderlich, wird der WCF-Service später das Zertifikat mit dem enthaltenen public Key an den Client senden, welcher dann diesen public Key nutzen wird, um die Kommunikation zu verschlüsseln. Damit der Service die Daten entschlüsseln kann, nutzt er den private Key. Während das Zertifikat mehr oder weniger frei zugänglich ist, muss ein private Key natürlich gesondert geschützt werden. Bei Windows werden die private Keys in speziellen Dateien gespeichert, welche im Ordner C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys zu finden sind. Allerdings ist die Ermittlung des zu einem Zertifikats assoziierten Dateinamens nicht so einfach.

image

Abbildung 4: Der Ordner mit verschiedenen private Keys.

Um unserem Service die Berechtigung zum lesen des private Keys zu geben, müssen wir ihm (bzw. dem ausführenden Benutzeraccount) die benötigten Dateirechte geben. Um nicht mühsam den Dateinamen zu einem Zertifikat zu ermitteln, können diese Rechte  im Zertifikatsmanager geändert werden (Rechtsklick auf Zertifikat –> Alle Aufgaben –> Private Schlüssel verwalten).

image

Abbildung 5: Die Berechtigungen für private Schlüssel können im Zertifikatsmanager geändert werden

image

Abbildung 6: Der ausführende Benutzer benötigt Leserechte auf den privaten Schlüssel

Hier wurde jedem Benutzer das lesen gestattet, im späteren Betrieb sollte das natürlich angepasst werden.

Damit ist unser Zertifikat erstellt, eingebunden und durch unseren Service nutzbar. Neben der Möglichkeit das Ganze durch den Zertifikatsspeicher von Windows verwalten zu lassen, bietet sich natürlich auch die Möglichkeit das Zertifikat direkt aus einer Datei zu laden, was allerdings nicht weniger kompliziert ist.

Authentifizierung des Benutzers

Damit der Service das Tupel aus Benutzername und Password auch auswerten kann, stehen diverse mögliche Validatoren zur Verfügung. Wir entscheiden uns für eine eigenen Implementierung, um beispielsweise einen eigenen Credentialstore zu nutzen. Dazu leiten wir eine Klasse von der Frameworkklasse UsernamePasswordValidator aus dem Namensbereich System.IdentityModel.Selectors ab und implementieren die abstrakte Methode Validate.

namespace Service
{
    class UserAuthenticator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName == "David" && password == "geheim")
                return;

            throw new SecurityException("Access denied");
        }
    }
}

Listing 4: Die Klasse UserAuthenticator prüft die empfangenen Credentials.

Konfiguration des Services

Das schöne an WCF (oder furchtbare, je nachdem wen man fragt…) ist Möglichkeit, die Sicherheit komplett in der Konfigurationsdatei zu beschreiben, ohne dazu unseren C#-Code verändern zu müssen.

Zunächst muss in dem entsprechenden Binding der CredentialMode Username aktiviert werden. Wir nutzen hier das wsHttpBinding, allerding sollte man vor Verwendung anderer Bindings in der MSDN überprüfen, ob dieses den CredentialMode Username auch unterstützt.

<!-- wsHttpBinding für Security umkonfigurieren -->    
<bindings>
  <wshttpbinding>
    <binding>
      <security>
        <message clientcredentialtype="UserName" />
      </security>
    </binding>
  </wshttpbinding>
</bindings>

Listing 5: Konfigurieren des wsHttpBindings

Danach konfigurieren wir das Serviceverhalten:

<!-- Servicezertifikat für Endpunkt definieren -->
<behaviors>
  <serviceBehaviors>
    <behavior name="wsEndpointBehavior">
      <serviceCredentials>
        
        <!-- Zertifikat definieren-->
        <serviceCertificate 
          findValue="david-tielke.de" 
          storeLocation="LocalMachine" 
          storeName="My" 
          x509FindType="FindBySubjectName"/>
            
        <!-- Custom NamePassword Validator definieren -->
        <userNameAuthentication userNamePasswordValidationMode="Custom" 
                                customUserNamePasswordValidatorType=
                                "Service.UserAuthenticator, Service"/>
       </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>

Listing 6: Konfiguration des Diensteverhaltens

Im Bereich “Zertifikat definieren” wird das zum Übertragen der Credentials eingerichtete Zertifikat angegeben und danach teilen wir der WCF mit, das die Credentials mit der selbsterstellen Klasse UserAuthenticator überprüft werden sollen.

Als letztes ist der Service selbst an der Reihe:

<!-- Service definieren -->
<services>
  <service name="Service.HelloService" 
              behaviorConfiguration="wsEndpointBehavior">
    <!-- Endpunkt-->
    <endpoint address="http://localhost:7940/HelloService"
              binding="wsHttpBinding"
              contract="SharedContracts.IHelloService"/>
  </service>      
</services>

Listing 7: Konfiguration des Services

Damit ist der Service fertig konfiguriert und kann aufgerufen werden.

Konfiguration des Clients

Während das erzeugte Zertifikat im Zertifikatsspeicher des Service-Hosts importiert werden musste, ist dies im Client nicht notwendig. Dieser bekommt nur zu Beginn der Kommunikation das Zertifikat und sendet dann seine Daten, welche mit dem public Key verschlüsselt wurden. Damit kann nur der Besitzer des private Keys des zugeordneten Zertifikats diese Daten entschlüsseln, also der Besitzer. Daher ist es enorm wichtig, dass der Absender auch wirklich vertrauenswürdig ist, denn mit der von uns gewählten Variante der Zertifikatserzeugung hätte sich auch jeder anderer ein Zertifikat mit meinem Namen erzeugen lassen können. Damit ein Zertifikat vertrauenswürdig ist, muss es von einer sogenannten Trusted Authority (TA) ausgestellt werden, welche den Zertifikatsinhaber zweifelsfrei identifizieren, beispielsweise durch einen Lichtbildausweis. Nachdem eine TA solch ein Zertifikat erstellt hat, bildet es aus den Zertifikatsdaten einen Hashwert und verschlüsselt ihn mit dem privaten Schlüssel der TA (die sogn. Signatur). Ein Beispiel für eine solche TA ist Verisign. Deren Zertifikat (mit public Key) ist auf einem Windows 7 Rechner bereits vorinstalliert, daher ist sichergestellt (insofern das Installationsmedium nicht manipuliert wurde), dass dieses Zertifikat echt ist. Bekommt nun der Client das Zertifikat vom Service welches  von Verisign signiert wurde, kann dieser mit dem public Key den signierten Hashwert entschlüsseln, selbst den Hashwert aus den Zertifikatsdaten berechnen und diese vergleichen. Sind beide Werte gleich, wurde das Zertifikat nicht verändert und ist echt. Ist als Absender der Name von Bob (sorry, ein PKI-Beispiel ohne Bob geht einfach gar nicht…) eingetragen, ist es auch definitiv Bobs Zertifikat. Stimmen die beiden Hashcodes nicht, wurde das Zertifikat nach dem signieren manipuliert und kann daher abgelehnt werden.

Standardmäßig akzeptiert ein WCF-Client nur vertrauenswürdige Zertifikate. Dies hat jedoch einige Einschränkungen und ist nicht zuletzt mit finanziellem Aufwand verbunden. Daher müssen wir im Serviceverhalten diese Überprüfung abschalten. Das damit verbundene Risiko nehmen wir bei diesem Beispiel in Kauf.

<!-- ENDPOINTVERHALTEN-->
<behaviors>
  <endpointBehaviors>
    <behavior name="endpointBehavior">
      <clientCredentials>
        <serviceCertificate>
          <!-- Auch nicht signierte Zertifikate akzeptieren -->
          <authentication certificateValidationMode="None"/>
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

Listing 8: Deaktivierung der Zertifikatsvalidierung

Als nächstes müssen wir, wie bereits im Service, den Credentialtype Username im Binding aktivieren:

<!-- BINDINGKONFIGURATION-->
<bindings>
  <wsHttpBinding>
    <binding>
      <security>
        <message clientCredentialType="UserName"/>
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

Listing 9: Aktivierung des Credentialtypes Username in der Bindingkonfiguration.

Als letztes müssen wir den Clientbereich selbst konfigurieren:

<client>
  <!-- Endpunkt zum Service-->
  <endpoint name="HelloService"
            address="http://localhost:7940/HelloService"
            binding="wsHttpBinding"
            contract="SharedContracts.IHelloService"
            behaviorConfiguration="endpointBehavior">
    <!-- Jeden Zertifikatsnamen akzeptieren -->
    <identity>
      <dns value="david-tielke"/>
    </identity>
  </endpoint>
</client>    

Listing 10: Der Endpunkt

Zunächst wird der Endpunkt definiert und mit dem Behavior konfiguriert. Danach ignorieren wir eine weitere Eigenart der Überprüfung von Zertifikaten: Normalerweise muss der Name eines Zertifikats dem Hostnamen entsprechen, in unserem Fall also Localhost. Ist dem nicht so, löst WCF eine Ausnahme aus. Damit der von uns definierte Wert david-tielke.de akzeptiert wird, definieren wir ihn hier direkt als Identitätswert.

Aufruf des Clients

Nachdem wir nun sowohl Client als auch Service für die Verwendung von Username/Passwort zur Authentifizierung eingerichtet haben, müssen wir diese Credentials natürlich auch beim Start des Dienstes angeben:

static void Main(string[] args)
{
    var proxy = new ChannelFactory<IHelloService>("HelloService");
    proxy.Credentials.UserName.UserName = "David";
    proxy.Credentials.UserName.Password = "geheim";
    proxy.Open();
    IHelloService client = proxy.CreateChannel();
    string response = client.Echo("Huhu");
    Console.WriteLine(response);
    Console.ReadKey();
}

Listing 11: Angabe von Benutzername und Kennwort beim Serviceaufruf.

Damit sollte alles erledigt sein und der Dienst ist jetzt nur noch durch Angabe von Benutzernamen und Passwort aufrufbar.

Fazit

Auch wenn die Anforderung recht einfach aussieht, ist es nicht unbedingt einfach einen Service durch Angabe von Benutzernamen und Passwort zu schützen. Ich hoffe das ich mit dieser kleinen Anleitungen einigen Entwicklern die mühseligen Stunden im Debugger oder in der MSDN ersparen konnte. Angemerkt sein sollte jedoch noch, das die hier verwendete Konfiguration im Produktivbetrieb natürlich noch durch ein von einer TA signierten Zertifikat erweitert werden sollte, denn sonst ist die Identität des Dienstes nicht sichergestellt und die leserechte für den privaten Schlüssel sollte natürlich auch nur dem Benutzer gewährt werden, der den jeweilgen Hostprozess ausführt.

Die Schritte noch einmal zusammengefasst:

  1. Client und Dienst ohne Security entwickeln und ggf. testen
  2. Zertifikat erzeugen und im Zertifikatsspeicher ablegen
  3. Rechte auf Zertifikat anpassen
  4. Ggf. eigenen CredentialValidator schreiben
  5. Service
    1. Binding konfigurieren (clientcredentialType="Username")
    2. Serviceverhalten konfigurieren (Zertifikat und CustomValidator)
    3. Service konfigurieren und Verhalten linken
  6. Client
    1. Endpunktverhalten konfigurieren (Zertifikatsvalidierung unterdrücken)
    2. Binding konfigurieren (clientcredentialType="Username")
    3. Endpunkt konfigurieren und Identität anpassen
    4. Credentials im Code setzen