Импорт PEM в хранилище ключей Java

148

Я пытаюсь подключиться к серверу SSL, который требует от меня аутентификации. Чтобы использовать SSL поверх Apache MINA, мне нужен подходящий файл JKS. Однако мне дали только файл .PEM.

Как мне создать файл JKS из файла PEM?

jwoolard
источник
1
Может быть, эта ссылка может быть полезной: http://www.agentbob.info/agentbob/79-AB.html
Laurent K,

Ответы:

245

Сначала преобразуйте свой сертификат в формат DER:

openssl x509 -outform der -in certificate.pem -out certificate.der

А после импортируйте его в хранилище ключей:

keytool -import -alias your-alias -keystore cacerts -file certificate.der
Энтони О.
источник
8
Не работает, если файл .pem содержит более одного сертификата.
MarioVilas
14
У меня есть единственный сертификат .pem, и он не работает. 1795: ошибка: 0906D06C: подпрограммы PEM: PEM_read_bio: нет стартовой строки: / usr / src / secure / lib / libcrypto /../../../ crypto / openssl / crypto / pem / pem_lib.c: 648: Ожидается : НАДЕЖНЫЙ СЕРТИФИКАТ
Брайан Ноблауч
5
Я нашел решение. Предварительно отложите корневой и промежуточный сертификаты в .pem, затем преобразуйте.
Брайан Ноблаух
1
@Anthony: эта команда только сообщает, как импортировать PEM в JKS. Может быть хорошей идеей добавить команду для экспорта JKS из магазина.
Vishal Biyani
2
Если у меня несколько сертификатов в .pem, как мне импортировать их в хранилище ключей Java?
Эрик
60

Если вы хотите только импортировать сертификат в формате PEM в хранилище ключей, keytool выполнит эту работу:

keytool -import -alias *alias* -keystore cacerts -file *cert.pem*
Зап
источник
11
Если я пойду так, я получаю сообщение об ошибке: ошибка keytool: java.lang.Exception:
введите
1
@frandevel, эта ошибка может быть вызвана тем, что входной файл PEM имеет заголовок над разделителем --- BEGIN или имеет несколько PEM в одном файле или обоих. Либо удалите все посторонние данные и загрузите каждый PEM по одному, либо используйте мой инструмент, как подробно описано в моем ответе.
Аластер МакКормак
Спасибо @Fuzzyfelt, я посмотрю
frandevel
1
Та же проблема, но файл .PEM чистый, со всеми соответствующими заголовками.
Брайан Ноблауч
17

Я разработал http://code.google.com/p/java-keyutil/, который импортирует сертификаты PEM прямо в хранилище ключей Java. Его основная цель - импортировать состоящие из нескольких частей пакеты сертификатов операционной системы PEM, такие как ca-bundle.crt. Они часто включают заголовки, которые keytool не может обрабатывать.

</self promotion>
Аластер МакКормак
источник
5
Неплохой игрушечный проект, но keytoolвсе это уже делает за вас (и не только). (Между прочим, вы должны закрыть свои FileOutputStreamи закрыть свои потоки ввода / вывода finally, если произойдет исключение.)
Бруно
8
Привет, Бруно, спасибо за советы. Реальный вариант использования - импортировать все записи в /etc/pki/tls/certs/ca-bundle.crt (RHEL / CentOS) за один раз. AFAIK, keytool импортирует только первую запись. Я видел, как многие люди делают это по-разному, но обычно это требует многократного вызова keytool для каждого сертификата. В Ubuntu есть сценарий обновления, который делает именно это, за исключением того, что Ubuntu хранит свои сертификаты в каталоге. Я добавлю поддержку каталогов в ближайшем будущем. Еще раз спасибо за просмотр кода.
Alastair McCormack
14

В моем случае у меня был файл pem, который содержал два сертификата и зашифрованный закрытый ключ для использования во взаимной SSL-аутентификации. Итак, мой файл pem выглядел так:

-----BEGIN CERTIFICATE-----

...

-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----

