1. Overview

WebLogic Server 14c 기준에서 Client 측에 TLS Protocol을 어떻게 다루는지 알아본다.

How-To-Enable-TLS-Server 에서는 Server측 기준이었으나,

How-To-Enable-TLS-Client 에서는 WLS가 Client가 되었을 경우를 설명한다.



2. Descriptions

2.1 Inbound TLS

다음 옵션으로 TLS를 받아들이는 Server측의 Protocol은 TLSv1.2 이상이 된다.

1
2
USER_MEM_ARGS="${USER_MEM_ARGS} -Djava.security.properties=${DOMAIN_HOME}/java.security"
USER_MEM_ARGS="${USER_MEM_ARGS} -Dweblogic.security.SSL.minimumProtocolVersion=TLSv1.2"




2.2 Outbound TLS

WLS가 Client가 될 때에는, 세가지 유형이 있다.

  • javax.net.ssl.HttpsURLConnection
  • java.net.URL
  • Apache HttpClient (주로 사용)


세가지 방법 모두 JSSE 구현체다.



2.3 URL openStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.net.*" %>

<%
String urlString = "https://...";

try {
    URL url = new URL(urlString);

    // openStream()만 사용
    BufferedReader br =
        new BufferedReader(new InputStreamReader(url.openStream()));

    String line;
    while ((line = br.readLine()) != null) {
        out.println(line + "<br/>");
    }
    br.close();

} catch (Exception e) {
    out.println("ERROR : " + e + "<br/>");
    e.printStackTrace(new PrintWriter(out));
}
%>


Outbound TLSv1.2 를 활성화 해야 한다. (위에서 Inbound TLS 설정했으면)

1
USER_MEM_ARGS="${USER_MEM_ARGS} -Djdk.tls.client.protocols=TLSv1.2"


-Djdk.tls.client.protocols=TLSv1.1 설정 시에는 아래처럼 표시된다.

1
2
3
4
5
6
7
8
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
        at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171)
        at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:106)
        at sun.security.ssl.TransportContext.kickstart(TransportContext.java:238)
        at sun.security.ssl.SSLEngineImpl.writeRecord(SSLEngineImpl.java:167)
        at sun.security.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:131)
        Truncated. see log file for complete stacktrace


weblogic.security.SSL.minimumProtocolVersion 시스템 속성 사용 의 아래 메모에 따르면, weblogic.security.SSL.minimumProtocolVersion옵션과, jdk.tls.client.protocols 옵션은 같이 적용할 수 없다고 나와 있다.

그렇기 때문에, 인스턴스를 2개로 분리하여 테스트 해야 한다.

Inbound TLS 설정한 A 인스턴스, Outbound 호출을 하는 App이 있는 B 인스턴스


오라클 diagnosing-tls-ssl-and-https 게시물 의 “JSSE 조정 매개변수” 에 따르면,

HttpsURLConnection 클래스, URL 클래스의 openStream 을 사용할 때는 https.protocols 옵션을 사용을 안내한다.

위 URL 클래스의 openStream 테스트시 jdk.tls.client.protocols 옵션에 영향이 미쳤다.

두 Class 모두 JSSE 구현체이므로 그러한 것이다.


내 테스트 환경에 따른, JDK 8 Security Enhancements 문서를 보면 Java SE 8 부터 jdk.tls.client.protocols 옵션을 가이드하고 있다. 그러므로 위 블로그의 내용이 아니라 공식 문서에 의견대로 옵션을 사용하는것이 올바라 보인다.



2.4 HttpsURLConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.io.*" %>
<%@ page import="java.net.*" %>
<%@ page import="javax.net.ssl.*" %>

<%
String urlString = "https://...";

try {
    URL url = new URL(urlString);

    HttpsURLConnection conn =
        (HttpsURLConnection) url.openConnection();

    // JSSE 명시 (UseSunHttpHandler와 무관)
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(null, null, null);
    conn.setSSLSocketFactory(ctx.getSocketFactory());

    // 테스트용 HostnameVerifier
    conn.setHostnameVerifier((host, sess) -> true);

    int code = conn.getResponseCode();
    out.println("Resp Code : " + code + "<br/>");
    out.println("Cipher    : " + conn.getCipherSuite() + "<br/><br/>");

    InputStream in =
        (code == HttpsURLConnection.HTTP_OK)
            ? conn.getInputStream()
            : conn.getErrorStream();

    BufferedReader br =
        new BufferedReader(new InputStreamReader(in));

    String line;
    while ((line = br.readLine()) != null) {
        out.println(line + "<br/>");
    }
    br.close();

} catch (Exception e) {
    out.println("ERROR : " + e + "<br/>");
    e.printStackTrace(new PrintWriter(out));
}
%>


Java 공식 언급 - Enabling TLSv1.3 by default on the client 에 따르면 HttpsUrlConnection과 URL.openStream() 사용 시에 어떤 옵션을 사용해야 하는지를 알려주고 있다.

그러나, 해당 설명과 다르게 정상/비정상 동작을 보이고 있어 더 확인이 필요한 상황이다.


위 어플리케이션으로 테스트시에, url.openConnection() Return으로 weblogic.net.http.SOAPHttpsURLConnection을 반환하여 에러가 발생했다.

