Arbeiten mit Zertifikaten in .NET 2.0 – Teil 1

Veröffentlicht: 26. Mai 2006

Von Dominick Baier

Die managed Unterstützung für Zertifikate samt Verschlüsselung und digitale Signaturen war sicherlich eines der heiß ersehnten Sicherheits-Features von .NET 2.0. Dieser zweiteilige Artikel beschreibt den Zugriff auf den Zertifikats-Speicher, den Umgang mit Zertifikaten, deren Prüfung sowie die kryptografischen Operationen, die mit Zertifikaten durchgeführt werden können.

Auf dieser Seite

 Das Zertifikat an sich…
 Eigenschaften und UI
 Der Zertifikat-Speicher
  Überprüfen von Zertifikaten
 Wo stehen wir?
 Der Autor

Das Zertifikat an sich…

…wird durch die Klasse X509Certificate2 im System.Security.Cryptography.X509Certificates Namensraum repräsentiert. Die etwas unschöne 2 am Ende des Klassen-Namens resultiert daraus, dass es bereits in .NET 1.1 eine Zertifikats-Klasse gab, die allerdings nur über sehr eingeschränkte Möglichkeiten verfügte. Benutzen Sie nach Möglichkeit immer die neue Version, die rein technisch eine Ableitung der Ur-Klasse ist. Wenn Sie aus Migrations-Gründen 1.1 Code unterstützen müssen, verfügt X509Certificate2 auch über einen Konstruktor, der aus dem alten Format konvertieren kann.

Üblicherweise arbeitet man mit Zertifikaten in zwei „Aggregat-Zuständen“. Aus dem Windows Zertifikat-Speicher (dazu später mehr) oder als Datei. In Datei-Form kennt man das „.cer“ Format, das lediglich ein Zertifikat mitsamt öffentlichem Schlüssel enthält, sowie „.pfx“, das zusätzlich noch den privaten Schlüssel umfasst und meist Passwort geschützt ist.

Um ein „.cer“ Datei vom Dateisystem zu laden, übergibt man den Dateinamen lediglich an den Konstruktor:

X509Certificate2 cert1 = new X509Certificate2("dbaier.cer");

Passwort-geschützte „.pfx“ Dateien müssen mit Hilfe der SecureString Klasse geöffnet werden. SecureString verschlüsselt das Passwort intern und versucht das Ablegen dieses Geheimnisses im Speicher zu minimieren. Aus diesem Grund kann ein SecureString nur mit (value type) chars befüllt werden. Wenn Sie den Benutzer von der Konsole aus nach dem Passwort fragen möchten, ist folgende Routine hilfreich:

private SecureString GetSecureStringFromConsole()
{
    SecureString password = new SecureString();

    Console.Write("Enter Password: ");
    while (true)
    {
        ConsoleKeyInfo cki = Console.ReadKey(true);

        if (cki.Key == ConsoleKey.Enter)
            break;
        else if (cki.Key == ConsoleKey.Escape)
            return null;
        else if (cki.Key == ConsoleKey.Backspace)
        {
            if (password.Length != 0)
                password.RemoveAt(password.Length - 1);
        }
        else
            password.AppendChar(cki.KeyChar);
    }

    Console.WriteLine("");
    return password;
}

Der Artikel "Credential Management with the .NET Framework 2.0" von Kenny Kerr beschreibt, wie man den gewohnten Windows Passwort-Dialog benutzen kann, um einen SecureString zu erzeugen. Wenn Sie lediglich eine Zeichenkette in einen SecureString umzuwandeln möchten, bewerkstelligt dies folgender Code (dabei geht allerdings der Schutz von SecureString verloren, auch wenn dieser gering ist):

private SecureString GetSecureString(string someString)
{
    SecureString ss = new SecureString();

    foreach (char c in someString)
    {
        ss.AppendChar(c);
    }

    return ss;
}

Nachdem Sie einen SecureString erzeugt haben, können Sie die „.pfx“ Datei per Konstruktor laden.

X509Certificate2 cert2 = new X509Certificate2
("dbaier.pfx", password);

 

