मैं विशिष्ट कनेक्शन पर विभिन्न प्रमाणपत्रों का उपयोग कैसे कर सकता हूं?


164

एक मॉड्यूल जो हम अपने बड़े जावा एप्लिकेशन में जोड़ रहे हैं, उसे किसी अन्य कंपनी की एसएसएल-सुरक्षित वेबसाइट के साथ जोड़ना है। समस्या यह है कि साइट एक स्व-हस्ताक्षरित प्रमाण पत्र का उपयोग करती है। मेरे पास यह प्रमाणित करने के लिए प्रमाण पत्र की एक प्रति है कि मैं एक आदमी के बीच के हमले का सामना नहीं कर रहा हूं, और मुझे इस कोड को इस तरह से हमारे कोड में शामिल करना होगा ताकि सर्वर से कनेक्शन सफल हो।

यहाँ मूल कोड है:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

स्व-हस्ताक्षरित प्रमाण पत्र के लिए किसी अतिरिक्त हैंडलिंग के बिना, यह निम्न अपवाद के साथ con.getOutputStream () में मर जाता है:

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

आदर्श रूप से, मेरे कोड को इस एक स्व-हस्ताक्षरित प्रमाण पत्र को स्वीकार करने के लिए जावा को सिखाना होगा, आवेदन में इस एक स्थान के लिए, और कहीं नहीं।

मुझे पता है कि मैं प्रमाण पत्र को JRE के प्रमाणपत्र प्राधिकरण स्टोर में आयात कर सकता हूं, और यह जावा को इसे स्वीकार करने की अनुमति देगा। अगर मैं मदद कर सकता हूं तो मैं ऐसा दृष्टिकोण नहीं लेना चाहता हूं; यह हमारे ग्राहक की सभी मशीनों पर एक मॉड्यूल के लिए बहुत आक्रामक लगता है जिसका वे उपयोग नहीं कर सकते हैं; यह एक ही JRE का उपयोग करके अन्य सभी जावा अनुप्रयोगों को प्रभावित करेगा, और मुझे यह पसंद नहीं है, भले ही इस साइट तक पहुंचने वाले किसी भी अन्य जावा अनुप्रयोग की संभावनाएं शून्य हैं। यह एक तुच्छ कार्रवाई नहीं है: यूनिक्स पर मुझे इस तरह से जेआरई को संशोधित करने के लिए एक्सेस अधिकार प्राप्त करने हैं।

मैंने यह भी देखा है कि मैं एक TrustManager उदाहरण बना सकता हूँ जो कुछ कस्टम जाँच करता है। ऐसा लगता है कि मैं भी एक TrustManager बनाने में सक्षम हो सकता है जो इस एक प्रमाण पत्र को छोड़कर सभी उदाहरणों में वास्तविक TrustManager को दर्शाता है। लेकिन ऐसा लगता है कि TrustManager विश्व स्तर पर स्थापित हो जाता है, और मुझे लगता है कि हमारे आवेदन से अन्य सभी कनेक्शन प्रभावित होंगे, और यह मेरे लिए बहुत सही गंध नहीं करता है, या तो।

स्व-हस्ताक्षरित प्रमाणपत्र को स्वीकार करने के लिए जावा एप्लिकेशन सेट करने के लिए पसंदीदा, मानक या सबसे अच्छा तरीका क्या है? क्या मैं उपरोक्त सभी लक्ष्यों को पूरा कर सकता हूं, या क्या मुझे समझौता करना होगा? क्या फ़ाइलों और निर्देशिकाओं और कॉन्फ़िगरेशन सेटिंग्स और थोड़ा-से-कोड शामिल करने का विकल्प है?


छोटा, वर्किंग फिक्स: rgagnon.com/javadetails/…

20
@Hasenpriester: कृपया इस पृष्ठ का सुझाव न दें। यह सभी ट्रस्ट सत्यापन को निष्क्रिय कर देता है। आप न केवल स्व-हस्ताक्षरित प्रमाण पत्र स्वीकार करना चाहते हैं, आप किसी भी प्रमाण पत्र को भी स्वीकार कर सकते हैं जो MITM हमलावर आपके साथ प्रस्तुत करेगा।
ब्रूनो

जवाबों:


168

SSLSocketखुद एक कारखाना बनाएं , और HttpsURLConnectionकनेक्ट करने से पहले इसे सेट करें ।

...
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslFactory);
conn.setMethod("POST");
...

आप एक बनाना चाहते हैं SSLSocketFactoryऔर इसे चारों ओर रखना चाहते हैं । यहां बताया गया है कि इसे कैसे आरंभ किया जाए:

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
sslFactory = ctx.getSocketFactory();

यदि आपको कुंजी स्टोर बनाने में सहायता की आवश्यकता है, तो कृपया टिप्पणी करें।


यहां कुंजी स्टोर लोड करने का एक उदाहरण दिया गया है:

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(trustStore, trustStorePassword);
trustStore.close();

PEM प्रारूप प्रमाण पत्र के साथ कुंजी स्टोर बनाने के लिए, आप अपने कोड का उपयोग करके लिख सकते हैं CertificateFactory, या इसे keytoolJDK से आयात कर सकते हैं (keytool "कुंजी प्रविष्टि" के लिए काम नहीं करेगा , लेकिन "विश्वसनीय प्रविष्टि" के लिए बस ठीक है )।

keytool -import -file selfsigned.pem -alias server -keystore server.jks

3
आपका बहुत बहुत धन्यवाद! अन्य लोगों ने मुझे सही दिशा में इंगित करने में मदद की, लेकिन अंत में तुम्हारा दृष्टिकोण है जो मैंने लिया। मेरे पास PKS प्रमाणपत्र फ़ाइल को JKS Java कीस्टोर फ़ाइल में परिवर्तित करने का भीषण कार्य था, और मुझे यहाँ इसके लिए सहायता मिली: stackoverflow.com/questions/722931/…
Skiphoppy

1
मुझे खुशी है कि इसने काम किया। मुझे खेद है कि आपने कुंजी स्टोर के साथ संघर्ष किया; मुझे इसे सिर्फ अपने उत्तर में शामिल करना चाहिए था। यह सर्टिफिकेट के साथ बहुत मुश्किल नहीं है। वास्तव में, मुझे लगता है कि मैं बाद में आने वाले किसी व्यक्ति के लिए एक अपडेट करूंगा।
इरिकसन

1
यह सब कोड दोहराता है कि आप JSSE रेफ़रेंस गाइड में वर्णित तीन सिस्टम गुण सेट करके क्या पूरा कर सकते हैं।
लोर्ने का मार्किव

2
@ ईजेपी: यह कुछ समय पहले था, इसलिए मुझे यकीन नहीं है, लेकिन मैं अनुमान लगा रहा हूं कि "बड़े जावा एप्लिकेशन" में, अन्य HTTP कनेक्शन स्थापित होने की संभावना है। वैश्विक गुणों को सेट करना कार्य कनेक्शन के साथ हस्तक्षेप कर सकता है, या इस एक पार्टी को सर्वर को खराब करने की अनुमति दे सकता है। यह मामला-दर-मामला आधार पर अंतर्निहित ट्रस्ट मैनेजर का उपयोग करने का एक उदाहरण है।
इरिकसन

2
जैसा कि ओपी ने कहा, "मेरे कोड को इस एक स्व-हस्ताक्षरित प्रमाण पत्र को स्वीकार करने के लिए जावा को सिखाना होगा, आवेदन में इस एक स्थान के लिए, और कहीं नहीं।"
इरिकसन

18

मैंने इस चीज़ को हल करने के लिए ऑनलाइन बहुत से स्थानों को पढ़ा। यह वह कोड है जिसे मैंने इसे काम करने के लिए लिखा था:

ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = "alias";//cert.getSubjectX500Principal().getName();

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null);
trustStore.setCertificateEntry(alias, cert);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(trustStore, null);
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
TrustManager[] trustManagers = tmf.getTrustManagers();

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
URL url = new URL(someURL);
conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());

app.certificateString एक स्ट्रिंग है जिसमें प्रमाण पत्र शामिल है, उदाहरण के लिए:

static public String certificateString=
        "-----BEGIN CERTIFICATE-----\n" +
        "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
        "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
        ... a bunch of characters...
        "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
        "-----END CERTIFICATE-----";

मैंने परीक्षण किया है कि आप प्रमाण पत्र स्ट्रिंग में किसी भी वर्ण को रख सकते हैं, यदि यह स्वयं हस्ताक्षरित है, जब तक आप सटीक संरचना को ऊपर रखते हैं। मैंने अपने लैपटॉप की टर्मिनल कमांड लाइन के साथ प्रमाणपत्र स्ट्रिंग प्राप्त किया।


1
@Josh को शेयर करने के लिए धन्यवाद। मैंने एक छोटा Github प्रोजेक्ट बनाया, जो आपके कोड को उपयोग में प्रदर्शित करता है: github.com/aasaru/ConnectToTrustedServerExample
मास्टर ड्रॉल्स

14

यदि बनाना एक SSLSocketFactoryविकल्प नहीं है, तो बस JVM में कुंजी आयात करें

  1. सार्वजनिक कुंजी प्राप्त करें: $openssl s_client -connect dev-server:443और फिर एक फ़ाइल dev-server.pem बनाएं जो दिखता है

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
  2. कुंजी आयात करें #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem:। पासवर्ड: परिवर्तन

  3. जेवीएम को फिर से शुरू करें

स्रोत: javax.net.ssl.SSLHandshakeException कैसे हल करें?


1
मुझे नहीं लगता कि यह मूल प्रश्न हल करता है, लेकिन इससे मेरी समस्या हल हो गई , इसलिए धन्यवाद!
एड नॉरिस

ऐसा करना भी एक महान विचार नहीं है: अब आपके पास यह यादृच्छिक अतिरिक्त सिस्टम वाइड स्व हस्ताक्षरित प्रमाण पत्र है जो सभी जावा प्रक्रियाओं को डिफ़ॉल्ट रूप से भरोसा करेगा।
user268396 15

12

हम JRE के ट्रस्टस्टोर को कॉपी करते हैं और अपने कस्टम प्रमाणपत्रों को उस ट्रस्टस्टोर में जोड़ते हैं, फिर सिस्टम प्रॉपर्टी के साथ कस्टम ट्रस्टस्टोर का उपयोग करने के लिए एप्लिकेशन को बताते हैं। इस तरह हम डिफ़ॉल्ट JRE ट्रस्टस्टोर को अकेले छोड़ देते हैं।

नकारात्मक पक्ष यह है कि जब आप JRE को अपडेट करते हैं, तो आपको इसका नया ट्रस्टस्टोर स्वचालित रूप से अपने कस्टम एक में विलय नहीं होता है।

आप शायद इंस्टॉलर या स्टार्टअप रूटीन द्वारा इस परिदृश्य को संभाल सकते हैं जो ट्रस्टस्टोर / jdk को सत्यापित करता है और बेमेल के लिए जाँच करता है या स्वचालित रूप से ट्रस्टस्टोर को अपडेट करता है। मुझे नहीं पता कि क्या होता है यदि आप एप्लिकेशन को चलाने के दौरान ट्रस्टस्टोर को अपडेट करते हैं।

यह समाधान 100% सुरुचिपूर्ण या मूर्ख नहीं है, लेकिन यह सरल है, काम करता है, और किसी भी कोड की आवश्यकता नहीं है।


12

मुझे कुछ इस तरह से करना पड़ता है जब आत्म-हस्ताक्षरित प्रमाणपत्र के साथ आंतरिक https सर्वर तक पहुंचने के लिए कॉमन्स-httpclient का उपयोग करते हैं। हां, हमारा समाधान एक कस्टम ट्रस्टमैन बनाने के लिए था जो बस सब कुछ पास कर रहा था (एक डिबग संदेश लॉगिंग)।

यह हमारे अपने एसएसएसएस लॉकेट को बनाने के लिए नीचे आता है जो हमारे स्थानीय SSLContext से एसएसएल सॉकेट बनाता है, जो कि केवल हमारे स्थानीय ट्रस्टमैन के साथ जुड़ा हुआ है। आपको एक कीस्टोर / सर्टिफिकेट के पास जाने की आवश्यकता नहीं है।

तो यह हमारे LocalSSLSocketFactory में है:

static {
    try {
        SSL_CONTEXT = SSLContext.getInstance("SSL");
        SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    } catch (KeyManagementException e) {
        throw new RuntimeException("Unable to initialise SSL context", e);
    }
}

public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
}

SecureProtocolSocketFactory को लागू करने के अन्य तरीकों के साथ। LocalSSLTrustManager उपरोक्त डमी ट्रस्ट प्रबंधक कार्यान्वयन है।


8
यदि आप सभी ट्रस्ट सत्यापन को अक्षम करते हैं, तो पहले स्थान पर एसएसएल / टीएलएस का उपयोग करने के लिए बहुत कम बिंदु है। यह स्थानीय रूप से परीक्षण के लिए ठीक है, लेकिन यदि आप बाहर से कनेक्ट करना चाहते हैं तो नहीं।
ब्रूनो

जावा 7. javax.net.ssl.SSLHandshakeException पर इसे चलाते समय मुझे यह अपवाद मिलता है: आम तौर पर कोई सिफर सूट आपकी सहायता नहीं कर सकता?
उड़ी लुकाच
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.