내가 읽으려고, 내맘대로 번역한 글.


//-----------------------------------------------------------------------------
// 번역자 : 이석우
// 번역일 : 2016.01.26
// 제목 : HTTPS with Client Certificates on Android
// 원문 : http://chariotsolutions.com/blog/post/https-with-client-certificates-on/
//-----------------------------------------------------------------------------

Many Android applications use REST or another HTTP based protocol to communicate with a server.
Working with HTTP and HTTPS on Android is generally fairly straightforward and well documented. Depending on the version of the Android OS, either HTTPClient or HttpURLConnection “just work”. Either one can be used, but the official recommendation is to use HttpURLConnection for Gingerbread or later, and HTTPClient for Froyo and earlier.
많은 안드로이드 어플리케이션들이 REST 또는 다른 HTTP 기반의 프로토콜을 사용하여 서버와 통신한다.
안드로이드에서 HTTP, HTTPS 작업은 일반적으로 매우 간단하고, 문서도 잘 되어 있다.
안드로이드OS 버젼에 따라서 HTTPClient 또는 HttpURLConnection 은 잘 동작한다.
공식적인 권장사항은 Gingerbread 이상은 HttpURLConnection를 사용하는 것이고,
Froyo 이하는 HTTPClient를 사용하는 것이다.


Things get interesting, however, once you go past “plain vanilla” HTTPS. On a recent project, we needed to communicate with an HTTPS server that required client certificates, and which used a self-signed server certificate. This presented some interesting challenges. The rest of this post describes how we met those challenges.
Things get interesting, however, once you go past “plain vanilla” HTTPS. -> 해석못함
최근프로젝트에서 클라이언트 인증서를 요구하는 HTTPS 서버와 통신해야만 했다.
HTTPS 서버는 자체서명된 서버인증서를 사용했다.
이것은 흥미로운 도전들을 제시했다.
이글의 나머지는 우리가 어떻게 그 도전들을 충족했는지 기술한다.


If you want to follow along by trying out the code, it’s available on GitHub. You will, of course, have to set up your own HTTPS server and your own server and client certificates. One way that you can do this is by using the Apache mod_ssl module.
너가 코드를 따라가기 원하는 GitHub에서 있다.
물론 너 스스로 HTTPS 서버와 서버용인증서, 클라이언트용인증서를 설정해야 한다.
이것을 하려면 Apache mod_ssl 모듈을 이용할수 있다.


Before starting out with using HTTP or HTTPS on Android, make sure that your application has permission to access the network by adding the following to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>
안드로이드에서 HTTP, HTTPS를 이용하기 전에, 너의 어플리케이션이 네트웍에 연결할 권한이 있는지 확인해라.
AndroidManifest.xml 파일에 아래의 내용을 넣어라


Challenge #1: Using a Client Certificate
도전#1 : 클라이언트인증서 이용하기


Our client certificate was issued in the PKCS 12 format, as a .p12 file.
우리의 인증서는 PKCS12포맷의 .p12 파일로 발행되었다.


To give our application access to the certificate, we used the DDMS utility to copy the certificate file to the root directory of the phone’s sdcard. If you are using an emulator, you can do the same thing to copy the certificate to the emulator’s sdcard.
You can also choose to mount the phone as a disk drive when you plug in the USB cable, and copy the file that way.
In our final application, we “imported” the certificate by copying it to application-specific storage, and then deleting the original from the sdcard root directory.
어플리케이션이 인증서에 접근할수있게 하려고, DDMS도구를 사용하여 인증ㅆ서를 폰의 sdcard 루트디렉토리에 복사하였다.
너가 에뮬레이터를 사용한다면 에뮬레이터의 sdcard에 인증서를 똑같이 복사하면 된다.
USB게이블을 꽂고, 폰에서 마운트하고 파일을 복사할수도 있다.
우리는 인증서를 어플리케이션의 내부공간으로 임포트하고, sdcard의 루트디렉토리에서 원본을 지웠다.


The secret to using our client certificate is setting up a custom KeyStore containing the certificate, and then using it to create a custom SSLContext.
우리가 클라이언트인증서를 사용하는 비밀은 그 인증서를 포함하는 커스텀 KeyStore를 설정하고 이걸 사용하는 커스텀 SSLContext를 만드는것이다


Once we have a reference to a File containing the client certificate and the password for the certificate,
we load it into an appropriate KeyStore (see the sample SSLContextFactory.java):
클라이언트인증서를 담고있는 파일의 참조와 패스워드를 가지게 되면
적절한 KeyStore를 만들수있다.


keyStore = KeyStore.getInstance("PKCS12");
fis = new FileInputStream(certificateFile);
keyStore.load(fis, clientCertPassword.toCharArray());


Now that we have the KeyStore containing the client certificate, we can use it to build an SSLContext:
이제 클라이언트인증서를 포함한 KeyStore를 가지게 되었으니, 이걸 이용해서 SSLContext를 만들수 있다.


KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, clientCertPassword.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, null, null);


The SSLContext can then be used with an HTTPUrlConnection to connect to the server:
SSLContext는 서버와 연결하는 HttpURLConnection과 같이 사용된다.


