Apache не будет запрашивать сертификат моего клиента SSL

Прежде всего, обратите внимание, что я новичок в настройке SSL. В прошлом мне всегда везло, что у меня был ИТ-отдел, который заблаговременно настраивал это для меня. Так что будьте готовы к тому, что мне может понадобиться уточнить некоторые из ваших ответов. знак равно

Что я пытаюсь сделать

Я создаю корпоративный корпоративный веб-сайт для сотрудников. Например, будет стартовая страница браузера, на которой отображается контент, настроенный для каждого сотрудника. Таким образом, мне нужно иметь возможность идентифицировать указанного сотрудника, не требуя имени пользователя/пароля или других подсказок (однако однократная настройка в порядке; я просто не хочу, чтобы им приходилось каждый раз запрашивать). Естественно, SSL кажется лучшим способом сделать это.

У меня будет настройка базы данных MySQL для связывания учетных записей «пользователей» с SSL_CLIENT_M_SERIAL и SSL_CLIENT_I_DN, которые, как я предполагаю, будут уникальными для каждого клиентского сертификата (?). Я получил эту идею из этой статьи: http://cweiske.de/tagebuch/ssl-client-certificates.htm

В первый раз, когда пользователь заходит на внутренний веб-сайт, у него не будет сертификата (я НЕ хочу генерировать его вручную для клиентов!), и в этом случае $_SERVER["SSL_CLIENT_VERIFY"] == "NONE". Если это произойдет, он перейдет на страницу настройки учетной записи пользователя, которая будет включать в себя шаг, на котором PHP генерирует клиентский сертификат SSL и отправляет его в браузер для установки пользователем. Красиво и просто. Затем пользователь устанавливает сертификат, создается ассоциация, и после перезапуска браузера (на всякий случай) пользователь возвращается на внутренний веб-сайт.

На этом этапе Apache должен запросить сертификат клиента, который затем отправляет браузер. Затем PHP-скрипт анализирует необходимые переменные $_SERVER, сравнивает их с базой данных MySQL, и все получают удовольствие. Опять же красиво и просто.

Что пока работает

У меня установлены сертификаты на стороне сервера. И да, они самоподписанные (по понятным причинам). В Apache установлен mod_ssl, и все работает нормально. Я создал PHP-скрипт, который просто выводит массив $_SERVER, и все значения ключей SSL_SERVER_* соответствуют созданному для него сертификату.

Проблема

Я не могу заставить клиентские сертификаты работать! В том же PHP-скрипте, что бы я ни делал, SSL_CLIENT_VERIFY == "NONE" и другие ключи SSL_CLIENT_* отсутствуют. Вот что происходит, если в ssl.conf для SSLVerifyClient задано необязательное значение. Согласно каждому учебнику, который я читал, все они говорят, что веб-сервер должен запрашивать у браузера сертификат клиента. Дело в том, что я не могу заставить его сделать это! Он просто переходит прямо к PHP-скрипту и предполагает, что у меня вообще нет клиентских сертификатов. Это происходит в Firefox, Chrome и IE.

Поэтому я попытался установить для SSLVerifyClient требуемое значение и перезапустил веб-сервер. С этой опцией я даже не могу установить SSL-соединение. Firefox просто сообщает, что соединение было сброшено (другие браузеры также отображают свои версии этой ошибки). Странно то, что журналы не показывают НИКАКОЙ активности при этих попытках подключения! т.е. access_log, error_log, ssl_access_log, ssl_error_log и ssl_request_log не показывают НИЧЕГО; как будто и попытки не было. Это расстраивает, потому что это означает, что у меня даже нет сообщения об ошибке, с которым можно работать. Просто веб-сервер пассивно-агрессивно говорит мне идти к черту.

Я попытался сгенерировать/установить свой собственный клиентский сертификат вручную, используя расширение PHP OpenSSL. Сертификат установился просто отлично, хотя я не могу найти никакой информации о том, как связать этот сертификат с серверным (при условии, что мне это даже нужно?). Кроме того, в любом случае это не имеет значения, потому что Apache по-прежнему даже не будет запрашивать сертификат клиента, если установлен необязательный параметр. И если установлено требование, оно просто взрывается без объяснения причин. В любом случае мне нужно, чтобы эта схема работала как необязательная.

Окружающая среда

ОС: CentOS 5.7 64-бит (VirtualBox)

Апач: 2.2.3

PHP: 5.3.10

Я предполагаю, что вам может понадобиться дополнительная информация, чтобы помочь мне, поэтому, пожалуйста, спросите! Я предоставлю тебе все, что тебе нужно.

