outcoldman
outcoldman Denis Gladkikh

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

SSL, x509, ASP.NET, JavaScript, Certificate, and Сертификаты

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

Одна из задач, которая у вас может возникнуть – это подписать некоторые данные на стороне клиента в браузере пользователя. Такая задача может возникнуть тогда, когда пользователь подает некоторые важные данные, и для того, чтобы он в будущем не отказывался со словами «Это делал не я!» от этих данных. Для этого могут служить сертификаты. Подробнее о сертификатах и о 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

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.