Tag Archives: https

Google Play 안전하지 않은 TrustManager 경고 대응하기

logo-google-play-vetor

구글이 기존의 수많은 앱들의 X509TrustManager를 무력화 시켜 구현한 것에 대한 대응을 나선 것 같습니다. 현재 HTTPS 통신을 위해 SSL 관련된 코드를 구현하였지만 그 방법이 잘못된 경우에 대해 구글 플레이에서 다음과 같은 경고가 뜨고 있습니다.

앱이 Apache HTTP 클라이언트가 있는 X509TrustManager 인터페이스의 안전하지 않은 구현을 사용하고 있어 보안 취약성에 노출되었습니다. 취약성 수정 마감일 등 자세한 내용은 Google 도움말 센터의 이 도움말을 참조하세요

영문판의 경우의 경고 메시지는 다음과 같습니다.

Your app is using an unsafe implementation of the X509TrustManager interface with an Apache HTTP client, resulting in a security vulnerability. Please see this Google Help Center article for details, including the deadline for fixing the vulnerability.

구글플레이에 앱을 출시하고 5시간(저의경우에는 하루정도 뒤에 확인 가능했었습니다)정도 뒤에 구글플레이에 앱 리스트에 경고 표시가 다음과같이 표기됩니다.

google_play_warning_x509trustmanager

이는 다음과 같은 코드가 앱내에 존재할 경우 발생하는 경고이며 2016년 5월 17일부터 해당 문제가 해결되지 않은 앱들을 차단할 예정이라고 합니다.

TrustManager tm = new X509TrustManager() {
	public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
	public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { }
	public X509Certificate[] getAcceptedIssuers() { return null; }
};

문제가 되는 부분은 checkServerTrusted 메소드의 내부 구현이 아무것도 없는것에서 기인합니다; 정확하게는 위의 코드는 SSL의 동작을 완벽하게 무력화 시켜 인증 여부를 보장할 수 없는 누구와도 통신할 수 있게 된 상태를 야기합니다. 만약 TrustManager를 커스터마이징해서 사용해야 하는 경우  내부에 원치 상황에 대해 CertificateException 또는 IllegalArgumentException 예외를 발생시키는 코드를 구현해야 합니다.

우선 문제가 되는 Apache의 HttpClient를 사용하는 올드한 형태의 예제를 확인해 보겠습니다.

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", new MySSLSocketFactory(trustStore), 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient client = new DefaultHttpClient(ccm, params);
String getURL = "https://eye.pe.kr/index.html";
HttpGet get = new HttpGet(getURL);
HttpResponse responseGet = client.execute(get);
HttpEntity resEntityGet = responseGet.getEntity();

if (resEntityGet != null) {
    Log.i("RESPONSE", EntityUtils.toString(resEntityGet));
}

그리고 다음의 코드는 개발하신 스타일에 따라 다른 모습이겠지만 SSLSocketFactory와 X509TrustManager를 구현한 코드입니다. 위의 코드에서 MySSLSocketFactory라는 이름으로 가져다 사용하고 있습니다.

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

SSLSocketFactory를 상속하여 내부적으로 커스터마이징 된 X509TrustManager를 사용한 SSLContext를 통하여 소켓을 생성하도록 구현되어있습니다. 이렇게 생성한 SSL Socket은 보안에 취약한 상태입니다.

사실 정상적인 CA가 발급한 SSL 인증서로 구축된 서버라면 다음과 같은 코드만으로 통신에 문제가 없습니다.

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient client = new DefaultHttpClient(ccm, params);
String getURL = "https://google.com";
HttpGet get = new HttpGet(getURL);
HttpResponse responseGet = client.execute(get);
HttpEntity resEntityGet = responseGet.getEntity();

if (resEntityGet != null) {
    Log.i("RESPONSE", EntityUtils.toString(resEntityGet));
}

하지만 TrustManager를 무력화 시키면서까지 HTTPS 통신을 하려 했다는건 인증서 비용등의 문제로 자체 발급한 Self-Signed 인증서를 사용중이기 때문이 아닐까 생각됩니다. 확인 할 수 없는 인증서로 인한 통신 불가 상황에서는 다음과 같은 예외가 발생합니다.

javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
at com.android.org.conscrypt.SSLNullSession.getPeerCertificates(SSLNullSession.java:104)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:98)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:394)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:219)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:172)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:130)
at org.apache.http.impl.client.DefaultRequestDirector.executeOriginal(DefaultRequestDirector.java:1317)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:707)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:694)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:520)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:498)
at example.trustmanager.theeye.pe.kr.trustmanagerexample.MainActivity$1$1.run(MainActivity.java:36)
at java.lang.Thread.run(Thread.java:818)

이 문제를 해결하는 가장 간단한 방법은 정상적인 CA가 발급한 인증서를 구매하여 서버에 채택하고 위쪽의 예제 코드처럼 SSLSocketFactory 를 기본 형태로 사용하는 방법입니다.

하지만 여전히 자체 발급한 인증서로도 구글의 권고안을 따라 문제를 해결할 수 있습니다. TrustManager를 재구현하면서 무력화 시킨 코드가 문제가 되기 때문에 TrustManager를 무력화 시키지 않는 방법으로도 해결을 해보도록 하겠습니다.

우선 자체 발급한 인증서는 일반적인 공인된 CA들이 인증해 주지 않는다는것이 중요하므로 자체적인 CA의 인증서를 앱내에 탑재하여 인증을 가능케 하는 방법이 있습니다. 인증서 자체 발급에 대해서는 [Self-Signed SSL로 Apache HTTPS 구현하기]를 참고해주시기 바랍니다.

앱내에 탑재될 인증서는 CA의 PEM 포맷의 인증서이며 위의 참고글에서 언급되는 rootCA.crt 파일이 여기에 해당됩니다. 이 인증서의 내용은 BASE64 인코딩이 되어있는 형태입니다. 이것을 이용하여 코드를 수정해 보겠습니다.

String certificateString = "-----BEGIN CERTIFICATE-----\n" +
        "MIIDdzCCAl+gAwIBAgIJAMapd+KIAR0MMA0GCSqGSIb3DBQUAMFIxCzAJBgNV\n" +
        "BAYTAktSMRQwEgYDVQQIDAtHeWVvbmdnaS1kbzEUMB1UEBwwLU2VvbmduYW0t\n" +
        "c2kxFzAVBgNVBAoMDlRoZWV5ZSBDb21wYW55MB4E2MDIyMDA5MjEyMVoXDTE3\n" +
        "MDIxOTA5MjEyMVowUjELMAkGA1UEBhMCS1IxSBgNVBAgMC0d5ZW9uZ2dpLWRv\n" +
        "MRQwEgYDVQQHDAtTZW9uZ25hbS1zaTEXMA1UECgwOVGhlZXllIENvbXBhbnkw\n" +
        "ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwgEKAoIBAQDbrzdE+u+cIMnGJRZWlOZ+\n" +
        "8fBil6pVPQntbu3nz92omfiTAeGjb25gkwkLamsM0QISnXSmI+gl3iJmuCyFw\n" +
        "r3F+GiAUXS7e87vJoOKclbGVIQHPtEsbx+IjK7C2MJgoToNczedFzhr1xI9NV\n" +
        "ZpFMKV+YONM7qCsKKMjVrj4TE7DCZUaWu8QlPYC6p8kR5ZM6DNtxoE2QOWpiB\n" +
        "RwRwM6k88KfUVXysuvj3LCRVOqN3GrpCGF8GjuMohrI0zcK2XHvZ6D8GaFUEA\n" +
        "TLG9QcfD1N/Jo1tHpdWe3kQIYjtGBvjZ3VfUmqt9PFsqjK5sx6Kppd9yPreQp\n" +
        "AgMBAAGjUDBOGA1UdDgQWBBTjlXdy+BlcQGHfVdF9/8QkW1ciTTAfBgNVHSME\n" +
        "GDAWgBTjl+BlcQGHfVdF9/8QkW1ciTTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3\n" +
        "DQEBBQ4IBAQCpWmUEr5p4CdWSGWHSopcNGPgJIODJ4K6Ir/IFJRb5tyzYY02R\n" +
        "mfUtyP39M8GnnEz5QqYNobCNIVV3cSgAMKeUTAbBQDbE+F04LzR6DRXtdhjp7\n" +
        "VtRG6McnecEi4D2Zq2ZGdFuAncfzhvdjybgkhkn1TZnbbtsiYqazKsNhdBvzR\n" +
        "mvEpRkC7eqpAw36A9zHyjvP9tJW0mVJjlUtevARDkLyX+VpMtKOUWHYikLXgK\n" +
        "UfRm9rtQMIpCC9n0qTMvDkxuBJakdSI7YRCpPYrsv0IEP23UN3Y32j1lthVIU\n" +
        "9S5KyRYKhDzJXadgO3dNI9bFL0H2SZ6mXqYL\n" +
        "-----END CERTIFICATE-----";

