Как читать веб-сайты сертификатов TLS с помощью PHP?

Я пытаюсь подключиться к защищенному websocket, созданному PHP, но по какой-то причине он не работает. Файлы сертификатов читаются для PHP.

Это мой код до сих пор (сторона PHP, удаленный код для простоты):

$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'allow_self_signed', false);
stream_context_set_option($context, 'ssl', 'verify_peer', true);
stream_context_set_option($context,  'ssl', 'peer_name', 'example.com');
stream_context_set_option($context,  'ssl', 'CN_match', 'example.com');
stream_context_set_option($context,  'ssl', 'SNI_enabled', true);
stream_context_set_option($context, 'ssl', 'local_cert',  '/path/to/ssl/cert/example.com');
stream_context_set_option($context, 'ssl', 'local_pk', '/path/to/ssl/private/example.com');

$serverSocket = stream_socket_server(
        'tls://example.com:8090',
        $errNo,
        $errStr,
        \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
        $context
);
$client = stream_socket_accept($serverSocket);

// send initial websocket connection stuff
$request = socket_read($client, 5000);
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack(
    'H*',
    sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
));
$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= 'Sec-WebSocket-Version: 13' . "\r\n";
$headers .= 'Sec-WebSocket-Accept: ' . $key . "\r\n\r\n";
socket_write($client, $headers, \mb_strlen($headers));

// do something here...

socket_close($client);
socket_close($serverSocket);

Клиентская сторона:

var con = new WebSocket('wss://' + host + ':' + port);
var $chat = $('#chat');

con.onmessage = function(e) {
    $chat.append('<p>' + e.data + '</p>');
};

con.onopen = function(e) {
    con.send('Hello Me!');
};

con.onclose = function (e) {
    console.log('connection closed.', arguments);
}

У меня нет файла *.pem. Просто два файла, которые используются на веб-сервере Apache. При необходимости можно было бы преобразовать эти файлы в файл pem. Но я думаю, что он также должен работать в php с этими обоими файлами, не так ли?

Для лучшего тестирования мы используем изолированный субдомен с сертификатом let encrypt. Потому что мы получили полный доступ к этому серверу. Однако из генератора certifacte я получил только эти два упомянутых файла. Для веб-сервера он работает отлично. Но как сделать то же самое для websocket в php?

Теперь, с помощью этого кода, после отправки некоторых сообщений клиенту серверный скрипт говорит мне, что он не смог получить партнера из сертификатов. Я не знаю, что это означает, и как это исправить. Я также пытался обменять local_cert и local_pk друг на друга, но это все равно не помогло.

Edit: после некоторого исследования выясняется, что php терпит неудачу с каждой комбинацией с другой ошибкой.

Мои созданные файлы сертификатов выглядят следующим образом:

Файл /opt/psa/var/certificates/cert-0x8zHR:

-----BEGIN CERTIFICATE REQUEST-----
some letters
-----END CERTIFICATE REQUEST-----

-----BEGIN PRIVATE KEY-----
some letters
-----END PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

Файл /opt/psa/var/certificates/cert-Du9H8N:

-----BEGIN CERTIFICATE-----
some letters
-----END CERTIFICATE-----

Все строки из строк сертификата из обоих из них заканчиваются в позиции символа 65.

Как вы можете прочитать в php-документации, php ожидает получить файл в формате PEM: http://php.net/manual/en/context.ssl.php#context.ssl.local-cert

Я нашел этот учебник для преобразования двух моих файлов в один файл PEM, но мои сертификаты выглядят иначе, чем упоминалось на сайте, а также я не знаю, что именно включить в файл PEM, который нужен php: https://www. digicert.com/ssl-support/pem-ssl-creation.htm

Изменить 2: Как упоминалось ниже, вот точные эры, которые я получаю:

с

stream_context_set_option($cont, 'ssl',  'local_pk', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'local_cert', '/opt/psa/var/certificates/cert-Du9H8N');

Предупреждение: stream_socket_accept(): невозможно установить файл закрытого ключа '/opt/psa/var/certificates/cert-0x8zHR' в... on line...

Предупреждение: stream_socket_accept(): Не удалось включить криптографию... в строке...

Предупреждение: stream_socket_accept(): accept failed: Success in... on line...

Предупреждение: socket_read() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Примечание: Неопределенное смещение: 1 в... на линии...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_close() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

И с

stream_context_set_option($cont, 'ssl',  'local_cert', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'local_pk', '/opt/psa/var/certificates/cert-Du9H8N');

Предупреждение: stream_socket_accept(): невозможно установить файл закрытого ключа '/opt/psa/var/certificates/cert-Du9H8N' в... on line...

Предупреждение: stream_socket_accept(): Не удалось включить криптографию... в строке...

Предупреждение: stream_socket_accept(): accept failed: Success in... on line...

Предупреждение: socket_read() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Примечание: Неопределенное смещение: 1 в... на линии...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_close() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

И с (просто конкатенация cert-0x8zHR с сертификатом-Du9H8N)

$file = dirname(__FILE__, 3) . \DIRECTORY_SEPARATOR . 'fullchain.pem';
stream_context_set_option($cont, 'ssl', 'local_cert', $file);

