Verteilung von VSTO 2005 SE – Lösungen [Teil 4]


Code Access Security

Jeglicher .NET Code unterliegt einer Sicherheitsprüfung, bei der der Code selbst Beweise für seine Daseinsberechtigung bringen muss. Dieses Security Framework heißt Code Access Security und ist, wie eben schon beschrieben, beweisbasiert (evidence based). Das bedeutet, das eine Assembly, die geladen wird, Beweise wie Herkunft (von welcher Adresse wurde sie geladen – http, unc, lokal) und Inhalt (Herausgeber, Digitale Signatur, Hash, Strong Name) mitbringt. Auf der lokalen Maschine gibt es in den 3 Policy Ebenen (Policy Level: Enterprise, Machine, User) sog. Codegruppen, die wiederum mit je einem Permission Set (eine Reihe von Berechtigungen) ausgestattet sind. Die Permission Sets definieren, was eine Assembly, die in diese Code Gruppe fällt, tun darf und was nicht. Also z.B. auf welche Systemressourcen zugegriffen werden darf. Mit jeder Code Gruppe sind Beweise verbunden, z.B. ein Strong Name oder eine URL. Stimmen die Beweise einer Assembly mit der einer Code Gruppe überein, so fällt die Assembly in diese Code Gruppe und bekommt die im zugeordneten Permission Set definierten Berechtigungen.

Besitzt ein Assembly mehrere Beweise, kann sie auch mehreren Codegruppen gleichzeitig zugeordnet werden. Die jeweiligen Berechtigungen werden dabei zu einer Vereinigungsmenge zusammengeführt.

Klingt kompliziert? Ist es aber nicht unbedingt. Es muss aber für die zu verteilende Assembly eine Codegruppe erzeugt und ein Beweis definiert werden, damit diese nach der Installation laufen kann. Das kann manuell (Administrative Tools | Microsoft .NET Framework 2.0 Configuration) oder per Script (Code Access Security Policy Editor: CasPol.exe) erledigt oder eben direkt in das Setup mit aufgenommen werden. Der manuelle Schritt ist auf der Zielmaschine i.d.R. nicht anwendbar, da das GUI Tool nur mit dem .NET Framework SDK installiert wird. Das wird aber in den wenigsten Fällen vorhanden sein. Zu CasPol.exe bzw. Code Access Security gibt es ausführliche Informationen in der MSDN Bibliothek [3] [8]. Gezeigt werden soll hier der Weg über eigenen Code.

Managed Custom Actions

Der Windows Installer unterstützt sog. Custom Actions für den Fall, dass eingebaute Mechanismen nicht mehr ausreichen, die gewünschte Funktionalität abzubilden. Es gibt vier davon: Install, Commit, Rollback und Uninstall. Install und Commit unterscheiden sich z.B. darin, an welcher Stelle im Installationsprozess die Ausführung erfolgt. Im vorliegenden Fall kann auf Commit verzichtet werden.

Da eine Custom Action für den MSI eine Black Box darstellt, ist es wichtig, Änderungen am Zielsystem, die in einer Custom Action vorgenommen werden, in Rollback und Uninstall wieder zurück zu nehmen. Das erfordern beispielsweise die Windows Design Guidelines.

In Visual Studio 2005 können Custom Actions mit Manged Code geschrieben werden. Das .NET Framework enthält Klassen, die Custom Actions unterstützen (System.Configuration.Install). Ebenso enthalten sind Klassen, die helfen, Code Access Security zu verwenden (System.Security). Einiges wird dem Entwickler abgenommen, wenn er eine Installer Klasse zum aktuellen Projekt (nicht Setup Projekt) hinzufügt. Damit ist die Vorlage für die zu schreibende Custom Action gegeben. Die eingefügte Klasse wird automatisch mit dem RunInstaller Attribut versehen. Auf true gesetzt, bedeutet das, dass diese Klasse bei der Installation ausgeführt wird, mit false kann die Ausführung blockiert werden. Es muss darauf geachtet werden, dass das Projekt über einen Verweis auf System.Configuration.Install verfügt.

