При разработке механизмов контроля доступа и защиты критически важных операций в 1С часто возникает задача: разрешать выполнение определенных действий (проведение документов, закрытие периода, выгрузка данных) только в том случае, если к компьютеру пользователя подключен физический носитель — флешка или токен с закрытым ключом.
Рассмотрим, почему нельзя прочитать контейнер закрытого ключа напрямую как обычный файл, как устроен механизм криптографии в платформе 1С и какими способами можно реализовать проверку наличия ключевого носителя.
Контейнер закрытого ключа (содержащий файлы header.key, masks.key, primary.key и др.) формируется специализированным криптопровайдером (например, КриптоПро CSP). Согласно стандартам безопасности, прямого программного доступа к структуре закрытого ключа нет ни у прикладного кода 1С, ни у сторонних утилит.
Проанализируем основные архитектурные ограничения:
МенеджерКриптографии не работает с файловой системой контейнера. Он обращается к системным криптографическим интерфейсам операционной системы (CryptoAPI, CNG), которые передают запрос установленному криптопровайдеру.Наиболее надежный и платформенный способ убедиться в наличии флешки с закрытым ключом — попытаться выполнить операцию подписания произвольного блока данных с помощью объекта МенеджерКриптографии.
Разберем по шагам порядок настройки и реализации:
.cer) в хранилище «Личные» текущего пользователя операционной системы, связав его с контейнером на флешке (без копирования закрытого ключа в реестр компьютера).Попытка ... Исключение.Посмотрим на пример реализации программной проверки:
&НаКлиенте
Функция ПроверитьНаличиеКлючевогоНосителя(ОтпечатокСертификата)
РезультатПроверки = Ложь;
// Инициализируем менеджер криптографии
Менеджер = Новый МенеджерКриптографии();
// Получаем сертификаты из хранилища
Хранилище = Менеджер.ПолучитьХранилищеСертификатов(ТипХранилищаСертификатовКриптографии.ПерсональныеСертификаты);
МассивСертификатов = Хранилище.НайтиПоОтпечатку(ОтпечатокСертификата);
Если МассивСертификатов.Количество() = 0 Тогда
ВызватьИсключение "Сертификат для проверки доступа не найден в персональном хранилище.";
КонецЕсли;
Сертификат = МассивСертификатов[0];
// Формируем тестовые данные для проверки подписи
ТестовыеДанные = ПолучитьДвоичныеДанныеИзСтроки("ПроверкаДоступа_" + Строка(ТекущаяДатаСеанса()));
Попытка
// Пытаемся подписать данные закрытым ключом
Подпись = Менеджер.Подписать(ТестовыеДанные, Сертификат);
РезультатПроверки = Истина;
Исключение
// Если флешка не вставлена или контейнер недоступен
ИнформацияОбОшибке = ОписаниеОшибки();
Сообщить("Ключевой носитель не обнаружен или доступ к нему заблокирован: " + ИнформацияОбОшибке);
РезультатПроверки = Ложь;
КонецПопытки;
Возврат РезультатПроверки;
КонецФункции
Если требуется более глубокий контроль контекста выполнения и детальный анализ ошибок криптографической службы, можно использовать COM-компоненты криптопровайдера, такие как CAdESCOM.CPSigner и CAdESCOM.CadesSignedData.
Рассмотрим пример проверки доступности контейнера через CAdESCOM:
&НаКлиенте
Функция ПроверитьТокенЧерезCOM(ОтпечатокСертификата)
Попытка
ХранилищеCOM = Новый COMОбъект("CAdESCOM.Store");
// Открываем личное хранилище текущего пользователя
ХранилищеCOM.Open(2, "My", 0); // 2 - CADESCOM_CURRENT_USER_STORE, 0 - CAPICOM_STORE_OPEN_READ_ONLY
НайденныеСертификаты = ХранилищеCOM.Certificates.Find(0, ОтпечатокСертификата); // 0 - CAPICOM_CERTIFICATE_FIND_SHA1_HASH
Если НайденныеСертификаты.Count = 0 Тогда
ХранилищеCOM.Close();
Возврат Ложь;
КонецЕсли;
СертификатCOM = НайденныеСертификаты.Item(1);
Подписант = Новый COMОбъект("CAdESCOM.CPSigner");
Подписант.Certificate = СертификатCOM;
ПодписанныеДанные = Новый COMОбъект("CAdESCOM.CadesSignedData");
ПодписанныеДанные.Content = "ПроверочнаяСтрока";
// Попытка создания отсоединенной подписи
Подпись = ПодписанныеДанные.SignCades(Подписант, 1, Истина); // 1 - CADES_BES
ХранилищеCOM.Close();
Возврат Истина;
Исключение
// Ошибка возникает, если контейнер на флешке отсутствует
Возврат Ложь;
КонецПопытки;
КонецФункции
В ситуациях, когда криптографическая стойкость не требуется, а нужно лишь убедиться, что в USB-порт вставлен конкретный съемный накопитель, можно прочитать аппаратный серийный номер устройства через WMI.
Посмотрим на пример функции, перечисляющей подключенные дисковые накопители:
&НаКлиенте
Функция НайтиПодключенныйUSBПоСерийномуНомеру(ИскомыйСерийныйНомер)
Результат = Ложь;
Попытка
Локатор = Новый COMОбъект("WbemScripting.SWbemLocator");
СервисWMI = Локатор.ConnectServer(".", "root\cimv2");
// Запрашиваем только диски с интерфейсом USB
КоллекцияДисков = СервисWMI.ExecQuery("SELECT DeviceID, InterfaceType, PNPDeviceID, SerialNumber FROM Win32_DiskDrive WHERE InterfaceType='USB'");
Для Каждого Диск Из КоллекцияДисков Цикл
// Проверяем серийный номер или PNPDeviceID устройства
Если Найти(ВРег(Строка(Диск.PNPDeviceID)), ВРег(ИскомыйСерийныйНомер)) > 0
Или Найти(ВРег(Строка(Диск.SerialNumber)), ВРег(ИскомыйСерийныйНомер)) > 0 Тогда
Результат = Истина;
Прервать;
КонецЕсли;
КонецЦикла;
Исключение
Сообщить("Ошибка при обращении к WMI: " + ОписаниеОшибки());
КонецПопытки;
Возврат Результат;
КонецФункции
Важное ограничение: Метод WMI проверяет только физические параметры носителя и не защищает от клонирования или подмены данных. Для обеспечения юридической значимости и защиты от копирования используйте штатные криптографические методы формирования электронной подписи.