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 ควรระบุอาร์กิวเมนต์เพียงตัวใดตัวหนึ่งเท่านั้น

get_ciphers() List[str]

รับรายการไซเฟอร์ที่เปิดใช้งาน คืนค่าเป็นรายการสตริง

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-SSL

  • do_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-middle

wrap_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.PROTOCOL_TLS_CLIENT: int

ค่าที่รองรับสำหรับพารามิเตอร์ protocol เลือกโหมดไคลเอนต์ TLS

ssl.PROTOCOL_TLS_SERVER: int

ค่าที่รองรับสำหรับพารามิเตอร์ protocol เลือกโหมดเซิร์ฟเวอร์ TLS

ssl.PROTOCOL_DTLS_CLIENT: int

ค่าที่รองรับสำหรับพารามิเตอร์ protocol เลือกโหมดไคลเอนต์ DTLS

ssl.PROTOCOL_DTLS_SERVER: int

ค่าที่รองรับสำหรับพารามิเตอร์ protocol เลือกโหมดเซิร์ฟเวอร์ DTLS

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 จำเป็นต้องมีใบรับรองที่ถูกต้องจากเพียร์