1. Overview

WebLogic Server 14.1 (14cR1) 및 JDK 1.8 환경에서

하나의 단일 HTTPS Port에 다양한 Cipher Suite Certificates 구성 방법을 살펴본다.

2. Descriptions

결론적으로, WLS 에서는 단일 HTTPS Port에 단 하나의 Keystore Alias만 설정할 수 있기 때문에

다양한 Cipher Suite의 Client와 Handshake 할 수 없다.

WLS 에서 SSL 구성 단계 과정 중, Keystore 에 있는 인증서 중 단 하나의 Alias만 Load 하도록 설정한다.

이로 인해, 단일 HTTPS Port 별로 1개의 Certificate만 Load 되고,

Load 된 Certificate 가 어떤 Cipher suite를 사용하는지에 따라 SSL Handshake 대상이 되는 Client가 정해진다.

JSSE(Java Secure Socket Extension) API로 구현하는 Java socket program 을 통해서는

KeyManager package에서 여러 Keystore, Alias를 하나의 HTTPS Port에서 사용 가능하다.

위 두 내용을 직접 테스트를 통해 검증한다.

WLS 또한 JSSE 를 구현하지만, 단일 Alias 선택이 가능한 점 때문에 차이가 발생한다.

2.1 WLS 에서 SSL 구성

2.1.1 인증서 생성

How-to-make-a-self-signed-certificate 을 참고하여 인증서를 생성한다.

아래와 같이, keystore.jks 내에 RSA 및 ECDSA cipher suite 인증서 2개가 포함되어 있다.

(.p12 파일은 여러 인증서를 하나에 집약할 수 있는 PKCS12 표준 포맷이며, 권장되나 여기 포스트에서는 사용하지 않을 것이다.)

1
2
$ ls
ec_cert.cer  keystore.jks  keystore.jks.p12  rsa_cert.cer  trust.jks

2.1.2 생성한 JKS를 WLS 도메인에 등록

  • {Server} - Configuration
    • General - SSL Listen Port Enabled and SSL Listen Port
    • Keystore : keystore.jks 와 trust.jks 를 등록한다.
    • SSL : Private Key Alias 는 1개만 등록이 가능하기 때문에, 여기서 단일 HTTPS Port에 단 하나의 Certificate만 사용 가능한점이 확인 된다. 진행을 위해 keystore.jks 에 등록된 “key-rsa” 를 입력한다.

2.1.3 SSL Debugging Log

  • {Server} - Logging - General 에서 Standard out 의 Log level을 Debug
  • {Server} - Debug 에서 weblogic.security.ssl 을 Enabled

nmap 또는 openssl 등으로 SSL Handshake를 요청하면 WLS Debug log가 아래의 내용을 포함한다.

1
2
3
4
5
6
7
8
9
# 일반적으로 기록되는 Logs
<Debug> <SecuritySSL> <BEA-000000> <[Thread[weblogic.socket.ServerListenThread,5,Pooled Threads]]weblogic.security.SSL.jsseadapter: SSLCONTEXT: Expected SSLContext service protocol: TLS>
<Debug> <SecuritySSL> <BEA-000000> <[Thread[weblogic.socket.ServerListenThread,5,Pooled Threads]]weblogic.security.SSL.jsseadapter: SSLCONTEXT: Got SSLContext, protocol=TLS, provider=SunJSSE>
<Debug> <SecuritySSL> <BEA-000000> <Using TLSv1.2 as the default minimum TLS protocol.>
<Debug> <SecuritySSL> <BEA-000000> <supportedProtocolVersions=TLSv1.3,TLSv1.2,TLSv1.1,TLSv1,SSLv3,SSLv2Hello>
<Debug> <SecuritySSL> <BEA-000000> <given minimumProtocolVersion=TLSv1.2>

# JDK 수준.
<Debug> <SecuritySSL> <BEA-000000> <[Thread[weblogic.socket.ServerListenThread,5,Pooled Threads]]weblogic.security.SSL.jsseadapter: SSLENGINE: SSLEngine.setEnabledCipherSuites(String[]): value=TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,...,TLS_EMPTY_RENEGOTIATION_INFO_SCSV.>

JDK 수준이라고 설명하는 log는 WLS 상위 JDK 에서 허용하는 Cipher suites 를 WLS 에서도 지원된다는 것을 보여준다.

JDK 1.8 에서 지원하는 Cipher Suites 목록 에서 WLS 자체적으로 취약점으로 제외한 것을 빼면 모두 언급되어 있다.

참고로, nmap 과 openssl 은 Target Server에서 지원하는 Cipher Suites 를 조회하는데 유용한 도구임에 틀림 없지만, Third party tool 의 결과이므로 온전히 신뢰할 수 없다. 직접 WLS의 Debugging Log를 통해 어떤 Cipher Suites 가 지원되는지 확인하는 것이 올바르다.

