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


116

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

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


클라이언트로부터 키 저장소와 신뢰 저장소에 추가해야하는 인증서를 식별하는 방법에 대해 두 개의 인증서를 제공했습니다. 이미 유사한 종류의 문제를
겪었으므로이

답변:


100

마지막으로 해결했습니다.). 여기 에 강력한 힌트가 있습니다 (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);
}

다음과 같은 URL을 사용했습니다. localhost : 8443 / Application_Name / getAttributes . / getAttribute URL 매핑을 사용하는 메서드가 있습니다. 이 메서드는 요소 목록을 반환합니다. HttpsUrlConnection을 사용했으며 연결 응답 코드는 200이지만 inputStream을 사용할 때 속성 목록을 제공하지 않고 로그인 페이지의 html 콘텐츠를 제공합니다. 인증을 완료하고 콘텐츠 유형을 JSON으로 설정했습니다. 제안하십시오
Deepak

83

권장되지는 않지만 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) {}
  }
}

72
이와 같이 인증서 유효성 검사를 비활성화하면 가능한 MITM 공격에 대한 연결이 열립니다 . 프로덕션에서는 사용하지 마십시오 .
브루노

3
다행히 코드가 컴파일되지 않습니다. 이 '솔루션'은 근본적으로 안전하지 않습니다.
론의 후작

5
@ neu242, 아니요, 사용하는 용도에 실제로 의존하지 않습니다. SSL / TLS를 사용하려면 MITM 공격으로부터 연결을 보호해야합니다. 그게 전부입니다. 아무도 트래픽을 변경할 수 없다는 것을 보장 할 수 있다면 서버 인증이 필요하지 않지만 네트워크 트래픽을 변경할 위치에 있지 않은 도청자가있을 수 있다고 의심되는 상황은 매우 드뭅니다.
브루노

1
@ neu242 코드 조각 주셔서 감사합니다. 실제로 매우 구체적인 목적 (웹 크롤링)을 위해 프로덕션에서 이것을 사용할 생각이며 질문에서 구현에 대해 언급했습니다 ( stackoverflow.com/questions/13076511/… ). 시간이 있으시다면 그것을보고 제가 놓친 보안 위험이 있는지 알려주시겠습니까?
Sal

1
@Bruno 내가 서버에 핑만하는 경우 MITM 공격으로 인해 실제로 나에게 영향을 미칠까요?
PhoonOne 2015-04-03

21

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

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

또는 코드에서

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

javax.net.ssl.trustStore와 동일


13

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

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

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

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


영업 이익은 HttpsURLConnection에 대해 다루고 있습니다, 없습니다 축
neu242

2
이해 했어요. 나는 일반적인 경우에 내 대답이 더 낫다는 것을 암시하지 않았습니다. 그것은 당신이 경우 단지이다 하는 축 프레임 워크를 사용하여, 당신이 그 상황에서 영업 이익의 질문이있을 수 있습니다. (그것이 처음에이 질문을 찾은 방법입니다.)이 경우 제가 제공 한 방법이 더 간단합니다.
Mark Meuer

5

나를 위해 이것은 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();
}

keyStore개인 키와 인증서를 포함하는 것이다.
EpicPandaForce 2014

1
convertPEMtoP12 코드가 작동하려면 다음 두 가지 종속성을 포함해야합니다. <dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcprov-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency> < dependency> <groupId> org.bouncycastle </ groupId> <artifactId> bcpkix-jdk15on </ artifactId> <version> 1.53 </ version> </ dependency>
BirdOfPrey

@EpicPandaForce 오류가 발생했습니다 .Caught : org.codehaus.groovy.runtime.typehandling.GroovyCastException : 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject'클래스가있는 개체를 ks 줄의 'int'클래스로 캐스팅 할 수 없습니다. setKeyEntry를 - 단서는 무슨 일이 될 수 있습니다
이씨 Biyani

예, 엄격하게 입력 된 언어 대신 Groovy를 사용하고 있습니다. (기술적 방법은 ID와 인증뿐만 아니라 인증을 소요)
EpicPandaForce

4

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

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

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


1
이것은 꽤 깔끔한 패키지처럼 보이지만 모든 것이 작동하도록해야하는 클래스 'AuthSSLProtocolSocketFactory'는 공식 배포판의 일부가 아니며 4.0 베타 (그렇다고 언급 한 릴리스 노트에도 불구하고) 나 3.1에서도 마찬가지입니다. 나는 그것을 약간 해킹했으며 이제 연결이 끊어지기 전에 5 분이 걸리지 않는 것 같습니다. 정말 이상합니다. CA와 클라이언트 인증서를 브라우저에로드하면 그냥 날아갑니다.

1
Apache HTTP Client 4는 SSLContext직접 사용할 수 있으므로 AuthSSLProtocolSocketFactory.
Bruno

1
외부 키 저장소를 통하지 않고 메모리에서 모든 클라이언트 인증서 작업을 수행하는 방법이 있습니까?
Sridhar Sarnobat

4

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

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

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

정리하고 다시 시작하려고했는데 핸드 셰이크 실패가 사라졌습니다. 이제 연결이 종료되기 전에 5 분 동안 무음이 발생합니다. o
Jan

1

아래 코드 사용

-Djavax.net.ssl.keyStoreType=pkcs12

또는

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

전혀 필요하지 않습니다. 또한 사용자 지정 SSL 팩토리를 만들 필요가 없습니다.

또한 동일한 문제가 발생했습니다. 제 경우에는 전체 인증서 체인 을 신뢰 저장소로 가져 오지 않는 문제가있었습니다 . 루트 인증서에서 keytool 유틸리티를 사용하여 인증서를 가져 오거나 메모장에서 cacerts 파일을 열고 전체 인증서 체인을 가져 왔는지 여부를 확인할 수도 있습니다. 인증서를 가져 오는 동안 제공 한 별칭 이름과 비교하여 인증서를 열고 얼마나 많은 인증서가 포함되어 있는지 확인합니다. 동일한 수의 인증서가 cacerts 파일에 있어야합니다.

또한 cacerts 파일은 응용 프로그램을 실행중인 서버에 구성해야합니다. 두 서버는 공개 / 개인 키를 사용하여 서로를 인증합니다.


사용자 지정 SSL 팩토리를 만드는 것은 두 가지 시스템 속성을 설정하는 것보다 훨씬 더 복잡하고 오류가 발생하기 쉽습니다.
Marquis of Lorne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.