How to check server host name

I use Indy TIdHTTP (comes with XE2) and the OpenSSL V1.0.1m DLL to verify the certificate when connecting via HTTPS. I implemented an event handler for the OnVerifyPeer event of the OnVerifyPeer component.

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

According to RFC 2818, chapter 3.1., If the host name is available to the client, the client MUST check it for server identity as presented in the server certificate message to prevent man-in-the-middle attacks.

Now I have a problem with checking the hostname of the server certificate:

Although there is a wildcard character in Common Name (CN), in the Subject field in the server certificate (* .google.com), the OnVerifyPeer event Certificate.Subject.OneLine parameter returns CN without a pattern (for example, google.com instead of * .google.com) .

As indicated in RFC 2818, chapter 3.1. the wildcard character * is used to match any component of the same domain or component fragment.

  • Can anyone confirm that the wildcard is removed by Indy or the OpenSSL libraries, although it is necessary to check the host name?

  • Does anyone have an idea to check the hostname in these circumstances?

Any help is appreciated. Thank you for reading.

+5
source share
2 answers

Unfortunately, I have to adhere to XE2-Indy and OpenSSL V1.0.1m due to internal specifications.

To check the host name on the topic CN and Subject Alternate Names, I did the following (using the cURL implementation approach):

1. When I start the application, I once try to expand access to methods in the Indy cryptography library.

 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. The follwing class helps me access and verify SAN and 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 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. In the OnVerifyPeer event of the TIdSSLIOHandlerSocketOpenSSL component, the class can be used as follows:

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

Can anyone confirm that the wildcard is removed by Indy or the OpenSSL libraries, although it is necessary to verify the host name?

No, OpenSSL does not delete it.

I do not know about the Indy library.


Can anyone confirm that the wildcard is removed by Indy or the OpenSSL libraries, although it is necessary to verify the host name?

I quote this twice for a reason :) Placing server names in Common Name (CN) is deprecated on both the IETF and the CA / B Forums (which browsers do).

What you are probably experiencing is similar to CN=example.com . In this case, example.com not the server name; rather it is domain Therefore, you should not assume that this means that it matches *.example.com .

And if the server responds to https://example.com , you should accept the certificate only if example.com specified as the alternative subject name, since the domains are listed in CN using public certificate authorities. Public CAs put DNS names in the SAN as they follow the CA / B forums.


Does anyone have an idea to check the hostname in these circumstances?

OpenSSL prior to 1.1.0 did not perform host name matching. The developer had to do this. OpenSSL 1.1.0 and higher has built-in functionality. See X509_check_host(3) and friends.

To match the host name, you must collect all the names from both the common name (CN) and the alternate topic name (SAN). Then its usually as simple as matching with a regular expression.

The IETFs are fast and free, and they allow you to display the host name in CN or SAN. The CA / B forum and browsers are more restrictive: if the hostname is in CN, it must also be present in the SAN (yes, it must be specified twice). Otherwise, the CA / B forum and browsers expect all host names in the SAN.

I believe that the OpenSSL and CA / B Forums only allow a wildcard on the leftmost label. I believe the IETF allows wildcards to be displayed anywhere.

If you want to see some sample code, then check out the cURL implementation. cURL uses OpenSSL, but does not depend on 1.1.0 X509_check_host(3) and friends. cURL has its own implementation.


Quick warning. Hostname matching is black art. For instance....

IETF allows you to map the global top-level domain (gTLD), for example *.com or *.net ; and a countryโ€™s top-level domain (ccTLD), for example *.uk or *.us . I believe this is an attack because I know that there is no single certification authority that can claim to be "own" or "certify" gTLD. If I experience one of these certificates in the wild, I reject it.

CA / B forums do not allow gTLD or ccTLD wildcards. Browsers try to avoid this using the Public Suffix List (PSL). Things only got worse with areas of fuss, for example *.google .

There is one more thing that browsers are trying to do with PSL. They are trying to carve administrative boundaries into subdomains. For example, Amazon owns all amazon.com, but they delegate authority to subdomains, for example example.amazon.com. Thus, the PSL is trying to allow Amazon to manage its amazon.com domain, but not your seller related subdomain, example.amazon.com .

The IETF is trying to resolve administrative boundaries in the DBOUND workgroup . But the committee was stalled.

+3
source

All Articles