PKI 令牌(Gemalto .NET 卡)许可 .NET 组件的详细解析
本文演示了如何编写一个 .NET 许可提供程序,该程序从 Gemalto .NET Card V2 获取许可。
引言
在上一篇文章中,我向您展示了如何使用 Gemalto .NET card V2 中的 RSA 证书进行挑战-响应认证。在本文中,我将向您展示如何将 .NET 许可与该令牌生成某些挑战字节的 RSA 签名的能力结合起来。互联网上已经有很多文章介绍了如何使用 .NET 许可机制,所以我不会详细介绍这部分,而是专注于使用智能卡来实现强大的许可。这种技术是大多数基于令牌的许可产品所使用的。得益于这款 Gemalto 产品,您现在可以以非常合理的价格轻松编写自己的产品,与过去相比,这要容易得多。
背景
请参阅我关于 Gemalto 智能卡的上一篇文章,以理解我将要描述的内容,因为我们将大量使用它。
扩展身份验证服务以支持 .NET 组件许可
在上一篇文章中,我在卡中编写了一段简单的代码,可以使用 RSA 私钥对挑战数据进行签名。此签名是使用 PKI 令牌实现许可机制的基础。您想要许可的应用程序或组件将生成随机数据,然后将其发送到智能卡,智能卡将使用其私钥对其进行签名。然后,卡服务返回可以加密的签名字节,许可提供程序将验证签名并授权创建该组件。
我将添加到签名机制中的是令牌许可的组件名称列表。请记住,您放置在此令牌中的所有内容都像一个虚拟保险箱,即使是最强的攻击也无法破解。
令牌中运行的代码和数据是用户无法访问的,不像硬盘或内存那样可以尝试攻击。访问代码的协议类似于银行智能卡中使用的协议。数据或命令可以用 PIN 码保护,您有三次机会猜对其值,该值可以是任何 ASCII 字符串。三次尝试后,代码将被锁定,卡片将被虚拟销毁。您可以尝试复杂的硬件攻击,但同样,PKI 卡具有强大的电子对抗措施,可以阻止对芯片中数据的访问。我说的不是软件,而是一种硬件自毁机制!智能卡没有后门,一旦管理员 PIN 被锁定……您可以扔掉卡片,它就死了!
在 Gemalto 卡上,管理员 PIN 码是 20 字节,用于加密身份验证……您有 5 次机会猜对它的值,祝您好运!
现在我已经介绍了我们将要使用的硬件保险箱,让我们在卡中设计一个简单的组件许可存储库。这是数据中的一个简单列表,将存储我们要在此令牌中许可的组件的类名。
如果您想控制实例数量、许可有效期,或者您想到的任何内容,这个简单的机制都可以无限扩展!
SmartCard LicensingService
在我上一篇文章中,我演示了如何使用 .NET 卡对挑战数据进行签名,我还描述了一个简单的登录/密码存储库,这意味着我们需要的积木已经具备了。
现在可以使用 ItemRepository
类来实现卡中所需的任何存储库。 .NET 卡框架不支持泛型,因此它使用 object
作为基本元素。基于此类,我实现了一个简单的 LicenseRepository
类来存储许可组件的信息。
/// <summary>
/// This class implements a simple license repository
/// </summary>
class LicenseRepository : ItemRepository
{
private static LicenseRepository _instance = null;
public static LicenseRepository Instance
{
get
{
if (_instance == null)
{
_instance = new LicenseRepository();
}
return _instance;
}
}
/// <summary>
/// There must be only one LicenseRepository
/// </summary>
private LicenseRepository()
{
}
/// <summary>
/// Adds a LicenseInfo to the repository
/// </summary>
/// <param name="name">License name</param>
/// <param name="licenseInfo">LicenseInfo object</param>
/// <returns>true if added, false otherwise</returns>
public bool AddLicense(string name, LicenseInfo licenseInfo)
{
return AddItem(name, licenseInfo);
}
/// <summary>
/// Remove a license from the repository
/// </summary>
/// <param name="name">License name</param>
/// <returns>true if removed, false otherwise</returns>
public bool RemoveLicense(string name)
{
bool removed = false;
int index = FindItemIndex(name);
if (index != NOT_FOUND)
{
RemoveItem(index);
removed = true;
}
return removed;
}
/// <summary>
/// Gets a LicenseInfo by its name
/// </summary>
/// <param name="name">License name</param>
/// <returns>LicenseInfo object, null if not found</returns>
public LicenseInfo GetLicense(string name)
{
LicenseInfo licenseInfo = null;
int index = FindItemIndex(name);
if (index != NOT_FOUND)
{
licenseInfo = GetItem(index) as LicenseInfo;
}
return licenseInfo;
}
/// <summary>
/// Gets the names of the license installed in this token
/// </summary>
/// <returns></returns>
public string[] GetLicenseNameList()
{
return GetItemAlias();
}
}
此存储库存储 LicenseInfo
实例。此类可以根据需要进行扩展,以便包含更多有用的信息来许可组件。
/// <summary>
/// Simple LicenseInfo. This class can be completed
/// with more information about the license for the
/// given component
/// </summary>
class LicenseInfo
{
private string componentClassName;
public LicenseInfo(string componentClassName)
{
this.componentClassName = componentClassName;
}
/// <summary>
/// Component class name
/// </summary>
public string ComponentClassName
{
get
{
return componentClassName;
}
}
在卡片生成的 AuthenticationService
中,可以使用证书和一种方法来获取公钥。在这种情况下,不能使用此方法,因为在所有可用于许可组件的令牌中,证书都必须相同。在此演示中,证书是静态的,由卡片本身生成,但包含在代码中。这意味着服务开发人员可以获取其值并虚拟地破解许可机制。
在接下来的文章中,我将演示一种通过主卡生成私钥并初始化许可卡的协议,这样即使是应用程序开发人员也无法获取证书私钥。对于像银行这样的安全环境,这种机制是强制性的。它也可以应用于许可应用程序或组件,因为您的工作就是金钱!
LicensingService 提供以下接口,可与卡片远程处理一起使用。
public class LicensingService : MarshalByRefObject
{
#region Public PIN methods
/// <summary>
/// Verify the PIN
/// </summary>
/// <param name="pinValue">PIN value</param>
public void VerifyPIN(string pinValue);
/// <summary>
/// Invalidate PIN if it is already verified
/// </summary>
public void InvalidatePIN();
public bool IsPINBlocked;
public bool IsPINVerified;
public int TriesRemainingForPIN;
#endregion
#region Public interface for the service
/// <summary>
/// Install a component license
/// </summary>
/// <param name="componentName">Component name to license</param>
/// <param name="componentClassName">Component type name,
/// including name spaces</param>
/// <returns>true if installed, false otherwise</returns>
public bool InstallLicense(string componentName, string componentClassName);
/// <summary>
/// Gets the list of installed licenses
/// </summary>
/// <returns>Array of license name</returns>
public string[] GetInstalledLicenseNames();
/// <summary>
/// Gets the license info, Compoenent class name in our case
/// </summary>
/// <param name="name">License name</param>
/// <returns>Component class name</returns>
public string GetLicenseInfo(string name);
/// <summary>
/// Get the signature for a given component.
/// This is a simple implemetation.
///
/// Unfortunately the .NET remoting version of the .NET
/// card cannot return a class instance. So if more info
/// would have to be returned, you need to use [out] parameters
///
/// In this simple example we don't need
/// to return anything except the signature
/// </summary>
/// <param name="componentName">Component name</param>
/// <param name="encrChallenge">Encrypted challenge</param>
/// <returns>Encrypted signature for that component.
/// Null if the component is not registered</returns>
public byte[] GetLicenseSignature(string componentName, byte[] encrChallenge);
#endregion
}
一个使用 LicensingService 的 LicenseProvider
现在我们有了一个可用于许可组件的令牌,我们需要将其连接起来并与 .NET 的许可机制一起使用。
.NET Framework 提供了一种机制,可以通过继承 Component
来装饰类,从而半自动地调用许可验证。我的目标不是详细描述此机制,而只是使用它,因此您可以从这篇文章中获取有关 .NET 许可的更多信息。
您需要实现 System.ComponentModel
命名空间中的两个类:License
和 LicenseProvider
。
在每个类中,必须实现一些方法,但您可以创建自己的方法来支持更复杂的许可功能。
对于 License
类,只需要实现一个方法和一个属性。下面是 ComponentLicense
的一个基本实现。
/// <summary>
/// Simple license implementation for Component
/// </summary>
public class ComponentLicense : License
{
private string componentType;
/// <summary>
/// Creates an instance
/// </summary>
/// <param name="componentType">Component full
/// type name (including namespace)</param>
public ComponentLicense(string componentType)
{
this.componentType = componentType;
}
public override void Dispose()
{
}
/// <summary>
/// Gets the license key, in our case the full
/// type name of the license component
/// </summary>
public override string LicenseKey
{ get { return componentType; }
}
}
必须实现的第二个类是 LicenseProvider
本身。下面给出了 SmartcardLicenseProvider
实现的一个片段。只需要实现一个方法,该方法负责验证并提供许可本身。
/// <summary>
/// Implements LicenseProvider for the Smartcard
/// </summary>
public class SmartcardLicenseProvider : LicenseProvider
{
public SmartcardLicenseProvider()
{
SetupSecrets();
}
/// <summary>
/// Implements the GetLicense method
/// </summary>
/// <param name="context"></param>
/// <param name="type"></param>
/// <param name="instance"></param>
/// <param name="allowExceptions"></param>
/// <returns>A ComponentLicense instance</returns>
/// <exception cref="LicenseException">When the license
/// is not verified</exception>
public override License GetLicense(LicenseContext context,
Type type, object instance, bool allowExceptions)
{
License license = GetComponentLicense(type);
if (license == null)
{
throw new LicenseException(type);
} return license;
}
#region Private methods
private void SetupSecrets()
{
aesAlgo = Rijndael.Create();
aesAlgo.Key = AESKey;
aesAlgo.IV = AESIv;
rsaAlgo.ImportParameters(RSATokenPublicKey);
}
private ComponentLicense GetComponentLicense(Type componentType)
{
ComponentLicense license = null;
using (LicensingServer licensingServer = new LicensingServer())
{
byte[] challenge = GetChallenge();
byte[] encrChallenge = AESEncrypt(challenge);
byte[] encrSignature =
licensingServer.GetSignatureForLicense(
componentType.Name, encrChallenge);
if (encrSignature != null)
{
byte[] signature = AESDecrypt(encrSignature);
if (VerifySignature(challenge, signature))
{
license = new ComponentLicense(componentType.FullName);
}}
}
return license;
}
}
关键方法是 GetComponentLicense()
方法,它为给定的组件进行令牌身份验证。在更复杂的场景中,此方法将从令牌中收集有关组件许可的信息。在此示例中,它仅请求对给定组件/许可名称进行身份验证。
一旦实现了这两个类,组件本身的许可就非常简单了。
/// <summary>
/// This component is licensed in the Token
/// </summary>
[LicenseProvider(typeof(SmartcardLicenseProvider))]
public class LicensedComponent : Component
{
public LicensedComponent()
{
ComponentLicense license =
LicenseManager.Validate(this.GetType(), this) as ComponentLicense;
}
}
您只需要用 LicenseProvider
属性装饰您想要许可的类,并在构造函数中调用 LicenseManager
的静态方法 Validate()
。它返回一个 License
,您可以将其转换为自己的类型。
演示应用程序
我创建的用于演示的令牌只有一个用于 LicensedComponent
组件的许可。演示应用程序尝试创建 LicensedComponent
的实例和 UnlicensedComponent
的实例。
class Program
{
static void Main(string[] args)
{
try
{
LicensedComponent component1 = new LicensedComponent();
Console.WriteLine("LicensedComponent created!");
UnlicensedComponent component2 = new UnlicensedComponent();
Console.WriteLine("LicensedComponent created!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
此代码产生以下结果
可以创建 LicensedComponent
的实例,但 UnlicensedComponent
的构造函数会抛出 LicenseException
。
简单的许可管理器应用程序
LicensingService 有两个方面:一个由许可提供程序调用的方法,用于验证许可,而无需 PIN 即可进行验证;以及一组用于在令牌中管理许可的方法。这些方法受 PIN 保护,PIN 当然对组件用户不可用。
关注点
密码学是杰出数学家的领域,但幸运的是,.NET 等框架为我们提供了这些复杂算法的实现,像我这样(不是数学家!)的人可以使用它们来构建事物。对于 IT 工程师或开发人员来说,像密码学这样的理论的重要性在于您可以使用它来解决日常工作中的问题。
RSA 公钥就是一个很好的例子。它被 IT 行业中的许多安全协议使用,例如,WCF 中使用证书启用安全性的 WS-* 标准,使用智能卡进行 Windows 登录,或者简单地对您喜欢的 .NET 程序集进行签名。您可以在互联网上找到大量关于 RSA PKI(公钥基础设施)的文献。
使用 PKI 令牌是一种非常强大的许可方法,但不幸的是,该过程中存在一个主要弱点,那就是 .NET 平台本身。基本上,这就像为您的木屋使用银行保险库门!没有人能够打破或打开门,但只需要用一台好的链锯从墙壁上开个口……对于 .NET 应用程序,链锯就像 Reflector 这样的反编译器。几年前,我在 CardSpace 的 CTP 上工作时,曾用它来修改和重新编译一些 Microsoft 工具。
我对此没有灵丹妙药,但您可以使其非常难以破解
- 对您要许可的组件所包含的库进行签名
- 混淆并签名执行许可验证的 DLL
- 系统地为您的公共接口使用私有实现并混淆您的 DLL
尽管混淆不会隐藏逻辑和对框架方法的调用,但当您只有其签名时,要重建许多私有方法相互调用的复杂结构将非常困难。
在我们关于门和墙的比喻中,您分发的代码就是墙。如果有人可以取出所有砖块并重新砌墙而没有门……强大的许可将毫无意义!另一方面,如果他们在重建墙壁时,有些砖块无法放在正确的位置,导致墙壁倒塌,那么许可就可能有用。