특정 연결에서 다른 인증서를 사용하려면 어떻게해야합니까?


147

대용량 Java 응용 프로그램에 추가 할 모듈은 다른 회사의 SSL 보안 웹 사이트와 대화해야합니다. 문제는 사이트에서 자체 서명 된 인증서를 사용한다는 것입니다. 중간자 공격이 발생하지 않았 음을 확인하기 위해 인증서 사본을 가지고 있으며이 인증서를 서버에 연결하는 방식으로 코드에 통합해야합니다. 이상적으로

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
.... 
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
.... 
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 

:이 다음을 제외하고 conn.getOutputStream()에 죽는다, 자체 서명 된 인증서 장소에서 추가 처리없이

void sendRequest(String dataPacket) { 
    String urlStr = "https://host.example.com/"; 
    URL url = new URL(urlStr); 
    HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 
    conn.setMethod("POST"); 
    conn.setRequestProperty("Content-Length", data.length()); 
    conn.setDoOutput(true); 
    OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream()); 
    o.write(data); 
    o.flush(); 
} 

:

는 여기에 기본 코드입니다 , 내 코드는 자바가이 하나의 자체 서명 인증서를 받아들이도록 가르쳐 줄 필요가있다.

나는 인증서를 JRE의 인증 기관 저장소로 가져올 수 있다는 것을 알고 있으며,이를 통해 Java가 인증서를 승인 할 수 있습니다. 내가 도울 수 있다면 그것은 내가 취하고 싶은 접근법이 아니다. 사용하지 못할 수도있는 한 모듈에 대해 고객의 모든 시스템에서 수행하는 것이 매우 침해적인 것처럼 보입니다. 동일한 JRE를 사용하는 다른 모든 Java 응용 프로그램에 영향을 미칠 것이며,이 사이트에 액세스하는 다른 Java 응용 프로그램의 확률이 아무리 높다고하더라도 나는 그것을 좋아하지 않습니다. 또한 사소한 작업이 아닙니다. UNIX에서이 방법으로 JRE를 수정하는 액세스 권한을 얻어야합니다.

사용자 지정 검사를 수행하는 TrustManager 인스턴스를 만들 수 있음을 알았습니다. 이 인증서를 제외한 모든 인스턴스에서 실제 TrustManager에 위임 한 TrustManager를 만들 수도 있습니다. 그러나 TrustManager가 전 세계적으로 설치되는 것처럼 보이고 응용 프로그램의 다른 모든 연결에 영향을 미칠 것으로 예상됩니다.

자체 서명 된 인증서를 수락하기 위해 Java 응용 프로그램을 설정하는 기본 방법은 무엇입니까? 위의 모든 목표를 성취 할 수 있습니까? 아니면 타협해야합니까? 파일과 디렉토리 및 구성 설정과 관련된 옵션이 있습니까?

  0

작은 수정판 : http://www.rgagnon.com/javadetails/java-fix-certificate-problem-in-HTTPS.html 16 aug. 112011-08-16 14:06:49

+16

@Hasenpriester :이 페이지를 제안하지 마십시오. 그것은 모든 신뢰 검증을 불가능하게합니다. 원하는 자체 서명 된 인증서를 수락 할뿐만 아니라 MITM 공격자가 제시하는 인증서도 수락해야합니다. 23 dec. 112011-12-23 13:23:14

150

공장에 직접 SSLSocket을 만들고 연결하기 전에 HttpsURLConnection에 설정하십시오.

... 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslFactory); 
conn.setMethod("POST"); 
... 

당신은 하나 SSLSocketFactory을 만들고 주위를 유지하는 것이 좋습니다.다음은 초기화 방법에 대한 스케치입니다.

/* Load the keyStore that includes self-signed cert as a "trusted" entry. */ 
KeyStore keyStore = ... 
TrustManagerFactory tmf = 
    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 
tmf.init(keyStore); 
SSLContext ctx = SSLContext.getInstance("TLS"); 
ctx.init(null, tmf.getTrustManagers(), null); 
sslFactory = ctx.getSocketFactory(); 

키 저장소를 만드는 데 도움이 필요하면 의견을 말하십시오. 여기


키 저장소로드의 예 :

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
keyStore.load(trustStore, trustStorePassword); 
trustStore.close(); 

는 PEM 형식의 인증서와 키 저장소를 만들려면을, 당신은 CertificateFactory를 사용하여 자신의 코드를 작성하거나에서 keytool로 가져올 수 있습니다 JDK (keytool 가 "키 입력"에 대해 작동하지 않지만 "신뢰할 수있는 항목"에 대해서는 정상적으로 작동합니다).

keytool -import -file selfsigned.pem -alias server -keystore server.jks 
+3

대단히 감사합니다! 다른 사람들은 올바른 방향으로 나를 가리키는 데 도움이되었지만, 결국 당신이 취한 접근 방식입니다. PEM 인증서 파일을 JKS Java 키 저장소 파일로 변환하는 힘든 작업이있었습니다. 여기에 대한 지원이 있습니다. http://stackoverflow.com/questions/722931/ssl-socket-php-code-needs-to- 변환 된 - 자바 13 may. 092009-05-13 21:37:13

