[java] Java 클라이언트에서 서버의 자체 서명 된 SSL 인증서 수락

일반적인 질문처럼 보이지만 어디에서나 명확한 방향을 찾을 수 없었습니다.

자체 서명 된 (또는 만료 된) 인증서로 서버에 연결하려고하는 Java 코드가 있습니다. 코드는 다음 오류를보고합니다.

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught
when processing request: sun.security.validator.ValidatorException: PKIX path
building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target

내가 이해하기 때문에 keytool 을 사용해야 하고이 연결을 허용해도 괜찮다고 Java에 알려야합니다.

이 문제를 해결하기위한 모든 지침은 다음과 같은 keytool에 능숙하다고 가정합니다.

서버의 개인 키를 생성하여 키 저장소로 가져 오기

자세한 지침을 게시 할 수있는 사람이 있습니까?

유닉스를 실행 중이므로 bash 스크립트가 가장 좋습니다.

중요한지 확실하지 않지만 코드는 jboss에서 실행됩니다.



답변

기본적으로 두 가지 옵션이 있습니다. 자체 서명 된 인증서를 JVM 신뢰 저장소에 추가하거나 클라이언트가

옵션 1

브라우저에서 인증서를 내보내고 JVM 신뢰 저장소로 가져 와서 신뢰 체인을 설정하십시오.

<JAVA_HOME>\bin\keytool -import -v -trustcacerts
-alias server-alias -file server.cer
-keystore cacerts.jks -keypass changeit
-storepass changeit 

옵션 2

인증서 유효성 검사 비활성화 :

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

// Install the all-trusting trust manager
try {
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
}
// Now you can access an https URL without having the certificate in the truststore
try {
    URL url = new URL("https://hostname/index.html");
} catch (MalformedURLException e) {
} 

참고 가 전혀 옵션의 # 2를하지 않는 것이 좋습니다 . 트러스트 관리자를 비활성화하면 SSL의 일부가 무효화되고 중간 공격에서 사람에게 취약합니다. 옵션 # 1을 선호하거나 서버가 잘 알려진 CA에서 서명 한 “실제”인증서를 사용하도록하는 것이 좋습니다.


답변

이 문제는 기본 JVM 신뢰할 수있는 호스트의 일부가 아닌 인증서 공급자에게 쫓겨났습니다 JDK 8u74. 공급자는 www.identrust.com 이지만 연결하려는 도메인이 아닙니다. 해당 도메인은이 공급자로부터 인증서를 받았습니다. JDK / JRE의 기본 목록으로 교차 루트 커버 신뢰를 참조 합니까?를 참조하십시오. -몇 가지 항목을 읽습니다. Let ‘s Encrypt를 지원하는 브라우저 및 운영 체제 도 참조하십시오 .

그래서 내가 관심있는 도메인에 연결하기 위해 인증서를 발급받은 identrust.com다음 단계를 수행했습니다. 기본적으로 DST Root CA X3JVM 에서 identrust.com ( ) 인증서를 신뢰해야했습니다. Apache HttpComponents 4.5를 사용하여 그렇게 할 수있었습니다.

1 : Certificate Chain Download Instructions (인증서 체인 다운로드 지침) 에서 불 신탁으로부터 인증서를 얻습니다 . 온 클릭 DST 루트 CA X3의 링크.

2 : 문자열을 “DST Root CA X3.pem”파일에 저장하십시오. 시작과 끝에 파일에 “—– BEGIN CERTIFICATE —–“및 “—– END CERTIFICATE —–“행을 추가해야합니다.

3 : 다음 명령으로 java 키 저장소 파일 cacerts.jks를 작성하십시오.

keytool -import -v -trustcacerts -alias IdenTrust -keypass yourpassword -file dst_root_ca_x3.pem -keystore cacerts.jks -storepass yourpassword

4 : 결과 cacerts.jks 키 저장소를 java / (maven) 애플리케이션의 resources 디렉토리에 복사하십시오.

5 : 다음 코드를 사용하여이 파일을로드하고 Apache 4.5 HttpClient에 첨부하십시오. 이렇게하면 indetrust.comutil oracle 에서 발급 한 인증서가있는 모든 도메인의 문제 가 JRE 기본 키 저장소에 인증서를 포함합니다.

SSLContext sslcontext = SSLContexts.custom()
        .loadTrustMaterial(new File(CalRestClient.class.getResource("/cacerts.jks").getFile()), "yourpasword".toCharArray(),
                new TrustSelfSignedStrategy())
        .build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslcontext,
        new String[] { "TLSv1" },
        null,
        SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
        .setSSLSocketFactory(sslsf)
        .build();

