2021. 11. 25. 10:12ㆍProgramming/Android
Android 앱에서 https 연결을 시도하려고 할 때 Trust anchor for certification path not found 라는 에러가 발생하면서 연결이 실패했다.
서비스 중인 앱이라 부랴부랴 원인을 찾기 시작함.
원인은 해당 앱에 신뢰성 있는 인증서를 찾지 못했기 때문인 것 같다.
우선 첫번째로 TrustManager를 사용해서 인증서 회피를 시도했다.
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain,
String authType)
throws java.security.cert.CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain,
String authType)
throws java.security.cert.CertificateException {
// TODO Auto-generated method stub
}
}};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
위의 코드를 적용 후 https에 접속하면 정상적으로 결과를 받아올 수 있는 것을 확인했다. 하지만 아래의 무시무시한 검색 결과를 발견!!
X509TrustManager 사용시 안전하지 않은 인터페이스를 구현했을 경우 앱이 차단될 수 있다고 구글에서 경고한 것이다.
코드 상에서도 보면 checkServerTrusted의 구현 부분에 아무런 코드가 없어도 동작이 잘되고 있는 것을 확인해 볼 수가 있다. 실제로는 저 부분에 신뢰할 수 있는 인증서인가를 확인할 코드를 구현해야 하는 것 같다.
여러가지 조건 등 (능력 부족) 으로 첫 번째 방법은 포기!!
두 번째 방법으로 다시 https 접근을 시도해 본다.
본래의 에러 내용이 신뢰할 수 없는 인증서를 찾지 못한것이기 때문에 신뢰할 수 있는 인증서를 앱 내에 등록하면 되지 않을까?
그래서 여러가지 찾아 봄...
우선 접속하고자 하는 url을 통해 인증서 정보를 가져오도록 하자.
$> openssl s_client -connect [접속하고자 하는 url]:443
위 결과를 실행시키면 아래와 같이 인증서 정보가 나타나게 된다.
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018
CN이 GlobalSign RSA OV SSL CA 2018로 되어 있다. 인터넷에서 찾아보자. (query : GlobalSign RSA OV SSL CA 2018 download)
와우 ~ 아래의 url을 찾아 냈다.
http://certificate.fyicenter.com/7530_GlobalSign_RSA_OV_SSL_CA_2018_Certificate-F8EF7FF2CD7867A8DE6F8F248D88F1870302B3EB.html
해당 url에 접속한 후 PEM Format 정보를 가져오자.
----BEGIN CERTIFICATE----
~~~
----END CERTIFICATE----
해당 정보를 복사한 후 안드로이드 res < raw < mycert.crt 의 파일로 만들었다.
이렇게 만든 인증 파일을 이용하는 코드를 구현해 보자.
CertificateFactory cf;
Certificate ca;
String result = "";
InputStream caInput;
try {
cf = CertificateFactory.getInstance("X.509");
caInput = ApplicationController.getAppContext().getResources()
.openRawResource(R.raw.mycert);
ca = cf.generateCertificate(caInput);
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null,null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
caInput.close();
URL url = new URL("접속하려는 https url");
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
InputStream in = urlConnection.getInputStream();
result = new BufferedReader(new InputStreamReader(in, "euc-kr"))
.lines().parallel().collect(Collectors.joining("\n"));
System.out.println(result);
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | KeyManagementException e) {
e.printStackTrace();
}
정상적으로 데이터를 받아 오는 걸 확인 할 수가 있다.
참고 url : https://developer.android.com/training/articles/security-ssl?hl=ko#UnknownCa