Универсальный загрузчик изображений | SSLHandshakeException: рукопожатие не удалось

У меня есть ListView с некоторым содержимым (TextViews, ImageView...) в элементах. Я использую UIL от Nostra для загрузки изображений в элементы, но некоторые из их не удается загрузить. Вот что я получаю, когда вызываю Log.v(String.valueOf(failReason.getCause()); :

11-16 23:52:20.447: V/javax.net.ssl.SSLHandshakeException: Handshake failed(17467): failz
11-16 23:52:20.657: V/NativeCrypto(17467): SSL handshake aborted: ssl=0x15fd758: Failure in SSL library, usually a protocol error
11-16 23:52:20.657: V/NativeCrypto(17467): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:762 0x4c2ed485:0x00000000)
11-16 23:52:21.207: V/NativeCrypto(17467): SSL handshake aborted: ssl=0x1562468: Failure in SSL library, usually a protocol error
11-16 23:52:21.207: V/NativeCrypto(17467): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:762 0x4c2ed485:0x00000000)

Разве вы не знаете, почему возникает эта проблема или как я могу ее решить?

Это один пример изображения, которое не загружается:

http://bigparty.cz/photos/headlinefoto/13.jpg

(могу приложить лог со всей ошибкой - ошибка, которую UIL автоматически заносит в лог)




Ответы (3)


Если я прав, вам нужно создать сертификат, подписать его и включить в свое приложение. Или измените конфигурацию сервера (дополнительная информация здесь).

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

Включите этот класс в свой проект

public class SSLCertificateHandler {

protected static final String TAG = "NukeSSLCerts";

/**
 * Enables https connections
 */
public static void nuke() {
    try {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                X509Certificate[] myTrustedAnchors = new X509Certificate[0];
                return myTrustedAnchors;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        } };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
    } catch (Exception e) {
    }
}

}

Расширьте свой Application и вызовите функцию 'nuke' в своем onCreate

public class YOURApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();

    //...

    // trust all SSL -> HTTPS connection
    SSLCertificateHandler.nuke();
}

Я нашел этот код в SO, но сейчас не могу найти ссылку....

person longi    schedule 25.11.2014
comment
Я знаю, что это полезно во время разработки. Но даже не думайте принимать все сертификаты после того, как приложение будет запущено. Это противоречит всей цели использования HTTPS, вместо этого вы можете использовать HTTP. Дополнительная информация здесь (но игнорируйте этот ответ) - person Krishnabhadra; 13.07.2016
comment
В Java можно включить условие, поэтому SSLCertificateHandler.nuke() вызывается только для сборок разработки - см. stackoverflow.com/a/23844716/1617737 . - person ban-geoengineering; 11.10.2016

Возможно, вам не хватает промежуточного сертификата CA на стороне вашего сервера.

Попробуйте прочитать это

Большинство общедоступных центров сертификации не подписывают сертификаты серверов напрямую. Вместо этого они используют свой основной сертификат ЦС, называемый корневым ЦС, для подписи промежуточных ЦС. Они делают это, чтобы корневой ЦС можно было хранить в автономном режиме, чтобы снизить риск компрометации. Однако операционные системы, такие как Android, обычно напрямую доверяют только корневым центрам сертификации, что оставляет небольшой разрыв в доверии между сертификатом сервера, подписанным промежуточным центром сертификации, и верификатором сертификата, который знает корневой центр сертификации. Чтобы решить эту проблему, сервер отправляет клиенту не только свой сертификат во время рукопожатия SSL, а цепочку сертификатов от ЦС сервера через любые промежуточные звенья, необходимые для достижения доверенного корневого ЦС.

Чтобы увидеть, как это выглядит на практике, вот цепочка сертификатов mail.google.com, просмотренная командой openssl s_client:

$ openssl s_client -подключить mail.google.com:443

Цепочка сертификатов 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=mail.google.com i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN= Thawte SGC CA 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA

i:/C=US/O=VeriSign, Inc./OU=Первичный общедоступный центр сертификации класса 3

Это показывает, что сервер отправляет сертификат для mail.google.com, выданный ЦС Thawte SGC, который является промежуточным ЦС, и второй сертификат для ЦС Thawte SGC, выданный ЦС Verisign, который является основным ЦС, которому доверяют Андроид.

Однако нередко настроить сервер так, чтобы он не включал необходимый промежуточный ЦС. Например, вот сервер, который может вызывать ошибку в браузерах Android и исключения в приложениях Android:

$ openssl s_client -connect egov.uscis.gov:443

Цепочка сертификатов 0 s:/C=US/ST=Округ Колумбия/L=Вашингтон/O=U.S. Министерство внутренней безопасности/OU=Служба гражданства и иммиграции США/OU=Условия использования на сайте www.verisign.com/rpa (c)05/CN=egov.uscis.gov