ByteArrayInputStream derInputStream = new ByteArrayInputStream(certificateString.getBytes());
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
String alias = cert.getSubjectX500Principal().getName();

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
trustStore.setCertificateEntry(alias, cert);

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_0);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", new MySSLSocketFactory(trustStore), 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient client = new DefaultHttpClient(ccm, params);
String getURL = "https://eye.pe.kr/index.html";
HttpGet get = new HttpGet(getURL);
HttpResponse responseGet = client.execute(get);
HttpEntity resEntityGet = responseGet.getEntity();

if (resEntityGet != null) {
    Log.i("RESPONSE", EntityUtils.toString(resEntityGet));
}

다른 많은 예제들은 인증서를 앱내에 파일로 포함하여 FileInputStream등으로 읽어서 쓰는 예시가 많았습니다만 파일을 앱에 포함할 정도면 텍스트를 그대로 하드코딩 해도 문제가 되지 않겠다고 생각되어 위와 같이 작성 해 보았습니다. 위에서 언급되었던 잘못된 MySSLSocketFactory의 구현은 다음과 같이 변경되었습니다.

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(truststore);

        sslContext.init(null, tmf.getTrustManagers(), null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

위와 같이 수정 후 통신 테스트를 해보면 잘 되는 것을 확인할 수 있습니다. 해당 CA로 부터 발급된 모든 인증서가 적용된 서버들과 통신이 문제없이 가능합니다. 만약에 지정한 CA 인증서와 연관이 없는 인증서가 적용된 서버일 경우 다음과 같은 오류가 발생합니다.

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:340)
at com.android.org.conscrypt.OpenSSLSocketImpl.waitForHandshake(OpenSSLSocketImpl.java:677)
at com.android.org.conscrypt.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:639)
at org.apache.http.impl.io.SocketInputBuffer.<init>(SocketInputBuffer.java:75)
at org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:88)
at org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:175)
at org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:111)
at org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:134)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:226)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:172)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:130)
at org.apache.http.impl.client.DefaultRequestDirector.executeOriginal(DefaultRequestDirector.java:1317)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:707)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:694)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:520)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:498)
at example.trustmanager.theeye.pe.kr.trustmanagerexample.MainActivity$1$1.run(MainActivity.java:93)
at java.lang.Thread.run(Thread.java:818)

만약에 서버의 CA인증서 존재 여부가 불분명하고 자체 발급하여 사용중인 인증서 역시 확인하기가 어렵다면 아쉬운대로 다음과 같은 명령을 사용하여 자체 발급된 인증서를 확인 하실 수 있습니다.

