Android에서 자체 서명 된 SSL 수락
-
07-07-2019 - |
문제
Android의 Java에서 자체 서명 된 인증서를 어떻게 수락합니까?
코드 샘플은 완벽합니다.
나는 인터넷의 모든 곳을 보았고 어떤 사람들은 해결책을 찾았다 고 주장하지만, 작동하지 않거나 샘플 코드가 없다고 주장합니다.
해결책
Exchangeit 에이 기능이 있으며 Webdav를 통해 Microsoft Exchange에 연결됩니다. 다음은 SSL을 통해 자체 서명 인증서에 연결하는 httpclient를 만들기위한 코드입니다.
SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
easysslsocketfactory입니다 여기, easyx509trustmanager입니다 여기.
Exchange의 코드는 오픈 소스이며 GoogleCode에서 호스팅됩니다. 여기, 문제가있는 경우. 더 이상 적극적으로 작업하지는 않지만 코드는 작동해야합니다.
Android 2.2 이후 프로세스가 약간 변경되었으므로 확인하십시오. 이것 위의 코드를 만들기 위해.
다른 팁
EJP가 올바르게 댓글을 달았 듯이 "독자들은이 기술이 근본적으로 안전하지 않다는 점에 주목해야합니다. SSL은 적어도 하나의 피어가 인증되지 않으면 안전하지 않습니다. RFC 2246 참조."
추가 수업이없는 또 다른 방법이 있습니다.
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
private void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
어제 회사의 편안한 API를 HTTP로 마이그레이션하면서 자체 서명 된 SSL 인증서를 사용 하면서이 문제에 직면했습니다.
나는 어디에서나보고 있지만 내가 찾은 모든 "올바른"표시 답변은 인증서 검증을 비활성화하는 것으로 구성되어 SSL의 모든 감각을 명확하게 무시했습니다.
나는 마침내 해결책에왔다 :
로컬 키 스토어를 만듭니다
앱이 자체 서명 된 인증서를 검증 할 수 있도록하려면 Android가 엔드 포인트를 신뢰할 수있는 방식으로 인증서를 사용자 정의 키 스토어에 제공해야합니다.
이러한 사용자 정의 Keystores의 형식은 "BKS"입니다. 바운시 캐슬, 따라서 다운로드 할 수있는 1.46 버전의 BouncycastleProvider가 필요합니다. 여기.
자체 서명 된 인증서도 필요합니다. self_cert.pem
.
이제 KeyStore를 만드는 명령은 다음과 같습니다.
<!-- language: lang-sh -->
$ keytool -import -v -trustcacerts -alias 0 \
-file *PATH_TO_SELF_CERT.PEM* \
-keystore *PATH_TO_KEYSTORE* \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
-storepass *STOREPASS*
PATH_TO_KEYSTORE
KeyStore가 생성되는 파일을 가리 킵니다. 그것 존재하지 않아야합니다.
PATH_TO_bcprov-jdk15on-146.jar.JAR
다운로드 된 .jar libary로가는 길입니다.
STOREPASS
새로 만들어진 Keystore 비밀번호입니다.
- 응용 프로그램에 Keystore를 포함시킵니다
새로 생성 된 키 스토어를 복사하십시오 PATH_TO_KEYSTORE
에게 res/raw/certs.bks
(certs.bks 파일 이름 일뿐입니다. 원하는 이름을 사용할 수 있습니다)
키를 만듭니다 res/values/strings.xml
~와 함께
<!-- language: lang-xml -->
<resources>
...
<string name="store_pass">*STOREPASS*</string>
...
</resources>
상속하는이 클래스를 만듭니다
DefaultHttpClient
import android.content.Context; import android.util.Log; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } }
이제 단순히 인스턴스를 사용하십시오 **MyHttpClient**
당신과 마찬가지로 **DefaultHttpClient**
HTTPS 쿼리를 만들려면 자체 서명 된 SSL 인증서를 올바르게 사용하고 검증합니다.
HttpResponse httpResponse;
HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...
MyHttpClient myClient = new MyHttpClient(myContext);
try{
httpResponse = myClient.(peticionHttp);
// Check for 200 OK code
if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
... do whatever you want with your response ...
}
}catch (Exception ex){
Log.d("httpError", ex.getMessage());
}
내가 무언가를 놓치지 않는 한,이 페이지의 다른 답변은 위험하며 SSL을 전혀 사용하지 않는 것과 동일합니다. 인증서가 예상되는 인증서인지 확인하기 위해 추가 점검을하지 않고 자체 서명 된 인증서를 신뢰하는 경우 누구나 자체 서명 된 인증서를 만들고 서버 인 척 할 수 있습니다. 그 시점에서, 당신은 실제 보안이 없습니다.
그만큼 뿐 이를 수행하는 합법적 인 방법 (전체 SSL 스택을 작성하지 않고)은 인증서 확인 프로세스 중에 신뢰할 수있는 추가 신뢰할 수있는 앵커를 추가하는 것입니다. 둘 다 신뢰할 수있는 앵커 인증서를 앱에 하드 코딩하고 OS가 제공하는 신뢰할 수있는 앵커에 추가하는 것과 관련이 있습니다 (그렇지 않으면 실제 인증서를 받으면 사이트에 연결할 수 없습니다).
이 작업을 수행하는 두 가지 방법을 알고 있습니다.
설명대로 사용자 정의 신뢰 상점을 만듭니다 http://www.ibm.com/developerworks/java/library/j-customssl/#8
x509trustmanager의 사용자 정의 인스턴스를 작성하고 getAcceptedissuers 메소드를 재정의하여 인증서가 포함 된 배열을 반환합니다.
public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; }
이 코드는 완전히 테스트되지 않았으며 컴파일되지 않을 수도 있지만 적어도 올바른 방향으로 조종해야합니다.
Brian Yarger의 답변은 다음과 같이 더 큰 CreateSocket 메소드 오버로드를 수정하면 Android 2.2에서도 작동합니다. 자체 서명 된 SSL을 작동시키는 데 시간이 걸렸습니다.
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
안드로이드에서 HttpProtocolParams
수락합니다 ProtocolVersion
보다는 HttpVersion
.
ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
@chris- 댓글을 추가 할 수 없기 때문에 이것을 답으로 게시합니다 (아직). 웹 뷰를 사용할 때 귀하의 접근 방식이 작동하는지 궁금합니다. Android 2.3에서는 그렇게 할 수 없습니다. 대신 흰색 화면을 얻습니다.
좀 더 검색 한 후, 나는 이것을 발견했다 웹 뷰에서 SSL 오류를 처리하기위한 간단한 수정 그것은 나를위한 매력처럼 작동했습니다.
핸들러에서 특별한 개발 모드에 있는지 확인하고 handler.proceed ()를 호출합니다. 그렇지 않으면 handler.cancel ()을 호출합니다. 이를 통해 지역 웹 사이트에서 자체 서명 된 인증서에 대한 개발을 수행 할 수 있습니다.