String result = null;
HttpURLConnection urlConnection = null;

 try {
    URL requestedUrl = new URL(url);
    urlConnection = (HttpURLConnection) requestedUrl.openConnection();
    if(urlConnection instanceof HttpsURLConnection) {
        ((HttpsURLConnection)urlConnection)
             .setSSLSocketFactory(sslContext.getSocketFactory());
    }
    urlConnection.setRequestMethod("GET");
    urlConnection.setConnectTimeout(1500);
    urlConnection.setReadTimeout(1500);
    lastResponseCode = urlConnection.getResponseCode();
    result = IOUtil.readFully(urlConnection.getInputStream());
    lastContentType = urlConnection.getContentType();
} catch(Exception ex) {
    result = ex.toString();
} finally {
    if(urlConnection != null) {
        urlConnection.disconnect();
    }
}



Challenge #2: Trusting a Self-Signed Server Certificate
도전 #2: 자체서명한 서버인증서를 신뢰하도록 하기


We now have Android client code that can connect to an HTTPS server and present a client certificate.
이제 HTTPS 서버와 연결할수 있고, 클라이언트인증서를 제출하는 안드로이드 코드를 알게되었다.


However, this only works if the server’s certificate is trusted. In practice, this means that the server certificate must be signed by one of the major certificate authorities, such as VeriSign, Thawte, Geotrust, Comodo, etc. CA certificates for these well-known providers are pre-loaded into the phone’s default trust store.
그러나 이것은 서버의 인증서가 신뢰되었을때만 동작한다. 실제로 서버인증서는 주요 인증기관의 한곳으로부터 사인을 받아야 한다.
(VeriSign, Thawte, Geotrust, Comodo 등등)
잘알려진 인증기관의 CA인증서는 폰의 기본 신뢰저장소에 미리 등록되어 있다.


In our case, the certificate was self-signed. This means that the default TrustManager in our SSLContext will not trust the server’s certificate, and the SSL connection will fail.
우리의 경우, 인증서는 자체서명되었다.
이것은 SSLContext 안의 기본 TrustManager는 서버인증서를 신뢰하지 않을거고, SSL 연결을 실패할거란걸 뜻한다.


If you search the web for ways to trust self-signed server certificates, you’ll likely see a lot of really bad advice. Most responses to this sort of question involve setting up a custom TrustManager that simply trusts everything. This approach is both pointless and obviously insecure.
너가 웹에서 자체서명된 서버인증서를 신뢰하는법을 검색했다면, 정말 나쁜 조언을 많이 보았을것이다.
이런종류의 답변 대부분은 그냥 모든것을 신뢰하도록 커스컴 TrustManager를 설정하는 법을 설명한다.
이러한 방법은 명백히 무의미하고 보안도 허술해진다.


What we really want to do is to set up a custom TrustManager that trusts our self-signed certificate, and provide that TrustManager to our custom SSLContext. To do this, we need a copy of the server’s certificate chain, which will have to include at least the self-signed CA Certificate and the Intermediate Certificate that is signed by the CA. If you simply export the server’s certificate, you will actually get a file with three certificates - the CA certificate, the Intermediate Certificate, and the server’s certificate. This is fine - the extra certificate is not needed, but won’t hurt, either (usually - see Challenge #3 below).
우리가 진짜로 원하는것은 우리의 자체서명된 인증서를 신뢰하도록 커스컴 TrustManager를 설정해서, 커스텀 SSLContext가 그걸 사용하는 것이다.
그러려면
CA에 의해 서명된 중간인증서,
자체서명된 CA인증서,
적어도 이 2개를 포함하는 서버인증서 체인의 복사본이 필요하다.
너가 단순히 서버의 인증서를 export 했다면, 실제로 3개의 인증서 파일을 얻게 된다 (CA인증서, 중간인증서, 서버인증서)
(좋다. 추가적인 인증서는 필요하지 않다, 다치지않는다. 하나. 뭔소린지 모르겠음)


The method for trusting the self-signed server certificate is very similar to the method for providing a client certificate - we load the certificate into a KeyStore, use that KeyStore to produce an array of TrustManagers, and then use those TrustManagers to create the SSLContext.
자체서명된 서버인증서를 신뢰하는 방법은 클라이언트인증서를 제공하는 방법과 매우 비슷하다.
KeyStore에 인증서를 넣고,
KeyStore를 이용해서 TrustManager의 배열을 만든고,
TrustManager배열을 이용해서 SSLContext를 만든다.


In our app, we included the server certificate file in the application resources (since it doesn’t change from user to user, and doesn’t change very often), but you could put this in an external file as well.
우리의 앱은 서버인증서 파일을 어플리케이션의 리소스에 포함시켰다.
(사용자마다 다르지 않고, 자주 바뀌지 않으니까)
하자만 너는 외부파일로 넣어도 된다.


Here’s the code for loading server certificate chain into a KeyStore that will be used as a “trust store”, based on having the base-64-encoded (.pem) certificate as a String:
서버인증서 체인을 KeyStore에 넣고 신뢰저장소로 사용하게 될 코드가 여기 있다.
서버인증서 체인은 base64로 인코딩된 문자열을 가지고 있다.


byte[] der = loadPemCertificate(new ByteArrayInputStream(certificateString.getBytes()));
ByteArrayInputStream derInputStream = new ByteArrayInputStream(der);
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);
trustStore.setCertificateEntry(alias, cert);