1
java.lang.ClassCastException: weblogic.net.http.SOAPHttpsURLConnection cannot be cast to javax.net.ssl.HttpsURLConnection


다음은 그 해결책.

java.lang.ClassCastException: weblogic.net.http.SOAPHttpsURLConnection을 javax.net.ssl.HttpsURLConnection으로 캐스트할 수 없음(Doc ID 2332805.1)


HttpsURLConnection이 javax.net.ssl이 아니라 weblogic.net.http.SOAPHttpsURLConnection 를 WLS 측에서 반환한다.

WLS SSL 또한 JSSE 구현을 하였는데, Class 가 다르므로 CastException이 발생한다.

다음의 옵션을 적용하면 WLS Class가 아닌 javax.next.ssl.HttpsURLConnection 이 반환되기 때문에 문제가 해결된다.

1
-DUseSunHttpHandler=true


위 옵션으로 실행 시, 추가로 인증서 옵션이 필요하였다.

아래는 전체 옵션이다.

1
2
3
4
5
-Djdk.tls.client.protocols=TLSv1.2
-DUseSunHttpHandler=true
-Djavax.net.ssl.trustStore=trust.jks
-Djavax.net.ssl.trustStorePassword=***
-Djavax.net.ssl.keyStoreType=JKS



2.5 Apache HttpClient

여기서는 HttpClient 4.5.14 (GA) 를 다운로드하였다.

다음을 WEB-INF/lib에 배치한다.

  • commons-codec-1.11.jar
  • commons-logging-1.2.jar
  • httpclient-4.5.14.jar
  • httpcore-4.4.16.jar


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page contentType="text/plain; charset=UTF-8" %>
<%@ page import="org.apache.http.client.methods.HttpGet" %>
<%@ page import="org.apache.http.impl.client.CloseableHttpClient" %>
<%@ page import="org.apache.http.impl.client.HttpClients" %>
<%@ page import="org.apache.http.client.methods.CloseableHttpResponse" %>
<%@ page import="org.apache.http.util.EntityUtils" %>

<%
    String url = request.getParameter("url");
    if (url == null) {
        url = "https://wls.local:1443";
    }

    try (CloseableHttpClient client = HttpClients.createDefault()) {
        HttpGet get = new HttpGet(url);

        try (CloseableHttpResponse resp = client.execute(get)) {
            String body = EntityUtils.toString(resp.getEntity(), "UTF-8");
            out.print(body);
        }
    } catch (Exception e) {
        e.printStackTrace(new java.io.PrintWriter(out));
    }
%>


HttpClient library는 가장 널리 사용되는 library 이며 내부적으로 JSSE 구현체이다.

그러므로 Outbound SSL 구현 시 다른 외부 영향에 간섭받지 않는다.



2.6 Debugging

다음의 Debugging option을 사용하면 all 보다는 낮은 레벨이지만 충분히 디버깅할 만한 로그들을 확인할 수 있다.

1
2
3
USER_MEM_ARGS="${USER_MEM_ARGS} -Dweblogic.log.StdoutSeverity=Debug"
USER_MEM_ARGS="${USER_MEM_ARGS} -Djavax.net.debug=ssl,handshake,record,trustmanager"
USER_MEM_ARGS="${USER_MEM_ARGS} -Dweblogic.security.SSL.verbose=true"



2.5.1 Mismatch protocols

jdk.tls.client.protocols 값이 Outbound Target인 Server의 TLS Version과 맞지 않으면 다음과 같이 에러가 발생한다.

  • Client WAS is TLSv1.1
  • Server WAS is TLSv1.3
1
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)


jdk.tls.client.protocols 값이 Outbound Target인 Server의 TLS Version과 교집합으로 설정될 경우.

  • Client WAS is TLSv1.1 ~ 1.3
  • Server WAS is TLSv1.3



위 어플리케이션 통신 시 정상적인 경우 javax.net.debug=all 을 살펴보면

아래와 같이 JDK 1.8의 기본값 TLSv1.2 가 사용되고 있다.

Client Hello의 지원되는 버전이 확인된다.

  • Client Hello
1
2
"ClientHello": {
  "client version"      : "TLSv1.2"



jdk.tls.client.protocols 값으로 설정한 TLSv1.1 은 안보인다…

1
2
    "supported_versions (43)": {
      "versions": [TLSv1.3, TLSv1.2]


Server Hello는

1
2
"ServerHello": {
  "server version"      : "TLSv1.2"
1
2
    "supported_versions (43)": {
      "selected version": [TLSv1.3]




4. Outcome

WLS에서는 과거 Certicom 이었으나 어느 버전부터 JSSE 구현을 따라 SSL 을 제공하고 있다.


사용자의 App에서 Outbound SSL 호출 시, JSSE 구현을 따라가게 되는데 HttpsURLConnection 사용 시 WebLogic Class가 반환될 때 CastException이 발생되는 것 말고 크게 신경 쓸게 없다.


이외에는 JSSE 구현을 상속하므로 javax.net.ssl.trustStore 옵션이 필요한것만 인지하면 된다.

일부 고객은 WLS의 DemoIdentity/DemoTrust 인증서 등과 같이 WLS내에 인증서가 사용되는것으로 오해를 하는 경우가 있다.