Denis Gladkikh
Russian   |  English

Использование сертификатов: Подпись данных на стороне клиента.

Одно из назначений для сертификата – это подписывание данных. Цель подписи может быть различной – валидация клиента или просто проверка достоверности данных.

Одна из задач, которая у вас может возникнуть – это подписать некоторые данные на стороне клиента в браузере пользователя. Такая задача может возникнуть тогда, когда пользователь подает некоторые важные данные, и для того, чтобы он в будущем не отказывался со словами «Это делал не я!» от этих данных. Для этого могут служить сертификаты. Подробнее о сертификатах и о SSL можно узнать где угодно, достаточно в поисковике набрать «SSL сертификаты» и информации об их назначении и о необходимости будет предостаточно.

Для реальных задач вам, скорее всего, понадобятся сертификаты удовлетворяющие алгоритмам ГОСТа, одна из программ реализующая их это КриптоПро, хотя даже вроде как и единственная. Но для того чтобы разрабатывать или тестировать сама программа вам не понадобится. Идея сертификатов так же в том, что пользователь и разработчик не должны вникать в то, какие алгоритмы они реализуют, какие типы ключей в них содержаться, и программа, работающая с сертификатами одного типа так же работала бы на сертификатах другого типа (это в теории, в практике может быть будут проблемы с некоторыми настройками контейнеров и вообще администрированием). Главное – для нашей задачи - это чтобы сертификат сервера и клиента соответствовали одному алгоритму.

Для тестирования мы можем воспользоваться Windows Server 2003 и его службой сертификации. Для этого необходимо при помощи мастера компонентов Windows установить службу сертификации, во время установки будут заданы некоторые вопросы, относительно имени сервера сертификации, срока его службы и остального. После установки мы будем иметь по адресу http://localhost/certsrv/ веб сайт, предоставляющий возможность запросить сертификат. А в администрировании в центре сертификации мы сможем подтверждать запросы на выдачу сертификатов. Если разрабатывать вы будете на Vista, либо Windows 7, тогда вам необходимо будет установить на Win2003 патч KB922706.

Другой способ создавать тестовые сертификаты при помощи программки makecert. Так же можно использовать и какие либо другие тестовые службы сертификации. У крипто про так же есть внешний тестовый сервер сертификации.

Первое что необходимо сделать – это установить сертификат ЦС на машину, на которой вы будете разрабатывать ваше приложение, чтобы она могла доверять этому центру выдачи и могла использовать сертификаты от этого центра.

Второе - это сгенерировать сертификат проверки сервера на эту машину. Имя сертификата должно совпадать с dns именем веб-приложения, но так как мы разрабатываем приложение локально, то для тестов мы возьмем имя localhost (хочу заметить, что если вы выдали сертификат на имя www.mydns.ru, то обратившись к mydns.ru вы увидите ошибку о том, что сертификат выдан не на это имя). Важно еще знать, что есть хранилище сертификатов пользователя, и отдельно хранилище сертификатов машины. По умолчанию, стандартно, сертификаты устанавливаются в хранилище пользователя, а для того чтобы сертификат проверки сервера использовать на сайте необходимо его положить в Local Computer/Personal. Посмотреть в каких местах какие сертификаты находятся можно следующим способом: запускаем Microsoft Management Console (вызывом mmc.exe) там нажимаем ctrl+m и выбираем snap-in Certificates (сначала пользователя, затем Local Computer) и там вы видим полное дерево сертификатов.

Следующим действием нам нужно установить сертификат localhost на сайт. Открываем IIS и в зависимости от его версии настройки могут быть разные. В IIS 5.1 или 6.0 в окне свойств сайта нужно перейти на вкладку Security и там при помощи кнопки Security установить сертификат. В IIS 7 необходимо открыть свойства “Bindings…” и там добавить binding с типом https, в этом же окне необходимо будет выбрать сертификат (если сертификата в выпадающем списке нету, значит он лежит не в верной директории либо сертификат не с типом проверки подлинности сервера).

Теперь можем проверить, что наш веб-сайт откликается на https и может соединиться посредством SSL шифрования для этого набираем в браузере https://localhost/... (если не открывается – проверьте что установлен сертификат ЦС в Trusted Root Certification Authorities).

Установка сертификата на сайт дает нам доступ к сайту с шифрованием. Можно использовать при помощи настроек так же и двухстороннее шифрование, тогда каждый пользователь, заходящий на сайт по https пути должен будет указать и свой сертификат (настройка может быть «не указывать», «желателен», «необходим»).

