Adicione programaticamente uma autoridade de certificação, mantendo os certificados SSL do sistema Android
Há muitas perguntas sobre este tópico no StackOverflow, mas não acho que encontre uma relacionada ao meu problema.
Eu tenho um aplicativo Android que precisa se comunicar com servidores HTTPS: alguns assinados com uma CA registrada no keystore do sistema Android (sites HTTPS comuns) e outros assinados com uma CA que eu possuo, mas não no keystore do sistema Android (um servidor com um certificado autoassinado, por exemplo).
Eu sei como adicionar minha CA programaticamente e forçar todas as conexões HTTPS a usá-las. Eu uso o seguinte código:
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();
}
}
}
No entanto, isso desativa o uso do keystore do sistema Android e não posso mais consultar sites HTTPS assinados com outra CA.
Tentei adicionar minha autoridade de certificação ao keystore do Android usando:
KeyStore.getInstance("AndroidCAStore")
... mas não consigo adicionar minha autoridade de certificação (uma exceção é lançada).
Eu poderia usar o método de instânciaHttpsURLConnection.setSSLSocketFactory(...)
em vez do global estáticoHttpsURLConnection.setDefaultSSLSocketFactory(...)
para dizer caso a caso quando minha CA deve ser usada.
Mas não é nada prático, tanto mais que às vezes não consigo passar por um pré-configuradoHttpsURLConnection
objeto para algumas bibliotecas.
Algumas idéias sobre como eu poderia fazer isso?
EDITAR - RESPOSTA
Ok, seguindo o conselho dado, aqui está o meu código de trabalho. Pode precisar de algumas melhorias, mas parece funcionar como ponto de partida.
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();
}
}
}