Доверять только конкретный сертификат, выпущенный CA – Android

Я разрабатываю приложение для Android, которое требует подтверждения SSL-авторизации только в том случае, если у сервера есть специальный сертификат, выданный CA (например, GoDaddy). Я ссылался на документацию на веб-сайте разработчика Android, но он говорит только о проверке самоподписанного сертификата или сертификата, которому не доверяют Android. В моем случае я должен получить сертификат клиента и добавить его в мое хранилище ключей. Я использую apache HttpClient для моего Запросы webservice. Буду признателен за любую оказанную помощь.

На самом деле это просто. Вы должны переопределить checkServerTrusted в своем X509TrustManager и выбросить исключение CertificateException, если эмитент не является GoDaddy. В коде, который я предоставил, я использовал «bla bla», вероятно, вы должны получить точное имя.

Сначала вы должны использовать провайдера для своих запросов Http: этот провайдер будет использоваться для выполнения запросов с использованием функции provider.execute:

private static AbstractHttpClient provider; static { try { BasicHttpParams httpParameters = new BasicHttpParams(); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null, null); SchemeRegistry registry = new SchemeRegistry(); registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", new EasySSLSocketFactory(), 443)); ClientConnectionManager ccm = new ThreadSafeClientConnManager(httpParameters, registry); provider = new DefaultHttpClient(ccm, httpParameters); } catch (Exception e) { e.printStackTrace(); provider = new DefaultHttpClient(); } } 

Теперь вам нужен ваш EasySSLSocketFactory:

 public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory { private SSLContext sslcontext = null; private static SSLContext createEasySSLContext() throws IOException { try { SSLContext context = SSLContext.getInstance("TLS"); context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null); return context; } catch (Exception e) { throw new IOException(e.getMessage()); } } private SSLContext getSSLContext() throws IOException { if (this.sslcontext == null) { this.sslcontext = createEasySSLContext(); } return this.sslcontext; } /** * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int, * java.net.InetAddress, int, org.apache.http.params.HttpParams) */ public Socket connectSocket(Socket sock, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { int connTimeout = HttpConnectionParams.getConnectionTimeout(params); int soTimeout = HttpConnectionParams.getSoTimeout(params); InetSocketAddress remoteAddress = new InetSocketAddress(host, port); SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket()); if ((localAddress != null) || (localPort > 0)) { // we need to bind explicitly if (localPort < 0) { localPort = 0; // indicates "any" } InetSocketAddress isa = new InetSocketAddress(localAddress, localPort); sslsock.bind(isa); } sslsock.connect(remoteAddress, connTimeout); sslsock.setSoTimeout(soTimeout); return sslsock; } /** * @see org.apache.http.conn.scheme.SocketFactory#createSocket() */ public Socket createSocket() throws IOException { return getSSLContext().getSocketFactory().createSocket(); } /** * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket) */ public boolean isSecure(Socket socket) throws IllegalArgumentException { return true; } /** * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int, * boolean) */ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose); } // ------------------------------------------------------------------- // javadoc in org.apache.http.conn.scheme.SocketFactory says : // Both Object.equals() and Object.hashCode() must be overridden // for the correct operation of some connection managers // ------------------------------------------------------------------- public boolean equals(Object obj) { return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class)); } public int hashCode() { return EasySSLSocketFactory.class.hashCode(); } } 

Наконец, и вот работа, вам нужен EasyX509TrustManager, который не будет принимать, кроме сертификатов, выпущенных GoDaddy:

 public class EasyX509TrustManager implements X509TrustManager { private X509TrustManager standardTrustManager = null; /** * Constructor for EasyX509TrustManager. */ public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException { super(); TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); factory.init(keystore); TrustManager[] trustmanagers = factory.getTrustManagers(); if (trustmanagers.length == 0) { throw new NoSuchAlgorithmException("no trust manager found"); } this.standardTrustManager = (X509TrustManager) trustmanagers[0]; } /** * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType) */ public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { standardTrustManager.checkClientTrusted(certificates, authType); } /** * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType) */ public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { X509Certificate c = certificates[0]; String name = c.getIssuerDN().getName(); if(!"bla bla".equals(name)) throw new CertificateException("OMG! it is not bla bla!"); standardTrustManager.checkServerTrusted(certificates, authType); } /** * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() */ public X509Certificate[] getAcceptedIssuers() { return this.standardTrustManager.getAcceptedIssuers(); } } 

Тебе нужно

  1. Добавьте сертификат-сверстник в свою доверенную сеть, чтобы вы доверяли ему.
  2. Удалите сертификат CA из своего магазина доверия, чтобы вы не доверяли другим сертификатам, которые он подписал.

Вы можете сделать это легко:

1. Добавьте сертификат CA, который вы хотите получить в своем доверенном магазине.
2. Удалите все другие сертификаты CA (по умолчанию) из своего магазина доверия и уловите исключение SSLHandshakeException.

Или создайте новый магазин доверия, который содержит только ваш сертификат CA.

Вот как я проверяю сертификаты SSL в своем коде.
Я проверяю только сертификаты CAcert. Но вы можете легко заменить сертификат корневым сертификатом Go Go Daddy.

Это очень легко и безопасно на API14 + (возможно, с 11+).
Существует некоторая дополнительная работа, чтобы запустить ее на более старых уровнях API. В основном я запускаю свои собственные проверки API <14 с реализацией, найденной здесь: Проверка сертификатов X509 с использованием Java APis

 // der formated certificate as byte[] private static final byte[] CACERTROOTDER = new byte[]{ 48, -126, 7, 61, 48, -126, 5, 37, -96, 3, 2, 1, 2, 2, 1, 0, // ... }; /** * Read x509 certificated file from byte[]. * * @param bytes certificate in der format * @return certificate */ private static X509Certificate getCertificate(final byte[] bytes) throws IOException, CertificateException { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate ca; ByteArrayInputStream is = new ByteArrayInputStream(bytes); try { ca = (X509Certificate) cf.generateCertificate(is); Log.d(TAG, "ca=", ca.getSubjectDN()); } finally { is.close(); } return ca; } /** * Trust only CAcert's CA. CA cert is injected as byte[]. Following best practices from * https://developer.android.com/training/articles/security-ssl.html#UnknownCa */ private static void trustCAcert() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, KeyManagementException { // Create a KeyStore containing our trusted CAs String keyStoreType = KeyStore.getDefaultType(); final KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("CAcert-root", getCertificate(CACERTROOTDER)); // if your HTTPd is not sending the full chain, add class3 cert to the key store // keyStore.setCertificateEntry("CAcert-class3", getCertificate(CACERTCLASS3DER)); // Create a TrustManager that trusts the CAs in our KeyStore final TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); // Create an SSLContext that uses our TrustManager SSLContext sslContext = SSLContext.getInstance("TLS"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // may work on HC+, but there is no AVD or device to test it sslContext.init(null, tmf.getTrustManagers(), null); } else { // looks like CLR is broken in lower APIs. implement out own checks here :x // see https://stackoverflow.com/q/18713966/2331953 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(final String hostname, final SSLSession session) { try { // check if hostname matches DN String dn = session.getPeerCertificateChain()[0].getSubjectDN().toString(); Log.d(TAG, "DN=", dn); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { return dn.equals("CN=" + hostname); } else { // no SNI on API<9, but I know the first vhost's hostname return dn.equals("CN=" + hostname) || dn.equals("CN=" + hostname.replace("jsonrpc", "rest")); } } catch (Exception e) { Log.e(TAG, "unexpected exception", e); return false; } } }); // build our own trust manager X509TrustManager tm = new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { // nothing to do return new X509Certificate[0]; } @Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { // nothing to do } @Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { // nothing to do Log.d(TAG, "checkServerTrusted(", chain, ")"); X509Certificate cert = chain[0]; cert.checkValidity(); CertificateFactory cf = CertificateFactory.getInstance("X.509"); ArrayList<X509Certificate> list = new ArrayList<X509Certificate>(); list.add(cert); CertPath cp = cf.generateCertPath(list); try { PKIXParameters params = new PKIXParameters(keyStore); params.setRevocationEnabled(false); // CLR is broken, remember? CertPathValidator cpv = CertPathValidator .getInstance(CertPathValidator.getDefaultType()); cpv.validate(cp, params); } catch (KeyStoreException e) { Log.d(TAG, "invalid key store", e); throw new CertificateException(e); } catch (InvalidAlgorithmParameterException e) { Log.d(TAG, "invalid algorithm", e); throw new CertificateException(e); } catch (NoSuchAlgorithmException e) { Log.d(TAG, "no such algorithm", e); throw new CertificateException(e); } catch (CertPathValidatorException e) { Log.d(TAG, "verification failed"); throw new CertificateException(e); } Log.d(TAG, "verification successful"); } }; sslContext.init(null, new X509TrustManager[]{tm}, null); } HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()); } 

Я написал маленький скрипт для создания byte[] из любого файла .der:

 #! /bin/sh if [ $# -lt 1 ] ; then echo "usage: $0 file" >&2 exit 1 fi echo "private static final byte[] $(echo "$(basename ${1})" | tr -Cd 'a-zA-Z0-9' | tr 'az' 'AZ' ) = new byte[]{" od -t d1 ${1} | head -n -1 | cut -d\ -f2- | sed -e 's:^ *::' -e 's: *: :g' -e 's: :, :g' -e 's:$:,:' -e 's:^: :' echo "};" 

Во всяком случае, вы можете использовать полный магазин доверия с рукой, полной доверенных сертификатов, если хотите.