Proc-Type: 4,ENCRYPTED

DEK-Info: DES-EDE3-CBC,C8BF220FC76AA5F9

...

-----END RSA PRIVATE KEY-----

-----BEGIN CERTIFICATE-----

...

-----END CERTIFICATE-----

Вот что я сделал

Разделите файл на три отдельных файла так, чтобы каждый из них содержал только одну запись, начинающуюся ---BEGIN..со ---END..строк и заканчивающуюся строками. Давайте предположим , что мы теперь имеем три файла: cert1.pem, cert2.pemи pkey.pem.

Преобразование pkey.pemв формат DER с использованием openssl и следующего синтаксиса:

openssl pkcs8 -topk8 -nocrypt -in pkey.pem -inform PEM -out pkey.der -outform DER

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

Если преобразование прошло успешно, вы получите новый файл с именем pkey.der.

Создайте новое хранилище ключей Java и импортируйте закрытый ключ и сертификаты:

String keypass = "password";  // this is a new password, you need to come up with to protect your java key store file
String defaultalias = "importkey";
KeyStore ks = KeyStore.getInstance("JKS", "SUN");

// this section does not make much sense to me, 
// but I will leave it intact as this is how it was in the original example I found on internet:   
ks.load( null, keypass.toCharArray());
ks.store( new FileOutputStream ( "mykeystore"  ), keypass.toCharArray());
ks.load( new FileInputStream ( "mykeystore" ),    keypass.toCharArray());
// end of section..


// read the key file from disk and create a PrivateKey

FileInputStream fis = new FileInputStream("pkey.der");
DataInputStream dis = new DataInputStream(fis);
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

byte[] key = new byte[bais.available()];
KeyFactory kf = KeyFactory.getInstance("RSA");
bais.read(key, 0, bais.available());
bais.close();

PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec ( key );
PrivateKey ff = kf.generatePrivate (keysp);


// read the certificates from the files and load them into the key store:

Collection  col_crt1 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert1.pem"));
Collection  col_crt2 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert2.pem"));

Certificate crt1 = (Certificate) col_crt1.iterator().next();
Certificate crt2 = (Certificate) col_crt2.iterator().next();
Certificate[] chain = new Certificate[] { crt1, crt2 };

String alias1 = ((X509Certificate) crt1).getSubjectX500Principal().getName();
String alias2 = ((X509Certificate) crt2).getSubjectX500Principal().getName();

ks.setCertificateEntry(alias1, crt1);
ks.setCertificateEntry(alias2, crt2);

// store the private key
ks.setKeyEntry(defaultalias, ff, keypass.toCharArray(), chain );

// save the key store to a file         
ks.store(new FileOutputStream ( "mykeystore" ),keypass.toCharArray());

(необязательно) Проверьте содержимое вашего нового хранилища ключей:

$ keytool -list -keystore mykeystore -storepass password

Тип хранилища ключей: JKS Поставщик хранилища ключей: SUN

Ваше хранилище ключей содержит 3 записи:

  • cn = ..., ou = ..., o = .., 2 сентября 2014 г., trustedCertEntry, отпечаток сертификата (SHA1): 2C: B8: ...

  • importkey, 2 сентября 2014 г., PrivateKeyEntry, отпечаток сертификата (SHA1): 9C: B0: ...

  • cn = ..., o = ...., 2 сентября 2014 г., trustedCertEntry, отпечаток сертификата (SHA1): 83:63: ...

(необязательно) Протестируйте свои сертификаты и закрытый ключ из нового хранилища ключей на сервере SSL: (Вы можете включить отладку в качестве параметра виртуальной машины: -Djavax.net.debug = all)

        char[] passw = "password".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS", "SUN");
        ks.load(new FileInputStream ( "mykeystore" ), passw );

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

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        TrustManager[] tm = tmf.getTrustManagers();

        SSLContext sclx = SSLContext.getInstance("TLS");
        sclx.init( kmf.getKeyManagers(), tm, null);

        SSLSocketFactory factory = sclx.getSocketFactory();
        SSLSocket socket = (SSLSocket) factory.createSocket( "192.168.1.111", 443 );
        socket.startHandshake();

        //if no exceptions are thrown in the startHandshake method, then everything is fine..

