2016-10-13 2 views
0

У меня возникла проблема с установкой соединения с сервером Socket с помощью SSL-сокетов. Я все еще участвую в процессе обучения SSL-шифрованию и занимаюсь сертификатами и хранилищами ключей. У меня есть клиент и сервер приложения, которые предполагается соединить друг с другом следующим образом:Справка по SSL-сокету - javax.net.ssl.SSLHandshakeException: получено фатальное предупреждение: certificate_unknown

SERVER - SSLReverseEchoer.java

import java.io.*; 
import java.net.*; 
import java.security.*; 
import javax.net.ssl.*; 
import org.bouncycastle.jce.provider.BouncyCastleProvider; 

public class SSLReverseEchoer { 
    public static void main(String[] args) { 
    String ksName = "sslkeystore.jks"; 
    String keystorePass = "sslkeystorepassword"; 
    char ksPass[] = keystorePass.toCharArray(); 

    int sslPort = 9099; 

    Security.addProvider(new BouncyCastleProvider()); 

    File file = new File(ksName); 
    String absPath = file.getAbsolutePath(); 

    System.setProperty("javax.net.ssl.keyStore", absPath); 
    System.setProperty("javax.net.ssl.keyStorePassword", "sslkeystorepassword"); 

    try { 

     //Get keystore w/ password 
     KeyStore ks = KeyStore.getInstance("JKS"); 
     ks.load(new FileInputStream(ksName), ksPass); 

     //Trust Manager 
     TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
     tmf.init(ks); 

     KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 
     kmf.init(ks, ksPass); 

     SSLContext sc = SSLContext.getInstance("TLS"); 
     sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); 

     SSLServerSocketFactory ssf = sc.getServerSocketFactory(); 
     SSLServerSocket s = (SSLServerSocket) ssf.createServerSocket(sslPort); 
     printServerSocketInfo(s); 

     //WAIT FOR CONNECTION TODO ADD THREAD for NIO 
     SSLSocket c = (SSLSocket) s.accept(); 
     printSocketInfo(c); 

     BufferedWriter w = new BufferedWriter(new OutputStreamWriter(
      c.getOutputStream())); 
     BufferedReader r = new BufferedReader(new InputStreamReader(
      c.getInputStream())); 

     String m = "Welcome to SSL Reverse Echo Server."+ 
      " Please type in some words."; 
     w.write(m,0,m.length()); 
     w.newLine(); 
     w.flush(); 
     while ((m=r.readLine())!= null) { 
      if (m.equals(".")) break; 
      char[] a = m.toCharArray(); 
      int n = a.length; 
      for (int i=0; i<n/2; i++) { 
       char t = a[i]; 
       a[i] = a[n-1-i]; 
       a[n-i-1] = t; 
      } 
      w.write(a,0,n); 
      w.newLine(); 
      w.flush(); 
     } 
     w.close(); 
     r.close(); 
     c.close(); 
     s.close(); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    } 
    private static void printSocketInfo(SSLSocket s) { 
    System.out.println("Socket class: "+s.getClass()); 
    System.out.println(" Remote address = " 
     +s.getInetAddress().toString()); 
    System.out.println(" Remote port = "+s.getPort()); 
    System.out.println(" Local socket address = " 
     +s.getLocalSocketAddress().toString()); 
    System.out.println(" Local address = " 
     +s.getLocalAddress().toString()); 
    System.out.println(" Local port = "+s.getLocalPort()); 
    System.out.println(" Need client authentication = " 
     +s.getNeedClientAuth()); 
    SSLSession ss = s.getSession(); 
    System.out.println(" Cipher suite = "+ss.getCipherSuite()); 
    System.out.println(" Protocol = "+ss.getProtocol()); 
    } 
    private static void printServerSocketInfo(SSLServerSocket s) { 
    System.out.println("Server socket class: "+s.getClass()); 
    System.out.println(" Socket address = " 
     +s.getInetAddress().toString()); 
    System.out.println(" Socket port = " 
     +s.getLocalPort()); 
    System.out.println(" Need client authentication = " 
     +s.getNeedClientAuth()); 
    System.out.println(" Want client authentication = " 
     +s.getWantClientAuth()); 
    System.out.println(" Use client mode = " 
     +s.getUseClientMode()); 
    } 
} 