+1

나는 다행이다 기쁩니다. 열쇠 가게와 힘들어해서 미안해. 나는 그것을 내 대답에 포함시켜야했다. CertificateFactory에서는 그리 어렵지 않습니다. 사실, 나중에 올 사람에 대한 업데이트를 할 것입니다. 13 may. 092009-05-13 21:40:51

  0

와우. PEM을 JKS로 변환하는 방법은 내가했던 것보다 훨씬 쉽습니다! 감사! :) 14 may. 092009-05-14 19:19:00

+1

이 코드는 모두 JSSE 참조 설명서에 설명 된 세 가지 시스템 등록 정보를 설정하여 수행 할 수있는 작업을 복제합니다. 31 may. 122012-05-31 19:36:53

+1

@EJP : 이것은 얼마 전이었습니다. 그래서 기억이 안납니다. 그러나 "큰 Java 응용 프로그램"에서 다른 HTTP 연결이 설정 될 가능성이 있다는 이론적 근거를 추측합니다. 전역 속성을 설정하면 연결을 방해하거나이 당사자가 서버를 스푸핑 할 수 있습니다. 이것은 사례별로 기본 제공되는 신뢰 관리자를 사용하는 예입니다. 31 may. 122012-05-31 22:26:58

+2

OP가 말했듯이, "내 코드는 자바가이 하나의 자체 서명 인증서를 받아들이도록 가르쳐 줄 필요가 있습니다. 애플리케이션의이 지점과 다른 곳에는 없습니다." 31 may. 122012-05-31 22:29:09

  0

당신의 솔루션을 구현하려고 시도했습니다. 도와주세요. http://stackoverflow.com/questions/20190364/how-to-bypass-certificate-by-java 25 nov. 132013-11-25 10:59:37

  0

"글로벌하게"사용하도록하고 싶다면 keytool을 사용하여 인증서를 신뢰할 수있는 인증서로 cacerts 파일에 설치합니다. 그런 다음 해당 JRE를 사용하여 시작된 모든 VM에 대해 신뢰할 수 있습니다. 06 dec. 132013-12-06 19:59:08

  0

@KyleM 그것은 세계적인 수용에 좋은 접근법이 아닙니다. 06 dec. 132013-12-06 20:03:33

  0

그래서 "global"을 따옴표로 묶어 실제로 어떻게 작동하는지 설명했습니다. 아무도 그렇게하는 것이 매우 일반적이며 받아 들여지는 방법 임에도 불구하고 그렇게 언급하지 않았습니다. 06 dec. 132013-12-06 20:07:48

  0

많은 대규모 조직에서는이 방법을 허용하지 않습니다. 심각한 결과를 초래하는 보안 위반이 될 수 있습니다. 06 dec. 132013-12-06 21:09:37

  0

@erickson 안녕하세요. 4 년 후 여러분이 가르쳐 준 접근 방식은 멋지게 번창 해 왔으며 우리가 통신하는 여러 서버에 사용되었습니다. 이제 내가 알아야 할 새로운 트릭이있다. (새로운 StackOverflow 질문을 만들 수있다.) 서버의 자체 서명 된 인증서로 기본 키 저장소와 사용자 지정 키 저장소를 모두 신뢰하고 싶다. 서버가 실제 인증서로 업그레이드되고 연결이 끊어진 경우가 있었지만 기본 키 스토어를 신뢰하는 경우라면 좋았을 것입니다. 13 feb. 142014-02-13 19:25:02

  0

@erickson API는 다소 오도 된 것입니다. SSLContext.init()에 TrustManager 배열을 전달할 수 있음을 의미하지만 해당 메소드의 javadoc은 배열의 첫 번째 TrustManager 만 사용됨을 나타냅니다. 나는 당신이 공유 할 수있는 통찰력을 듣고 싶습니다. 13 feb. 142014-02-13 19:26:18

  0

@skiphoppy 나는 이것을 조금 실험 해보고 다시해야 할 것이다. 나는 그 일을 전에하지 않았지만 나는 효과가 있을지도 모른다. 13 feb. 142014-02-13 20:03:55

+1

@skiphoppy 흠, 내가 발견 한 유일한 방법은 임시 KeyStore (메모리에)를 만들고 그곳에있는 모든 "실제"키 저장소의 루트 인증서를 추가하거나 (거의 동일한 방법으로)'Set <TrustAnchor>' 신뢰할 수있는 키 스토어 내의 모든 엔트리로부터 취득 해,'PKIXParameters'를 초기화 해, 그것을 사용해,'TrustManagerFactory'를 초기화합니다. 13 feb. 142014-02-13 21:48:48

  0

고마워요 @erickson 나는 아직도 그걸 가지고 놀고 있으며, 길고 많은 미로를 방황하고 있습니다. 나는 이것을 줄 것이다. 13 feb. 142014-02-13 22:03:09


12

