Невозможно выполнить аутентификацию клиента с помощью закрытого ключа PKCS # 1 с помощью java keyStore

У меня есть открытый ключ и закрытый ключ (PKCS # 1: т.е. "НАЧАТЬ ЗАКРЫТЫЙ КЛЮЧ RSA") в качестве отдельных строковых входов. Эти ключи создаются с помощью следующих команд:

openssl req -new -x509 -keyout ca-key.pem -out ca-cert.pem -days 365 -passout pass:abcd123 -subj "/C=IN/ST=KAD/L=CAT/O=MIN/OU=OPQ/CN=*.xyz.com/[email protected]"
openssl genrsa -out client_private.key 2048
openssl req -new -sha256 -key client_private.key -subj "/C=IN/ST=KAD/L=CAT/O=MIN/OU=OPQ/CN=client.xyx.com" -out client.csr
openssl x509 -req -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client.crt -days 365 -sha256
openssl x509 -in client.crt -out client.pem -outform PEM

где содержимое client.csr предоставляется как строка открытого ключа, а содержимое client_private.key предоставляется как строка закрытого ключа

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

javax.net.ssl.SSLProtocolException: Handshake message sequence violation, 2

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

public void initClientAuthStore(String certString, String keyString)
        throws GeneralSecurityException, IOException {
    clientAuthStore = KeyStore.getInstance(getType());
    clientAuthStore.load(null, getPassword().toCharArray());

    Certificate clientCert = CertificateHandler.getCertificate(certString);
    Certificate[] certificateChain = {trustStoreCertificate, clientCert};

    log.debug("initClientAuthStore: Certificate is created");
    // keyString.getBytes(StandardCharsets.UTF_8);
    //    clientAuthStore.setKeyEntry(
    //        keyStoreAlias, getPrivateKey(keyString), getPassword().toCharArray(), certificate);
    PrivateKey privateKey = getPrivateKey(keyString);
    clientAuthStore.setKeyEntry(
            keyStoreAlias,
            privateKey,
            getPassword().toCharArray(),
            certificateChain);

    log.debug("initClientAuthStore: setting certificate and private key is success.");
}

public PrivateKey getPrivateKey(String key) throws IOException {
    PemObject privateKeyObject;
    PemReader pemReader =
            new PemReader(
                    new InputStreamReader(
                            new ByteArrayInputStream(key.getBytes(Charset.forName("UTF-8"))),
                            StandardCharsets.UTF_8));
    privateKeyObject = pemReader.readPemObject();
    RSAPrivateCrtKeyParameters privateKeyParameter;
    if (privateKeyObject.getType().endsWith("RSA PRIVATE KEY")) {
        log.info("PRIVATE KEY TYPE: pkcs#1");
        // PKCS#1 key
        RSAPrivateKey rsa = RSAPrivateKey.getInstance(privateKeyObject.getContent());
        privateKeyParameter =
                new RSAPrivateCrtKeyParameters(
                        rsa.getModulus(),
                        rsa.getPublicExponent(),
                        rsa.getPrivateExponent(),
                        rsa.getPrime1(),
                        rsa.getPrime2(),
                        rsa.getExponent1(),
                        rsa.getExponent2(),
                        rsa.getCoefficient());
    } else if (privateKeyObject.getType().endsWith("PRIVATE KEY")) {
        log.info("PRIVATE KEY TYPE: pkcs#8");
        // PKCS#8 key
        privateKeyParameter =
                (RSAPrivateCrtKeyParameters) PrivateKeyFactory.createKey(privateKeyObject.getContent());
    } else {
        throw new RuntimeException("Unsupported key type: " + privateKeyObject.getType());
    }

    JcaPEMKeyConverter jcaPemKeyConverter = new JcaPEMKeyConverter();
    PrivateKey privateKey =
            jcaPemKeyConverter.getPrivateKey(
                    PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParameter));
    log.info("PRIVATE KEY generated. {}", privateKey.getFormat());
    return privateKey;
}

Получение ошибки ниже:

javax.net.ssl.SSLProtocolException: Handshake message sequence violation, 2

Не уверен, что не так, и если мне не хватает какого-либо кодирования/декодирования и т. д. Я получаю закрытый ключ PKCS # 1 в качестве ввода строки, и мне приходится использовать надувной замок для преобразования в PKCS # 8, генерировать закрытый ключ и добавлять в хранилище. Примечание: закрытый ключ является содержимым client_private.key.


person Sudarshan sridhar    schedule 03.07.2019    source источник


Ответы (1)


(Не ответ, но слишком много для комментариев.)

где содержимое client.csr предоставляется в виде строки открытого ключа...

CSR — это не открытый ключ, это CSR. Но код, который вы показываете, явно не использует его. Очевидно, он использует сертификат, который также не является открытым ключом, это сертификат, который содержит открытый ключ. Однако для аутентификации SSL/TLS правильно использовать сертификат, а НЕ открытый ключ ИЛИ CSR.

Я получаю закрытый ключ PKCS # 1 в качестве ввода строки, и мне нужно использовать надувной замок для преобразования в PKCS # 8, генерировать закрытый ключ и добавлять в хранилище....

На самом деле вам не нужно конвертировать в PKCS8; PEMParser плюс JcaPEMKeyConvertor могут преобразовывать PKCS1 напрямую в KeyPair, содержащую пригодный для использования (RSA)PrivateKey, что намного проще. Или OpenSSL может легко создать ключ PKCS8, который может прочитать стандартный JCE (без BouncyCastle), или, в этом отношении, PKCS12, который уже является хранилищем ключей Java без какого-либо преобразования. Но то, что у вас есть, работает.

Теперь мне нужно создать хранилище ключей Java, добавить эти ключи и прагматично экспортировать в виде файла JKS.

В сторону: вы, вероятно, имеете в виду программно, а не прагматически. Ваш код не экспортирует файл хранилища ключей, и в этом нет необходимости; и вам не нужно специально использовать хранилище ключей JKS, любое поддерживаемое хранилище ключей в памяти должно работать, хотя вы не показываете код, выполняющий соединение и использующий хранилище ключей. Начиная с Java 9 (почти два года назад) Oracle рекомендует отказаться от использования JKS и вместо этого использовать PKCS12, хотя я сомневаюсь, что они действительно откажутся от JKS в ближайшее время.

javax.net.ssl.SSLProtocolException: нарушение последовательности сообщений рукопожатия, 2

Это ваша реальная проблема, и это не хранилище ключей или ключ, и это не должно быть сертификатом. Даже если вы отправляете ключ и сертификат, который ему не нравится, сервер должен отклонить аутентификацию внутри протокола(ов), не нарушая протокол. А 2 — это ServerHello, которое должно происходить только перед отправкой или проверкой сертификата в любом направлении. Выполните хотя бы одно из следующих действий:

  • стандартная отладка Java SSL/TLS: запустите с помощью sysprop javax.net.debug=ssl,handshake и запишите/зарегистрируйте выходные данные. (Ниже Java 11 вы можете использовать только ssl, и он автоматически включает handshake.) Это точно покажет, что ваша система отправляет и получает, и позволит определить, как это неправильно, что должно, по крайней мере, помочь определить, почему.

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

  • попробуйте подключиться с помощью другого инструмента (или инструментов), используя тот же ключ + сертификат и с того же компьютера, если это вообще возможно - это может быть проблема, связанная с сетью, которая зависит от того, откуда вы подключаетесь. Поскольку у вас есть openssl и уже есть файлы в форматах OpenSSL, проще всего использовать

    openssl s_client -connect $host:$port -key keyfile -cert certfile [see text]

    s_client также необычен (уникальный, насколько мне известно) тем, что вы можете контролировать, отправляется ли SNI (расширение индикации имени сервера), и в настоящее время со многими серверами это может повлиять на поведение сервера (возможно, запуск или подавление ошибки). Отправляет ли Java (JSSE) SNI, зависит от комбинации, как правило, нескольких факторов, о которых вы нам не сказали: всегда от версии Java и, как правило, от некоторых деталей кода, выполняющего попытку подключения, особенно от того, использует ли он HttpsURLConnection, new-j11+ HttpClient , сторонний код, такой как Apache или google, или пользовательский код. Для выпусков OpenSSL до 1.1.0 SNI не отправляется по умолчанию, и вы должны добавить -servername $host для его отправки. Для 1.1.1 он отправляется по умолчанию, но не отправляется, если вы добавите -noservername.

    В этом примере я не указал хранилище доверенных сертификатов. s_client по умолчанию будет сверяться со своим хранилищем доверенных сертификатов по умолчанию, которое может быть или может быть правильным для вашего сервера, но проигнорирует любую ошибку и продолжит работу в любом случае. Если вы отлаживаете проблему доверия к сертификату, важно сопоставить хранилище доверенных сертификатов OpenSSL с тем, которое используется вашей Java, но не для вашей проблемы.

  • уточните у оператора(ов) сервера, как эта проблема воспринимается на их стороне: считают ли они, что с вашим рукопожатием что-то не так, и если да, то что? Какое программное обеспечение (или, как правило, более подходящее промежуточное ПО) они используют, с какой конфигурацией или параметрами?

Если вы добавите (некоторую/достаточную) эту информацию в свой вопрос, я постараюсь обновить ее, чтобы она стала реальным ответом.

person dave_thompson_085    schedule 04.07.2019