Предупреждение: stream_socket_accept(): не удалось получить сертификат одноранговой сети...

Предупреждение: stream_socket_accept(): Не удалось включить криптографию в......

Предупреждение: stream_socket_accept(): accept failed: Успех в......

Предупреждение: socket_read() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Примечание: Неопределенное смещение: 1 в... на линии...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в......

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в......

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в......

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в......

Предупреждение: socket_write() ожидает, что параметр 1 будет ресурсом, boolean задан в......

Предупреждение: socket_close() ожидает, что параметр 1 будет ресурсом, boolean задан в... on line...

Редактировать 3: Да, действительно, есть ошибка openssl. После третьего предупреждения socket_stream_accept теперь я получаю эту ошибку, просто используя код из примера php doc:

error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch

Я искал эту ошибку в Интернете. Говорят, если эта ошибка появляется, я выбрал неверный файл сертификата.

Кроме того, если я играю эту команду:

openssl x509 -noout -in cert-0x8zHR -modulus

Я получил две разные строки для модуля. Но я не знаю, почему, и как это исправить. Эти оба файла используются в конфигурации vhost для веб-сервера Apache и отлично работают:

SSLEngine on
SSLVerifyClient none
SSLCertificateFile /opt/psa/var/certificates/cert-0x8zHR
SSLCACertificateFile /opt/psa/var/certificates/cert-Du9H8N

PS: Если вы знаете какой-либо инструмент для локального преобразования в файл pem, пожалуйста, дайте мне знать. Онлайн-конвертация невозможна.

Ответ 1

Вы сказали:

Мои созданные файлы сертификатов выглядят следующим образом:

Файл /opt/psa/var/certificates/cert-0x8zHR:

-----BEGIN СЕРТИФИКАТ REQUEST----- некоторые буквы -----END СЕРТИФИКАТ REQUEST-----

-----BEGIN ЧАСТНЫЙ KEY----- несколько писем -----END ЧАСТНЫЙ KEY-----

-----BEGIN CERTIFICATE----- несколько букв -----END CERTIFICATE-----

-----BEGIN CERTIFICATE----- несколько букв -----END CERTIFICATE-----

Это не правильно на многих уровнях.

Сначала часть "CERTIFICATE REQUEST" полностью бесполезна, как только вы получите сертификат. Вы можете просто проигнорировать эту часть.

Теперь скопируйте часть "ЧАСТНЫЙ КЛЮЧ" с заголовками в один файл. Это ваш личный ключ, вы можете использовать везде, где есть опции "local_pk" или программное обеспечение, запрашивающее ключ.

Тогда у вас есть два сертификата. И еще один в другом файле. Вам нужно будет отсортировать все это. Если вы дали реальный контент сертификатов (которые являются общедоступным контентом), люди могли бы помочь вам лучше. Здесь, только с "буквами", мы можем только догадываться.

В вышеупомянутом файле два сертификата могут быть CA и промежуточными. Вы должны скопировать их в другой файл, и это будет соответствовать опции "ca_cert".

Я предполагаю, что второй файл - ваш тестовый сертификат (только догадка, без ваших данных). Используйте его везде, где есть "local_cert" или запрашиваете сертификат. Этот сертификат должен соответствовать файлу закрытого ключа (вы не можете проверить, что вручную, вам нужна инструментальная обработка).

После создания всех правильных файлов первый из них больше не будет использоваться. Я бы рекомендовал использовать лучшую семантику для имени файла, потому что cert-X и cert-Y будут довольно сложными. Вместо этого используйте имя веб-сайта, чтобы у вас были такие файлы, как www.example.com.cert, www.example.com-ca.cert и www.example.com.key, так что сразу же понятно, что к чему. Или с каталогом www.example.com/, а затем cert.pem, ca.pem и key.pem внутри каталога. Расширения сами по себе не используются программным обеспечением и, независимо от содержания, вам решать только то, что имеет смысл.

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

Если возможно, я также рекомендую вам попробовать использовать библиотеку абстракций более высокого уровня для обработки TLS, так как это явно слишком мало, и вы обнаружите так много параметров, которые вы теряете.

Ответ 2

Похоже, OpenSSL не нравится ваш ключ.

Вы можете попробовать позвонить http://php.net/manual/en/function.openssl-error-string.php, чтобы узнать, есть ли информация о том, почему?

Или попробуйте различные комбинации файлов ключей, в зависимости от этих, возможно, связанных вопросов, которые имеют ту же ошибку:

GL

Ответ 3

Попробуй это:

stream_context_set_option($cont, 'ssl', 'local_cert', '/opt/psa/var/certificates/cert-0x8zHR');
stream_context_set_option($cont, 'ssl', 'ca_file', '/opt/psa/var/certificates/cert-Du9H8N');

Если это не сработает, попробуйте прокомментировать вторую строку (ca_flie) и установите для параметра verify_peer значение false

Что такое сообщения об ошибках в обоих случаях?

Обновление: BTW, ваши сертификаты уже находятся в .pem (ASCII-base64-encoded).