Veröffentlicht: 08. Feb 2004 | Aktualisiert: 29. Jun 2004
Von Keith Brown
Betrifft:
Microsoft® .NET Framework
Starke Namen (Strong Names, SN) werden für die eindeutige Identifizierung einer Assembly benötigt, damit diese im globalen Assemblierungs-Cache (GAC) abgelegt werden kann. Weiterhin sind sie für die Verwendung des Versionsprüfungssystems in der Common Language Runtime (CLR) von Microsoft .NET Framework erforderlich. In diesem Artikel erhalten Sie Informationen zu starken Namen und ihrer Verwendung.
Auf dieser Seite
Von GUIDs zu öffentlichen Schlüsseln
RSA und digitale Signaturen
Die CLR und öffentliche Schlüssel
Starke Namen und Überprüfung
Starke Namen und .NET-Sicherheitsrichtlinien
Öffentliche Schlüssel und Versionsprüfung
Verwenden von verzögertem Signieren zur Verhinderung der Offenlegung
Schützen Ihres Entwicklungsteams
Schlussfolgerung
Von GUIDs zu öffentlichen Schlüsseln
Für ein Verständnis des Konzepts von starken Namen ist es nützlich, das bisherige Benennungsschema für Komponenten auf der Microsoft Windows-Plattform zu betrachten - die GUID (Globally Unique Identifier). Eine GUID ist eine eindeutige 128-Bit-Ganzzahl (16 Byte), die zur Benennung in der COM-Welt dient. Jeder, der sich schon einmal mit der Registrierung befasst hat, weiß, dass heutzutage eine Fülle von GUIDs benutzt werden. COM-Programmierer wissen, dass der Grund dafür darin besteht, dass GUIDs für die Benennung von sehr detaillierten Elementen verwendet wurden. Alle COM-Klassen, COM-Schnittstellen, Anwendungen, Typbibliotheken und Enumerationen benötigen ihre eigene GUID, was dazu führt, dass es eigentlich ein Übermaß an GUIDs gibt.
Vor Windows 2000 wurden GUIDs auf der Basis der UUID (Universally Unique Identifier) generiert, die als Teil des DCE-RPC definiert wurde. Knapp gesagt, erhält die UUID ihre Eindeutigkeit vom aktuellen Datum und der aktuellen Uhrzeit sowie der eindeutigen 48-Bit-IEEE 802-Adresse Ihrer Netzwerkkarte (NIC).
Seit Windows 2000 werden GUIDs nicht mehr auf der Basis dieses Algorithmus generiert. Stattdessen gibt es zufällige 16-Byte-Ganzzahlen, die durch das Aufrufen des Zufallszahlgenerators der CryptoAPI generiert werden. Warum diese Änderung? Aller Wahrscheinlichkeit nach war dies auf ein datenschutzrechtliches Problem zurückzuführen, das Anfang 1999 auftrat. Microsoft Office verwendete die GUID als eindeutigen Bezeichner in Datendateien. Dies hatte den unerwünschten Nebeneffekt, dass der Autor eines Dokuments über die in der GUID verwendete Netzwerkadresse zurückverfolgt werden konnte. Die Integration von GUIDs in Datendateien hatte negative Auswirkungen auf die Sicherheit, da dadurch die Anonymität verletzt wurde.
Angesichts all dieser Probleme mit der Namensgebung in COM wollte das Common Language Runtime-Team eine bessere Methode für die eindeutige Identifizierung von Komponenten entwickeln. Eine wichtige Entscheidung war die Verwendung eines hierarchischen Benennungsschemas. Anstatt jedem einzelnen Typen seinen eigenen eindeutigen Bezeichner zuzuweisen, wie dies bei COM mit GUIDs der Fall war, sollten CLR-Typen auf der Basis ihres vollständigen Typennamens identifiziert werden, einschließlich des Namespace sowie des Namens der Assembly, in der der Typ verpackt wurde. Dies würde die Verwendung einfacher Typnamen für Klassen, Schnittstellen etc. ermöglichen. Da das Ladeprogramm den Namen der Assembly als Teil des Namens jedes einzelnen Typs ansieht, muss jeder Assembly eigentlich nur ein Name gegeben werden, der räumlich und zeitlich eindeutig ist, obwohl Namespaces dennoch für das Auflösen von Benennungskonflikten zur Kompilierzeit wichtig sind. Mit der Einführung eines hierarchischen Benennungsschemas war das Problem der uneingeschränkten GUID-Vermehrung ein für alle Mal gelöst.
Nun musste das CLR-Team nur noch einen Algorithmus für die Generierung eines eindeutigen Assembly-Namens entwickeln. Eventuell könnte Teil des Namens eine große Zufallszahl sein, die einer GUID ähnlich ist, vielleicht sogar größer als 16 Byte, um eine zusätzliche Resistenz gegenüber Konflikten zu erhalten. Diese würde verhindern, dass jemand zufällig den gleichen Bezeichner auswählt, den Sie gerade für die Benennung Ihrer Assemblys verwenden. Keinen Schutz dagegen hätte Sie vor böswilligen Anwendern, die versuchen, eine Trojanerassembly zu erstellen, die mit Ihrer identisch ist. Auf diesem Grund traf das CLR-Team eine interessante Entscheidung: Anstatt einfach eine große Zufallszahl zu verwenden, beschloss es, einen öffentlichen RSA-Schlüssel mit 1024 Bit zu verwenden. Dabei handelt es sich um eine 128-Byte-Zahl, die aus zwei sehr großen zufälligen Primärzahlen besteht, die miteinander multipliziert worden sind.
Für ein Verständnis von starken Namen ist zunächst die Rolle der Kryptografie zu erläutern, die in den nächsten Abschnitten beschrieben wird.
RSA und digitale Signaturen
Eine umfassende Einführung in dieses Thema finden Sie in der Publikation Practical Cryptography (in Englisch) von Ferguson und Schneier (Wiley 2003). Kurz gesagt steckt hinter RSA die Vorstellung, dass Schlüssel paarweise generiert werden: ein öffentlicher und ein privater Schlüssel. Der private Schlüssel ist geheim und wird niemandem mitgeteilt. Der öffentliche Schlüssel andererseits kann beliebig weitergegeben werden. Es wird angenommen, dass es unmöglich ist, den privaten Schlüssel anhand des öffentlichen Schlüssels zu berechnen. Alle Daten, die mit dem öffentlichen Schlüssel verschlüsselt sind, können nur mit dem privaten Schlüssel entschlüsselt werden. Entsprechend dazu können alle Daten, die mit dem privaten Schlüssel verschlüsselt sind, nur mit dem öffentlichen Schlüssel entschlüsselt werden.
RSA-Schlüssel dienen dazu, die Integrität von Daten über eine digitale Signatur zu gewährleisten. Zum Signieren von Daten wandeln Sie diese zuerst mithilfe eines kryptografischen Algorithmus in Hash um und verschlüsseln den sich ergebenden Hashwert mit Ihrem privaten Schlüssel. Die Signatur ist eigentlich nur dieser verschlüsselte Hashwert. Wenn Sie die Daten und die Signatur veröffentlichen, können beliebige Benutzer, die Ihren öffentlichen Schlüssel kennen, die Signatur überprüfen, indem sie die Daten selbst in Hash umwandeln, und ihren eigenen Hashwert mit dem in Ihrer Signatur vergleichen, die sie mit Ihrem öffentlichen Schlüssel entschlüsseln. Dahinter verbirgt sich die Vorstellung, dass die Hashes nur übereinstimmen, wenn zwei Bedingungen erfüllt sind: Die empfangenen Daten sind mit den ursprünglich signierten Daten identisch, und der Signaturgeber kannte den öffentlichen Schlüssel. Durch die Anwendung dieses Verfahrens auf eine Assembly können Sie es für einen Angreifer unmöglich machen, Ihre Assembly durch seine Trojanerversion zu ersetzen, da der Angreifer nicht in der Lage ist, Ihre Signatur zu fälschen. Dies setzt natürlich voraus, dass dieser Ihren privaten Schlüssel nicht kennt. Der Schutz Ihrer privaten Schlüssel ist von größter Bedeutung und ihre Verwaltung ist einer der Hauptgründe für diesen Artikel.
Durch Verwendung eines öffentlichen RSA-Schlüssels als Teil eines Assemblynamens konnte das CLR-Team auf elegante Weise zwei Fliegen mit einer Klappe schlagen. Erstens bietet die Tatsache, dass die sehr großen, zufällig generierten, öffentlichen RSA-Schlüssel konfliktfrei sind, Schutz vor versehentlichen Namenskonflikten: Ein öffentlicher Schlüssel mit 1024-Bit hat die achtfache Größe einer GUID. Zweitens können die entsprechenden privaten Schlüssel zum Signieren einer Assembly verwendet werden, was Sicherheit gegenüber Angreifern ermöglicht, die versuchen, Ihre Assembly durch eigenen Code zu ersetzen. Aber wie bei allen anderen Sicherheitsmaßnahmen hat auch dies seinen Preis: Es gibt nun vertrauliche Daten, die verwaltet werden müssen. Falls Ihr privater Schlüssel gefährdet ist, kann es zu Sicherheitsverletzungen und fatalen Kompatibilitätsproblemen kommen.
Die CLR und öffentliche Schlüssel
Einer Assembly mit einem starken Namen wurde ein öffentlicher Schlüssel zugeordnet. Dies ist die Aufgabe eines Compilers. Wenn Sie beispielsweise für sich selbst ein Schlüsselpaar generieren, können Sie Ihren öffentlichen Schlüssel von Ihnen erstellten Assemblys zuweisen, indem Sie den Compiler anweisen, wo dieser nach Ihrer Schlüsseldatei suchen kann:
using System.Reflection;
[assembly: AssemblyKeyFile(@"javascript:void(null);")]
class Foo {...}
Die meisten der von Assistenten generierten Projekte in Microsoft Visual Studio .NET fügen dieses Attribut in eine Datei mit der Bezeichnung AssemblyInfo ein. Sie können dieses Attribut auch in eine beliebige Quelldatei einfügen. Wenn der Compiler dieses Attribut liest, kopiert dieser den gesamten öffentlichen Schlüssel in die Metadaten der Assembly und verwendet den privaten Schlüssel zur Bildung einer digitalen Signatur. Dies erfolgt durch Umwandeln der Dateien in der Assembly in Hashcode, Einbinden dieser Hashwerte in das Manifest für die Assembly, Umwandeln des Manifests in Hashcode, Verschlüsseln dieses endgültigen Hashwerts mithilfe des entsprechenden privaten Schlüssels und Einbinden dieses Werts in die Assembly wie einen ganz normalen Block mit Metadaten.
(Beachten Sie, dass dies ein etwas vereinfachter Ansatz ist. Der Compiler benötigt nicht nur den öffentlichen Schlüssel, sondern auch den privaten Schlüssel aus der Datei c:\temp\mykeyfile. Weiter unten in diesem Artikel wird eine sicherere Methode für Codesignaturen beschrieben.)
Wenn Sie das Manifest einer Assembly mithilfe von ILDASAM betrachten, können Sie ihren öffentlichen Schlüssel eindeutig sehen, wie in Abbildung 1 dargestellt.
Abbildung 1. Ein öffentlicher Schlüssel, der einer Assembly zugeordnet ist.
Was Sie nicht sehen, ist die Signatur oder die Intermediate-Hashwerte. Dies ist manchmal für Benutzer verwirrend, die sich gerade mit dem Konzept der starken Namen vertraut machen. ILDASM zeigt diese Informationen nicht an, da es sich dabei um einen Disassembler handelt. Dieser hat die Aufgabe, IL zu produzieren, die in eine Assembly kompiliert werden kann. Bedenken Sie weiterhin, dass die Signatur und die Hashwerte vom Compiler ausgegeben werden (was in diesem Fall der ILASM wäre). Wenn Sie sehen möchten, ob einer Assembly ein öffentlicher Schlüssel zugewiesen wurde, verwenden Sie das Strong Name-Tool SN.EXE.
sn -Tp foo.dll
Dieser Befehl gibt entweder den öffentlichen Schlüssel der Assembly aus oder informiert Sie, dass die Assembly über keinen starken Namen verfügt, was heißt, dass ihr kein öffentlicher Schlüssel zugewiesen wurde. Aber allein die Tatsache, dass eine Assembly über einen öffentlichen Schlüssel verfügt, bedeutet nicht unbedingt, dass sie eine entsprechende Signatur besitzt, oder dass die Signatur gültig ist. Mit dem folgenden Befehl können Sie das Vorhandensein und die Gültigkeit einer Signatur testen:
sn -vf foo.dll
Dies bewirkt, dass das Programm SN.EXE seinen eigenen Hash jeder Datei in der Assembly berechnet, um sicherzustellen, dass sich die Assembly-Binärdateien seit ihrer Signatur nicht geändert haben. Anschließend berechnet es seinen eigenen Hash des Assemblymanifests, entschlüsselt die in der Assembly verpackte Signatur (diejenige, die von ILDASM nicht angezeigt wird) und vergleicht seinen eigenen berechneten Hashwert mit der entschlüsselten Signatur. Falls das Hash übereinstimmt, meldet es die Gültigkeit der Assembly. Andernfalls wird eine Fehlermeldung angezeigt. Ein Fehler kann darin bestehen, dass der Assembly einfach noch keine Signatur zugeordnet wurde, was auf Assemblys mit verzögertem Signieren zutrifft (mehr dazu später).
Wenn Sie die im Lieferumfang von .NET Framework enthaltenen Tools zur Installation einer neuen Assembly in den globalen Assemblierungs-Cache (GAC) verwenden (GACUTIL.EXE oder die Fusion-Cacheanzeige), führen diese eine Signaturüberprüfung durch, die dem folgenden Befehl entspricht:
sn -v foo.dll
Das Gleiche passiert immer dann, wenn die CLR eine Assembly mit starkem Namen lädt, die sich nicht im GAC befindet. Falls ein öffentlicher Schlüssel vorhanden ist, überprüft sie die Signatur. Der feine Unterschied zwischen den Befehlen sn -vf und sn -v besteht darin, dass letzterer die Signaturüberprüfung für alle Schlüssel überspringt, die vom Administrator als vertrauenswürdig registriert wurden (dazu später mehr). Zusammenfassend lässt sich sagen, dass die CLR Assembly-Signaturen entweder zur Ladezeit überprüft oder wenn die Assembly im GAC installiert wird.
Beachten Sie, dass der GAC als vertrauenswürdiges Repository angesehen wird: Das Einzige, was eine Assembly vor Änderungen nach ihrer Installation im GAC schützt, ist die Strong-Dateisystem-ACL, die den gesamten Inhalt des GAC betrifft. Dies ist im Wesentlichen die gleiche ACL, die die CLR und andere Binärdateien des Betriebssystems schützt. Falls ein Angreifer administrative Kontrolle über einen Computer erlangt, kann er Assemblys im GAC durch Trojanerversionen ersetzen. Die CLR ist dabei ebenfalls ahnungslos, da sie die Signaturen beim Laden von Assemblys aus dem GAC nicht erneut überprüft. Falls ein Angreifer jedoch über ausreichend Zugriff auf das Dateisystem verfügt, um Assemblys im GAC zu verändern oder zu ersetzen, kann er das Gleiche mit CLR-Binärdateien (MSCORWKS.DLL und Konsorten) oder dem Betriebssystem selbst tun. Zugunsten schnellerer Ladezeiten kümmert sich die CLR nicht um die erneute Überprüfung von Signaturen für aus dem GAC geladene Assemblys.
Starke Namen und Überprüfung
Das CLR-Assembly-Auflösungsprogramm verwendet eine von zwei Methoden, um auf Assemblys zu verweisen: schwach oder stark. Die schwache Methode berücksichtigt nur den kurzen Assemblynamen, der nur aus dem Dateinamen ohne die Erweiterung besteht. Der kurze Assemblyname für FOO.DLL lautet z.B. einfach FOO. Zur Ladezeit wird keine Versionsüberprüfung durchgeführt. Ein starker Name andererseits besteht aus dem kurzen Namen plus drei weiteren Teilen: einer Versionsnummer, Kulturinformationen und einem öffentlichen Schlüssel. Wenn Sie Ihrer Assembly einen öffentlichen Schlüssel zuweisen, verfügt diese über einen starken Namen. Andere Assemblys, die auf Ihre verweisen, verwenden diesen stärkeren vierteiligen Namen Ihrer Assembly. In der Praxis bedeutet dies, dass Sie Ihre Assembly im GAC ablegen und Versionsrichtlinien nutzen können.
Bei all diesem Gerede über öffentliche Schlüssel, Signaturen, Hashing usw. kann man leicht den Blick darauf verlieren, welche Schutzfunktionen man nun wirklich erhält. Die CLR versucht, Folgendes zu gewährleisten: Wenn Sie eine Assembly FOO.DLL erstellen und diese signieren (ihr damit einen starken Namen zuweisen), erhalten Benutzer, die von ihrer Assembly aus zur Kompilierzeit auf FOO.DLL verweisen, eine FOO.DLL zur Laufzeit, die von Ihnen erstellt wurde (der Person, die den privaten Schlüssel hinter dem starken Namen kennt). Es handelt sich dabei eventuell nicht um die exakt identische FOO.DLL, da die Versionsrichtlinien der CLR eventuell gestatten, eine unterschiedliche Version anstelle des Originals zu verwenden. Trotzdem sollte gewährleistet sein, dass der Code zur Laufzeit von derselben Person generiert wurde, die auch die ursprüngliche FOO.DLL erstellt hat. Dies hindert Dritte daran, FOO.DLL durch eine Trojanerversion zu ersetzen, die eventuell böswillig ist.
So wird diese Funktion implementiert: Wenn Sie eine Assembly kompilieren, z.B. BAR.EXE, und diese auf eine Assembly mit starkem Namen verweist, z.B. FOO.DLL, zeichnet der Compiler den starken Namen von FOO.DLL im Manifest von BAR.EXE auf. Dazu gehört ein Verweis auf den öffentlichen Schlüssel (siehe Abbildung 2). Neben den normalen Signaturüberprüfungen, die dazu dienen, eine unberechtigte Änderung der Binärdateien der Assembly zu erkennen, stellt das Ladeprogramm zur Ladezeit sicher, dass der öffentliche Schlüssel in FOO.DLL mit demjenigen übereinstimmt, der in BAR.EXE aufgezeichnet wurde. Auf diese Weise werden die Verknüpfungen zwischen Assemblys geschützt.
Abbildung 2. Ein Verweis auf eine Assembly mit starkem Namen
Was schützt BAR.EXE? Wenn Sie eine Befehlsshell öffnen und einfach BAR.EXE durch Eingabe von BAR <Eingabetaste> ausführen, welche Garantie haben Sie, dass diese nicht durch einen Trojaner ersetzt wurde? In diesem Fall keine. Denken Sie einmal darüber nach. Falls Sie eine Garantie möchten, benötigen Sie eine Methode, dem Betriebssystem den bekannten, gültigen öffentlichen Schlüssel für BAR.EXE mitzuteilen. Andernfalls könnte der Angreifer seine Trojanerversion von BAR.EXE mit einem zufällig generierten Schlüssel signieren. Die CLR wäre sicherlich in der Lage, zu überprüfen, ob BAR.EXE in sich widerspruchsfrei ist, d.h., ob ihre Signatur mithilfe des öffentlichen Schlüssels im Manifest überprüft werden kann. Nur handelt es sich dabei nicht um den öffentlichen Schlüssel, der vom ursprünglichen Autor von BAR.EXE zugewiesen wurde. Sie könnten dieses Problem eventuell lösen, indem Sie ein kleines Ladeprogramm schreiben, das einfach Assembly.Load() aufruft und die Informationen des öffentlichen Schlüssels zusammen mit dem Assemblynamen übergibt. Sie können dieses Programm z.B. LOADER.EXE nennen, hätten jedoch bei der Überprüfung von LOADER.EXE wieder das gleiche Problem.
Sie müssen sich eine Microsoft ASP.NET-Seite ähnlich wie BAR.EXE vorstellen. Während Sie auf eine Assembly mit starkem Namen von Ihrer Seite aus verweisen können:
<<A href="mailto:%@assembly">%@assembly</A> name='foo, Version=1.0.0.0,
Culture=neutral,PublicKeyToken=2d7adc3047e7238d'%>
gibt es keine Möglichkeit, ASP.NET den starken Namen der Assembly Ihrer Seite mitzuteilen. Selbst wenn Sie Ihrer Seite einen starken Namen zuweisen könnten, wäre dies wenig hilfreich. Andererseits kann auf einen vorkompilierten Handler oder ein in web.config oder machine.config registriertes Modul über einen starken Namen verwiesen werden. Natürlich schlägt diese Überprüfung fehl, falls der Angreifer die Konfigurationsdatei bearbeiten und Ihren öffentlichen Schlüssel durch seinen ersetzen kann.
Ein Implementierungsdetail ist in diesem Zusammenhang erwähnenswert. Achten Sie im oben genannten Assembly-Verweis darauf, wie auf Assemblies mit einem "öffentlichen Schlüsseltoken" anstatt mit einem vollständigen öffentlichen Schlüssel verwiesen wird. Dieses Token ist wie ein Fingerabdruck des öffentlichen Schlüssels. Wenn wir auf eine Assembly mit ihrem starken Namen verweisen, ist die beste Überprüfung, die das Ladeprogramm uns liefern kann, darauf beschränkt, sicherzustellen, dass der von uns angegebene Fingerabdruck dem öffentlichen Schlüssel der geladenen Assembly entspricht, da wir immer das öffentliche Schlüsseltoken verwenden. Wie schwierig wäre es für einen Angreifer, ein weiteres RSA-Schlüsselpaar zu generieren (für das er den privaten Schlüssel kennt), dessen öffentlicher Schlüssel denselben Fingerabdruck wie unserer hat? Ein öffentliches Schlüsseltoken wird mithilfe der niedrigen 8 Byte des 20-Byte-SHA1-Hash des öffentlichen Schlüssels konstruiert. Es gibt 2^64 mögliche Werte für ein 8-Byte-Token, was nicht sofort einen Angriff ausschließt, da die Durchführung von 2^64 Schritten mit der heutigen Hardware relativ einfach ist. Jedoch ist die Berechnung von RSA-Schlüsselpaaren ziemlich kostenaufwändig, und ihre 2^64-fache Durchführung ist eventuell nicht ohne beträchtliche finanzielle Mittel für spezielle Hardware möglich. Mit anderen Worten: Ein durchschnittlicher Hacker könnte sich diesen Aufwand eventuell alleine nicht leisten, aber ihr freundlicher Nachrichtendienst von nebenan hat es möglicherweise bereits getan.
Falls die Strong Name-Überprüfung ein Sicherheitsmerkmal ist, von dem Sie abhängig sind, sollte Ihr Bedrohungsmodell Angriffe auf den Fingerabdruck berücksichtigen, da dies eindeutig das schwächste Glied in der Kette ist. Es ist nicht schwer zu verstehen, warum sich das CLR-Team für diesen speziellen Sicherheitskompromiss entschieden hat: Die Eingabe eines öffentlichen Schlüssels mit 8-Byte erfordert 16 Tastenanschläge. Die Eingabe des vollständigen 20-Byte-SHA1-Hash, das ein höheres Maß an Sicherheit bieten würde, erfordert 40 Tastenanschläge. Aber wie häufig müssen wir öffentliche Schlüsseltokens eingeben? Nicht zu häufig, angesichts von Tools wie dem .NET Framework-Konfigurationstool. Und es muss nur eine Person ein alternatives Schlüsselpaar berechnen und veröffentlichen, das das gleiche öffentliche Schlüsseltoken wie z.B. der öffentliche Schlüssel von Microsoft hat. Wir können nur darauf hoffen, dass dieser Fingerabdruck in Zukunft verlängert wird.
Kein Aspekt dieser theoretischen Diskussion über Fingerabdrücke spielt mehr eine Rolle, falls Sie es zulassen, dass Ihr privater Schlüssel gefährdet wird. Dies würde es einem Angreifer ermöglichen, jede beliebige Assembly zu signieren, die als Trojaner verwendet werden soll.
Starke Namen und .NET-Sicherheitsrichtlinien
Bei einer Gefährdung Ihres privaten Schlüssels müssen Sie sich nicht nur über Trojanerassemblies Gedanken machen. Damit ein Angreifer eine Assembly mit starkem Namen auf Ihrem Computer durch eine Trojanerversion ersetzen kann, muss er die Strong Name-Überprüfung umgehen. Außerdem muss er Code auf Ihren Computer kopieren, was hoffentlich kein einfacher Vorgang ist. Aber es gibt noch einen direkteren und gefährlicheren Angriff, der etwas mit den .NET-Sicherheitsrichtlinien zu tun hat: Im Repository, in dem Entscheidungen über Vertrauenswürdigkeit getroffen werden. Diese Richtlinien schützen Ihren Computer vor verwalteter Malware (malicious software, böswillige Software), die über das Netzwerk an Sie gesendet werden könnte. Wenn Sie das nächste Mal als Administrator auf Ihrem Computer angemeldet sind, öffnen Sie das .NET Framework-Konfigurationstool, das sich im Menü Start unter Administrative Tools (Verwaltung) befindet. Verwenden Sie dieses Tool, um in der Laufzeitsicherheitsrichtlinie herumzustöbern. Wenn Sie die Struktur mit Codegruppen unter der Computerrichtlinienebene vollständig erweitern, werden Sie feststellen, dass diese Sicherheitsrichtlinie in einigen Fällen ein sehr hohes Maß an Vertrauenswürdigkeit auf Basis von starken Namen gewährt. Es gibt beispielsweise eine Codegruppe mit der Bezeichnung Microsoft_Strong_Name, die jedem lokal installierten Code die Berechtigung "Voll vertrauenswürdig" gewährt, der mit einem speziellen Microsoft-proprietären Schlüssel signiert ist. Dieser Schlüssel dient zum Signieren von zentralen Assemblys, aus denen sich das .NET Framework selbst zusammensetzt.
Wenn eine Organisation das .NET Framework einführt und mit der Verwendung von Features wie der No-Touch-Deployment-Funktion (NTD) beginnt, besteht die Wahrscheinlichkeit, dass immer mehr Sicherheitsrichtlinien auf der Basis von starken Namen festgelegt werden. Es erscheint zweifelhaft, dass viele Shops Programme erstellen möchten, die in Umgebungen mit eingeschränkter Vertrauenswürdigkeit ausgeführt werden. Aus diesem Grund werden Richtlinien so konfiguriert, dass sie die Berechtigung "Voll vertrauenswürdig" auf der Basis von firmeninternen starken Namen gewähren. Glücklicherweise wird bei der Angabe eines starken Namens als Teil der Sicherheitsrichtlinie der vollständige öffentliche Schlüssel verwendet, nicht nur der Fingerabdruck. Dies hilft jedoch auch nicht, wenn ein Angreifer Ihren privaten Schlüssel gestohlen hat. Sobald ein Angreifer über Ihren privaten Schlüssel verfügt, kann dieser jeden beliebigen Code signieren und ihm Ihren starken Namen zuweisen.
Das folgende Szenario verdeutlicht diese Bedrohung: Angenommen, Sie verwenden eine Windows Forms-Anwendung, die als Thick Client für einen Webdienst fungiert. Aus Gründen der Benutzerfreundlichkeit haben Sie diesen Thick Client über NTD veröffentlicht. Die Benutzer klicken einfach auf eine Verknüpfung in ihrem Browser und erhalten immer problemlos die aktuellste und beste Version des Client. Das Clientprogramm führt jedoch gelegentlich Aufrufe über P/Invoke durch, um auf Legacycode zuzugreifen, und dies löst aufgrund der Art und Weise, wie der Client weitergegeben wurde, eine Ausnahme aus. Beim Download von Code aus dem Netzwerk wird dieser als mobiler Code angesehen, der standardmäßig nicht ausreichend Vertrauenswürdigkeit besitzt, um nicht verwalteten Code direkt aufzurufen. Zur Lösung dieses Problems könnten Sie ein Update der .NET-Sicherheitsrichtlinie innerhalb Ihrer gesamten Organisation entwickeln, das jeder Assembly mit Ihrem starken Namen die Berechtigung "Voll vertrauenswürdig" gewährt. Abbildung 3 zeigt, wie dies aussehen könnte.
Abbildung 3. Vergeben der Berechtigung "Voll vertrauenswürdigkeit" an einen starken Namen
Wenn in diesem Szenario ein Angreifer Ihren privaten Schlüssel gestohlen hat, kann er Code auf seiner Website veröffentlichen, der Ihren starken Namen besitzt. Kann er dann noch jemanden aus Ihrer Organisation überzeugen, auf eine Verknüpfung zu klicken, die auf seinen Code verweist, wird sein Code mit der vollständigen Berechtigung der Person ausgeführt, die auf die Verknüpfung geklickt hat. Dies geschieht automatisch und ohne Warnung. Dies ist ein unheimliches Szenario und zeigt, wie wichtig es ist, Ihre privaten Schlüssel sorgfältig zu schützen.
Beachten Sie, dass dies auch für andere Typen von automatischen Schlüsseln gilt, einschließlich solcher, die zur Bildung von Authenticode-Signaturen verwendet werden. Stellen Sie sich das gleiche Szenario vor, in dem eine Authenticode-Signatur (was in der .NET-Sicherheitsrichtlinie dem Publisher-Nachweis entspricht) für die Gewährung der vollen Vertrauenswürdigkeit anstatt eines starken Namens verwendet wurde. Die Gefahren sind in Wesentlichen die gleichen.
Beachten Sie die Möglichkeit zur "Tiefenabwehr", indem Sie die ACME_Strong_Name-Codegruppe nicht auf der Stammebene, sondern unter der LocalIntranet_Zone-Codegruppe (wie in Abbildung 4) einfügen. Dies entspricht in etwa der Art und Weise, wie Microsoft_Strong_Name unter der My_Computer_Zone-Codegruppe platziert ist. Untergeordnete Codegruppen werden nur bewertet, wenn die übergeordneten Codegruppen übereinstimmen. Auf diese Weise erstellen Sie eine Richtlinie, die zwei Dinge voraussetzt, bevor die volle Vertrauenswürdigkeit gewährt wird: Die Assembly muss aus der LocalIntranet-Zone geladen werden und über Ihren starken Namen verfügen. Natürlich müssen Sie sich jetzt über interne Angriffe Gedanken machen, was eine ernstere Bedrohung ist, als die meisten Unternehmen zugeben wollen.
Die kann nicht oft genug betont werden. Falls Sie starke Namen verwenden, benötigen Sie ein sicheres Verfahren zum Signieren Ihrer Assemblies, da Ihre privaten Schlüssel sonst anfällig sind. Auf einen solchen Prozess gehen wir später noch ein. Zunächst aber gibt es einen weiteren Grund, warum Ihre privaten Schlüssel geschützt werden sollten.
Öffentliche Schlüssel und Versionsprüfung
Versionsrichtlinien sind ein interessanter Aspekt im Hinblick auf öffentliche Schlüssel. Als Grundlage dient hierbei die Annahme, dass eine Assembly immer über denselben öffentlichen Schlüssel verfügt und sich nur ihre Version im Laufe der Zeit ändern kann. Es gibt verschiedene Möglichkeiten dafür, wie Versionsrichtlinien von einem Systemadministrator oder Softwareherausgeber genutzt werden können, um die Version einer Assembly zu beeinflussen, die in eine Anwendung geladen wird. Versionsrichtlinien funktionieren jedoch nur, wenn der Name der Assembly und ihr öffentlicher Schlüssel konstant bleiben. Mit anderen Worten: Es ist nicht ratsam, Version 1 einer Assembly FOO mit einem öffentlichen Schlüssel zu veröffentlichen und dann Version 2 von FOO mit einem zweiten öffentlichen Schlüssel. Öffentliche Schlüssel für Assemblys stellen langfristige Festlegungen dar: Sobald Sie einen ausgewählt und einer Assembly zugeordnet haben, müssen Sie diesen wirklich während des gesamten Lebenszyklus dieser Assembly und durch alle Versionen hindurch beibehalten.
Dies widerspricht völlig dem Grundgedanken der Schlüsselsperrung, einem wichtigen Aspekt bei PKI-Systemen (Public Key Infrastructure), dem folgende Vorstellung zugrunde liegt: Wenn Sie Ihren privaten Schlüssel verlieren oder glauben, dass dieser gefährdet ist, können Sie Ihren öffentlichen Schlüssel widerrufen und einen neuen beantragen (dies ist in Wahrheit nicht annähernd so einfach, wie es klingt). Falls Sie in der CLR Versionsrichtlinien und den GAC zur Verwaltung von Assemblys verwenden, die von vielen Anwendungen gemeinsam genutzt werden, stellt es aus der Sicht der Versionsprüfung und Kompatibilität eine Katastrophe dar, wenn Ihr privater Schlüssel gefährdet ist. Wenn Sie ein neues Schlüsselpaar für sich erstellen, müssen Sie alle Anwendungen neu kompilieren, die von Ihrem alten öffentlichen Schlüssel abhängig waren. In diesem Fall gibt es kein sauberes Update, was ein weiterer sehr guter Grund dafür ist, Ihren privaten Schlüssel zu schützen.
Wenn Ihnen die Sicherheit wirklich wichtig ist (und das sollte sie sein), investieren Sie in Hardware, die Ihnen eine Offlinespeicherung Ihrer Schlüssel ermöglicht. Dabei handelt es sich um so genannte "Smartcards". Dies ist eine so gute Idee, dass sich ein zukünftiger Artikel diesem Thema widmen wird. In der Zwischenzeit soll jedoch beschrieben werden, wie Sie sofort mit einem Verfahren beginnen können, das "verzögertes Signieren" genannt wird.
Verwenden von verzögertem Signieren zur Verhinderung der Offenlegung
Eine der einfachsten Methoden, die Offenlegung Ihrer privaten Schlüssel zu verhindern, besteht darin, Ihre Assembly verzögert zu signieren. Dieses Verfahren ermöglicht Ihrem Compiler die Erstellung einer Assembly ohne Kenntnis Ihres privaten Schlüssels. Dieser benötigt dazu nur den öffentlichen Schlüssel, der kein Geheimnis darstellt. Und so funktioniert's:
Der erste Schritt besteht in der Generierung eines oder mehrerer RSA-Schlüsselpaare für Ihre starken Namen. Wenn Sie noch auf Ihre Smartcard-Hardware warten (die Bestellung ist aufgegeben, oder?), müssen Sie Ihre Schlüssel im Dateisystem speichern. Stellen Sie also sicher, dass dies auf einem sicheren Computer erfolgt, der mit keinem Netzwerk verbunden ist. Mit dem folgenden Befehl generieren Sie die einzelnen RSA-Schlüsselpaare:
sn -k pubpriv
Das SN-Tool erstellt ein neues Schlüsselpaar in einer Datei mit der Bezeichnung pubpriv. Führen Sie sofort darauf den folgenden Befehl aus, um nur den öffentlichen Schlüssel in eine zweite Datei mit der Bezeichnung pub zu kopieren.
Dsn -p pubpriv pub
Kopieren Sie pub auf einen Wechseldatenträger, und übertragen Sie diesen auf einen anderen Computer. Entfernen Sie nun pubpriv vom Computer, und lagern Sie die Datei in einem Tresor. Sie benötigen diese Datei erst wieder, wenn Sie bereit sind, Ihre erste signierte Assembly an jemanden außerhalb Ihrer Produktgruppe auszuliefern. Geben Sie pub an alle Benutzer weiter, die ihre Assemblys kompilieren müssen. Es handelt sich um kein Geheimnis. Machen Sie sich also keine Sorgen, dass diese von einem böswilligen Benutzer gestohlen wird.
Verwenden Sie diese Prozedur jeweils einmal für jedes Schlüsselpaar, das Sie für starke Namen benutzen möchten. Sobald alle privaten Schlüssel sicher im Tresor gelagert sind und Sie sicher sind, dass diese nicht verloren gehen, beschädigt oder gestohlen werden können, zerstören Sie die Festplatte des Computers, der für ihre Generierung verwendet wurde, so dass keinerlei Daten mehr rekonstruiert werden können. Wenn Sie natürlich eine Smartcard verwendet hätten, wäre kein privater Schlüssel jemals auf einer Festplatte gespeichert worden, was die Aufbewahrung und Wartung vereinfacht (ist Ihre Smartcard-Hardware bereits angekommen?).
Verwenden Sie für ein verzögertes Signieren einer Assembly das AssemblyKeyFile-Attribut, um auf die pub-Datei zu verweisen, die sich auf dem Computers jedes Entwicklers befinden sollte, der diese benötigt. Wenden Sie außerdem das AssemblyDelaySign-Attribut wie folgt an:
[assembly: AssemblyKeyFile(@"javascript:void(null);")]
[assembly: AssemblyDelaySign(true)]
Dies weist den Compiler an, zwar den öffentlichen Schlüssel in die von ihm erstellte Assembly einzubetten, jedoch keine Signatur zu generieren. Der Compiler reserviert einen Platz in der Assembly, damit die Signatur zu einem späteren Zeitpunkt hinzugefügt werden kann.
Weisen Sie schließlich auf allen Computern, die zum Testen dieser nicht signierten Assemblys verwendet werden, die CLR an, die Strong Name-Überprüfung für den entsprechenden öffentlichen Schlüssel zu überspringen. Dafür benötigen Sie das öffentliche Schlüsseltoken für Ihren öffentlichen Schlüssel, also den weiter oben beschriebenen Fingerabdruck. Sie können diesen mit dem folgenden Befehl ermitteln:
sn -t pub
Angenommen, das öffentliche Schlüsseltoken lautete bc19568c6e03e7e6. Sie können das Token für die Überprüfungsumgehung auf dem Computer folgendermaßen registrieren:
sn -Vr *,bc19568c6e03e7e6
Dadurch wird die CLR vom Versuch abgehalten, die Signatur aller Assemblys mit dem oben genannten öffentlichen Schlüsseltoken zu überprüfen, entweder zur Ladezeit oder wenn Sie die Assembly in den GAC installieren.
Wenn Sie bereit sind, eine Assembly an jemanden außerhalb Ihres Entwicklungsteams zu liefern, übertragen Sie die kompilierte Assembly auf einen sicheren Computer, holen Sie die Datei pubpriv aus Ihrem Tresor, installieren Sie diese auf dem Computer, und führen Sie den folgenden Befehl aus:
sn -R assemblyfile
Dadurch wird der private Schlüssel zum Signieren der Assembly verwendet und der vom Compiler reservierte Platz ausgefüllt. Da durch diesen Vorgang die Zerstörung einer weiteren Festplatte erforderlich wird, scheint die Verwendung von Smartcards die einzig praktikable Lösung.
Wenn Ihnen all dieses Gerede über das Zerstören von Festplatten nicht gefällt und Sie aus irgend einem Grund nicht ca. 100 für diese Smartcard-Hardware ausgeben möchten, verwenden Sie anstatt eines Festplattenlaufwerks ein RAM-Laufwerk, wenn Sie einen temporären Speicher für private Schlüssel benötigen. Ein Neustart des Computers, nachdem Sie fertig sind, stellt sicher, dass Ihre vertraulichen Daten für niemanden, bis auf finanziell extrem gut ausgestattete Angreifer, zugänglich sind.
Schützen Ihres Entwicklungsteams
Das verzögerte Signieren stellt keineswegs eine perfekte Lösung dar. Da Ihr Team die Strong Name-Überprüfung für einen oder mehrere öffentliche Schlüssel deaktivieren muss, um ihre eigenen Assemblies während der Entwicklung zu testen, sollten Sie es unbedingt vermeiden, Vertrauenswürdigkeit für eine Assembly allein aufgrund des Vorhandenseins eines dieser unüberprüften starken Namen zu übertragen. Mit Ihrem öffentlichen Schlüssel kann ein Angreifer eine bösartige Assembly genau so einfach verzögert signieren, wie Sie dies mit Ihren eigenen Assemblies tun können. Und Sie müssen davon ausgehen, dass jeder Angreifer Ihren öffentlichen Schlüssel leicht ermitteln kann, da dieser als Metadaten in jeder Strong Name-Assembly eingebettet ist, die Sie erstellen.
Stellen Sie sicher, dass Sie Ihr Team über diese Problematik aufklären. Wenn Sie Ihre Assemblies gegenüber .NET-Sicherheitsrichtlinien identifizieren müssen, verwenden Sie keinen ungeprüften starken Namen, da dieser leicht von einem böswilligen Benutzer wie beschrieben in einen Trojaner verwandelt werden könnte. Ein Alternative wäre die Verwendung eines temporären, intern vergebenen Codesignaturzertifikats (das alt bewährte Authenticode), dessen privater Schlüssel innerhalb Ihres Teams bekannt ist und das zum Signieren aller Assemblys verwendet wird. In diesem Fall verwenden Sie einen Publisher-Nachweis anstatt eines Strong Name-Nachweises, um Ihre Assemblys in der Richtlinie während der Entwicklungs- und Testphase zu identifizieren.
Schlussfolgerung
Starke Namen sind äußerst leistungsfähig, aber damit verbunden ist auch eine große Verantwortung. Die Fähigkeit der CLR, Sicherheitsgarantieren auf der Grundlage von starken Namen zu übernehmen, ist nur so gut wie der Schutz, den wir unseren privaten Schlüsseln zu Teil werden lassen. Also schützen Sie Ihre Schlüssel!