i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Условия использования на https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA — G3

Интересно отметить, что посещение этого сервера в большинстве настольных браузеров не приводит к ошибке, которую может вызвать полностью неизвестный ЦС или самозаверяющий сертификат сервера. Это связано с тем, что большинство настольных браузеров со временем кэшируют доверенные промежуточные центры сертификации. После того как браузер посетил и узнал о промежуточном ЦС с одного сайта, ему не нужно будет включать промежуточный ЦС в цепочку сертификатов в следующий раз.

Некоторые сайты делают это намеренно для вторичных веб-серверов, используемых для обслуживания ресурсов. Например, их основная HTML-страница может обслуживаться сервером с полной цепочкой сертификатов, но серверы для ресурсов, таких как изображения, CSS или JavaScript, не включают ЦС, предположительно для экономии полосы пропускания. К сожалению, иногда эти серверы могут предоставлять веб-службу, которую вы пытаетесь вызвать из своего приложения для Android, что не так щадяще.

Существует два подхода к решению этой проблемы:

Настройте сервер для включения промежуточного ЦС в цепочку серверов. Большинство ЦС предоставляют документацию о том, как это сделать для всех распространенных веб-серверов. Это единственный подход, если вам нужно, чтобы сайт работал с браузерами Android по умолчанию хотя бы через Android 4.2. Или относитесь к промежуточному ЦС как к любому другому неизвестному ЦС и создайте TrustManager, чтобы доверять ему напрямую, как это было сделано в предыдущих двух разделах.

person Suhail Mehta    schedule 26.11.2014

Как уже упоминалось, доверять всем сертификатам — не лучший подход. На самом деле это очень плохой подход в производственной среде, поскольку он практически сводит на нет эффект SSL и делает вас уязвимым для Атаки типа "человек посередине".

Проблема сводится к ошибке POODLE SSL3, из-за которой некоторые (большинство?) Поддержка SSLv3. Это плохая новость для нас, Android-разработчиков, потому что HttpConnection по умолчанию использует SSLv3 в Build.VERSION.SDK_INT >= 9 && Build.VERSION.SDK_INT ‹= 20.

В системе отслеживания проблем Android зарегистрирована проблема с дополнительной информацией. и решение состоит в предоставлении пользовательской SSLFactory, которая отказывается устанавливать SSLv3 в качестве единственного резервного протокола, и вот код из этой самой проблемы. Я не беру на себя ответственность за это решение, но это то, что мы в настоящее время используем для всех версий до Lollipop.

/**
 * An {@link javax.net.ssl.SSLSocket} that doesn't allow {@code SSLv3} only connections
 * <p>fixes https://github.com/koush/ion/issues/386</p>
 */
private static class NoSSLv3SSLSocket extends DelegateSSLSocket {

private NoSSLv3SSLSocket(SSLSocket delegate) {
    super(delegate);

    String canonicalName = delegate.getClass().getCanonicalName();
    if (!canonicalName.equals("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")){
        // try replicate the code from HttpConnection.setupSecureSocket()
        try {
            Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class);
            if (null != msetUseSessionTickets) {
                msetUseSessionTickets.invoke(delegate, true);
            }
        } catch (NoSuchMethodException ignored) {
        } catch (InvocationTargetException ignored) {
        } catch (IllegalAccessException ignored) {
        }
    }
}

@Override
public void setEnabledProtocols(String[] protocols) {
    if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
        // no way jose
        // see issue https://code.google.com/p/android/issues/detail?id=78187
        List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
        if (enabledProtocols.size() > 1) {
            enabledProtocols.remove("SSLv3");
        } else {
            LogManager.getLogger().w("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
        }
        protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
    }
    super.setEnabledProtocols(protocols);
}
}


/**
 * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections
 */
private static class NoSSLv3Factory extends SSLSocketFactory {
private final SSLSocketFactory delegate;

private NoSSLv3Factory() {
    this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
    return delegate.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
    return delegate.getSupportedCipherSuites();
}

private static Socket makeSocketSafe(Socket socket) {
    if (socket instanceof SSLSocket) {
        socket = new NoSSLv3SSLSocket((SSLSocket) socket);
    }
    return socket;
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
    return makeSocketSafe(delegate.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException {
    return makeSocketSafe(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
    return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
    return makeSocketSafe(delegate.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
    return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
}
}

static {
HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory());
}
person Qw4z1    schedule 17.12.2014
comment
это мне очень помогло. Я надеюсь, что это также поможет другим. благодарю вас - person marson; 24.12.2014