пятница, 25 сентября 2015 г.

Пример работы с SDK "Kalkan"

Так как меня не покидала уверенность в том что пример такой работы нужен остальным программистам которые используют  SDK "Kalkan",  я решил потратить часть своего времени на его создание. В дальнейшим по тексту вместо слова "пример" я буду использовать название проекта "Ldw-Example-Kalkan"  чтобы избежать любых неоднозначностей. Собственно сам  исходный  код можно скачать здесь.
Кода я писал "Ldw-Example-Kalkan" то держал в голове такие цели:
1) Приложение после получения его с гит-хранилища должно запускаться сразу, без какой либо дополнительной конфигурации.
2) Клиент запустив открыв "Ldw-Example-Kalkan" в браузере должен иметь возможность подписать произвольные данные с помощью апплета от НУЦ РК  и отправить  их на сервер для проверки.
3) Должна быть реализована на сервере "правильная проверка" подписи данных которые пришли с браузера клиента. Что же это такое "правильная проверка"? Это соответствие между моей реализацией программного кода и той бумажкой с требованиями которые прислал НУЦ РК.
4) Помимо "правильной проверки" есть дополнительные требования к проверке на сервере. Это возможность одновременной работы с ЭЦП разных версий и проверка БИНа или ИННа в ЭЦП. 
В дальнейшем статья состоит из того как мне удалось или не удалось достичь эти цели.

Цель 1. Конфигурация.
Приложение после получения его с гит-хранилища должно запускаться сразу, без какой либо дополнительной конфигурации.
Не получилось. В проекте используются некоторые либы и сертификаты из SDK "Kalkan" . Я не могу их включить в "Ldw-Example-Kalkan" так как НУЦ РК ограничивает их распространение.
Так что немножко придется потрудиться, коллега. У Вас должен быть SDK "Kalkan" на компе, чтобы запустить "Ldw-Example-Kalkan" в работу.
Путь к SDK "Kalkan" обозначим как "\PATH_SDK_Kalkan". Путь к папке где находиться проект  "Ldw-Example-Kalkan" обозначим "\PATH_EXAMPLE_Kalkan";
Для конфигурации "Ldw-Example-Kalkan" необходимо выполнить нижеследующие шаги как
а)  В локальный мавен-репозиторий необходимо добавить jar из SDK "Kalkan" : knca_provider_jce_kalkan.jar,  commons-logging-1.1.1.jar, xmlsec-1.4.4.jar
Перейдите в папку "\PATH_SDK_Kalkan\Java(Kalkan)\libs\" и выполните команды:
mvn install:install-file -Dfile=knca_provider_jce_kalkan-16.jar -DgroupId=kz.gov.pki -DartifactId=knca_provider_jce_kalkan -Dversion=1.1 -Dpackaging=jar

mvn install:install-file -Dfile=commons-logging-1.1.1.jar -DgroupId=kz.gov.pki.libs -DartifactId=commons-logging -Dversion=1.1.1 -Dpackaging=jar

mvn install:install-file -Dfile=xmlsec-1.4.4.jar -DgroupId=kz.gov.pki.libs -DartifactId=xmlsec -Dversion=1.4.4 -Dpackaging=jar

б) Скопируете файлы  "new_NCA_GOST.cer" и "new_NCA_RSA.cer"  из папки "\PATH_SDK_Kalkan\Keys and Certs\Текущий НУЦ (old oids)\CA Root Certs\"  в папку "\PATH_EXAMPLE_Kalkan\src\main\resources\kz\pki\ca\nuc1\"
в) Скопируете файлы  "rca_gost.crt" и "rca_rsa.crt"  из папки "\PATH _SDK_Kalkan\ Keys and Certs\Нового НУЦ (new oids)\ca\КУЦ\"  в папку "\PATH_EXAMPLE_Kalkan\src\main\resources\kz\pki\ca\ kuc\"
г) Скопируете файлы  "knca_root.crt" и "knca_rsa.crt"  из папки "\PATH _SDK_Kalkan\ Keys and Certs\Нового НУЦ (new oids)\ ca\НУЦ 2.0\"  в папку "\PATH_EXAMPLE_Kalkan\src\main\resources\kz\pki\ca\ kuc\"
д) ВАЖНО!  Так как хост на котором я писал пример находиться за прокси, то в коде я мне пришлось учитывать при получении списка отозванных ЭЦП.  
Если ваш хост работает БЕЗ прокси, то в классе com.lastdaywaiting.example.kalkan.service.SecureManager  исправите  константу DEFAUL_USE_PROXY на false.
Если ваш хост работает ЗА прокси, то в классе com.lastdaywaiting.example.kalkan.service.SecureManager  проставьте  константам DEFAUL_PROXY_ADDRESS  и  DEFAUL_PROXY_PORT нужные значения.
Другой вариант изменения этих настроек, это через свойства объекта. Так например:
SecureManager secure = new SecureManager(bin, name, respCode);
secure.setUseProxy( false );
secure.setProxyAddress( "192.168.1.1" );
secure.setProxyPort( 11111 );

