2015-05-25 19 views
5

Sto utilizzando Indy TIdHTTP (fornito con XE2) e le DLL della libreria OpenSSL V1.0.1m per verificare un certificato durante la connessione tramite HTTPS. Ho implementato un gestore di eventi per l'evento OnVerifyPeer del componente TIdSSLIOHandlerSocketOpenSSL.Come verificare il nome host del server

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

Secondo RFC 2818, capitolo 3.1., Se il nome host è disponibile per del cliente, il cliente deve verificare contro l'identità del server come presentato nel messaggio il certificato del server, al fine di prevenire attacchi man-in-the-middle.

Ora ho un problema di verificare il nome host del certificato del server:

Anche se un jolly è presente nel nome comune (CN) campo nel campo Oggetto entro il certificato del server (* .google .com), il parametro Certificate.Subject.OneLine dell'evento OnVerifyPeer restituisce il CN senza alcun carattere jolly (ad esempio google.com anziché * .google.com).

Come indicato nella RFC 2818, capitolo 3.1. il carattere jolly * viene utilizzato per corrispondere a qualsiasi singolo componente del nome di dominio o frammento di componente.

  1. qualcuno può confermare che il carattere jolly viene rimosso da Indy o le librerie OpenSSL, anche se è necessario verificare l'hostname?

  2. Qualcuno ha un'idea per verificare il nome host in queste circostanze?

Qualsiasi aiuto è molto apprezzato. Grazie per aver letto.

+0

I dati del certificato è fornito da OpenSSL stessa. La classe 'TIdX509' di Indy racchiude semplicemente un handle' PX509' fornito da OpenSSL all'interno della funzione di callback di verifica di Indy. 'TIdX509' non scherza con i dati del certificato, lo presenta così com'è. La proprietà 'Subject' racchiude un handle' PX509_NAME' dalla funzione 'X509_get_subject_name()' di OpenSSL, e la proprietà 'OneLine' restituisce qualsiasi valore restituisce la funzione' X509_NAME_oneline() 'di OpenSSL. Quindi è OpenSSL stesso che sta eliminando il carattere jolly. –

+1

Detto questo, OpenSSL ha le funzioni 'X509_check_host()' e 'certificate_host_name_override()'. Puoi passare loro l'handle 'PX509' originale (il membro' TIdX509.FX509' - dovresti usare una classe accessor per raggiungerlo comunque) e il nome host a cui ti sei connesso. –

+0

Grazie a Remy per la rapida risposta. Ci proverò domani e ti farò sapere i miei risultati. – Cheesy

risposta

1

Sfortunatamente devo attenermi a XE2-Indy e OpenSSL V1.0.1m a causa delle specifiche interne.

Per verificare il nome host contro il soggetto CN e nomi soggetto alternativi, ho fatto la seguente (utilizzando l'approccio cURL's implementation):

1. All'avvio dell'applicazione, sto cercando una volta per estendere l'accesso ai metodi all'interno della libreria crittografica di 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. La classe follwing mi aiuta ad accedere e verificare le SAN e 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. Nel caso OnVerifyPeer del componente TIdSSLIOHandlerSocketOpenSSL, la classe può essere utilizzato come segue:

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

@RemyLebeau: Sto incasinando internamente Indy usando questo approccio? Finora non ho avuto problemi. Voglio solo impedire che altre persone stiano facendo qualcosa di sbagliato usando questa implementazione. – Cheesy

+0

Grazie per i suggerimenti. Anche se ho accettato la risposta, mi piacerebbe comunque ricevere una risposta da Remy. – Cheesy

+0

Prestare attenzione con un dominio nel 'CN', che è abbastanza comune. Cioè, sperimenterai 'CN = example.com'. In questo caso, 'example.com' è un dominio *** ***, e non un *** nome server ***. Non dovresti interpretarlo come host 'example.com', o tutti gli host nel dominio' * .example.com'. I nomi dei server vanno in 'Subject Alternate Name (SAN)'. – jww

