9.17. Socket mã hóa và TLS¶
Tất cả những gì đã trình bày từ đầu đến đây đều truyền dữ liệu dưới dạng văn bản rõ ràng. Bất kỳ thiết bị nào trên đường đi giữa camera và máy chủ -- bộ định tuyến gia đình, nhà cung cấp dịch vụ internet, một điểm truy cập độc hại tại quán cà phê -- đều có thể đọc hoặc chỉnh sửa dữ liệu truyền qua. Đối với hầu hết lưu lượng internet, điều này là không thể chấp nhận được. Giải pháp tiêu chuẩn là bọc kết nối trong một lớp mã hóa: TLS, giao thức Transport Layer Security. Biểu tượng khóa "HTTPS" trong trình duyệt chính là TLS chạy trên TCP, và cùng cách bọc này là thứ làm cho bất kỳ giao thức internet nào khác trở nên "an toàn". Module ssl của camera là thứ bọc socket trong TLS.
9.17.1. TLS bổ sung gì và cam đi kèm những gì¶
TLS nằm giữa TCP và ứng dụng -- ứng dụng ghi byte vào socket được bọc TLS, TLS mã hóa chúng và chuyển kết quả cho TCP, và quá trình đảo ngược ở phía bên kia. Ở dạng đầy đủ, TLS cung cấp ba đảm bảo trên nền TCP thuần túy:
Bảo mật. Những kẻ nghe lén trên đường truyền không thể đọc được những gì hai điểm cuối đang trao đổi.
Toàn vẹn. Mọi sự thay đổi lưu lượng trong quá trình truyền đều bị phát hiện; kết nối bị ngắt thay vì giao dữ liệu đã bị giả mạo.
Xác thực. Máy chủ chứng minh đó là máy chủ được đặt tên, không phải kẻ mạo danh (và, tùy chọn, client cũng chứng minh nó là ai).
Hai điều đầu đến từ bản thân quá trình mã hóa. Điều thứ ba cần chứng chỉ ở ít nhất một phía, cộng với thứ gì đó được tin cậy sẵn để xác minh các chứng chỉ đó. OpenMV cam không đi kèm bất kỳ kho chứng chỉ tích hợp nào: một cam mới flash không tin tưởng bất kỳ cơ quan chứng chỉ nào, không có chứng chỉ máy chủ của riêng mình, và chế độ xác minh mặc định (ssl.CERT_NONE) không kiểm tra chứng chỉ của đối tác với bất cứ thứ gì. Vì vậy, ngay lập tức, TLS trên cam cung cấp hai đảm bảo đầu tiên -- mã hóa chống nghe lén và giả mạo bởi một người quan sát thụ động -- nhưng không có điều thứ ba.
9.17.2. Mã hóa kết nối đi ra ngoài¶
Cách sử dụng đơn giản nhất là bọc một kết nối TCP đi ra ngoài. Quy trình là: mở một socket TCP thông thường, chuyển nó cho ssl.wrap_socket(), sau đó đọc và ghi qua socket được bọc theo cách giống như với socket thuần túy:
import socket
import ssl
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)
s = ssl.wrap_socket(sock)
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(s.recv(4096))
s.close()
Thao tác bọc thực hiện bắt tay TLS; sau đó mọi byte qua s.send đều được mã hóa khi gửi đi và mọi byte từ s.recv đều được mã hóa trên đường truyền. Không có chứng chỉ nào được cấu hình, không có neo tin cậy nào được cung cấp -- TLS chỉ thương lượng khóa phiên tạm thời với bất kỳ máy chủ nào trả lời và sử dụng nó.
Bắt tay TLS ssl.wrap_socket() thực thi. Nó nằm trên kết nối TCP đã mở từ hình trước; sau khi cả hai phía đã gửi Finished, phần còn lại của cuộc trò chuyện được mã hóa theo cả hai hướng.¶
Cảnh báo
Đây là mã hóa đơn thuần, không phải TLS có xác thực. Cam nói chuyện an toàn với bất cứ thứ gì đã trả lời ở đầu kia của kết nối TCP. Nếu một kẻ tấn công trung gian chuyển hướng kết nối đến máy chủ nó kiểm soát và máy chủ đó trình bày bất kỳ chứng chỉ nào, bắt tay vẫn thành công và cam cuối cùng nói chuyện an toàn với kẻ tấn công. Chỉ dùng chế độ này khi tấn công trung gian không phải là một phần của mô hình mối đe dọa -- mạng cục bộ đóng, môi trường phát triển, cam nói chuyện với dịch vụ chạy trên cùng phần cứng -- không phải khi kết nối ra internet công cộng.
Để xác thực thực sự -- cam xác minh máy chủ công cộng, cam hoạt động như một máy chủ TLS, hoặc TLS lẫn nhau -- bạn cần mang chứng chỉ vào thiết bị. Câu chuyện đầy đủ nằm trong Làm việc với chứng chỉ TLS.
Thao tác bọc tương tự hoạt động cho lưu lượng TCP đến, bằng cách chọn giao thức máy chủ và truyền server_side=True cho ssl.wrap_socket(). Cảnh báo ở trên vẫn áp dụng: không có chứng chỉ của riêng mình, cam không thể chứng minh nó là ai với client, và một client tò mò sẽ thấy lỗi bắt tay "no certificate" trên hầu hết các ngăn xếp TLS. Quy trình chứng chỉ phía sản xuất là thứ mở khóa việc chạy cam như một máy chủ TLS theo cách hữu ích.
9.17.3. Với asyncio¶
Chương asyncio đã trình bày asyncio.open_connection() cho client TCP thuần túy. Cùng lệnh gọi đó chấp nhận từ khóa ssl=True để bọc kết nối trong TLS, một lần nữa không cần cấu hình chứng chỉ:
import asyncio
async def main():
reader, writer = await asyncio.open_connection(
"example.com", 443, ssl=True,
)
writer.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
await writer.drain()
print(await reader.read(4096))
writer.close()
await writer.wait_closed()
asyncio.run(main())
Cặp reader/writer đằng sau kết nối TLS có cùng hình dạng như cho kết nối TCP thuần túy -- chỉ khác ở phần thiết lập. Cùng lưu ý về xác thực áp dụng: ssl=True một mình chỉ cho mã hóa, không có xác minh.
9.17.4. DTLS -- TLS qua UDP¶
TLS như đã thảo luận cho đến nay chạy trên TCP. Giao thức song song cho UDP là DTLS (Datagram TLS), và module ssl của camera hỗ trợ nó theo cùng cách. Trong khi TLS biến một kết nối TCP thành một luồng byte mã hóa, DTLS biến một socket UDP thành một luồng datagram được mã hóa, giao riêng lẻ -- vì vậy các thuộc tính mất gói / không theo thứ tự / không kiểm soát luồng của UDP từ UDP -- gửi gói tin, hy vọng điều tốt nhất đều được kế thừa, với các byte bên trong mỗi datagram bây giờ được mã hóa.
Thao tác bọc trông giống như trường hợp TLS, chỉ với socket SOCK_DGRAM và các hằng số giao thức DTLS:
import socket
import ssl
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(socket.getaddrinfo("example.com", 4433)[0][-1])
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.send(b"ping")
print(s.recv(64))
s.close()
(Gọi connect() trên socket UDP không mở kết nối -- nó chỉ ghi nhớ một đích đến mặc định để các lệnh gọi send() / recv() tiếp theo không phải lặp lại nó. DTLS cần đích đến cố định đó để chạy bắt tay với nó.)
Bắt tay có cùng hình dạng như sơ đồ TLS ở trên; sự khác biệt là mỗi thông điệp bắt tay bản thân nó là một datagram UDP, và một trong hai phía sẽ thử lại nếu mất gói.
Ghi chú
Mất gói có phá vỡ mã hóa không? Không. Mỗi gói DTLS mang một số thứ tự, và mã hóa sử dụng số đó để tạo ra đầu ra khác nhau cho mỗi gói -- vì vậy cùng một đầu vào không bao giờ mã hóa thành cùng một byte hai lần, và bất kỳ gói nào cũng có thể được giải mã độc lập mà không cần gói trước đó đã đến. Các gói bị mất hoặc không theo thứ tự không làm lệch đồng bộ hai phía. (Bắt tay bản thân là phần duy nhất phải đến đáng tin cậy, và DTLS xử lý điều đó với cơ chế truyền lại của riêng nó.)
Cảnh báo mã hóa đơn thuần không có chứng chỉ từ trên vẫn áp dụng: bắt tay DTLS với đối tác CERT_NONE mã hóa lưu lượng nhưng không xác minh phía kia là ai. Quy trình DTLS đầy đủ -- chứng chỉ, cookie chống giả mạo phía máy chủ, lý do điều này có cùng bề mặt như TLS ngoài các hằng số giao thức -- được đề cập cùng với tài liệu TLS trong Làm việc với chứng chỉ TLS.
Phiên bản asyncio sử dụng cùng mẫu UDP không chặn từ Sockets với asyncio. Thực hiện bắt tay đồng bộ ở phía trước, chuyển socket sang không chặn, sau đó thăm dò bên trong một coroutine:
import asyncio
import socket
import ssl
async def dtls_ping(target_addr, period_ms):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(target_addr)
# Handshake while still blocking, then switch to async polling.
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.setblocking(False)
while True:
try:
s.send(b"ping")
except OSError:
pass
await asyncio.sleep_ms(period_ms)
Bắt tay là nơi duy nhất coroutine này chặn vòng lặp sự kiện; sau đó, mọi s.send / s.recv trả về ngay lập tức (hoặc raise OSError), và await asyncio.sleep_ms giữ cho phần còn lại của chương trình tiếp tục chạy.
9.17.5. Tìm hiểu thêm¶
Tất cả hơn mã hóa đơn thuần TLS -- xác minh chứng chỉ máy chủ HTTPS công cộng, chạy cam như một máy chủ TLS có xác thực, TLS lẫn nhau giữa cam và back-end, chọn khóa và loại khóa, xử lý hết hạn chứng chỉ -- nằm trong Làm việc với chứng chỉ TLS. Phần đó đề cập đến cách tạo chứng chỉ tự ký cho kiểm thử cục bộ, cách lấy chứng chỉ ký bởi CA cho sản xuất, cách đưa chúng vào cam theo định dạng đúng (DER), cách xác minh máy chủ công cộng khi cam là client, cách suy nghĩ về bảo vệ khóa trên thiết bị mà kẻ tấn công có thể tháo rời, và cách lên kế hoạch cho ngày chứng chỉ hết hạn.
Để tham khảo API ssl đầy đủ -- các phiên bản TLS được hỗ trợ, bộ cipher, và các tùy chọn context -- xem ssl --- Mô-đun SSL/TLS.