Eigenschaften und UI

Ein geladenes Zertifikat gibt über seine zahlreichen Properties und Methoden Auskunft über seinen Inhalt. Sie haben auch Zugriff auf die Windows Standard-Dialoge für Zertifikate. So können Sie mit folgendem Code die Zertifikats-Eigenschaften auf dem Bildschirm anzeigen

X509Certificate2UI.DisplayCertificate(cert1);

Bb979598.cert_properties(de-de,MSDN.10).jpg

Abbildung 1: Der Zertifikats-Dialog

Wenn Sie dem Benutzer mehrere Zertifikate zur Auswahl geben möchten, gibt es dafür ebenfalls einen Dialog. Fassen Sie dazu die Zertifikate in einer Collection zusammen.

X509Certificate2Collection certCol = 
  new X509Certificate2Collection();
certCol.Add(cert1);
certCol.Add(cert2);

X509Certificate2 selectedCert = 
  X509Certificate2UI.SelectFromCollection(
    certCol, 
    "Meine Anwendung", 
    "Wählen Sie ein Zertifikat aus", 
    X509SelectionFlag.SingleSelection)[0];

Bb979598.cert_picker(de-de,MSDN.10).jpg

Abbildung 2: Der Zertifikat-Auswahl Dialog

Hinweis: Die UI-Klassen befinden sich im System.Security.dll Assembly. Sie müssen diese Referenz manuell hinzufügen.

Die nachfolgende Tabelle beschreibt die wichtigsten Eigenschaften eines Zertifikats.

Eigenschaft

Bedeutung

Subject

Der sog. Subject Name des Zertifikats, bei einem Active Directory Benutzer-Zertifikate z.B. CN=Dominick Baier, CN=Users, DC=leastprivilege, DC=home

Issuer

Name des Herausgebers, z.B. CN=LeastPrivilegeCA, DC=leastprivilege, DC=home

FriendlyName

Beschreibender Name (falls vorhanden)

NotBefore / NotAfter

Gültigkeits-Bereich

Thumbprint

SHA1 Hash des Zertifikats

HasPrivateKey

Gibt an ob ein assozierter privater Schlüssel vorhanden ist

PublicKey / PrivateKey

Zugriff auf den öffentlichen bzw. privaten Schlüssel (falls vorhanden)

Zusätzlich erlaubt die GetNameInfo() Methode Zugriff auf andere eingebettete Identitäts-Informationen. So ermittelt folgender Code den UPN (User Principal Name) Namen eines Zertifikates. Dieser wird von der Windows Active Directory CA eingebettet und macht es einfach, den Windows-Benutzer mit dem Zertifikat zu assoziieren. Der UPN könnte z.B. direkt benutzt werden um eine WindowsIdentity mit Hilfe von Protocol Transition zu erzeugen (Protocol Transition wird nur auf Windows Server 2003 unterstützt).

string upn = cert1.GetNameInfo(X509NameType.UpnName, false);
WindowsIdentity id = new WindowsIdentity(upn);

 

Der Zertifikat-Speicher

Windows beinhaltet einen Dienst zum Speichern und Verwalten von Zertifikaten. Die Benutzung dieses Dienstes im Gegensatz zum direkten Ablegen der Schlüssel auf der Festplatte hat folgende Vorteile:

  • Jeder Benutzer erhält seinen eigenen Speicher, der in seinem Profil abgelegt ist.

  • Dadurch werden Zertifikate und private Schlüssel automatisch per Benutzer mit Zugriffsrechten versehen

  • Es gibt einen Maschinen-Speicher, auf den alle Benutzer zugreifen können (sofern es die Zugriffs-Rechte erlauben)

  • Der Speicher lässt sich in benannte logische Unterordner unterteilen

  • Der Speicher abstrahiert den physikalischen Speicher-Ort, an dem die Schlüssel abgelegt sind. Unterstützte Hardware wie Smart Cards oder USB Tokens werden transparent in den Speicher „eingeblendet“. Es ist dafür kein spezieller API notwendig.

