2015-05-25 18 views
5

Tôi đang sử dụng Indy TIdHTTP (được giao với XE2) và thư viện OpenSSL DLLs V1.0.1m để xác minh chứng chỉ khi kết nối qua HTTPS. Tôi đã triển khai trình xử lý sự kiện cho sự kiện OnVerifyPeer của thành phần TIdSSLIOHandlerSocketOpenSSL.Cách xác minh tên máy chủ của máy chủ

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

Theo RFC 2818, chương 3.1., Nếu tên máy chủ là có sẵn cho khách hàng, khách hàng phải kiểm tra xem nó chống lại sao chép của máy chủ như được trình bày trong bài viết Giấy chứng nhận của máy chủ, để ngăn chặn các cuộc tấn công trung gian.

Bây giờ tôi có một vấn đề cần xác minh hostname của server certificate:

Mặc dù một ký tự đại diện có mặt trong lĩnh vực trong lĩnh vực Chủ đề trong phạm vi chứng chỉ máy chủ Common Name (CN) (* .google .com), thông số Certificate.Subject.OneLine của sự kiện OnVerifyPeer trả về CN mà không có bất kỳ ký tự đại diện nào (ví dụ: google.com thay vì * .google.com).

Như đã nêu trong RFC 2818, chương 3.1. ký tự đại diện * được sử dụng để khớp với bất kỳ thành phần hoặc thành phần tên miền đơn lẻ nào.

  1. bất cứ ai có thể xác nhận rằng các ký tự đại diện được lấy ra bởi Indy hoặc các thư viện OpenSSL, mặc dù nó là cần thiết để xác minh tên máy?

  2. Có ai có ý tưởng để xác minh tên máy trong những trường hợp?

Bất kỳ trợ giúp nào được đánh giá cao. Cảm ơn vì đã đọc.

+0

Các dữ liệu chứng chỉ được cung cấp bởi OpenSSL riêng của mình. Lớp 'TIdX509' của Indy chỉ đơn giản là kết thúc xử lý' PX509' được cung cấp bởi OpenSSL bên trong hàm gọi lại xác minh của Indy. 'TIdX509' không lộn xộn xung quanh với dữ liệu chứng chỉ, nó trình bày nó như là. Thuộc tính 'Subject' bao bọc một xử lý' PX509_NAME' từ hàm 'X509_get_subject_name()' của OpenSSL và thuộc tính 'OneLine' trả về bất kỳ giá trị nào của hàm OpenSSL' X509_NAME_oneline() 'trả về. Vì vậy, chính OpenSSL đang loại bỏ ký tự đại diện. –

+1

Điều đó đang được nói, OpenSSL có các hàm 'X509_check_host()' và 'certificate_host_name_override()'. Bạn có thể vượt qua chúng xử lý 'PX509' ban đầu (thành viên' TIdX509.FX509' - bạn sẽ phải sử dụng một lớp accessor để tiếp cận nó) và tên máy chủ mà bạn đã kết nối tới. –

+0

Cảm ơn bạn đã phản hồi nhanh. Tôi sẽ cố gắng vào ngày mai và cho bạn biết về kết quả của tôi. – Cheesy

Trả lời

1

Thật không may, tôi phải tuân theo XE2-Indy và OpenSSL V1.0.1m do thông số kỹ thuật bên trong.

Để xác minh tên máy so với CN Subject và Subject Alternate Names, tôi đã thực hiện như sau (sử dụng phương pháp cURL's implementation):

1. Lúc khởi động ứng dụng, tôi đang cố gắng một lần để mở rộng khả năng tiếp cận cho các phương thức trong thư viện mã hóa 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. Lớp follwing giúp tôi tiếp cận và xác minh SAN và 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. Trong trường hợp OnVerifyPeer của các thành phần TIdSSLIOHandlerSocketOpenSSL, lớp có thể được sử dụng như sau:

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

@RemyLebeau: Tôi có làm phiền Indy nội bộ bằng cách sử dụng phương pháp này không? Cho đến nay tôi không gặp vấn đề gì. Tôi chỉ muốn ngăn chặn người khác đang làm điều gì đó sai trái bằng cách sử dụng triển khai này. – Cheesy

+0

Cảm ơn các gợi ý. Mặc dù tôi đã chấp nhận câu trả lời, tôi vẫn muốn nhận được phản hồi từ Remy. – Cheesy

+0

Hãy quan tâm đến miền trong 'CN', điều này khá phổ biến. Tức là, bạn sẽ trải nghiệm 'CN = example.com'. Trong trường hợp này, 'example.com' là một tên miền *** và không phải là tên máy chủ ***. Bạn không nên giải thích nó có nghĩa là máy chủ 'example.com' hoặc tất cả các máy chủ trong miền' * .example.com'. Tên máy chủ đi vào 'Subject Alternate Name (SAN)'. – jww

3

Bất kỳ ai có thể xác nhận rằng ký tự đại diện bị xóa bởi thư viện Indy hoặc OpenSSL, mặc dù cần xác minh tên máy chủ?

Không, OpenSSL không xóa nó.

Tôi không biết về thư viện Indy.


bất cứ ai có thể xác nhận rằng các ký tự đại diện được lấy ra bởi Indy hoặc các thư viện OpenSSL, mặc dù nó là cần thiết để xác minh tên máy?

tôi trích dẫn này hai lần vì một lý do :) Đặt tên máy chủ trong Common Name (CN) bị phản đối bởi cả IETF và Diễn đàn CA/B (những gì các trình duyệt theo).

Những gì bạn có thể gặp phải là một cái gì đó như CN=example.com.Trong trường hợp này, example.comkhông một tên máy chủ; chứ không phải nó một miền. Vì vậy, bạn không nên cho nó có nghĩa là để phù hợp với *.example.com.

Và nếu một máy chủ trả lời tại https://example.com, bạn chỉ nên chấp nhận giấy chứng nhận nếu Subject Alternate Name bao gồm example.com vì tên miền được liệt kê trong CN bởi các CA công cộng. Public CAs đặt tên DNS trong SAN vì chúng tuân theo Diễn đàn CA/B.


Có ai có ý tưởng để xác minh tên máy trong những trường hợp?

OpenSSL trước 1.1.0 đã làm không thực hiện đối sánh tên máy chủ. Các nhà phát triển đã phải làm điều đó. OpenSSL 1.1.0 trở lên có chức năng được tích hợp. Xem X509_check_host(3) và bạn bè.

Để phù hợp với một hostname, bạn nên thu thập tất cả các tên từ cả các Common Name (CN)Subject Alternate Name (SAN). Sau đó, nó thường đơn giản như một biểu thức chính quy phù hợp.

IETF nhanh và lỏng, đồng thời cho phép tên máy chủ hiển thị trong CN hoặc SAN. Diễn đàn và trình duyệt CA/B nghiêm ngặt hơn: nếu tên máy chủ nằm trong CN, thì nó cũng phải có mặt trong SAN (có, nó phải được liệt kê hai lần). Nếu không, Diễn đàn và trình duyệt CA/B mong đợi tất cả tên máy chủ trong SAN.

Tôi tin rằng OpenSSL và Diễn đàn CA/B chỉ cho phép ký tự đại diện ở nhãn ngoài cùng bên trái. Tôi tin rằng IETF cho phép các ký tự đại diện xuất hiện ở bất cứ đâu.

Nếu bạn muốn xem mã mẫu, hãy kiểm tra triển khai của cURL. cURL sử dụng OpenSSL, nhưng không phụ thuộc vào X509_check_host(3) và bạn bè của 1.1.0. cURL đã thực hiện riêng.


Cảnh báo nhanh. Đối sánh tên máy chủ là một nghệ thuật đen. Ví dụ:

IETF cho phép khớp với Tên miền cấp cao nhất toàn cầu (gTLD) như *.com hoặc *.net; và Tên miền cấp cao nhất quốc gia (ccTLD) như *.uk hoặc *.us. Tôi coi đây là một cuộc tấn công bởi vì tôi biết không có CA đơn lẻ nào có thể yêu cầu "sở hữu" hoặc "chứng nhận" một gTLD. Nếu tôi trải qua một trong những loại certs đó trong tự nhiên, thì tôi từ chối nó.

Diễn đàn CA/B không cho phép gTLD ký tự đại diện hoặc ccTLD. Các trình duyệt cố gắng tránh nó bằng cách sử dụng Public Suffix List (PSL). Mọi thứ trở nên tồi tệ hơn với các tên miền ảo, như *.google.

Có một điều khác mà trình duyệt cố gắng thực hiện với PSL. Họ cố gắng khắc phục ranh giới hành chính trên các tên miền phụ. Ví dụ, Amazon sở hữu tất cả amazon.com, nhưng họ ủy quyền cho các tên miền phụ, như example.amazon.com.Vì vậy, PSL cố gắng cho phép Amazon kiểm soát miền của họ amazon.com, nhưng không cho phép miền phụ có liên quan đến người bán của bạn là example.amazon.com.

IETF đang cố gắng giải quyết các ranh giới hành chính trong DBOUND Working Group. Nhưng mọi thứ dường như bị đình trệ trong ủy ban.

+0

** Bất cứ ai có thể xác nhận rằng ký tự đại diện được xóa bởi thư viện Indy hoặc OpenSSL, mặc dù nó là cần thiết để xác minh tên máy chủ? ** Mặc dù tôi đã khá chắc chắn rằng ký tự đại diện đã bị loại bỏ trong "Certificate.Subject.OnLine", hiện tượng này đã không xảy ra một lần nữa (thử nghiệm với https://www.youtube.com). – Cheesy

Các vấn đề liên quan