Соединение через сокет SSL через Java

Прочитав многочисленные ответы на эти темы, я обнаружил, что совершенно не могу связать воедино кусочки головоломки, надеюсь, вы меня извините за это.

Я пытаюсь изменить свое простое соединение с сокетом в Java для использования SSL. Я бы хотел, чтобы и сервер, и клиент аутентифицировали себя, если это возможно, но только аутентификация сервера будет хорошим началом.

В настоящее время это чрезвычайно простой код на стороне сервера:

    ServerSocket serverSocket = new ServerSocket(port);
    Socket socket = serverSocket.accept();

И это код на стороне клиента:

    Socket socket = null;
    while (true) {
        try {
            socket = new Socket(ipAddress, port);
            break;
        } catch (Exception e) {}
    }

Это нормально работает, но без SSL.

Я сгенерировал SSL-сертификаты для сервера и клиента с помощью OpenSSL, получив в итоге:

  • Сертификат для сервера (формат PEM)
  • Сертификат для клиента (формат PEM)
  • Приватный ключ для сервера (формат PEM)
  • Приватный ключ для клиента (формат PEM)
  • Файл CA (формат PEM, CER и CRT)

Исходя из этого, я использовал OpenSSL для создания хранилищ ключей PKCS12 (.p12) как для клиента, так и для сервера, как показано ниже.

  • server.p12, созданный с помощью openssl pkcs12 -export -in server-cert.pem -inkey server-private-key.pem -out server.p12
  • client.p12, созданный с помощью openssl pkcs12 -export -in client-cert.pem -inkey client-private-key.pem -out client.p12

Затем я превратил их в хранилища ключей JKS с помощью keytool (например, для сервера команда была keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.jks -deststoretype JKS), в результате появятся два файла с именами server.jks и client.jks.

Затем я использую следующий код в качестве замены предыдущего фрагмента сервера:

    char[] keyStorePassword =  "JKSPassword".toCharArray();
    FileInputStream keyStoreFile = new FileInputStream("somepath/server.jks");
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(keyStoreFile, keyStorePassword);
    KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
    SSLContext sslContext = SSLContext.getDefault();
    ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(port);
    Socket socket = serverSocket.accept();

И следующий код в качестве замены клиентского фрагмента:

    Socket socket = null;
    while (true) {
        try {
            char[] keyStorePassword =  "JKSPassword".toCharArray();
            FileInputStream keyStoreFile = new FileInputStream("somepath/client.jks");
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(keyStoreFile, keyStorePassword);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyManagerFactory.init(keyStore, "PKCS12Password".toCharArray());
            SSLContext sslContext = SSLContext.getDefault();
            socket = sslContext.getSocketFactory().createSocket(ipAddress, port);
            break;
        } catch (Exception e) {}
    }

Теперь я все еще получаю javax.net.ssl.SSLHandshakeException: нет общих наборов шифров на сервере (и javax.net.ssl.SSLHandshakeException: получено фатальное предупреждение: handshake_failure on клиент).

Что могло быть не так?


person Infima    schedule 03.09.2015    source источник
comment
Какую версию Java вы используете на клиенте и сервере?   -  person Robert    schedule 03.09.2015
comment
Java 8u20 (конечно, включая SDK) как на клиенте, так и на сервере (это одна и та же машина, и для ip я в настоящее время использую localhost).   -  person Infima    schedule 04.09.2015
comment
Ваш серверный и клиентский коды бессмысленны. Вы инициализируете KeyStore, но никогда не используете его. Внимательно изучите Справочное руководство JSSE.   -  person user207421    schedule 05.09.2015


Ответы (1)


Я нашел недостающую часть головоломки (кажется).

Вместо строки SSLContext sslContext = SSLContext.getDefault(); на клиенте я поставил:

    BufferedInputStream serverCertificateFile = new BufferedInputStream(new FileInputStream("somepath/server-cert.der"));
    X509Certificate serverCertificate = (X509Certificate)
              CertificateFactory.getInstance("X.509").generateCertificate(serverCertificateFile);
            sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] arg0,
                            String arg1) throws CertificateException {
                        throw new CertificateException();
                    }
                    @Override
                    public void checkServerTrusted(X509Certificate[] arg0,
                            String arg1) throws CertificateException {
                        boolean valid = false;
                        for (X509Certificate certificate : arg0) {
                            try {
                                certificate.verify(serverCertificate.getPublicKey());
                                valid = true;
                                break;
                            } catch (SignatureException e) {}
                        }
                        if (!valid) {
                            throw new CertificateException();
                        }
                    }
                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
            }, new SecureRandom());

server-cert.der - это файл, созданный с помощью openssl509 -outform der -in server-cert.pem -out server-cert.der.

Мне не удалось найти хороших руководств или вопросов по этому поводу на StackOverflow, поэтому я создал это, пытаясь понять справочное руководство.

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

Кажется, это работает, дайте мне знать, если в этом подходе есть что-то принципиально неправильное (я, к сожалению, предполагаю, что есть). Спасибо, что так много прочитали!

person Infima    schedule 05.09.2015
comment
Я не понимаю, почему вы сначала посмотрели на StackOverflow, а не на официальную документацию. Как только вы заглянули в нужное место, вы сразу же нашли ответ. Есть урок. - person user207421; 05.09.2015
comment
Сначала я просмотрел официальную документацию, и мне все еще приходилось пробовать разные вещи, прежде чем что-то сработало, поэтому я не уверен, что понимаю это даже сейчас. Как я уже сказал ранее, я не мог соединить кусочки головоломки ... официальная документация была лабиринтом, мягко говоря. Я бы хотел, чтобы не было учебника. - person Infima; 05.09.2015