Abbildung 3 zeigt den Standard Zertifikats-Speicher für einen Benutzer. Dieses Verwaltungs-Tool wird über certmgr.msc von der Kommandozeile aus gestartet.

Bb979598.certmgr(de-de,MSDN.10).jpg

Abbildung 3: Der Zertifikat-Speicher

Der Zertifikats-Speicher wird über die Klasse X509Store angesprochen. Um den Speicher programmatisch zu öffnen, gibt man den Speicher-Ort an (Benutzer oder Maschine), sowie den Namen des Unterordners. Für die Standard Ordner kann man den StoreName Aufzählungs-Typ benutzen. Die Werte sind allerdings nicht immer synchron mit den Bildschirm-Namen im MMC Snap-In. Nachfolgende Tabelle zeigt die wichtigsten Ordner, deren Bedeutung und den dazugehörigen Namen im Enum.

Ordner Name

StoreName Wert

Bedeutung

Personal

My

Ordner für persönliche Zertifikate. Diese Zertifikate haben für gewöhnlich einen assozierten privaten Schlüssel

Other People

AddressBook

Ordner für die Zertifikate von Menschen oder Diensten, mit denen man Daten austauscht.

Trusted Root Certification Authorities

Root

Liste der Zertifikate von vertrauenswürdigen CAs.

Trusted Publishers

TrustedPublishers

Liste von Zertifikaten von vertrauenswürdigen Software-Herausgebern (wird z.B. von ClickOnce verwendet).

Um das erste Zertifikat aus dem persönlichen Ordner des aktuellen Benutzers zu öffnen, ist folgender Code notwendig:

X509Store store = new X509Store(
  StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);

X509Certificate2 cert = store.Certificates[0];

store.Close();

Da das Certificates Property der X509Store Klasse vom Typ X509Certificate2Collection ist, kann dieses auch an die UI Klasse zur Auswahl eines Zertifikates weitergereicht werden.

Das Suchen nach Zertifikaten im Speicher wird ebenfalls unterstützt. Dazu muss man sich entscheiden, nach welchem Kriterium gesucht werden soll. Der X509FindType Aufzählungstyp hält hier diverse Werte vor, z.B. nach Aussteller, Gültigkeitsbereich, Subject Name oder auch nach dem Typ der Vorlage, die benutzt wurde, um das Zertifikat zu beantragen. Ein eindeutiger Wert ist der SubjectKeyIdentifier oder der Thumbprint. Kopieren Sie einen der beiden Werte aus dem Zertifikats-Eigenschaften-Dialog in Ihren Code, um ein Zertifikat damit zu referenzieren.

private static X509Certificate2 FindCert(X509Store store)
{
    return store.Certificates.Find(
        X509FindType.FindBySubjectKeyIdentifier,
        "21f2bf447298e83056a69eb02ebe9085ed97f10a", 
        true)[0]; 
}

Es ist ebenfalls möglich, neue Ordner zu erstellen, sowie Zertifikate in vorhandene bzw. neue Ordner zu importieren. Dies ist vor allem in Deployment Szenarien interessant.Übergeben Sie einen nicht existierenden Ordner-Namen an den X509Store Konstruktor, wird dieser beim ersten Gebrauch erzeugt. Danach können sie mit der Add Methode der Certificates Collection Zertifikate hinzufügen. Nachfolgender Code erzeugt einen Ordner mit Namen „TestStore“ und importiert ein Zertifikat von der Festplatte

static void ImportCert()
{
    X509Certificate2 cert = new X509Certificate2("dbaier.cer");

    X509Store store = new X509Store(
      "TestStore", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadWrite);
    store.Add(cert);
    store.Close();
}

 

Überprüfen von Zertifikaten