Подводя итог, мне нужно знать, как заставить Apache запрашивать сертификат клиента SSL с учетом условий, изложенных выше. Кроме того, если необходимо выполнить какую-либо специальную подпись/и т. д., чтобы сделать сертификат клиента «совместимым» с сертификатом сервера (опять же, БЕЗ выполнения этого вручную через оболочку для каждого сертификата клиента!), мне нужно знать, что как Что ж.

Я на 100% застрял в этом на данный момент. Не могу найти ничего даже отдаленно полезного в Google. Любая помощь, которую вы можете оказать в этом, будет ОЧЕНЬ оценена!! Спасибо! знак равно


person Kris Craig    schedule 20.04.2012    source источник
comment
Является ли каждый сгенерированный клиентский сертификат самоподписанным? Или вы создали свой ЦС для подписи сгенерированных запросов? Клиент запросит у вас сертификат только в том случае, если у вас установлен сертификат, подписанный доверенным центром (который определяется хранилищем доверенных сертификатов сервера). Если каждый клиентский сертификат является самоподписанным, вам потребуется, чтобы каждый из этих сгенерированных сертификатов находился в хранилище доверенных сертификатов сервера, что было бы невозможно.   -  person bobz32    schedule 21.04.2012
comment
Я попытался создать сертификат клиента на сервере с помощью openssl, экспортировать как p12, а затем установить его в Firefox, но теперь я получаю сообщение об ошибке «Невозможно получить сертификат локального эмитента» от Apache. Я подписал сертификат клиента, используя сертификат сервера, так почему же он не работает? Я попытался раскомментировать строку SSLCACertificateFile в ssl.conf, но затем это просто вернуло меня к первой проблеме: страница загружалась нормально, но без распознавания сертификата клиента. Итак, я снова вернулся к исходной точке....   -  person Kris Craig    schedule 21.04.2012


Ответы (3)


Во-первых, вам нужно настроить Apache Httpd для запроса клиентского сертификата. Для этого вам нужно как минимум использовать SSLVerifyClient optional для местоположения/каталога, который вы хотите аутентифицировать с помощью этого метода.

Во-вторых, сертификаты, отправленные клиентом, также должны быть доверенными для Apache Httpd. (В принципе, вы можете использовать SSLVerifyClient optional_no_ca, позволить любому клиенту пройти сертификацию в стеке Apache Httpd SSL/TLS и только затем проверить сертификат в PHP, но это довольно сложная работа, для которой вам нужно быть немного более осторожным, поскольку это не обязательно простой код; что более важно, это было бы совершенно бесполезно в этом контексте, поскольку вы находитесь в сценарии, в котором вы контролируете свой ЦС.)

Насколько я понимаю, SSL_CLIENT_VERIFY (переменная, которую я сам мало использовал) кажется действительно полезной только с SSLVerifyClient optional_no_ca. Это может работать с SSLVerifyClient optional, но я в этом сомневаюсь. SSLVerifyClient require будет отклонять соединения с использованием ненадежного сертификата клиента (одним из ЦС в SSLCACertificateFile/SSLCACertificatePath) или при отсутствии сертификата. Насколько я знаю, SSLVerifyClient optional пропустит клиента без сертификата или с доверенным сертификатом, но и отклонит соединение, если сертификат не доверенный.

Здесь под отклонением соединения я имею в виду резкое закрытие соединения SSL/TLS с предупреждением. Нет возможности создать страницу ошибки HTTP(S). Все, что вы получите в стандартной ошибке браузера, что-то вроде строк ssl_error_unknown_certificate_.... (Вы должны учитывать это с точки зрения удобства использования.)

С этого момента вам нужно настроить свой собственный ЦС, возможно, в Интернете с генерацией ключей в браузере и на том же веб-сайте. Вам не нужен SSLVerifyClient require для этого, потому что вам нужно будет разрешить пользователям, у которых еще нет сертификата (вместо этого используйте optional). При этом эти директивы не обязательно должны применяться ко всему хосту, но могут быть специфичными для определенных местоположений/каталогов.

Интеграция собственного веб-ЦС (или, в более общем смысле, создание собственного ЦС) не всегда проста, если вы новичок во всем этом. Существуют готовые инструменты (например, OpenCA), или вы можете создать свой собственный, используя различные фрагменты JavaScript/ActiveX, и вы бы нужен код на стороне сервера для обработки запросов SPKAC или PKCS#10 (и для выдачи фактического сертификата). (Чтобы такой ЦС был полезен, вы бы хотели, чтобы пользователи, подающие заявку на новый сертификат, предоставили какое-либо подтверждение личности во время подачи заявки, возможно, пароль.)