WLS 수준에서 지원되는 Cipher Suites 목록을 조정하려면 WLST를 이용해 수정할 수 있다.

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
connect('username', 'password', 'ADM_URL')
edit()
startEdit()

# AdminServer의 SSL 설정을 가져옴
server = cmo.lookupServer('{Server}')
ssl = server.getSSL()

# 설정할 cipher suite 목록
ciphers = [
    'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256',
    'TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256',
    'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384',
    'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
    'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'
]

# 설정 적용
ssl.setCiphersuites(ciphers)

# 설정 저장 및 활성화
save()
activate()
disconnect()
exit()

2.1.4 Cipher Suites That Available

다음의 Java code로 WLS 에서 지원가능한 Cipher Suites를 받아올 수 있다.

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
46
47
48
49
50
51
52
cat << EOF > SSLClient.java
import javax.net.ssl.*;
import java.io.*;
import java.net.Socket;
import java.util.Arrays;

public class SSLClient {
    public static void main(String[] args) throws Exception {
        String host = "{HOSTNAME}";
        int port = {PORT};

        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[]{
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }
            }
        };

        // Install the all-trusting trust manager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        // Create socket factory with the all-trusting manager
        SSLSocketFactory factory = sslContext.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);

        socket.startHandshake();

        System.out.println("Enabled Cipher Suites:");
        for (String cipherSuite : socket.getEnabledCipherSuites()) {
            System.out.println(cipherSuite);
        }

        System.out.println("Supported Cipher Suites:");
        for (String cipherSuite : socket.getSupportedCipherSuites()) {
            System.out.println(cipherSuite);
        }

        socket.close();
    }
}
EOF
javac -cp . SSLClient.java
java -cp . SSLClient

확인해보면, 결과는 2.1.3 SSL Debugging Log 와 같다.

이 Cipher Suites 목록에는 RSA 및 ECDSA 등등 다양한 서명 알고리즘이 포함되어 있다.

2.1.5 SSL Handshake to WLS

앞서 우리는 RSA 및 ECDSA 서명 알고리즘을 사용한 두 개의 인증서를 하나의 Keystore file로 저장했다.

물론 WLS 에서는 하나의 Private Key Alias 만을 허용하고, 이때문에 RSA 인증서만 Load 해둔 상태다.

이 상황에서, 아래 Java code를 이용하여 RSA 와 ECDSA 인증서를 사용하는 Client가 SSL Handshake를 시도해볼 수 있다.

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
cat << EOF > SSLClient.java
import javax.net.ssl.*;
import java.io.*;
import java.net.Socket;
import java.util.Arrays;

public class SSLClient {
    public static void main(String[] args) throws Exception {
        String host = "{HOSTNAME}";
        int port = {PORT};

        // Specify the desired cipher suite
        String[] desiredCipherSuites = {
          "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
          "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
          "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
          "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
          "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
        };

        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = {
            new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }

                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                }
            }
        };

        // Install the all-trusting trust manager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

        // Create socket factory with the all-trusting manager
        SSLSocketFactory factory = sslContext.getSocketFactory();

        // Create the SSLSocket
        SSLSocket socket = null;
        for (String cipherSuite : desiredCipherSuites) {
            try {
                // Create a new socket with the desired cipher suite
                socket = (SSLSocket) factory.createSocket(host, port);
                socket.setEnabledCipherSuites(new String[]{cipherSuite});

                // Start SSL/TLS handshake
                socket.startHandshake();

                // Get the enabled cipher suite negotiated during the handshake
                String negotiatedCipherSuite = socket.getSession().getCipherSuite();

                // Print negotiated cipher suite
                System.out.println("Negotiated Cipher Suite:");
                System.out.println(negotiatedCipherSuite);
            } catch (Exception e) {
                // If the handshake fails, try the next cipher suite
                System.out.println("Failed to negotiate using cipher suite: " + cipherSuite);
                e.printStackTrace();
            } finally {
                // Close the socket if it was created
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
EOF
javac -cp . SSLClient.java
java -cp . SSLClient

ECDSA 3개 Cipher suites를 사용할 시, Java code는 아래와 같이 exception이 발생했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Failed to negotiate using cipher suite: <ECDSA cipher suite name>
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
        at sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at sun.security.ssl.Alert.createSSLException(Alert.java:117)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:364)
        at sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
        at sun.security.ssl.TransportContext.dispatch(TransportContext.java:203)
        at sun.security.ssl.SSLTransport.decode(SSLTransport.java:155)
        at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1320)
        at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1233)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:417)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:389)
        at SSLClient.main(SSLClient.java:51)

동시에 WLS 에서도 exception이 발생했다.