Наконец, зарегистрируйте свои сертификаты с помощью HttpsURLConnection, если планируете его использовать:

        char[] passw = "password".toCharArray();
        KeyStore ks = KeyStore.getInstance("JKS", "SUN");
        ks.load(new FileInputStream ( "mykeystore" ), passw );

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

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        TrustManager[] tm = tmf.getTrustManagers();

        SSLContext sclx = SSLContext.getInstance("TLS");
        sclx.init( kmf.getKeyManagers(), tm, null);

        HostnameVerifier hv = new HostnameVerifier()
        {
            public boolean verify(String urlHostName, SSLSession session)
            {
                if (!urlHostName.equalsIgnoreCase(session.getPeerHost()))
                {
                    System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
                }
                return true;
            }
        };

        HttpsURLConnection.setDefaultSSLSocketFactory( sclx.getSocketFactory() );
        HttpsURLConnection.setDefaultHostnameVerifier(hv);
Интеркот
источник
Ваш верификатор имени хоста неверен, session.getPeerHost()возвращает не имя в сертификате, а имя, с которым вы связались (т.е. urlHostNameздесь), так что это всегда будет правдой. Ты все trueравно всегда возвращаешься .
Bruno
11

Если вам нужен простой способ загрузки файлов PEM на Java без использования внешних инструментов (opensll, keytool) , вот мой код, который я использую в производстве:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.xml.bind.DatatypeConverter;

public class PEMImporter {

    public static SSLServerSocketFactory createSSLFactory(File privateKeyPem, File certificatePem, String password) throws Exception {
        final SSLContext context = SSLContext.getInstance("TLS");
        final KeyStore keystore = createKeyStore(privateKeyPem, certificatePem, password);
        final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keystore, password.toCharArray());
        final KeyManager[] km = kmf.getKeyManagers();
        context.init(km, null, null);
        return context.getServerSocketFactory();
    }

    /**
     * Create a KeyStore from standard PEM files
     * 
     * @param privateKeyPem the private key PEM file
     * @param certificatePem the certificate(s) PEM file
     * @param the password to set to protect the private key
     */
    public static KeyStore createKeyStore(File privateKeyPem, File certificatePem, final String password)
            throws Exception, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        final X509Certificate[] cert = createCertificates(certificatePem);
        final KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(null);
        // Import private key
        final PrivateKey key = createPrivateKey(privateKeyPem);
        keystore.setKeyEntry(privateKeyPem.getName(), key, password.toCharArray(), cert);
        return keystore;
    }

    private static PrivateKey createPrivateKey(File privateKeyPem) throws Exception {
        final BufferedReader r = new BufferedReader(new FileReader(privateKeyPem));
        String s = r.readLine();
        if (s == null || !s.contains("BEGIN PRIVATE KEY")) {
            r.close();
            throw new IllegalArgumentException("No PRIVATE KEY found");
        }
        final StringBuilder b = new StringBuilder();
        s = "";
        while (s != null) {
            if (s.contains("END PRIVATE KEY")) {
                break;
            }
            b.append(s);
            s = r.readLine();
        }
        r.close();
        final String hexString = b.toString();
        final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
        return generatePrivateKeyFromDER(bytes);
    }

    private static X509Certificate[] createCertificates(File certificatePem) throws Exception {
        final List<X509Certificate> result = new ArrayList<X509Certificate>();
        final BufferedReader r = new BufferedReader(new FileReader(certificatePem));
        String s = r.readLine();
        if (s == null || !s.contains("BEGIN CERTIFICATE")) {
            r.close();
            throw new IllegalArgumentException("No CERTIFICATE found");
        }
        StringBuilder b = new StringBuilder();
        while (s != null) {
            if (s.contains("END CERTIFICATE")) {
                String hexString = b.toString();
                final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
                X509Certificate cert = generateCertificateFromDER(bytes);
                result.add(cert);
                b = new StringBuilder();
            } else {
                if (!s.startsWith("----")) {
                    b.append(s);
                }
            }
            s = r.readLine();
        }
        r.close();

        return result.toArray(new X509Certificate[result.size()]);
    }

    private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
        final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory factory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) factory.generatePrivate(spec);
    }

    private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
        final CertificateFactory factory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
    }

}