Когда это настроено, вы должны настроить SSLCACertificateFile (или ...Path) так, чтобы он указывал на сертификат ЦС вашего внутреннего ЦС (будь то веб-ЦС или нет, на том же сайте или нет). (Конечно, держите закрытый ключ вашего ЦС закрытым, возможно, настроенным в веб-приложении ЦС, но самому Apache Httpd не нужно знать об этом.) Браузеры будут предлагать только сертификаты, выданные этими ЦС или промежуточными звеньями (если только вы также настроили SSLCADNRequestFile, который вместо этого будет использоваться для отправки списка принятых центров сертификации).

Обратите внимание, что эти два шага (настройка ЦС и настройка веб-сайта для использования клиентских сертификатов) на самом деле независимы друг от друга. Тот факт, что оба могут быть частью одного и того же сайта, может быть удобным, но не обязательным. Вы можете попробовать настроить Apache Httpd без предварительного развертывания ЦС на сайте (я бы порекомендовал это, даже если это просто для того, чтобы посмотреть, во что вы ввязываетесь). Существует ряд инструментов для создания собственного небольшого ЦС, которым можно управлять с помощью нескольких сертификатов: OpenSSL's CA.pl или TinyCA. Вы также можете использовать эти тестовые сертификаты (localhost и testclient, testclient_r аннулируется, если вы хотите использовать CRL, возможно, сначала не нужно): все пароли testtest.

Как вы уже ожидали (с вашей базой данных MySQL), вам нужно будет управлять сертификатами, которые вы выдаете, и сопоставлять их с пользователями. Однако SSL_CLIENT_M_SERIAL и SSL_CLIENT_I_DN не подходят для использования. SSL_CLIENT_I_DN — это DN эмитента (т. е. DN субъекта ЦС). Вам нужно найти SSL_CLIENT_S_DN: DN субъекта сертификата клиента. SSL_CLIENT_M_SERIAL — это серийный номер сертификата: не используйте его, поскольку он уникален для каждого сертификата: у одного пользователя может быть несколько сертификатов с одним и тем же DN субъекта (например, если срок действия одного истек или он отозван).


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

  • Во-первых, пользователь все равно должен защищать свои сертификаты паролем. Думаю, вам действительно нужна какая-то форма единого входа (SSO).

  • Во-вторых, в зависимости от степени компьютерной грамотности ваших пользователей, управлять сертификатами может быть довольно сложно.

    Тот факт, что слово «сертификат», строго говоря, вообще не включает закрытый ключ, но иногда подразумевает использование закрытого ключа, может сбить с толку некоторых. С одной стороны, вы иногда слышите «Импортируйте свой сертификат в браузер» и «Используйте свой сертификат для входа в систему»; с другой стороны, вы также можете услышать «пришлите мне свой сертификат». Первый подразумевает использование и доступность закрытого ключа ("сертификат" может просто означать .p12 в этих выражениях). Последнее определенно не должно включать закрытый ключ.

    Пользовательские интерфейсы браузера, как правило, довольно плохие или запутанные для управления сертификатами или выхода из системы. Опять же, если сертификат не распознан, соединение SSL/TLS не будет установлено, поэтому веб-сервер не получит возможности отобразить HTML-страницу с ошибкой любого рода.

Возможно, вы также могли бы рассмотреть другие формы SSO (например, CAS, что-то на основе SAML или Kerberos/SPNEGO).

person Bruno    schedule 21.04.2012

У меня похожая проблема с:

  • ЦенОС 6.3
  • Апач 2.2.15

После некоторых попыток я узнаю свою проблему.

Если я устанавливаю SSLVerifyClient optional или SSLVerifyClient optional_no_ca, а также указываю SSLCACertificateFile или SSLCACertificatePath, Apache получает сертификат клиента только в том случае, если он выпущен из CA, найденного в справочном файле CA/пути, указанном в конфигурации.

person lgaggini    schedule 25.01.2013

Вы можете ознакомиться с документом по Apache, если это еще не сделано.

Общий принцип заключается в том, что вы создаете свой самозаверяющий сертификат и проверяете его, прежде чем пытаться его использовать.

Тогда похоже, что клиент подключается к вашему сайту интрасети через http. Оттуда есть много разных способов переключиться на https, используя ваш сертификат ssl. Самый простой способ — использовать модуль перезаписи apache. Но в вашем случае, когда вы выполняете проверки php/mysql, вы можете перенаправить своего клиента с http на https, что не так просто.

