SSLHandsahke 도움 부탁드립니다.

2,826 views
Skip to first unread message

김성준

unread,
Nov 20, 2014, 11:07:55 PM11/20/14
to nett...@googlegroups.com
안녕하세요, 김성준이라고 합니다.

NETTY프레임워크 유용하게 잘 사용하고 있습니다.

SSL 서버를 구축하였는데 SSLHandshakeException이 발생하여 조언 부탁드립니다. 



버전은 JAVA8 / netty-all-4.0.21.Final.jar 입니다.

example의 selfsign인증 대신 코모도 인증서를 사용하였습니다.

기존에 apache용으로 발급받았던 인증서로 

tomcat용으로 재발급이 여의치 않아 key파일만 PKCS#8포맷으로 변경하고 

JDK스토어에 별도 등록하지는 않은상태입니다.

브라우져로(크롬,IE) 접근시에는 이상없이 정상작동합니다.(보안경고없이 올바른 인증서로 인식됩니다)

php의 fsockopen API를 이용하여 통신시도시에도 이상없이 정상작동합니다.

java의 URL.openConnection 이용하여 통신시도시에 SSLHandshakeException이 발생합니다.

동일한 구현인지는 모르겠지만 apache httpclient 라이브러리를 사용해도 동일하게 SSLHandshakeException이 발생합니다.


SSL 지식이 없다보니 브라우져에서 접근할때와 java기반 서버사이드에서 접근할때의 차이점을 모르고 있습니다.

java기반 시스템에서 접근시에는 서버측 JDK스토어에 반드시 trust를 해줘야 되는지요?

작은팁이라도 공유해주시면 감사하겠습니다.

문제를 오래 끌고갈수는 없는 상황이라서 트러블슈팅 가능하신분 계시면 유료 기술지원이라도 괜찮습니다.



로그와 구현코드는 아래와 같습니다.


// ======클라이언트로그======
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
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection$10.run(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection$10.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at sun.net.www.protocol.http.HttpURLConnection.getChainedException(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at java.net.HttpURLConnection.getResponseCode(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
at com.jcuesoft.cuebridge.sample.ctrl.TestAct.main(TestAct.java:33)
Caused by: 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
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
at sun.security.ssl.Handshaker.processLoop(Unknown Source)
at sun.security.ssl.Handshaker.process_record(Unknown Source)
at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderField(Unknown Source)
at java.net.URLConnection.getContentEncoding(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getContentEncoding(Unknown Source)
at com.jcuesoft.cuebridge.sample.ctrl.TestAct.main(TestAct.java:31)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
at sun.security.validator.Validator.validate(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
... 16 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
at java.security.cert.CertPathBuilder.build(Unknown Source)
... 22 more

// ======서버로그======
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:272)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:149)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
at java.lang.Thread.run(Unknown Source)
Caused by: javax.net.ssl.SSLException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLEngineImpl.fatal(Unknown Source)
at sun.security.ssl.SSLEngineImpl.fatal(Unknown Source)
at sun.security.ssl.SSLEngineImpl.recvAlert(Unknown Source)
at sun.security.ssl.SSLEngineImpl.readRecord(Unknown Source)
at sun.security.ssl.SSLEngineImpl.readNetRecord(Unknown Source)
at sun.security.ssl.SSLEngineImpl.unwrap(Unknown Source)
at javax.net.ssl.SSLEngine.unwrap(Unknown Source)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1001)
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:927)
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:873)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:241)
... 12 more



import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;

import com.jcuesoft.cuebridge.link.util.UtilProperties;

public class LinkServer {
    static int port;

    static final boolean SSL = true;
    public static void main(String[] args) throws Exception {
        

        UtilProperties util = new UtilProperties();
        final SslContext sslCtx;
                
        sslCtx = SslContext.newServerContext(util.getServiceSSLCertChainFile(), util.getServiceSSLKeyFile());
        
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new LinkServerInitializer(sslCtx));
            Channel ch = b.bind(port).sync().channel();
            System.err.println("Open your web browser and navigate to " + (SSL? "https" : "http") + "://127.0.0.1:" + port + '/');

            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
        
    }

}


