четверг, 1 сентября 2011 г.

Работа с HASP-ключами

Выжимка из класса для работы с hasp-ключами под OC Windows старше 2000 версии. HASP (англ. Hardware Against Software Piracy) — это мультиплатформенная аппаратно-программная система защиты программ и данных от нелегального использования и несанкционированного распространения, разработанная компанией Aladdin Knowledge Systems Ltd.
Электронные ключи HASP выпускаются c различными интерфейсами, в статье речь идет только о USB-брелках HASP HL.
Технологии HASP HL и HASP SRM на данный момент не взломаны, надежность защиты целиком и полностью зависит от человека, который будет внедрять защиту в свое приложение, момент "внедрения защиты" в статье опущен, а сама статья касается непосредственной манипуляцией с ключами, как примитивным устройством (open, close, read, write, etc) + распознавание вставки и извлечения ключа из USB-порта.

Сборка hasp_net_windows.dll, которая шла вместе с драйверами не поддерживает установку двух и более ключей одной серии на один компьютер одновременно. Поэтому класс по работе с ключами логично объявить статическим и первый воткнутым и опознанный ключ считать единственным в системе.
После добавление в Reference и прописывания namespace Aladdin.HASP можно начинать работать с ключами.
Первое что необходимо сделать, определить поля:
private static HaspFeature feature = HaspFeature.FromProgNum(0);//номер функции по умолчанию, поле касается защиты софта и не вписывается в тему статьи
private static HaspStatus status;// статус выполненной операции
private static int id = 0;// хранение идентификатора личного ключа
Для регистрации событий вставки и извлечения ключа введем перечисление last_operation с двумя состояниями
public enum last_operation
        {
            INSERT,// ключ вставлен
            REMOVE // ключ извлечен
        }
и поле, характеризующее текущее состояние ключа:
private static last_operation lastoperation;
Ещё потребуются два поля - экземпляров Hasp hasp - сам ключ, и HaspFile hasp_file - его файловой системы
Для проверки открыта или нет сессия по работе с ключом, объявим свойство-геттер:
private static bool flg_isopen;

        public static bool isopen
        {
            get { return flg_isopen; }
        }
private static bool flg_isopen;
Два события на вставку и извлечения ключа:
public static event EventHandler KeyArrived;
public static event EventHandler KeyRemoved;
И два приватных слушателя usb-портов:
private static ManagementEventWatcher watcher1;
private static ManagementEventWatcher watcher2;
Вот и все поля.

Перейдем к методам. Первое что надо сделать,- открыть сессию по работе с ключами.
public static void open()
if (flg_isopen)
    return;// открывать два раза не хорошо, можно на открытии следует вызвать метод Close, если задача позволяет
// инициализация ключа
feature.SetOptions(FeatureOptions.NotRemote, FeatureOptions.Default);// для примера: только локальные ключи
hasp = new Hasp(feature);
Далее необходимо подписаться на сообщения системы о подключении/извлечении ключа, делается это через WQL-технологию из namespace System.Management. Для регистрации отключения:
WqlEventQuery queryRemove = new WqlEventQuery("Select * " +
                "FROM __InstanceDeletionEvent within 1 " +
                "WHERE TargetInstance ISA 'Win32_PnPEntity' "+
                "AND TargetInstance.Description='Aladdin HASP HL Key' "+
                "AND TargetInstance.ClassGuid='{36FC9E60-C465-11CF-8056-444553540000}'");
            watcher1 = new ManagementEventWatcher(queryRemove);
            watcher1.EventArrived += new EventArrivedEventHandler(watcher_remove);
            watcher1.Start();
Важными являются строчки с дескриптором и гуидом класса. По аналогии можно отслеживать подключение принтеров, флешек и тд и тп. Технология WQL очень мощная и гибкая.
Для регистрации подключения к usb-порту точно так же, но "FROM __InstanceCreationEvent within 1 "
flg_isopen = true;  // далее взводится флаг об успешном открытии сессии
if(detecting_key()) // и проверяется, вставлен ли уже ключ  
     KeyArrived(null, EventArgs.Empty);
На этом метод Open заканчивается. В нем использованы обработчики событий watcher_remove и watcher_insert и приватный метод detecting_key() возвращающий true, если ключ уже вставлен на момент вызова Open. Опишем его:
public static bool detecting_key()
        {
            if (Detected("Aladdin USB Key", "Aladdin Knowledge Systems", "VID_0529&PID_0001"))
            {
                if ((id=KeyId) != 0)
                {
                    lastoperation = last_operation.INSERT;
                    return true;
                }
            }
            else
                lastoperation = last_operation.REMOVE;
            return false;
        }
