2020. 12. 4. 21:39ㆍProgramming/Android
크롤링 하는 서버가 http -> https로 변경됨에 따라 나 역시 서비스 중인 앱을 https로 변경하였다.
단순히 주소만 https로 변경하면 되는 줄 알았는데, 그게 아니었다 ㅠ_ㅠ
인증서 없이 간단하게 https 통신을 가능하게 하기 위해서 아래의 코드처럼 모든 인증서를 허용해주는 객체를 만들고 httpsConnection에 설정을 추가해 준다.
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager(){
public X509Certificate[] getAcceptedIssuers(){return new X509Certificate[0];}
public void checkClientTrusted(X509Certificate[] certs, String authType){}
public void checkServerTrusted(X509Certificate[] certs, String authType){}
}
};
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
통신이 잘됨을 확인했고, 기분 좋게 구글플레이에 배포를 완료했다.
배포 완료 후 몇 분 뒤 아래와 같은 메일을 구글로부터 받게 된다 -_-
Hello Google Play Developer,
We reviewed, with package name, and found that your app uses software that contains security vulnerabilities for users. Apps with these vulnerabilities can expose user information or damage a user’s device, and may be considered to be in violation of our Malicious Behavior policy.
Below is the list of issues and the corresponding APK versions that were detected in your recent submission. Please migrate your apps to use the updated software as soon as possible and increment the version number of the upgraded APK.
내가 어떤 것을 위반 했는데, 그게 TrustManager와 관련이 있는 것 같고 2021.02.24일 까지 개선하라는게 주된 내용이다.
Deadline to fix라는 글씨가 엄청 무겁게만 느껴졌다..
그럼 이제부터 이렇게 메일을 받게 된 원인을 파악해보자.
TrustManager 객체를 만들 때 checkServerTrusted란 함수를 정의해 주고 그 안에서 인증서의 유효성등을 확인해야 하는데 나같은 경우에는 아무런 처리도 안하고 있기 때문에 구글쪽에서는 해당 코드가 위험하다고 판단한 것이다. (예상하건데 checkServerTrusted 함수란이 비어있는 경우 기계적으로 판단해서 걸러내는 게 아닐까?)
일단은 checkServerTrusted 함수 내에 checkValidity 함수로 걸러내는 방법도 안되는 것 같다. (이것도 사실 정확한 문제 해결은 아님)
계속 우회할 수 있는 방법을 찾다가 결국에는 통신하고자 하는 서버의 인증서를 앱 내에 집어넣기로 했다.
해당 서버의 인증서를 가져오기 위해서 아래와 같이 명령어를 날려준다.
$> openssl s_client -connect daum.net:443
요렇게 인증서를 가져온 후, -----BEGIN CERTIFICATE----- ~ -----END CERTIFICATE----- 에 해당하는 부분을 긁어서 앱 내의 String 변수로 넣어주자.
String cert = "----BEGIN ~~~ ";
이제는 아까 모든 인증서를 통과 시키는 객체 대신 위의 인증서를 매개체로 통신하는 객체를 만들어 준다.
public void setCert(){
try {
ByteArrayInputStream derInputStream = new ByteArrayInputStream(AppString.SSL_KEY.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);
SSLContext sc = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustStore);
sc.init(null, tmf.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}catch (CertificateException e){
e.printStackTrace();
}catch (KeyStoreException e){
e.printStackTrace();
}catch (NoSuchAlgorithmException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}catch (KeyManagementException e){
e.printStackTrace();
}
}
요렇게 htts 통신 프로토콜에 설정해주면 정상적으로 해당 url 통신이 완료되는 것을 확인할 수가 있다.
나같은 경우에는 다른 문제가 발생했는데, 저렇게 https 설정을 하고 난 뒤 광고가 표시가 안 되는.....
아무래도 해당 인증서 이외는 통신이 안되는 문제가 발생한 듯 하다..
그래서 설정을 해제하는 함수를 추가로 구현해서 통신 시에만 해당 설정이 적용되도록 앱을 수정했다.
public void releaseCert(){
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
}catch (KeyManagementException e){
e.printStackTrace();
}catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
}
다른 분들은 고생하지 마세요~