Anwendung über eine an die MAC-Adresse gebundene Lizenzierung schützen

Heute hatte ich im Rahmen eines Projektes die Gelegenheit, mich mit einer Lizenzierung einer eigenen C# Anwendung zu beschäftigen. Nach kurzer Recherche bin ich dann bei Rhino Licensing gelandet. Auch wenn es nur wenige How-Tos und Tutorials dazu gibt, so zögerte ich aufgrund der guten Rezensionen nicht, die Bibliothek mal auszuprobieren. Herausgekommen ist eine nette Implementierung, die ich hier kurz vorstellen möchte, ohne mich mit den Grundlagen aufzuhalten. Für die ganz eiligen gibt’s am Ende das VS2010-Projekt. Hinweis: Ein guter, knapper Einstieg ist hier zu finden. Das Vorgehen wird von mir aufgegriffen.

Die Oberfläche zu Erzeugung der privaten und öffentlichen Schlüssel und der Lizenzen

Über die grafische Oberfläche können komfortabel sowohl private und öffentliche Schlüssel und die Lizenzen erzeugt werden. Es können Standard- und Trial-Lizenzen mit einem definiertem Ablaufdatum erzeugt werden. Über die Angabe beliebiger zusätzlicher Attribute können weitere Informationen in der Lizenz verankert werden, was hier insbesondere für die Bindung an eine MAC-Adresse genutzt wird. Das VS2010 Projekt steht am Ende des Artikels zum Download bereit. Das grundsätzliche Vorgehen (straight-forward ohne GUI Gedöns) inklusive der Integration in die zu schützende Anwendung soll im folgenden kurz erläutet werden.

Welche Features sind gefordert?
  • Eine eigene Anwendung soll mittels Lizenzierung geschützt werden
  • Lizenz soll in Form einer Lizenz-Datei vorliegen (z.B. license.xml)
  • Es soll möglich sein, die Lizenz an einen Rechner zu binden (z.B. über die MAC-Adresse)
Was bietet Rhino Licensing?
  • .NET open source licensing framework
  • Asymetrisches Verschlüsselungsverfahren (es gibt einen public und einen private key)
  • Einbindung zusätzlicher Attribute (z.B. MAC-Adresse) in die Lizenz
  • Unterschiedliche Arten von Lizenzen (z.B. Trial)
  • Verifikation der Lizenz an einem Server – dieses Feature wurde jedoch nicht benötigt und nicht weiter berücksichtigt
Ziel der Implementierung: Gültige Lizenz und passende MAC-Adresse

Beim Start der eigenen .NET Anwendung soll die Lizenz-Datei license.xml ausgewertet werden. Ist sie gültig und stimmt die MAC-Adresse aus der Lizenz mit einer des PCs überein, startet die Anwendung. Andernfalls erfolgt eine Fehlermeldung.

1)  Erzeugung des öffentlichen und privaten Schlüssels

Wir benötigen zunächst ein Schlüsselpaar: Der private Schlüssel ist nur mir bekannt und wird später zur Erzeugung der Lizenzen benötigt. Der öffentliche Schlüssel wird mit der Anwendung ausgeliefert und ermöglicht eine Validierung der Lizenz. Das ist ziemlich einfach:

var rsa = new RSACryptoServiceProvider( 2048);
File.WriteAllText( "publicKey.xml", rsa.ToXmlString( false ) );
File.WriteAllText( "privateKey.xml" rsa.ToXmlString( true ) );

2) Erzeugung der Lizenz

Zunächst muss ein Verweis auf die Assembly Rhino.Licensingerzeugt werden. Um nun eine Lizenz zu erzeugen, benötigen wir den privaten Schlüssel, den Namen und in meinem Falle das zusätzliche Attribut “MAC-Adresse”. Wir erzeugen eine Lizenz-Datei license.xml mit wenigen Zeilen:

var id = Guid.NewGuid();

LicenseType licenseType = LicenseType.Standard;
var expirationDate = DateTime.Now + TimeSpan.FromDays(30);
var name = "flowtec";

IDictionary attributes = new Dictionary();
attributes.Add( "MacAddress", "F0DEF14E8928" );

var generator = new LicenseGenerator(File.ReadAllText( "privateKey.xml" ));
string license = generator.Generate( name, id, expirationDate, attributes, licenseType );

File.WriteAllText( "license.xml", license );

Die nun erzeugte license.xml:

<?xml version="1.0" encoding="utf-8"?>
<license id="28407afb-3fe2-4be5-b6ed-18a53be750bb" expiration="2012-07-17T13:26:37.3241452" type="Standard" MacAddress="F0DEF14E8928">
  <name>flowtec</name>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>CYVu0UbkwROv0JS0mV/CXFh+oRg=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>cbYwmq6pk6ZSaV8QCXtXbqLgr1ODDw6lP88MezU0boCpftR14Y/alV3PMw7RcNbBfvQbyeoIWk9xghJHNlC6qnQ20p/CAPqrxkiE9IOUND8gNvc1L+TJSx+o/s5SJhDjWU7t9PsiK6I3aMSBYhjKIR029ydVlGm8L3ItTIhmGsVGueU0kig2+HoEGMfDI2axqbftk+KjZW662Z5u9gAi4mu7tm29kVOvAMsqqCI2o3RJ4iTXw3E5Dyz8Ad7GlP75RlEQFXi0h6us7HUjMtKfDsg7AQvQpuCM6fr1dxTesoDmjXc6dwm4h8yKf+WYYnMqqd+6eoSi/XZZqUe/AeH3MA==</SignatureValue>
  </Signature>
</license>

3) Schützen der Anwendung

Die Anwendung wird mit license.xml und publicKey.xml ausgeliefert. Für den öffentlichen Schlüssel muss eine grundsätzliche Entscheidung getroffen werden, ob der Schlüssel als Datei beigefügt, oder als Resource in die Anwendung eingebettet wird. Für die Einbettung als Ressource spricht:

  • Eine Datei weniger, die mit ausgeliefert werden muss
  • Über eine Änderung des öffentlichen Schlüssel lässt sich bewirken, dass alte Lizenz-Dateien nicht mehr funktionieren (praktisch im Falle einer neuen Software-Version)
  • … aber: die Lizenzen müssen immer mit dem privaten Schlüssel erzeugt werden, dessen passender öffentlicher in der Anwendung integriert ist

