2011-06-21 65 views
7

Tôi muốn triển khai proxy SSL trong Java. Về cơ bản, tôi mở hai ổ cắm browser-proxy, proxy-server và chạy hai luồng ghi vào proxy-server những gì họ đọc từ browser-proxy và ngược lại. Mỗi sợi trông giống như sau:Đóng đúng SSLSocket

while (true) { 
    nr = in.read(buffer); 
    if (nr == -1) System.out.println(sockin.toString()+" EOF "+nr); 
    if (nr == -1) break; 
    out.write(buffer, 0, nr); 
} 
sockin.shutdownInput(); 
sockout.shutdownOutput(); // now the second thread will receive -1 on read 

Mỗi luồng sẽ chỉ đóng ổ cắm đầu vào, để cuối cùng cả hai đầu cắm đều được đóng lại.

Nhưng tôi phải làm gì nếu tôi muốn sử dụng một số SSLSocket? Có vẻ như phương thức shutdownOutput/Input không được hỗ trợ ở đó. Đây là ngoại lệ tôi nhận được.

Exception in thread "Thread-35" java.lang.UnsupportedOperationException: \ 
The method shutdownInput() is not supported in SSLSocket 
    at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.shutdownInput(Unknown Source) 

Những gì tôi đã đưa ra là:

try { 
    while (true) { 
     nr = in.read(buffer); 
     if (nr == -1) System.out.println(sockin.toString()+" EOF "+nr); 
     if (nr == -1) break; 
     out.write(buffer, 0, nr); 
    } 
    // possible race condition if by some mysterious way both threads would get EOF 
    if (!sockin.isClosed()) sockin.close(); 
    if (!sockout.isClosed()) sockout.close(); 
} catch (SocketException e) {/*ignore expected "socket closed" exception, */} 

tôi phải nắm bắt và bỏ qua hết ổ cắm ngoại lệ mỗi khi ổ cắm của tôi kết thúc.

Câu hỏi của tôi là:

  1. Nếu shutdownInput() không được hỗ trợ, làm thế nào mà tôi nhận được một câu trả lời -1 từ một SSLSocket.
  2. Có giải pháp nào tốt hơn cho sự đau đớn của tôi không? Tôi không nghĩ rằng có bất cứ điều gì lành mạnh, vì một trong những chủ đề có thể đã được trong phương pháp chặn read khi các chủ đề khác đánh dấu anh ta "Tôi đang thực hiện". Và cách duy nhất tôi thấy để đá anh ta ra khỏi chủ đề ngăn chặn là bằng cách đóng các ổ cắm và nâng cao một ngoại lệ "đóng socket".

    (Bạn có thể đoán sử dụng một số kết hợp không đồng bộ của thông điệp đa luồng và đọc dữ liệu không đồng bộ để báo hiệu chủ đề khác mà công việc của bạn đã hoàn thành, nhưng điều này thật kinh khủng đến nỗi cảnh sợ phong cách của IntelliJ sẽ đến sau tôi chỉ cho suy nghĩ về nó ...)

làm rõ: tôi biết rằng shutdownInputshutdownOutput không có ý nghĩa, vì bạn không thể có một kết nối TLS half-duplex, mỗi spec. Nhưng cho rằng, làm thế nào tôi có thể kết thúc một kết nối TLS trong Java mà không nhận được một ngoại lệ?

