مرحبا يا هبر!
في الآونة الأخيرة ، اضطررت إلى ربط SSL بالمصادقة المتبادلة إلى Spring Web Reactive Webclient. قد يبدو هذا أمرًا بسيطًا ، ولكنه أدى إلى تجول في مصادر JDK مع نهاية غير متوقعة. تم اكتساب الخبرة لمقال كامل ، والذي قد يكون مفيدًا للمهندسين في المهام اليومية أو استعدادًا لإجراء مقابلة.
بيان المشكلة
هناك خدمة REST على جانب العميل تعمل من خلال HTTPS.
يجب عليك الوصول إليه من تطبيق Java العميل.أول شيء تم إعطاؤه لي في هذا المسعى كان ملفين بملحق .pem - شهادة عميل ومفتاح خاص. لقد راجعت أداءهم باستخدام Postman: لقد حددت المسارات لهم في الإعدادات ، وبعد سحب الطلب ، تأكدت من أن الخادم يستجيب بـ 200 OK ونص استجابة ذات مغزى. بشكل منفصل ، تحققت من أنه بدون شهادة العميل ، يعرض الخادم حالة HTTP 500 ورسالة قصيرة في نص الاستجابة بأن "استثناء الأمان" حدث برمز معين.
بعد ذلك ، يجب عليك تكوين تطبيق Java العميل بشكل صحيح.
بالنسبة لطلبات REST ، استخدمت Spring Web Reactive WebClient مع الإدخال / الإخراج غير المحظور.
تحتوي الوثائق
على مثال لكيفية تخصيصها عن طريق رمي كائن SslContext إليها ، والذي يخزن فقط الشهادات والمفاتيح الخاصة.
كان التكوين الخاص بي في نسخة مبسطة يكاد يتم لصق النسخ من الوثائق:
SslContext sslContext = SslContextBuilder .forClient() .keyManager(…) .build(); ClientHttpConnector connector = new ReactorClientHttpConnector( builder -> builder.sslContext(sslContext)); WebClient webClient = WebClient.builder() .clientConnector(connector).build();
وفقًا لمبدأ TDD ، كتبت أيضًا اختبارًا يستخدم WebTestClient بدلاً من WebClient ، والذي يعرض مجموعة من معلومات التصحيح. كان التأكيد الأول على هذا النحو:
webTestClient .post() .uri([ ]) .body(BodyInserters.fromObject([ , , ])) .exchange() .expectStatus().isOk()
لم ينجح هذا الاختبار البسيط على الفور: أرجع الخادم 500 بنفس النص كما لو أن Postman لم يحدد شهادة عميل.
بشكل منفصل ، ألاحظ أنه أثناء تصحيح الأخطاء ، قمت بتشغيل الخيار "عدم التحقق من شهادة الخادم" ، أي أنني مررت مثيل InsecureTrustManagerFactory مثل TrustManager لـ SslContext. كان هذا الإجراء زائدا ، لكنه استبعد نصف الخيارات بالتأكيد.
لم تسلط معلومات تصحيح الأخطاء في الاختبار الضوء على المشكلة ، ولكن يبدو أن هناك خطأ ما في مرحلة مصافحة SSL ، لذلك قررت أن أقارن بمزيد من التفصيل كيف يحدث الاتصال في كلتا الحالتين: لكل من Postman وعميل Java. كل هذا يمكن رؤيته باستخدام Wireshark - هذا هو محلل حركة مرور الشبكة الشهير. في الوقت نفسه ، رأيت كيف تحدث مصافحة SSL بمصادقة ثنائية الاتجاه ، إذا جاز التعبير ، على الهواء مباشرة (يحبون أن يسألوا ذلك أثناء المقابلات)
- بادئ ذي بدء ، يرسل العميل رسالة Hello Client تحتوي على معلومات وصفية مثل إصدار البروتوكول وقائمة خوارزميات التشفير التي يدعمها
- استجابة لذلك ، يرسل الخادم على الفور حزمة من الرسائل التالية: Server Hello ، Certificate ، Server Key Exchange ، طلب Certificate ، Server Hello Done .
يشير Server Hello إلى خوارزمية التشفير التي حددها الخادم (مجموعة التشفير). داخل الشهادة تكمن شهادة الخادم. تبادل الخادم الرئيسي يحمل بعض المعلومات اللازمة للتشفير ، اعتمادًا على الخوارزمية المختارة (نحن لسنا مهتمين بالتفاصيل الآن ، لذلك نفترض أن هذا مجرد مفتاح عام ، على الرغم من أن هذا غير صحيح!). أيضًا ، في حالة المصادقة الثنائية ، في رسالة طلب الشهادة ، يقوم الخادم بتقديم طلب للحصول على شهادة عميل ويشرح التنسيقات التي يدعمها والمصدر الذي يثق به. - بعد استلام هذه المعلومات ، يتحقق العميل من شهادة الخادم ويرسل شهادته و "مفتاحه العام" ومعلومات أخرى في الرسائل التالية: الشهادة ، وتبادل مفتاح العميل ، والتحقق من الشهادة . آخر رسالة هي ChangeCipherSpec ، تشير إلى أن كل الاتصالات الإضافية ستحدث في شكل مشفر
- أخيرًا ، بعد كل هذه المراوغة ، سيتحقق الخادم من شهادة العميل ، وإذا كان كل شيء على ما يرام ، فسيعطي إجابة.
بعد خمس عشرة دقيقة من الالتصاق بحركة المرور ، لاحظت أن عميل Java ، استجابة لطلب
الشهادة من الخادم ، لسبب ما لا يرسل شهادته ، على عكس عميل Postman. أي ، هناك رسالة شهادة ، لكنها فارغة.
بعد ذلك ، أحتاج إلى النظر أولاً في
مواصفات بروتوكول TLS ، والتي تنص حرفياً على ما يلي:
إذا كانت قائمة شهادات_السلطات في رسالة طلب الشهادة غير فارغة ، فيجب إصدار إحدى الشهادات في سلسلة الشهادات من قبل أحد مراجع التصديق المدرجة.
نحن نتحدث عن قائمة
Certificate_authorities المحددة في رسالة
طلب الشهادة الواردة من الخادم. يجب أن يتم توقيع شهادة العميل (واحدة على الأقل من السلاسل) من قبل أحد المصدرين المدرجين في هذه القائمة. نسمي هذا
الاختيار X.لم أكن أعرف عن هذا الشرط ووجدته عندما وصلت إلى أعماق JDK في تصحيح الأخطاء (لدي JDK9). يستخدم HttpClient من Netty ، الذي يكمن وراء Spring WebClient ، SslEngine من JDK بشكل افتراضي. بدلاً من ذلك ، يمكنك تبديله إلى موفر OpenSSL عن طريق إضافة التبعيات الضرورية ، لكنني في النهاية لم أكن بحاجة إلى ذلك.
لذلك ، قمت بتعيين نقاط توقف داخل فئة sun.security.ssl.ClientHandshaker وفي معالج الخادم الرسالة الترحيبية وجدت شيك X لم يمر: لم يكن أي من المصدرين في سلسلة شهادات العميل في قائمة المصدرين الذين يثق بهم الخادم ( من رسالة
طلب الشهادة من الخادم).
التفت إلى العميل للحصول على شهادة جديدة ، لكن العميل اعترض على أن كل شيء يعمل بشكل جيد بالنسبة له ، وسلم Python برنامجًا نصيًا ، والذي عادة ما يتحقق من الشهادات للحصول على وظيفة. لم يفعل النص أكثر من إرسال طلب HTTPS باستخدام مكتبة الطلبات ، وأعاد 200 OK. لقد فوجئت أخيرًا عندما أعاد الضفيرة القديمة الجيدة 200 OK. تذكرت على الفور النكتة: "الشركة بأكملها تخرج عن الدرج ، ملازم واحد في الساق".
تعتبر Curl ، بالطبع ، أداة ذات سمعة جيدة ، ولكن معيار TLS ليس أيضًا قطعة من ورق التواليت. لم أكن أعرف أي شيء آخر أتحقق منه ، صعدت أتجول بلا هدف حول وثائق التجعيد ، وعلى Github ، حيث وجدت
مثل هذا الخطأ الشهير.
وصف المراسل اختبار X بالضبط: في حالة الالتفاف مع الخلفية الافتراضية (OpenSSL) ، لم يتم تشغيله ، على عكس curl مع الواجهة الخلفية GnuTLS. لم أكن كسولًا جدًا ، جمعت تجعيد الشعر من المصدر مع الخيار
- مع gnutls ، وأرسلت طلبًا يعاني منذ فترة طويلة. وأخيرًا ، أعاد عميل آخر ، إلى جانب JDK ، حالة HTTP 500 جنبًا إلى جنب مع "استثناء Secutiry"!
لقد كتبت عن هذا إلى العميل ردًا على حجة "حسنًا ، يعمل الضفيرة" ، وحصلت على شهادة جديدة ، تم إعادة إنشاؤها وتثبيتها بدقة على الخادم. باستخدامه ، عملت تهيئة WebClient على ما يرام. نهاية سعيدة.
استغرقت ملحمة SSL أكثر من أسبوعين مع جميع المراسلات (شملت دراسة سجلات جافا التفصيلية ، واختيار رمز مشروع آخر يعمل لصالح العميل ، واختيار أنفه فقط).
ما أذهلني لفترة طويلة ، بالإضافة إلى الاختلاف في سلوك العميل ، هو أنه تم تكوين الخادم بطريقة تطلبها الشهادة ، ولكن لم يتم التحقق منها. ومع ذلك ، هناك تفسيرات لذلك في المواصفات:
أيضًا ، إذا كان بعض جوانب سلسلة الشهادات غير مقبولة (على سبيل المثال ، لم يتم التوقيع عليها من قبل المرجع المصدق المعروف والموثوق) ، فقد يقوم الخادم وفقًا لتقديره إما بمواصلة المصافحة (مع مراعاة عدم مصادقة العميل) أو إرسال تنبيه قاتل.