ssl --- وحدة SSL/TLS

توفر هذه الوحدة الوصول إلى تسهيلات أمان طبقة النقل (المعروفة سابقاً وعلى نطاق واسع باسم "طبقة المقابس الآمنة") للتشفير ومصادقة الطرف لمقابس الشبكة، سواء في جانب العميل أو جانب الخادم.

نصيحة

جديد على 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 المعطى وتُرجع كائن مقبس مغلّف جديد. تنفيذ هذه الدالة هو أولاً إنشاء 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 هو مسار ملف شهادات جهة الإصدار. cadata هو كائن bytes يحتوي على شهادات جهة الإصدار. يجب تقديم واحدة فقط من هاتين الوسيطتين.

get_ciphers() List[str]

يحصل على قائمة الأنظمة المشفّرة المفعّلة، تُرجَع على شكل قائمة من السلاسل النصية.

set_ciphers(ciphers: List[str]) None

يضبط الأنظمة المشفّرة المتاحة للمقابس المنشأة بهذا السياق. ينبغي أن تكون 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 المعتادة مثل read() وwrite() وما إلى ذلك.

  • تحدّد server_side ما إذا كان المقبس المغلّف في جانب الخادم أم جانب العميل. ينبغي إنشاء مقبس SSL لجانب الخادم من مقبس عادي مُرجَع من accept() على مقبس خادم استماع غير SSL.

  • تحدّد do_handshake_on_connect ما إذا كان المصافحة تُجرى كجزء من wrap_socket أم تؤجَّل لتُجرى كجزء من عمليات القراءة أو الكتابة الأولية. بالنسبة للمقابس الحاجبة، يُعد إجراء المصافحة فوراً هو السلوك المعياري. أما بالنسبة للمقابس غير الحاجبة (أي عندما يكون الـ sock المُمرَّر إلى wrap_socket في الوضع غير الحاجب) فينبغي عموماً تأجيل المصافحة لأن wrap_socket خلاف ذلك يحجب حتى اكتمالها.

  • تُستخدم server_hostname كعميل، وتضبط اسم المضيف الذي يُتحقَّق منه مقابل شهادة الخادم المستلمة. كما تضبط الاسم لإشارة اسم الخادم (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 يحتوي على طرق نمطية للمقابس، مثل send وrecv وما إلى ذلك. أما wrap_socket في MicroPython فتُرجع كائناً أكثر تشابهاً مع كائن SSLObject في CPython الذي لا يحتوي على طرق المقابس هذه.

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.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 (أو ما يشبهه) يحمل معرّفاً خاصاً بالنقل يمثّل العميل.

    أبسط نهج هو تحويل المجموعة (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". وذلك لأن ملف تعريف "Hello Verify" الخاص بـ DTLS غير معروف بعد لدى العميل. وإذا اتصل العميل نفسه مرة ثانية فإن wrap_socket ستنجح.

  • ترتبط ملفات تعريف "Hello Verify" الخاصة بـ DTLS بكائن 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. يُطلب من الطرف تقديم شهادة صالحة.