В любом из обоих случаев (автоматическое перенаправление apache через mod_rewrite или перенаправление с помощью каскадных тестов (php/javascript/html) вам необходимо правильно настроить 2 виртуальных хоста (один для http и один для https), но это предполагает какая-то гипотеза.

Например (debian - apache 2.2), вот автоматическое перенаправление, сделанное Apache (например, 1-й случай, описанный выше):

кошка /etc/apache2/сайты доступны/тест

# VHOST test

<VirtualHost *:80>

    DocumentRoot /home/www/test
    ServerName www.test.dev

    # ######################
    # Redirect commons
    # ######################
    RewriteEngine on
    # Case of vhosts
    RewriteOptions Inherit

    # ######################
    # Redirect (empty index)
    # ######################

    # Condition 1 to redirect : request matching with the server
    RewriteCond %{HTTP_HOST}   ^www\.test\.dev [NC]

    # Condition 2 to redirect : non empty HOST
    RewriteCond %{HTTP_HOST}   !^$

    # Automatic Empty requests Redirect
    RewriteRule ^(.*)/$ /index.php


    # ######################
    # Redirect to SSL
    # ######################
    RewriteCond %{HTTP_HOST}   ^www\.test\.dev [NC]
    RewriteCond %{HTTP_HOST}   !^$
    RewriteCond %{SERVER_PORT} ^80$
    RewriteCond %{REQUEST_URI} /

    # Redirection
    RewriteRule ^/(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R]

</VirtualHost>

Второй виртуальный хост для SSL: cat /etc/apache2/sites-available/test-ssl

# VHOST for ssl

DocumentRoot "/home/www/test"
ServerName www.test.dev

    # SSL
    SSLEngine on
    SSLCACertificateFile /etc/apache2/ssl/cur_cert/ca.pem
    SSLCertificateFile /etc/apache2/ssl/cur_cert/serveur.pem
    SSLCertificateKeyFile /etc/apache2/ssl/cur_cert/serveur.key


    <Directory "/home/www/test">
        Options FollowSymLinks MultiViews
        AllowOverride None
        Order allow,deny
        Allow from 127.0.0.1 192.168.0.0/16
    </Directory>


    <Directory "/home/www/test/cgi-bin">
        Options FollowSymLinks MultiViews
        AllowOverride None
        Order allow,deny
        Allow from 127.0.0.1 192.168.0.0/16
        Options +ExecCGI
        AddHandler cgi-script .cgi
    </Directory>

</VirtualHost>

Your case might defer slightly from this, eg you will not have the redirect portion in the 1st vhost, but only a simple vhost and the second one for https (ssl). The redirection will be done by php/javascript once you have achieved your mysql checks.

Вот пример абстракции от класса php для способа каскадного переключения с http на https, используя php, затем javascript, затем html:

public function Redirect($url){

    if (TRUE !== Validator::isValidURL($url))
        die ("FATAL ERR: url not valid");

    // PHP ABSOLUTE URL REDIRECT (HTTP1.1)
    if (!headers_sent()) {

        header("Status: 200");
        header("Cache-Control: no-cache, must-revalidate"); // required for HTTP/1.1
        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // past Date
        header("Pragma: no-cache");
        header('Location: '.$url); // note: 302 code return by default with "Location"
        flush();
        exit();

        // if headers are already sent... do javascript redirect... if javascript is disabled, do html redirect.
    } else {

        // Js redirect
        echo '<script type="text/javascript">';
        //echo "<!--";
        echo 'document.location="'. $url .'";';
        //echo "//-->";
        echo '</script>';

        // HTML redirect if js disabled
        echo '<noscript>';
        echo '<meta http-equiv="refresh" content="0;url="'.$url.'" />';
        echo '</noscript>';

        exit();
    }

    return FALSE;

} /* end of method (redirect) */

Надеюсь, это поможет вам лучше понять, как действовать и адаптировать этот подход к вашему конкретному случаю.

person hornetbzz    schedule 20.04.2012
comment
Но проблема, с которой я сталкиваюсь, не связана с перенаправлением. Я захожу на страницу напрямую через https. т.е. в браузере я использую https://. Так что порт 80 не проблема. - person Kris Craig; 21.04.2012
comment
Я попытался создать сертификат клиента на сервере с помощью openssl, экспортировать как p12, а затем установить его в Firefox, но теперь я получаю сообщение об ошибке «Невозможно получить сертификат локального эмитента» от Apache. Я подписал сертификат клиента, используя сертификат сервера, так почему же он не работает? Я попытался раскомментировать строку SSLCACertificateFile в ssl.conf, но затем это просто вернуло меня к первой проблеме: страница загружалась нормально, но без распознавания сертификата клиента. - person Kris Craig; 21.04.2012
comment
Извините, теперь я лучше понимаю вашу точку зрения, и у меня нет другого простого решения, кроме подключения http и переключения https после того, как проверки mysql были выполнены после входа в систему, что выглядит как более безопасный подход для меня, но по опыту довольно большой потребитель кода. - person hornetbzz; 21.04.2012
comment
Это не имеет никакого отношения к вопросу. - person paf.goncalves; 19.06.2014