2015-05-25 2 views
5

Я использую Indy TIdHTTP (поставляется с XE2) и DLL библиотеки OpenSSL V1.0.1m для проверки сертификата при подключении через HTTPS. Я применил обработчик событий для события OnVerifyPeer компонента TIdSSLIOHandlerSocketOpenSSL.Как проверить имя хоста сервера

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509; 
    AOk: Boolean; ADepth, AError: Integer): Boolean; 
begin 
    (...) 
end; 

В соответствии с RFC 2818, глава 3.1., Если имя хоста доступен для клиента, клиент должен проверить его против подлинности сервера в качестве , представленного в сообщении сертификата сервера, чтобы предотвратить Атаки "человек-в-середине".

Теперь у меня есть проблема, чтобы проверить имя хоста сертификата сервера:

Хотя подстановочные присутствует в Common Name (CN) поля в поле Тема в сертификате сервера (* .Google .com), параметр Certificate.Subject.OneLine события OnVerifyPeer возвращает CN без какого-либо шаблона (например, google.com вместо * .google.com).

Как указано в RFC 2818, глава 3.1. символ подстановки * используется для соответствия любому компоненту одного домена или фрагменту компонента.

  1. Может кто-нибудь подтвердить, что символ подстановки удаляется Инди или библиотеки OpenSSL, хотя нужно проверить имя хоста?

  2. У кого-нибудь есть идея проверить имя хоста при таких обстоятельствах?

Любая помощь очень ценится. Спасибо за прочтение.

+0

Данные сертификата обеспечивается самим OpenSSL. Класс Indy 'TIdX509' просто обертывает дескриптор' PX509', предоставляемый OpenSSL внутри функции обратного вызова проверки Indy. 'TIdX509' не возится с данными сертификата, он представляет его как есть. Свойство 'Subject' переносит дескриптор' PX509_NAME' из функции 'X509_get_subject_name()' OpenSSL, а свойство 'OneLine' возвращает любое значение, возвращаемое функцией OpenSSL' X509_NAME_oneline() '. Таким образом, сам OpenSSL лишает шаблон. –

+1

Как сказано, OpenSSL имеет функции 'X509_check_host()' и 'cert_host_name_override()'. Вы можете передать им оригинальный дескриптор 'PX509' (член' TIdX509.FX509' - для этого вам нужно будет использовать класс accessor) и имя хоста, к которому вы подключились. –

+0

Спасибо Реми за быстрый ответ. Я попробую это завтра и сообщит вам о моих результатах. – Cheesy

ответ

1

К сожалению, я должен придерживаться XE2-Indy и OpenSSL V1.0.1m из-за внутренних спецификаций.