JRE의 신뢰 저장소를 복사하고 사용자 정의 인증서를 해당 신뢰 저장소에 추가 한 다음 응용 프로그램에 시스템 특성과 함께 사용자 정의 신뢰 저장소를 사용하도록 지시합니다. 이렇게하면 기본 JRE 트러스트 스토어 만 남겨 둡니다.

JRE를 업데이트 할 때 새로운 트러스트 스토어가 자동으로 사용자 정의와 병합되지 않는다는 단점이 있습니다.

truststore/jdk를 확인하고 불일치를 확인하거나 트러스트 스토어를 자동으로 업데이트하는 설치 프로그램이나 시작 루틴을 사용하면이 시나리오를 처리 할 수 ​​있습니다. 응용 프로그램이 실행되는 동안 트러스트 스토어를 업데이트하면 어떻게 될지 모르겠습니다.

이 솔루션은 100 % 우아하지 않거나 간단하지만 간단하고 작동하며 코드가 필요하지 않습니다.


12

commons-httpclient를 사용하여 자체 서명 인증서가있는 내부 https 서버에 액세스 할 때 이와 같은 작업을해야했습니다. 네, 우리의 솔루션은 모든 것을 통과시킨 (디버그 메시지 로깅) 커스텀 TrustManager를 만드는 것이 었습니다.

로컬 SSLContext에서 SSL 소켓을 생성하는 자체 SSLSocketFactory를 가지고 있습니다.이 SSLSocketFactory는 로컬 TrustManager 만 연결되도록 설정되어 있습니다. 키 스토어/certstore 근처에 갈 필요가 없습니다.

그래서 이것은 우리 LocalSSLSocketFactory에 있습니다 SecureProtocolSocketFactory를 구현하는 다른 방법과 함께

static { 
    try { 
     SSL_CONTEXT = SSLContext.getInstance("SSL"); 
     SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null); 
    } catch (NoSuchAlgorithmException e) { 
     throw new RuntimeException("Unable to initialise SSL context", e); 
    } catch (KeyManagementException e) { 
     throw new RuntimeException("Unable to initialise SSL context", e); 
    } 
} 

public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 
    LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) }); 

    return SSL_CONTEXT.getSocketFactory().createSocket(host, port); 
} 

. LocalSSLTrustManager는 전술 한 더미 트러스트 매니저 구현입니다. SSLSocketFactory를 생성하는 옵션이 없으면

+7

모든 신뢰 확인을 사용 중지하면 처음에는 SSL/TLS를 사용하는 것이 거의 없습니다. 로컬로 테스트해도 괜찮지 만 외부에 연결하고 싶지는 않습니다. 23 dec. 112011-12-23 13:26:31

  0

자바 7에서 실행할 때이 예외가 발생합니다. javax.net.ssl.SSLHandshakeException : 공통 암호 스위트가 없습니다 도움이 될 수 있습니까? 19 dec. 132013-12-19 12:55:10


14

, 그냥 JVM

  1. 로 키를 가져 공개 키 검색 : $openssl s_client -connect dev-server:443는 다음

    과 같은 파일 DEV-의 server.pem을 만듭니다
    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl 
    lklkkkllklklklklllkllklkl 
    lklkkkllklk.... 
    -----END CERTIFICATE----- 
    
  2. 키를 가져 오려면 #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. 암호 : changeit를

  3. 다시 시작 JVM

출처 : How to solve javax.net.ssl.SSLHandshakeException?

+1

이것이 원래의 질문을 해결할 수 있다고 생각하지 않지만 * my * 문제를 해결했습니다. 감사합니다! 15 may. 132013-05-15 15:10:40


11

내가 SO에 많은 곳을 가서 웹이이 일을 해결하기 위해. 난 당신이 인증서 문자열의 모든 문자를 넣을 수있는 테스트 한

  static public String certificateString= 
      "-----BEGIN CERTIFICATE-----\n" + 
      "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" + 
      "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" + 
      ... a bunch of characters... 
      "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" + 
      "-----END CERTIFICATE-----"; 

:

   ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes()); 
       CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); 
       X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream); 
       String alias = "alias";//cert.getSubjectX500Principal().getName(); 

       KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 
       trustStore.load(null); 
       trustStore.setCertificateEntry(alias, cert); 
       KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 
       kmf.init(trustStore, null); 
       KeyManager[] keyManagers = kmf.getKeyManagers(); 

       TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); 
       tmf.init(trustStore); 
       TrustManager[] trustManagers = tmf.getTrustManagers(); 

       SSLContext sslContext = SSLContext.getInstance("TLS"); 
       sslContext.init(keyManagers, trustManagers, null); 
       URL url = new URL(someURL); 
       conn = (HttpsURLConnection) url.openConnection(); 
       conn.setSSLSocketFactory(sslContext.getSocketFactory()); 

app.certificateString 예를 들어, 인증서를 포함하는 문자열입니다 : 이것은 나를 위해 일한 코드입니다 , 자기 서명 된 것이라면 위의 정확한 구조를 유지해야합니다. 내 노트북의 터미널 명령 줄을 사용하여 인증서 문자열을 얻었습니다.