1
2
3
4
5
6
7
8
9
10
11
<Debug> <SecuritySSL> <BEA-000000> <[Thread[ExecuteThread: '2' for queue: 'weblogic.socket.Muxer',5,Thread Group for Queue: 'weblogic.socket.Muxer']]weblogic.security.SSL.jsseadapter: SSLENGINE: Exception occurred during SSLEngine.wrap(ByteBuffer,ByteBuffer).
javax.net.ssl.SSLHandshakeException: no cipher suites in common
        at sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at sun.security.ssl.Alert.createSSLException(Alert.java:117)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:364)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:320)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:311)
        at sun.security.ssl.ServerHello$T12ServerHelloProducer.chooseCipherSuite(ServerHello.java:460)
        ...
        
<Debug> <SecuritySSL> <BEA-000000> <[Thread[ExecuteThread: '2' for queue: 'weblogic.socket.Muxer',5,Thread Group for Queue: 'weblogic.socket.Muxer']]weblogic.security.SSL.jsseadapter: SSLENGINE: SSLEngine.wrap(ByteBuffer,ByteBuffer) called: result=Status = CLOSED HandshakeStatus = NOT_HANDSHAKING

반면, RSA Handshake에는 아래처럼 정상 수행이 되었다. (WLS Log)

1
2
3
<Debug> <SecuritySSL> <BEA-000000> <[Thread[ExecuteThread: '2' for queue: 'weblogic.socket.Muxer',5,Thread Group for Queue: 'weblogic.socket.Muxer']]weblogic.security.SSL.jsseadapter: SSLENGINE: negotiatedCipherSuite: <RSA cipher suite name>>

<Debug> <SecuritySSL> <BEA-000000> <[Thread[ExecuteThread: '0' for queue: 'weblogic.socket.Muxer',5,Thread Group for Queue: 'weblogic.socket.Muxer']]weblogic.security.SSL.jsseadapter: SSLENGINE: SSLEngine.wrap(ByteBuffer,ByteBuffer) called: result=Status = OK HandshakeStatus = FINISHED

이로써, 다양한 Cipher suites를 사용하는 Client를 지원하기 위한 요구가 있지만 WLS에서 지원하지 않는 한계가 있다는 것을 검증하였다.

WLS 만 구성한 시스템일 경우, 가장 광범위하게 사용되는 RSA 인증서를 사용하면 될 것이며,

가장 권장 되는 케이스로는 앞단에 다양한 Cipher 를 지원하는 LB 또는 Web server를 두는 것이다.

2.2 JSSE API 에서 SSL 구성

2.2.1 인증서 생성

2.1.1 인증서 생성과 동일하다.

2.2.2 Start SSL Server Socket

다음의 Java code로 SSL Server를 생성한다.

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
46
47
48
49
50
51
52
cat << EOF > SSLServer.java
import javax.net.ssl.*;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateException;

public class SSLServer {
    public static void main(String[] args) {
        int port = <HTTPS PORT>;
        
        try {
            // Load keystore
            String keystorePath = "FULLPATH of keystore.jks";
            char[] keystorePassword = "<Keystore Password>".toCharArray();
            char[] keyPassword = "<Key Password>".toCharArray();
            
            KeyStore keyStore = KeyStore.getInstance("JKS");
            FileInputStream inputStream = new FileInputStream(keystorePath);
            keyStore.load(inputStream, keystorePassword);
            
            // Initialize key manager factory
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, keyPassword);

            // Initialize SSL context
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

            // Create SSL server socket factory
            SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();

            // Create SSL server socket
            SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);

            // Accept incoming connections
            while (true) {
                SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
                SSLSession sslSession = sslSocket.getSession();
                System.out.println("SSL handshake successful with:");
                System.out.println("   Protocol: " + sslSession.getProtocol());
                System.out.println("   Cipher suite: " + sslSession.getCipherSuite());
                sslSocket.close();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
EOF
javac -cp . SSLServer.java
java -cp . SSLServer

2.2.3 SSL Handshake to Java

2.1.5 SSL Handshake to WLS 의 Java code를 실행하여 Java SSL Server와 Handshake를 수행하면,

다음과 같이 RSA 및 ECDSA 복합적인 Cipher Suites 를 모두 소화한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SSL handshake successful with:
   Protocol: TLSv1.2
   Cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
   
SSL handshake successful with:
   Protocol: TLSv1.2
   Cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
   
SSL handshake successful with:
   Protocol: TLSv1.2
   Cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
   
SSL handshake successful with:
   Protocol: TLSv1.2
   Cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
   
SSL handshake successful with:
   Protocol: TLSv1.2
   Cipher suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

JSSE API(여기서는 KeyManagerFactory 사용) 으로 SSL Server Socket 를 구현하면 여러 Keystore 및 여러 Alias 를 Load 할 수 있기 때문에, 다양한 Cipher suite를 갖는 Client에 대한 제약이 없다.