IT/안드로이드+JAVA

[Android] HTTPS 통신 시 사설인증서 사용 방법 (SSLHandshakeException, SSLPeerUnverifiedException)

안경 쓴 귀니 2020. 11. 7. 01:44
반응형

 

HTTPS 통신 시 사설인증서 사용하는 방법

사설 인증서의 경우, 공인된 인증기관에서 인증을 받은 것이 아니기 때문에 인증서 검증을 우회하는 방법을 사용해야 한다. 인증서 검증은 우회하지만 보안 통신은 그대로 진행하기 때문에 별다른 문제는 없다.

 

HTTPS 통신을 하며 사설인증서를 사용하고 그냥 통신을 하는 경우 SSLHandshakeException 오류가 발생한다.

SSLHandshakeException 오류는 HTTPS 통신 시 서버 인증서와 관련해 발생하며 아래와 같은 이유로 발생할 수 있다.

1. 서버 인증서를 발급한 CA를 알 수 없는 경우

2. CA에서 서버 인증서에 서명한 것이 아니라 자체 서명되어있는 경우

3. 서버 구성에 중간 CA가 누락되어 있는 경우

 

자세한 설명은 아래 링크를 참고

developer.android.com/training/articles/security-ssl?hl=ko

 

사설인증서를 사용하여 인증서 검증을 우회하는 방법

인증서(crt 확장자 파일)를 raw 폴더에 넣고 아래 소스코드를 적용한다. (res\raw)

raw 폴더에 server.crt 파일이 있다고 가정하면, int res 파라미터에는 R.raw.server 식으로 사용한다.

public static SSLSocketFactory getPinnedCertSslSocketFactory(Context context, int res) {
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput = context.getResources().openRawResource(res); 
        Certificate ca = null;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } catch (CertificateException e) {
             e.printStackTrace();
        } finally {
            caInput.close();
        }

        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        if (ca == null) {
            return null;
        }
        keyStore.setCertificateEntry("ca", ca);

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        SSLContext sslContext= SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        return sslContext.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

 

 

주의사항은 HttpURLConnection이 아닌 HttpsURLConnection을 사용해야 한다.

URL urlService = new URL(url);
HttpsURLConnection https = (HttpsURLConnection) urlService.openConnection();
SSLSocketFactory sslSocketFactory = MYSSLSocketFactory.getPinnedCertSslSocketFactory(context, R.raw.server);
https.setSSLSocketFactory(sslSocketFactory);

 

 

이렇게 사용했는데도 통신이 실패하는 경우가 있을 것이다.

바로 SSLPeerUnverifiedException 오류가 발생하는 경우이다.

SSLPeerUnverifiedException 오류는 피어가 자신을 식별할 수 없는 경우(인증서 없음, 사용중인 특정 암호 그룹이 인증을 지원하지 않거나 SSL 핸드 셰이킹 중에 피어 인증이 설정되지 않음) 발생한다.

 

자세한 설명은 아래 링크를 참고

developer.android.com/reference/javax/net/ssl/SSLPeerUnverifiedException

 

이 경우는 아래와 같이 검증을 true 처리 해주면 된다.

HostnameVerifier hv = new HostnameVerifier() {
    public boolean verify(String arg0, SSLSession arg1) {
        return true;
    }
};

https.setHostnameVerifier(hv);

 

 

최종 소스 코드

HostnameVerifier hv = new HostnameVerifier() {
    public boolean verify(String arg0, SSLSession arg1) {
        return true;
    }
};

URL urlService = new URL(url);
HttpsURLConnection https = (HttpsURLConnection) urlService.openConnection();
https.setHostnameVerifier(hv);
SSLSocketFactory sslSocketFactory = MYSSLSocketFactory.getPinnedCertSslSocketFactory(context, R.raw.server);
https.setSSLSocketFactory(sslSocketFactory);
반응형