Importar PEM en Java Key Store

144

Estoy tratando de conectarme a un servidor SSL que requiere que me autentique. Para usar SSL sobre Apache MINA, necesito un archivo JKS adecuado. Sin embargo, solo me han dado un archivo .PEM.

¿Cómo haría para crear un archivo JKS a partir de un archivo PEM?

jwoolard
fuente
1
Este enlace puede ser útil: http://www.agentbob.info/agentbob/79-AB.html
Laurent K

Respuestas:

235

Primero, convierta su certificado en un formato DER:

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

Y después, impórtelo en el almacén de claves:

keytool -import -alias your-alias -keystore cacerts -file certificate.der
Anthony O.
fuente
77
No funciona si el archivo .pem contiene más de un certificado.
MarioVilas
14
Tengo un solo certificado .pem y esto no funciona. 1795: error: 0906D06C: rutinas PEM: PEM_read_bio: sin línea de inicio: / usr / src / secure / lib / libcrypto /../../../ crypto / openssl / crypto / pem / pem_lib.c: 648: esperando : CERTIFICADO
Brian Knoblauch
44
Encontré la solución. Pre-pendular los certificados raíz e intermedios al .pem, luego convertir.
Brian Knoblauch
1
@Anthony este comando solo le dice cómo importar un PEM a JKS. Puede ser una buena idea agregar un comando para exportar JKS desde la tienda.
Vishal Biyani
2
Si tengo varios certificados en el .pem, ¿cómo importo a un almacén de claves Java?
Erick
55

Si solo desea importar un certificado en formato PEM en un almacén de claves, keytool hará el trabajo:

keytool -import -alias *alias* -keystore cacerts -file *cert.pem*
Borrar
fuente
11
Si voy así me sale un error: error de keytool: java.lang. Excepción: no se
ingresa
1
@frandevel, este error puede ser causado por el archivo de entrada PEM que tiene un encabezado por encima del delimitador --- BEGIN o que tiene múltiples PEM en un archivo o en ambos. Elimine todos los datos extraños y alimente en cada PEM de uno en uno o use mi herramienta, como se detalla en mi respuesta.
Alastair McCormack
Gracias @Fuzzyfelt, echaré un vistazo
frandevel
1
Mismo problema y el archivo .PEM está limpio, con todos los encabezados apropiados.
Brian Knoblauch
17

Desarrollé http://code.google.com/p/java-keyutil/ que importa certificados PEM directamente en un almacén de claves Java. Su propósito principal es importar paquetes de certificados del sistema operativo PEM de varias partes, como ca-bundle.crt. Estos a menudo incluyen encabezados que keytool no puede manejar

</self promotion>
Alastair McCormack
fuente
44
No es un mal proyecto de juguete, pero keytoolya hace todo esto por ti (y más). (Por cierto, debe cerrar su FileOutputStream, y cerrar sus transmisiones de E / S en finallycaso de que ocurra una excepción.)
Bruno
8
Hola Bruno, gracias por los consejos. El caso de uso real es importar todas las entradas de /etc/pki/tls/certs/ca-bundle.crt (RHEL / CentOS) de una sola vez. AFAIK, keytool solo importará la primera entrada. He visto a varias personas hacer esto de manera diferente, pero generalmente implica invocar keytool varias veces para cada certificado. Ubuntu tiene un script de actualización que hace exactamente esto, excepto que Ubuntu almacena sus certificados en un directorio. Agregaré soporte para directorios en un futuro cercano. Gracias nuevamente por revisar el código.
Alastair McCormack
14

En mi caso, tenía un archivo pem que contenía dos certificados y una clave privada encriptada para ser utilizada en la autenticación SSL mutua. Entonces mi archivo pem se veía así:

-----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-----

Aquí esta lo que hice

Divida el archivo en tres archivos separados, para que cada uno contenga solo una entrada, comenzando ---BEGIN..y terminando con ---END..líneas. Asumamos ahora tenemos tres archivos: cert1.pem, cert2.pem, y pkey.pem.

