delphi ssl openssl certificate indy

delphi - Cómo verificar el nombre de host del servidor



ssl openssl (2)

Desafortunadamente, tengo que apegarme a XE2-Indy y OpenSSL V1.0.1m debido a las especificaciones internas.

Para verificar el nombre de host contra el CN ​​sujeto y los nombres alternativos del sujeto, hice lo siguiente (usando el enfoque de la implementación de cURL ):

1. Al inicio de la aplicación, intento una vez extender el acceso a los métodos dentro de la biblioteca crypto de 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 siguiente clase me ayuda a acceder y verificar la SAN y 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. En el evento OnVerifyPeer del componente TIdSSLIOHandlerSocketOpenSSL, la clase se puede usar de la siguiente manera:

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

Estoy usando Indy TIdHTTP (incluido con XE2) y las DLL de la biblioteca OpenSSL V1.0.1m para verificar un certificado cuando me conecto a través de HTTPS. Implementé un controlador de eventos para el evento TIdSSLIOHandlerSocketOpenSSL componente TIdSSLIOHandlerSocketOpenSSL .

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

De acuerdo con RFC 2818, capítulo 3.1., Si el nombre de host está disponible para el cliente, el cliente DEBE verificarlo con la identidad del servidor tal como se presenta en el mensaje de Certificado del servidor, para evitar ataques de hombre en el medio.

Ahora tengo un problema para verificar el nombre de host del certificado del servidor:

Aunque un comodín está presente en el campo Nombre común (CN) en el campo Asunto dentro del certificado del servidor (* .google.com), el parámetro Certificate.Subject.OneLine del evento OnVerifyPeer devuelve el CN ​​sin ningún comodín (es decir, google. com en lugar de * .google.com).

Como se indica en RFC 2818, capítulo 3.1. el carácter comodín * se usa para unir cualquier componente de nombre de dominio o fragmento de componente.

  1. ¿Alguien puede confirmar que Indy o las bibliotecas OpenSSL eliminan el carácter comodín, aunque es necesario verificar el nombre de host?

  2. ¿Alguien tiene una idea para verificar el nombre de host en estas circunstancias?

Cualquier ayuda es muy apreciada. Gracias por leer.


¿Alguien puede confirmar que Indy o las bibliotecas OpenSSL eliminan el carácter comodín, aunque es necesario verificar el nombre de host?

No, OpenSSL no lo elimina.

No sé sobre la biblioteca de Indy.

¿Alguien puede confirmar que Indy o las bibliotecas OpenSSL eliminan el carácter comodín, aunque es necesario verificar el nombre de host?

Estoy citando esto dos veces por una razón :) Poner nombres de servidor en el Nombre común (CN) está en desuso tanto en el IETF y CA / B Forums (lo que siguen los navegadores).

Lo que probablemente experimentes es algo así como CN=example.com . En este caso, example.com no es un nombre de servidor; más bien es un dominio. Por lo tanto, no debe asumir que significa hacer coincidir *.example.com .

Y si un servidor responde en https://example.com , solo debe aceptar el certificado si el Nombre alternativo del sujeto incluye example.com porque las CA públicas enumeran los dominios en el CN. Las CA públicas colocan nombres DNS en la SAN porque siguen los foros CA / B.

¿Alguien tiene una idea para verificar el nombre de host en estas circunstancias?

OpenSSL anterior a 1.1.0 no realizó la coincidencia de nombre de host. El desarrollador tuvo que hacerlo. OpenSSL 1.1.0 y superior tiene la funcionalidad integrada. Consulte X509_check_host(3) y amigos.

Para hacer coincidir un nombre de host, debe reunir todos los nombres tanto del Nombre común (CN) como del Nombre alternativo del sujeto (SAN) . Entonces, es generalmente tan simple como una coincidencia de expresiones regulares.

El IETF es rápido y flexible, y permiten que un nombre de host aparezca en el CN ​​o SAN. El CA / B Forum y los navegadores son más estrictos: si un nombre de host está en el CN, entonces también debe estar presente en la SAN (sí, debe aparecer dos veces). De lo contrario, el foro de CA / B y los navegadores esperan todos los nombres de host en la SAN.

Creo que OpenSSL y los foros de CA / B solo permiten un comodín en la etiqueta situada más a la izquierda. Creo que el IETF permite que los comodines se muestren en cualquier lugar.

Si desea ver el código de muestra, entonces consulte la implementación de cURL. cURL usa OpenSSL, pero no depende de X509_check_host(3) ''s X509_check_host(3) y amigos. cURL tiene su propia implementación.

Una advertencia rápida. La coincidencia de nombre de host es un arte negro. Por ejemplo....

El IETF permite la coincidencia con un dominio de nivel superior global (gTLD) como *.com o *.net ; y dominio de nivel superior nacional (ccTLD) como *.uk o *.us . Considero que esto es un ataque porque sé que no existe una CA única que pueda afirmar que es "propietario" o "certifica" un gTLD. Si experimento uno de esos certs en la naturaleza, entonces lo rechazo.

Los Foros CA / B no permiten gTLD o ccTLD de comodines. Los navegadores intentan evitarlo mediante el uso de Public Suffix List (PSL). Las cosas solo han empeorado con los dominios de tocador, como *.google .

Hay otra cosa que los navegadores intentan hacer con el PSL. Intentan delimitar los límites administrativos en los subdominios. Por ejemplo, Amazon posee todo amazon.com, pero delegan autoridad a los subdominios, como example.amazon.com. Por lo tanto, la PSL intenta permitir que Amazon controle su dominio amazon.com , pero no su subdominio relacionado con el comerciante de example.amazon.com .

El IETF está tratando de abordar los límites administrativos en el Grupo de Trabajo DBOUND . Pero las cosas parecen estar estancadas en el comité.