2019. 8. 20. 22:45 ㆍ개발 이야기/android
SSL pinning 적용방법에 대해서 알아보겠습니다!
retrofit 기반의 소스코드라는 점! 참고해주세요
먼저! SSL pinning이 뭐냐? 이거 왜할까요 ?
중간자 공격(man in the middle attack, MITM)은 네트워크 통신을 조작하여 통신 내용을 도청하거나 조작하는 공격 기법이다. 중간자 공격은 통신을 연결하는 두 사람 사이에 중간자가 침입하여, 두 사람은 상대방에게 연결했다고 생각하지만 실제로는 두 사람은 중간자에게 연결되어 있으며 중간자가 한쪽에서 전달된 정보를 도청 및 조작한 후 다른 쪽으로 전달한다.
많은 암호 프로토콜은 중간자 공격을 막기 위하여 인증을 사용한다. 예를 들어, TLS/SSL 프로토콜은 공개 키를 기반으로 한 인증을 사용한다.
MITM 공격 중 하나인 WEB Proxy 공격을 막는 방법인데요!
인증서를 고정시켜서!
즉, 내 서버에 맞는 인증서만 통신이 가능하도록해서 !
proxy 공격에 대응하는 것입니다
기존에 SSL pinning 우회 방법을 블로그에 올렸었는데요
필요하신 분들은 참고해주세요!
적용 방법에 대해서 많이 알려진 내용이 없어서 알아보도록 하겠습니다!
자 먼저 가장 필요한 것은 당연히! 인증서입니다.
인증서를
코도모 같은 곳에서 구매하게 되면
이런 식으로 인증서가 담긴 압축파일을 주는데요!
이 중에 .crt 확장자 파일 중 key가 없는 파일을 열어보면
하기와 같이 된 인증서가 존재합니다.
이 파일을 안드로이드 앱 디렉터리에 res 디렉터리 하위에 raw 디렉터리에 넣어줍니다
그리고 3가지의 함수를 만들어서 쓸건데요
먼저 인증서를 통해서 KeyStore를 리턴하는 함수!
private static KeyStore getKeyStore(Context context){
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = context.getResources().openRawResource(R.raw.인증서파일이름!);
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);
return keyStore;
}catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
이렇게 keystore를 리턴하는 함수를 만들어준 후!
SSLSocketFactory 함수를 만들어 줍니다
static SSLSocketFactory getPinnedCertSslSocketFactory(Context context) {
try {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(getKeyStore(context));
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
그리고나서 x509TrustManager 클래스를 만들어 줄껀데요
하기와 같이 만들어 줍니다!
package 패키지명
......
public class x509TrustManager implements X509TrustManager {
private final X509TrustManager originalX509TrustManager;
private final KeyStore trustStore;
public x509TrustManager(KeyStore trustStore) throws NoSuchAlgorithmException, KeyStoreException {
this.trustStore = trustStore;
TrustManagerFactory originalTrustManagerFactory = TrustManagerFactory.getInstance("X509");
originalTrustManagerFactory.init((KeyStore) null);
TrustManager[] originalTrustManagers = originalTrustManagerFactory.getTrustManagers();
originalX509TrustManager = (X509TrustManager) originalTrustManagers[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
originalX509TrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException originalException) {
try {
X509Certificate[] reorderedChain = reorderCertificateChain(chain);
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
CertificateFactory factory = CertificateFactory.getInstance("X509");
CertPath certPath = factory.generateCertPath(Arrays.asList(reorderedChain));
PKIXParameters params = new PKIXParameters(trustStore);
params.setRevocationEnabled(false);
validator.validate(certPath, params);
} catch (Exception ex) {
throw originalException;
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
private X509Certificate[] reorderCertificateChain(X509Certificate[] chain) {
X509Certificate[] reorderedChain = new X509Certificate[chain.length];
List<X509Certificate> certificates = Arrays.asList(chain);
int position = chain.length - 1;
X509Certificate rootCert = findRootCert(certificates);
reorderedChain[position] = rootCert;
X509Certificate cert = rootCert;
while ((cert = findSignedCert(cert, certificates)) != null && position > 0) {
reorderedChain[--position] = cert;
}
return reorderedChain;
}
private X509Certificate findRootCert(List<X509Certificate> certificates) {
X509Certificate rootCert = null;
for (X509Certificate cert : certificates) {
X509Certificate signer = findSigner(cert, certificates);
if (signer == null || signer.equals(cert)) { // no signer present, or self-signed
rootCert = cert;
break;
}
}
return rootCert;
}
/**
* A helper method for certificate re-ordering.
* Finds the first certificate in the list of certificates that is signed by the sigingCert.
*/
private X509Certificate findSignedCert(X509Certificate signingCert, List<X509Certificate> certificates) {
X509Certificate signed = null;
for (X509Certificate cert : certificates) {
Principal signingCertSubjectDN = signingCert.getSubjectDN();
Principal certIssuerDN = cert.getIssuerDN();
if (certIssuerDN.equals(signingCertSubjectDN) && !cert.equals(signingCert)) {
signed = cert;
break;
}
}
return signed;
}
/**
* A helper method for certificate re-ordering.
* Finds the certificate in the list of certificates that signed the signedCert.
*/
private X509Certificate findSigner(X509Certificate signedCert, List<X509Certificate> certificates) {
X509Certificate signer = null;
for (X509Certificate cert : certificates) {
Principal certSubjectDN = cert.getSubjectDN();
Principal issuerDN = signedCert.getIssuerDN();
if (certSubjectDN.equals(issuerDN)) {
signer = cert;
break;
}
}
return signer;
}
}
사실은 x509TrustManager 클래스는 없어도 무관한데요!
x509TrustManager 만들어주는 이유는 구글 정책에 맞게 개발하기 위해서 입니다
아래서 사용될 sslSocketFactory에 파라미터로
SSLSocketFactory와 X509TrustManager가 들어가게 만들어주는 것을 권고하고 있더라구요
@deprecated 다 아시졍 ?
그러고 나서 OkHttpClient를 생성해주는
getSafeOkHttpClient 함수를 만들어줍니다
x509TrustManager 를 생성하기 위해서는 keystore가 필요한데요
아래와 같이 생성해주고
sslSocketFactory 파라미터로 getPinnedCertSslSocketFactory와 함께 넣어줍니다.
패킷 로깅용 인터셉터는 제외시켜주셔도 되요!
static OkHttpClient getSafeOkHttpClient(Context context) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
x509TrustManager x509TrustManager = null;
try {
x509TrustManager = new x509TrustManager(getKeyStore(context));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); // 패킷 로깅
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //패킷 로깅
return builder
.sslSocketFactory(Objects.requireNonNull(getPinnedCertSslSocketFactory(context)), Objects.requireNonNull(x509TrustManager))
.addNetworkInterceptor(new AddCookiesInterceptor())
.addInterceptor(new ReceivedCookiesInterceptor())
.addInterceptor(interceptor) //패킷 로깅
.build();
}
이렇게 만들어 준 후 하기와 같이 사용해주시면 됩니다!
public class APIClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(Context context) {
OkHttpClient client = SSLUtil.getSafeOkHttpClient(context);
retrofit = new Retrofit.Builder()
.baseUrl(Config.URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
이렇게 적용된 SSL 통신을 했을때
웹 프록시 도구인 Burp suite를 이용해 Burp 인증서를 설치한 후!
프록시를 시도하게되면 어떻게 될까요!?
이렇게 SSL 핸드쉐이크 에러가 발생하게 됩니다!
그럼! 이상으로 retrofit을 이용한 ssl pinning 적용방법이었습니다.
'개발 이야기 > android' 카테고리의 다른 글
#안드로이드 Third party 로그인 연동 3(파이어베이스 연동) (0) | 2019.09.15 |
---|---|
#안드로이드 Third party 로그인 연동 2(네이버 연동) (0) | 2019.09.12 |
#안드로이드 Third party 로그인 연동 1(카카오톡 로그인 연동) (1) | 2019.09.08 |
안드로이드 네트워크 연결상태 확인 (0) | 2019.08.27 |
안드로이드 FFMPEG 64bit 지원 (7) | 2019.08.01 |