3

Qualcuno può confermare che il carattere jolly viene rimosso da Indy o dalle librerie OpenSSL, sebbene sia necessario verificare il nome host?

No, OpenSSL non lo rimuove.

Non so della libreria di Indy.


qualcuno può confermare che il carattere jolly viene rimosso da Indy o le librerie OpenSSL, anche se è necessario verificare l'hostname?

sto citando questo due volte per un motivo :) Posizionando i nomi dei server nel Common Name (CN) è sconsigliata sia dalla IETF e Forum CA/B (quello che i browser seguono).

Quello che probabilmente stai vivendo è qualcosa come CN=example.com.In questo caso, example.comnon è un nome server; piuttosto è è un dominio. Quindi tu non dovresti supporre che significhi corrispondere a *.example.com.

e se un server risponde a https://example.com, si dovrebbe solo accettare il certificato se il nome soggetto alternativo comprende example.com perché i domini sono elencati nel NC dal CA pubbliche. Le CA pubbliche inseriscono nomi DNS nella SAN perché seguono i forum CA/B.


Qualcuno ha un'idea per verificare il nome host in queste circostanze?

OpenSSL prima 1.1.0 fatto non eseguire corrispondenza nome host. Lo sviluppatore ha dovuto farlo. OpenSSL 1.1.0 e versioni successive ha la funzionalità integrata. Vedi X509_check_host(3) e gli amici.

per abbinare un nome host, è necessario raccogliere tutti i nomi da sia il nome comune (CN) e il nome soggetto alternativo (SAN). Quindi, di solito è semplice come un abbinamento di espressioni regolari.

IETF è veloce e loose e consente di visualizzare un nome host nel CN ​​o nella SAN. Il forum CA/B e i browser sono più rigidi: se un nome host è nel CN, deve essere presente anche nella SAN (sì, deve essere elencato due volte). In caso contrario, il forum CA/B e i browser prevedono tutti i nomi host nella SAN.

Credo che OpenSSL e il forum di CA/B consentano solo un carattere jolly nell'etichetta più a sinistra. Credo che IETF permetta ai caratteri jolly di comparire ovunque.

Se si desidera visualizzare il codice di esempio, verificare l'implementazione di cURL. cURL utilizza OpenSSL, ma non dipende da 1.1.0 di X509_check_host(3) e dagli amici. cURL ha una sua implementazione.


Un avviso rapido. La corrispondenza del nome host è un'arte nera. Ad esempio ....

IETF consente la corrispondenza con un dominio di primo livello globale (gTLD) come *.com o *.net; e Country Top Level Domain (ccTLD) come *.uk o *.us. Considero questo un attacco perché so che non esiste una singola CA che possa pretendere di "possedere" o "certificare" un gTLD. Se provo uno di quei certs in natura, allora lo rifiuto.

I forum CA/B non consentono gTLD o ccTLD con caratteri jolly. I browser tentano di evitarlo usando Public Suffix List (PSL). Le cose sono peggiorate solo con i domini vanity, come *.google.

C'è un'altra cosa che i browser tentano di fare con la PSL. Tentano di scavare i confini amministrativi sui sottodomini. Ad esempio, Amazon possiede tutto di amazon.com, ma delegano l'autorizzazione ai sottodomini, come example.amazon.com.Pertanto, il PSL tenta di consentire ad Amazon di controllare il proprio dominio amazon.com, ma non il sottodominio relativo al commerciante di example.amazon.com.

IETF sta tentando di affrontare i confini amministrativi nel DBOUND Working Group. Ma le cose sembrano essere bloccate in commissione.

+0

** Qualcuno può confermare che il carattere jolly sia stato rimosso da Indy o dalle librerie OpenSSL, sebbene sia necessario verificare il nome host? ** Anche se ero abbastanza sicuro che il carattere jolly fosse stato rimosso in "Certificate.Subject.OnLine", questo fenomeno non si è più verificato (testato con https://www.youtube.com). – Cheesy