Der letzte große Bereich der Zertifikat APIs beschäftigt sich mit Validierung. Es gibt diverse Gründe, warum Zertifikate nicht mehr gültig sein können, z.B. sie könnten bereits abgelaufen sein, von einer nicht als vertrauenswürdig eingestuften CA stammen, oder auf einer Rückruf-Liste stehen. Diese Überprüfungen will man oft auch für die gesamte Zertifikats-Kette und nicht nur für das unmittelbar vorliegende Zertifikate durchführen. Die neue Klasse X509Chain erlaubt es eine Überprüfungs-Richtlinie zu konstruieren und auf ein Zertifikat anzuwenden. Mit dem ChainPolicy Property werden die Überprüfungs-Kriterien konfiguriert. Nachfolgende Tabelle erklärt die Bedeutung der einzelnen Einstellungen.

ChainPolicy Einstellung

Bedeutung

VerificationFlags

Damit lassen sich bestimmte Überprüfungen abschalten, z.B. auf unbekannte CAs oder abgelaufene Zertifikate. Einer vollständigen Überprüfung entspricht X509VerificationFlags.NoFlag.

RevocationMode

Bestimmt, ob Rückruf-Listen überprüft werden sollen. Die Werte X509RevocationMode.Online bzw. Offline geben an ob etwaige Rückruf-Listen der CA(s) heruntergeladen werden sollen.

RevocationFlag

Bestimmt, welche Zertifikate in der Kette auf Rückruf getestet werden – Alle, Alle außer dem Root-Zertifikate oder nur das End-Zertifikat. Einer Überprüfung aller Zertifikate entspricht der Wert X509RevocationFlag.EntireChain.

UrlRetrievalTimout

Gibt das Timeout für den Download von Online Rückruf-Listen an.

VerificationTime

Gibt einen Zeitpunkt an, aus dessen Sicht die Überprüfung durchgeführt werden soll. Dies ist oftmals bei der Verifizierung von digitalen Signaturen wichtig, da das Zertifikat in diesem Fall nicht zwingend zum Überprüfungs-Zeitpunkt gültig sein muss, sondern zu dem Zeitpunkt, an dem die Signatur geleistet wurde.

ExtraStore

Ermöglicht eine zusätzliche Zertifikat-Collection anzugeben, die bei der Überprüfung berücksichtigt werden soll.

Die Prüfung wird durch Aufruf der Build() Methode durchgeführt. Danach hat man Zugriff auf einen Gesamt-Status Code über die ChainStatus-Eigenschaft. Wenn mehrere Fehler aufgetreten sind, kann man die Details durch Untersuchen der ChainElements Collection in Erfahrung bringen.

Nachfolgender Code führt eine vollständige Prüfung eines Zertifikates durch:

static void CheckCert(X509Certificate2 cert)
{
  X509Chain chain = new X509Chain();
    
  chain.ChainPolicy.RevocationFlag = 
    X509RevocationFlag.EntireChain;
  chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
  chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

  chain.ChainPolicy.VerificationFlags = 
    X509VerificationFlags.NoFlag;

  // Verschieben des Prüfungs-Zeitpunktes
  //chain.ChainPolicy.VerificationTime = 
  //  new DateTime(1999, 1, 1);

  chain.Build(cert);

  if (chain.ChainStatus.Length != 0)
    Console.WriteLine(chain.ChainStatus[0].Status);
}

 

Wo stehen wir?

Sie kennen sich nun im X509Certificates Namensraum aus. Sie können Zertifikate laden – entweder aus einer Datei oder dem Zertifikat-Speicher, die Eigenschaften erfragen und die Standard Windows Dialoge für Zertifikate benutzen. Weiterhin wissen Sie, wie man Zertifikate in den Speicher importiert und Zertifikate auf ihre Gültigkeit überprüft. Im zweiten Teil dieses Artikels beschäftigen wir uns mit dem Schwester-Namensraum Pkcs. In ihm sind die Klassen enthalten, um mit Zertifikaten Verschlüsselung und digitale Signaturen durchzuführen.

 

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 unterwww.leastprivilege.com

Artikel-Serie „Arbeiten mit Zertifikaten in .NET 2.0“ im Überblick:

Teil 1
https://www.microsoft.com/germany/msdn/library/security/ArbeitenmitZertifikateninNET20Teil1.mspx

Teil 2
https://www.microsoft.com/germany/msdn/library/security/ArbeitenmitZertifikateninNET20Teil2.mspx