programing tip

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

itbloger 2020. 6. 4. 19:17
반응형

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


대규모 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-----";

I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line.


If creating a SSLSocketFactory is not an option, just import the key into the JVM

  1. Retrieve the public key: $openssl s_client -connect dev-server:443, then create a file dev-server.pem that looks like

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
    
  2. Import the key: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Password: changeit

  3. Restart JVM

Source: How to solve javax.net.ssl.SSLHandshakeException?


We copy the JRE's truststore and add our custom certificates to that truststore, then tell the application to use the custom truststore with a system property. This way we leave the default JRE truststore alone.

The downside is that when you update the JRE you don't get its new truststore automatically merged with your custom one.

You could maybe handle this scenario by having an installer or startup routine that verifies the truststore/jdk and checks for a mismatch or automatically updates the truststore. I don't know what happens if you update the truststore while the application is running.

This solution isn't 100% elegant or foolproof but it's simple, works, and requires no code.


I've had to do something like this when using commons-httpclient to access an internal https server with a self-signed certificate. Yes, our solution was to create a custom TrustManager that simply passed everything (logging a debug message).

This comes down to having our own SSLSocketFactory that creates SSL sockets from our local SSLContext, which is set up to have only our local TrustManager associated with it. You don't need to go near a keystore/certstore at all.

So this is in our 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);
}

Along with other methods implementing SecureProtocolSocketFactory. LocalSSLTrustManager is the aforementioned dummy trust manager implementation.

참고URL : https://stackoverflow.com/questions/859111/how-can-i-use-different-certificates-on-specific-connections

반응형