1

Я пытаюсь извлечь страницу, использующую DH с длиной ключа 480 бит, но я не могу сделать это с помощью HTTP-клиента Apache. Я могу успешно получить страницу, если я использую Async HTTP Client с поставщиком JDK.Как получить страницу SSL с помощью Diffie-Hellman с длиной в 480 бит?

Я знаю, что JDK имеет ограничения по максимальному размеру, но эта проблема, похоже, наоборот. Размер ключа слишком мал и не кратен 64.

Я написал SSCCE, чтобы проиллюстрировать это поведение, и после двух дней охоты я нахожусь в своем остроумие.

import java.security.KeyManagementException; 
import java.security.NoSuchAlgorithmException; 

import javax.net.ssl.SSLContext; 

import org.apache.http.HttpResponse; 
import org.apache.http.client.HttpClient; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.config.Registry; 
import org.apache.http.config.RegistryBuilder; 
import org.apache.http.conn.HttpClientConnectionManager; 
import org.apache.http.conn.socket.ConnectionSocketFactory; 
import org.apache.http.conn.socket.PlainConnectionSocketFactory; 
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 
import org.apache.http.impl.client.HttpClientBuilder; 
import org.apache.http.impl.conn.BasicHttpClientConnectionManager; 
import org.apache.http.util.EntityUtils; 
import org.junit.Assert; 
import org.junit.Test; 

import com.ning.http.client.AsyncHttpClient; 
import com.ning.http.client.AsyncHttpClientConfig; 
import com.ning.http.client.providers.jdk.JDKAsyncHttpProvider; 

public class DH480Test extends Assert { 

    private final String URL = "https://dh480.badssl.com/"; 

    @Test 
    public void testNingHTTPClientWithJDKProvider() { 
     AsyncHttpClientConfig config = new AsyncHttpClientConfig.Builder().build(); 
     AsyncHttpClient client = new AsyncHttpClient(new JDKAsyncHttpProvider(config), config); 

     try { 
      client.prepareGet(URL).execute().get(); 
     } catch (Exception e) { 
      e.printStackTrace(); 
      fail(); 
     } 
    } 

    @Test 
    public void testApacheHTTClient() throws NoSuchAlgorithmException, KeyManagementException { 
     SSLContext sslContext = SSLContext.getInstance("SSL"); 
     sslContext.init(null, null, null); 

     Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() 
       .register("https", new SSLConnectionSocketFactory(sslContext)) 
       .register("http", new PlainConnectionSocketFactory()) 
       .build(); 
     HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry); 
     HttpClientBuilder builder = HttpClientBuilder.create(); 
     builder.setConnectionManager(ccm); 

     HttpClient client = builder.build(); 

     try { 
      HttpGet get = new HttpGet(URL); 
      HttpResponse r = client.execute(get); 
      EntityUtils.consume(r.getEntity()); 
     } catch (Exception e) { 
      e.printStackTrace(); 
      fail(); 
     } 
    } 
} 

Это трассировка стека неудачного теста.

javax.net.ssl.SSLException: java.lang.RuntimeException: Could not generate DH keypair 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:208) 
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949) 
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1906) 
    at sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1889) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1410) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) 
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:290) 
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:259) 
    at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:125) 
    at org.apache.http.impl.conn.BasicHttpClientConnectionManager.connect(BasicHttpClientConnectionManager.java:318) 
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:363) 
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:219) 
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195) 
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86) 
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108) 
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) 
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) 
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106) 
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57) 
    at DH480Test.testApacheHTTClient(DH480Test.java:59) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:497) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) 
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137) 
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) 
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234) 
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:497) 
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) 
Caused by: java.lang.RuntimeException: Could not generate DH keypair 
    at sun.security.ssl.DHCrypt.<init>(DHCrypt.java:142) 
    at sun.security.ssl.DHCrypt.<init>(DHCrypt.java:114) 
    at sun.security.ssl.ClientHandshaker.serverKeyExchange(ClientHandshaker.java:708) 
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:268) 
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979) 
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:914) 
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062) 
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) 
    ... 41 more 
Caused by: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 2048 (inclusive) 
    at com.sun.crypto.provider.DHKeyPairGenerator.initialize(DHKeyPairGenerator.java:120) 
    at java.security.KeyPairGenerator$Delegate.initialize(KeyPairGenerator.java:674) 
    at sun.security.ssl.DHCrypt.<init>(DHCrypt.java:128) 
    ... 49 more 

java.lang.AssertionError 
    ... 

Вот еще один автономный тест, который демонстрирует, что я могу принести страницу, используя стандартную LIBS в JDK сети. Это базовая сетевая реализация, используемая клиентом async http. Вот мои детали JVM

➜ ~ java -version 
java version "1.8.0_66" 
Java(TM) SE Runtime Environment (build 1.8.0_66-b17) 
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode) 

@Test 
public void testHTTPURLConnection() throws IOException { 
    try { 
     java.net.URL url = new URL(URL); 
     HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); 
     connection.setHostnameVerifier((s, sslSession) -> true); 
     connection.setRequestMethod("GET"); 
     connection.setReadTimeout(15 * 1000); 
     connection.connect(); 
     System.out.println(IOUtils.toString(connection.getInputStream())); 
    } catch (Exception e) { 
     fail(); 
    } 
} 
+0

Почему страница использует этот размер ключа? – EJP

+0

@EJP это всего лишь тест для проверки жесткости SSL на BadSSL.com –

+0

И каков желаемый результат? Успех или неудача? Вам действительно нужно иметь возможность обрабатывать ключи DH из 480 бит? – EJP

ответ

1

Отказ от ответственности: Я являюсь хранителем AsyncHttpClient (пожалуйста, не называйте это Ning).

Удовлетворительный факт: это работает с поставщиком JDK AHC при работе с JDK8, но не с JDK7!

Теперь: поставщик AHC JDK (который является простой игрушкой, которая уходит в AHC2, вы не должны ее использовать) сводится к Http (-ам) UrlConnection, поэтому ожидается, что получат те же результаты.

Затем клиенты на основе NIO, такие как другие поставщики AHC, и Apache HttpComponents не могут использовать базовые компоненты HTTPS, которые используются HttpsUrlConnection, потому что они не являются общедоступными!

Таким образом, им необходимо напрямую использовать javax.net.ssl.SSLEngine. Но этот API был предназначен для общего TLS, а не для протоколов, построенных поверх него, таких как HTTPS.

Например, перед JDK7 это вызывает огромную проблему безопасности на всех HTTP-клиентах на основе Java NIO, поскольку у них не было возможности правильно выполнить проверку имени хоста. Проверка имени хоста специфична для HTTPS, поэтому вы можете передать HostnameVerifier в HttpsUrlConnection (специфичный HTTPS), но не SSLEngine (общий TLS).

Теперь, поскольку JDK7, мы можем, наконец, настроить протокол (HTTPS или LDAP) на SSLEngine, чтобы он заботился о конкретном поведении HTTPS. Но тем не менее, у вас меньше возможностей управления, чем при использовании HttpsUrlConnection.

Здесь, похоже, вы обнаружили ошибку JDK и что протокол HttpsUrlConnection и SSLEngine w/HTTPS не работает одинаково.

Например, независимо от того, что я делаю (независимо от того, что включено в cyphersuites), я не могу получить сервер BadSSL.com, который выбирает TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, который выбирает HttpsUrlConnection. Он всегда выбирает TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (без эллиптической кривой). См. ServerHello при включении отладки TLS (-Djavax.net.debug=all).

+0

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

+0

Какую версию AHC вы используете? –

+0

Я использую 1.9.33 –

0

Я не эксперт и никогда не работал с DH в Java, но после просмотра журналов и небольшое исследование кода на DHKeyPairGenerator Grepcode это действительно очень ясно, как в функции initialize, что она будет всегда кидать исключение, если Prime size < 512 как это в письменном виде в коде,

83   if ((keysize < 512) || (keysize > 1024) || (keysize % 64 != 0)) { 
84    throw new InvalidParameterException("Keysize must be multiple " 
85             + "of 64, and can only range " 
86             + "from 512 to 1024 " 
87             + "(inclusive)"); 
88   } 

и

118 if ((pSize < 512) || (pSize > 1024) || 
119   (pSize % 64 != 0)) { 
120   throw new InvalidAlgorithmParameterException 
121    ("Prime size must be multiple of 64, and can only range " 
122     + "from 512 to 1024 (inclusive)"); 
123  } 

так что наверняка го вы не можете использовать эти стандартные библиотеки для 480-битной длины ключа.

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

Я мало исследовал и нашел то же самое DHKeyPairGenerator class в пакете bouncycastle без ограничений и проверок на размер ключа.