# openssl s_client -connect eye.pe.kr:443
CONNECTED(00000003)
depth=0 C = KR, ST = Gyeonggi-do, L = Seongnam-si, O = TheEye Company
verify error:num=18:self signed certificate
verify return:1
depth=0 C = KR, ST = Gyeonggi-do, L = Seongnam-si, O = TheEye Company
verify return:1
---
Certificate chain
 0 s:/C=KR/ST=Gyeonggi-do/L=Seongnam-si/O=TheEye Company
   i:/C=KR/ST=Gyeonggi-do/L=Seongnam-si/O=Theeye Company
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDIDCCAggCCQCkHog/OqFR2TANBgkqhkiG9w0BAQUFADBSMQswCQYDVQwJL
UjEUMBIGA1UECAwLR3llb25nZ2ktZG8xFDASBgNVBAcMC1Nlb25nbmFNpMRcw
FQYDVQQKDA5UaGVleWUgQ29tcGFueTAeFw0xNjAyMjAxMDI3NDlaxNzAyMTkx
MDI3NDlaMFIxCzAJBgNVBAYTAktSMRQwEgYDVQQIDAtHeWVvbaS1kbzEUMBIG
A1UEBwwLU2VvbmduYW0tc2kxFzAVBgNVBAoMDlRoZUV5ZS21wYW55MIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAthNyPJFOp3z7vvuiEwVpuz56o
Y7bmZziN0dKoh8E1DXQnUeIMeotegSvS4r1S1lzK5KqR0Aka8ft7QccOFM5zx
bqv7yvBZ57fzoKeTwNUHrURseuSj90Q4zHK68l/OUyxULDR7HYRmxtVCB/72L
W11awXCqnEyAdS26XLeFf5JZv1sSleW/J/1FGsWDy8/AcQ4hEiDj/nke9TkQ4
JlHHPuLObouKOhoJkj2wZaNlC2GuJ3+mIFFsleEVx2IXH5ezcLG4gE0FQJk+9
lohtAXZwHq3A/CF+bMspbiHVwOYT3cCLJ/Hi8J5Mz0NOzTIhvygaA4wIDAQAB
MA0GCSqGSIb3DQEBBQUAA4IBAy/LVoqKqlvYEcfDAconAGGOTlxkkxIWOMfUK
DdViDNgAvIlO8J5bvkCdi9ap0UAX8bN+XLuxXmd+t55qmHAaA/54uswhYpqz0
2pQeP/Hpt5HPlAPUMySdVkJovAJbYi0vBSv6L8WpDGDa/9AX0yPvaW1OLtzVY
VddpqTLlYGoYzcvps/GnZO7v/4/meda6gQyphg8S91WrPbUKvziGkdtex6CST
8zwSn7gTvo7FXbTjK28DoWDq/rRr3Ni8KyqWvXtcDQWGyAjUvjO6O/p7C2UPy
BC15dcAEkGL4GMYANJPJsiIjo1Umbu5kbXeE8ISW+pbyh
-----END CERTIFICATE-----

s_client 명령을 사용하여 통신하고자 하는 서버의 인증서를 확인 할 수 있습니다. 이는 CA 인증서는 아니지만 이렇게 조회된 인증서를 이용해도 1:1간의 통신에는 문제가 없습니다.

문제를 해결하고 Version Code를 올린 APK를 재등록하고 하루정도가 지나면 해당 경고메시지가 사리지는것을 확인하실 수 있습니다.

스크린샷 2016-02-22 오후 3.26.24

참고 :

  • https://support.google.com/faqs/answer/6346016
  • https://developer.android.com/intl/ko/training/articles/security-ssl.html#UnknownCa

Self-Signed SSL로 Apache HTTPS 구현하기

apache-logo

기존의 HTTP(HyperText Transfer Protocol)과 다르게 HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)는 SSL위에서 돌아가는 HTTP의 평문 전송 대신에 암호화된 통신을 하는 프로토콜입니다.

이런 HTTPS를 통신을 서버에서 구현하기 위해서는 신뢰할 수 있는 상위 기업이 발급한 인증서가 필요로 한데 이런 발급 기관을 CA(Certificate authority)라고 합니다. 하지만 이러한 기업의 인증서를 발급받는것은 무료가 아니며 단순히 모바일앱과의 통신이라던가 테스트 목적에서 발급을 받기에는 부담스러운 부분이 있을 수 있습니다.

이런 경우 자체적으로 인증서를 발급하여 사용하는 방법을 고려해 볼 수 있습니다. 이 경우 브라우저 접속시에 보안 경고가 발생하므로 주의하시기 바랍니다. 이 글에서는 CentOS와 Apache 웹서버 구동 환경에서의 HTTPS 구축하는 방법을 정리해 보겠습니다.

