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

대규모 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 응용 프로그램을 설정하는 기본, 표준 또는 최상의 방법은 무엇입니까? 위에서 생각한 모든 목표를 달성 할 수 있습니까, 아니면 타협해야합니까? 파일 및 디렉토리, 구성 설정 및 코드가 거의없는 옵션이 있습니까?



답변

만들기 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


답변

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

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-----";

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


답변

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을 해결하는 방법?


답변

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

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

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

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


답변

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는 위에서 언급 한 더미 트러스트 관리자 구현입니다.


답변