证书杂谈

证书杂谈

密钥对和签名概念

不对称加密方式要求提供一个密钥对,既私钥(privateKey)公钥(publicKey).

在tls中,密钥对要求有第三方ca的签名认证,因此认证的提供方式通常为Certificate(cert.pem)Key(key.pem)

  • Certificate: Certificate中包含公钥和签名认证, 一般还有出现证书链,即可以提供多个Certificate.
    例如java.security.KeyStore.setKeyEntry(java.lang.String, java.security.Key, char[], java.security.cert.Certificate[]),可以看到可以提供一个Certificate数组.
  • Key: Key就很简单了,就是上面的私钥(privateKey)

PEM是何方神圣

pem格式比较常用,通常是base64编码的文本文件. pem并不是指私钥或者公钥什么的,它是一种文本格式,可以将公钥、私钥、证书等等base64编码后,就变成了pem文件.


JAVA中的KeyStore和KeyManager

在JAVA中,KeyStore就类似于pem一样,私钥公钥签名都能存, 通常以KeyStore的形式存储Certificate(cert.pem)Key(key.pem),还可以设置一个密码来加密这个KeyStore, 读取时要求提供密码.

可以想见,KeyStorecert.pem/key.pem是可以相互转换的. 参考

PEM -> KeyStore

/**
 * 引用 {@link com.github.dockerjava.core.util.CertificateUtils}
 */
public class PEM2KeyStore {
    public static KeyStore createKeyStore(final String keypem, final String certpem) throws NoSuchAlgorithmException,
            InvalidKeySpecException, IOException, CertificateException, KeyStoreException {
        PrivateKey privateKey = loadPrivateKey(keypem);
        requireNonNull(privateKey);
        List<Certificate> privateCertificates = loadCertificates(certpem);

        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null);

        keyStore.setKeyEntry("docker",
                privateKey,
                "docker".toCharArray(),
                privateCertificates.toArray(new Certificate[privateCertificates.size()])
        );

        return keyStore;
    }

    /**
     * Return KeyPair from "key.pem"
     */
    @CheckForNull
    public static PrivateKey loadPrivateKey(final String keypem) throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        try (StringReader certReader = new StringReader(keypem);
             BufferedReader reader = new BufferedReader(certReader)) {
            return loadPrivateKey(reader);
        }
    }

    /**
     * Return private key ("key.pem") from Reader
     */
    @CheckForNull
    public static PrivateKey loadPrivateKey(final Reader reader) throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        try (PEMParser pemParser = new PEMParser(reader)) {
            Object readObject = pemParser.readObject();
            while (readObject != null) {
                PrivateKeyInfo privateKeyInfo = getPrivateKeyInfoOrNull(readObject);
                if (privateKeyInfo != null) {
                    return new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo);
                }
                readObject = pemParser.readObject();
            }
        }

        return null;
    }

    /**
     * from "cert.pem" String
     */
    public static List<Certificate> loadCertificates(final String certpem) throws IOException,
            CertificateException {
        final StringReader certReader = new StringReader(certpem);
        try (BufferedReader reader = new BufferedReader(certReader)) {
            return loadCertificates(reader);
        }
    }

    /**
     * "cert.pem" from reader
     */
    public static List<Certificate> loadCertificates(final Reader reader) throws IOException,
            CertificateException {
        try (PEMParser pemParser = new PEMParser(reader)) {
            List<Certificate> certificates = new ArrayList<>();

            JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter()
                    .setProvider(BouncyCastleProvider.PROVIDER_NAME);
            Object certObj;

            while ((certObj = pemParser.readObject()) != null) {
                if (certObj instanceof X509CertificateHolder) {
                    X509CertificateHolder certificateHolder = (X509CertificateHolder) certObj;
                    certificates.add(certificateConverter.getCertificate(certificateHolder));
                }
            }

            return certificates;
        }
    }
    
    
}

KeyStore -> pem

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.core.LocalDirectorySSLConfig;
import com.github.dockerjava.core.util.CertificateUtils;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;


