繁体   English   中英

如何将 SSL 证书添加到 okHttp 双向 TLS 连接?

[英]how to add SSL certificates to okHttp mutual TLS connection?

我已经有一个 .pem 和一个 .key 文件,我不想在本地安装/导入它,只需告诉我的客户使用它们,这可能吗? 它不是自签名证书

基本上,在我的 curl 我做这样的事情:

curl --key mykey.key --cert mycert.pem https://someurl.com/my-endpoint

我已经检查了这个答案

如何在 Retrofit 中使用 ssl 证书发出 https 请求

还有这个https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java (但可能没有意义,因为我没有得到类型的对象我需要)

基本上我有我的 okHttpClient

val okHttpClient = OkHttpClient.Builder()
    .sslSocketFactory(?, ?) //here I tried to call sslSocketFactory, trustManager following the example from the CustomTrust.java
    .build()

任何解决方案的想法?

也检查了此文档,但 ssl 部分没有完成,也没有在示例中完成

https://square.github.io/okhttp/https/#customizing-trusted-certificates-kt-java

所以我尝试这样做(基于 okhttp 示例)

private fun trustedCertificatesInputStream(): InputStream {
        val comodoRsaCertificationAuthority = (""
            + "-----BEGIN CERTIFICATE-----\n" +
            "-----END CERTIFICATE-----")
        return Buffer()
            .writeUtf8(comodoRsaCertificationAuthority)
            .inputStream()
    }


    val loggingInterceptor = HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    }


    fun createClient() : OkHttpClient {

        val trustManager: X509TrustManager
        val sslSocketFactory: SSLSocketFactory
        try {
            trustManager = trustManagerForCertificates(trustedCertificatesInputStream())
            val sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, arrayOf<TrustManager>(trustManager), null)
            sslSocketFactory = sslContext.socketFactory



        } catch (e: GeneralSecurityException) {
            throw RuntimeException(e)
        }
        return OkHttpClient.Builder()
            .sslSocketFactory(sslSocketFactory, trustManager)
            .connectTimeout(45, TimeUnit.SECONDS)
            .readTimeout(45, TimeUnit.SECONDS)
            .protocols(listOf(Protocol.HTTP_1_1))
            .addInterceptor(loggingInterceptor)
            .build()
    }


    @Throws(GeneralSecurityException::class)
    private fun trustManagerForCertificates(input: InputStream): X509TrustManager {
        val certificateFactory: CertificateFactory = CertificateFactory.getInstance("X.509")
        val certificates: Collection<Certificate?> = certificateFactory.generateCertificates(input)
        val password = "password".toCharArray() // Any password will work.
        val keyStore = newEmptyKeyStore(password)

        for ((index, certificate) in certificates.withIndex()) {
            val certificateAlias = index.toString()
            keyStore.setCertificateEntry(certificateAlias, certificate)
        }
        // Use it to build an X509 trust manager.
        val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        keyManagerFactory.init(keyStore, password)

        val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustManagerFactory.init(keyStore)

        val trustManagers: Array<TrustManager> = trustManagerFactory.getTrustManagers()
        return trustManagers[0]!! as X509TrustManager
    }

    @Throws(GeneralSecurityException::class)
    private fun newEmptyKeyStore(password: CharArray): KeyStore {
        return try {
            val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
            val inputStream: InputStream? = null // By convention, 'null' creates an empty key store.
            keyStore.load(inputStream, password)
            keyStore
        } catch (e: IOException) {
            throw AssertionError(e)
        }

    }

我得到一个错误

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

搜索错误似乎我应该在本地安装 SSL,我应该避免这样做,因为我无法在服务器中以这种方式安装它,有什么办法可以使它工作?

我想您想配置 TLS 相互身份验证,这就是他们的关键吗?

看看okhttp-tls ,它具有用于将证书和私钥转换为相应 Java 对象的 API。

查看您的curl命令和java代码,我发现它们的配置不同。 让我们先看看你的 curl 命令并分析它:

你的 curl 命令:

curl --key mykey.key --cert mycert.pem https://someurl.com/my-endpoint

curl 选项解释:

  • 密钥 => 您的客户端私钥
  • cert => 您的客户端证书链
  • cacert => 可信服务器证书

您的cacert选项为空,因此如果您的 curl 通过,则意味着它与基于 curl 中可用的default trusted certificates的服务器证书匹配。 curl 中的默认可信证书可能与 java 中的默认可信证书不同,因此可能导致不同的行为。 我建议将服务器证书添加到 curl 命令和 java 代码片段。

根据您的 curl 命令,我们可以将选项转换为 java:

从 curl 到 java 的映射

  • 密钥 => 密钥管理器
  • 证书 => 密钥管理器
  • cacert => 信任管理器

默认的 java 类对解析 pem 格式的私钥的支持有限。 据我所知,它只能解析未加密的私钥。 我可以推荐 Bouncy Castle 来轻松解析加密的 pem 格式的私钥。 下面的示例假设您有一个未加密的私钥。

选项1

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class App {

    public static void main(String[] args) throws Exception {
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

        InputStream trustedCertificateAsInputStream = Files.newInputStream(Paths.get("/path/to/server-certificate.pem"), StandardOpenOption.READ);
        Certificate trustedCertificate = certificateFactory.generateCertificate(trustedCertificateAsInputStream);
        KeyStore trustStore = createEmptyKeyStore("secret".toCharArray());
        trustStore.setCertificateEntry("server-certificate", trustedCertificate);

        String privateKeyContent = new String(Files.readAllBytes(Paths.get("/path/to/mykey.key")), Charset.defaultCharset())
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END PRIVATE KEY-----", "");

        byte[] privateKeyAsBytes = Base64.getDecoder().decode(privateKeyContent);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyAsBytes);

        InputStream certificateChainAsInputStream = Files.newInputStream(Paths.get("/path/to/mycert.pem"), StandardOpenOption.READ);
        Certificate certificateChain = certificateFactory.generateCertificate(certificateChainAsInputStream);

        KeyStore identityStore = createEmptyKeyStore("secret".toCharArray());
        identityStore.setKeyEntry("client", keyFactory.generatePrivate(keySpec), "secret".toCharArray(), new Certificate[]{certificateChain});

        trustedCertificateAsInputStream.close();
        certificateChainAsInputStream.close();

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(trustStore);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(identityStore, "secret".toCharArray());
        KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagers, trustManagers, null);

        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        OkHttpClient okHttpClient = OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory, trustManagers[0])
                .build();
    }

    public static KeyStore createEmptyKeyStore(char[] keyStorePassword) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, keyStorePassword);
        return keyStore;
    }
}

代码有点冗长,但它应该可以为您解决问题。

选项 2

如果您更喜欢不那么冗长的替代方案,您可以尝试以下代码片段:

X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(Paths.get("/path/to/mycert.pem"), Paths.get("/path/to/mycert.pem"));
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(Paths.get("/path/to/server-certificate.pem"));

SSLFactory sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();
X509ExtendedtrustManager trustManager = sslFactory.getTrustManager().orElseThrow();

OkHttpClient okHttpClient = OkHttpClient.Builder()
          .sslSocketFactory(sslSocketFactory, trustManager)
          .build();

上面的库由我维护,你可以在这里找到它: GitHub - SSLContext Kickstart

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM