Java – SocketException: сброс соединения, когда клиент отправляет запрос

В Java Network Programming 4th Edition, глава 10 о безопасном Безопасный сервер. Код можно найти здесь.

Я пытаюсь сделать более простую версию кода. Вот мой код:

 try {
        SSLContext context = SSLContext.getInstance(algorithm);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        KeyStore ks = KeyStore.getInstance("JKS");

        //char[] password = System.console().readPassword("Password: "); // open the .class with command line
        char[] password = {'t', 'h', 'i', 's', 'i', 's', 't', 'i', 'a', 'n'};

        ks.load(new FileInputStream("src/jnp4e.keys"), password);            
        kmf.init(ks, password);
        context.init(kmf.getKeyManagers(), null, null); // null = accept the default

        // wipe the password
        Arrays.fill(password, '0');

        SSLServerSocketFactory factory
                = context.getServerSocketFactory();
        SSLServerSocket server
                = (SSLServerSocket) factory.createServerSocket(PORT);

        // add anonymous (non-authenticated) cipher suites
        String[] supported = server.getSupportedCipherSuites();
        String[] anonCipherSuitesSupported = new String[supported.length];
        int numAnonCipherSuitesSupported = 0;
        for (int i = 0; i < supported.length; i++) {
            if (supported[i].indexOf("_anon_") > 0) {
                anonCipherSuitesSupported[numAnonCipherSuitesSupported++]
                        = supported[i];
            }
        }
        String[] oldEnabled = server.getEnabledCipherSuites();
        String[] newEnabled = new String[oldEnabled.length
                + numAnonCipherSuitesSupported];
        System.arraycopy(oldEnabled, 0, newEnabled, 0, oldEnabled.length);
        System.arraycopy(anonCipherSuitesSupported, 0, newEnabled,
                oldEnabled.length, numAnonCipherSuitesSupported);
        server.setEnabledCipherSuites(newEnabled);

        System.out.println("OK..");

        // Now all the set up is complete and we can focus
        // on the actual communication.
        while (true) {
            // This socket will be secure,
            // but there's no indication of that in the code!
            try (Socket theConnection = server.accept()) {
                InputStream in = theConnection.getInputStream();
                Reader r = new InputStreamReader(in, "UTF-8");
                int c;
                while ((c = r.read()) != -1) {
                    System.out.write(c);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    } catch (IOException | KeyManagementException | KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
        ex.printStackTrace();
    }

Единственная разница в том, что в моем коде я создаю Reader, чтобы сервер мог читать символы. Я попробовал этот сервер с простым клиентом, который отправляет текст. Вот Клиент:

int port = 7000; // default https port
    String host = "localhost";
    SSLSocketFactory factory
            = (SSLSocketFactory) SSLSocketFactory.getDefault();


    SSLSocket socket = null;
    try {
        socket = (SSLSocket) factory.createSocket(host, port);

        // enable all the suites
        String[] supported = socket.getSupportedCipherSuites();
        socket.setEnabledCipherSuites(supported);

        Writer out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
        out.write("Hello");

    }catch(Exception e){

    }

Сначала я запускаю сервер, а затем Cient. Но когда сервер принимает ввод от клиента, он выдает это исключение:

java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:189)
at java.net.SocketInputStream.read(SocketInputStream.java:121)
...

ОБНОВЛЕНИЕ КЛИЕНТА Основываясь на ответе Дейва, я добавляю 2 строки кода flush() и close()

...
out.write("Hello");
out.flush();
socket.close();
...

Но приходит другое исключение:

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

person Altiano Gerung    schedule 26.06.2015    source источник
comment
Почему вы включаете наборы анонимных шифров, если у вас уже есть хранилище ключей? Который предположительно содержит закрытый ключ и сертификат?   -  person user207421    schedule 26.06.2015
comment
просто сделайте этот сервер более гибким, я думаю   -  person Altiano Gerung    schedule 26.06.2015
comment
Вы имеете в виду, что это просто делает ваш сервер небезопасным. Не делай этого.   -  person user207421    schedule 26.06.2015
comment
@EJP это может сделать клиента(ов) небезопасным; сервер уже не запрашивает и не получает клиентскую аутентификацию. Серверные тесты, такие как ssllabs, понизят рейтинг сервера за разрешение анонимных наборов, но даже в этом случае клиент может запретить фактическое использование анонимных наборов, и большинство из них делает это по умолчанию, включая JSSE; клиентский код здесь переопределяет значение по умолчанию и разрешает анонимность, но не предпочитает его, поэтому большинство серверов (включая JSSE здесь) по-прежнему не будут его использовать.   -  person dave_thompson_085    schedule 27.06.2015
comment
@EJP Меня действительно разочаровывает, что Книга Java Networking предлагает использовать наборы анонимных шифров без разъяснения последствий (раздел Выбор наборов шифров, хотя есть короткое предложение после примера 10-2, по-видимому).   -  person Bruno    schedule 27.06.2015
comment
@Bruno, книга просто показывает нам все типы наборов шифров, которые предоставляет JSSE, и как их включить, но на самом деле предлагает не использовать анон. Вероятно, вам следует избегать любых из этих наборов, которые содержат NULL, ANON или EXPORT в своих именах, если вы не хотите АНБ читать ваши сообщения.   -  person Altiano Gerung    schedule 28.06.2015
comment
@AltianoGerung Правда, но по опыту (особенно вопросы здесь по SO) многие учащиеся будут пытаться использовать кратчайший путь, чтобы заставить их код работать: это часто включает попытку пропустить шаги, необходимые для работы с сертификатами (например, с помощью анонимного шифра наборы или полное отключение проверки доверия). Я не читал всю книгу, но на этих страницах также не упоминается проверка имени хоста, что является еще одним шагом, который следует предпринять (намного проще сделать, начиная с Java 7, кстати).   -  person Bruno    schedule 28.06.2015
comment
@Bruno, тогда можете ли вы дать несколько ссылок (ссылки, книги, блоги ..), чтобы сделать это правильно?   -  person Altiano Gerung    schedule 28.06.2015


Ответы (1)


OutputStreamWriter в потоке сокета, по-видимому, буферизует, а ваш клиент не выполнил .flush() или .close(), поэтому ваши данные на самом деле не отправляются.

Если ваша Java-программа (или, точнее, JVM) выходит без выполнения .close() в потоке сокета (включая закрытие Writer, который проходит через поток), обработка зависит от платформы; в Windows он отправляет RST, что вызывает исключение "Сброс соединения", которое вы видите на узле. Unix нормально закрывает соединение на уровне TCP, что на самом деле не совсем нормально для SSL/TLS, но «достаточно близко» (так сказать), чтобы Java рассматривала его как EOF.

Изменить для следующего вопроса:

Сервер получает исключение SSLHandshakeException "получено предупреждение bad_certificatecertificate_unknown", что теоретически может означать несколько вещей, но почти всегда означает, что сертификат, который использует сервер (от загруженное вами хранилище ключей вместе с соответствующим закрытым ключом) не подписано ЦС (центром сертификации), которому доверяет клиент.

Код, который вы показываете для клиента, ничего не делает для установки или изменения его хранилища доверия; если в другом месте нет кода, который делает это, или внешних настроек, таких как java параметр командной строки -Dx=y для установки свойств системы, клиент будет использовать JSSE хранилище доверенных сертификатов по умолчанию, которым является файл JRE/lib/security/jssecacerts, если он существует в противном случае файл JRE/lib/security/cacerts (где JRE — это каталог, в котором установлена ​​ваша JRE; если вы используете JDK, JRE — это подкаталог каталога JDK). Если вы (и кто-либо еще в вашей системе) не изменяли эти файлы с момента установки JRE, jssecacerts не существует, а cacerts содержит набор "известных корневых" ЦС, определенных Oracle. , такие как Verisign и Equifax и т. д.

Таким образом, вам нужно либо:

  • использовать сертификат, выданный известным ЦС; если у вас еще нет такого сертификата, вы должны получить его в ЦС, подтвердив (как минимум) свой контроль над сертифицированными доменными именами и, в зависимости от ЦС, возможно, заплатив комиссию; если у вас есть или вы получили такой сертификат, установите его в хранилище ключей, в запись закрытого ключа, вместе с любыми цепочками сертификатов (для известных центров сертификации почти всегда хотя бы один сертификат цепочки).

  • использовать сертификат, выданный любым другим ЦС, включая созданный вами специальный ЦС, и включая в крайнем случае самозаверяющий сертификат, который неявно собственный ЦС, такой как тот, который keytool -genkeypair генерирует автоматически; и поместите сертификат ЦС для этого ЦС (или этот самозаверяющий сертификат) в хранилище доверенных сертификатов, используемое клиентом. Для этого есть два пути:

    • поместите сертификат ЦС сервера (или самозаверяющий сертификат) в файл хранилища доверенных сертификатов по умолчанию JRE, используемый клиентом. Это влияет на любые другие программы, совместно использующие это хранилище доверенных сертификатов по умолчанию, что потенциально является всеми другими программами, использующими эту JRE. Если вы используете jssecacerts, это влияет только на JSSE, если вы используете cacerts, это также влияет на проверку подписанного кода (если есть), плюс он стирается, если вы обновляете свою JRE на месте, как это обычно происходит автоматически в Windows.

    • создайте (или повторно используйте) другое хранилище доверенных сертификатов, поместите туда сертификат ЦС сервера и попросите клиента использовать это хранилище доверенных сертификатов не по умолчанию. Для этого есть несколько вариантов: установить системные свойства для хранилища доверенных сертификатов по умолчанию извне, установить их явно в вашей программе (перед первым использованием JSSE!), явно загрузить файл «хранилища ключей» (фактически содержащий сертификаты) и использовать его доверительный менеджер в SSLSocketFactory не по умолчанию, так же, как ваш серверный код делает для менеджера ключей, или даже напишите свой собственный доверительный менеджер с любым магазином (ами?), Который вам нравится, и используйте его аналогичным образом.

Простой пример редактирования #2

Подробное описание всех этих вариантов было бы слишком длинным, но один простой вариант выглядит следующим образом.

  1. Создайте пару ключей и (по умолчанию) самозаверяющий сертификат:

keytool -genkeypair -keystore ksfile -keyalg RSA

Для приглашения «имя и фамилия» (которое на самом деле является атрибутом CommonName в сертификате) введите имя сервера, в частности имя, которое клиент (ы) будет использовать для подключения к серверу; в вопросе это «localhost». Другие поля имени не имеют значения; заполните или опустите их по своему усмотрению, за исключением того, что страна, если она используется, должна состоять из 2 букв, как говорит подсказка. Вместо ответа на подсказки вы можете добавить в командную строку -dname "CN=common_name_value". Если у вас есть несколько имен для серверов, некоторые параметры здесь опущены. Для некоторых других приложений вам может понадобиться указать имя записи с помощью -alias name; для этого вопроса это не нужно.

  1. Получить копию сертификата:

keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile

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

  1. Поместите сертификат в хранилище доверенных сертификатов клиента. Как и выше, есть много вариантов, но тот, который вы, вероятно, увидите, предлагается чаще всего, потому что обычно быстрее всего запустить его, просто поместив его в файл JRE по умолчанию, cacerts:

keytool -importcert -keystore JRE/lib/security/cacerts -file certfile

где JRE — это каталог, в котором установлена ​​ваша JRE (среда выполнения Java). Это зависит от вашей ОС и от того, как вы установили JRE (или JDK, который включает JRE), например, с помощью диспетчера пакетов или нет. Если у вас установлено более одной JRE и/или JDK, это зависит от того, какую из них вы используете.

В Unix, если вы вызываете java без указания пути, which java (или в bash и, возможно, в других оболочках, type java) сообщит вам полный запущенный путь. Обратите внимание, однако, что это часто символическая ссылка на реальное местоположение, которая должна быть в форме /somewhere/java[version]/bin/java; измените часть bin/java на lib/security/cacerts.

В Windows, если вы устанавливаете обычную общесистемную Java, программа, которую вы запускаете, имеет номер %windir%\system32\java.exe, где windir обычно c:\windows, но фактический код и файлы для JRE находятся в c:\program files\java\jreN или c:\program files (x86)\java\jreN в зависимости от вашей архитектуры (32-разрядная или 64-разрядная). бит), а jreN в настоящее время jre7 или jre8, в зависимости от обстоятельств, но, вероятно, будет расширен в будущем. Или запустите панель управления Java; на вкладке Java кнопка «Просмотр» показывает расположение установленных JRE.

person dave_thompson_085    schedule 26.06.2015
comment
OutputStreamWriter буферов, все само по себе. Это не имеет ничего общего с «на сокете». Java не обрабатывает FIN как EOF в SSL: вы получите предупреждение SSL как исключение SSLException. Не то чтобы это имело отношение к этому вопросу. - person user207421; 26.06.2015
comment
@dave: я обновляю свой код, как вы можете видеть выше. Но появляется новое исключение javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown. Что сейчас пойдет не так? - person Altiano Gerung; 26.06.2015
comment
@ dave_thompson_085 Быстрый вопрос. плохой_сертификат = неизвестен_сертификат? исключение: certificate_unknown - person Altiano Gerung; 27.06.2015
comment
@AltianoGerung извините, окно редактирования не оставляет вопрос или комментарии видимыми, и я неправильно запомнил; исправлено, а также немного улучшено. - person dave_thompson_085; 27.06.2015
comment
@dave_thompson_085: хорошо. это очень хороший ответ, и я думаю, что для этого конкретного вопроса ответ может быть принят. Но для меня было бы лучше, если бы вы могли добавить учебник для каждого из вариантов, которые вы дали, или дать мне ссылку или любую ссылку, чтобы сделать это? Мой приоритет номер 1 - это вариант 2 (другой ЦС) и первый способ (самоподписанный сертификат). - person Altiano Gerung; 27.06.2015
comment
@dave_thompson_085 ты еще здесь? - person Altiano Gerung; 29.06.2015
comment
@AltianoGerung (я взял отпуск на выходные) Полное руководство было бы для меня слишком длинным, если бы не SE; Иван Ристич (создатель известного сайта www.ssllabs.com) только что опубликовал 528-страничную книгу об этом feistyduck.com/books/bulletproof-ssl-and-tls . Я редактировал один простой случай. - person dave_thompson_085; 30.06.2015
comment
@EJP Я ожидал исключения, но в моих тестах этого не произошло. Я протестировал случай, который мне проще всего создать: клиент (в Unix) открывает сервер и рукопожатия, клиент делает одну запись в потоке, а затем выходит, сервер выполняет два чтения в потоке. На 7u80 и 8u45 второе чтение получает длину -1 (EOF) и не вызывает исключения; Wireshark подтверждает только FIN/ACK без предупреждения (отключения). Если вы сообщите подробности о случае, который вызывает исключение, я буду рад попробовать его. - person dave_thompson_085; 30.06.2015
comment
@dave_thompson_085 спасибо за ссылку и за ответ тоже. (я знаю, что у вас были выходные, поэтому я жду 2 дня, прежде чем снова комментировать :D) - person Altiano Gerung; 30.06.2015