Convierta pkey.pemal formato DER usando openssl y la siguiente sintaxis:

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

Tenga en cuenta que si la clave privada está encriptada, debe proporcionar una contraseña (obtenerla del proveedor del archivo pem original) para convertirla al formato DER, opensslle pedirá una contraseña como esta: "ingrese una frase de contraseña para pkey.pem:".

Si la conversión es exitosa, obtendrá un nuevo archivo llamado pkey.der.

Cree un nuevo almacén de claves de Java e importe la clave privada y los certificados:

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());

(opcional) Verifique el contenido de su nuevo almacén de claves:

$ keytool -list -keystore mykeystore -storepass password

Tipo de almacén de claves: JKS Proveedor de almacén de claves: SUN

Su almacén de claves contiene 3 entradas:

  • cn = ..., ou = ..., o = .., 2 de septiembre de 2014, TrustedCertEntry, huella digital del certificado (SHA1): 2C: B8: ...

  • importkey, 2 de septiembre de 2014, PrivateKeyEntry, Certificado de huella digital (SHA1): 9C: B0: ...

  • cn = ..., o = ...., 2 de septiembre de 2014, TrustedCertEntry, huella digital de certificado (SHA1): 83:63: ...

(opcional) Pruebe sus certificados y clave privada de su nuevo almacén de claves en su servidor SSL: (Puede habilitar la depuración como una opción de VM: -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..

Finalmente, registre sus certificados con HttpsURLConnection si planea usarlo:

        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);
Interkot
fuente
Su verificador de nombre de host es incorrecto, session.getPeerHost()no devuelve el nombre en el certificado, sino el nombre con el que se conectó (es decir, urlHostNameaquí), por lo que eso siempre será cierto. Siempre regresas de truetodos modos.
Bruno
9

Si necesita una manera fácil de cargar archivos PEM en Java sin tener que lidiar con herramientas externas (opensll, keytool) , aquí está mi código que uso en producción:

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));
    }

}

Que te diviertas.

BluEOS
fuente
La pregunta era sobre "SSL sobre Apache MINA", que es más fácil de configurar con la función "SSLServerSocketFactory de PEM" proporcionada, consulte mina.apache.org/mina-project/userguide/ch11-ssl-filter/… .
BluEOS
8

Siempre estoy olvidando cómo hacer esto porque es algo que solo hago de vez en cuando, esta es una posible solución, y simplemente funciona:

  1. Vaya a su navegador favorito y descargue el certificado principal del sitio web seguro.
  2. Ejecute las dos líneas de código siguientes:

    $ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
    $ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
  3. Si se ejecuta en un entorno Java SE, agregue las siguientes opciones:

    $ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
  4. O agregue lo siguiente al código de Java:

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

La otra opción para el paso 2 es simplemente usar el keytoolcomando. A continuación se muestra un ejemplo con una cadena de certificados:

$ 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
Marco
fuente
7

Utilicé Keystore Explorer

  1. Abrir JKS con una clave privada
  2. Examinar PEM firmado de CA
  3. Clave de importación
  4. Guardar JKS
Leos Literak
fuente
3
Keystore Explorer es impresionante y muy versátil. Ahorra una vez de pasar unos minutos sin sentido en la terminal.
TheRealChx101
3

También hay una herramienta GUI que permite la creación visual de JKS y la importación de certificados.

http://portecle.sourceforge.net/

Portecle es una aplicación GUI fácil de usar para crear, administrar y examinar almacenes de claves, claves, certificados, solicitudes de certificados, listas de revocación de certificados y más.

Vadzim
fuente
1
Key Store Explorer es la versión moderna de Portecle. no hay diferencia entre sus menús y funcionalidades en absoluto.
Setmax
0

Lo obtuve de internet. Funciona bastante bien para archivos pem que contienen múltiples entradas.

#!/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>
John Smith
fuente