2009-11-03 29 views
170

मैं एचटीटीपीएस/एसएसएल/टीएलएस के लिए बिल्कुल नया हूं और मैं थोड़ा उलझन में हूं कि प्रमाण पत्र के साथ प्रमाणीकरण करते समय ग्राहकों को क्या पेश करना है।जावा HTTPS क्लाइंट प्रमाणपत्र प्रमाणीकरण

मैं एक जावा क्लाइंट लिख रहा हूं जिसे किसी विशेष URL पर डेटा का एक साधारण पोस्ट करने की आवश्यकता है। वह हिस्सा ठीक काम करता है, केवल एक ही समस्या यह है कि यह HTTPS पर किया जाना चाहिए। एचटीटीपीएस भाग को संभालने में काफी आसान है (या तो HTTP क्लाइंट के साथ या जावा के अंतर्निर्मित HTTPS समर्थन का उपयोग करके), लेकिन मैं क्लाइंट प्रमाणपत्रों के साथ प्रमाणित करने पर अटक गया हूं। मैंने देखा है कि यहां पहले से ही एक बहुत ही समान प्रश्न है, जिसने अभी तक मेरे कोड के साथ प्रयास नहीं किया है (जल्द ही ऐसा कर देगा)। मेरा वर्तमान मुद्दा यह है कि - जो भी मैं करता हूं - जावा क्लाइंट प्रमाण पत्र के साथ कभी नहीं भेजता है (मैं इसे पीसीएपी डंप के साथ देख सकता हूं)।

मुझे पता है कि वास्तव में क्या ग्राहक जब प्रमाण पत्र को प्रमाणित करने सर्वर से पेश करने के लिए माना जाता है चाहते हैं (- कि अगर सब पर मायने रखती है जावा के लिए विशेष रूप)? क्या यह एक जेकेएस फ़ाइल है, या पीकेसीएस # 12? उनमें क्या होना चाहिए; बस ग्राहक प्रमाण पत्र, या एक कुंजी? यदि हां, तो कौन सी कुंजी? सभी अलग-अलग प्रकार की फाइलों, प्रमाणपत्र प्रकारों और इस तरह के बारे में काफी भ्रम है।

जैसा कि मैंने कहा है कि मैं एचटीटीपीएस/एसएसएल/टीएलएस के लिए नया हूं, इसलिए मैं कुछ पृष्ठभूमि की जानकारी भी सराहना करता हूं (निबंध होना आवश्यक नहीं है; मैं अच्छे लेखों के लिंक के लिए व्यवस्थित रहूंगा)।

उत्तर

186

अंत में सभी मुद्दों को हल करने में कामयाब रहे, तो मैं अपने ही सवाल का जवाब देंगे।ये वे सेटिंग्स/फ़ाइलें हैं जिन्हें मैंने प्रबंधित करने के लिए उपयोग किया है ताकि मेरी विशेष समस्या हल हो सके;

ग्राहक की कुंजीस्टोर एक PKCS # 12 प्रारूप

  1. ग्राहक की सार्वजनिक प्रमाण पत्र (इस उदाहरण में स्व-हस्ताक्षरित सीए द्वारा हस्ताक्षर किए गए)
  2. ग्राहक की निजी युक्त फ़ाइल है कुंजी

इसे उत्पन्न करने के लिए मैंने ओपनएसएसएल 0 का उपयोग कियाकमांड, उदाहरण के लिए;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever" 

युक्ति: सुनिश्चित करें कि आप नवीनतम OpenSSL प्राप्त करते हैं, नहीं संस्करण 0.9.8h क्योंकि कि एक बग जो आप ठीक ढंग से PKCS # 12 फ़ाइलें उत्पन्न करने के लिए अनुमति नहीं है से ग्रस्त लगता है।

यह पीकेसीएस # 12 फ़ाइल क्लाइंट प्रमाणपत्र को सर्वर पर प्रस्तुत करने के लिए जावा क्लाइंट द्वारा उपयोग की जाएगी जब सर्वर ने स्पष्ट रूप से क्लाइंट को प्रमाणित करने का अनुरोध किया है। क्लाइंट सर्टिफिकेट प्रमाणीकरण वास्तव में काम करता है (यह भी बताता है कि हमें क्लाइंट की निजी कुंजी क्यों चाहिए) के बारे में एक सिंहावलोकन के लिए Wikipedia article on TLS देखें।

ग्राहक की truststore एक सीधे आगे JKS प्रारूपजड़ या मध्यवर्ती CA प्रमाणपत्र युक्त फ़ाइल है। ये सीए प्रमाण पत्र निर्धारित करेंगे कि आपको किस अंतराल के साथ संवाद करने की अनुमति दी जाएगी, इस मामले में यह आपके क्लाइंट को किसी भी सर्वर से कनेक्ट होने की अनुमति देगा जो किसी ट्रस्टस्टोर के सीए द्वारा हस्ताक्षरित प्रमाण पत्र प्रस्तुत करता है।

इसे उत्पन्न करने के लिए आप मानक जावा कीटोल का उपयोग कर सकते हैं, उदाहरण के लिए;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever 
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca 

इस truststore का उपयोग करना, अपने ग्राहक सभी सर्वरों जो एक प्रमाण पत्र सीए myca.crt द्वारा की पहचान द्वारा हस्ताक्षर किए गए पेश के साथ एक पूर्ण एसएसएल हाथ मिलाना करने की कोशिश करेंगे।

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

मुद्दों/टिप्पणियां/सुझाव

  1. क्लाइंट प्रमाणपत्र प्रमाणीकरण केवल सर्वर द्वारा लागू किया जा सकता है।
  2. (महत्वपूर्ण!) जब सर्वर क्लाइंट प्रमाणपत्र (टीएलएस हैंडशेक के हिस्से के रूप में) का अनुरोध करता है, तो यह प्रमाणपत्र अनुरोध के हिस्से के रूप में विश्वसनीय सीए की एक सूची भी प्रदान करेगा। जब आप क्लाइंट प्रमाण पत्र प्रमाणीकरण के लिए प्रस्तुत करना चाहते हैं तो इन सीए में से किसी एक द्वारा हस्ताक्षरित नहीं है, यह बिल्कुल प्रस्तुत नहीं किया जाएगा (मेरी राय में, यह अजीब व्यवहार है, लेकिन मुझे यकीन है कि इसके लिए एक कारण है) ।यह मेरे मुद्दों का मुख्य कारण था, क्योंकि दूसरी पार्टी ने अपने स्वयं के हस्ताक्षरित ग्राहक प्रमाण पत्र को स्वीकार करने के लिए अपने सर्वर को सही तरीके से कॉन्फ़िगर नहीं किया था और हमने माना कि समस्या मेरे ग्राहक के अनुरोध को उचित रूप से प्रदान करने के लिए मेरे अंत में थी।
  3. Wireshark प्राप्त करें। इसमें बहुत अच्छा एसएसएल/एचटीटीपीएस पैकेट विश्लेषण है और यह समस्या में मदद करने और खोजने में जबरदस्त मदद होगी। यह -Djavax.net.debug=ssl जैसा है लेकिन यदि आप जावा एसएसएल डीबग आउटपुट से असहज हैं तो अधिक संरचित और (तर्कसंगत) व्याख्या करना आसान है।
  4. अपाचे httpclient लाइब्रेरी का उपयोग करना पूरी तरह से संभव है। यदि आप httpclient का उपयोग करना चाहते हैं, तो गंतव्य URL को HTTPS समकक्ष के साथ प्रतिस्थापित करें और निम्न JVM तर्क जोड़ें (जो किसी भी अन्य क्लाइंट के लिए समान हैं, चाहे वह लाइब्रेरी चाहे आप HTTP/HTTPS पर डेटा भेजने/प्राप्त करने के लिए उपयोग करना चाहते हों) :

    -Djavax.net.debug=ssl 
    -Djavax.net.ssl.keyStoreType=pkcs12 
    -Djavax.net.ssl.keyStore=client.p12 
    -Djavax.net.ssl.keyStorePassword=whatever 
    -Djavax.net.ssl.trustStoreType=jks 
    -Djavax.net.ssl.trustStore=client-truststore.jks 
    -Djavax.net.ssl.trustStorePassword=whatever
+3

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

+2

उपरोक्त टिप्पणी का एक उदाहरण के रूप में, ऐसी स्थिति पर विचार करें जहां आपके पास एक रूट सीए (आरसीए 1) और दो इंटरमीडिएट सीए (आईसीए 1 और आईसीए 2) है। अपाचे टॉमकैट पर यदि आप ट्रस्ट स्टोर में आरसीए 1 आयात करते हैं, तो आपका वेब ब्राउज़र आईसीए 1 और आईसीए 2 द्वारा हस्ताक्षरित सभी प्रमाण पत्र पेश करेगा, भले ही वे आपके ट्रस्ट स्टोर में न हों। ऐसा इसलिए है क्योंकि यह वह श्रृंखला है जो व्यक्तिगत चेतावनी नहीं देती है। – KyleM

+2

"मेरी राय में, यह अजीब व्यवहार है, लेकिन मुझे यकीन है कि इसके लिए एक कारण है"। इसका कारण यह है कि आरएफसी 2246 में यही कहा गया है। इसके बारे में कुछ भी अजीब नहीं है। ग्राहकों को ऐसे प्रमाणपत्र प्रस्तुत करने की अनुमति देना जो सर्वर द्वारा स्वीकार नहीं किए जाएंगे, यह अजीब होगा, और समय और स्थान की पूरी बर्बादी होगी। – EJP

24

वे JKS फ़ाइल सिर्फ प्रमाण पत्र और कुंजी जोड़े के लिए एक कंटेनर है। एक क्लाइंट-साइड प्रमाणीकरण परिदृश्य में, कुंजी के विभिन्न भागों यहां स्थान दिया जाएगा:

  • ग्राहक की दुकान ग्राहक की निजी और सार्वजनिक कुंजी युग्म शामिल होंगे। यह एक कुंजीस्टोर कहा जाता है।
  • सर्वर की दुकान में ग्राहक का सार्वजनिक कुंजी होगा। यह एक truststore कहा जाता है।

ट्रस्टस्टोर और कीस्टोर का अलगाव अनिवार्य नहीं है लेकिन अनुशंसित है। वे एक ही भौतिक फ़ाइल हो सकता है।

दो दुकानों के फ़ाइल व्यवस्था स्थिति सेट करने के लिए निम्न सिस्टम गुण का उपयोग करें: एक के लिए

-Djavax.net.ssl.trustStore=serversidestore.jks 

ग्राहक के प्रमाण पत्र (सार्वजनिक कुंजी) निर्यात करने के लिए:

-Djavax.net.ssl.keyStore=clientsidestore.jks 

और सर्वर पर फ़ाइल, ताकि आप इसे सर्वर से कॉपी कर सकते हैं,

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks 

का उपयोग ग्राहक की publi आयात करने के लिए सर्वर की कुंजी संग्रह में ग कुंजी, उपयोग (पोस्टर उल्लेख किया, यह पहले से ही सर्वर व्यवस्थापक के द्वारा किया गया है)

keytool -import -file publicclientkey.cer -store serversidestore.jks 
+0

मुझे शायद यह उल्लेख करना चाहिए कि मेरे पास सर्वर पर कोई नियंत्रण नहीं है। सर्वर ने हमारे सार्वजनिक प्रमाणपत्र आयात किया है। मुझे उस प्रणाली के प्रशासकों ने बताया है कि मुझे स्पष्ट रूप से प्रमाण पत्र प्रदान करने की आवश्यकता है ताकि इसे हैंडशेक के दौरान भेजा जा सके (उनका सर्वर स्पष्ट रूप से अनुरोध करता है)। – tmbrggmn

+0

आपको जेकेएस फ़ाइल के रूप में अपने सार्वजनिक प्रमाणपत्र (जो सर्वर पर जाना जाता है) के लिए सार्वजनिक और निजी कुंजी की आवश्यकता होगी। – sfussenegger

+0

उदाहरण कोड के लिए धन्यवाद। उपरोक्त कोड में, "mykey-public.cer" बिल्कुल क्या है? क्या यह ग्राहक सार्वजनिक प्रमाणपत्र है (हम स्वयं हस्ताक्षरित प्रमाणपत्र का उपयोग करते हैं)? – tmbrggmn

8

Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>some.examples</groupId> 
    <artifactId>sslcliauth</artifactId> 
    <version>1.0-SNAPSHOT</version> 
    <packaging>jar</packaging> 
    <name>sslcliauth</name> 
    <dependencies> 
     <dependency> 
      <groupId>org.apache.httpcomponents</groupId> 
      <artifactId>httpclient</artifactId> 
      <version>4.4</version> 
     </dependency> 
    </dependencies> 
</project> 

जावा कोड:

package some.examples; 

import java.io.FileInputStream; 
import java.io.IOException; 
import java.security.KeyManagementException; 
import java.security.KeyStore; 
import java.security.KeyStoreException; 
import java.security.NoSuchAlgorithmException; 
import java.security.UnrecoverableKeyException; 
import java.security.cert.CertificateException; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javax.net.ssl.SSLContext; 
import org.apache.http.HttpEntity; 
import org.apache.http.HttpHost; 
import org.apache.http.client.config.RequestConfig; 
import org.apache.http.client.methods.CloseableHttpResponse; 
import org.apache.http.client.methods.HttpPost; 
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 
import org.apache.http.ssl.SSLContexts; 
import org.apache.http.impl.client.CloseableHttpClient; 
import org.apache.http.impl.client.HttpClients; 
import org.apache.http.util.EntityUtils; 
import org.apache.http.entity.InputStreamEntity; 

public class SSLCliAuthExample { 

private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName()); 

private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS"; 
private static final String CA_KEYSTORE_PATH = "./cacert.jks"; 
private static final String CA_KEYSTORE_PASS = "changeit"; 

private static final String CLIENT_KEYSTORE_TYPE = "PKCS12"; 
private static final String CLIENT_KEYSTORE_PATH = "./client.p12"; 
private static final String CLIENT_KEYSTORE_PASS = "changeit"; 

public static void main(String[] args) throws Exception { 
    requestTimestamp(); 
} 

public final static void requestTimestamp() throws Exception { 
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
      createSslCustomContext(), 
      new String[]{"TLSv1"}, // Allow TLSv1 protocol only 
      null, 
      SSLConnectionSocketFactory.getDefaultHostnameVerifier()); 
    try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) { 
     HttpPost req = new HttpPost("https://changeit.com/changeit"); 
     req.setConfig(configureRequest()); 
     HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin")); 
     req.setEntity(ent); 
     try (CloseableHttpResponse response = httpclient.execute(req)) { 
      HttpEntity entity = response.getEntity(); 
      LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine()); 
      EntityUtils.consume(entity); 
      LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString()); 
     } 
    } 
} 

public static RequestConfig configureRequest() { 
    HttpHost proxy = new HttpHost("changeit.local", 8080, "http"); 
    RequestConfig config = RequestConfig.custom() 
      .setProxy(proxy) 
      .build(); 
    return config; 
} 

public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { 
    // Trusted CA keystore 
    KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE); 
    tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray()); 

    // Client keystore 
    KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE); 
    cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray()); 

    SSLContext sslcontext = SSLContexts.custom() 
      //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize 
      .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate 
      .build(); 
    return sslcontext; 
} 

} 
+0

यदि आप किसी विशेष JVM स्थापना का उपयोग करने वाले सभी अनुप्रयोगों के लिए प्रमाणपत्र उपलब्ध कराएंगे, तो [इस उत्तर] का पालन करें (http: //stackoverflow.com/a/16397662/1134080) इसके बजाए। – ADTC

+0

क्लाइंट प्रोजेक्ट की प्रॉक्सी को सही करने के लिए विधि 'configRequest() 'विधि है? – shareef

+0

हां, यह http क्लाइंट कॉन्फ़िगरेशन है और यह इस मामले में प्रॉक्सी कॉन्फ़िगरेशन है – wildloop

26

अन्य उत्तरों क्लाइंट प्रमाणपत्रों को विश्व स्तर पर कॉन्फ़िगर करने का तरीका दिखाते हैं। लेकिन अगर आप प्रोग्राम एक विशेष कनेक्शन के लिए ग्राहक कुंजी निर्धारित करने के बजाय विश्व स्तर पर अपने JVM पर चलाने वाले प्रत्येक आवेदन भर में इसे परिभाषित चाहते हैं, तो आप ऐसा तरह अपने खुद के SSLContext कॉन्फ़िगर कर सकते हैं:

String keyPassphrase = ""; 

KeyStore keyStore = KeyStore.getInstance("PKCS12"); 
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray()); 

SSLContext sslContext = SSLContexts.custom() 
     .loadKeyMaterial(keyStore, null) 
     .build(); 

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); 
HttpResponse response = httpClient.execute(new HttpGet("https://example.com")); 
+0

मुझे 'sslContext = SSLContexts.custom() का उपयोग करना पड़ा। LoadTrustMaterial (keyFile, PASSWORD) .build();'। मैं इसे 'loadKeyMaterial (...)' के साथ काम नहीं कर सका। –

+1

@ConorSvensson ट्रस्ट सामग्री क्लाइंट के लिए रिमोट सर्वर प्रमाणपत्र पर भरोसा करने के लिए है, मुख्य सामग्री क्लाइंट पर भरोसा करने वाले सर्वर के लिए है। – Magnus

+1

मुझे वास्तव में इस संक्षिप्त और ऑन-पॉइंट उत्तर पसंद है। यदि लोग रुचि रखते हैं, तो मैं निर्माण निर्देशों के साथ यहां एक उदाहरण का उदाहरण प्रदान करता हूं। https://stackoverflow.com/a/46821977/154527 –

0

मैं ठीक लगता है यहां कीस्टोर प्रकार था, pkcs12 (pfx) में हमेशा निजी कुंजी होती है और जेकेएस प्रकार निजी कुंजी के बिना मौजूद हो सकता है। जब तक आप अपने कोड में निर्दिष्ट नहीं करते हैं या ब्राउज़र के माध्यम से प्रमाण पत्र का चयन नहीं करते हैं, तो सर्वर को यह जानने का कोई तरीका नहीं है कि यह दूसरे सिरे पर एक ग्राहक का प्रतिनिधित्व कर रहा है।

संबंधित मुद्दे