Vui lòng không cho tôi biết shutdownIput không có ý nghĩa đối với TLS. Tôi biết rằng, đây không phải là những gì tôi yêu cầu. Tôi hỏi những gì khác tôi có thể sử dụng, để đóng SSLSocket đúng cách (do đó tiêu đề, "Đóng SSLSocket đúng cách".

+0

Ý của bạn là gì khi bạn nói shutdownInput/Output không được hỗ trợ? Liệu phương pháp ném một ngoại lệ? Nếu vậy, ngoại lệ là gì? – Pace

+0

Có, nó thực sự ném một ngoại lệ 'Ngoại lệ trong chủ đề" Thread-4 "java.lang.UnsupportedOperationException: Phương thức shutdownInput() không được hỗ trợ trong SSLSocket'. Tôi cho rằng người hiểu biết đủ để trả lời câu hỏi của tôi, sẽ biết một thực tế là họ không được hỗ trợ với SSL. –

+0

Bạn có thể làm rõ tại sao "bây giờ luồng thứ hai sẽ nhận -1 khi đọc" tại 'sockout.shutdownOutput()'? Có lẽ, trong ví dụ của bạn, 'in = sockin.getInputStream()'. – Bruno

Trả lời

17

Đây là câu trả lời đầu tiên của tôi (tạm thời bị xóa), nhưng dường như không đủ tốt, kể từ khi nó đã được -2'ed khá nhanh chóng ... Tôi đoán tôi nên thêm nhiều lời giải thích. 01

Không thể đóng một nửa kết nối cho ổ cắm SSL/TLS để tuân thủ giao thức TLS, như được chỉ định trong section on closure alerts.

Máy khách và máy chủ phải chia sẻ kiến ​​thức rằng kết nối là kết thúc để tránh bị cắt ngắn tấn công. Một trong hai bên có thể bắt đầu việc trao đổi tin nhắn đóng.

close_notify Thông báo này thông báo cho người nhận rằng người gửi sẽ không gửi thêm bất kỳ tin nhắn nào trên kết nối này. Phiên sẽ trở thành không thể thay đổi nếu bất kỳ kết nối nào bị chấm dứt mà không có thông báo close_notify đúng với mức độ bằng cảnh báo.

Một trong hai bên có thể bắt đầu đóng bằng cách gửi cảnh báo close_notify. Bất kỳ dữ liệu nào nhận được sau khi đóng cảnh báo bị bỏ qua.

Mỗi bên được yêu cầu gửi cảnh báo close_notify trước khi đóng phần ghi của kết nối. Yêu cầu yêu cầu bên kia trả lời với cảnh báo close_notify riêng và đóng kết nối ngay lập tức, hủy bất kỳ đang chờ xử lý nào. Nó không được yêu cầu cho người khởi xướng việc đóng để chờ đợi đối với cảnh báo close_notify phản hồi trước khi đóng kết nối của số kết nối.

Mặc dù bạn có thể muốn đóng chỉ là một nửa của kết nối (đầu vào hay đầu ra thông qua shutdownInput()/shutdownOutput()), lớp TLS cơ bản vẫn cần phải gửi close_notify gói này trước khi thực sự tắt thông tin liên lạc, nếu không, nó sẽ được được coi là một cuộc tấn công cắt ngắn.

Khắc phục khá đơn giản: chỉ sử dụng close() trên SSLSocket s: nó không chỉ đóng kết nối như đối với các cổng TCP bình thường, mà nó còn sạch theo đặc tả SSL/TLS.

EDIT: Giải thích bổ sung.

Câu trả lời ngắn gọn vẫn là: sử dụng close(), bạn cũng có thể cần tìm hiểu thời điểm thích hợp để thực hiện điều đó từ giao thức ở trên SSL/TLS.

(Chỉ cần làm rõ, những gì bạn đang cố gắng thực hiện có hiệu quả là proxy "Man-In-The-Middle" (MITM). Bạn sẽ cần máy khách được cấu hình để tin cậy chứng chỉ của proxy, có thể như thể đó là chứng chỉ máy chủ nếu điều này có nghĩa là để xảy ra một cách minh bạch. Làm thế nào kết nối HTTPS làm việc thông qua một proxy HTTP là một different question.)

trong một số ý kiến ​​của bạn để other related question của bạn, tôi có ấn tượng rằng bạn nghĩ rằng không trở -1 đã "phá vỡ giao thức TLS". Điều này không thực sự về giao thức, nhưng về API: cách các cấu trúc lập trình (các lớp, phương thức, hàm, ...) được cung cấp cho người dùng (lập trình viên) của ngăn xếp TLS để có thể sử dụng TLS. SSLSocket chỉ là một phần của API để cung cấp cho các lập trình viên khả năng sử dụng SSL/TLS theo cách tương tự như đồng bằng Socket. Đặc tả TLS không nói về ổ cắm. Trong khi TLS (và người tiền nhiệm của nó, SSL) được xây dựng với mục đích làm cho nó có thể cung cấp loại trừu tượng này, vào cuối ngày SSLSocket chỉ là: trừu tượng hóa. Phù hợp với nguyên tắc thiết kế OOP, SSLSocket kế thừa Socket và cố gắng bám sát nhất có thể với hành vi của Socket. Tuy nhiên, do cách SSL/TLS hoạt động (và vì một SSLSocket có hiệu quả nằm trên đầu trang của một thông thường Socket), không thể có bản đồ chính xác của tất cả các tính năng.

Cụ thể, khu vực chuyển đổi từ ổ cắm TCP thông thường sang ổ cắm SSL/TLS không thể được mô hình hóa một cách minh bạch. Một số lựa chọn tùy ý (ví dụ như cách bắt tay) phải được thực hiện khi thiết kế lớp SSLSocket: đó là sự thỏa hiệp giữa không (a) phơi bày quá nhiều đặc trưng của TLS cho người dùng SSLSocket, (b) tạo TLS công việc cơ chế và (c) ánh xạ tất cả điều này đến giao diện hiện có được cung cấp bởi lớp cha (Socket). Những lựa chọn này chắc chắn có một số tác động đến chức năng của SSLSocket và đó là lý do tại sao số Javadoc API page của nó có số lượng văn bản tương đối lớn. SSLSocket.close(), về API, phải tuân theo mô tả của Socket.close(). InputStream/OutputStream thu được từ số SSLSocket không giống với số từ đồng bằng cơ bản Socket, trong đó (trừ khi you've converted it explicitly) bạn có thể không nhìn thấy.

SSLSocket được thiết kế để sử dụng được càng sát càng có thể như thể nó là một đồng bằng SocketInputStream bạn nhận được thiết kế để hoạt động như chặt chẽ như có thể để một input stream tập tin dựa trên. Điều này không cụ thể đối với Java, nhân tiện. Ngay cả các ổ cắm Unix trong C cũng được sử dụng với một bộ mô tả tập tin và read(). Những tóm tắt này thuận tiện và hoạt động phần lớn thời gian, miễn là bạn biết giới hạn của chúng là gì.

Khi nói đến read(), ngay cả trong C, khái niệm về EOF xuất phát từ việc chấm dứt kết thúc tệp nhưng bạn không thực sự xử lý tệp. Vấn đề với cổng TCP là, trong khi bạn có thể phát hiện khi bên từ xa đã chọn đóng kết nối khi nó gửi FIN, bạn không thể phát hiện ra nó không có, nếu nó không gửi bất cứ điều gì. Khi đọc không có gì từ ổ cắm, không có cách nào để biết sự khác biệt giữa kết nối không hoạt động và bị hỏng. Như vậy, khi nói đến lập trình mạng, dựa vào việc đọc -1 từ InputStream nếu được, nhưng bạn không bao giờ chỉ dựa vào điều đó, nếu không bạn có thể kết thúc với các kết nối chưa được phát hành. Trong khi "lịch sự" để một bên đóng kết nối TCP một cách chính xác (theo cách như bên từ xa đọc số -1 trên số InputStream hoặc thứ gì đó tương đương, như được mô tả trong Orderly Versus Abortive Connection Release in Java), việc xử lý trường hợp này là không đủ. Đây là lý do tại sao các giao thức trên đầu trang của TCP thường là designed to give an indication as to when they're done sending data: ví dụ: SMTP gửi QUIT (và có các thuật ngữ cụ thể), HTTP 1.1 sử dụng mã hóa Content-Length hoặc chunked.

(Nhân tiện, nếu bạn không hài lòng với sự trừu tượng được cung cấp bởi SSLSocket, hãy thử sử dụng trực tiếp SSLEngine. Có thể khó hơn và sẽ không thay đổi thông số kỹ thuật TLS nói về việc gửi close_notify.)

Nói chung với TCP, bạn nên biết, ở cấp giao thức ứng dụng, khi dừng gửi và nhận dữ liệu (để tránh các kết nối chưa được phát hành và/hoặc dựa vào các lỗi thời gian chờ). Câu này trong đặc tả TLS làm cho thực hành tốt này trở thành yêu cầu mạnh hơn khi nói đến TLS: "Yêu cầu bên kia phản hồi cảnh báo close_notify và đóng kết nối ngay lập tức loại bỏ mọi ghi đang chờ xử lý. " Bạn sẽ phải đồng bộ hóa bằng cách nào đó, thông qua giao thức ứng dụng hoặc bên từ xa vẫn có thể có thứ gì đó để viết (đặc biệt nếu bạn cho phép yêu cầu/phản hồi pipelined).

Nếu proxy bạn đang viết là để chặn kết nối HTTPS (dưới dạng MITM), bạn có thể xem nội dung của yêu cầu. Ngay cả khi các yêu cầu HTTP được pipelined, bạn có thể đếm số lượng phản hồi được gửi bởi máy chủ mục tiêu (và mong đợi khi chúng kết thúc, thông qua Content-Length hoặc phân tách đoạn).

Tuy nhiên, bạn sẽ không thể có thiết bị chặn "TLS" thuần túy trong suốt. TLS được thiết kế để đảm bảo việc vận chuyển giữa hai bên và không được đặt ở giữa. Trong khi hầu hết cơ chế để đạt được sự bảo vệ đó (tránh một MITM) được thực hiện thông qua chứng chỉ máy chủ, một số cơ chế TLS khác sẽ cản trở (cảnh báo đóng cửa là một trong số chúng). Hãy nhớ rằng bạn đang tạo hai kết nối TLS riêng biệt một cách hiệu quả, thực tế là chúng khó kết hợp như một kết quả không đáng ngạc nhiên, xem xét mục đích của TLS.

SSLSocket.close() là cách phù hợp để đóng số SSLSocket, nhưng giao thức ứng dụng cũng sẽ giúp bạn xác định thời điểm thích hợp để thực hiện việc này.

+0

Bạn đã xem giải pháp đề xuất của tôi chưa? Tôi nghĩ rằng tôi đã đề xuất những gì bạn đang nói, tôi có đúng không? Tôi đang sử dụng 'close', nhưng đầu kia ném ngoại lệ, thay vì trả về' -1' một cách sạch sẽ khi đọc. Có thể làm cho Java trả về '-1' khi đóng kết nối TLS không? Có nó là, nó như vậy khi tôi liên lạc với một thực thể không Java. Làm thế nào tôi có thể bắt chước hành vi này? –

+1

Tôi vừa mới nhận ra phần này của [RFC 2818] (http://tools.ietf.org/html/rfc2818#section-2.2) cũng phải có liên quan: * "Vì TLS không biết đến ranh giới yêu cầu/đáp ứng HTTP, nó là cần thiết để kiểm tra dữ liệu HTTP chính nó (cụ thể là tiêu đề nội dung chiều dài) để xác định xem cắt ngắn xảy ra bên trong một tin nhắn hoặc giữa các tin nhắn. "* – Bruno

+0

Hi Bruno, bạn có biết một số giải pháp làm sẵn của động cơ SSL? –

0

Ngữ nghĩa SSL cho kết nối đóng khác với TCP, do đó shutdownInput và shutdownOutput không thực sự hợp lý, ít nhất là ở cấp điều khiển có sẵn trong SSLSocket. SSLSocket về cơ bản giúp bạn giảm bớt gánh nặng xử lý hồ sơ SSL và xử lý thông báo bắt tay SSL. Nó cũng xử lý xử lý cảnh báo SSL và một trong các cảnh báo này là cảnh báo close_notify. Đây là văn bản từ RFC 2246 về ý nghĩa của cảnh báo này.

"...Either party may initiate a close by sending a close_notify alert. 
    Any data received after a closure alert is ignored. 

    Each party is required to send a close_notify alert before closing 
    the write side of the connection. It is required that the other party 
    respond with a close_notify alert of its own and close down the 
    connection immediately, discarding any pending writes. It is not 
    required for the initiator of the close to wait for the responding 
    close_notify alert before closing the read side of the connection...." 

Java cung cấp các nhà tự tử với API khác, API SSLEngine. Đừng chạm vào nó, nó sẽ làm tổn thương bạn.

+0

Tôi hiểu rằng, nhưng nó vẫn không có ý nghĩa rằng đóng kết nối sẽ ném một ngoại lệ ở phía bên kia. Tôi sẽ làm rõ rằng tôi biết rằng trong phần bình luận. Tôi nên làm gì thay vì 'shutdownInput' và' shutdownOutput' để đảm bảo phía bên kia sẽ nhận '-1' khi anh ta cố đọc luồng, thay vì ngoại lệ? –

+2

@Elazar Leibovich Bạn nên gọi SSLSocket.close(). Tôi vẫn chưa rõ về nơi bạn đang nhận được ngoại lệ. –

-3

Để giải quyết vấn đề của bạn, bạn phải làm như sau:

OutputStream sockOutOStream = sockout.getOutputStream(); 
sockOutOStream.write(new byte[0]); 
sockOutOStream.flush(); 
sockout.close(); 

Ở đầu bên kia bên mà đọc từ ổ cắm sẽ nhận được thông báo -1 độ dài thay vì SocketException ném.

+0

Vô nghĩa. Điều này không viết dài không gửi bất cứ điều gì cả, hãy để một mình một 'chiều dài tin nhắn'. Không có thứ gì như một thông điệp '-1 chiều dài'. -1 được trả về bởi hàm read() là giá trị * out of band * có nghĩa là kết thúc luồng, và nó được gây ra bởi dấu đóng() ở cuối gửi, không phải bởi bất cứ thứ gì bạn có thể viết. Lệnh flush() cũng dư thừa ở đây. Điều duy nhất được yêu cầu là đóng(). – EJP

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