import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class LinkServerInitializer extends ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public LinkServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception{
        ChannelPipeline p = ch.pipeline();
        if (sslCtx != null) {
            SSLEngine engine = sslCtx.newEngine(ch.alloc());
            engine.setUseClientMode(false);
            engine.setNeedClientAuth(false);
            engine.setWantClientAuth(false);

            
            p.addLast(new SslHandler(engine));
        }
        p.addLast(new HttpServerCodec());
        p.addLast(new HttpObjectAggregator(65536));
        p.addLast(new ChunkedWriteHandler());
        p.addLast(new LinkServerHandler());        
    }
}

이희승 (Trustin Lee)

unread,
Nov 21, 2014, 12:39:06 AM11/21/14
to nett...@googlegroups.com
우선 certChainFile 에 Comodo 에서 제공한 CA 까지 모두 들어가 있는지 확인해 보세요.
 
예를 들어, 제가 PositiveSSL 인증서를 발급받았을 당시, 인증서 파일에는 제 도메인에 대한 인증서만 들어 있었습니다.  여기에 추가로 제공된 두 개의 인증서 파일을 합쳐서 (소위 certificate bundle 로 불리우는) certificate chain file 을 생성했습니다:
 
    cat server.crt PositiveSSLCA2.crt AddTrustExternalCARoot.crt > bundle.crt
 
이렇게 합쳐진 bundle.crt 를 certChainFile 로 제공하게 되면, 클라이언트 측에 intermediary CA 파일이 없어도 서버 측에서 intermediary CA 를 함께 제공하기 때문에 문제 없이 인증이 되는 걸로 알고 있습니다.  다만 초기 핸드쉐이크 시에 트래픽 량이 다소 증가할 수는 있겠습니다.  제 경우 약 1.9 KB 에서 5 KB 로 증가했습니다.
 
위의 방법이 되지 않는다면 요 방법도 있습니다: http://onjavadev.com/?p=1197 하지만 보통은 위의 번들을 이용하는 방법으로 해결이 되어야 합니다.
 
최후의 수단으로는 인증서의 핑거프린트만을 검사하는 FingerprintTrustManagerFactory 를 사용하는 방법이 있습니다:
 
 
기본적으로 인증서의 SHA1 체크섬만으로 서버를 인증하는 방식으로, 간편하지만 인증서가 갱신될 때마다 클라이언트측의 핑거프린트 값을 바꿔 줘야 하고 SHA1 의 헛점을 이용해 가짜 인증서를 만들 수 있다는 단점이 있지요.
 
그럼 해 보시고 어느 쪽으로 해결이 되었는지 알려주세요~
 
--
이 메일은 Google 그룹스 'Netty Korean User Group' 그룹에 가입한 분들에게 전송되는 메시지입니다.
이 그룹에서 탈퇴하고 더 이상 이메일을 받지 않으려면 netty-ko+u...@googlegroups.com에 이메일을 보내세요.
더 많은 옵션을 보려면 https://groups.google.com/d/optout을(를) 방문하세요.
 

김성준

unread,
Nov 21, 2014, 2:48:19 AM11/21/14
to nett...@googlegroups.com
t님 꿀같은 노하우 감사합니다.

네 말씀하신대로 crt 3개를 합치니까 잘 됩니다. ^^

이게 끝인줄 알았으나 

javax.net.ssl.SSLHandshakeException: SSLv2Hello is disabled 얘가 또 덤벼드네요. 하하;










2014년 11월 21일 금요일 오후 2시 39분 6초 UTC+9, t 님의 말:

이희승 (Trustin Lee)

unread,
Nov 21, 2014, 3:02:20 AM11/21/14
to nett...@googlegroups.com
도움이 되어 기쁩니다.
 
SSLv2Hello is disabled 문제는 클라이언트측에서 SSLv2와 v3는 끄시고 TLSv1.2, TLSv1.1, TLSv1 만 켜시면 될 듯 하네요.  아시다시피 최근 POODLE 때문에 SSLv3 도 끄게 되었네요.
 
이희승 드림

김성준

unread,
Nov 21, 2014, 4:25:22 AM11/21/14
to nett...@googlegroups.com
아 네~ 이희승님이셨네요.

닉네임만보고 실례했습니다.^^;

무슨 말씀인지 알겠습니다.

접근하는 클라이언트가 불특정 다수 기관들이라서 강제하기가 어렵네요.

취약점을 이유로 한번 잘 설득해봐야겠습니다.

좋은 프레임워크 공개해주셔서 다시 한번 감사드립니다.

편안한 주말 되세요. ^^



2014년 11월 21일 금요일 오후 5시 2분 20초 UTC+9, t 님의 말:
Reply all
Reply to author
Forward
0 new messages