ssl — מודול SSL/TLS

מודול זה מספק גישה לאמצעי הצפנה ואימות עמיתים של Transport Layer Security (שהיה ידוע בעבר ובהרחבה בשם “Secure Sockets Layer”) עבור network sockets, הן בצד הלקוח והן בצד השרת.

טיפ

חדשים ב-TLS על המצלמה? התחילו עם המדריך עבודה עם אישורי TLS. הוא עובר על בחירת סוגי מפתחות, יצירה והמרה של אישורים לפורמט DER שהמצלמה דורשת, העברתם אל המכשיר, ואימות שרתים ולקוחות – עם דוגמאות עבודה מלאות.

הערה

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 הנתון ומחזיר אובייקט wrapped-socket חדש. מימוש פונקציה זו הוא ליצור תחילה 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]

מקבל רשימה של ה-ciphers המופעלים, המוחזרת כרשימה של מחרוזות.

set_ciphers(ciphers: List[str]) None

מגדיר את ה-ciphers הזמינים עבור sockets שנוצרים עם הקשר זה. ciphers צריך להיות רשימה של מחרוזות בפורמט IANA cipher suite .

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 הבסיסי. לאובייקט המוחזר יש את מתודות ממשק ה-stream הרגילות כמו read(), write() וכו«.

  • server_side בוחר האם ה-wrapped socket נמצא בצד השרת או בצד הלקוח. socket SSL בצד השרת צריך להיווצר מ-socket רגיל המוחזר מ-accept() על socket האזנה שאינו SSL.

  • do_handshake_on_connect קובע האם ה-handshake מתבצע כחלק מ-wrap_socket או שהוא נדחה כדי להתבצע כחלק מהקריאות או הכתיבות הראשוניות. עבור blocking sockets ביצוע ה-handshake באופן מיידי הוא הסטנדרט. עבור non-blocking sockets (כלומר כאשר ה-sock שהועבר אל wrap_socket נמצא במצב non-blocking) יש לדחות בדרך כלל את ה-handshake כי אחרת wrap_socket נחסם עד שהוא מסתיים.

  • server_hostname מיועד לשימוש כלקוח, ומגדיר את שם המארח לבדיקה מול אישור השרת שהתקבל. הוא גם מגדיר את השם עבור Server Name Indication (SNI), ומאפשר לשרת להציג את האישור המתאים.

  • client_id הוא ארגומנט הרחבה ספציפי ל-MicroPython המשמש רק בעת מימוש שרת DTLS. ראו תמיכה ב-DTLS לפרטים.

אזהרה

כברירת מחדל לא מתבצע אימות אישור (ssl.CERT_NONE). לחיבור מאובטח עליכם לאמת את האישור של העמית על ידי הגדרת cert_reqs / SSLContext.verify_mode ל-ssl.CERT_REQUIRED; אחרת החיבור פגיע למתקפות man-in-the-middle.

ה-wrap_socket של CPython מחזיר אובייקט SSLSocket שיש לו מתודות אופייניות ל-sockets, כגון send, recv וכו«. ה-wrap_socket של MicroPython מחזיר אובייקט דומה יותר ל-SSLObject של CPython שאין לו את מתודות ה-socket האלה.

verify_mode

מגדיר או מקבל את ההתנהגות לאימות אישורי עמיתים. חייב להיות אחד מקבועי ה-CERT_*.

הערה

ssl.CERT_REQUIRED דורש שהתאריך/השעה של המכשיר יוגדרו כראוי, למשל באמצעות mpremote rtc --set או ntptime, ויש לציין את server_hostname בצד הלקוח.

תמיכה ב-DTLS

הבדל מ-CPython

זוהי הרחבה של MicroPython.

מודול זה תומך ב-DTLS במצב לקוח ובמצב שרת באמצעות הקבועים PROTOCOL_DTLS_CLIENT ו-PROTOCOL_DTLS_SERVER שניתן להשתמש בהם כארגומנט ה-protocol של SSLContext.

במקרה זה ה-socket הבסיסי צפוי להתנהג כ-datagram socket (כלומר כמו ה-socket שנפתח עם 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(). מזהה זה חייב להיות אובייקט bytes (או דומה) עם מזהה ספציפי-תעבורה המייצג את הלקוח.

    הגישה הפשוטה ביותר היא להמיר את ה-tuple של (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“. הסיבה היא שה-cookie של ”Hello Verify“ של DTLS עדיין אינו ידוע ללקוח. אם אותו לקוח מתחבר בפעם שנייה אז wrap_socket יצליח.

  • DTLS cookies עבור ”Hello Verify“ משויכים לאובייקט ה-SSLContext, ולכן יש להשתמש באותו אובייקט SSLContext כדי לעטוף חיבור עוקב מאותו לקוח. מימוש ה-cookie כולל timeout ובעל שימוש זיכרון קבוע ללא קשר לכמה לקוחות מתחברים, ולכן זה בסדר לעשות שימוש חוזר באותו אובייקט 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. נדרש אישור תקף מהעמית.