Тут происходит вызов метода обнаружения оборудование в дереве устройств машины с помощью технологии WMI и обращение к свойству KeyId, для начала опишем сам метод Detected: первый аргумент,- дескриптор оборудования, дальше производитель и последний аргумент это PID&VID, всё в виде строк:
private static bool Detected(string Description, string Manufacturer, string VersionIDandVendorID)
        {
            bool rez = false;
            ManagementScope scope = new ManagementScope("root\\CIMV2");
            scope.Options.EnablePrivileges = true;
            ObjectQuery query = new ObjectQuery("Select * From Win32_USBControllerDevice"/*Win32_USBControlerDevice*/);
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
            StringBuilder Response = new StringBuilder();
            foreach (ManagementObject mgmtObj in searcher.Get())
            {
                 string[] arrDeviceID;
                ManagementObjectSearcher mySearcher =
                    new ManagementObjectSearcher("Select * From Win32_PnPEntity "
                    + "Where DeviceID =" + mgmtObj["Dependent"].ToString().Replace("\"", "'").Split('=')[1]/*Win32_PnPEntity*/);
                foreach (ManagementObject mobj in mySearcher.Get())
                {
                    arrDeviceID = mobj["DeviceID"].ToString().Split('\\');
                    if (mobj["Manufacturer"] != null)
                    {
                        if (
                            mobj["Description"].ToString().Equals(Description) &&
                            mobj["Manufacturer"].ToString().Equals(Manufacturer) &&
                            arrDeviceID[1].Equals(VersionIDandVendorID) 
                            )
                        {
                            rez = true;
                            break;
                        }
                    }
                }
            }
            return
                rez;
        }
Значения аргументов берутся из диспетчера оборудования. Метод универсальный, позволяет проверять большинство доступного оборудования в системе.
Перед описанием свойства KeyId, которое возвращает идентификатор ключа, необходимо описать ещё два приватных метода appl_stat() и appl_stop():
private static bool appl_stat()
{
	if (hasp.IsLoggedIn()) 
		return true;// если сессия уже открыта
	status = hasp.Login(ASCIIEncoding.ASCII.GetBytes(vendor_code));
	hasp_file = hasp.GetFile(HaspFiles.Main);
	// проверка верности открытии сессии
	if (HaspStatus.StatusOk == status)
		return true;
	return	false;
}
private static bool appl_stop()
{
	if(!hasp.IsLoggedIn() 
		return true;// если сессия уже закрыта
	status = hasp.Logout();
	// проверка верности закрытия сессии
	if (HaspStatus.StatusOk == status)
		return true;
	return false;
}
Тут важным является вход на ключ, который осуществяется вызовом API ALADDIN'а, где vendor_code - мастер-код ключа разработчика. Логика такая, вызывается метод старта работы с ключем, выполняется операция, потом вызывается метод стоп работы с ключом, это описано в документации для разработчика, поставляемой вместе с ключами.
Вернемся к свойству KeyId:
public static Int32 KeyId
{
	get {
		if (appl_stat())
		{
			string s = null;
			hasp.GetSessionInfo(Hasp.KeyInfo, ref s);
			XmlDataDocument x = new XmlDataDocument();
			x.LoadXml(s);
			XmlNode l = x.DocumentElement.GetElementsByTagName("haspid").Item(0).FirstChild;
			Int32 i
			Int32.TryParse(l.Value, out i);
			appl_stop();
			return i;
		}
		else return 0;
	     }
}
XML-документ всегда содержит информацию только об одном ключе,- таково ограничение HASP HL, поэтому Item(0).FirstChild
По аналогии можно определить свойство Size, отличие только в запросе элемента:
XmlNode l = x.DocumentElement.GetElementsByTagName("size").Item(0).FirstChild;
Дополним класс методами, которые обрабатывают вставку/извлечение ключа:
private static void watcher_remove(object sender, EventArrivedEventArgs e)
{
	if (lastoperation != last_operation.REMOVE && id != KeyId)
	{// вытаскивание 'нашего' ключа, т.к. KeyId вернет ноль если он был вытащен
		lastoperation = last_operation.REMOVE;
		id = 0;
		KeyRemoved(null, EventArgs.Empty);// генерация события в приложение
	}
}
private static void watcher_insert(object sender, EventArrivedEventArgs e)
{
	if (lastoperation != last_operation.INSERT && id == 0)
	{// если идентификатор ноль - ключа не было, отсечка всех остальных ключей
		if ((id = KeyId) != 0)
                {// идентификатор получен
			lastoperation = last_operation.INSERT;
			KeyArrived(null, EventArgs.Empty);
                }
	}
}
Со вставкой/извлечением и инициализацией работы ключа всё, теперь необходимо описать метод Close, для корректного завершения работы с ключом:
public static void close()
{
        if (hasp_file != null)
		hasp_file.Dispose();// уничтожение файловой системы ключа
	hasp.Dispose();// уничтожение самого себя
	// уничтожение слушателей вставки/извлечения ключа
        watcher1.Stop();
        watcher2.Stop();
	// сброс флагов и текущего идетификатора ключа
        id = 0;
        flg_isopen = false;
}
Методы записи/чтения/обнуления ключа зависят от конкретной информации, записываемой на ключ и политики защиты, структурно выглядят примерно так:
public static bool write()
{// запись данных на ключ
	if (appl_stat() == true)// открытие сессии работы с ключем
	{
		hasp_file.FilePos = 0;
		// запись семерки
		status = hasp_file.Write(BitConverter.GetBytes(7), 0, sizeof(int));
		if (HaspStatus.StatusOk != status)
		{
			return false;// запись не прошла
		}
		appl_stop();// закрытие сессии
		return true;
		}
		return false;
	}
}
Последний важный момент, события KeyArrived и KeyRemoved генерируются не в UI-потоке приложения, поэтому необходимо вызвать инвок обработчика этих событий, например так:
private void hasp_Key_KeyRemoved(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
                BeginInvoke((EventHandler)hasp_Key_KeyRemoved, null);
            else
            {
                // чего-то сделать при извлечении ключа
            } 
        }

Комментариев нет:

Отправить комментарий