Никаких общих наборов шифров при установлении безопасного соединения

Я пытаюсь установить безопасное соединение между двумя проектами Java, но получаю исключение SSLHandshakeException (никаких общих наборов шифров). Это методы для создания сокетов с обеих сторон:

Клиент:

private SSLSocket getSocketConnection() throws SSLConnectionException {
    try {

        /* Load properties */
        String keystore = properties.getProperty("controller.keystore");
        String passphrase = properties.getProperty("controller.passphrase");
        String host = properties.getProperty("controller.host");
        int port = Integer.parseInt(properties
                .getProperty("controller.port"));

        /* Create keystore */
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());

        /* Get factory for the given keystore */
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);
        SSLContext ctx = SSLContext.getInstance("SSL");
        ctx.init(null, tmf.getTrustManagers(), null);
        SSLSocketFactory factory = ctx.getSocketFactory();

        return (SSLSocket) factory.createSocket(host, port);
    } catch (Exception e) {
        throw new SSLConnectionException(
                "Problem connecting with remote controller: "
                        + e.getMessage(), e.getCause());
    }
}

Сервер:

private SSLServerSocket getServerSocket() throws SSLConnectionException {
    try {

        /* Load properties */
        Properties properties = getProperties("controller.properties");

        String keystore = properties.getProperty("controller.keystore");
        String passphrase = properties.getProperty("controller.passphrase");
        int port = Integer.parseInt(properties
                .getProperty("controller.port"));

        /* Create keystore */
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(new FileInputStream(keystore), passphrase.toCharArray());

        /* Get factory for the given keystore */
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(keyStore);
        SSLContext ctx = SSLContext.getInstance("SSL");
        ctx.init(null, tmf.getTrustManagers(), null);
        SSLServerSocketFactory factory = ctx.getServerSocketFactory();

        return (SSLServerSocket) factory.createServerSocket(port);
    } catch (Exception e) {
        throw new SSLConnectionException(
                "Problem starting auth server: "
                        + e.getMessage(), e.getCause());
    }
}

У меня есть ключ RSA, сгенерированный с помощью keytool. Этот код загружает его с диска.

Что я делаю не так?

ОБНОВЛЕНИЕ: я добавил вызов setEnabledCipherSuites в обе стороны с этим массивом:

String enableThese[] =
{
    "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
    "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
    "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"
};

Я получаю тот же результат.

1 ответ

Решение

На стороне сервера вы не инициализируете хранилище ключей / менеджеры ключей, только хранилище доверенных сертификатов / доверенные менеджеры: ctx.init(null, tmf.getTrustManagers(), null),

На сервере инициализация менеджера ключей всегда необходима для настройки сертификата сервера. Инициализация склада доверенных сертификатов необходима только в том случае, если вы хотите использовать аутентификацию по сертификату клиента. (В этом вопросе есть более подробная информация о разнице между keymanager и trustmanager.)

Без какого-либо настроенного менеджера ключей нет доступных сертификатов на основе RSA или DSA, поэтому нет доступных наборов шифров, которые полагаются на сертификат для аутентификации (все, которые включены по умолчанию). Следовательно, вы не получаете общих наборов шифров между клиентом и сервером.

Вам нужно что-то вроде этого:

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, password.toCharArray()); // That's the key's password, if different.
// ...
ctx.init(kmf.getKeyManagers(), null, null);

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


РЕДАКТИРОВАТЬ: (Я постараюсь объяснить по-другому, так как это было не всем понятно. Возможно, это может помочь.)

Следующий код инициализирует SSLContext с null массив ключевых менеджеров (первый аргумент): ctx.init(null, tmf.getTrustManagers(), null), (Нет менеджера ключей по умолчанию.)

Менеджер ключей - это то, что управляет вашими (личными) ключами и сертификатами на стороне, где выполняется код. На сервере менеджер ключей отвечает за обработку сертификата сервера и его закрытого ключа. Сам менеджер ключей обычно инициализируется "хранилищем ключей хранилища ключей". " хранилище ключей " в Java может иметь несколько значений. Одним из значений хранилища ключей является сущность, в которой могут храниться ключи и сертификаты, обычно файл. Такое хранилище ключей может использоваться для инициализации диспетчера доверия 1 (в этом случае он называется хранилищем доверенных сертификатов) или диспетчера ключей (в этом случае он называется хранилищем ключей). Извините, я не выбираю имена, но так называются системные свойства.

Когда сервер настроен с помощью диспетчера пустых ключей, он настраивается без какого-либо сертификата и соответствующего закрытого ключа. Следовательно, у него нет сертификата RSA или DSA. Следовательно, он не сможет использовать *_RSA_* или же *_DSS_* комплекты шифров, независимо от того, были они явно включены или нет (они будут отключены автоматически при отсутствии сертификата для использования с ними). Это фактически отбрасывает любой набор шифров, включенный по умолчанию (или любой такой набор шифров, включенный явно в любом случае). Следовательно, "общего набора шифров нет".

Короче говоря, SSLContext на стороне сервера должен быть настроен сертификат и его закрытый ключ 2. Это делается путем настройки его менеджера ключей. В свою очередь, это часто делается с помощью хранилища ключей с KeyManagerFactory (не TrustManagerFactory).

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

2: Некоторые комплекты шифров, поддерживаемые JSSE, не нуждаются в сертификатах, но это либо анонимные комплекты шифров (небезопасные), либо комплекты шифров Kerberos (которые необходимо настраивать по-разному). Оба отключены по умолчанию.

Другие вопросы по тегам