Delphi 如何验证服务器主机名

Delphi 如何验证服务器主机名,delphi,ssl,openssl,certificate,indy,Delphi,Ssl,Openssl,Certificate,Indy,我使用IndyTIdHTTP(XE2附带)和OpenSSL库DLL V1.0.1m在通过HTTPS连接时验证证书。我已经为TIdSSLIOHandlerSocketOpenSSL组件的OnVerifyPeer事件实现了一个事件处理程序 function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean; begi

我使用Indy
TIdHTTP
(XE2附带)和OpenSSL库DLL V1.0.1m在通过HTTPS连接时验证证书。我已经为
TIdSSLIOHandlerSocketOpenSSL
组件的
OnVerifyPeer
事件实现了一个事件处理程序

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509;
  AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
  (...)
end;
根据RFC 2818第3.1章,如果主机名可用于 客户端,客户端必须根据服务器的身份进行检查 出现在服务器的证书消息中,以防止 中间人攻击

现在,我在验证服务器证书的主机名时遇到了一个问题:

尽管通用名称(CN)中存在通配符 服务器证书(*.google.com)中主题字段中的字段,
OnVerifyPeer
事件的参数
Certificate.Subject.OneLine
返回不带任何通配符的CN(即google.com而不是*.google.com)

如RFC 2818第3.1章所述。使用通配符* 匹配任何单个域名组件或组件片段

  • 尽管需要验证主机名,但是否有人可以确认Indy或OpenSSL库删除了通配符

  • 在这种情况下,有人想验证主机名吗

  • 非常感谢您的帮助。谢谢你的阅读

    尽管需要验证主机名,但是否有人可以确认Indy或OpenSSL库删除了通配符

    不,OpenSSL不会删除它

    我不知道印第图书馆的事


    尽管需要验证主机名,但是否有人可以确认Indy或OpenSSL库删除了通配符

    我两次引用这句话的原因是:)IETF和CA/B论坛都不赞成将服务器名放在通用名(CN)中(浏览器遵循的原则)

    您可能正在经历类似于
    CN=example.com
    。在本例中,
    example.com
    不是服务器名;相反,它是一个领域。因此,您不应该假定它意味着匹配
    *.example.com

    如果服务器在
    https://example.com
    ,您应该仅在受试者备选名称包括
    example.com
    时才接受证书,因为公共CA在CN中列出了域。公共CA在SAN中放置DNS名称,因为它们遵循CA/B论坛


    在这种情况下,有人想验证主机名吗

    1.1.0之前的OpenSSL未执行主机名匹配。开发商必须这样做。OpenSSL 1.1.0及以上版本具有内置功能。再见,朋友们

    要匹配主机名,您应该从公共名称(CN)和主题备用名称(SAN)中收集所有名称。然后,它通常与正则表达式匹配一样简单

    IETF是快速而松散的,它们允许主机名显示在CN或SAN中。CA/B论坛和浏览器更严格:如果主机名在CN中,那么它也必须出现在SAN中(是的,必须列出两次)。否则,CA/B论坛和浏览器期望SAN中的所有主机名

    我相信OpenSSL和CA/B论坛只允许在最左边的标签中使用通配符。我相信IETF允许通配符出现在任何地方

    如果您想查看示例代码,请查看cURL的实现。cURL使用OpenSSL,但不依赖于1.1.0和朋友。cURL有自己的实现


    快速警告。主机名匹配是一门黑色艺术。例如

    IETF允许匹配到全局顶级域(gTLD),如
    *.com
    *.net
    ;和国家顶级域名(ccTLD),如
    *.uk
    *.us
    。我认为这是一个攻击,因为我知道没有单一的CA可以声称“拥有”或“证明”GTLD。如果我在野外体验过这些证书,那么我会拒绝它

    CA/B论坛不允许通配符GTLD或CCTLD。浏览器试图通过使用(PSL)来避免它。虚荣域名的情况只会变得更糟,比如
    *.google

    浏览器试图用PSL做另一件事。他们试图在子域上划出行政界限。例如,Amazon拥有所有Amazon.com,但他们将权限委托给子域名,如example.Amazon.com。因此,PSL试图允许亚马逊控制他们的域名
    Amazon.com
    ,但不允许你的
    example.Amazon.com
    的商户相关子域


    IETF正试图解决该地区的行政边界问题。但委员会的工作似乎停滞不前。

    不幸的是,由于内部规范,我不得不坚持使用XE2 Indy和OpenSSL V1.0.1m

    为了对照Subject CN和Subject备用名称验证主机名,我(使用该方法)执行了以下操作:

    1。在应用程序启动时,我尝试一次扩展对Indy crypto库中方法的访问。

    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;
    
    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;
    
    2。下面的类帮助我访问和验证SAN和CN。

    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;
    
    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;
    

    证书数据由OpenSSL本身提供。Indy的
    TIdX509
    类只是将OpenSSL提供的
    PX509
    句柄封装在Indy的验证回调函数中
    TIdX509
    不会弄乱证书数据,而是按原样显示。
    Subject
    属性包装OpenSSL的
    X509\u NAME
    函数中的
    PX509\u-get\u-Subject\u-NAME()
    句柄,并且
    OneLine
    属性返回OpenSSL的
    X509\u-NAME\u-OneLine()函数返回的任何值。也就是说,OpenSSL确实具有
    X509\u check\u host()
    certificate\u host\u override()
    功能