Для того чтобы производить подписывание на клиенте данных необходимости в установке серверного сертификата нету, но вряд ли перед вами будет стоят задача подписи без установки зашифрованного соединения.

Теперь приступим к подписыванию данных. Создаем ASP.NET веб-сайт, на страничку Default.aspx кладем следующие контролы:

<div> 
    <!--сюда пишем данные,которые будем подписывать--> 
    <asp:TextBox runat="server" ID="tbDataText" Width="400px"/> 
</div> 
<div> 
    <!--кнопка, которая выполняет функцию подписи данных--> 
    <asp:Button runat="server" ID="btnSignData" Text="Подписать данные" 
            OnClientClick="if (SignData() == false) return false;" 
            onclick="btnSignData_Click" /> 
</div> 
<div> 
    <!--сюда записываем данные--> 
    <asp:TextBox runat="server" ID="tbSignedData" TextMode="MultiLine" Width="600px" Rows="24" /> 
</div> 
<div> 
    <!--сюда выводим сообщения после подписи данных--> 
    <asp:Label runat="server" ID="lblData" /> 
</div> 

Кнопка btnSignData сначала вызывает javascript функцию SignData() со следующим кодом:

function SignData() {
    // Необходимые константы 
    var CAPICOM_STORE_OPEN_READ_ONLY = 0;
    var CAPICOM_CURRENT_USER_STORE = 2;
    // проверяем, что поддерживаются ActiveXObject (Internet Explorer) 
    if (window.ActiveXObject) {
        try {
            // Подписываемые данные 
            var tbDataText = document.getElementById('<%= tbDataText.ClientID %>');
            //Создаем необходимые объекты ActiveX 
            var CertStore = new ActiveXObject("CAPICOM.Store");
            var Signer = new ActiveXObject("CAPICOM.Signer");
            var SignedAuth = new ActiveXObject("CAPICOM.SignedData");
            //Открываем хранилище сертификатов пользователя только для чтения 
            CertStore.Open(CAPICOM_CURRENT_USER_STORE, "MY", CAPICOM_STORE_OPEN_READ_ONLY);
            //Выводим пользователю окно выбора сертификата 
            try {
                var certificate = CertStore.Certificates.Select("Выберите сертификат для подписи документа.", 
                        "Выберите один из сертификатов", false);
            }
            catch (e) {
                // Пользователь не выбрал сертификат 
                return false;
            }
            //Подписываемые данные 
            SignedAuth.Content = "Дата подписи: " + (new Date()) + ", Данные: " + tbDataText.value;
            //Выбранный сертификат 
            Signer.Certificate = certificate.Item(1);
            //Сюда запишем данные (можно писать в hidden поле, тут сделано для примера) 
            var lblData = document.getElementById('<%= tbSignedData.ClientID %>');
            // Подписываем 
            lblData.value = SignedAuth.Sign(Signer, false);
        } catch (e) {
            alert('Невозможно подписать данные. Убедитесь что браузером разрешно использование ActiveX. ' 
                + ' Добавьте сайт в Trusted Sites.');
            return false;
        }
        return true;
    }
    else {
        alert('Используйте Internet Explorer для просмотра данного сайта');
        return false;
    }
} 

Данный скрипт как раз и подписывает данные на стороне клиента. Он работает только в IE, во время работы скрипта он может ругаться, требовать прав на запуск ActiveX объектов, самое простое добавить данный сайт в зону Trusted Sites.

На стороне сервера можем выполнять следующий код при нажатии на кнопку:

protected void btnSignData_Click(object sender, EventArgs e)
{
    StringBuilder sb = new StringBuilder();
    // Смотрим, что подписано  
    SignedCms cms = new SignedCms();
    cms.Decode(Convert.FromBase64String(tbSignedData.Text));
    // Проверяем подпись  
    cms.CheckSignature(false);
    // Что подписано  
    sb.AppendLine(Encoding.Unicode.GetString(cms.ContentInfo.Content));
    //Сравним сертификат, которым были подписаны данные, и клиентский сертификат   
    if (Request.IsSecureConnection && Request.ClientCertificate.IsPresent)
    {
        X509Certificate2 cert = new X509Certificate2(Request.ClientCertificate.Certificate);
        if (cms.SignerInfos.Count > 0 && string.Compare(cms.SignerInfos[0].Certificate.SerialNumber, cert.SerialNumber) == 0
            && string.Compare(cms.SignerInfos[0].Certificate.Issuer, cert.Issuer) == 0)
        {
            sb.AppendLine("<br/>Данные подписаны клиентским сертификатом");
        }
        else
        {
            sb.AppendLine("<br/>Данные подписаны отличным от клиентского сертификатом");
        }
    }
    lblData.Text = sb.ToString();
}