Цель 2. "Ldw-Example-Kalkan" в  браузере.
Приложение "Ldw-Example-Kalkan" стандартное веб-приложение на платформе Spring framework. В нем всего одна страница на которой пользователь может последовательно выполнить ниже следующие шаги:
а) Ввести сообщение которое нужно подписать.
б) Загрузить апплет от НУЦ РК. В данный момент не все браузеры это могут сделать. Я тестировал все в IE. Я знаю только косвенные способы определения того что апплет  не загрузился, чтобы не усложнять пример я их не стал вставлять в javascript.
в) Выбрать нужное ЭЦП в формате файла p12 . Другие виды источников ЭЦП в примере не доступны, также с целью упрощения кода. Если Вам интересно работа сними, то смотрите пример в SDK "Kalkan", там все другие источники представлены (Казтокен , Личное Удостоверение, EToken Java 72k, AK JaCarta) .
г) Подписать сообщение выбранным ЭЦП. Не забудьте ввести пароль.
д) Отправить сообщение на проверку. Сообщение в формате JSON, которое будет отправлено на сервер для проверки, Вы можете изменить на той же страничке для целей тестирования. 
Каждый раз когда вы нажимаете любую кнопку на странице в верху страницы показывается результат выполненного действия:  Успешно или описание ошибки.

Цель 3. Проверка подписи на сервере.
Проверка подписи на сервере должна соответствовать  тем требованиям которые предъявляет к нам НУЦ РК. Опять наверное покажусь занудой, но мне не понравилось то как составили свои требования. Слишком кратко и непонятно что делать на мой взгляд. Все требования которые хотел от меня НУЦ РК реализованны  в классе com.lastdaywaiting.example.kalkan.service.SecureManager . Для проверки того хорошая это подпись или нет, надо вызвать метод isGoodSignature.  А всю остальную работу с JCE и т.д. пользователю метода знать не обязательно. 
Если все-таки Вы хотите взглянуть на реализацию более подробнее, то ниже приведены требования от НУЦ РК и методы которые их реализуют. Некоторые требования я посчитал не достаточно важными чтобы их реализовывать.  Но лучше всего для анализа конечно смотреть сам исходный код.
1) "Замена криптопровайдера в ИС"
Самый ясный пункт из всех. На сервере приложений надо использовать их новый криптопровайдер kz.gov.pki.kalkan.jce.provider.KalkanProvider
В примере этот пункт реализован в секций static #1 класса SecureManager.
            2) "Проверка электронной цифровой подписи"
В этом пункте  я  посчитал что требуется  убедиться в том, что данные присланные на сервер  - подпись  от этих данных  и сертификат которым подписывали данные совпадают.
В примере этот пункт реализован в методе reCheckClientSignature класса SecureManager.
3) " Проверка построения корректной цепочки от проверяемого регистрационного свидетельства до доверенного корневого регистрационного свидетельства удостоверяющего центра, с учетом промежуточных регистрационных свидетельств удостоверяющих центров;"
Тут есть не ясность в цепочке подписанных сертификатов "КУЦ->НУЦ2->ЭЦП респа". Зачем  проверять то что НУЦ2 был подписан сертификатом  КУЦ? Оба эти сертификата находятся в "Ldw-Example-Kalkan".  Я знаю что они правильные. Зачем еще одна проверка?  Я сделал эту проверку, но глубинный смысл её необходимости остался для меня сокрытым.
В примере этот пункт реализован в методах checkNucOneCertificateType для ЭЦП НУЦ 1 и checkNucTwoCertificateType для ЭЦП НУЦ 2  класса SecureManager.

4) " Проверка срока действия регистрационного свидетельства. Проверка сроков действия от проверяемого регистрационного свидетельства до доверенного корневого регистрационного свидетельства удостоверяющего центра, с учетом промежуточных регистрационных свидетельств удостоверяющих центров;"
Код проверки размазан по методам reCheckClientSignature для проверки ЭЦП респа и методам  checkNucOneCertificateType для сертификата НУЦ 1 и checkNucTwoCertificateType для сертификата НУЦ 2 .
            5) Проверка регистрационного свидетельства на аннулирование. Проверка регистрационного свидетельства на аннулирование осуществляется одним из методов:
на основе СОРС НУЦ РК. Данный метод проверки подтверждает, аннулировано ли проверяемое регистрационное свидетельство на момент начала срока действия СОРС НУЦ РК;
онлайн проверка регистрационного свидетельства на аннулирование, основанная на протоколе OCSP (On-lineCertificateStatusProtocol). Данный метод проверки подтверждает, аннулировано ли проверяемое регистрационное свидетельство на момент отправки запроса (текущее время);
на основе дополнительного СОРС. Данный сервис необходимо использовать совместно с сервисом СОРС, что позволяет получить более актуальную информацию, чем в сервисе СОРС. Данный метод проверки подтверждает, аннулировано ли проверяемое регистрационное свидетельство на момент начала срока действия дополнительного СОРС НУЦ РК
Здесь на выбор предоставляют три возможности для проверки отозваности. Я выбрал первый. Это загрузка CRL файлов из инета и проверка по ним.  Загрузка CRL- файлов происходит не всегда а через определенный период (константа HOURS_OF_RELOAD) . После загрузки CRL-файлов , они сидят в кэше.
В примере этот пункт реализован в методах isNotRevokedCertNucOne для ЭЦП от НУЦ 1 и isNotRevokedCertNucTwo для ЭЦП от НУЦ 2.
6) Проверка области использования ключа. Проверка заключается в проверке значения поля регистрационного свидетельства «использование ключа» (KeyUsage). Если поле «использование ключа» содержит значения «Цифровая подпись» и «Неотрекаемость», то это регистрационное свидетельство используется для ЭЦП. А если поле «использование ключа» содержит значения «Цифровая подпись» и  «Шифрование ключей», то это регистрационное свидетельство используется для аутентификации;
В примере этот пункт реализован в методе  isBadKeyUsage.  В нашем примере нужна только подпись. Так что проверяем наличие свойства «Неотрекаемость».
7) Проверка номера политики регистрационного свидетельства и разрешенных способах его использования. Если политика проверяемого регистрационного свидетельства предусматривает ограничение его использования (только в одной системе), то данное регистрационное свидетельство и соответствующий закрытый ключ не использоваться в других системах;
Не реализовано.
8) Проверка метки времени. Доказательством подписания документа в указанный момент времени является квитанция метки времени, полученная в НУЦ РК и содержащая время подписания документа. Данная проверка производится для электронных документов долговременного хранения и формируется в момент подписания документа;
Не реализовано.

9) Проверка полномочий  лица подписавшего документ. Механизмы проверки полномочий возлагаются на Систему.
Не реализовано.

Цель 4. Дополнительные проверки подписи на сервере.
Тут все относительно просто.  Эти требования которые я сам себе выдумал.
1)  Хотя в требованиях от НУЦ РК этого не сказано, но нам придется работать еще какое-то время  с сертификатами старого (НУЦ 1) и нового образца (НУЦ 2) . 
Поэтому в коде вначале сертификат проверяют на соответствие НУЦ 1 в методе checkNucOneCertificateType, а затем на соответствие НУЦ 2 в методе checkNucTwoCertificateType
2) В реальной системе  у меня пользователь заходит на сайт по протоколу HTTPS используя свой сертификат для аутентификаций на сайте. При подписаний используется другой сертификат и я хочу быть уверенным то что пользователь не ошибся в выборе сертификата. Это значит я знаю на момент проверки подписи какой БИН или ИИН должен быть сертификате подписавшем данные. Этот  БИН или ИИН я передаю в конструктор SecureManager.
Сама проверка проходит в методе isBadBinOrIin.  

PS1: К сожалению мне удавалось выделять достаточно свободного времени на этот проект. По причине почти полного отсутствия оного. Поэтому все затянулось.
PS2: Пока писал этот пример нашелся еще один человек который взял эту миссию на себя. Смотрите здесь.  У нас разные подходы, он писал либу для SDK "Kalkan",  я пример реализаций их требований. И возможно его подход будет для вас более удобным и понятным для использования SDK "Kalkan". Я не настаиваю.
PS3: Я тестил "Ldw-Example-Kalkan" только на сертификатах  юридических и физических лиц. Другие типы сертификатов (казначейство и нотариус)  работать скорее всего не будут.

 PS4: Вполне возможно что Вы найдете ошибки у меня. Как коде, так в этой статье. Просто я их еще не вижу. Если Вы их заметили , то дайте мне знать, плиз.