Quantcast
Channel: TheUndeadable entwickelt » Windows Communication Foundation
Viewing all articles
Browse latest Browse all 2

Verschlüsselte Verbindung mit WCF

0
0

Für ein neues Projekt sollen zwei Prozesse über einen Kommunikationskanal miteinander kommunizieren. Als Kommunikationsprotokoll habe ich mir zum Zweck der Eigenbildung Windows Communication Foundation definiert.

Für die Kommunikation gelten folgende Anforderungen:

  • #1: Die Kommunikation muss verschlüsselt ablaufen.
  • #2: Die Kommunikation darf erst nach Übergabe von Benutzernamen und Kennwort möglich sein.
  • #3: Die Verschlüsselung und Authentifizierung muss transparent auf Protokoll-Ebene erfolgen.
  • #4: Die Konfiguration der Kommunikationsschnittstelle muss veränderbare Parameter unterstützen.

Windows Communication Foundation bietet in der Grund-Variante im Prinzip mehrere prinzipielle Varianten der Kommunikation:

  1. HTTP(s)-basierend: Hierzu wird SOAP verwendet um die Schnittstelle nach außen zur Verfügung zu stellen.
  2. Reine Datenströme über TCP: Hierzu wird ein binäres Protokoll verwendet. Dieses Verfahren ist auch schneller als das HTTP(s)-basierende Protokoll. Der Nachteil ist die fehlende Verbreitung des Protokolls. Als besonderes Gimmick ermöglicht hier Windows die Verwendung des gleichen Server-Ports für viele Prozesse.

Aus Forderung #4 folgt, dass die Konfiguration selbst im Code erfolgen muss und damit keine Xml-Konfiguration über app.config erwünscht ist.

Verschlüsselung

WCF fordert für eine Verschlüsselung ein Zertifikat, das auch im Windows-Zertifikaten-Manager eingetragen ist.

Hierzu legen wir ein neues Zertifikat an (Aus dem Windows-SDK-Prompt):

# makecert -sky exchange -n CN=FBK –ss My –pre

Für einen vereinfachten Fall fordern wir einfach, dass auf Grund der internen Verwendung und keine Notwendigkeit zur Überprüfung der Server-Authentizität keine vollständige Zertifikatskette gefordert wird. Damit reicht dieses Zertifikat.

Projektstruktur

Wir gehen von folgender Visual Studio-Projektmappenstruktur aus:

  • Library: Gemeinsame Bibliothek
  • Server: Beinhaltet den Server, der eine Funktionalität nach außen hin zur Verfügung stellt.
  • Client: Beinhaltet den Client, der sich mit dem Server verbindet

Die Schnittstelle

Über eine Schnittstelle wird definiert, welche Funktionalität der Server zur Verfügung stellt:

Diese befindet sich im Library-Modul und wird sowohl vom Client, als auch vom Server genutzt.

using System.ServiceModel;

namespace Library
{
    /// <summary>
    /// Interface for calculator
    /// </summary>
    [ServiceContract]
    public interface ICalculator
    {
        /// <summary>
        /// Calculates the square
        /// </summary>
        /// <param name="number">Number, whose square shall be calculated</param>
        /// <returns>Square value</returns>
        [OperationContract]
        double GetSquare(double number);
    }
}

Die Methode GetSquare nimmt eine Fließkommazahl an und gibt eine solche wieder aus. In diesem Falle soll das Quadrat berechnet werden.

Benutzerauthentifizierung

Die Benutzer-Authentifizierung läuft in WCF über eine abgeleitete Klasse des UserNamePasswordValidators. Diese ist im Server-Projekt zu implementieren:

using System;
using System.IdentityModel.Selectors;

namespace Server
{
    public class CustomUserNameValidator : UserNamePasswordValidator
    {
        // This method validates users. It allows in two users,
        // test1 and test2 with passwords 1tset and 2tset respectively.
        // This code is for illustration purposes only and
        // MUST NOT be used in a production environment because it
        // is NOT secure.
        public override void Validate(string userName, string password)
        {
            if (null == userName || null == password)
            {
                throw new ArgumentNullException();
            }

            if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
            {
                throw new InvalidOperationException("Unknown Username or Incorrect Password");
            }
        }
    }
}

Der Server

In diesem Fall nehmen wir das TCP-Protokoll, da für die Verwendung des HTTPS-Protokolls scheinbar eine Konfiguration des http.sys-Moduls erforderlich ist.

Zuerst implementieren wir den Server:

using Library;

namespace Server

    public class Calculator : ICalculator
    {
        /// <summary>
        /// Calculates the square
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public double GetSquare(double number)
        {
            return number * number;
        }
    }
}

Diese Implementation überrascht jetzt nicht. Wichtig ist, dass hier keine Attribute verwendet werden.

Der Server selbst wird folgendermaßen implementiert:

using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using Library;

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting");
            using (var serviceHost = new ServiceHost(typeof(Calculator)))
            {
                var binding = new NetTcpBinding();
                binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
                binding.Security.Message.ClientCredentialType =
                    MessageCredentialType.UserName;
                var bindingMex = new NetTcpBinding();
                serviceHost.AddServiceEndpoint(
                    typeof(ICalculator),
                    binding,
                    "net.tcp://127.0.0.1:8080/test/");

                serviceHost.Credentials
                    .UserNameAuthentication.UserNamePasswordValidationMode =
                    UserNamePasswordValidationMode.Custom;
                serviceHost.Credentials
                    .UserNameAuthentication.CustomUserNamePasswordValidator =
                    new CustomUserNameValidator();

                serviceHost.Credentials.ServiceCertificate.SetCertificate(
                    StoreLocation.CurrentUser,
                    StoreName.My,
                    X509FindType.FindBySubjectName,
                    "FBK");

                // Metadata
                var metadataBehavior =
                    serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
                if (metadataBehavior == null)
                {
                    metadataBehavior = new ServiceMetadataBehavior();
                    serviceHost.Description.Behaviors.Add(metadataBehavior);
                }
                serviceHost.AddServiceEndpoint(
                    typeof(IMetadataExchange),
                    MetadataExchangeBindings.CreateMexHttpBinding(),
                    "http://127.0.0.1:8081/test/");

                // Open
                serviceHost.Open();

                Console.WriteLine("Started");               

                Console.ReadKey();

                serviceHost.Close();
            }
        }
    }
}

Kurz beschrieben:

Es wird ein ServiceHost erzeugt. Dieser ist die eigentliche Service-Bereitstellung und fordert Informationen über den Contract, das Binding und die Endpoint-Address.

Als Contract wählen wir die ICalculator-Schnittstelle.

Als Binding wird das TCP-Protokoll verwendet und es wird in den nachfolgenden Schritten so konfiguriert, dass es sowohl Transport-Verschlüsselung bietet als auch eine Benutzerüberprüfung auf Nachrichteneben anbietet.

Als Endpoint wird eine virtuelle TCP-Adresse angegeben.

Dem ServiceHost werden in den nachfolgenden Schritten die Benutzer-Authentifizierung und das Zertifikat mitgegeben.

Um eine automatische Erzeugung des Proxy-Objektes zu ermöglichen, wird ein Metadaten-Dienst erzeugt, der aus einem WCF-Binding die notwendigen WSDL-Informationen erzeugt.

Über Open wird der Port effektiv gestartet und es wird auf Tastendruck gewartet, bis dieser wieder geschlossen werden kann.

Der Client

Der Client besteht aus zwei Dateien:

  1. Implementation des Proxy-Objektes zur typsicheren Verwendung.
  2. Die Verwendung des Proxy-Objektes

Das Proxy-Objekt

Das Proxy-Objekt kann entweder manuell erstellt werden oder mit Hilfe des Kommandozeilentools ‘svcutil’. Dazu startet man den Server und gibt folgenden Befehl im SDK-Prompt ein.

# svcutil http://127.0.0.1:8081/test/ /out:Calculator.cs

Danach wird die Datei Calculator.cs erzeugt, die dann in der Projekt-Mappe Verwendung finden kann.