Радоваться, веселиться.

BluEOS
источник
Вопрос касался «SSL через Apache MINA», который проще настроить с помощью предоставленной «SSLServerSocketFactory из функции PEMs», см. Mina.apache.org/mina-project/userguide/ch11-ssl-filter/… .
BluEOS
Я пробовал вышеуказанную методологию для загрузки файлов PEM (cert.pem и key.pem), но получил сообщение об ошибке при попытке подключения. Connecteion Ex: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: сбой построения пути PKIX: sun.security.provider.certpath.SunCertPathBuilderException: невозможно найти действительный путь сертификации к запрошенной цели
ugendrang
«Невозможно найти действительный путь сертификации к запрошенной цели» часто возникает при использовании самоподписанного сертификата или при отсутствии промежуточного сертификата ...
BluEOS
8

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

  1. Перейдите в свой любимый браузер и загрузите основной сертификат с защищенного веб-сайта.
  2. Выполните две следующие строки кода:

    $ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
    $ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
    
  3. При выполнении в среде Java SE добавьте следующие параметры:

    $ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
    
  4. Или добавьте в код java следующее:

    System.setProperty("javax.net.ssl.trustStore", "GlobalSignRootCA.jks");
    System.setProperty("javax.net.ssl.trustStorePassword","trustStorePassword");
    

Другой вариант для шага 2 - просто использовать keytoolкоманду. Ниже приведен пример цепочки сертификатов:

$ keytool -import -file org.eu.crt -alias orgcrt -keystore globalsignrs.jks
$ keytool -import -file GlobalSignOrganizationValidationCA-SHA256-G2.crt -alias globalsignorgvalca -keystore globalsignrs.jks
$ keytool -import -file GlobalSignRootCA.crt -alias globalsignrootca -keystore globalsignrs.jks
Марко
источник
7

Я использовал Keystore Explorer

  1. Откройте JKS с закрытым ключом
  2. Изучите подписанный PEM из CA
  3. Импортировать ключ
  4. Сохранить JKS
Лев Литерак
источник
3
Keystore Explorer потрясающий и очень универсальный. Экономит одно время от нескольких бессмысленных минут на терминале.
TheRealChx101
4

Существует также инструмент с графическим интерфейсом, который позволяет визуально создавать JKS и импортировать сертификаты.

http://portecle.sourceforge.net/

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

Вадим
источник
1
проводник хранилища ключей - это современная версия portecle. нет никакой разницы между их меню и функциональностью.
Setmax,
0

Получил из интернета. Он очень хорошо работает для файлов pem, содержащих несколько записей.

#!/bin/bash
pemToJks()
{
        # number of certs in the PEM file
        pemCerts=$1
        certPass=$2
        newCert=$(basename "$pemCerts")
        newCert="${newCert%%.*}"
        newCert="${newCert}"".JKS"
        ##echo $newCert $pemCerts $certPass
        CERTS=$(grep 'END CERTIFICATE' $pemCerts| wc -l)
        echo $CERTS
        # For every cert in the PEM file, extract it and import into the JKS keystore
        # awk command: step 1, if line is in the desired cert, print the line
        #              step 2, increment counter when last line of cert is found
        for N in $(seq 0 $(($CERTS - 1))); do
          ALIAS="${pemCerts%.*}-$N"
          cat $pemCerts |
                awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
                $KEYTOOLCMD -noprompt -import -trustcacerts \
                                -alias $ALIAS -keystore $newCert -storepass $certPass
        done
}
pemToJks <pem to import> <pass for new jks>
Джон Смит
источник