КЛИЕНТ - SSLSocketClient.java

import java.io.*; 
import java.net.*; 
import javax.net.ssl.*; 

public class SSLSocketClient { 

    public static void main(String[] args) { 

    int sslPort = 9099; 

    BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 
    PrintStream out = System.out; 

    SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault(); 

    try { 
     SSLSocket c = (SSLSocket) f.createSocket("localhost", sslPort); 
     printSocketInfo(c); 
     c.startHandshake(); 

     BufferedWriter w = new BufferedWriter(new OutputStreamWriter(c.getOutputStream())); 
     BufferedReader r = new BufferedReader(new InputStreamReader(c.getInputStream())); 

     String m = null; 
     while ((m=r.readLine())!= null) { 
      out.println(m); 
      m = in.readLine(); 
      w.write(m,0,m.length()); 
      w.newLine(); 
      w.flush(); 
     } 
     w.close(); 
     r.close(); 
     c.close(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
    } 
    private static void printSocketInfo(SSLSocket s) { 
    System.out.println("Socket class: "+s.getClass()); 
    System.out.println(" Remote address = " 
     +s.getInetAddress().toString()); 
    System.out.println(" Remote port = "+s.getPort()); 
    System.out.println(" Local socket address = " 
     +s.getLocalSocketAddress().toString()); 
    System.out.println(" Local address = " 
     +s.getLocalAddress().toString()); 
    System.out.println(" Local port = "+s.getLocalPort()); 
    System.out.println(" Need client authentication = " 
     +s.getNeedClientAuth()); 
    SSLSession ss = s.getSession(); 
    System.out.println(" Cipher suite = "+ss.getCipherSuite()); 
    System.out.println(" Protocol = "+ss.getProtocol()); 
    } 
} 

Я запускаю СЕРВЕР и начинает прослушивание на указанном порту, но когда Выполняется CLIENT. Я получаю следующие стеки.

SERVER ВЫХОД

Server socket class: class sun.security.ssl.SSLServerSocketImpl 
    Socket address = 0.0.0.0/0.0.0.0 
    Socket port = 9099 
    Need client authentication = false 
    Want client authentication = false 
    Use client mode = false 
Socket class: class sun.security.ssl.SSLSocketImpl 
    Remote address = /127.0.0.1 
    Remote port = 62145 
    Local socket address = /127.0.0.1:9099 
    Local address = /127.0.0.1 
    Local port = 9099 
    Need client authentication = false 
    Cipher suite = SSL_NULL_WITH_NULL_NULL 
    Protocol = NONE 
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown 
    at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1541) 
    at sun.security.ssl.SSLSocketImpl.checkWrite(SSLSocketImpl.java:1553) 
    at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:71) 
    at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) 
    at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) 
    at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295) 
    at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) 
    at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) 
    at java.io.BufferedWriter.flush(BufferedWriter.java:254) 
    at com.example.SSLReverseEchoer.main(SSLReverseEchoer.java:60) 
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154) 
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023) 
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125) 
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) 
    at sun.security.ssl.SSLSocketImpl.getSession(SSLSocketImpl.java:2267) 
    at com.example.SSLReverseEchoer.printSocketInfo(SSLReverseEchoer.java:94) 
    at com.example.SSLReverseEchoer.main(SSLReverseEchoer.java:49) 

КЛИЕНТ ВЫХОД

Socket class: class sun.security.ssl.SSLSocketImpl 
    Remote address = localhost/127.0.0.1 
    Remote port = 9099 
    Local socket address = /127.0.0.1:62145 
    Local address = /127.0.0.1 
    Local port = 62145 
    Need client authentication = false 
    Cipher suite = SSL_NULL_WITH_NULL_NULL 
    Protocol = NONE 