Для выполнения данного кода может потребоваться установить reference на библиотеку System.Security, включающую namespace System.Security.Cryptography.

Таким способом вы сможете не просто хранить какие-либо данные, может быть важные вам, а к тому же и быть уверенным в их достоверности. Надеюсь, эта статья вам поможет в начальном изучении сертификатов.

Скачать пример: CertificateWebExample.zip


Вас также может заинтересовать

rss twitter

Комментарии (36)

Андрей ( ) #
avatar
Очень интересная и главное полезная статья с хорошим примером. Сейчас сам разбираюсь как работать с ЭЦП в asp.net приложении - статья очень помогла. Буду ждать новых публикаций на эту тему от автора.
Никаких аналогичных статей на эту тему в интернете не встречал. Если бы были ссылки на дополнительную информацию по этой теме с примерами, было бы вообще супер, но и так материал Очень полезный.
Denis Gladkikh ( ) #
avatar
Андрей, спасибо за отзыв. На сайте gotdotnet.ru есть ссылка на эту статью, где в свое время так же обсуждалась данная проблема. К вашему сожалению я больше не занимаюсь автоматизацией с использованием ЭЦП (сменил место деятельности,) потому вряд ли буду в будущем писать новый статьи по этой теме, но если от вас будут вопросы, а у меня будут "не короткие" ответы, то статьи будут, ну и я рад буду проконсультировать, если смогу.
Андрей ( ) #
avatar
Да, есть еще вопросик по приведенному примеру обработки подписанного сообщения на стороне сервера:

каким образом можно выбрать сертификат с помощью которого мы хотим прочитать подписанное пользователем сообщение? (в этой теме я новичек, и не исключаю что не корректно сформулировал вопрос, но идея в том, что когда у меня много пользователей я не знаю как определить что данные подписаны именно тем ключем, которым нужно).
Denis Gladkikh ( ) #
avatar
Андрей, посмотрите на метод btnSignData_Click, там я из подписанного сообщения получаю SignedCms, который уже содержит информацию о тех сертификатах, которые подписывали данное сообщение. Так же в этом методе я делаю проверку, что сертификат, которым подписали совпадает с сертификатом, который предоставил пользователь при подключении по https.
Андрей ( ) #
avatar
Собственно этот метод я и смотрел. Все вопросы возникли, когда на строке cms.CheckSignature(false);
у меня стал вылетать Exception
<
Цепочка сертификатов обработана, но обработка прервана на корневом сертификате, у которого отсутствует отношение доверия с поставщиком доверия.
>
Походу что то я не настроил в винде с сертификатами, а может еще что?

Спасибо за помощь! :-)
Denis Gladkikh ( ) #
avatar
Ага, тут все просто, скорее всего ваш сервер или где вы проверяете сертификат ничего не знает о УЦ, который выдал сертификат, которым подписано сообщение. Положите сертификат УЦ в Trusted Publishers сервера.
Андрей ( ) #
avatar
Спасибо за очередную подсказку! :-)
Проверка пользовательского сертификата заработала, после того как я раздобыл сертификат удостоверяющего центра и добавил его в доверенные.

Теперь буду разбираться с настройкой SSL (раньше с ним тоже никогда не работал). В данном примере подразумевается что при создании защищенного соединения каким то образом используется клиентский сертификат?
Denis Gladkikh ( ) #
avatar
Да, именно. Это достаточно просто настраивается в IIS. Требование клиентского сертификата.
Андрей ( ) #
avatar
Вот попытался настроить сертификат для создания защищенного соединения на стороне сервера. Когда открываю страничку по протоколу https у меня вылетает Exception

Невозможно проверить функцию отзыва, т.к. сервер отзыва сертификатов недоступен.

на строке
cms.CheckSignature(false);

