Nhập PEM vào Java Key Store


144

Tôi đang cố gắng kết nối với máy chủ SSL yêu cầu tôi tự xác thực. Để sử dụng SSL trên Apache MINA, tôi cần một tệp JKS phù hợp. Tuy nhiên, tôi chỉ được cung cấp một tệp .PEM.

Làm cách nào để tạo một tệp JKS từ tệp PEM?


1
Có thể liên kết này có thể hữu ích: http://www.agentbob.info/agentbob/79-AB.html
Laurent K

Câu trả lời:


235

Đầu tiên, chuyển đổi chứng chỉ của bạn ở định dạng DER:

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

Và sau đó, nhập nó trong kho khóa:

keytool -import -alias your-alias -keystore cacerts -file certificate.der

7
Không hoạt động nếu tệp .pem chứa nhiều chứng chỉ.
MarioVilas

14
Tôi đã có một chứng chỉ .pem và điều này không hoạt động. 1795: lỗi: 0906D06C: Các thói quen của PEM: PEM_read_bio: không có dòng bắt đầu: / usr / src / safe / lib / libcrypto /../../../ crypto / openssl / crypto / pem / pem_lib.c: 648 : GIẤY CHỨNG NHẬN
Brian Knoblauch

4
Tôi tìm thấy giải pháp. Trước khi chuyển chứng chỉ gốc và chứng chỉ trung gian sang .pem, sau đó chuyển đổi.
Brian Knoblauch

1
@Anthony lệnh này chỉ cho biết cách nhập PEM vào JKS. Có thể là một ý tưởng tốt để thêm một lệnh để xuất JKS từ cửa hàng.
Vishal Biyani

2
Nếu tôi có nhiều chứng chỉ trên .pem, làm cách nào để nhập vào kho khóa Java?
Erick

55

Nếu bạn chỉ muốn nhập chứng chỉ ở định dạng PEM vào kho khóa, keytool sẽ thực hiện công việc:

keytool -import -alias *alias* -keystore cacerts -file *cert.pem*

11
Nếu tôi đi như thế này, tôi gặp lỗi: keytool error: java.lang.Exception: Input không phải là chứng chỉ X.509
frandevel

1
@frandevel, lỗi này có thể do tệp đầu vào PEM có tiêu đề phía trên --- dấu phân cách BEGIN hoặc có nhiều PEM trong một tệp hoặc cả hai. Hoặc xóa tất cả dữ liệu không liên quan và cung cấp dữ liệu trong từng PEM tại một thời điểm hoặc sử dụng công cụ của tôi, như chi tiết trong câu trả lời của tôi.
Alastair McCormack

Cảm ơn @Fuzzyfelt, tôi sẽ xem xét
frandevel

1
Cùng một vấn đề và tệp .PEM sạch, với tất cả các tiêu đề thích hợp.
Brian Knoblauch

17

Tôi đã phát triển http://code.google.com.vn/p/java-keyutil/ để nhập chứng chỉ PEM trực tiếp vào kho khóa Java. Mục đích chính của nó là để nhập một gói chứng chỉ Hệ điều hành PEM nhiều phần, chẳng hạn như ca-bundle.crt. Chúng thường bao gồm các tiêu đề mà keytool không thể xử lý

</self promotion>

4
Không phải là một dự án đồ chơi tồi, nhưng keytoolđã làm tất cả điều này cho bạn (và hơn thế nữa). (Nhân tiện, bạn nên đóng FileOutputStreamvà đóng luồng I / O của mình finally, nếu có ngoại lệ xảy ra.)
Bruno

8
Xin chào Bruno, cảm ơn vì lời khuyên. Trường hợp sử dụng thực tế là nhập tất cả các mục của /etc/pki/tls/certs/ca-bundle.crt (RHEL / CentOS) trong một lần. AFAIK, keytool sẽ chỉ nhập mục đầu tiên. Tôi đã thấy một số người làm điều này khác nhau nhưng nó thường liên quan đến việc gọi keytool nhiều lần cho mỗi chứng chỉ. Ubuntu có một kịch bản cập nhật thực hiện chính xác điều này, ngoại trừ việc Ubuntu lưu trữ certs của nó trong một thư mục. Tôi sẽ thêm hỗ trợ cho các thư mục trong tương lai gần. Cảm ơn một lần nữa để xem lại mã.
Alastair McCormack

14

Trong trường hợp của tôi, tôi đã có một tệp pem chứa hai chứng chỉ và khóa riêng được mã hóa để sử dụng trong xác thực SSL lẫn nhau. Vì vậy, tập tin pem của tôi trông như thế này:

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

Đây là những gì tôi đã làm

Chia tệp thành ba tệp riêng biệt, sao cho mỗi tệp chỉ chứa một mục nhập, bắt đầu bằng ---BEGIN..và kết thúc bằng ---END..các dòng. Cho phép giả bây giờ chúng tôi có ba tác phẩm: cert1.pem, cert2.pem, và pkey.pem.

Chuyển đổi pkey.pemsang định dạng DER bằng openssl và cú pháp sau:

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

Lưu ý rằng nếu khóa riêng được mã hóa, bạn cần cung cấp mật khẩu (lấy từ nhà cung cấp tệp pem gốc) để chuyển đổi sang định dạng DER, opensslsẽ yêu cầu bạn nhập mật khẩu như sau: "nhập cụm mật khẩu cho pkey.pem:".

Nếu chuyển đổi thành công, bạn sẽ nhận được một tệp mới được gọi là pkey.der.

Tạo kho khóa java mới và nhập khóa riêng và chứng chỉ:

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

(tùy chọn) Xác minh nội dung của kho lưu trữ khóa mới của bạn:

$ keytool -list -keystore mykeystore -storepass password

Loại kho khóa: JKS Nhà cung cấp khóa: SUN

Kho khóa của bạn chứa 3 mục:

  • cn = ..., ou = ..., o = .., ngày 2 tháng 9 năm 2014, TrustedCertEntry, Chứng nhận dấu vân tay (SHA1): 2C: B8: ...

  • importkey, ngày 2 tháng 9 năm 2014, PrivateKeyEntry, Chứng chỉ vân tay (SHA1): 9C: B0: ...

  • cn = ..., o = ...., ngày 2 tháng 9 năm 2014, TrustedCertEntry, Chứng nhận dấu vân tay (SHA1): 83:63: ...

(tùy chọn) Kiểm tra chứng chỉ và khóa riêng từ kho lưu trữ khóa mới của bạn với máy chủ SSL của bạn: (Bạn có thể muốn bật gỡ lỗi dưới dạng tùy chọn 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..

Cuối cùng đăng ký chứng chỉ của bạn với HttpsURLCconnectection nếu có kế hoạch sử dụng nó:

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

Trình xác minh tên máy chủ của bạn sai, session.getPeerHost()không trả lại tên trong chứng chỉ, nhưng tên bạn đã kết nối với (tức là urlHostNameở đây), vì vậy điều đó luôn luôn đúng. trueDù sao bạn cũng luôn trở về .
Bruno

9

Nếu bạn cần một cách dễ dàng để tải các tệp PEM trong Java mà không phải xử lý các công cụ bên ngoài (opensll, keytool) , đây là mã tôi sử dụng trong sản xuất:

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

}

Chúc vui vẻ.


Câu hỏi là về "SSL trên Apache MINA", cấu hình đơn giản hơn với "SSLServerSocketFactory được cung cấp từ chức năng PEMs", xem mina.apache.org/mina-project/userguide/ch11-ssl-filter/ .
BluEOS

8

Tôi luôn quên cách làm điều này bởi vì đó là điều mà tôi chỉ làm một lần, đây là một giải pháp khả thi và nó chỉ hoạt động:

  1. Truy cập trình duyệt yêu thích của bạn và tải xuống chứng chỉ chính từ trang web được bảo mật.
  2. Thực hiện hai dòng mã sau:

    $ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
    $ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
  3. Nếu thực thi trong môi trường Java SE, hãy thêm các tùy chọn sau:

    $ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
  4. Hoặc thêm đoạn mã sau vào mã java:

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

Tùy chọn khác cho bước 2 là chỉ sử dụng keytoollệnh. Bellow là một ví dụ với một chuỗi các chứng chỉ:

$ 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

6

Tôi đã sử dụng Keystore Explorer

  1. Mở JKS bằng khóa riêng
  2. Kiểm tra PEM đã ký từ CA
  3. Nhập khóa
  4. Lưu JKS

3
Keystore Explorer là tuyệt vời và rất linh hoạt. Tiết kiệm một lần từ việc dành một vài phút không suy nghĩ về thiết bị đầu cuối.
TheRealChx101

3

Ngoài ra còn có một công cụ GUI cho phép nhập chứng chỉ và nhập JKS trực quan.

http://portecle.sourceforge.net/

Portecle là một ứng dụng GUI thân thiện với người dùng để tạo, quản lý và kiểm tra các kho khóa, khóa, chứng chỉ, yêu cầu chứng chỉ, danh sách thu hồi chứng chỉ và hơn thế nữa.


1
key explorer là phiên bản hiện đại của portecle. không có sự khác biệt giữa các menu và chức năng của họ cả.
Setmax

0

Tôi đã nhận nó từ internet. Nó hoạt động khá tốt cho các tệp pem có chứa nhiều mục.

#!/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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.