Java - SocketException: сброс соединения при отправке клиентом запроса
В главе 10, посвященной Secure Socket, в Java Network Programming 4th Edition приведен пример сборки Secure Server. Код можно найти здесь.
Я пытаюсь сделать более простой вариант кода. Вот мой код:
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
1 ответ
OutputStreamWriter в потоке сокета, очевидно, буферизуется, а ваш клиент этого не сделал .flush()
или же .close()
так что ваши данные на самом деле не отправлены.
Если ваша Java-программа (или точнее JVM) завершает работу без выполнения .close()
в потоке сокетов (включая закрытие Writer, который проходит через Stream) обработка зависит от платформы; в Windows он отправляет RST, что вызывает исключение "Сброс подключения", которое вы видите на узле. Unix обычно закрывает соединение на уровне TCP, что на самом деле не совсем нормально для SSL/TLS, но "достаточно близко" (как бы), чтобы Java воспринимала его как EOF.
Изменить для следующего вопроса:
Сервер получает SSLHandshakeException "получено предупреждение bad_certificate Certificate_unknown", что теоретически может означать несколько вещей, но почти всегда означает, что сертификат, который использует сервер (из загруженного вами хранилища ключей вместе с соответствующим закрытым ключом), не подписан доверенным центром сертификации (центром сертификации) клиентом
Код, который вы показываете для клиента, ничего не делает для установки или изменения его хранилища доверенных сертификатов; если в другом месте нет кода, который делает это, или внешние настройки, такие как java
опция командной строки -Dx=y
чтобы установить системные свойства, клиент будет использовать хранилище доверенных сертификатов JSSE по умолчанию, которое является файлом JRE/lib/security/jssecacerts
если он существует в противном случае файл JRE/lib/security/cacerts
(где JRE - это каталог, в котором установлена ваша JRE; если вы используете JDK, JRE - это подкаталог каталога JDK). Если вы (и кто-либо еще в вашей системе) не изменили эти файлы с момента установки JRE, jssecacerts
не существует и cacerts
содержит набор "хорошо известных корневых" CA, определенных Oracle, например, Verisign, Equifax и т. д.
Таким образом, вам нужно либо:
использовать сертификат, выданный известным CA; если у вас еще нет такого сертификата, вы должны получить его от CA, доказав (как минимум) ваш контроль над сертифицированными доменными именами, и в зависимости от CA, возможно, заплатив плату; если у вас есть или получен такой сертификат, установите его в своем хранилище ключей, в записи privatekey, с любыми цепными сертификатами (для хорошо известных ЦС почти всегда есть хотя бы один цепной сертификат).
использовать сертификат, выданный любым другим центром сертификации, в том числе созданным вами специальным центром сертификации, а также в качестве предельного случая самоподписанный сертификат, который неявно является его собственным центром сертификации, таким как сертификат
keytool -genkeypair
генерирует автоматически; и поместите сертификат CA для этого CA (или того самоподписанного сертификата) в склад доверенных сертификатов, используемый клиентом. Для этого есть два пути:поместите сертификат CA сервера (или сертификат с собственной подписью) в файл хранилища доверенных сертификатов по умолчанию для JRE, используемого клиентом. Это влияет на любые другие программы, совместно использующие это хранилище доверенных сертификатов по умолчанию, которое потенциально является всеми другими программами, использующими эту JRE. Если вы используете
jssecacerts
это влияет только на JSSE, если вы используетеcacerts
это также влияет на проверку подписанного кода (если таковой имеется), плюс он стирается, если вы обновляете JRE на месте, как это обычно происходит в Windows автоматически.создайте (или повторно используйте) другое хранилище доверенных сертификатов, поместите туда сертификат CA сервера и попросите клиента использовать хранилище доверенных сертификатов по умолчанию. Для этого есть несколько вариантов: установить системные свойства для хранилища доверенных сертификатов по умолчанию, установить их явно в вашей программе (перед первым использованием JSSE!), Явно загрузить файл "хранилища ключей" (на самом деле содержащий сертификаты) и использовать его доверительный менеджер в не по умолчанию SSLSocketFactory, как ваш серверный код для keymanager, или даже написать свой собственный доверительный менеджер с любым хранилищем (ами), которые вам нравятся, и используйте его аналогичным образом.
Edit # 2 Простой пример
Подробное описание всех этих вариантов будет слишком длинным, но один простой вариант заключается в следующем.
- Создайте пару ключей и (по умолчанию) самоподписанный сертификат:
keytool -genkeypair -keystore ksfile -keyalg RSA
Для подсказки "имя и фамилия" (которая на самом деле является атрибутом CommonName в сертификате) введите имя сервера, в частности имя, которое клиент (ы) будет использовать для подключения к серверу; в вопросе это "localhost". Другие поля имени не имеют значения; заполните или опустите их, как вам нравится, за исключением того, что страна, если она используется, должна состоять из 2 букв, как указано в подсказке. Вместо того, чтобы отвечать на запросы, вы можете добавить в командной строке -dname "CN=common_name_value"
, Если у вас есть более одного имени для сервера (ов), некоторые параметры здесь опущены. Для некоторых других приложений вам может потребоваться указать имя записи с -alias name
; для этого вопроса это не нужно.
- Получить копию сертификата:
keytool -exportcert -rfc -keystore ksfile [-alias name] -file certfile
В этом примере клиент находится на той же машине, что и сервер. В другом случае необходимо скопировать содержимое этого файла с сервера на клиент; это часто делается наиболее удобно путем копирования файла.
- Поместите сертификат в доверенное хранилище клиентов. Как и выше, есть много вариантов, но один из них, который вы, вероятно, увидите, предлагает наиболее часто, потому что обычно он запускается быстрее всего, просто поместив его в файл 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 кнопка View показывает расположение (я) установленных JRE.