Istnieje wiele pytań na ten temat w StackOverflow, ale nie wydaje mi się, aby jeden był związany z moim problemem.Programowe dodawanie urzędu certyfikacji przy zachowaniu certyfikatów SSL systemu Android
Mam aplikację na Androida, która musi komunikować się z serwerami HTTPS: niektóre są podpisane za pomocą urzędu certyfikacji zarejestrowanego w magazynie kluczy systemu Android (typowe witryny HTTPS), a niektóre są podpisane przy użyciu urzędu certyfikacji, który jest właścicielem, ale nie w magazynie kluczy systemu Android (serwer z certyfikatem z autouzupełnieniem na przykład).
Wiem, jak dodać program CA i zmusić każde połączenie HTTPS do korzystania z niego. Używam następujący kod:
public class SslCertificateAuthority {
public static void addCertificateAuthority(InputStream inputStream) {
try {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(inputStream);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
// 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();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Jednak robi to wyłącza korzystanie z systemu Android kluczy, a ja nie mogę kwerendy HTTPS strony podpisały z innymi CA więcej.
Próbowałem dodać swój urząd w Android kluczy, używając:
KeyStore.getInstance("AndroidCAStore")
... ale nie można wtedy dodać swój urząd w nim (wyjątek jest uruchomiony).
Mogę użyć metody instancji HttpsURLConnection.setSSLSocketFactory(...)
zamiast statycznego globalnego HttpsURLConnection.setDefaultSSLSocketFactory(...)
do opowiedzenia w każdym przypadku, kiedy mój CA musi być użyty.
Ale to wcale nie jest praktyczne, tym bardziej, że czasami nie mogę przekazać wstępnie skonfigurowanego obiektu HttpsURLConnection
do niektórych bibliotek.
Kilka pomysłów na temat tego, w jaki sposób mogę to zrobić?
EDIT - ODPOWIEDŹ
Ok, po danej rady, tu jest mój kod roboczych. Może wymagać pewnych ulepszeń, ale wydaje się działać jako punkt wyjścia.
public class SslCertificateAuthority {
private static class UnifiedTrustManager implements X509TrustManager {
private X509TrustManager defaultTrustManager;
private X509TrustManager localTrustManager;
public UnifiedTrustManager(KeyStore localKeyStore) throws KeyStoreException {
try {
this.defaultTrustManager = createTrustManager(null);
this.localTrustManager = createTrustManager(localKeyStore);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
private X509TrustManager createTrustManager(KeyStore store) throws NoSuchAlgorithmException, KeyStoreException {
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init((KeyStore) store);
TrustManager[] trustManagers = tmf.getTrustManagers();
return (X509TrustManager) trustManagers[0];
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException ce) {
localTrustManager.checkServerTrusted(chain, authType);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTrustManager.checkClientTrusted(chain, authType);
} catch (CertificateException ce) {
localTrustManager.checkClientTrusted(chain, authType);
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] first = defaultTrustManager.getAcceptedIssuers();
X509Certificate[] second = localTrustManager.getAcceptedIssuers();
X509Certificate[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
}
public static void setCustomCertificateAuthority(InputStream inputStream) {
try {
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(inputStream);
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// 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 and system CA
UnifiedTrustManager trustManager = new UnifiedTrustManager(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{trustManager}, null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Czy jesteś pewien, że Twój codee działa zarówno z zaufanym certyfikatem, jak i samopodpisanym? Próbowałem, działa tylko z tym certyfikatem ze strumienia wejściowego; kiedy przełączam się na serwer z zaufanym certyfikatem, niż metoda "checkServerTrusted" rośnie wyjątek –
Czy Twój test został wykonany z Androidem N lub poprzednią wersją? –
Przetestowałem na 4.4, 6.0 –