Нет предположений из-за чего может быть такое?
Denis Gladkikh ( ) #
avatar
УЦ периодически выпускает цепочку отозванных сертификатов, это считайте те, которые были утеряны или отозваны от ненадобности. Проверка подписи включает в себя так же проверку того, что сертификат, которым подписан не включен в список отозванных. Ну и видимо просто с того компьютера нет доступа до УЦ.
Андрей ( ) #
avatar
23.04.2010 17:38
если от вас будут вопросы, а у меня будут "не короткие" ответы, то статьи будут, ну и я рад буду проконсультировать, если смогу.
-------------------------------------------
Вот возник еще вопросик, в системе по работе с ЭЦП каким образом (в базе данных?) правильно можно привязать электронный ключик к конкретному пользователю?
Есть ли какая-нибудь литература, в которой раскрываются юридические аспекты оформления сделок с использованием ЭЦП? - может есть у Вас возможность поделиться опытом в этой сфере?
Denis Gladkikh ( ) #
avatar
Андрей, к сожалению таких книг не встречал. На практике достаточно записать серийный номер сертификата и удостоверяющий центр, его выдавший. Оба поля строками.
Андрей ( ) #
avatar
А можно как нибудь методт SignData привязать не просто к кнопке, а к кнопке asp:LoginView?
Сейчас решаю задачу проверки ЭЦП при авторизации пользователя в системе, может как нибудь по другому лучше проверять (пока я рассматриваю без SSL шифрования с клиентской стороны)
Denis Gladkikh ( ) #
avatar
Вы тут путаете, SingData для подписи данных, вам же нужно установить как я понял соединение на обе стороны имеющие сертификат, и к тому же проверить пользователя.

Я это решал так: в IIS выставлял accepted сертификат, когда пользователь нажимал кнопку "Войти при помощи сертификата", то я просто перекидывал пользователя на этот же сайт, но только с https, IIS сам спрашивал пользователя сертификат, и потом уже на сервере я мог получить доступ к нему при помощи Request.ClientCertificate.
Андрей ( ) #
avatar
Приведенный Вами пример действительно логичный, я уже его опробовал, и он работает.Но сейчас я создаю соединение только с использованием серверного сертификата.
Хочу при авторизации пользователя (проверки логина и пароля) передавать дату/время на со стороны клиента, подписанную пользовательским сертификатом. Затем на сервере сравнивать соответствие логина пользователя и цифровой подписи подписанного сообщения.
На данный момент думаю придется в LoginView сделать свой блок anonimus template (или как он там называется для не авторизированного пользователя) и там разместить простую кнопку для проверки сертификата и обработчик проверки логина пароля. (надеюсь я не сильно запутанно изложил свои мысли).
Вопрос бы в следующем: можно ли привязать SignData к LoginView как нибудь по-проще? :-)
Denis Gladkikh ( ) #
avatar
Забыть про LoginView, либо сделать template с кнопкой (точно не помню как там можно шаманить над LoginView). Идея такая, что asp:Button имеет событие OnClientClick - это javascript строка, которая выполнится перед отправкой формы на postback, там же если вернуть false то отправки не будет.
И я предупреждал что подход не правильный ;)
Андрей ( ) #
avatar
Я тут подумал, что лучше сразу сделать качественно :-) (Особенно после фразы "И я предупреждал что подход не правильный" :-) ) . Может тогда подскажете как настроить IIS, чтобы один и тот же сайт работал и по http и https ?
Denis Gladkikh ( ) #
avatar
а) так главное чтобы был binding настроен на http и на https.
б) так же смотрите на SSL Settings, там можно выставить Require SSL (уберите, чтобы http работал), и по клиентским сертификатам: Ignore, Accept, Require. Вот выставите второе, тогда пользователь может прикладывать сертификат при заходе через https.
Андрей ( ) #
avatar
Соединение с https работает с серверным протоколом без клиентского, и с клиентским - т.е. как положено.
А вот binding на http настроить не получается? Эта настройка есть в IIS 6.0 ?
Denis Gladkikh ( ) #
avatar
в iis6 думаю это нужно просто настроить при помощи Require SSL
Андрей ( ) #
avatar
Да, все сделал именно так, 100 раз уже проверил, по http не соединякется :-( Только по https, причем даже если вообще снимаю все галочки (не требовать сертификаты) все равно не работает по http, походу как только в IIS 6.0. добавляешь сертификат он всегда его требует, хотя может я что не так делаю...
Denis Gladkikh ( ) #
avatar
Попробуйте сделать сайт с самого начала, без https, потом добавите сертификат и посмотрите что получится.
Андрей ( ) #
avatar
Попробовал сделать с самого начала - вроде получилось! :-))) По крайней мере работает и с https и http, с клиентским сертификатом и без него.

