Почему я получаю сообщение об ошибке «Не удается сохранить не PrivateKeys» при создании сокета SSL в Java? - PullRequest
6 голосов
/ 12 июля 2011

Я работаю на более старой версии IBM iSeries (IBM-i, i5OS, AS / 400 и т. Д.) С Java 5 JVM (Classic, не ITJ J9) на O / S версии V5R3M0.

Вот сценарий в двух словах:

  1. Я создал хранилище ключей типа JKS, используя Portecle 1.7 (Примечание: я пытался преобразовать хранилище ключей в JCEKS, но этобыл отклонен как неподдерживаемый формат, поэтому кажется, что JKS - это единственный вариант с компьютером iSeries (по крайней мере, с версией, на которой я работаю).
  2. Затем я создал пару ключей и CSR и отправил CSR вThawte должен быть подписан.
  3. Я успешно импортировал подписанный сертификат из Thawte, используя формат PKCS # 7 для импорта всей цепочки сертификатов, включая мой сертификат, посредника Thawte и корневой каталог сервера Thawte.

Все это работало, как и ожидалось.

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

java.security.KeyStoreException: Cannot store non-PrivateKeys

Может кто-нибудь сказать мне, где я ошибся или что я должен проверить дальше?

Ответы [ 3 ]

35 голосов
/ 12 июля 2011

Сообщение об ошибке «Не удается сохранить не PrivateKeys» обычно указывает на то, что вы пытаетесь использовать секретные симметричные ключи с типом хранилища ключей JKS.Тип хранилища ключей JKS поддерживает только асимметричные (открытые / закрытые) ключи.Вам потребуется создать новое хранилище ключей типа JCEKS для поддержки секретных ключей.

5 голосов
/ 12 июля 2011

Как оказалось, это была тонкая проблема, и здесь стоит дать ответ, если у кого-то есть что-то подобное.

Ответ TLDR заключается в том, что я не проверял, чтобы мой ключ и сертификат не были нулевыми, и в результате попытался добавить нулевой ключ и сертификат в хранилище ключей. Более длинный ответ следует.

То, как наш веб-сервер настроен на использование SSL, в частности, для поддержки типичной конфигурации нашего пользователя, где IP-адрес используется для настройки адреса прослушивания веб-сайта, а не DNS-имени, заключается в том, что он находит сертификат в мастер-хранилище ключей, используя псевдоним, и создает временное хранилище ключей, содержащее только сертификат для этого веб-сайта, используя это хранилище ключей для настройки контекста SSL и фабрики сокетов SSL, например:

// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
    final char[]      BLANK_PWD=new char[0];
    SSLContext        ctx=SSLContext.getInstance("TLS");
    KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    Key               ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
    Certificate[]     ctfchn=mstkst.getCertificateChain(svrctfals);
    KeyStore          sktkst;

    sktkst=KeyStore.getInstance("jks");
    sktkst.load(null,BLANK_PWD);
    sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
    kmf.init(sktkst,BLANK_PWD);
    ctx.init(kmf.getKeyManagers(),null,null);
    ssf=ctx.getServerSocketFactory();
    }
catch(java.security.GeneralSecurityException thr) {
    throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
    }

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

Поскольку у меня был пароль к сертификату, ключ и сертификат не были извлечены, и ноль был передан sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn); Однако setKeyEntry проверяет переданный Key, используя instanceof, и приходит к выводу (правильно), что null не instanceof PrivateKey, что приводит к вводящей в заблуждение ошибке, которую я видел.

Исправленный код проверяет, найдены ли ключ и сертификат, и отправляет соответствующие ошибки:

// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
    final char[]      BLANK_PWD=new char[0];
    SSLContext        ctx=SSLContext.getInstance("TLS");
    KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    Key               ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
    Certificate[]     ctfchn=mstkst.getCertificateChain(svrctfals);
    KeyStore          sktkst;

    if(ctfkey==null) {
        throw new IOException("Cannot create server socket factory: No key found for alias '"+svrctfals+"'");
        }
    if(ctfchn==null || ctfchn.length==0) {
        throw new IOException("Cannot create server socket factory: No certificate found for alias '"+svrctfals+"'");
        }

    sktkst=KeyStore.getInstance("jks");
    sktkst.load(null,BLANK_PWD);
    sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
    kmf.init(sktkst,BLANK_PWD);
    ctx.init(kmf.getKeyManagers(),null,null);
    ssf=ctx.getServerSocketFactory();
    }
catch(java.security.GeneralSecurityException thr) {
    throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
    }
3 голосов
/ 12 июля 2011

Вместо использования эфемерного хранилища ключей вы можете обрабатывать все в пределах одного SSLContext.

. Вам нужно будет инициализировать SSLContext, используя пользовательский X509KeyManager вместоиспользуя тот, который задан по умолчанию KeyManagerFactory.В этом X509KeyManager, chooseServerAlias(String keyType, Principal[] issuers, Socket socket) должен возвращать другой псевдоним в зависимости от локального адреса, полученного из сокета.

Таким образом, вам не придется беспокоиться о копировании закрытого ключа из одного хранилища ключей в другоеи это будет работать даже для типов хранилищ ключей, из которых вы не можете извлечь (и, следовательно, скопировать), но использовать только закрытый ключ, например, PKCS # 11.

...