找不到 SSL 证书的 Java HttpClient 错误,在代码中使用证书作为字符串?

Posted

技术标签:

【中文标题】找不到 SSL 证书的 Java HttpClient 错误,在代码中使用证书作为字符串?【英文标题】:Java HttpClient error for no SSL certificate found, using certificate as String within code? 【发布时间】:2011-06-23 16:28:58 【问题描述】:

我在尝试使用 HttpClient 调用使用自签名证书的 https 站点时有点困惑。我有如下代码,它使我能够拨打电话,但随后我收到javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found 之类的错误我已经从我的网络浏览器下载了证书并且知道我可以将其导入密钥库但我宁愿只是把它进入代码并以这种方式使用它,有没有办法做到这一点?

    HttpClient client = new HttpClient();

    EasySSLProtocolSocketFactory easySSLProtocolSocketFactory = new EasySSLProtocolSocketFactory();
    Protocol https = new Protocol("https", easySSLProtocolSocketFactory,
            443);
    Protocol.registerProtocol("https", https);

    BufferedReader br = null;

    String responseString = "";

    GetMethod method = new GetMethod(path);

    int returnCode = client.executeMethod(method);

【问题讨论】:

你到底为什么要把它放到代码中?当服务器证书过期并获得新证书时会发生什么?不要这样做。 【参考方案1】:

假设您的证书是 PEM 格式。您可以将其嵌入代码中并使用BouncyCastle 的PEMReader 将其转换为X509Certificate 实例。完成后,在内存中创建一个KeyStore 实例并将此 X.509 证书放入其中。然后,使用 KeyStore 作为信任库实例化一个新的 SSLContext,并让您的 HTTP 客户端使用它。

这看起来像这样(没试过,记得关闭阅读器并捕获异常...):

