14.4.5. التحقق من خادم عام (الكاميرا كعميل)¶
كل ما ورد في الصفحة السابقة عن أن العميل "يملك الجذر بالفعل" صحيح بالنسبة للمتصفحات والهواتف وأجهزة الكمبيوتر، لكنه ليس صحيحاً بالنسبة للكاميرا. لا تأتي وحدة ssl في MicroPython بأي مخزن ثقة مدمج: فالكاميرا حديثة التحديث لا تثق بأي جهة تصديق على الإطلاق، والوضع الافتراضي (ssl.CERT_NONE) لا يتحقق من أي شيء ويكون مفتوحاً على مصراعيه أمام هجوم الوسيط. لذا عندما تكون الكاميرا هي العميل الذي يتصل بخادم TLS عام (واجهة برمجة تطبيقات HTTPS، أو وسيط MQTT، ...) وتريد منها أن تتحقق فعلاً من ذلك الخادم، فعليك أن توفّر مرساة الثقة بنفسك.
الآلية مماثلة لمثال العميل ذي التوقيع الذاتي في الشهادات الموقّعة ذاتياً؛ والفرق الوحيد هو أن الملف الذي تحمّله هو شهادة جهة تصديق حقيقية بدلاً من شهادة النظير الخاصة به:
احصل على شهادة جهة التصديق التي ترسي سلسلة الخادم. "الإرساء" يعني الشهادة الموجودة في (أو قرب) أعلى سلسلة الخادم والتي تختارها نقطة انطلاق ثقتك. يرسل خادم TLS شهادته الطرفية وعادة شهادته (شهاداته) الوسيطة؛ لكنه لا يرسل أبداً جذره. عليك أن تحصل على مرساة الثقة تلك بنفسك وبشكل مستقل عن الخادم -- فمجرد الوثوق بأي شيء يسلّمه لك الخادم يبطل الغرض الكامل من التحقق.
اكتشف أولاً أي جهة تصديق أصدرت فعلاً شهادة الخادم. على سبيل المثال، مقابل
openmv.ioopenssl s_client -connect openmv.io:443 -showcerts < /dev/nullتسرد كتلة
Certificate chainكل شهادة مع موضوعها (s:) ومُصدِرها (i:)؛ كما يطبع OpenSSL الأحدث سطريa:(نوع المفتاح) وv:(الصلاحية) اللذين يمكنك تجاهلهما هنا:Certificate chain 0 s:CN=openmv.io i:C=US, O=Let's Encrypt, CN=E8 1 s:C=US, O=Let's Encrypt, CN=E8 i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
الإدخال 0 هو الشهادة الطرفية (
openmv.io)، صادرة عن الشهادة الوسيطةE8. الإدخال 1 هو تلك الشهادة الوسيطة، صادرة عن الجذرISRG Root X1. يسمّي مُصدِر (i:) الإدخال الأعلى الجذرَ -- وهناISRG Root X1. (الشهادة الوسيطة هيE8بدلاً منR10/R11التي ربما رأيتها في مكان آخر لأنopenmv.ioيستخدم شهادة ECDSA؛ توقّع Let's Encrypt الشهادات الطرفية من نوع ECDSA بشهاداتها الوسيطة من سلسلةEوالشهادات الطرفية من نوع RSA بشهاداتها من سلسلةR. وكلاهما يتسلسل إلىISRG Root X1.)يطبع OpenSSL أيضاً سطور
depth=وقد يُبلغ عن الجذر بـVerification: OK. ولا يحدث ذلك إلا لأن جهاز الكمبيوتر لديك يثق أصلاً بـISRG Root X1-- فالخادم لم يرسله (الخادم لا يرسل جذره أبداً)، والكاميرا، إذ لا تملك مخزن ثقة، لن تملكه هي الأخرى. وهذا بالضبط هو سبب وجوب توفيرك له.نزّل ذلك الجذر من الجذور المنشورة الخاصة بجهة التصديق نفسها. يفهرس Let's Encrypt جميع جذوره في صفحة شهادات Let's Encrypt؛ والملف المباشر لـ ISRG Root X1 هو isrgrootx1.pem (كما يوفّرونه مُرمّزاً مسبقاً بصيغة isrgrootx1.der). تنشر جهات التصديق الأخرى جذورها في صفحة "شهادات الجذر" / "المستودع" مماثلة؛ والمجموعة العامة المرجعية هي برنامج جهات تصديق Mozilla (CCADB). تأكّد من أنك جلبت الملف الصحيح بمقارنة بصمته بالقيمة التي تنشرها جهة التصديق (أضف
-inform DERإذا نزّلت ملف.der):openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256إذا كنت تفضّل عدم تتبّع جذر، يمكنك بدلاً من ذلك نسخ الشهادة الوسيطة مباشرةً من مُخرَج
-showcerts(كتلة-----BEGIN CERTIFICATE-----الثانية)، والوثوق بها، وقبول أنه يجب عليك تحديثها كلما دوّرت جهة التصديق الشهادة الوسيطة -- وهو ما يحدث أكثر بكثير من الجذر (انظر المفاضلة أدناه).حوّلها إلى DER، تماماً كما في السابق:
openssl x509 -in isrgrootx1.pem -outform DER -out ca.derانسخ
ca.derإلى الكاميرا (نظام الملفات أو ROMFS) وحمّلها كمرساة ثقة:import socket import ssl import ntptime ntptime.settime() # validity check needs the clock addr = socket.getaddrinfo("api.example.com", 443)[0][-1] sock = socket.socket() sock.connect(addr) 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="api.example.com")
server_hostnameمطلوب هنا: فهو يقود SNI وهو الاسم الذي يُتحقق منه مقابلsubjectAltNameفي شهادة الخادم.
نصيحة
اختصار الحالة الشائعة. Let's Encrypt هي جهة التصديق العامة الأوسع استخداماً، وتتسلسل كل من شهاداتها من نوعي RSA و ECDSA حالياً إلى ISRG Root X1 (كما يبيّن مثال openmv.io أعلاه). إذا كانت الخوادم التي تتحدث معها كاميرتك تستخدم Let's Encrypt، فيمكنك تخطّي الفحص كلياً: ما عليك سوى وضع isrgrootx1.der على الكاميرا وتمريره إلى load_verify_locations.
هذا لا يجعل TLS يعمل مع كل موقع. فالخادم الذي تأتي شهادته من جهة تصديق مختلفة (DigiCert أو Google Trust Services أو Amazon أو Sectigo، ...) سيظل يفشل في التحقق، ولأن الكاميرا تثق بشهادة DER واحدة لكل ssl.SSLContext فلا يمكنك تجميع كل جذر بالطريقة التي يفعلها المتصفح. عند الشك، حدّد جهة التصديق الفعلية للخادم كما هو موضّح أعلاه وثق بذلك الجذر.
أي شهادة تثق بها هي مسألة مفاضلة:
الجذر (موصى به). طويل العمر -- غالباً عقوداً -- بحيث نادراً ما يتغيّر
ca.der. يتطلّب من الخادم إرسال شهادته الوسيطة حتى يتمكن mbedTLS من بناء المسار الطرفية ← الوسيطة ← جذرك الموثوق؛ وعملياً كل خادم عام مُهيّأ بشكل صحيح يفعل ذلك.الشهادة الوسيطة. تعمل أيضاً، وتظل تعمل حتى لو أغفل خادم إرسال شهادته الوسيطة، لكن الشهادات الوسيطة تُدوّر أكثر بكثير من الجذور، لذا سيتعيّن عليك تحديث
ca.derبوتيرة أكبر.الشهادة الطرفية نفسها (تثبيت الشهادة). الأكثر إحكاماً، لكن الشهادة الطرفية تتغيّر مع كل تجديد -- كل 90 يوماً تقريباً مع Let's Encrypt -- لذا لا يكون هذا منطقياً إلا عندما تتحكم أنت أيضاً بالخادم وتستطيع دفع التثبيت الجديد إلى كل كاميرا بالتزامن. وهذا بالضبط ما يفعله مثال العميل ذي التوقيع الذاتي.
ملاحظة
تأخذ ssl.SSLContext.load_verify_locations() شهادة جهة تصديق واحدة مُرمّزة بصيغة DER، لذا تثق الكاميرا بمرساة واحدة بالضبط في كل مرة. للوصول إلى خوادم تحت جهات تصديق مختلفة، استخدم ssl.SSLContext منفصلاً لكل مرساة. ولأن تلك الشهادة نفسها ستنتهي صلاحيتها في النهاية أو تُدوّرها جهة التصديق، عاملها مثل أي شهادة أخرى على الجهاز.