WCF LiveID Authentication einer Windows 8 Store App – Teil 3

23. Januar 2013

div class=”articleAbstract”>In Teil 3 der Artikelserie wird eine transportgesicherte (SSL) WCF Kommunikation mit dem Service erstellt.

Überblick zur Artikelserie

Teil 1 gibt eine Übersicht über die eingesetzten Technologien und Portale
Teil 2 beschäftigt sich mit der Erzeugung und Konfiguration der Windows 8 Store App mit Live Anmeldung
Teil 3 erzeugt den WCF Service und sichert diesen über SSL ab
Teil 4 autorisiert den angemeldeten Live-Benutzer an den WCF Services
Teil 5 hosted den Service in Windows Azure

Teil 3 – WCF Kommunikation mit SSL Transportsicherung

Als Ausgangssituation wird die Implementierung der Windows Store App aus Teil 2 genutzt, die in diesem Teil um eine Service erweitert wird. Die Kommunikation wird dabei transportgesichert auf Basis von WCF erfolgen.

image

Die erste freudige Nachricht, dass WCF auch durch Windows Store Apps genutzt werden kann wird schnell getrübt durch die Erkenntnis, dass nur wenige Security Bindings unterstützt werden. Hierzu muss man berücksichtigen, dass WinRT nur eine sehr abgespeckte Version des .NET Frameworks unterstützt.
Unterstützt werden (laut http://msdn.microsoft.com/en-us/library/hh556233.aspx)

  • BasicHttpBinding
  • NetTcpBinding
  • NetHttpBinding
  • CustomBinding

Es fehlen somit potentiell die WS*Bindings, MsmqBindings sowie die FederationBindings. Speziell die FederationBindings fehlen sehr, da die Verbindung von Windows Store Apps mit Azure Services und Azure ACS sehr naheliegend ist.
Bleibt also für den Hausgebrauch noch das BasicHttpBinding. Dies kannte ich bislang nur als unsichere und unverschlüsselten Kommunikationsmechanismus – aber da war doch noch was … In meiner Erinnerungen taucht dunkel der Begriff TansportWithMessageCredential auf. Starten wir aber erst einmal mit einer reinen Transportsicherung und lassen die MessageCredentials außen vor.

WCF Service erzeugen

Eine neue WCF Service Application wird mit Add New Project hinzugefügt.
image
Die Klassen IService1.s und Service1.svc können gelöscht werden. Mit Add => WCF Service wird ein neuer Service „SayHelloService“ hinzugefügt. Dieser enthält eine einzige Methode, die ein Gruß zurückliefert.

ISayHelloService.cs

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Runtime.Serialization;

   5: using System.ServiceModel;

   6: using System.Text;

   7:  

   8: namespace WcfService

   9: {

  10:     [ServiceContract]

  11:     public interface ISayHelloService

  12:     {

  13:         [OperationContract]

  14:         string SayHello(string input);

  15:     }

  16: }

SayHelloService.cs

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Runtime.Serialization;

   5: using System.ServiceModel;

   6: using System.Text;

   7:  

   8: namespace WcfService

   9: {

  10:     public class SayHelloService : ISayHelloService

  11:     {

  12:         public string SayHello(string input)

  13:         {

  14:             return "Hello " + input;

  15:         }

  16:     }

  17: }

web.config

In einem ersten Schritt wird der Service völlig ungesichert konfiguriert.

   1: <?xml version="1.0"?>

   2: <configuration>

   3:   <system.web>

   4:     <compilation debug="true" targetFramework="4.5" />

   5:     <httpRuntime targetFramework="4.5"/>

   6:   </system.web>

   7:   <system.serviceModel>

   8:     <services>

   9:       <service name="WcfService.SayHelloService" behaviorConfiguration="SayHelloServiceBehavior">

  10:         <endpoint binding="basicHttpBinding" bindingConfiguration="SayHelloServiceBinding"

  11:                   name="SayHelloService"

  12:                   contract="WcfService.ISayHelloService">

  13:         </endpoint>

  14:         <endpoint binding="mexHttpBinding" address="mex" contract="IMetadataExchange"/>

  15:       </service>

  16:     </services>

  17:     <bindings>

  18:       <basicHttpBinding>

  19:         <binding name="SayHelloServiceBinding">

  20:           <security mode="None"/>

  21:         </binding>

  22:       </basicHttpBinding>

  23:     </bindings>

  24:     <behaviors>

  25:       <serviceBehaviors>

  26:         <behavior name="SayHelloServiceBehavior">

  27:           <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />

  28:           <serviceDebug includeExceptionDetailInFaults="true" />

  29:         </behavior>

  30:       </serviceBehaviors>

  31:     </behaviors>

  32:     <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />

  33:   </system.serviceModel>

  34:   <system.webServer>

  35:     <modules runAllManagedModulesForAllRequests="true"/>

  36:     <directoryBrowse enabled="true"/>

  37:   </system.webServer>

  38: </configuration>

  39:  

Somit ist der Service auch schon fertig und kann direkt gestartet und getestet werden.

Client erweitern

Der Service wird dem Client per „Add Service Reference“ unter dem Namen „WcfServiceReference“ bekannt gemacht.

image

In einer Store App werden die Proxies direkt erzeugt. Der Anwendungstyp hat keine Konfigurationsdatei mehr, in der Bindings, Endpoints und Behaviors konfiguriert werden können. Jeder Parameter, der nicht automatisch vom Service ermittelbar ist muss programmatisch hinzugefügt werden..
Für den Aufruf des Services wird MainPage.xaml um zwei weitere Elemente am Ende des Grids erweitert. Der Button “CallWCFButton” ruft den WCF Services auf. Die Textbox zeigt das Ergebnis an:


Neue Controls für die MainPage.xaml

   1: <Button x:Name="CallWCFButton" Content="Call WCF" 

   2:         HorizontalAlignment="Left" Margin="160,197,0,0"

   3:         VerticalAlignment="Top" Click="CallWCFButton_Click"/>

   4: <TextBox x:Name="WcfOutputText" HorizontalAlignment="Left" 

   5:          Margin="295,200,0,0" TextWrapping="Wrap" 

   6:          VerticalAlignment="Top" FontSize="16" Width="990"

   7:          Text="{Binding WcfOutput}" Height="299"/>

Auch der zusätzliche Code im CodeBehind ist recht klein und enthält nur den Aufruf des WCF Services sowie das Binding-Property für die neue Textbox.

Neue Methode für MainPage.xaml.cs

   1: private async void CallWCFButton_Click(object sender, RoutedEventArgs e)

   2: {

   3:   SayHelloServiceClient helloClient = new SayHelloServiceClient(

   4:     SayHelloServiceClient.EndpointConfiguration.SayHelloService);

   5:   WcfOutput = await helloClient.SayHelloAsync("eXpert");

   6: }

   7:  

   8: private string m_WcfOutput;

   9:  

  10: public string WcfOutput

  11: {

  12:   get { return m_WcfOutput; }

  13:   set

  14:   {

  15:     m_WcfOutput = value;

  16:     OnPropertyChanged("WcfOutput");

  17:   }

  18: }

Der Client kann nun gestartet werden. Mit dem Button „Call WCF“ kann der derzeit noch unsichere Service getestet werden.

image

SSL Transportsicherung

Die Konfiguration der Transportsicherung wird durch eine einfache Erweiterung des Bindings auf der Service-Seite durchgeführt.

Änderung an der web.config

   1: <binding name="SayHelloServiceBinding">

   2:   <security mode="Transport">

   3:   </security>

   4: </binding>

Mit dem angegebenen Binding wird gefordert, dass eine Transportsicherung (SSL) vorhanden sein muss. basicHttpBinding selber bietet keine Möglichkeit dies zu konfigurieren. Somit muss der ServiceHost die SSL Verbindung bereitstellen. Im konkreten Fall ist dies IIS Express, für den SSL im Projekt aktiviert werden muss. Dies erfolgt konfigurativ mit „SSL Enabled = True“ auf den Projekteigenschaften.

image

Nach Änderung der Servicekonfiguration muss die Service Referenz im Client aktualisiert werden. Diese übernimmt die aktuellen Konfigurationen in die Client Proxies. Wird der Client nun gestartet erscheint eine SecurityNegotiationException „Could not establish trust relationship for the SSL/TLS security channel“. Was ist passiert?

SSL Zertifikat im Client nutzen

Der IIS-Express verwendet für seine SSL Kommunikation ein Zertifikat mit dem Namen „localhost“, das er bei seiner Installation anlegt. Eine gültige SSL Verbindung wird nur dann hergestellt, wenn das Server-Zertifikat mit dem Hostnamen übereinstimmt und wenn das Zertifikat einen gültigen Zertifikatspfad besitzt, d.h. von einer RootCA signiert ist.
Windows Store Apps als Nutzer des Service akzeptieren nur Zertifikate, die beide Bedingungen erfüllen. Die in vielen WCF-Beispielen übliche Umgehung der Zertifikatsprüfung (Stichwort: Accept all Certificates) kann in einer Store App nicht genutzt werden.
Bei einem Self-Signed-Zertifikat, wie es auch durch IIS-Express genutzt wird, erreicht man die Beglaubigung des Zertifikats durch Installation des öffentlichen Anteils des SSL-Zertifikat als Root-CA. Für eine Windows Store App werden benötigte Zertifikat mit Angabe des Speicherorts (z.B. als Trusted Root Authority) mit ausgeliefert.
Der öffentliche Anteil von „localhost“ wird nun exportiert und der Store App zur Verfügung gestellt:

Anzeige des Zertifikats:

  • Start von „mmc.exe“
  • Snap-In hinzufügen
  • Zertifikate für Computerkonto hinzufügen

image

Das IIS-Express Zertifikat heißt „localhost“ mit dem Anzeigename „IIS Express Development Certificate“. Das „localhost“-Zertifikat sollte nur einmal existieren. Der Export des öffentlichen Anteils des Schlüssels erfolgt mittels
Rechte Maustaste auf localhost => Alle Aufgaben => Exportieren
=> Nein, privaten Schlüssel nicht exportieren
=> DER-codiert-binär X.509 (.CER)
=> Als Dateiname wird das Asset-Verzeichnis des Clients angegeben:
{ClientProjektPfad}/Assets/localhost.cer
Das Zertifikat wird im Anschluss mit „Add Existing Item“ im Solution Explorer dem Projekt bekannt gegeben. Damit das Zertifikat auch beim kompilieren im Output-Verzeichnis landet, wird für die Zertifikatsdatei folgende Einstellung in den Properties gesetzt:
=> Build Action: Content
=> CopyToOutputDirectory: CopyIfNewer
Nun muss das Zertifikat im Manifest eingetragen werden. Am einfachsten öffnet man Package.appxmanifest im XML-Editor (Solution Explorer => Rechte Maustaste auf der Datei => View Code). Nachfolgend Zeilen werden im Package-Tag (am besten kurz vor dem Closing-Tag) eingetragen.

Erweiterung von Package.appxmanifest

   1: <Extensions>

   2:     <!--Certificates Extension-->

   3:     <Extension Category="windows.certificates">

   4:       <Certificates>

   5:         <Certificate StoreName="Root" Content="Assetslocalhost.cer" />

   6:       </Certificates>

   7:     </Extension>

   8:   </Extensions>

   9: </Package>

Finale

Der WCF Service kann nun wieder aufgerufen werden. Die Transportsicherung erfolgt über SSL, wie der “Fiddler Web Debugger” bestätigt. Der Datenverkehr kann ohne spezielles Zertifikatsmanagement (man in the middle attac) nicht mehr gelesen werden.

image
Knackpunkt der Entwicklung war sicherlich die Registrierung des Service-Zertifikats in der Windows Store App, da sich die Zertifikatsprüfung bei einer Store App nicht ausschalten lässt.

image
Nun fehlt noch die Autorisierung des WCF Services. Diese folgt im nächsten Teil.