CONNECT запрос на перенаправление HTTP-прокси по SSL-соединению?
Я пишу HTTP-прокси, и мне трудно понять некоторые детали запроса CONNECT по TLS. Чтобы получить лучшую картину, я экспериментирую с Apache, чтобы наблюдать, как она взаимодействует с клиентами. Это от моего виртуального хоста по умолчанию.
NameVirtualHost *:443
<VirtualHost>
ServerName example.com
DocumentRoot htdocs/example.com
ProxyRequests On
AllowConnect 22
SSLEngine on
SSLCertificateFile /root/ssl/example.com-startssl.pem
SSLCertificateKeyFile /root/ssl/example.com-startssl.key
SSLCertificateChainFile /root/ssl/sub.class1.server.ca.pem
SSLStrictSNIVHostCheck off
</VirtualHost>
Разговор между Apache и моим клиентом происходит следующим образом.
а. клиент подключается к example.com:443
и отправляет example.com
в квитирование TLS.
б. клиент отправляет HTTP-запрос.
CONNECT 192.168.1.1:22 HTTP/1.1
Host: example.com
Proxy-Connection: Keep-Alive
с. Apache говорит HTTP/1.1 400 Bad Request
. В журнале ошибок Apache говорится:
Hostname example.com provided via SNI and hostname 192.168.1.1
provided via HTTP are different.
Похоже, что Apache не смотрит на заголовок Host, а не видит, что он существует, поскольку HTTP/1.1 требует его. Я получаю идентичное неудачное поведение, если клиент отправляет Host: foo
. Если я сделаю HTTP-запрос example.com:80 без TLS, тогда Apache подключит меня к 192.168.1.1:22.
Я не совсем понимаю это поведение. Что-то не так с запросом CONNECT? Кажется, я не могу найти соответствующие части RFC, которые объясняют все это.
Ответы
Ответ 1
Не ясно, пытаетесь ли вы использовать Apache Httpd в качестве прокси-сервера, это объясняет код статуса 400, который вы получаете.
CONNECT
используется клиентом и отправляется на прокси-сервер (возможно, Apache Httpd, но обычно нет), а не на целевой веб-сервер.
CONNECT
используется между клиентом и прокси-сервером до установления соединения TLS между клиентом и конечным сервером. Клиент (C) подключается к прокси (P) proxy.example.com
и отправляет этот запрос (включая пустую строку):
C->P: CONNECT www.example.com:443 HTTP/1.1
C->P: Host: www.example.com:443
C->P:
Прокси открывает TCP-соединение с www.example.com:443
(P-S) и отвечает клиенту кодом состояния 200, принимая запрос:
P->C: 200 OK
P->C:
После этого соединение между клиентом и прокси-сервером (C-P) остается открытым. Прокси-сервер передает все соединения C-P на P-S и обратно. Клиент обновляет свое активное (P-S) соединение с соединением SSL/TLS, инициируя квитирование на TLS на этом канале. Поскольку все теперь передано на сервер, это как если бы обмен TLS выполнялся непосредственно с www.example.com:443
.
Прокси не играет никакой роли в рукопожатии (и, следовательно, с SNI). Рукопожатие TLS эффективно происходит непосредственно между клиентом и конечным сервером.
Если вы пишете прокси-сервер, все, что вам нужно сделать, чтобы позволить вашим клиентам подключаться к серверам HTTPS, читается в запросе CONNECT
, делает соединение с прокси-сервером на конечном сервере (данное в CONNECT
), отправьте клиенту с ответом 200 OK
, а затем переместите все, что вы прочитали с клиента на сервер, и наоборот.
RFC 2616 рассматривает CONNECT
как способ установить простой туннель (который он есть). Об этом больше говорится в RFC 2817, хотя остальная часть RFC 2817 (обновления до TLS в не-прокси-HTTP-соединении) редко используется.
Похоже, что вы пытаетесь установить соединение между клиентом (C) и прокси (P) через TLS. Это хорошо, но клиент не будет использовать CONNECT
для подключения к внешним веб-серверам (если это не соединение с сервером HTTPS тоже).
Ответ 2
Из RFC 2616 (раздел 14.23):
Поле заголовка запроса хоста указывает хост и порт Интернета номер запрашиваемого ресурса, полученный из оригинала URI, предоставленный пользователем или ссылочным ресурсом (как правило, URL-адрес HTTP, как описано в разделе 3.2.2). Значение поля Host должно ДОЛЖНО представлять именования сервера происхождения или шлюза, предоставленного исходный URL.
Я понимаю, что вам нужно скопировать адрес из строки CONNECT в строку HOST. В общем, адрес ресурса - 192.168.1.1, а тот факт, что вы подключаетесь через example.com, ничего не меняет с точки зрения RFC.
Ответ 3
Ты делаешь все правильно. Это Apache, у которого что-то не так. Поддержка CONNECT over TLS была добавлена только недавно (https://issues.apache.org/bugzilla/show_bug.cgi?id=29744), и еще есть кое-что, что нужно сгладить. Проблема, с которой вы сталкиваетесь, является одним из них.
Ответ 4
Довольно редко можно увидеть метод CONNECT внутри TLS (https). Я действительно не знаю ни одного клиента, который это делает (и мне было бы интересно узнать, кто это делает, потому что я думаю, что это действительно хорошая функция).
Обычно клиент соединяется с http (простой tcp) с прокси-сервером и отправляет метод CONNECT (и заголовок узла) на хост: 443. Затем прокси сделает прозрачное соединение с конечной точкой, а затем клиент отправит квитирование SSL через.
В этом случае данные защищены ssl "от конца до конца".
Метод CONNECT на самом деле не указан, он зарезервирован только в HTTP RFC. Но, как правило, это довольно просто, поэтому он совместим. Метод определяет хост [: порт]. Хост: заголовок можно просто игнорировать. Могут потребоваться некоторые дополнительные заголовки аутентификации прокси. Когда тело соединения начинается, синтаксический анализ больше не должен выполняться прокси-сервером (некоторые из них, потому что проверяют правильное SSL-подтверждение).