Чтобы проверить имя хоста против субъекта CN и Subject Alternate Names, я сделал следующее (с использованием подхода cURL's implementation):

1. При запуске приложения я пытаюсь однажды расширить доступ к методам в криптографической библиотеке Indy.

function ExtendIndyCryptoLibrary(): Boolean; 
var 
    hIdCrypto: HMODULE; 
begin 
    Result := False; 

    // Try to get handle to Indy used crypto library 
    if not IdSSLOpenSSL.LoadOpenSSLLibrary() then 
    Exit; 
    hIdCrypto := IdSSLOpenSSLHeaders.GetCryptLibHandle(); 
    if hIdCrypto = 0 then 
    Exit(); 

    // Try to get exported methods that are needed additionally 
    @X509_get_ext_d2i := GetProcAddress(hIdCrypto, 'X509_get_ext_d2i'); 

    Result := Assigned(X509_get_ext_d2i); 
end; 

2. follwing класс помогает мне получить доступ и проверить SAN и CN.

type 
    THostnameValidationResult = (hvrMatchNotFound, hvrNoSANPresent, hvrMatchFound); 
var 
    X509_get_ext_d2i: function(a: PX509; nid: TIdC_INT; var pcrit: PIdC_INT; var pidx: PIdC_INT): PSTACK_OF_GENERAL_NAME; cdecl = nil; 
type 
    TIdX509Access = class(TIdX509) 
    protected 
    function Hostmatch(Hostname, Pattern: String): Boolean; 
    function MatchesSAN(Hostname: String): THostnameValidationResult; 
    function MatchesCN(Certificate: TIdX509; Hostname: String): THostnameValidationResult; 
    public 
    function ValidateHostname(Certificate: TIdX509; Hostname: String): THostnameValidationResult; 
    end; 

implementation 

{ TIdX509Access } 

function TIdX509Access.Hostmatch(Hostname, Pattern: String): Boolean; 
begin 
    // Match hostname against pattern using RFC, CA/Browser Forum, ... 
    // (...) 
end; 

function TIdX509Access.MatchesSAN(Hostname: String): THostnameValidationResult; 
var 
    pcrit, pidx: PIdC_INT; 
    psan_names: PSTACK_OF_GENERAL_NAME; 
    san_names_nb: Integer; 
    pcurrent_name: PGENERAL_NAME; 
    i: Integer; 
    DnsName: String; 
begin 
    Result := hvrMatchNotFound; 

    // Try to extract the names within the SAN extension from the certificate 
    pcrit := nil; 
    pidx := nil; 
    psan_names := X509_get_ext_d2i(FX509, NID_subject_alt_name, pcrit, pidx); 
    // Check if SAN is present 
    if psan_names <> nil then 
    begin 
    san_names_nb := sk_num(PSTACK(psan_names)); 
    // Check each name within the extension 
    for i := 0 to san_names_nb-1 do 
    begin 
     pcurrent_name := PGENERAL_NAME(sk_value(PSTACK(psan_names), i)); 
     if pcurrent_name._type = GEN_DNS then 
     begin 
     // Current name is a DNS name, let's check it 
     DnsName := String(pcurrent_name.d.dNSName.data); 
     // Compare expected hostname with the DNS name 
     if Hostmatch(Hostname, DnsName) then 
     begin 
      Result := hvrMatchFound; 
      Break; 
     end; 
     end; 
    end; 
    end 
    else 
    Result := hvrNoSANPresent; 
    // Clean up 
    sk_free(PSTACK(psan_names)); 
end; 

function TIdX509Access.MatchesCN(Certificate: TIdX509; 
    Hostname: String): THostnameValidationResult; 
var 
    TempList: TStringList; 
    Cn: String; 
begin 
    Result := hvrMatchNotFound; 

    // Extract CN from Subject 
    TempList := TStringList.Create(); 
    TempList.Delimiter := '/'; 
    TempList.DelimitedText := Certificate.Subject.OneLine; 
    Cn := Trim(TempList.Values['CN']); 
    FreeAndNil(TempList); 

    // Compare expected hostname with the CN 
    if Hostmatch(Hostname, Cn) then 
    Result := hvrMatchFound; 
end; 

function TIdX509Access.ValidateHostname(Certificate: TIdX509; 
    Hostname: String): THostnameValidationResult; 
begin 
    // First try the Subject Alternative Names extension 
    Result := MatchesSAN(Hostname); 
    if Result = hvrNoSANPresent then 
    begin 
    // Extension was not found: try the Common Name 
    Result := MatchesCN(Certificate, Hostname); 
    end; 
end; 

3. В случае OnVerifyPeer компонента TIdSSLIOHandlerSocketOpenSSL, класс может быть использован следующим образом:

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509; 
    AOk: Boolean; ADepth, AError: Integer): Boolean; 
begin 
    // (...) 
    Result := TIdX509Access(Certificate).ValidateHostname(Certificate, IdHttp1.URL.Host) = hvrMatchFound; 
    // (...) 
end; 
+0

@RemyLebeau: Я возился с Indy внутри, используя этот подход? До сих пор у меня не было проблем. Я просто хочу, чтобы другие люди делали что-то неправильно, используя эту реализацию. – Cheesy

+0

Спасибо за подсказки. Хотя я принял ответ, я все равно хотел бы получить ответ от Реми. – Cheesy

+0