Zuerst einmal müssen die 3 Custom Actions der Basis-Klasse (die Klasse leitet von der Klasse Installer ab) überschrieben werden. Dazu wird nach dem Konstruktor der Klasse „public override“ geschrieben und Intellisense bietet die überschreibbaren Methoden in einem Kontextmenü an. Gewählt werden nacheinander Install, Rollback und Uninstall. Damit ist das Gerüst fertig und es kann an die Implementierung der beiden Methoden CreatePolicy und RemovePolicy gegangen werden, die sich um die Erzeugung bzw. das Entfernen der Code Access Security Policy kümmern.

private void CreatePolicy(string PLevel, string CodeGroupName, string CodeGroupDescription)
// PLevel: Policy level (Enterprise, Machine or User)
// CodeGroupName: Name of the new code group
// CodeGroupDescription: Friendly name
{
   //Get a reference to the "All Code" group of the PLevel (User, Machine, etc):
  
PolicyLevel PolLevel = null;
   IEnumerator PolLevels = SecurityManager.PolicyHierarchy();
   while (PolLevels.MoveNext())
   {
      PolLevel = (PolicyLevel)PolLevels.Current;
      if (PolLevel.Label == PLevel)
         break;
   }


   // get a reference to the code group root:
  
UnionCodeGroup AllCodeGroup = (UnionCodeGroup)PolLevel.RootCodeGroup;


   // create a new permission set
  
PermissionSet permSet = PolLevel.GetNamedPermissionSet("FullTrust");


   // ... a Strong Name as evidence of membership ...
  
Assembly thisAsm = this.GetType().Assembly;
   AssemblyName asmName = thisAsm.GetName();
   byte[] asmKey = asmName.GetPublicKey();
   StrongNamePublicKeyBlob snPKBlob = new StrongNamePublicKeyBlob(asmKey);
   StrongNameMembershipCondition snMemCond = new StrongNameMembershipCondition(snPKBlob, null, null);


   // ... create a new code group
  
UnionCodeGroup NewCodeGroup = new UnionCodeGroup(snMemCond, new PolicyStatement(permSet));
   NewCodeGroup.Name = CodeGroupName;
   NewCodeGroup.Description = CodeGroupDescription;
   AllCodeGroup.AddChild(NewCodeGroup);


   // Save the policy.
  
SecurityManager.SavePolicy();
}


CreatePolicy liest den Public Key aus der mit einem Strong Name signierten Assembly aus (Voraussetzung ist natürlich, dass die Assembly vorher signiert wurde) und erstellt eine Codegruppe im übergebenen Policy Level. Diese Code Gruppe erhält das Permissionset “FullTrust”, welches vordefiniert ist (sog. Named Permission Set). FullTrust wird wegen der Kommunikation mit Office über COM Interop benötigt und ist kein Security Problem, da nur Assemblies in diese Gruppe fallen, die mit genau diesem einen Strong Name signiert sind. Hier liegt auch der Vorteil des Strong Name-Einsatzes: diese Code Gruppe muss nur ein einziges Mal erzeugt werden, wenn alle zu verteilenden Lösungen mit demselben Strong Name signiert worden sind.

RemovePolicy sucht im jeweiligen Policy Level lediglich nach einem übereinstimmenden Namen und löscht diese dann. Vorsicht ist hier geboten, wenn mehrere Lösungen auf demselben Strong Name basieren. Dann ist eine zentrale Verwaltung der Code Groups z.B. per Active Directory und losgelöst von der eigentlichen Lösung besser. Damit kann dann auch der o.g. Vorteil einer Signierung mit einem Strong Name (o.ä.) genutzt werden.

private void RemovePolicy(string PLevel, string CodeGroupName)
{
   PolicyLevel PolLevel = null;
   IEnumerator PolLevels = SecurityManager.PolicyHierarchy();
   while (PolLevels.MoveNext())
   {
      PolLevel = (PolicyLevel)PolLevels.Current;


      if (PolLevel.Label == PLevel)
         break;
   }


   // get a reference to the code group root:
   UnionCodeGroup AllCodeGroup = (UnionCodeGroup)PolLevel.RootCodeGroup;


   // Locate a code group with the same name in the specified level
  
IEnumerator AvailableCodeGroups = AllCodeGroup.Children.GetEnumerator();
   while (AvailableCodeGroups.MoveNext())
   {
      CodeGroup ActCodeGroup = (CodeGroup)AvailableCodeGroups.Current;


      if (ActCodeGroup.Name == CodeGroupName)
      {
         // if found, remove the child group
        
AllCodeGroup.RemoveChild(ActCodeGroup);
         SecurityManager.SavePolicy();
      }
   }
}


