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


164

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

기본 코드는 다음과 같습니다.

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

자체 서명 된 인증서에 대한 추가 처리가 없으면 conn.getOutputStream ()에서 다음과 같은 예외가 발생합니다.

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

이상적으로, 내 코드는 Java 가이 하나의 자체 서명 된 인증서를 승인하도록 응용 프로그램의 다른 곳에서 사용하도록 Java를 가르쳐야합니다.

인증서를 JRE의 인증 기관 저장소로 가져올 수 있으며 Java가이를 승인 할 수 있음을 알고 있습니다. 그것은 내가 도울 수 있다면 내가 취하고 싶은 접근법이 아닙니다. 사용하지 않을 수있는 하나의 모듈에 대해 모든 고객 컴퓨터에서 수행하는 것은 매우 침습적입니다. 동일한 JRE를 사용하는 다른 모든 Java 응용 프로그램에 영향을 미치며이 사이트에 액세스하는 다른 Java 응용 프로그램의 가능성이 전혀 없어도 마음에 들지 않습니다. 또한 사소한 작업은 아닙니다. UNIX에서는 JRE를 이러한 방식으로 수정하려면 액세스 권한을 얻어야합니다.

또한 일부 사용자 지정 검사를 수행하는 TrustManager 인스턴스를 만들 수 있음을 확인했습니다. 이 인증서를 제외한 모든 인스턴스에서 실제 TrustManager에 위임하는 TrustManager를 작성할 수도있는 것 같습니다. 그러나 TrustManager가 전 세계적으로 설치되는 것처럼 보이며 응용 프로그램의 다른 모든 연결에 영향을 줄 것으로 예상되며 나에게도 좋지 않습니다.

자체 서명 된 인증서를 승인하도록 Java 응용 프로그램을 설정하는 기본, 표준 또는 최상의 방법은 무엇입니까? 위에서 생각한 모든 목표를 달성 할 수 있습니까, 아니면 타협해야합니까? 파일 및 디렉토리, 구성 설정 및 코드가 거의없는 옵션이 있습니까?


작은 작업 수정 : rgagnon.com/javadetails/…

20
@Hasenpriester :이 페이지를 제안하지 마십시오. 모든 트러스트 확인을 비활성화합니다. 원하는 자체 서명 된 인증서를 수락 할뿐만 아니라 MITM 공격자가 제공 할 인증서도 수락해야합니다.
Bruno

답변:


168

만들기 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하거나 keytoolJDK에서 가져 오십시오 (keytool "키 항목" 에는 작동 하지 않지만 "신뢰할 수있는 항목"에는 적합 함) ).

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

3
대단히 감사합니다! 다른 사람들이 나를 올바른 방향으로 안내하는 데 도움을 주었지만 결국 당신이 내가 취한 접근법입니다. PEM 인증서 파일을 JKS Java 키 저장소 파일로 변환하는 힘든 작업이 있었고 여기에서 이에 대한 지원을 찾았습니다. stackoverflow.com/questions/722931/…
skiphoppy

1
다행 이네요. 키 저장소에 어려움을 겪어 죄송합니다. 방금 답변에 포함시켜야합니다. CertificateFactory는 그리 어렵지 않습니다. 사실, 나는 나중에 오는 사람을 위해 업데이트를 할 것이라고 생각합니다.
erickson

1
이 코드는 JSSE 참조 안내서에 설명 된 세 가지 시스템 속성을 설정하여 수행 할 수있는 작업을 복제합니다.
Lorne의 후작

2
@ EJP : 이것은 얼마 전이므로 기억이 나지 않지만 그 이유는 "큰 Java 응용 프로그램"에서 다른 HTTP 연결이 설정되었을 가능성이 있다고 생각합니다. 전역 속성을 설정하면 연결이 제대로 작동하지 않거나이 당사자가 서버를 스푸핑 할 수 있습니다. 내장 된 트러스트 관리자를 사례별로 사용하는 예입니다.
erickson

2
OP가 말했듯이, "내 코드는 Java가이 하나의 자체 서명 된 인증서를 승인하도록 응용 프로그램의 다른 곳에서는 허용하지 않아야합니다."
erickson

18

이 문제를 해결하기 위해 온라인으로 많은 장소를 읽었습니다. 이것은 내가 작동하게하기 위해 작성한 코드입니다.

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은 인증서를 포함하는 문자열입니다 (예 :

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

위의 정확한 구조를 유지하는 한 자체 서명 된 경우 인증서 문자열에 문자를 넣을 수 있음을 테스트했습니다. 랩톱의 터미널 명령 줄에서 인증서 문자열을 얻었습니다.


1
@Josh를 공유해 주셔서 감사합니다. 사용중인 코드를 보여주는 작은 Github 프로젝트를 만들었습니다. github.com/aasaru/ConnectToTrustedServerExample
Master Drools

14

SSLSocketFactory옵션을 만드는 것이 옵션이 아닌 경우 키를 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 재시작

출처 : javax.net.ssl.SSLHandshakeException을 해결하는 방법?


1
나는 이것이 원래의 질문을 해결한다고 생각하지 않지만 문제를 해결 했으므로 감사합니다!
Ed Norris

이 작업을 수행하는 것도 좋지 않습니다. 이제 모든 Java 프로세스가 기본적으로 신뢰하는 임의의 추가 시스템 전체 자체 서명 인증서가 있습니다.
user268396

12

JRE의 신뢰 저장소를 복사하고 사용자 정의 인증서를 해당 신뢰 저장소에 추가 한 후 애플리케이션이 사용자 정의 신뢰 저장소를 시스템 특성과 함께 사용하도록 지시합니다. 이 방법으로 기본 JRE 신뢰 저장소를 그대로 둡니다.

단점은 JRE를 업데이트 할 때 새 신뢰 저장소가 사용자 정의 신뢰 저장소와 자동으로 병합되지 않는다는 것입니다.

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

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


12

commons-httpclient를 사용하여 자체 서명 된 인증서로 내부 https 서버에 액세스 할 때 이와 같은 작업을 수행해야했습니다. 예, 우리의 솔루션은 단순히 모든 것을 전달하는 사용자 정의 TrustManager를 작성하는 것입니다 (디버그 메시지 로깅).

이는 로컬 SSLContext에서 SSL 소켓을 작성하는 자체 SSLSocketFactory가 있으며 로컬 TrustManager 만 연결되도록 설정됩니다. 키 스토어 / 인증서 근처에 갈 필요는 없습니다.

따라서 이것은 LocalSSLSocketFactory에 있습니다.

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

SecureProtocolSocketFactory를 구현하는 다른 방법과 함께. LocalSSLTrustManager는 위에서 언급 한 더미 트러스트 관리자 구현입니다.


8
모든 신뢰 확인을 비활성화하면 처음에는 SSL / TLS를 사용하는 것이 거의 없습니다. 로컬 테스트는 가능하지만 외부에 연결하려는 경우는 아닙니다.
Bruno

Java 7에서 실행할 때이 예외가 발생합니다. javax.net.ssl.SSLHandshakeException : 공통 암호 스위트 없음 지원할 수 있습니까?
Uri Lukach
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.