HTTPS / SSL üzerinden Java istemci sertifikaları


116

Java 6 kullanıyorum ve HttpsURLConnectionbir istemci sertifikası kullanarak uzak bir sunucuya karşı bir oluşturmaya çalışıyorum .
Sunucu, kendinden imzalı bir kök sertifika kullanıyor ve parola korumalı bir istemci sertifikasının sunulmasını gerektiriyor. Sunucu kök sertifikasını ve istemci sertifikasını /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5) içinde bulduğum varsayılan bir java anahtar deposuna ekledim . Anahtar deposu dosyasının adı, istemci sertifikasının oraya girmemesi gerektiğini gösteriyor gibi görünüyor?

Her neyse, bu mağazaya kök sertifika eklemek kötü şöhreti çözdü javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Ancak, şimdi istemci sertifikasını nasıl kullanacağım konusunda takılı kaldım. İki yaklaşımı denedim ve ikisi de beni hiçbir yere götürmedi.
İlk önce ve tercih edilen, şunu deneyin:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://somehost.dk:3049");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
// The last line fails, and gives:
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

HttpsURLConnection sınıfını atlamayı denedim (sunucuyla HTTP konuşmak istediğim için ideal değil) ve bunun yerine şunu yap:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049);
InputStream inputstream = sslsocket.getInputStream();
// do anything with the inputstream results in:
// java.net.SocketTimeoutException: Read timed out

İstemci sertifikasının burada sorun olduğundan bile emin değilim.


istemciden iki sertifika verdim hangisinin anahtar deposu ve
güven deposuna

Yanıtlar:


100

Sonunda çözdü;). Burada güçlü bir ipucu var (Gandalf'ın cevabı da biraz değindi). Eksik bağlantılar (çoğunlukla) aşağıdaki parametrelerin ilkiydi ve bir dereceye kadar anahtar depoları ile güven depoları arasındaki farkı göz ardı ettim.

Kendinden imzalı sunucu sertifikası bir güven mağazasına aktarılmalıdır:

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Bu özelliklerin ayarlanması gerekir (komut satırında veya kodda):

-Djavax.net.ssl.keyStoreType=pkcs12
-Djavax.net.ssl.trustStoreType=jks
-Djavax.net.ssl.keyStore=clientcertificate.p12
-Djavax.net.ssl.trustStore=gridserver.keystore
-Djavax.net.debug=ssl # very verbose debug
-Djavax.net.ssl.keyStorePassword=$PASS
-Djavax.net.ssl.trustStorePassword=$PASS

Çalışma örnek kodu:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setSSLSocketFactory(sslsocketfactory);
InputStream inputstream = conn.getInputStream();
InputStreamReader inputstreamreader = new InputStreamReader(inputstream);
BufferedReader bufferedreader = new BufferedReader(inputstreamreader);

String string = null;
while ((string = bufferedreader.readLine()) != null) {
    System.out.println("Received " + string);
}

Şunun gibi bir url kullanıyorum: localhost: 8443 / Application_Name / getAttributes . / GetAttribute url eşlemesi ile bir yöntemim var. Bu yöntem, bir öğe listesi döndürür. HttpsUrlConnection kullandım, bağlantı yanıt kodu 200, ancak inputStream kullandığımda bana öznitelik listesini vermiyor, giriş sayfamın html içeriğini veriyor. Kimlik Doğrulaması yaptım ve içerik türünü JSON olarak ayarladım. Lütfen önerin
Deepak

83

Önerilmese de, SSL sertifika doğrulamasını tamamen devre dışı bırakabilirsiniz:

import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SSLTool {

  public static void disableCertificateValidation() {
    // Create a trust manager that does not validate certificate chains
    TrustManager[] trustAllCerts = new TrustManager[] { 
      new X509TrustManager() {
        public X509Certificate[] getAcceptedIssuers() { 
          return new X509Certificate[0]; 
        }
        public void checkClientTrusted(X509Certificate[] certs, String authType) {}
        public void checkServerTrusted(X509Certificate[] certs, String authType) {}
    }};

    // Ignore differences between given hostname and certificate hostname
    HostnameVerifier hv = new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting trust manager
    try {
      SSLContext sc = SSLContext.getInstance("SSL");
      sc.init(null, trustAllCerts, new SecureRandom());
      HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
      HttpsURLConnection.setDefaultHostnameVerifier(hv);
    } catch (Exception e) {}
  }
}

72
Sertifika doğrulamasını bu şekilde devre dışı bırakmanın olası MITM saldırılarına olan bağlantıyı açtığına dikkat edilmelidir: üretimde kullanmayın .
Bruno

3
Neyse ki kod derlenmiyor. Bu 'çözüm' kökten güvensizdir.
Lorne Markisi

5
@ neu242, hayır, gerçekten ne için kullandığınıza bağlı değil. SSL / TLS kullanmak istiyorsanız, bağlantınızı MITM saldırılarına karşı güvence altına almak istiyorsunuz, asıl mesele bu. Hiç kimsenin trafiği değiştiremeyeceğini garanti edebiliyorsanız sunucu kimlik doğrulaması gerekli değildir, ancak ağ trafiğini de değiştirebilecek durumda olmayacak gizli dinleyicilerin olabileceğinden şüphelendiğiniz durumlar oldukça nadirdir.
Bruno

1
@ neu242 Kod pasajı için teşekkürler. Aslında bunu üretimde çok özel bir amaç için kullanmayı düşünüyorum (web taraması) ve uygulamanızdan bir soruda bahsettim. ( Stackoverflow.com/questions/13076511/… ). Vaktiniz varsa, lütfen ona bakıp kaçırdığım herhangi bir güvenlik riski olup olmadığını bana bildirir misiniz?
Sal

1
@Bruno Sunucuya sadece ping atıyorsam, bu beni gerçekten MITM saldırıları tarafından etkiler mi?
PhoonOne

21

KeyStore ve / veya TrustStore Sistem özelliklerini ayarladınız mı?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456

veya koddan

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

Javax.net.ssl.trustStore ile aynı


13

Axis çerçevesini kullanarak bir web servis çağrısı yapıyorsanız, çok daha basit bir cevap var. İstemcinizin SSL web hizmetini arayabilmesi ve SSL sertifikası hatalarını görmezden gelebilmesi için tek yapmanız gereken, herhangi bir web hizmetini çalıştırmadan önce bu ifadeyi eklemeniz yeterlidir:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Bunun bir üretim ortamında yapılması Çok Kötü Bir Şey olduğuna dair olağan sorumluluk reddi beyanları geçerlidir.

Bunu Axis wiki'de buldum .


OP, Axis ile değil, HttpsURLConnection ile uğraşıyor
neu242

2
Anlıyorum. Genel durumda cevabımın daha iyi olduğunu ima etme niyetinde değildim. Bu eğer sadece budur edilir Eksen çerçevesini kullanarak, o bağlamda OP'ın soru olabilir. (Bu soruyu ilk etapta böyle buldum.) Bu durumda, sağladığım yol daha basit.
Mark Meuer

5

Benim için, Apache HttpComponents ~ HttpClient 4.x kullanılarak işe yarayan şey buydu:

    KeyStore keyStore  = KeyStore.getInstance("PKCS12");
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12"));
    try {
        keyStore.load(instream, "helloworld".toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, "helloworld".toCharArray())
        //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store
        .build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO
    CloseableHttpClient httpclient = HttpClients.custom()
        .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO
        .setSSLSocketFactory(sslsf)
        .build();
    try {

        HttpGet httpget = new HttpGet("https://localhost:8443/secure/index");

        System.out.println("executing request" + httpget.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpget);
        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }

P12 dosyası, BouncyCastle ile oluşturulan istemci sertifikasını ve istemci özel anahtarını içerir:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile,
    final String password)
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException,
    NoSuchProviderException
{
    // Get the private key
    FileReader reader = new FileReader(keyFile);

    PEMParser pem = new PEMParser(reader);
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject());
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC");
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);

    PrivateKey key = keyPair.getPrivate();

    pem.close();
    reader.close();

    // Get the certificate
    reader = new FileReader(cerFile);
    pem = new PEMParser(reader);

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject();
    java.security.cert.Certificate x509Certificate =
        new JcaX509CertificateConverter().setProvider("BC")
            .getCertificate(certHolder);

    pem.close();
    reader.close();

    // Put them into a PKCS12 keystore and write it to a byte[]
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
        new java.security.cert.Certificate[]{x509Certificate});
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
}

keyStoreÖzel anahtarı ve sertifikayı içeren budur.
EpicPandaForce

1
ConvertPEMtoP12 kodunun çalışması için şu 2 bağımlılığı eklemeniz gerekir: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < bağımlılık> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1.53 </version> </dependency>
BirdOfPrey

@EpicPandaForce Bir hata alıyorum: Yakalandı: org.codehaus.groovy.runtime.typehandling.GroovyCastException: 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' sınıfına ks satırındaki 'int' sınıfına dönüştürülemiyor. setKeyEntry - neyin yanlış olabileceğine dair ipuçları
Vishal Biyani

Evet, kesinlikle yazılmış bir dil yerine Groovy kullanıyorsun. (teknik olarak bu yöntem sadece bir sertifika değil, bir kimlik ve sertifika alır)
EpicPandaForce

4

Mevcut projemde bunu yapmak için Apache commons HTTP İstemci paketini kullanıyorum ve SSL ve kendinden imzalı bir sertifika ile sorunsuz çalışıyor (bahsettiğiniz gibi cacer'lara yükledikten sonra). Lütfen buraya bir göz atın:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html


1
Bu oldukça düzgün bir paket gibi görünüyor, ancak hepsini çalıştırması gereken sınıf 'AuthSSLProtocolSocketFactory' görünüşe göre resmi dağıtımın bir parçası değil, ne 4.0beta'da (öyle olduğunu belirten sürüm notlarına rağmen) ne de 3.1. Bununla biraz uğraştım ve şimdi bağlantıyı kesmeden önce 5 dakikalık bir askıda kalıcı olarak sıkışmış gibi görünüyor. Bu gerçekten tuhaf - CA'yı ve istemci sertifikasını herhangi bir tarayıcıya yüklersem, uçar gider.
19

1
Apache HTTP Client 4 SSLContextdoğrudan alabilir , böylece kullanmak yerine tüm bunları bu şekilde yapılandırabilirsiniz AuthSSLProtocolSocketFactory.
Bruno

1
Tüm istemci sertifikasını harici bir anahtar deposu yerine bellekte yapmanın bir yolu var mı?
Sridhar Sarnobat

4

Sunucu sertifikanızla ilgili bir sorununuz olduğunu düşünüyorum, geçerli bir sertifika değil (bu durumda "handshake_failure" ifadesinin anlamı budur):

Sunucu sertifikanızı, istemcinin JRE'sindeki güvenici anahtar deponuza aktarın. Bu, keytool ile kolayca yapılır :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts

Temizlemeyi ve yeniden başlamayı denedim ve el sıkışma başarısızlığı gitti. Şimdi bağlantı sonlandırılmadan önce 5 dakikalık sessiz sessizlik alıyorum: o
19

1

Aşağıdaki kodu kullanarak

-Djavax.net.ssl.keyStoreType=pkcs12

veya

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore);

hiç gerekli değil. Ayrıca kendi özel SSL fabrikanızı oluşturmanıza gerek yoktur.

Ben de aynı sorunla karşılaştım, benim durumumda sertifika zincirinin tamamının emanet mağazalarına aktarılmaması sorunu vardı . Keytool yardımcı programı sağ kök sertifikasını kullanarak sertifikaları içe aktarın, ayrıca cacerts dosyasını not defterinde açabilir ve tüm sertifika zincirinin içe aktarılıp aktarılmadığını görebilirsiniz. Sertifikaları içe aktarırken sağladığınız takma adı kontrol edin, sertifikaları açın ve kaç tane içerdiğini görün, cacerts dosyasında aynı sayıda sertifika bulunmalıdır.

Ayrıca cacerts dosyası, uygulamanızı çalıştırdığınız sunucuda yapılandırılmalıdır, iki sunucu ortak / özel anahtarlarla birbirini doğrular.


Kendi özel SSL fabrikanızı oluşturmak, iki sistem özelliğini ayarlamaktan çok daha karmaşık ve hataya açıktır.
Marquis of Lorne
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.