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.
Ü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:
[sourcecode language=“csharp“]
var rsa = new RSACryptoServiceProvider( 2048);
File.WriteAllText( "publicKey.xml", rsa.ToXmlString( false ) );
File.WriteAllText( "privateKey.xml" rsa.ToXmlString( true ) );
[/sourcecode]
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:
[sourcecode language=“csharp“]
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 );
[/sourcecode]
Die nun erzeugte license.xml:
[sourcecode language=“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>
[/sourcecode]
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:
[sourcecode language=“csharp“]
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;
}
[/sourcecode]
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