Ob die Policy für einem User oder alle angewandt werden soll, spielt ebenso eine Rolle. Der Windows Installer kennt die Property ALLUSERS. Ist sie gesetzt, wird per Machine installiert, wenn nicht per User (Es wird ebenfalls noch zwischen 1 und 2 unterschieden, bei 2 schlägt die Installation fehl, wenn die Rechte fehlen, bei 1 wird dann per User installiert). Diese Public Properties des Windows Installers können in die Installer Klasse übernommen und dort ausgewertet werden. Vorerst müssen allerdings erst einmal die Custom Actions im Installer Paket definiert und verdrahtet werden. Dazu wird im Custom Actions Editor (Klick auf 2. Symbol von rechts im Solution Explorer) der Primary Output des Projektes angegeben, welches die Installer Klasse enthält (Abb. 6).






Jens Häupel - Custom Actions  
Abb. 6:

Custom Actions Editor

Jetzt ist die Voraussetzung für die Übernahme der ALLUSERS Property gegeben. Nacheinander werden alle 3 Custom Actions selektiert und im Eigenschaftsfenster der Wert für CustomActionsData auf „/allUsers=[ALLUSERS]“ gesetzt. Dadurch wird vor der Ausführung der Custom Action der Wert der ALLUSERS Property auf den internen Parameter allUsers geschrieben. Im Code kann der Wert über this.Context.Parameters["allUsers"] abgefragt werden. Das Listing zeigt die vollständig implementierten Methoden Install und Rollback. Das ebenfalls zu implementierende Uninstall ist identisch mit Rollback.

public override void Install(System.Collections.IDictionary stateSaver)
{
   base.Install(stateSaver);
   string PolLevel = "Machine";


   if (string.IsNullOrEmpty(this.Context.Parameters["allUsers"]))
   {
      PolLevel = "User";
   }


   CreatePolicy(PolLevel, <CodeGroupName>, <Beschreibung>);
}


public override void Rollback(System.Collections.IDictionary savedState)
{
   base.Rollback(savedState);
   string PolLevel = "Machine";


   if (string.IsNullOrEmpty(this.Context.Parameters["allUsers"]))
   {
      PolLevel = "User";
   }


   RemovePolicy(PolLevel, <CodeGroupName>);
}


Bitte beachten Sie:

VSTO Add-Ins können unter Office 2007 nicht mehr für alle Benutzer installiert werden. Das Sicherheitsmodell von Office 2007 unterbindet das Laden solcher Add-Ins!

+++ Fortsetzung in Teil 5 +++

Comments (3)

  1. Carsten Kuhn says:

    Gibt es eine Möglichkeit auch bei einer VSTO-Anwendung beim Start zu prüfen ob CAS (FullTrust) gesetzt ist?

    Bei .NET-Anwendungen ist das zumindest mit Demand() möglich.

    Hinweis: Wenn geschachtelte Codegruppen angelegt werden, dann muss die Reihenfolge im Code beachtet werden. Die inneren Codegruppen werden der übergeordneten hinzugefügt (die in der Regel das "Nothing" PermissionSet hat). Erst dann wird die gesamte Codegroup zum Root "AllCode" hinzugefügt. Ein Kollege war darüber gestolpert…

  2. jensha says:

    Carsten,

    sorry, ich hatte den Kommentar glatt übersehen. Das sollte schon funktionieren. z.B. so:

    try

    {

     SecurityPermission permission = new SecurityPermission(PermissionState.Unrestricted);

     permission.Demand();

    }

    catch (SecurityException)

    {

     throw new InstallException("You have insufficient privileges…");

    }

Skip to main content