Aus Sicherheitsgründen kann man danach im Server-Code die Metadaten-Veröffentlichung entfernen.

Die Verwendung

WCF hat eine Angewohnheit, dass es sehr penibel und korrekt mit Zertifikaten umgeht. Für eine interne Verwendung kann man definieren, dass eine vollständige Zertifikatskette nicht gebraucht wird.

Daher finden sich hier ein paar zusätzliche Befehle, die die Sicherheitsüberprüfungen deaktivieren:

using System;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            var binding = new NetTcpBinding(
                SecurityMode.TransportWithMessageCredential);
            binding.Security.Message.ClientCredentialType
                = MessageCredentialType.UserName;
            var address = new EndpointAddress(
                new Uri("net.tcp://127.0.0.1:8080/test/"),
                EndpointIdentity.CreateDnsIdentity("FBK"));

            var calc = new Calc.CalculatorClient(binding, address);
            calc.ClientCredentials.ServiceCertificate
                .Authentication.CertificateValidationMode =
                X509CertificateValidationMode.None;
            calc.ClientCredentials.ServiceCertificate
                .Authentication.RevocationMode =
                X509RevocationMode.NoCheck;
            calc.ClientCredentials.UserName.UserName = "test1";
            calc.ClientCredentials.UserName.Password = "1tset";

            while (true)
            {
                var number = Convert.ToInt32(Console.ReadLine());
                Console.WriteLine(calc.GetSquare(number));

                if (number == 0)
                {
                    break;
                }
            }
        }
    }
}

Im ersten Schritt wird eine Binding erzeugt, die aussagt, dass wir sowohl eine Verschlüsselung als auch eine Benutzerauthentifizierung per Benutzernamen und Kennwort möchten.

Im zweiten Schritt legen wir fest, dass wir die virtuelle TCP-Adresse des Servers fest und behaupten, dass dieser Server den Namen ‘FBK’ besitzt. Dieser Namensgebung ist für die Zertifikatsüberprüfung notwendig, da WCF überprüft, ob der Server auch wirklich derjenige ist, für den er sich ausgibt.

Danach wird der Client mit der angegebenen Bindung und dem Endpunkt erzeugt. Da wir keine Zertifikats-Überprüfung möchten, deaktivieren wir sowohl die Zertifikatsketten-Prüfung als auch die Abfrage, ob dieses Zertifikat mittlerweile gesperrt ist (revoke).

Nach der Übergabe des Benutzernamens und Kennwort können wir nun endlich mit dem Dienst reden.

Der in diesem Blog-Post entwickelte Source-Code wurde über viele Stunden mit großem Debuggen ermittelt und ich möchte nicht garantieren, dass er nicht irgendwo doch noch Exceptions wirft…

Es gibt viele Möglichkeiten

Viel Spaß mit dieser Übersicht, ich werde sie öfter brauchen.


Viewing all articles
Browse latest Browse all 2

Latest Images

Vimeo 10.7.0 by Vimeo.com, Inc.

Vimeo 10.7.0 by Vimeo.com, Inc.

HANGAD

HANGAD

MAKAKAALAM

MAKAKAALAM

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Vimeo 10.6.2 by Vimeo.com, Inc.

Vimeo 10.6.2 by Vimeo.com, Inc.

Vimeo 10.6.1 by Vimeo.com, Inc.

Vimeo 10.6.1 by Vimeo.com, Inc.

Vimeo 10.6.0 by Vimeo.com, Inc.

Vimeo 10.6.0 by Vimeo.com, Inc.

Re:

Re:





Latest Images

Vimeo 10.7.0 by Vimeo.com, Inc.

Vimeo 10.7.0 by Vimeo.com, Inc.

HANGAD

HANGAD

MAKAKAALAM

MAKAKAALAM

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Doodle Jump 3.11.30 by Lima Sky LLC

Vimeo 10.6.1 by Vimeo.com, Inc.

Vimeo 10.6.1 by Vimeo.com, Inc.

Vimeo 10.6.0 by Vimeo.com, Inc.

Vimeo 10.6.0 by Vimeo.com, Inc.

Re:

Re:

Re:

Re: