Hướng dẫn php tcp connect

CẬP NHẬT : có một cách tốt hơn để làm điều này, hãy xem các nhận xét.

Bạn có thể chụp chứng chỉ và trò chuyện với máy chủ bằng cách sử dụng openssllàm bộ lọc. Bằng cách này, bạn có thể trích xuất chứng chỉ và kiểm tra nó trong cùng một kết nối.

Đây là một triển khai chưa hoàn chỉnh (không có cuộc trò chuyện gửi thư thực tế) nên bạn sẽ bắt đầu:

 array('pipe', 'r'),
                            1 => array('pipe', 'w'),
                            2 => array('pipe', 'r'),
                    ),
                    $pipes,
                    '/tmp',
                    array()
            );
    list($smtpout, $smtpin, $smtperr) = $pipes; unset($pipes);

    $stage  = 0;
    $cert   = 0;
    $certificate = '';
    while(($stage < 5) && (!feof($smtpin)))
    {
            $line = fgets($smtpin, 1024);
            switch(trim($line))
            {
                    case '-----BEGIN CERTIFICATE-----':
                            $cert   = 1;
                            break;
                    case '-----END CERTIFICATE-----':
                            $certificate .= $line;
                            $cert   = 0;
                            break;
                    case '---':
                            $stage++;
            }
            if ($cert)
                    $certificate .= $line;
    }
    fwrite($smtpout,"HELO mail.example.me\r\n"); // .me is client, .com is server
    print fgets($smtpin, 512);
    fwrite($smtpout,"QUIT\r\n");
    print fgets($smtpin, 512);

    fclose($smtpin);
    fclose($smtpout);
    fclose($smtperr);
    proc_close($pid);

    print $certificate;

    $par    = openssl_x509_parse($certificate);
?>

Tất nhiên, bạn sẽ di chuyển phân tích và kiểm tra chứng chỉ trước khi bạn gửi bất kỳ thứ gì có ý nghĩa đến máy chủ.

Trong $parmảng, bạn sẽ tìm thấy (trong số các phần còn lại) tên, được phân tích cú pháp giống như chủ đề.

Array
(
    [name] => /C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
    [subject] => Array
        (
            [C] => US
            [ST] => California
            [L] => Mountain View
            [O] => Google Inc
            [CN] => smtp.gmail.com
        )

    [hash] => 11e1af25
    [issuer] => Array
        (
            [C] => US
            [O] => Google Inc
            [CN] => Google Internet Authority
        )

    [version] => 2
    [serialNumber] => 280777854109761182656680
    [validFrom] => 120912115750Z
    [validTo] => 130607194327Z
    [validFrom_time_t] => 1347451070
    [validTo_time_t] => 1370634207
    ...
    [extensions] => Array
        (
            ...
            [subjectAltName] => DNS:smtp.gmail.com
        )

Để kiểm tra tính hợp lệ, ngoài việc kiểm tra ngày, v.v., SSL tự thực hiện, bạn phải xác minh rằng ĐỦ các điều kiện sau áp dụng:

  • CN của thực thể là tên DNS của bạn, ví dụ: "CN = smtp.your.server.com"

  • có những phần mở rộng được xác định và chúng chứa một subjectAltName, tên này đã từng bùng nổ explode(',', $subjectAltName), mang lại một loạt các DNS:bản ghi đã được sửa trước, ít nhất một trong số đó khớp với tên DNS của bạn. Nếu không có gì phù hợp, chứng chỉ sẽ bị từ chối.

Xác minh chứng chỉ bằng PHP

Tốt nhất là ý nghĩa của xác minh máy chủ trong các phần mềm khác nhau .

Vì vậy, tôi quyết định tìm hiểu ở phần cuối của điều này, và tải xuống mã nguồn của OpenSSL (openssl-1.0.1c) và cố gắng tự mình kiểm tra.

Tôi không tìm thấy tham chiếu nào đến mã mà tôi mong đợi, cụ thể là:

  • cố gắng phân tích cú pháp một chuỗi được phân tách bằng dấu hai chấm
  • tham chiếu đến subjectAltName(mà OpenSSL gọi SN_subject_alt_name)
  • sử dụng "DNS [:]" làm dấu phân tách

OpenSSL dường như đưa tất cả các chi tiết chứng chỉ vào một cấu trúc, chạy các bài kiểm tra rất cơ bản trên một số chúng, nhưng hầu hết các trường "con người có thể đọc được" đều bị bỏ lại. Nó có ý nghĩa: có thể lập luận rằng kiểm tra tên ở cấp độ cao hơn kiểm tra chữ ký chứng chỉ

Sau đó, tôi cũng tải xuống cURL mới nhất và tarball PHP mới nhất.

Trong mã nguồn PHP, tôi cũng không tìm thấy gì; rõ ràng bất kỳ tùy chọn nào chỉ được truyền xuống dòng và nếu không thì bị bỏ qua. Mã này chạy không có cảnh báo:

    stream_context_set_option($smtp, 'ssl', 'I-want-a-banana', True);

stream_context_get_optionssau đó được lấy lại một cách nghiêm túc

    [ssl] => Array
        (
            [I-want-a-banana] => 1
            ...

Điều này cũng có ý nghĩa: PHP không thể biết, trong ngữ cảnh "context-option-setting", những tùy chọn nào sẽ được sử dụng dưới dòng.

Cũng như vậy, mã phân tích cú pháp chứng chỉ phân tích cú pháp chứng chỉ và trích xuất thông tin mà OpenSSL đặt ở đó, nhưng nó không xác thực thông tin tương tự.

Vì vậy, tôi đã đào sâu hơn một chút và cuối cùng đã tìm thấy mã xác minh chứng chỉ trong cURL, tại đây:

// curl-7.28.0/lib/ssluse.c

static CURLcode verifyhost(struct connectdata *conn,
                       X509 *server_cert)
{

nơi nó thực hiện những gì tôi mong đợi: nó tìm kiếm subjectAltNames, nó kiểm tra tất cả chúng xem có tỉnh táo không và chạy chúng qua hostmatch, nơi các kiểm tra như hello.example.com == * .example.com được chạy. Có các kiểm tra độ tỉnh táo bổ sung: "Chúng tôi yêu cầu có ít nhất 2 dấu chấm trong mẫu để tránh đối sánh ký tự đại diện quá rộng." và xn-- kiểm tra.

Tóm lại, OpenSSL chạy một số kiểm tra đơn giản và giao phần còn lại cho người gọi. cURL, gọi OpenSSL, thực hiện nhiều kiểm tra hơn. PHP cũng chạy một số kiểm tra trên CN với verify_peer, nhưng vẫn để subjectAltNameyên. Những kiểm tra này không thuyết phục tôi quá nhiều; xem bên dưới trong "Kiểm tra".

Thiếu khả năng truy cập các chức năng của cURL, giải pháp thay thế tốt nhất là thực hiện lại các chức năng đó trong PHP.

Ví dụ: đối sánh miền ký tự đại diện biến có thể được thực hiện bằng cách chấm nổ cả miền thực và miền chứng chỉ, đảo ngược hai mảng

com.example.site.my
com.example.*

và xác minh rằng các mục tương ứng là bằng nhau hoặc chứng chỉ một là *; nếu điều đó xảy ra, chúng tôi phải kiểm tra ít nhất hai thành phần, ở đây comexample.

Tôi tin rằng giải pháp trên là một trong những giải pháp tốt nhất nếu bạn muốn kiểm tra tất cả các chứng chỉ trong một lần. Thậm chí tốt hơn là có thể mở luồng trực tiếp mà không cần đến opensslmáy khách - và điều này là có thể ; xem bình luận.

Kiểm tra

Tôi có một chứng chỉ tốt, hợp lệ và hoàn toàn đáng tin cậy từ Thawte được cấp cho "mail.eve.com".

Đoạn mã trên chạy trên Alice sau đó sẽ kết nối an toàn với mail.eve.comvà đúng như mong đợi.

Bây giờ tôi cài đặt cùng một chứng chỉ đó mail.bob.com, hoặc theo cách khác, tôi thuyết phục DNS rằng máy chủ của tôi là Bob, trong khi thực tế nó vẫn là Eve.

Tôi hy vọng các kết nối SSL để làm việc vẫn (giấy chứng nhận hợp lệ và đáng tin cậy), nhưng chứng chỉ không được cấp cho Bob - nó cấp cho Eve. Vì vậy, ai đó phải kiểm tra lần cuối này và cảnh báo Alice rằng Bob thực sự đang bị Eve mạo danh (hoặc tương đương, Bob đang sử dụng chứng chỉ bị đánh cắp của Eve).

Tôi đã sử dụng mã dưới đây:

    $smtp = fsockopen( "tcp://mail.bob.com", 25, $errno, $errstr );
    fread( $smtp, 512 );
    fwrite($smtp,"HELO alice\r\n");
    fread($smtp, 512);
    fwrite($smtp,"STARTTLS\r\n");
    fread($smtp, 512);
    stream_set_blocking($smtp, true);
    stream_context_set_option($smtp, 'ssl', 'verify_host', true);
    stream_context_set_option($smtp, 'ssl', 'verify_peer', true);
    stream_context_set_option($smtp, 'ssl', 'allow_self_signed', false);
    stream_context_set_option($smtp, 'ssl', 'cafile', '/etc/ssl/cacert.pem');
    $secure = stream_socket_enable_crypto($smtp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
    stream_set_blocking($smtp, false);
    print_r(stream_context_get_options($smtp));
    if( ! $secure)
            die("failed to connect securely\n");
    print "Success!\n";

và:

  • nếu chứng chỉ không thể xác minh với một cơ quan đáng tin cậy:
    • verify_host không làm gì cả
    • verify_peer TRUE gây ra lỗi
    • verify_peer FALSE cho phép kết nối
    • allow_self_signed không làm gì cả
  • nếu chứng chỉ hết hạn:
    • Tôi nhận được một lỗi.
  • nếu chứng chỉ có thể xác minh được:
    • được phép kết nối với "mail.eve.com" mạo danh "mail.bob.com" và tôi nhận được thông báo "Thành công!" thông điệp.

Tôi hiểu điều này có nghĩa là, trừ một số lỗi ngu ngốc từ phía tôi, PHP không tự kiểm tra các chứng chỉ so với tên .

Sử dụng proc_openmã ở đầu bài đăng này, tôi lại có thể kết nối, nhưng lần này tôi có quyền truy cập subjectAltNamevà do đó có thể tự mình kiểm tra, phát hiện ra sự mạo danh.

8 hữu ích 5 bình luận chia sẻ