Die Überprüfung der MAC-Adresse geschieht nicht mit Rhino Licensing, sondern muss selbst gemacht werden. Meine Implementierung:

        static void Main()
        {
            try
            {
                var publicKey = File.ReadAllText( "publicKey.xml" );

                var licenseValidator = new LicenseValidator( publicKey, "license.xml" );
                licenseValidator.AssertValidLicense();

                foreach ( KeyValuePair pair in licenseValidator.LicenseAttributes )
                {
                    if ( pair.Key.Equals( "MacAddress" ) )
                    {
                        if ( !pair.Value.Equals( GetFirstMacAddress() ) )
                        {
                            throw new Rhino.Licensing.LicenseNotFoundException("invalid mac address");
                        }
                    }
                }
            }
            catch ( Rhino.Licensing.LicenseFileNotFoundException ex )
            {
                MessageBox.Show( "License file not found.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error );
                return;
            }
            catch ( Rhino.Licensing.LicenseNotFoundException ex )
            {
                MessageBox.Show( "License is invalid.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error );
                return;
            }

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault( false );
            Application.Run( new Form1() );
        }

        private static string GetFirstMacAddress()
        {
            var mac =
            (
                from nic in NetworkInterface.GetAllNetworkInterfaces()
                select nic.GetPhysicalAddress().ToString()
            ).FirstOrDefault();

            return mac;
        }

Das war’s!

Noch folgende Anmerkungen:

  • Die MAC-Adresse steht im Klartext in der Lizenz-Datei license.xml. Eine manuelle Manipulation würde dennoch fehlschlagen, da die Signatur in der Lizenz dann nicht mehr passt. Ein Nachteil ist es dennoch, da es möglich ist die eigene MAC-Adresse zu fälschen. Eine einfache Lösung für dieses Problem wäre, die MAC-Adresse nicht im Klartext in der Lizenz anzugeben – mehr dazu bald hier in diesem Blog.
  • Es wird immer die erste MAC Adresse ausgelesen und verglichen – da gibt es wahrscheinlich bessere Strategien?!
  • Weitere Attribute wie Firmenname, PC-Name, Software-Version,.. ließen sich genauso manipulationssicher in der Lizenz festhalten.

Verwendung des Beispiel-Projektes:

  • LicenseGenerator ausführen, öffentlichen und privaten Schlüssel erstellen
  • Privaten Schlüssel auf der linken Seite laden
  • Lizenz license.xml erstellen, ggf. mit MAC-Adresse
  • license.xml speichern in den Ordner der TestApplication

Download: LicensingTools

Share

Keine Kommentare

Suchen und Ersetzen mittels Regular Expressions

Ich veränderte die Art und Weise, wie ich meine Settings anspreche – und zwar implementierte ich die Library Nini.

Dazu musste ich in verschiedenen Dateien einige Zeilen ändern, womit sich die Gelegenheit bot mal das Suchen und Ersetzen mittels Regular Expressions auszuprobieren.

Properties.Connection.Default.ComPort
Properties.Connection.Default.BaudRate.ToString()

… sollte werden zu:

configCommunication.Get( "ComPort" )
configCommunication.Get( "BaudRate" )

Suchen nach diesem Pattern: Properties.Connection.Default.{[:c]+}[.ToString\(\)]*
Ersetzen mit diesem Pattern: configCommunication.Get( \1″ )

Die geschweiften Klammern gruppieren, um dann später bei der Ersetzung diese Gruppe mit \1 anzusprechen. :c steht für ein alphanumierischen Zeichen, welches durch die Klammern herum und das folgenden Plus in beliebiger Anzahl größer 1 vorkommen darf. Der letzte Ausdruck in eckigen Klammern wird durch den Stern optional – das Pattern trifft zu, egal ob er vorhanden ist oder nicht.

Share

Keine Kommentare

Individueller MenuStrip – Schriftfarbe anpassen

In einer Windows-Form habe ich einen MenuStrip mit einem blauen Hintergrund. Die Schriftfarbe (ForeColor) wurde daher von System.ControlText zu System.Control geändert, da es sonst schlecht lesbar wäre. Allerdings ergab sich nun das Problem, dass beim Aktivieren des Menüs der Hover-Effekt für einen hellen Hintergrund sorgt und der Text kaum noch lesbar ist.

Der Hover-Effekt selbst sollte so bleiben, aber die Schriftfarbe entsprechend des Hintergrundes bzw. Status (aktiv/inaktiv) angepasst werden. Möglich macht es das Event ToolStripMenuItem.Paint. Damit man aber sowohl das direkte Hovern als auch das aufgeklappte DropDown-Menüs erwischt, müssen offenbar die Eigenschaften Selected und Pressed abgefragt werden.


myItem.Paint += new PaintEventHandler( myItem_Paint );

void myItem_Paint( object sender, PaintEventArgs e )
{
	var item = (ToolStripMenuItem) sender;

	if (item.Selected || item.Pressed)
	{
		item.ForeColor = SystemColors.ControlText;
	}
	else
	{
		item.ForeColor = SystemColors.Control;
	}
}

Share

1 Kommentar

Visual Studio: Warnung über fehlende XML Kommentare unterdrücken

Ich habe angefangen meinen Quelltext mit den XML-Kommentaren zu formatieren. Dazu gibt man einfach /// ein und Visual Studio erzeugt daraus sowas wie:


/// <summary>
///
/// </summary>
/// <param name="addr"></param>
/// <param name="value"></param>
public static void WriteData(int addr, short value)
{
}

Visual Studio meckert dann aber später über jede Methode, die noch nicht kommentiert wurde. Dafür gibt es in den Projekteinstellungen Warnungen unterdrücken (Reiter Erstellen). Dort wird hinzugefügt: 1591,1592,1573,1571,1570,1572

Share

Keine Kommentare

C# Properties.Settings portabel machen

… oder: “Warum werden meine Änderungen in meiner AppName.exe.config nicht berücksichtigt?!”

Es hat eine Weile gedauert, bis ich die Funktionsweise kapiert hatte.

Folgendes musste ich erst herausfinden:

  • Bereich Benutzer sind zur Laufzeit änderbar, Anwendung nicht und werden erst durch einen Neustart der Anwendung aktiv
  • Das Ändern der Settings in der XML Datei AppName.exe.config klappt nicht, da Benutzer-Einstellungen für jeden Nutzer seperat abgespeichert werden unter einem kryptischem Pfad wie z.B:
    C:\Users\flowschi\AppData\Local\[Herstellername]\[Anwendungsname mit kryptischem String] – genauer Pfad abhängig von Faktoren wie Roaming true/false und Bereich der Settings (Anwendung/Benutzer)
  • Löscht man diese user.config, wird sie beim nächsten Programmstart erneut mit den Standardwerten aus der AppName.exe.config aus dem Programmverzeichnis erstellt
  • An den Pfad kommt man über die .NET Anwendung mit (Verweis zu System.Configuration nötig):
// Machine Configuration Path
string path1 = ConfigurationManager.OpenMachineConfiguration().FilePath;
// Application Configuration Path
string path2 = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None).FilePath;
// User Configuration Path
string path3 = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;

Das alles mag teilweise recht praktisch sein, aber ich suchte nach einer Möglichkeit das ganze sehr einfach zu halten und mit einer einzigen Konfigurationsdatei auszukommen. Die sollte auch schön bei der Anwendung liegen, so dass ein Benutzer ggf. per Hand leicht was an der Konfiguration ändern kann und vor allem die ganze Anwendung mitsamt Einstellungen portabel bleibt.

Fündig geworden bin ich bei Mike Nathan auf Geek Republic. Sein C# Portable Settings Provider macht genau das. Da ich meine Settings nicht unter Properties gespeichert habe sondern in einem eigenen Ordner, war noch etwas Anpassung nötig:

// Application Specific Node
//string APPNODE = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name + ".Properties.Settings";
string APPNODE = "SYMAP_Daten_Logger.Settings";

Weiß jemand wie ich den Standard-Namespace meines Projektes herausbekomme ohne ihn statisch festzulegen?

Eines sollte einem aber noch bewusst sein: Baut man doch einen Installer und installiert die Software unter C:\Program Files\ …, so wird auch versucht dort die Konfigurations-Datei zu speichern. Das geht aber schief, weil hierfür die nötigen Rechte fehlen.

Share

, ,

7 Kommentare