javax.net.ssl.SSLException: Connection has been shutdown: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
    at sun.security.ssl.SSLSocketImpl.checkEOF(SSLSocketImpl.java:1541) 
    at sun.security.ssl.SSLSocketImpl.checkWrite(SSLSocketImpl.java:1553) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1399) 
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) 
    at com.example.SSLSocketClient.main(SSLSocketClient.java:23) 
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) 
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949) 
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302) 
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296) 
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509) 
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216) 
    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) 
    at sun.security.ssl.SSLSocketImpl.getSession(SSLSocketImpl.java:2267) 
    at com.example.SSLSocketClient.printSocketInfo(SSLSocketClient.java:55) 
    at com.example.SSLSocketClient.main(SSLSocketClient.java:22) 
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387) 
    at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292) 
    at sun.security.validator.Validator.validate(Validator.java:260) 
    at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324) 
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229) 
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124) 
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491) 
    ... 9 more 
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 
    at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) 
    at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) 
    at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280) 
    at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382) 
    ... 15 more 

Я использовал хранилище ключей для генерации sslkeystore.jks и X509_certificate.cer со следующими параметрами:

Сформировать sslkeystore.jks

keytool -genkey -keyalg RSA -alias sslsocket -keystore sslkeystore.jks -storepass sslkeystorepassword -validity 365 -keysize 2048 
What is your first and last name? 
    [Unknown]: Gandalf 
What is the name of your organizational unit? 
    [Unknown]: Wizardry 
What is the name of your organization? 
    [Unknown]: Arnock 
What is the name of your City or Locality? 
    [Unknown]: Minas Tirith 
What is the name of your State or Province? 
    [Unknown]: Gondor 
What is the two-letter country code for this unit? 
    [Unknown]: GD 
Is CN=Gandalf, OU=Wizardry, O=Arnock, L=Minas Tirith, ST=Gondor, C=GD correct? 
    [no]: yes 

Enter key password for <sslsocket> 
    (RETURN if same as keystore password): 

Экспорт Certificate.cer

keytool -export -alias sslsocket -keystore sslkeystore.jks -rfc -file X509_certificate.cer 
Enter keystore password: 
Certificate stored in file <X509_certificate.cer> 

Любые предложения о том, что происходит здесь не так?

То, что я пытаюсь достичь здесь, - это асимметричное шифрование в сокете с открытыми и закрытыми ключами. Не закрывает ли * .jks открытый ключ, а * .cer имеет закрытый ключ? Если это так, мне нужно что-то дополнительное в клиенте для использования закрытого ключа или оно предоставляется клиенту во время рукопожатия?

Любая помощь будет оценена по достоинству.

ответ

1

У вас есть это в обратном направлении.As the Wikipedia article begins:

Криптография с открытым ключом или асимметричная криптография, любая криптографическая система, которая использует пары ключей: публичные ключи, которые могут быть распространены широко [которые математически] спаренные с секретными ключами, которые известны только владельцу.

Сервер SSL/TLS сохраняет личный секретный ключ; это открытый ключ, который выдается другим, в частности клиентам.Но как wp также говорит

Связывание между открытым ключом и его «владельцем» должно быть правильным, иначе алгоритм может функционировать отлично и все же быть абсолютно небезопасным на практике. [...] Связывание открытого ключа с его владельцем обычно осуществляется с помощью протоколов, реализующих инфраструктуру открытого ключа - это позволяет официально подтвердить достоверность ассоциации путем ссылки на доверенную третью сторону в форме либо иерархического центра сертификации (например, X.509) [...]

Хотя PKC в общем имеет другие варианты, обычно SSL/TLS (и всегда на Java) использует сертификаты X.509. A сертификат для участника, здесь сервер содержит публикацию этой стороны, личность этой стороны (для сервера SSL/TLS, обычно DNS-имя или иногда несколько имен сервера) и другую информацию вы можете игнорировать, обычно подписывается Certificate Authority.

Как правило, лидеры (клиенты) уже установили копии «корневых» сертификатов известных ЦС, таких как Verisign, GoDaddy и т. Д., И, возможно, более локальные центры сертификации, такие как ваш работодатель или правительство штата, а сервер просто отправляет свой собственный сертификат, а также любую необходимую «цепочку» или промежуточный сертификат (сертификаты), предоставленные CA, в рукопожатии SSL/TLS; клиент может проверить сертификат, используя предварительно установленную копию корня. (Для клиента Java см. Ниже.)

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

Конкретно в Java:

  • keytool -genkeypair, или его устаревший синоним -genkey, создает пару ключей в файл, по умолчанию файл JKS, с собственной подписью (по умолчанию) сертификат

  • keytool -exportcert (или -export) копирует сертификат (содержащий публикацию и имя) в файл, который здесь вы назвали X509_certficate.cer. Поскольку вы не получили подлинный сертификат CA, это самозаверяющий сертификат, и вам нужно поместить его в доверительный магазин каждого клиента.

Есть три способа получения сертификата в доверенных сертификатов клиента Java-:

  • Явный 1: поместить сертификат в файл хранилища ключей, обычно JKS, используя keytool -importcert, прочитал, что хранилище ключей в память , и используйте его для инициализации TrustManager, затем SSLContext, а затем SSLSocketFactory. Это похоже на ваш код сервера, за исключением без части KeyManager, и вы используете классы SSLSocket вместо классов SSLServerSocket.

  • Явные 2: чтение (только), что сертификат из файла, используя CertificateFactory типа X.509, создать хранилище ключей в памяти (без файла ключей) и поместить сертификат в него, а затем действовать в явном 1. If вам нужно несколько доверенных сертификатов, вы можете прочитать их и поместить их в память, но это быстро становится больше работы, чем чтение одного файла хранилища ключей, содержащего все из них.

  • средний: поместить сертификат в файл хранилища ключей и набор свойств системы javax.net.ssl.trustStore и ...trustStorePassword...trustStoreType, если не по умолчанию JKS кнопку, или в Java8 также PKCS12), чтобы указать на это, до первого создания сокета SSL , Вы можете установить системные свойства, вызвав System.setProperty или используя флаг -Dname=value в командной строке при запуске java.

  • неявные: добавить сертификат в файл, используемый по умолчанию, который JREHOME/lib/security/cacerts (или jssecacerts, если это существует, но по умолчанию он не делает) и установлен, чтобы содержать около сотни хорошо известного ЦА. Это затрагивает все программы, использующие эту JRE, если они не устанавливают свои собственные магазины доверия с использованием вышеперечисленных опций, что может быть или не быть проблемой в зависимости от того, что еще вы или кто-либо еще запускаете (в Java) в этой системе.

Наконец, CommonName в/сертификате сервера SSL TLS, который является поле фактически установить, когда keytool запрашивает first and last name (уведомление CN=Gandalf в обратном считывании, CN означает CommonName), должна быть именем хоста из сервер, или, точнее, имя , используемое клиентами (клиентами) для подключения к серверам (что в вашем примере было, по-видимому, localhost). В качестве альтернативы, особенно для сертификатов реального CA, для этого может использоваться расширение под названием SubjectAlternativeName (сокращенное SAN), но выполнение SAN с keytool довольно неуклюжие.

Основная SSLSocket логика не (в настоящее время) проверяет имя хоста, но большинство других клиентов, таких как веб-браузеры (и даже HttpsURLConnection в Java) сделать это проверить, и если ваш сервер не может быть подключен как Gandalf (без какой-либо квалификации домена) тех, клиенты откажутся подключиться к вашему серверу с помощью сертификата, даже если сам сертификат является «действительным».

+0

Спасибо за подробный ответ. Теперь я понимаю, что моя формулировка общественных/частных отношений была отключена. Я пытаюсь добавить самозаверяющий сертификат в доверительный магазин клиентов. У меня есть следующий вопрос: – DatumPlane

+0

Является ли обычной практикой для java-клиента иметь сертификат, связанный с приложением? и если вы используете самоподписанный сертификат, вы должны сопоставить CommonName с именем хоста сервера? Будут ли ошибки аутентификации, если они отличаются? – DatumPlane

+0

(1), конечно, можно связать сертификат для однорангового узла с Java-приложением; формат jar упрощает управление и использование. Я не уверен, насколько это распространено. Если подписывается банка, которая обрабатывает проблему «распространять безопасно», хотя она идет к следующей черепахе: с каким ключом и сертификатом вы подписываетесь? Также, чтобы изменить/обновить сертификат, вы должны создать и распространить новую версию приложения; если ваше приложение обновляется часто по другим причинам, это легко добавить, но в противном случае может быть меньше. –

Смежные вопросы