Es gibt viele Fragen zu diesem Thema in StackOverflow, aber ich finde keine, die sich auf mein Problem beziehen.Programmgesteuertes Hinzufügen einer Zertifizierungsstelle bei Beibehaltung der SSL-Zertifikate für Android-Systeme
Ich habe eine Android-Anwendung, die mit HTTPS-Servern kommunizieren muss: einige signiert mit einer im Android-System Keystore (gemeinsame HTTPS-Websites) registriert, und einige mit einer CA unterzeichnet ich besitze aber nicht im Android-System keystore (ein Server mit einem automatisch signierten Zertifikat für Beispiel).
Ich weiß, wie ich meine CA programmgesteuert hinzufügen und erzwinge jede HTTPS-Verbindung, um es zu verwenden. Ich verwende den folgenden Code ein:
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();
}
}
}
jedoch tun, dass die Verwendung der Android-System deaktiviert Schlüsselspeicher, und ich kann nicht abfragen HTTPS-Sites mit anderen CA unterzeichnet mehr.
Ich versuchte, meine CA im Android Schlüsselspeicher hinzuzufügen, mit:
KeyStore.getInstance("AndroidCAStore")
... aber ich kann meine CA darin (eine Ausnahme gestartet wird) nicht dann hinzuzufügen.
Ich könnte die Instanzmethode HttpsURLConnection.setSSLSocketFactory(...)
anstelle der statischen globalen HttpsURLConnection.setDefaultSSLSocketFactory(...)
verwenden, um von Fall zu Fall zu sagen, wann meine CA verwendet werden muss.
Aber es ist überhaupt nicht praktisch, um so mehr, da ich manchmal ein vorkonfiguriertes HttpsURLConnection
Objekt an einige Bibliotheken nicht übergeben kann.
Einige Ideen, wie ich das tun könnte?
EDIT - ANTWORT
Ok, nach dem gegebenen Rat, hier ist mein Arbeitscode. Es könnte einige Verbesserungen benötigen, aber es scheint als Ausgangspunkt zu arbeiten.
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();
}
}
}
Sind Sie sicher, dass Ihr Code mit vertrauenswürdigem Zertifikat und selbstsignierter Signatur arbeitet? Ich habe versucht, es funktioniert nur mit diesem Zertifikat aus dem Eingabestream; Wenn ich auf einen Server mit vertrauenswürdigem Zertifikat umschalte, wird die Methode "checkServerTrusted" ausgelöst. Ausnahme –
Wurde der Test mit Android N oder einer früheren Version durchgeführt? –
Ich testete auf 4.4, 6.0 –