public class KeyStore2PEM {
    public static void KeyStore2Pem(KeyStore keyStore, File outKeyPemFile, File outCertPemFile) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, IOException {
        PrivateKey privateKey = (PrivateKey) keyStore.getKey("docker", "docker".toCharArray());
        Certificate[] certificateChain = keyStore.getCertificateChain("docker");

        // 写入私钥文件, key.pem文件
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(outKeyPemFile))) {
            pemWriter.writeObject(privateKey);
        }

        // 写入证书文件, cert.pem
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(outCertPemFile))) {
            for (Certificate certificate : certificateChain) {
                pemWriter.writeObject(certificate);
            }
        }
    }
    
}
        


JAVA中的TrustStore和TrustManager

在JAVA中,密钥存储还分为两大类: trustStore和KeyStore. 参考
trustStore 的目的 是验证凭据 ,keyStore 的目的 是提供凭据.
trustStore 用于TrustManager, keyStore 用于KeyManager, in Java

First and major difference between trustStore and keyStore is that trustStore is used by TrustManager and keyStore is used by KeyManager class in Java. KeyManager and TrustManager performs different job in Java, TrustManager determines whether remote connection should be trusted or not i.e. whether remote party is who it claims to and KeyManager decides which authentication credentials should be sent to the remote host for authentication during SSL handshake. if you are an SSL Server you will use private key during key exchange algorithm and send certificates corresponding to your public keys to client, this certificate is acquired from keyStore. On SSL client side, if its written in Java, it will use certificates stored in trustStore to verify identity of Server.

在Java发起的https请求中,一般都要求提供trustManager来验证服务器的证书,也就是说,trustStore可以看作ca.pem的等价,是中心证书.
作为客户端在一般的https请求中是不需要提供keyManager的, 因为https是单向tls认证,只需要客户端验证服务端,而不需要服务端认证客户端.
如果启用了双向tls认证(Docker的tls verify就是这样的),那么在我们的程序发起请求时,也需要提供KeyManager,作为服务端认证客户端的认证方式.

题外话,在一些内部的调用中,使用自签名证书,往往需要忽略tls证书认证,这时候就是通过提供空的trustManager来实现忽略证书认证.

同样的, TrustStoreca.pem也是可以相互转换的

PEM -> TrustStore

/**
 * 引用 {@link com.github.dockerjava.core.util.CertificateUtils}
 */
public class PEM2TrustStore {

    /**
     * "ca.pem" from String
     */
    public static KeyStore createTrustStore(String capem) throws IOException, CertificateException,
            KeyStoreException, NoSuchAlgorithmException {
        try (Reader certReader = new StringReader(capem)) {
            return createTrustStore(certReader);
        }
    }

    /**
     * "ca.pem" from Reader
     */
    public static KeyStore createTrustStore(final Reader certReader) throws IOException, CertificateException,
            KeyStoreException, NoSuchAlgorithmException {
        try (PEMParser pemParser = new PEMParser(certReader)) {

            KeyStore trustStore = KeyStore.getInstance("JKS");
            trustStore.load(null);

            int index = 1;
            Object pemCert;

            while ((pemCert = pemParser.readObject()) != null) {
                Certificate caCertificate = new JcaX509CertificateConverter()
                        .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                        .getCertificate((X509CertificateHolder) pemCert);
                trustStore.setCertificateEntry("ca-" + index, caCertificate);
                index++;
            }

            return trustStore;
        }
    }
}

TrustStore -> PEM

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.DockerClientException;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.core.LocalDirectorySSLConfig;
import com.github.dockerjava.core.util.CertificateUtils;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;


public class TrustStore2PEM {
    public static void KeyStore2Pem(KeyStore trustStore, File outCAPemFile) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, IOException {
        Certificate[] certificateChain = trustStore.getCertificateChain("docker");

        // 写入证书文件, ca.pem
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(outCAPemFile))) {
            for (Certificate certificate : certificateChain) {
                pemWriter.writeObject(certificate);
            }
        }
    }
}
posted @ 2024-02-01 14:54  小小记录本  阅读(12)  评论(0编辑  收藏  举报