Integration in Windows Security mit .NET 2.0 - Teil 1

Veröffentlicht: 19. Dez 2005
Von Dominick Baier

Als Entwickler auf der Windows-Plattform steht man immer wieder vor einer entscheidenden Frage: das Rad neu erfinden und eigene Sicherheits-Dienste wie Authentifizierung und Autorisierung implementieren - oder sich auf die bereits vorhandenen Security-Features von Windows stützen?

Windows bietet eine mächtige und flexible Sicherheits-Infrastruktur, bestehend aus Benutzern, Gruppen, Privilegien und Zugriffslisten. Erweiterte Features wie Impersonierung und Delegation erlauben es, flexible Anwendungs-Designs zu realisieren. Dieser Artikel bildet den Anfang einer Serie rund um Features von .NET 2.0, die es Entwicklern erlauben, sich nahtlos in Windows-Sicherheit zu integrieren und ohne P/Invoke-Umwege direkt zum Ziel zu kommen.

Auf dieser Seite

 Windows Tokens
 SIDs
 Wo stehen wir?
 Der Autor

Windows Tokens

Der Windows Token enthält alle sicherheits-relevanten Informationen über einen Benutzer, z.B. seinen Namen, Gruppenzugehörigkeiten (lokal und in der Domäne) sowie seine Privilegien. Ein Token ist das Resultat einer erfolgreichen lokalen Anmeldung an Windows oder einer Netzwerk-Authentifizierung, z.B. an ASP.NET, mit NegotiateStream, Remoting in 2.0 oder WCF.

.NET wrapped Tokens mit der System.Security.WindowsIdentity Klasse. Um z.B. den Token des aktuell angemeldeten Benutzers zu erlangen (z.B. in Desktop-Anwendungen), benutzt man die statische Methode GetCurrent().

WindowsIdentity id = WindowsIdentity.GetCurrent();

Danach kann man über das Name Property den Benutzernamen erfragen und auf Basis dieses Wertes, z.B. Sicherheits-Entscheidungen treffen oder das UI eines Programms anpassen.

Will man auch Gruppenüberprüfungen für den Benutzer durchführen, z.B. wenn sich ein Programm anders verhalten soll, wenn der Benutzer in der Rolle "Vorstand" enthalten ist, muss man ein WindowsPrincipal Objekt zur Hilfe holen. WindowsPrincipal unterstützt die IsInRole Methode, die einen Gruppennamen als Parameter entgegennimmt und einen true/false Wert zurückliefert.

WindowsPrincipal p = new WindowsPrincipal(id);
if (p.IsInRole("Contoso\\Vorstand"))
  lblHeader.Text = "Hallo Chef";

Windows-Gruppennamen bestehen immer aus zwei Teilen: der Autorität und dem eigentlichen Namen. Die Autorität ist bei lokalen Gruppen der lokale Machinen-Name und bei Domains der Domain-Name.

Ein Problem kann auf Sie zukommen, wenn Sie den Benutzer nach "speziellen" Gruppenzugehörigkeiten prüfen möchten, z.B. "Domänen Administratoren". Diese eingebauten Windows-Gruppen sind nämlich lokalisiert und haben auf jeder Windows-Sprachversion einen anderen Namen. So ist z.B. der englische Name dieser Gruppe "Domain Admins". Diesem Problem kommt man mit dem String-Overload von IsInRole nicht einfach bei. Sie möchten aber sicherlich trotzdem, dass Ihr Programm auf allen Sprach-Versionen von Windows funktioniert. Dazu müssen wir einen Ausflug in die Eingeweide von Windows machen.

SIDs

Intern speichert Windows Benutzer und Gruppen nicht mit Ihren "freundlichen Namen" wie "Marketing" oder "Fred" ab. Es wird eine Zeichenkette verwendet die Security Identifier (kurz SID) heißt. Der Aufbau einer SID ist folgendermaßen:

S-1-5-21-2334373287-406835450-3753124356-1110

"S-1-5-21" enthält ein Versionsnummer und einen Verweis auf das Sicherheits-Subsystem von Windows. Die darauf folgende Zeichenkette ist die SID der Autorität, die das Konto ausgestellt hat, und der letzte Teil (in diesem Fall die "1110") ist die ID des Kontos relativ zur Autorität.

SIDs werden zufällig erzeugt und niemals wieder verwendet. Bestimmte SIDs sind allerdings reserviert und werden für eben jene "speziellen" Gruppen- und Benutzer-Konten wie die Domänen-Administratoren verwendet.

Das Arbeiten mit SIDs und Accounts war bislang nur unter Zuhilfenahme von Win32 APIs möglich. Ab .NET 2.0 werden diese in den Klassen SecurityIdentifier und NTAccount managed abgebildet. Weiterhin besteht die Möglichkeit, zwischen SIDs und Konto-Namen zu übersetzen. Möchte man z.B. die SID eines beliebigen Benutzers ausgeben, reicht dieses kurze Stück Code:

string getSid(string username)
{
     NTAccount account = new NTAccount(username);
     SecurityIdentifier sid = (SecurityIdentifier)
            account.Translate(typeof(SecurityIdentifier));
     return sid.Value;
}

Die Translate-Methode kann in alle von IdentityReference abgeleiteten Klassen übersetzen. Im Moment gibt es nur die bereits genannten, lässt aber Freiraum für zukünfige alternative Darstellungsformen von Accounts. Die Übersetzung funktioniert natürlich in beide Richtungen.

Zurück zu unserem Problem der "speziellen" Konten-Namen: Für diesen Zweck gibt es einen Aufzählungstyp mit dem Namen WellKnownSidType. Hier findet man wichtige Werte, wie den lokalen Administrator, SYSTEM, Network Service oder eben die Domänen-Administratoren. Folgende Routine übersetzt jeden der well known SIDs in den lokalisierte Namen:

string getName(WellKnownSidType wksid)
{
     SecurityIdentifier sid = new SecurityIdentifier(wksid, null);
     NTAccount account = (NTAccount)sid.Translate(typeof(NTAccount));
     return account.Value;
}

Für WellKnownSidType.BuiltInAdministratorsSid erhalten Sie nun auf einem deutschen Windows "Vordefiniert\Administratoren", während ein englisches Windows "Builtin\Administrators" zurückliefert. Der Clou ist, dass eine neue Überladung der IsInRole Methode nun auch direkt SIDs unterstützt. So ist es nun möglich, auf jegliche Gruppen zu prüfen, ohne deren lokalisierten Namen kennen zu müssen.

SecurityIdentifier sidLocalAdmins = new 
  SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);

if (p.IsInRole(sidLocalAdmins))
       Console.WriteLine("Hallo Admin!");

Manche dieser "wohl bekannten" SIDs benötigen aber zusätzlich noch die ID der Autorität, z.B. Domänen-Accounts wie die Domänen-Administratoren. Dies ist der zweite Parameter des SecurityIdentifier Konstruktors, der im vorangegangenen Beispiel stillschweigend auf null gesetzt wurde.

Die ID des Konten-Ausstellers erhält man über das AccountDomainSid Property eines SecurityIdentifier objekts. So kann man z.B. die Domänen-ID des momentan angemeldeten Benutzers über das User Property der zuvor erzeugten WindowsIdentity erfahren. Der vollständige Code würde folgendermaßen aussehen:

WindowsIdentity id = WindowsIdentity.GetCurrent();
WindowsPrincipal p = new WindowsPrincipal(id);

SecurityIdentifier sidDomainAdmin = new 
  SecurityIdentifier(WellKnownSidType.AccountDomainAdminsSid, 
  id.User.AccountDomainSid));

if (p.IsInRole(sidDomainAdmin))
  Console.WriteLine("Hallo Domänen Admin!");

Eine häufige Anforderung ist es auch, alle Gruppen eines Benutzers in Erfahrung zu bringen. Dies war vor 2.0 ebenfalls die Domäne von Win32 APIs. Dies kann nun auch sehr einfach erledigt werden. Das Groups Property von WindowsIdentity liefert eine Liste von IdentityReference Objekten, die wiederrum in eine Liste von SIDs oder Accounts übersetzt werden kann.

private string[] getGroups(WindowsIdentity id)
{
      List<string> groups = new List<string>();
      IdentityReferenceCollection irc = 
        id.Groups.Translate(typeof(NTAccount));

      foreach (NTAccount acc in irc)
      {
           groups.Add(acc.Value);
      }

      return groups.ToArray();
}

Wo stehen wir?

Sie wissen nun, wie man mit Windows Tokens arbeitet und im speziellen mit dem des aktuell angemeldeten Benutzers. Sie können nun Ihren Programm-Ablauf aufgrund der Gruppenzugehörigkeit anpassen und sich mit Hilfe von SIDs vor unliebsamen Überraschungen bei mehrsprachigen Installationen schützen.

In den nächsten Folgen schauen wir uns Features wie Impersonierung und Delegation, ACLs und Netzwerk-Authentifizierung an.

Der Autor

Dominick Baier entwickelt ASP.NET- und Security-Kurse bei DevelopMentor, einer Entwickler-Schulungsfirma. Darüber hinaus unterstützt er Unternehmen beim Schreiben von sicheren verteilten Anwendungen. Als Referent kann man ihn auf diversen Konferenzen antreffen (BASTA, WinDev, DevWeek u.a.); Online findet man ihn auf seinem Blog unter www.leastprivilege.com.

Artikel-Serie „ Integration in Windows Security mit .NET 2.0“ im Überblick:


Anzeigen: