[java] HTTPS / SSL을 통한 Java 클라이언트 인증서

Java 6을 HttpsURLConnection사용하고 있으며 클라이언트 인증서를 사용하여 원격 서버에 대해 생성하려고합니다 .
서버는 자체 서명 된 루트 인증서를 사용하고 있으며 암호로 보호 된 클라이언트 인증서를 제공해야합니다. /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts(OSX 10.5) 에서 찾은 기본 Java 키 저장소에 서버 루트 인증서와 클라이언트 인증서를 추가했습니다 . 키 저장소 파일의 이름이 클라이언트 인증서가 거기에 들어가면 안된다는 것을 암시하는 것 같습니다.

어쨌든이 저장소에 루트 인증서를 추가하면 악명 높은 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

그러나 이제는 클라이언트 인증서를 사용하는 방법에 대해 고민하고 있습니다. 나는 두 가지 접근 방식을 시도했지만 어느 쪽도 나를 어디로 향하지 않습니다.
먼저, 선호하는 방법은 다음과 같습니다.

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 클래스를 건너 뛰려고 시도했습니다 (서버와 HTTP를 대화하고 싶기 때문에 이상적이지 않음). 대신 다음을 수행합니다.

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

여기에서 클라이언트 인증서가 문제인지 확실하지 않습니다.



답변

마지막으로 해결했습니다.). 여기 에 강력한 힌트가 있습니다 (Gandalfs 답변도 약간 만졌습니다). 누락 된 링크는 (대부분) 아래의 매개 변수 중 첫 번째였으며 어느 정도 키 저장소와 신뢰 저장소의 차이를 간과했습니다.

자체 서명 된 서버 인증서를 신뢰 저장소로 가져와야합니다.

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

다음 속성을 설정해야합니다 (명령 줄 또는 코드에서).

-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

작동 예제 코드 :

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);
}


답변

권장되지는 않지만 SSL 인증서 유효성 검사를 모두 함께 비활성화 할 수도 있습니다.

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) {}
  }
}


답변

KeyStore 및 / 또는 TrustStore 시스템 속성을 설정 했습니까?

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

또는 코드에서

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

javax.net.ssl.trustStore와 동일


답변

Axis 프레임 워크를 사용하여 웹 서비스 호출을 처리하는 경우 훨씬 더 간단한 대답이 있습니다. 클라이언트가 SSL 웹 서비스를 호출하고 SSL 인증서 오류를 무시할 수 있도록하는 것이 원하는 경우 웹 서비스를 호출하기 전에 다음 명령문을 넣으십시오.

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

이것이 프로덕션 환경에서 수행하는 매우 나쁜 일에 대한 일반적인 면책 조항이 적용됩니다.

Axis wiki 에서 이것을 찾았습니다 .


답변

나를 위해 이것은 Apache HttpComponents ~ HttpClient 4.x를 사용하여 작동했습니다.

    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 파일에는 BouncyCastle로 생성 된 클라이언트 인증서와 클라이언트 개인 키가 포함되어 있습니다.

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();
}


답변

Apache commons HTTP Client 패키지를 사용하여 현재 프로젝트에서이 작업을 수행하고 SSL 및 자체 서명 된 인증서 (당신이 언급 한대로 cacerts에 설치 한 후)와 잘 작동합니다. 여기에서 살펴보세요 :

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

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


답변

서버 인증서에 문제가 있다고 생각합니다. 유효한 인증서가 아닙니다 (이 경우 “handshake_failure”가 의미하는 것임).

클라이언트 JRE의 trustcacerts 키 저장소로 서버 인증서를 가져옵니다. 이것은 keytool 로 쉽게 수행됩니다 .

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