Как переопределить шифрованный список, отправленный на сервер Android при использовании HttpsURLConnection?

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

Я знаю, что я могу использовать setSSLSocketFactory в объекте HttpsURLConnection, чтобы установить его для использования SSLSocketFactory. Это полезно, когда я хочу изменить trustmanager и т.д., Который используется SSLSocket, возвращаемым SSLSocketFactory.

Я знаю, что в целом этот список ciphersuite можно редактировать с помощью объекта SSLParameters и передавать его объектам SSLSocket или SSLEngine, используя методы, которые они предоставляют.

НО SSLSocketFactory, похоже, не раскрывает такие методы!

Я не могу найти способ изменить SSLParameters возвращаемых объектов SSLSocket, созданных SSLSocketFactory I, чтобы перейти к HttpsURLConnection.

Что делать?

Я предполагаю, что это также относится к Java в целом, а не только для Android. Возможно, существует способ OO (например, продлить SSLSocketFactory и предоставить это HttpsURLConnection?)

Ответ 1

Этот фрагмент кода немного сырой. используйте с осторожностью.

public class PreferredCipherSuiteSSLSocketFactory extends SSLSocketFactory {


private static final String PREFERRED_CIPHER_SUITE = "TLS_RSA_WITH_AES_128_CBC_SHA";

private final SSLSocketFactory delegate;

public PreferredCipherSuiteSSLSocketFactory(SSLSocketFactory delegate) {

    this.delegate = delegate;
}

@Override
public String[] getDefaultCipherSuites() {

    return setupPreferredDefaultCipherSuites(this.delegate);
}

@Override
public String[] getSupportedCipherSuites() {

    return setupPreferredSupportedCipherSuites(this.delegate);
}

@Override
public Socket createSocket(String arg0, int arg1) throws IOException,
        UnknownHostException {

    Socket socket = this.delegate.createSocket(arg0, arg1);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(InetAddress arg0, int arg1) throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3)
        throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
        throws IOException, UnknownHostException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

@Override
public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2,
        int arg3) throws IOException {

    Socket socket = this.delegate.createSocket(arg0, arg1, arg2, arg3);
    String[] cipherSuites = setupPreferredDefaultCipherSuites(delegate);
    ((SSLSocket)socket).setEnabledCipherSuites(cipherSuites);

    return socket;
}

private static String[] setupPreferredDefaultCipherSuites(SSLSocketFactory sslSocketFactory) {

    String[] defaultCipherSuites = sslSocketFactory.getDefaultCipherSuites();

    ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(defaultCipherSuites));
    suitesList.remove(PREFERRED_CIPHER_SUITE);
    suitesList.add(0, PREFERRED_CIPHER_SUITE);

    return suitesList.toArray(new String[suitesList.size()]);
}

private static String[] setupPreferredSupportedCipherSuites(SSLSocketFactory sslSocketFactory) {

    String[] supportedCipherSuites = sslSocketFactory.getSupportedCipherSuites();

    ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(supportedCipherSuites));
    suitesList.remove(PREFERRED_CIPHER_SUITE);
    suitesList.add(0, PREFERRED_CIPHER_SUITE);

    return suitesList.toArray(new String[suitesList.size()]);
}
}

Если вы хотите его использовать.

            HttpsURLConnection connection = (HttpsURLConnection) (new URL(url))
                .openConnection();
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManager tm[] = {new SSLPinningTrustManager()};
        context.init(null, tm, null);
        SSLSocketFactory preferredCipherSuiteSSLSocketFactory = new PreferredCipherSuiteSSLSocketFactory(context.getSocketFactory());
        connection.setSSLSocketFactory(preferredCipherSuiteSSLSocketFactory);
                    connection.connect();

Спасибо.

Ответ 2

Я связал технику в @ThinkChris, чтобы ответить 1 на мертвый простой вызов метода. Вы можете использовать библиотеку NetCipher, чтобы получить современную конфигурацию TLS при использовании Android HttpsURLConnection. NetCipher настраивает экземпляр HttpsURLConnection для использования наилучшей поддерживаемой версии TLS, удаляет поддержку SSLv3 и настраивает лучший набор шифров для этой версии TLS. Сначала добавьте его в свой build.gradle:

compile 'info.guardianproject.netcipher:netcipher:1.2'

Или вы можете загрузить netcipher-1.2.jar и включить его прямо в свое приложение. Затем вместо вызова:

HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();

Вызов:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);

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

Ответ 3

Этот код произвел чудеса для неожиданного javax.net.ssl.SSLHandshakeException.

Обновление до jdk1.8.0_92 и файлов политики неограниченной прочности Oracle JCE не помогло, и я не смог применить конкретный SSLParameters к HttpsUrlConnection.

В частности, попытка использовать HttpsUrlConnection для чтения https://www.adrbnymellon.com приводит к следующей ошибке:

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

Этот сайт работал нормально до 4/15/2016, а затем начал сбой. Я считаю, что неудача вызвана тем, что веб-сайт прекратил поддержку SSLv2Hello и SSLv3 из-за уязвимости DROWN. См. этот для отличного анализа.

Доступ к сайту начал работать, изменив код с изменениями только на 2 константы:

private static final String PREFERRED_CIPHER_SUITE = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
SSLContext context = SSLContext.getInstance("TLSv1.2");

Я надеюсь, что это поможет кому-то другому.