Будьте осторожны с доменом в 'CN', что довольно распространено. То есть вы столкнетесь с «CN = example.com». В этом случае 'example.com' является *** доменом ***, а не *** именем *** сервера. Вы не должны интерпретировать его как host 'example.com' или все хосты в домене' * .example.com'. Имена серверов переходят в 'Subject Alternate Name (SAN)'. – jww

3

Может ли кто-нибудь подтвердить, что символ подстановки удаляется Indy или библиотеками OpenSSL, хотя необходимо проверить имя хоста?

Нет, OpenSSL не удаляет его.

Я не знаю о библиотеке Indy.


Может кто-нибудь подтвердить, что символ подстановки удаляется Инди или библиотеки OpenSSL, хотя нужно проверить имя хоста?

Я со ссылкой на это дважды по причине :) Размещение имен серверов в Common Name (CN) является устаревшим как IETF и форумах CA/B (какие браузеры следуют).

Что вы, вероятно, испытываете, это что-то вроде CN=example.com.В этом случае example.comне является имя сервера; скорее это is домен. Итак, вы не должны предположим, что это означает, что оно соответствует *.example.com.

И если сервер отвечает на https://example.com, вы должны принимать только сертификат, если альтернативного имя включает example.com потому что домены указаны в CN общественного УЦ. Публичные ЦС помещают имена DNS в SAN, поскольку они следуют за форумами CA/B.


кто-нибудь идею, чтобы проверить имя хоста в этих условиях?

OpenSSL до 1.1.0 не выполнить имя хоста сопоставление. Разработчик должен был это сделать. OpenSSL 1.1.0 и выше имеют встроенную функциональность. См. X509_check_host(3) и друзья.

Чтобы соответствовать имя хоста, вы должны собрать все имена из какCommon Name (CN) и Subject Alternate Name (SAN). Затем, как правило, это просто, как сопоставление с регулярным выражением.

IETF быстро и свободно, и они позволяют указать имя хоста в CN или SAN. Форум CA/B и браузеры более строгие: если имя хоста находится в CN, оно также должно присутствовать в SAN (да, оно должно быть указано дважды). В противном случае форум CA/B и браузеры ожидают, что все имена хостов в SAN.

Я считаю, что OpenSSL и CA/B Forums допускают только подстановочный знак на самой левой метке. Я считаю, что IETF позволяет показывать символы в любом месте.

Если вы хотите увидеть пример кода, то проверьте реализацию cURL. cURL использует OpenSSL, но не зависит от 1.1.0's X509_check_host(3) и друзей. cURL имеет свою собственную реализацию.


Быстрое предупреждение. Соответствие имени хоста - это черное искусство. Например ...

IETF позволяет сопоставлять глобальный домен верхнего уровня (gTLD), такой как *.com или *.net; и домен верхнего уровня страны (ccTLD), такой как *.uk или *.us. Я считаю, что это нападение, потому что я знаю, что нет единого центра сертификации, который может претендовать на «собственное» или «сертифицировать» gTLD. Если я испытываю один из этих сертификатов в дикой природе, то я его отвергаю.

Форумы CA/B не разрешают групповые gTLD или ccTLD. Браузеры пытаются избежать этого, используя Public Suffix List (PSL). Вещи только ухудшились с областями суеты, например *.google.

Есть еще одна вещь, которую браузеры пытаются сделать с PSL. Они пытаются вырезать административные границы на субдоменах. Например, Amazon владеет всем amazon.com, но они делегируют полномочия на субдомены, например example.amazon.com.Таким образом, PSL пытается разрешить Amazon управлять своим доменом amazon.com, но не связанным с продавцом субдоменом example.amazon.com.

IETF пытается решить административные границы в DBOUND Working Group. Но в комитете все кажется заторможенным.

+0

** Может ли кто-нибудь подтвердить, что символ подстановки удаляется Indy или библиотеками OpenSSL, хотя необходимо проверить имя хоста? ** Хотя я был совершенно уверен, что подстановочный знак был отключен в «Certificate.Subject.OnLine», это явление снова не повторилось (протестировано https://www.youtube.com). – Cheesy

Смежные вопросы