Estou tentando conectar-me a um servidor SSL que exige que eu me autentique. Para usar o SSL no Apache MINA, preciso de um arquivo JKS adequado. No entanto, recebi apenas um arquivo .PEM.
Como eu criaria um arquivo JKS a partir de um arquivo PEM?
Estou tentando conectar-me a um servidor SSL que exige que eu me autentique. Para usar o SSL no Apache MINA, preciso de um arquivo JKS adequado. No entanto, recebi apenas um arquivo .PEM.
Como eu criaria um arquivo JKS a partir de um arquivo PEM?
Respostas:
Primeiro, converta seu certificado em um formato DER:
openssl x509 -outform der -in certificate.pem -out certificate.der
E depois, importe-o no keystore:
keytool -import -alias your-alias -keystore cacerts -file certificate.der
Se você deseja importar apenas um certificado no formato PEM para um keystore, o keytool fará o trabalho:
keytool -import -alias *alias* -keystore cacerts -file *cert.pem*
Desenvolvi o http://code.google.com/p/java-keyutil/ que importa certificados PEM diretamente para um keystore Java. Seu principal objetivo é importar um pacote de certificados do sistema operacional PEM com várias partes, como ca-bundle.crt. Isso geralmente inclui cabeçalhos que o keytool não suporta
</self promotion>
keytool
já faz tudo isso para você (e muito mais). (By the way, você deve fechar o seu FileOutputStream
, e fechar o I / O ribeiros no finally
, se uma exceção acontece.)
No meu caso, eu tinha um arquivo pem que continha dois certificados e uma chave privada criptografada para ser usada na autenticação SSL mútua. Então, meu arquivo pem ficou assim:
-----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-----
Divida o arquivo em três arquivos separados, para que cada um contenha apenas uma entrada, começando com ---BEGIN..
e terminando com ---END..
linhas. Vamos supor agora temos três arquivos: cert1.pem
, cert2.pem
, e pkey.pem
.
Converta pkey.pem
no formato DER usando o openssl e a seguinte sintaxe:
openssl pkcs8 -topk8 -nocrypt -in pkey.pem -informações PEM -out pkey.der -outform DER
Observe que, se a chave privada estiver criptografada, você precisará fornecer uma senha (obtenha-a do fornecedor do arquivo pem original) para converter para o formato DER,
openssl
solicitará a senha da seguinte maneira: "insira uma senha para pkey.pem
:".
Se a conversão for bem sucedida, você receberá um novo arquivo chamado pkey.der
.
Crie um novo keystore java e importe a chave privada e os 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 o conteúdo do seu novo armazenamento de chaves:
$ keytool -list -keystore mykeystore -storepass password
Tipo de keystore: provedor de keystore JKS: SUN
Seu keystore contém 3 entradas:
cn = ..., ou = ..., o = .., 2 de setembro de 2014, trustCertEntry, impressão digital do certificado (SHA1): 2C: B8: ...
importkey, 2 de setembro de 2014, PrivateKeyEntry, impressão digital do certificado (SHA1): 9C: B0: ...
cn = ..., o = ...., 2 de setembro de 2014, trustCertEntry, impressão digital do certificado (SHA1): 83:63: ...
(opcional) Teste seus certificados e chave privada do seu novo armazenamento de chaves em seu servidor SSL: (convém ativar a depuração como uma opção 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..
Por fim, registre seus certificados no HttpsURLConnection se planeja usá-lo:
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()
não retorna o nome no certificado, mas o nome com o qual você se conectou (ou seja, o urlHostName
aqui), portanto sempre será verdade. Você está sempre voltando de true
qualquer maneira.
Se você precisar de uma maneira fácil de carregar arquivos PEM em Java sem precisar lidar com ferramentas externas (opensll, keytool) , eis o meu código que uso na produção:
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));
}
}
Diverta-se.
Estou sempre esquecendo como fazer isso, porque é algo que eu faço de vez em quando, essa é uma solução possível e simplesmente funciona:
Execute as duas seguintes linhas de código:
$ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
$ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
Se estiver executando no ambiente Java SE, adicione as seguintes opções:
$ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
Ou adicione o seguinte ao código java:
System.setProperty("javax.net.ssl.trustStore", "GlobalSignRootCA.jks");
System.setProperty("javax.net.ssl.trustStorePassword","trustStorePassword");
A outra opção para a etapa 2 é usar apenas o keytool
comando Abaixo está um exemplo com uma cadeia 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
Eu usei o Keystore Explorer
Há também uma ferramenta GUI que permite a criação visual do JKS e a importação de certificados.
http://portecle.sourceforge.net/
Portecle é um aplicativo GUI amigável para a criação, gerenciamento e análise de keystores, chaves, certificados, solicitações de certificados, listas de revogação de certificados e muito mais.
Eu peguei na internet. Funciona muito bem para arquivos pem que contêm várias 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>