프로젝트가 빌드되면 cacerts.jks가 클래스 경로에 복사되어로드됩니다. 이 시점에서 다른 ssl 사이트에 대해 테스트하지는 않았지만이 인증서의 위의 코드 “체인”도 작동하지만 다시는 모릅니다.

참조 : 사용자 정의 SSL 컨텍스트Java HttpsURLConnection으로 자체 서명 된 인증서를 수락하는 방법은 무엇입니까?


답변

Apache HttpClient 4.5는 자체 서명 된 인증서 수락을 지원합니다.

SSLContext sslContext = SSLContexts.custom()
    .loadTrustMaterial(new TrustSelfSignedStrategy())
    .build();
SSLConnectionSocketFactory socketFactory =
    new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
    RegistryBuilder.<ConnectionSocketFactory>create()
    .register("https", socketFactory)
    .build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpClient = HttpClients.custom()
    .setConnectionManager(cm)
    .build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);

이것은 SSL 소켓 팩토리를 빌드하여 SSL 소켓 팩토리를 사용하고 TrustSelfSignedStrategy이를 사용자 정의 연결 관리자에 등록한 다음 해당 연결 관리자를 사용하여 HTTP GET을 수행합니다.

“제작에서이 작업을 수행하지 마십시오”라는 노래에 동의하지만 프로덕션 외부에서 자체 서명 된 인증서를 수락하는 사용 사례가 있습니다. 자동화 된 통합 테스트에 사용하므로 프로덕션 하드웨어에서 실행되지 않는 경우에도 프로덕션과 같은 SSL을 사용합니다.


답변

기본 소켓 팩토리를 설정하는 대신 (IMO가 나쁜 것입니다)-열려는 모든 SSL 연결이 아닌 현재 연결에만 영향을 미칩니다.

URLConnection connection = url.openConnection();
    // JMD - this is a better way to do it that doesn't override the default SSL factory.
    if (connection instanceof HttpsURLConnection)
    {
        HttpsURLConnection conHttps = (HttpsURLConnection) connection;
        // Set up a Trust all manager
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
        {

            public java.security.cert.X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }

            public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
            {
            }
        } };

        // Get a new SSL context
        SSLContext sc = SSLContext.getInstance("TLSv1.2");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        // Set our connection to use this SSL context, with the "Trust all" manager in place.
        conHttps.setSSLSocketFactory(sc.getSocketFactory());
        // Also force it to trust all hosts
        HostnameVerifier allHostsValid = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        // and set the hostname verifier.
        conHttps.setHostnameVerifier(allHostsValid);
    }
InputStream stream = connection.getInputStream();


답변

모든 인증서를 TrustStore신뢰할 수 있는 더 나은 대안이 있습니다. 지정된 인증서를 구체적으로 신뢰하는 인증서를 작성하고이 인증서를 사용 SSLContext하여 SSLSocketFactory에서 설정할 인증서를 작성 하십시오 HttpsURLConnection. 완전한 코드는 다음과 같습니다.

File crtFile = new File("server.crt");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(crtFile));

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());

KeyStore또는 파일 에서 직접 파일을 로드 하거나 신뢰할 수있는 소스에서 X.509 인증서를 검색 할 수 있습니다.

이 코드를 사용하면 인증서 cacerts가 사용되지 않습니다. 이 HttpsURLConnection인증서는이 특정 인증서 만 신뢰합니다.


답변

모든 SSL 인증서 신뢰 :-테스트 서버에서 테스트하려는 경우 SSL을 무시할 수 있습니다. 그러나 프로덕션에이 코드를 사용하지 마십시오.

public static class NukeSSLCerts {
protected static final String TAG = "NukeSSLCerts";

public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    X509Certificate[] myTrustedAnchors = new X509Certificate[0];
                    return myTrustedAnchors;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] certs, String authType) {}

                @Override
                public void checkServerTrusted(X509Certificate[] certs, String authType) {}
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) {
    }
}

}

Activity 또는 Application 클래스의 onCreate () 함수에서이 함수를 호출하십시오.

NukeSSLCerts.nuke();

Android의 Volley에 사용할 수 있습니다.


답변

허용되는 답변은 괜찮지 만 Mac에서 IntelliJ를 사용하고 있고 JAVA_HOMEpath 변수를 사용하여 작동하지 못했기 때문에 여기에 무언가를 추가하고 싶습니다 .

IntelliJ에서 응용 프로그램을 실행할 때 Java Home이 다릅니다.

정확한 위치를 파악하려면 System.getProperty("java.home")신뢰할 수있는 인증서를 읽는 위치에서 수행하면됩니다.