नमस्कार, हेब्र!
हाल ही में, मुझे एसएसएल को स्प्रिंग रिएक्टिव वेबक्लाइंट के लिए पारस्परिक प्रमाणीकरण के साथ खराब करना पड़ा। ऐसा लगता है कि एक साधारण मामला है, लेकिन यह एक अप्रत्याशित अंत के साथ JDK स्रोतों में भटकने का परिणाम है। अनुभव एक पूरे लेख के लिए प्राप्त किया गया था, जो रोजमर्रा के कार्यों में इंजीनियरों के लिए उपयोगी हो सकता है या एक साक्षात्कार के लिए तैयारी में हो सकता है।
समस्या का बयान
ग्राहक पक्ष पर एक REST सेवा है जो HTTPS के माध्यम से काम करती है।
आपको इसे क्लाइंट जावा एप्लिकेशन से एक्सेस करना होगा।इस खोज में मुझे जो पहली चीज़ दी गई, वह थी .pem के साथ 2 फाइलें - एक ग्राहक प्रमाणपत्र और एक निजी कुंजी। मैंने पोस्टमैन का उपयोग करते हुए उनके प्रदर्शन की जांच की: मैंने उन्हें सेटिंग्स में पथ निर्दिष्ट किया और, एक अनुरोध निकाला, यह सुनिश्चित किया कि सर्वर 200 ओके और एक सार्थक प्रतिक्रिया बॉडी के साथ प्रतिक्रिया करता है। अलग-अलग, मैंने जाँच की कि क्लाइंट सर्टिफिकेट के बिना, सर्वर 500 की HTTP स्थिति और प्रतिक्रिया के शरीर में एक छोटा संदेश देता है जो एक विशिष्ट कोड के साथ "सुरक्षा अपवाद" हुआ।
अगला, आपको क्लाइंट जावा एप्लिकेशन को सही ढंग से कॉन्फ़िगर करना चाहिए।
अन्य अनुरोधों के लिए, मैंने गैर-अवरोधक I / O के साथ स्प्रिंग रिएक्टिव वेबक्लाइंट का उपयोग किया।
प्रलेखन का
एक उदाहरण है कि इसे कैसे SslContext ऑब्जेक्ट को फेंककर अनुकूलित किया जा सकता है, जो सिर्फ प्रमाण पत्र और निजी कुंजी संग्रहीत करता है।
सरलीकृत संस्करण में मेरा कॉन्फ़िगरेशन लगभग दस्तावेज़ से कॉपी-पेस्ट था:
SslContext sslContext = SslContextBuilder .forClient() .keyManager(…) .build(); ClientHttpConnector connector = new ReactorClientHttpConnector( builder -> builder.sslContext(sslContext)); WebClient webClient = WebClient.builder() .clientConnector(connector).build();
टीडीडी के सिद्धांत का पालन करते हुए, मैंने एक परीक्षण भी लिखा, जो वेबक्लेयर के बजाय वेबटेस्टिएंट का उपयोग करता है, जो डिबगिंग जानकारी का एक गुच्छा प्रदर्शित करता है। इस तरह का पहला जोर था:
webTestClient .post() .uri([ ]) .body(BodyInserters.fromObject([ , , ])) .exchange() .expectStatus().isOk()
यह सरल परीक्षण तुरंत पास नहीं हुआ: सर्वर ने उसी निकाय के साथ 500 वापस कर दिया जैसे कि डाकिया ने क्लाइंट प्रमाण पत्र निर्दिष्ट नहीं किया है।
अलग-अलग, मैं ध्यान देता हूं कि डिबगिंग के दौरान, मैंने विकल्प "सर्वर सर्टिफिकेट की जांच न करें" चालू किया, अर्थात्, मैंने SslContext के लिए TrustManager के रूप में InsecureTrustManagerFactory उदाहरण दिया। यह उपाय निरर्थक था, लेकिन सुनिश्चित करने के लिए आधे विकल्पों को छोड़कर।
परीक्षण में डिबगिंग की जानकारी समस्या पर प्रकाश नहीं डालती थी, लेकिन ऐसा लग रहा था कि SSL हैंडशेक चरण में कुछ गलत हो गया है, इसलिए मैंने अधिक विस्तार से तुलना करने का फैसला किया कि दोनों मामलों में कनेक्शन कैसे होता है: पोस्टमैन के लिए और जावा क्लाइंट के लिए। यह सब Wireshark का उपयोग करके देखा जा सकता है - यह एक ऐसा लोकप्रिय नेटवर्क ट्रैफ़िक विश्लेषक है। उसी समय, मैंने देखा कि एसएसएल हैंडशेक दो-तरफ़ा प्रमाणीकरण के साथ कैसे होता है, इसलिए बोलने के लिए, लाइव (वे साक्षात्कार के दौरान यह पूछना पसंद करते हैं):
- सबसे पहले, क्लाइंट एक क्लाइंट हैलो संदेश भेजता है जिसमें प्रोटोकॉल संस्करण की तरह मेटा-जानकारी और एन्क्रिप्शन एल्गोरिदम की सूची है जो इसका समर्थन करता है
- जवाब में, सर्वर तुरंत निम्नलिखित संदेशों का एक पैकेट भेजता है: सर्वर हैलो, सर्टिफिकेट, सर्वर की एक्सचेंज, सर्टिफिकेट रिक्वेस्ट, सर्वर हैलो डोने ।
सर्वर हैलो सर्वर (सिफर सूट) द्वारा चयनित एन्क्रिप्शन एल्गोरिदम को इंगित करता है। सर्टिफिकेट के अंदर सर्वर सर्टिफिकेट निहित है। सर्वर कुंजी एक्सचेंज एन्क्रिप्शन के लिए आवश्यक कुछ जानकारी को ले जाता है, चुने हुए एल्गोरिथ्म पर निर्भर करता है (हम अब विवरण में रुचि नहीं रखते हैं, इसलिए हम मानते हैं कि यह सिर्फ एक सार्वजनिक कुंजी है, हालांकि यह गलत है!)। साथ ही, सर्टिफिकेट रिक्वेस्ट मैसेज में टू-वे ऑथेंटिकेशन के मामले में सर्वर क्लाइंट सर्टिफिकेट के लिए रिक्वेस्ट करता है और बताता है कि यह किस फॉर्मेट को सपोर्ट करता है और कौन सा ट्रस्ट इसे जारी करता है। - यह जानकारी प्राप्त करने के बाद, ग्राहक सर्वर के प्रमाणपत्र की पुष्टि करता है और अपना प्रमाणपत्र, "सार्वजनिक कुंजी", और निम्नलिखित संदेशों में अन्य जानकारी भेजता है: प्रमाणपत्र, क्लाइंट कुंजी विनिमय, प्रमाणपत्र सत्यापन । अंतिम चेंजसीपरस्पेक्ट संदेश है, यह दर्शाता है कि एन्क्रिप्टेड रूप में सभी आगे संचार होगा
- अंत में, इन सभी फेरबदल के बाद, सर्वर क्लाइंट के प्रमाण पत्र की जांच करेगा और, अगर सब कुछ ठीक है, तो यह एक जवाब देगा।
पंद्रह मिनट तक ट्रैफिक में चिपके रहने के बाद, मैंने देखा कि जावा क्लाइंट, सर्वर से
सर्टिफिकेट रिक्वेस्ट के जवाब में, किसी कारणवश पोस्टमैन क्लाइंट के विपरीत, अपना सर्टिफिकेट नहीं भेजता है। यही है, एक प्रमाणपत्र संदेश है, लेकिन यह खाली है।
इसके बाद, मुझे
टीएलएस प्रोटोकॉल विनिर्देश पर
सबसे पहले देखने की आवश्यकता होगी, जो शाब्दिक रूप से निम्नलिखित कहता है:
यदि प्रमाणपत्र अनुरोध संदेश में प्रमाण पत्र_अधिकारी सूची गैर-रिक्त थी, तो प्रमाणपत्र श्रृंखला SHOULD में से एक प्रमाण पत्र को सूचीबद्ध सीए में से एक द्वारा जारी किया जाना चाहिए।
हम सर्वर से आने वाले
सर्टिफिकेट रिक्वेस्ट मैसेज में
सर्टिफिकेट_ऑथोरिटीज लिस्ट के बारे में बता रहे हैं। इस सूची में जारी किए गए जारीकर्ताओं में से एक ग्राहक प्रमाणपत्र (कम से कम एक श्रृंखला) पर हस्ताक्षर किए जाने चाहिए। हम इसे
एक्स चेक कहते हैं ।
मुझे इस स्थिति के बारे में पता नहीं था और यह तब मिला जब मैं डीबगिंग में जेडीके की गहराई तक पहुंच गया (मेरे पास जेडीआर 9 है)। नेट्टी के हेटप्लिएंट, जो स्प्रिंग वेबक्लाइंट को रेखांकित करता है, डिफ़ॉल्ट रूप से JDK से SllEngine का उपयोग करता है। वैकल्पिक रूप से, आप आवश्यक निर्भरता जोड़कर इसे ओपनएसएसएल प्रदाता में बदल सकते हैं, लेकिन मुझे अंततः इसकी आवश्यकता नहीं थी।
इसलिए, मैं sun.security.ssl.ClientHandshaker वर्ग के अंदर विराम बिंदु सेट करता हूं और सर्वरहेल्डोन संदेश के लिए हैंडलर में मैंने एक एक्स चेक पाया जो पास नहीं हुआ: क्लाइंट सर्टिफिकेट श्रृंखला में कोई भी जारीकर्ता, सर्वर ट्रस्टर्स की सूची में नहीं है ( सर्वर से
प्रमाणपत्र अनुरोध संदेश)।
मैंने एक नए प्रमाण पत्र के लिए ग्राहक की ओर रुख किया, लेकिन ग्राहक ने आपत्ति जताई कि उसके लिए सब कुछ ठीक रहा, और पायथन को एक स्क्रिप्ट सौंपी, जिसके साथ उसने आमतौर पर कार्यक्षमता के लिए प्रमाण पत्रों की जांच की। स्क्रिप्ट ने अनुरोध लाइब्रेरी का उपयोग करके HTTPS अनुरोध भेजने से अधिक कुछ नहीं किया, और 200 ओके वापस कर दिया। मुझे अंत में आश्चर्य हुआ जब अच्छे पुराने कर्ल भी 200 ओके लौट आए। मैंने तुरंत मजाक को याद किया: "पूरी कंपनी एक कदम से बाहर निकलती है, एक पैर में लेफ्टिनेंट कदम।"
कर्ल, ज़ाहिर है, एक सम्मानित उपयोगिता है, लेकिन टीएलएस मानक भी टॉयलेट पेपर का एक टुकड़ा नहीं है। यह जानने के लिए कि और क्या जांचना है, मैं लक्ष्यहीन रूप से कर्ल प्रलेखन के आसपास घूमता रहा, और गितुब पर, जहां मुझे
ऐसा प्रसिद्ध बग मिला।
रिपोर्टर ने एक्स परीक्षण का बिल्कुल सही वर्णन किया: कर्ल में डिफॉल्ट बैकएंड (ओपनएसएसएल) के साथ, यह नहीं चला, GnuTLS बैकेंड के साथ कर्ल के विपरीत। मैं बहुत आलसी नहीं था, विकल्प के साथ स्रोत से कर्ल एकत्र किया
- साथ-साथ , और लंबे समय से पीड़ित अनुरोध भेजा। और, अंत में, JDK के अलावा एक अन्य क्लाइंट ने "Secutiry अपवाद" के साथ HTTP स्थिति 500 लौटा दी!
मैंने ग्राहक को इस बारे में तर्क के जवाब में लिखा "ठीक है, कर्ल काम करता है," और एक नया प्रमाण पत्र प्राप्त किया, सर्वर पर पुनर्जीवित और बड़े करीने से स्थापित किया गया। इसके साथ, WebClient के लिए मेरा विन्यास ठीक काम करता है। सुखद अंत।
एसएसएल महाकाव्य को सभी पत्राचार के साथ दो सप्ताह से अधिक समय लगा (इसमें विस्तृत जावा लॉग का अध्ययन शामिल था, ग्राहक के लिए काम करने वाली एक अन्य परियोजना का कोड उठाते हुए, और सिर्फ अपनी नाक को उठाकर)।
क्लाइंट व्यवहार में अंतर के अलावा, लंबे समय तक मुझे क्या परेशान करता था, यह था कि सर्वर को इस तरह से कॉन्फ़िगर किया गया था कि प्रमाण पत्र का अनुरोध किया गया था, लेकिन सत्यापित नहीं किया गया था। हालाँकि, कल्पना में इसके लिए स्पष्टीकरण हैं:
इसके अलावा, यदि प्रमाण पत्र श्रृंखला का कुछ पहलू अस्वीकार्य था (उदाहरण के लिए, यह एक ज्ञात, विश्वसनीय सीए द्वारा हस्ताक्षरित नहीं किया गया था), सर्वर MAY अपने विवेक पर या तो हाथ मिलाना जारी रखे (क्लाइंट को बिना सोचे समझे) या एक घातक चेतावनी भेजें।