PEMReader pemReader = new PEMReader(new StringReader("----- BEGIN ......");
X509Certificate cert = (X509Certificate) pemReader.readObject();

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("some name", cert);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(
    TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tmf.getTrustManagers(), null);

然后,使用此SSLContext 进行连接。如果您使用的是 4.x 版,您可以使用 Apache HttpClient 的 SSLSocketFactory (如果您使用的是 3.x 版,则可以使用 this)。我建议现在使用 Apache HttpClient 4.x。

【讨论】:

谢谢,这是很好的信息,我不熟悉 SSLSocketFactory 并且正在努力寻找一个例子,我猜它会在我的代码中替换 EasySSLProtocolSocketFactory?非常感谢任何有关使用它的建议 实际上我认为我导入了错误的 SSL 工厂(来自 Java.net.ssl.. 我知道我现在需要 Apache 一个 你能澄清一下你使用的是哪个 SSLContext 包吗,抱歉我有点困惑,因为有超过 1 个 我链接到的SSLSocketFactory 用于 Apache HttpClient 4。您似乎正在使用 Apache HttpClient 3(在这种情况下,您可以使用 jSSLutils 中的那个)。如果您不处理使用 HttpClient 3 的遗留代码,您不妨升级到版本 4,因为不再支持版本 3。 (我说的是javax.net.ssl.SSLContext【参考方案2】:

在Alexander Chzhen 的回答和 HttpClient 4.3 的基础上,我首先创建了一个信任所有人的上下文:

SSLContextBuilder sslctxb = new SSLContextBuilder();

sslctxb.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
                          new TrustSelfSignedStrategy() 
  @Override
  public boolean isTrusted(X509Certificate[] chain,
                           String            authType)
    throws CertificateException 
    return true;
  
);

SSLContext sslctx = sslctxb.build();

然后是客户端生成器:

HttpClientBuilder hcb = HttpClients.custom();

我只设置上下文。我不使用setSSLSocketFactory,因为它会干扰下面的setHostnameVerifier:

hcb.setSslcontext(sslctx);

最后我设置了一个验证所有的主机名验证器:

hcb.setHostnameVerifier(new X509HostnameVerifier() 
  @Override
  public void verify(String host, SSLSocket ssl)
    throws IOException 
  

  @Override
  public void verify(String host, X509Certificate cert)
    throws SSLException 
  

  @Override
  public void verify(String host, String[] cns, String[] subjectAlts)
    throws SSLException 
  

  @Override
  public boolean verify(String hostname, SSLSession session) 
    return true;
  
);

最终构建客户端:

HttpClient c = hcb.build();

【讨论】:

@Mike,这两种解决方案的问题在于您接受了 any 自签名证书(这或多或少与完全禁用证书验证相同)。 【参考方案3】:

如果您只想接受这个单一证书而不是所有自签名证书,那么您应该下载该证书并将pem 文件存储在某处。

现在您可以使用此代码加载pem 文件,使用此证书创建一个新的信任库,并将该信任库用于您的HttpClient

//use java. ... .X509Certificate, not javax. ... .X509Certificate
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

@Test
public void testSslCertificate()
        throws IOException, KeyStoreException, NoSuchAlgorithmException,
               CertificateException, KeyManagementException 

    X509Certificate cert;
    try (FileInputStream pemFileStream = new FileInputStream(newFile("your.pem"))) 
        CertificateFactory certFactory = CertificateFactory.getInstance("X509");
        cert = (X509Certificate) certFactory.generateCertificate(pemFileStream);
    

    //create truststore
    KeyStore trustStore = KeyStore.getInstance("JKS");
    trustStore.load(null); //create an empty trustore

    //add certificate to truststore - you can use a simpler alias
    String alias = cert.getSubjectX500Principal().getName() + "["
            + cert.getSubjectX500Principal().getName().hashCode() + "]";
    trustStore.setCertificateEntry(alias, cert);

    //configure http client
    TrustManagerFactory trustManagerFactory =
       TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trustStore);

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

    HttpClientBuilder httpClientBuilder = HttpClientBuilder.
                                          create().setSslcontext(sslContext);

    try (CloseableHttpClient httpClient = httpClientBuilder.build()) 
        HttpGet httpGetRequest = new HttpGet("https://yourServer");
        try (CloseableHttpResponse httpResponse =
                            httpClient.execute(httpGetRequest)) 
            Assert.assertEquals(200,
                                httpResponse.getStatusLine().getStatusCode());
        
    

【讨论】:

【参考方案4】:

这是使 Apache HttpClient 4.3 接受自签名证书的方法:

HttpClientBuilder cb = HttpClientBuilder.create();
SSLContextBuilder sslcb = new SSLContextBuilder();
sslcb.loadTrustMaterial(KeyStore.getInstance(KeyStore.getDefaultType()),
                        new TrustSelfSignedStrategy());
cb.setSslcontext(sslcb.build());
HttpClient client = cb.build()

现在使用标准执行方法来执行 POST 或 GET 请求:

HttpResponse response = client.execute(...);

提醒:当您信任自签名证书时,您很容易受到攻击。

【讨论】:

当我使用您的代码时出现此异常:“线程中的异常“main”javax.net.ssl.SSLException:java.lang.RuntimeException:意外错误:java.security.InvalidAlgorithmParameterException:trustAnchors 参数必须为非空”【参考方案5】:

在许多情况下,证书固定可能比硬编码特定证书更可取。

是的,您可以将证书硬编码到您的代码中,这样既有效又安全。这是一个完全合理的方法。但是,它确实有一些缺点。一个陷阱是最终该证书将过期,然后您的应用程序将停止工作。此外,如果您想更改您的私钥,您将无法更改。

因此,在许多情况下,使用证书固定更灵活,可能更可取。请参阅here 以获取有关如何实现证书固定的参考。

【讨论】:

以上是关于找不到 SSL 证书的 Java HttpClient 错误,在代码中使用证书作为字符串?的主要内容,如果未能解决你的问题,请参考以下文章

找不到请求目标的有效证书路径 - java

网站绿锁里面显示的ssl证书 在iis服务器证书列表里面找不到,请问这个证书是在哪儿绑定的

重新定义默认 SSL 上下文时找不到 Java 密钥库

SSL 客户端身份验证 - 即使我的客户端证书与“证书颁发机构”中的列表匹配,也找不到合适的证书

在 Java 客户端中接受服务器的自签名 ssl 证书

证书找不到SunCertPathBuilderException