Осталось только сделать адекватный переход с http на https при авторизации :-) Можно получить текущий адрес, разобрать, и собрать новый URL с https, но наверно есть более красивый переход (просто жестко указать ссылку вида https://mysite.ru/default.aspx - тоже нормальный вариант? )
Denis Gladkikh ( ) #
avatar
Я именно так просто и делал, перекидывал просто с http на https, очень просто и эффективно.
sdelaem-site.com.ua ( ) #
avatar
да согласен полезная вещь, интересно почитать также про безопасность, сам подписываю иногда наверно практически все видели и подвержали
Андрей ( ) #
avatar
Денис, а тебе ни разу не приходилось подписывать ЭЦП документы MS WORD 2003/2007? Ведь если подписывать просто текстовый файл (или любой другой файл) - потом он становится "нечитабельным". А документы Word можно подписать таким образом, чтобы они читались, мало того, еще было видно кем и когда он (документ) был подписан.
Если есть опыт или читал про подпись вордовских документов - скинь ссылочку! :-)
Denis Gladkikh ( ) #
avatar
Андрей, нет, такой задачи не было. Знаю что там есть встроенный механизм для этого, но особо не вдавался в подробности.
Владимир ( ) #
avatar
Жалко, что только сейчас наткнулся на этот блог. Тем не менее, задам свой вопрос:

В приведенном примере на стороне клиента подпись формируется javascript функцией SignData() с использованием объектов COM.

Возможна ли реализация этой же функции на стороне клиента с помощью классов .NET Framework?
Denis Gladkikh ( ) #
avatar
Владимир, конечно возможно, но что это будет? Плагин для браузера? WPF xBAP приложение? Просто вызвать .NET код не получится, в любом случае нужен какой-то посредник. Silverlight тоже не даст.
Владимир ( ) #
avatar
Вот это меня и мучает, что не могу понять как исполнить .NET код, передаваемый сервером, на стороне клиента.

Если использовать скрипты, то мне нет никакого смысла затеваться с ASP.NET, все-равно у клиента нужно устанавливать capicom.dll и xenroll.dll, скрипты же я могу затолкать в файл .hta и распространять вместе с .dll

А хотелось бы приложение запускать с web-сайта, а вместо .dll у клиента устанавливать .NET Framework (через Интернет).

Я сейчас прорабатываю возможность безболезненно перейти от скриптов на технологии .NET, поэтому прошу извинить чайника.
Максим ( ) #
avatar
Господа, приветствую.

Возникла необходимость использовать .Net вместо CAPICOM по причине того, что его невозможно заюзать на 64битных системах.

Владимир, у Вас что нибудь получилось?
Егор ( ) #
avatar
Спасибо, Вам, за статью!!

То, что надо для работы с сайтом через сертификаты. Вот только мне надо все это проделать с WCF сервисом :(

Вы случаем не знаете как это реализуется на WCF? Буду очень признателен за любую информацию :)
Владимир ( ) #
avatar
Максим, я не стал долго мучаться, сделал все на VBScript с помощью CAPICOM. Насколько успел уловить, для использования КриптоПро CSP на платформе .NET нужно купить у cryptopro.ru SDK продукт КриптоПро Sharpei за 30 т.р. и на каждую клиентскую машину устанавливать КриптоПро Sharpei RTE.
Максим ( ) #
avatar
Владимир, спасибо за информацию. Да уж, накладно получается. Придется юзать CAPICOM. :)
Андрей ( ) #
avatar
Еще раз хочу отметить, что для меня этот материал для меня был очень очень полезен. Но сейчас столкнулся с еще 1 проблемой, описываю по пунктам что делаю:

1. Создаю Хэш документа

2. Подписываю хэш документа

3. Сохраняю хэш и подписанный хэг в базе, в base64string

4. Выгружаю подписанный хэш на страницу и пытаюсь второй раз подсисать использую CAPICOM

Тут и возникает проблема. CAPICOM подписывает "ранее подписанный хэш" как будтно жто просто данные для подписи, а мне нужно получить 2 подписи к исходному хэшу.

Надеюсь я не сильно запутанно объяснил? Что-нибудь посоветуете?
Владимир ( ) #
avatar
Андрей, как то все сложно.

Если нужно две подписи, то у объекта SignedData в CAPICOM есть два метода: Sign - создает цифровую подпись контента; CoSign - подписывает уже подписанное сообщение.

CoSign удобно использовать, когда подписываемое сообщение упаковывается вместе с подписью в контейнер #PKCS07 (присоединенная подпись).

Другой вариант - отсоединенная (detached) подпись, тут можно использовать метод Sign сколь угодно раз, сохраняя полученные подписи (шифрованный на закрытом ключе подписующего хэш) в удобные хранилища.
Добавить комментарий

Если вы хотите получать уведомления о новых комментариях к данному топику, укажите, пожалуйста, email и отметьте соответствующий пункт в форме. Если вы хотите добавить код в тексте комментария, то заключите его внутри тега [code]...[/code], более того можно уточнить язык, на котором написан данный код при помощи [code cs]...[/code], где вместо cs могут быть cs, html, xml, java, js, php, sql, cpp, css.

 

busy