# yum install openssl mod_ssl

우선 위와 같이 openssl과 mod_ssl을 설치해줍니다. 만약 설치가 되어있는 상태라면 무시해도 됩니다. 이번엔 다음과 같은 명령을 사용하여 인증서를 생성해 줍니다.

(선택 1) 한번에 원하는 인증서를 발급하기

따로 원하는 CA를 둘것없이 간편하게 서버에서 사용할 인증서를 발급하는 방법은 다음과 같습니다.

# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/certs/mycert.key -out /etc/ssl/certs/mycert.crt
Generating a 2048 bit RSA private key
........................+++
writing new private key to '/etc/ssl/certs/mycert.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:KR
State or Province Name (full name) []:Gyeonggi-do
Locality Name (eg, city) [Default City]:Seongnam-si
Organization Name (eg, company) [Default Company Ltd]:TheEye Company
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

(선택 2) 자체 CA 인증서를 생성하고 이를 통해 인증서를 발급하기

자체적으로 CA를 구축하는 방법도 있습니다. 세상 누구도 알아주지 않겠지만 다양한 서비스를 동시에 운영중이라면 해볼만한 시도라고 생각이 됩니다. 우선 CA 인증서부터 발급합니다.

# openssl genrsa -out /etc/ssl/certs/rootCA.key 2048
Generating RSA private key, 2048 bit long modulus
......................................................+++
..........+++
e is 65537 (0x10001)

# openssl req -x509 -new -nodes -key /etc/ssl/certs/rootCA.key -days 365 -out /etc/ssl/certs/rootCA.crt
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:KR
State or Province Name (full name) []:Gyeonggi-do
Locality Name (eg, city) [Default City]:Seongnam-si
Organization Name (eg, company) [Default Company Ltd]:Theeye Company
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

이번엔 CA에 인증서 발급을 요청하기 위한 CSR(Certificate Signing Request)를 생성합니다.

# openssl genrsa -out /etc/ssl/certs/mycert.key 2048    
Generating RSA private key, 2048 bit long modulus
........+++
..........................+++
e is 65537 (0x10001)

# openssl req -new -key /etc/ssl/certs/mycert.key -out /etc/ssl/certs/mycert.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:KR
State or Province Name (full name) []:Gyeonggi-do
Locality Name (eg, city) [Default City]:Seongnam-si
Organization Name (eg, company) [Default Company Ltd]:TheEye Company
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

이번엔 마지막으로 CA 인증서와 CSR을 이용하여 서비스에 사용할 인증서를 발급해 보도록 하겠습니다.

# openssl x509 -req -in /etc/ssl/certs/mycert.csr -CA /etc/ssl/certs/rootCA.crt -CAkey /etc/ssl/certs/rootCA.key -CAcreateserial -out /etc/ssl/certs/mycert.crt -days 365
Signature ok
subject=/C=KR/ST=Gyeonggi-do/L=Seongnam-si/O=TheEye Company
Getting CA Private Key

아파치 웹서버 설정에 적용

이제 /etc/ssl/certs 디렉토리에 mycert.key 파일과 mycert.crt 파일이 생성되었습니다. 이제 Apache 설정에 다음과 같은 형태로 이 키를 지정해 줍니다.

LoadModule ssl_module modules/mod_ssl.so

...

<VirtualHost 124.217.198.56:443>
  DocumentRoot /home/theeye/public_html
  ServerName theeye.pe.kr
  SSLEngine on
  SSLCertificateFile /etc/ssl/certs/mycert.crt
  SSLCertificateKeyFile /etc/ssl/certs/mycert.key
</VirtualHost>

이제 Apache 데몬을 재시작 한 뒤 해당 도메인에 https 로 접속하면 다음과 같은 보안 경고가 뜨게 됩니다.

not_secure_on_apache_https

하지만 아래의 안전하지 않음으로 이동을 통해 사이트에 정상적으로 접속하는 것이 가능합니다. 만약 iOS/Android에서 구동되는 어플리케이션의 경우 별도의 처리가 필요할 것입니다.