ssl --- โมดูล SSL/TLS¶
โมดูลนี้ให้การเข้าถึงการเข้ารหัส Transport Layer Security (เดิมและเป็นที่รู้จักกันดีในชื่อ "Secure Sockets Layer") และระบบยืนยันตัวตนสำหรับซ็อกเก็ตเครือข่าย ทั้งฝั่งไคลเอนต์และเซิร์ฟเวอร์
Tip
ยังใหม่กับ TLS บนกล้อง? เริ่มต้นด้วยบทเรียน การทำงานกับ TLS certificate ซึ่งจะแนะนำขั้นตอนการเลือกประเภทคีย์ การสร้างและแปลงใบรับรองเป็นรูปแบบ DER ที่กล้องต้องการ การนำใบรับรองขึ้นไปบนอุปกรณ์ และการตรวจสอบเซิร์ฟเวอร์และไคลเอนต์ -- พร้อมตัวอย่างที่ใช้งานได้จริงอย่างครบถ้วน
Note
MicroPython ไม่ได้นำ ssl.SSLError มาใช้งาน ความล้มเหลวของ SSL/TLS จะถูกโยนเป็น OSError แทน
ตัวอย่าง¶
TLS ไคลเอนต์ ตรวจสอบใบรับรองของเซิร์ฟเวอร์กับใบรับรอง CA (ในรูปแบบ DER) ที่เก็บบนระบบไฟล์:
import socket
import ssl
import ntptime
# CERT_REQUIRED checks the certificate's validity dates, so the clock
# must be set (see the certificates tutorial linked above).
ntptime.settime()
# Open a plain TCP connection.
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS and require a valid certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="ca.der")
ssock = ctx.wrap_socket(sock, server_hostname="example.com")
ssock.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(ssock.read())
ssock.close()
สำหรับการเชื่อมต่อที่รวดเร็วแบบ ไม่ปลอดภัย (ไม่มีการตรวจสอบใบรับรอง) สามารถใช้ฟังก์ชันอำนวยความสะดวก ssl.wrap_socket() แทน:
ssock = ssl.wrap_socket(sock, server_hostname="example.com")
TLS เซิร์ฟเวอร์ นำเสนอใบรับรองของตนเองและคีย์ส่วนตัว (รูปแบบ DER):
import socket
import ssl
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(socket.getaddrinfo("0.0.0.0", 8443)[0][-1])
sock.listen(1)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key")
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
sclient.close()
ฟังก์ชัน¶
- ssl.wrap_socket(sock: Any, server_side: bool = False, key: bytes | None = None, cert: bytes | None = None, cert_reqs: int = CERT_NONE, cadata: bytes | None = None, server_hostname: str | None = None, do_handshake: bool = True) Any¶
ห่อหุ้ม sock ที่กำหนดและคืนค่าออบเจ็กต์ซ็อกเก็ตที่ห่อหุ้มใหม่ การนำฟังก์ชันนี้ไปใช้งานคือการสร้าง
SSLContextก่อน จากนั้นเรียกเมธอดSSLContext.wrap_socket()บนออบเจ็กต์บริบทนั้น อาร์กิวเมนต์ sock, server_side และ server_hostname จะถูกส่งผ่านไปยังการเรียกเมธอดโดยไม่เปลี่ยนแปลง อาร์กิวเมนต์ do_handshake จะถูกส่งผ่านเป็น do_handshake_on_connect อาร์กิวเมนต์ที่เหลือมีพฤติกรรมดังนี้:cert_reqs กำหนดว่าเพียร์ (เซิร์ฟเวอร์หรือไคลเอนต์) ต้องนำเสนอใบรับรองที่ถูกต้องหรือไม่ โปรดทราบว่า
ssl.CERT_NONEและssl.CERT_OPTIONALไม่ตรวจสอบใบรับรองใด ๆ มีเพียงssl.CERT_REQUIREDเท่านั้นที่ตรวจสอบcadata คือออบเจ็กต์ bytes ที่มีห่วงโซ่ใบรับรอง CA (ในรูปแบบ DER) ที่จะตรวจสอบใบรับรองของเพียร์ ปัจจุบันรองรับเพียงใบรับรอง DER เดียวเท่านั้น
คลาส¶
- class ssl.SSLContext(protocol: int, /)¶
สร้างอินสแตนซ์ SSLContext ใหม่ อาร์กิวเมนต์ protocol ต้องเป็นหนึ่งในค่าคงที่
PROTOCOL_*- load_cert_chain(certfile: str | bytes, keyfile: str | bytes) None¶
โหลดคีย์ส่วนตัวและใบรับรองที่ตรงกัน certfile คือสตริงที่มีเส้นทางไฟล์ของใบรับรอง keyfile คือสตริงที่มีเส้นทางไฟล์ของคีย์ส่วนตัว
ข้อแตกต่างจาก CPython
ส่วนขยาย MicroPython: certfile และ keyfile สามารถเป็นออบเจ็กต์ bytes แทนสตริงได้ ซึ่งในกรณีนั้นจะถูกตีความเป็นข้อมูลใบรับรอง/คีย์จริง
- load_verify_locations(cafile: str | None = None, cadata: bytes | None = None) None¶
โหลดห่วงโซ่ใบรับรอง CA ที่จะตรวจสอบใบรับรองของเพียร์ cafile คือเส้นทางไฟล์ของใบรับรอง CA cadata คือออบเจ็กต์ bytes ที่มีใบรับรอง CA ควรระบุอาร์กิวเมนต์เพียงตัวใดตัวหนึ่งเท่านั้น
- set_ciphers(ciphers: List[str]) None¶
ตั้งค่าไซเฟอร์ที่ใช้งานได้สำหรับซ็อกเก็ตที่สร้างด้วยบริบทนี้ ciphers ควรเป็นรายการสตริงในรูปแบบ IANA cipher suite format
- wrap_socket(sock: Any, *, server_side: bool = False, do_handshake_on_connect: bool = True, server_hostname: str | None = None, client_id: bytes | None = None) Any¶
รับ stream sock (โดยปกติคืออินสแตนซ์ socket.socket ประเภท
SOCK_STREAM) และคืนค่าอินสแตนซ์ของ ssl.SSLSocket ที่ห่อหุ้มสตรีมพื้นฐาน ออบเจ็กต์ที่คืนค่ามีเมธอดอินเทอร์เฟซ stream ตามปกติ เช่นread(),write()เป็นต้นserver_side เลือกว่าซ็อกเก็ตที่ห่อหุ้มอยู่ฝั่งเซิร์ฟเวอร์หรือไคลเอนต์ ซ็อกเก็ต SSL ฝั่งเซิร์ฟเวอร์ควรสร้างจากซ็อกเก็ตปกติที่คืนมาจาก
accept()บนซ็อกเก็ตเซิร์ฟเวอร์ที่รับฟังแบบ non-SSLdo_handshake_on_connect กำหนดว่าการ handshake จะทำเป็นส่วนหนึ่งของ
wrap_socketหรือจะถูกเลื่อนออกไปทำเป็นส่วนหนึ่งของการอ่านหรือเขียนครั้งแรก สำหรับซ็อกเก็ตแบบ blocking การ handshake ทันทีเป็นมาตรฐาน สำหรับซ็อกเก็ตแบบ non-blocking (กล่าวคือเมื่อ sock ที่ส่งเข้าwrap_socketอยู่ในโหมด non-blocking) การ handshake ควรถูกเลื่อนออกไปเพราะมิฉะนั้นwrap_socketจะบล็อกจนกว่าจะเสร็จสิ้นserver_hostname ใช้ในฐานะไคลเอนต์ และตั้งค่าชื่อโฮสต์เพื่อตรวจสอบกับใบรับรองเซิร์ฟเวอร์ที่ได้รับ นอกจากนี้ยังตั้งชื่อสำหรับ Server Name Indication (SNI) เพื่อให้เซิร์ฟเวอร์นำเสนอใบรับรองที่เหมาะสม
client_id คืออาร์กิวเมนต์ส่วนขยายเฉพาะของ MicroPython ที่ใช้เฉพาะเมื่อนำ DTLS Server ไปใช้งาน ดู รองรับ DTLS สำหรับรายละเอียด
Warning
โดยค่าเริ่มต้นจะไม่มีการตรวจสอบใบรับรอง (
ssl.CERT_NONE) สำหรับการเชื่อมต่อที่ปลอดภัย คุณต้องตรวจสอบใบรับรองของเพียร์โดยตั้งค่า cert_reqs /SSLContext.verify_modeเป็นssl.CERT_REQUIREDมิฉะนั้นการเชื่อมต่อจะเสี่ยงต่อการโจมตีแบบ man-in-the-middlewrap_socketของ CPython คืนค่าออบเจ็กต์SSLSocketที่มีเมธอดทั่วไปสำหรับซ็อกเก็ต เช่นsend,recvเป็นต้นwrap_socketของ MicroPython คืนค่าออบเจ็กต์ที่คล้ายกับSSLObjectของ CPython มากกว่า ซึ่งไม่มีเมธอดซ็อกเก็ตเหล่านี้
- verify_mode¶
ตั้งค่าหรือรับพฤติกรรมสำหรับการตรวจสอบใบรับรองเพียร์ ต้องเป็นหนึ่งในค่าคงที่
CERT_*Note
ssl.CERT_REQUIREDกำหนดให้วันที่/เวลาของอุปกรณ์ต้องตั้งค่าอย่างถูกต้อง เช่น ใช้ mpremote rtc --set หรือntptimeและต้องระบุserver_hostnameเมื่ออยู่ฝั่งไคลเอนต์
รองรับ DTLS¶
ข้อแตกต่างจาก CPython
นี่คือส่วนขยายของ MicroPython
โมดูลนี้รองรับ DTLS ในโหมดไคลเอนต์และเซิร์ฟเวอร์ผ่านค่าคงที่ PROTOCOL_DTLS_CLIENT และ PROTOCOL_DTLS_SERVER ที่ใช้เป็นอาร์กิวเมนต์ protocol ของ SSLContext
ในกรณีนี้ซ็อกเก็ตพื้นฐานคาดว่าจะทำงานเป็นซ็อกเก็ต datagram (กล่าวคือเหมือนซ็อกเก็ตที่เปิดด้วย socket.socket โดยใช้ socket.AF_INET เป็น af และ socket.SOCK_DGRAM เป็น type)
รองรับเซิร์ฟเวอร์ DTLS¶
การรองรับเซิร์ฟเวอร์ DTLS ของ MicroPython ได้รับการกำหนดค่าด้วย "Hello Verify" ตามที่กำหนดสำหรับ DTLS 1.2 ซึ่งเป็นแบบโปร่งใสสำหรับไคลเอนต์ DTLS แต่มีข้อควรพิจารณาที่เกี่ยวข้องเมื่อนำเซิร์ฟเวอร์ DTLS ไปใช้งานใน MicroPython:
เซิร์ฟเวอร์ควรส่งอาร์กิวเมนต์เพิ่มเติม client_id เมื่อเรียก
SSLContext.wrap_socket()ID นี้ต้องเป็นออบเจ็กต์bytes(หรือคล้ายกัน) พร้อมตัวระบุเฉพาะการขนส่งที่แทนไคลเอนต์วิธีที่ง่ายที่สุดคือการแปลงทูเพิล
(client_ip, client_port)ที่คืนมาจากsocket.recv_from()เป็นสตริงไบต์ กล่าวคือ:_, client_addr = sock.recvfrom(1, socket.MSG_PEEK) sock.connect(client_addr) # Connect back to the client sock = ssl_ctx.wrap_socket(sock, server_side=True, client_id=repr(client_addr).encode())
ครั้งแรกที่ไคลเอนต์เชื่อมต่อ การเรียก
wrap_socketของเซิร์ฟเวอร์จะล้มเหลวด้วยข้อผิดพลาดOSErrorว่า "Hello Verify Required" เนื่องจากคุกกี้ DTLS "Hello Verify" ยังไม่เป็นที่รู้จักของไคลเอนต์ หากไคลเอนต์เดิมเชื่อมต่อเป็นครั้งที่สองแล้วwrap_socketจะสำเร็จคุกกี้ DTLS สำหรับ "Hello Verify" ผูกกับออบเจ็กต์
SSLContextดังนั้นควรใช้ออบเจ็กต์SSLContextเดิมในการห่อหุ้มการเชื่อมต่อถัดไปจากไคลเอนต์เดิม การนำคุกกี้ไปใช้มีการหมดเวลาและใช้หน่วยความจำคงที่โดยไม่คำนึงว่าจะมีไคลเอนต์เชื่อมต่อกี่ราย ดังนั้นจึงไม่เป็นปัญหาที่จะนำออบเจ็กต์SSLContextเดิมกลับมาใช้ตลอดอายุการใช้งานของเซิร์ฟเวอร์
ค่าคงที่¶
- ssl.CERT_NONE: int¶
ค่าที่รองรับสำหรับพารามิเตอร์ cert_reqs และแอตทริบิวต์
SSLContext.verify_modeไม่มีการตรวจสอบใบรับรองบนเพียร์
- ssl.CERT_OPTIONAL: int¶
ค่าที่รองรับสำหรับพารามิเตอร์ cert_reqs และแอตทริบิวต์
SSLContext.verify_modeการตรวจสอบใบรับรองเป็นทางเลือก โปรดทราบว่าบน OpenMV Cam ค่านี้จะทำงานเหมือนssl.CERT_NONE
- ssl.CERT_REQUIRED: int¶
ค่าที่รองรับสำหรับพารามิเตอร์ cert_reqs และแอตทริบิวต์
SSLContext.verify_modeจำเป็นต้องมีใบรับรองที่ถูกต้องจากเพียร์