Now that we have the “trustStore” KeyStore with the server’s certificate, we use it to initialize the SSLContext. Adding to the code that we had before, production of the SSLContext now becomes:
이제 trustStore라는 KeyStore(서버의 인증서와 함께)를 가지고 있고, 이걸 이용해서 SSLContext를 초기화 할거다.이전에 SSLContext를 만드는 코드를 아래처럼 바꿔라


KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, clientCertPassword.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);<br>TrustManager[] trustManagers = tmf.getTrustManagers(); 

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);


Now, we can securely connect to our server, trust its certificate (but not others), and present our client certificate. If all goes well, you’re done, and can safely stop reading.
이제 우리는 서버와 안전하게 연결할수 있고, 서버인증서를 신뢰할수 있고, 클라이언트인증서를 제시할수있다.
모든게 잘 되면. 끝이다. 더이상 이글을 읽지 않아도 된다.


Challenge #3: Handling an Improperly Ordered Server Certificate Chain
도전 #3: 잘못 정렬된 서버인증서 체인의 처리


As it turns out, some Apache mod_ssl installations (and possibly other SSL providers), whether due to a bug or mis-configuration, provide the server certificate chain in the wrong order.
일부 아파치 mod_ssl 설치본에서, 서버인증서 체인을 잘못된 순서로 정렬하는 버그가 발견되었다.


To work properly, the certificates in the server’s certificate chain must start with the “root”, or CA certificate, followed by any intermediate certificates. If the server’s certificate is included, it must come last. Each certificate in the chain (other than the root) must be preceded by the certificate that was used to sign it.
올바로 동작하려면 서버인증서 체인은 반드시 root 또는 CA인증서로 시작해야 한고,
그뒤에 중간인증서들이 나와야 한다.
만약 서버의 인증서가 포함된다면 반드시 맨뒤에 있어야 한다.
체인파일안의 각 인증서는 반드시 그것을 서명하는데 사용된 인증서보다 먼저 나와야 한다 (root 빼고)


In our case, a mis-configured server was producing a certificate chain that started with the root CA certificate, but which had the server’s certificate next, followed by the intermediate certificate. This caused the SSL handshake to fail.
우리의 경우는, 인증체인이 잘못되어서 root CA인증서로 시작하지만, 서버의 인증서 다음에 중간인증서가 있게되었다.이것때문에 SSL handshake 가 실패했다.


To work properly, the certificates in the server’s certificate chain must start with the server certificare, followed by any intermediate certificates. If the “root”, or CA certificate, is included, it must come last. Each certificate in the chain (other than the root) must be followed by the certificate that was used to sign it.
올바로 동작하려면, 서버인증체인은 반드시 서버인증서로 시작하고, 그 다음에 중간인증서들이 나와야 한다.
만약 root 또는 CA인증서가 있다면, 반드시 맨 뒤에 나와야 한다
체인파일안의 각 인증서는 반드시 그것을 서명하는데 사용된 인증서보다 먼저 나와야 한다 (root 빼고)
-> 어라 위의 문장과 내용이 다르네... 뭐가 맞는건지...


The most straightforward thing to do in this situation, if you can, is to correct the server’s certificate chain. If you can’t, then the out-of-order certificate chain must be handled by the client.
이 상황에서 해야할 가장 간단한 것은, 가능하다면 서버의 인증서체인을 고치는것이다.
불가능하다면, 인증서체인의 잘못된 순서를 클라이언트에서 처리해야 한다.


In our Android app, we handled the out-of-order certificate chain by implementing a custom X509TrustManager that re-orders the chain
우리의 안드로이드앱은 커스텀 X509TrustManager를 구현하여 잘못된 인증서체인을 재정렬하도록 하였다.


The code for the custom X509TrustManager is too large to include here (see the full source on GitHub), but once we have it implemented, we use it in the creation of the SSLContext by replacing the ‘trustStores’ array with one containing the custom implementation:
커스텀 X509TrustManager의 코드는 너무 커서 여기에 넣을수 없다 (전체 소스는 GitHub를 봐라. https://github.com/rfreedman/android-ssl)
하지만 일단 한번 만들어놓으면, trustStores 배열 대신하는 SSLContext를 만들고 사용할수 있다.


KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, clientCertPassword.toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();

TrustManager[] trustManagers = {new CustomTrustManager(trustStore)};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);


By loading our own certificates into our keystore and trust store, and re-ordering the server’s certificate chain, we were able to provide an SSLContext that let us connect to an SSL server with client a certificate and a badly-ordered, self-signed server certificate chain.
우리의 인증서를 keystore와 신뢰저장소에 넣고, 서버의 인증서 체인을 재정렬 함으로서,
SSLContext를 만들수 있고, SSL 서버에 연결할수 있다.

끝.


반응형
Posted by 돌비
,