14.4.3. الشهادات الموقّعة ذاتياً¶
الشهادة الموقّعة ذاتياً هي أسرع طريقة لتشغيل TLS بين جهازين تتحكم بهما: يثق الطرفان بشهادة واحدة تولّدها بنفسك. وهي تغطي كل عملية نشر تقوم فيها بتهيئة طرفي الاتصال -- ولا تدخل الجهة المصدّقة العامة (Certificate Authority) في الصورة إلا عندما يتعين على عملاء من طرف ثالث الاتصال دون أن يُطلب منهم الوثوق بشهادة مخصصة.
14.4.3.1. إنشاء شهادة موقّعة ذاتياً¶
شغّل OpenSSL على جهاز التطوير لديك. إن subjectAltName (SAN) هو ما يتحقق منه عملاء TLS الحديثون أثناء التحقق من اسم المضيف، لذا اضبطه على اسم/أسماء المضيف و/أو عنوان/عناوين IP التي سيستخدمها العملاء للوصول إلى الكاميرا (إن CN وحده قديم ويتجاهله كثير من العملاء). استبدل DNS:openmv / IP:192.168.1.50 بالعنوان الذي يتصل به عملاؤك فعلاً.
ECDSA P-256 -- موصى به:
# Generate a P-256 private key.
openssl ecparam -name prime256v1 -genkey -noout -out server.key
# Self-signed certificate valid for one year, with a SAN.
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
ECDSA P-384 -- أقوى، أكبر/أبطأ:
openssl ecparam -name secp384r1 -genkey -noout -out server.key
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
RSA-2048 -- أقصى توافق:
openssl req -new -x509 -newkey rsa:2048 -nodes -keyout server.key \
-out server.crt -days 365 -subj "/CN=openmv" \
-addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
ملاحظة
تُنشأ شهادة العميل (المستخدمة للمصادقة المتبادلة، أدناه) بنفس هذه الأوامر تماماً -- فلا يوجد شيء خاص بالعميل في الشهادة نفسها. ما عليك سوى توليد زوج ثانٍ مستقل من المفتاح/الشهادة بأسماء مختلفة (مثلاً client.key / client.crt) واستخدامه على العميل كما هو موضح في مثال mTLS. إن subjectAltName يهم فقط بالنسبة للطرف الذي يتحقق النظير من اسم مضيفه (يتحقق العميل من اسم الخادم؛ ولا شيء يتحقق من العميل)، لذا يمكن حذفه لشهادة خاصة بالعميل فقط. كما أن -subj / CN هو مجرد تسمية على شهادة العميل -- فجانب الخادم هنا يتحقق فقط من أن الشهادة تتسلسل إلى جهة مصدّقة (CA) موثوقة، وهو لا يطابق الاسم أبداً -- لذا اضبطه على ما يعرّف ذلك العميل (مثلاً /CN=sensor-01). أبقِ على قيمة -subj ما على أي حال، حتى يتمكن OpenSSL من توليد الشهادة دون تفاعل.
يُحدد عمر الشهادة بواسطة -days؛ تنتهي صلاحية الشهادات ويجب إعادة توليدها وإعادة نشرها قبل ذلك.
14.4.3.2. التحويل إلى DER¶
حوّل كلاً من الشهادة والمفتاح الخاص إلى DER قبل نسخهما إلى الكاميرا:
openssl x509 -in server.crt -outform DER -out server.der
openssl pkey -in server.key -outform DER -out server.key.der
14.4.3.3. نسخ الملفات إلى الكاميرا¶
انسخ ملفات DER إلى نظام ملفات الكاميرا -- على سبيل المثال بسحبها إلى محرك USB الخاص بـ OpenMV Cam، أو باستخدام mpremote cp server.der : و mpremote cp server.key.der :. على الجانب الذي يقوم بالتحقق، انسخ أيضاً شهادة الجهة المصدّقة (CA) / شهادة النظير بصيغة DER.
ليس من الضروري أن تعيش ملفات DER على نظام الملفات القابل للكتابة. يمكن لـ MicroPython أيضاً تركيب صورة ROMFS للقراءة فقط عند /rom، والشهادات الموضوعة هناك تُحمَّل تماماً مثل أي ملف آخر -- مثلاً ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). تُحضَّر صورة ROMFS على جهاز التطوير لديك وتكون للقراءة فقط في وقت التشغيل، لذا لا يمكن تغيير الشهادة على الجهاز -- وهو أمر مفيد لإحكام إغلاق وحدة إنتاجية. لاحظ أن المفتاح الخاص المخزن في ROMFS لا يزال قابلاً للقراءة من قبل البرنامج النصي الذي يعمل على الكاميرا؛ فـ ROMFS يحمي من التعديل وليس الاستخراج. ولا يمكن استبدال شهادة مقيمة في ROMFS إلا بإعادة بناء وومض الصورة.
14.4.3.4. استخدام الشهادة¶
عميل كامل يضبط الساعة، ويفتح مقبساً، ويتحقق من خادم موقّع ذاتياً، ويتبادل البيانات:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
# Open a plain TCP connection.
addr = socket.getaddrinfo("openmv", 8443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS, trusting the server's self-signed certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
ssock = ctx.wrap_socket(sock, server_hostname="openmv")
ssock.write(b"hello\n")
print(ssock.read())
ssock.close()
خادم كامل يقدّم شهادته ومفتاحه:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
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)
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
print(sclient.read())
sclient.close()
بالنسبة لـ المصادقة المتبادلة (mTLS) يطلب الخادم بالإضافة إلى ذلك شهادة عميل ويتحقق منها، ويقدّم العميل واحدة خاصة به:
# Server side: also demand and verify a client certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="client.der")
# Client side: present a certificate of our own.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_cert_chain("client.der", "client.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
انظر وثائق الوحدة ssl للاطلاع على واجهة API الكاملة.
ملاحظة
كل ما في هذه الصفحة ينطبق دون تغيير على DTLS (TLS فوق UDP). فالمفاتيح والشهادات وصيغة DER ونموذج الثقة ومخاوف انتهاء الصلاحية واستدعاءات load_cert_chain / load_verify_locations متطابقة؛ والاختلاف الوحيد هو في النقل -- إذ تغلّف مقبس socket.SOCK_DGRAM وتختار ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER بدلاً من ثوابت بروتوكول TLS. التعقيد الإضافي الوحيد هو ملف تعريف ارتباط مضاد للانتحال من جانب الخادم -- إذ يُتوقع أن يفشل الاتصال الأول من عميل جديد ويعيد العميل المحاولة ببساطة؛ انظر دعم DTLS للتفاصيل.