为什么 Java / Apache HttpClient 使用与我的浏览器不同(且已过期)的 X509 根证书?
Why does Java / Apache HttpClient use a different (and expired) X509 root certificate than my browser?
换一个Stack Exchange project, I'm downloading various links from all over the internet with a Java program that uses Apache HttpClient。它会检查过期的 SSL 证书,这可能是图像不再可见的原因之一。
我注意到有时 Java 程序认为 SSL 证书已过期,而我的浏览器却认为它没有。示例如下 URL:https://www.dewharvest.com/uploads/3/4/5/4/34546214/oak-from-seed_orig.jpg
我的浏览器(macOS 上的 Firefox)认为它是有效的:
- Certificate chain
- dewharvest.com(结束实体)
- Sectigo RSA Domain Validation Secure Server CA(中级)
- USERTrust RSA Certification Authority(根)
但是当我 运行 下面的剥离 Java 程序时,这就是我得到的:
Name: CN=www.dewharvest.com
Not after: Tue Feb 09 00:59:59 CET 2021
Not before: Fri Feb 07 01:00:00 CET 2020
Serial #: 6f698f95c0f23b77fb181bf76141b080
Thumbprint: 580d0025564d9603ede46f6aba03dfc5045d207a
Name: CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
Not after: Sat May 30 12:48:38 CEST 2020
Not before: Tue May 30 12:48:38 CEST 2000
Serial #: 13ea28705bf4eced0c36630980614336
Thumbprint: eab040689a0d805b5d6fd654fc168cff00b78be3
Name: CN=Sectigo RSA Domain Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB
Not after: Wed Jan 01 00:59:59 CET 2031
Not before: Fri Nov 02 01:00:00 CET 2018
Serial #: 7d5b5126b476ba11db74160bbc530da7
Thumbprint: 33e4e80807204c2b6182a3a14b591acd25b5f0db
顺序不同(终端实体;根;中间),您可以看到终端实体和中间实体的指纹和序列号与我的浏览器匹配。根证书不同,主要问题是这个证书有效期到去年五月。这里发生了什么事?我检查了我的 Java 密钥库(使用 keytool -list
),它包含与我的浏览器相同的根证书,但不是上面过期的根证书:
usertrustrsaca [jdk], Aug 25, 2016, trustedCertEntry,
Certificate fingerprint (SHA1): 2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E
Java程序在这里;我正在使用 version 4.5.11 of Apache HttpClient 但更新到 4.5.13 没有帮助。
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.xml.bind.DatatypeConverter;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class Test {
public static void main(String[] args) throws Exception {
X509TrustManager x509TrustManager = getDefaultX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
sslContext.init(new KeyManager[0], new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
x509TrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509Certificate certificate : chain) {
System.out.println("Name: " + certificate.getSubjectX500Principal().getName());
System.out.println("Not after: " + certificate.getNotAfter());
System.out.println("Not before: " + certificate.getNotBefore());
System.out.println("Serial #: " + certificate.getSerialNumber().toString(16));
System.out.println("Thumbprint: " + DatatypeConverter
.printHexBinary(messageDigest.digest(certificate.getEncoded())).toLowerCase());
System.out.println();
}
x509TrustManager.checkServerTrusted(chain, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return x509TrustManager.getAcceptedIssuers();
}
} }, new SecureRandom());
final CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
client.execute(new HttpGet("https://www.dewharvest.com/"));
}
private static X509TrustManager getDefaultX509TrustManager() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager)
return (X509TrustManager)trustManager;
}
throw new Exception("No X509 trust manager found.");
}
}
Certigo 存在特定问题,请参阅:
如果我理解正确的话,网站的所有者应该已经从他们的证书链中删除了旧的根证书,因为新的根证书默认安装在浏览器中并且也在 Java 的信任库中作为你看到了。
SSLMate 有一个很好的在线测试器:https://whatsmychaincert.com/?www.dewharvest.com
浏览器与 Java 的 TrustManager 之间的区别在于,在 Java 中,如果证书链中的证书过期,则不会检查替代证书(在本地信任库中)没有了。
我记得在 Java 6 或 7 中,有一个不同的问题,即只在最终证书上检查到期日期,而不在中间证书上检查,但我不记得它们的确切时间修复了。
换一个Stack Exchange project, I'm downloading various links from all over the internet with a Java program that uses Apache HttpClient。它会检查过期的 SSL 证书,这可能是图像不再可见的原因之一。
我注意到有时 Java 程序认为 SSL 证书已过期,而我的浏览器却认为它没有。示例如下 URL:https://www.dewharvest.com/uploads/3/4/5/4/34546214/oak-from-seed_orig.jpg
我的浏览器(macOS 上的 Firefox)认为它是有效的:
- Certificate chain
- dewharvest.com(结束实体)
- Sectigo RSA Domain Validation Secure Server CA(中级)
- USERTrust RSA Certification Authority(根)
但是当我 运行 下面的剥离 Java 程序时,这就是我得到的:
Name: CN=www.dewharvest.com
Not after: Tue Feb 09 00:59:59 CET 2021
Not before: Fri Feb 07 01:00:00 CET 2020
Serial #: 6f698f95c0f23b77fb181bf76141b080
Thumbprint: 580d0025564d9603ede46f6aba03dfc5045d207a
Name: CN=USERTrust RSA Certification Authority,O=The USERTRUST Network,L=Jersey City,ST=New Jersey,C=US
Not after: Sat May 30 12:48:38 CEST 2020
Not before: Tue May 30 12:48:38 CEST 2000
Serial #: 13ea28705bf4eced0c36630980614336
Thumbprint: eab040689a0d805b5d6fd654fc168cff00b78be3
Name: CN=Sectigo RSA Domain Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB
Not after: Wed Jan 01 00:59:59 CET 2031
Not before: Fri Nov 02 01:00:00 CET 2018
Serial #: 7d5b5126b476ba11db74160bbc530da7
Thumbprint: 33e4e80807204c2b6182a3a14b591acd25b5f0db
顺序不同(终端实体;根;中间),您可以看到终端实体和中间实体的指纹和序列号与我的浏览器匹配。根证书不同,主要问题是这个证书有效期到去年五月。这里发生了什么事?我检查了我的 Java 密钥库(使用 keytool -list
),它包含与我的浏览器相同的根证书,但不是上面过期的根证书:
usertrustrsaca [jdk], Aug 25, 2016, trustedCertEntry,
Certificate fingerprint (SHA1): 2B:8F:1B:57:33:0D:BB:A2:D0:7A:6C:51:F7:0E:E9:0D:DA:B9:AD:8E
Java程序在这里;我正在使用 version 4.5.11 of Apache HttpClient 但更新到 4.5.13 没有帮助。
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.xml.bind.DatatypeConverter;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class Test {
public static void main(String[] args) throws Exception {
X509TrustManager x509TrustManager = getDefaultX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
final MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
sslContext.init(new KeyManager[0], new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
x509TrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (X509Certificate certificate : chain) {
System.out.println("Name: " + certificate.getSubjectX500Principal().getName());
System.out.println("Not after: " + certificate.getNotAfter());
System.out.println("Not before: " + certificate.getNotBefore());
System.out.println("Serial #: " + certificate.getSerialNumber().toString(16));
System.out.println("Thumbprint: " + DatatypeConverter
.printHexBinary(messageDigest.digest(certificate.getEncoded())).toLowerCase());
System.out.println();
}
x509TrustManager.checkServerTrusted(chain, authType);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return x509TrustManager.getAcceptedIssuers();
}
} }, new SecureRandom());
final CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
client.execute(new HttpGet("https://www.dewharvest.com/"));
}
private static X509TrustManager getDefaultX509TrustManager() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore)null);
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager)
return (X509TrustManager)trustManager;
}
throw new Exception("No X509 trust manager found.");
}
}
Certigo 存在特定问题,请参阅:
如果我理解正确的话,网站的所有者应该已经从他们的证书链中删除了旧的根证书,因为新的根证书默认安装在浏览器中并且也在 Java 的信任库中作为你看到了。
SSLMate 有一个很好的在线测试器:https://whatsmychaincert.com/?www.dewharvest.com
浏览器与 Java 的 TrustManager 之间的区别在于,在 Java 中,如果证书链中的证书过期,则不会检查替代证书(在本地信任库中)没有了。
我记得在 Java 6 或 7 中,有一个不同的问题,即只在最终证书上检查到期日期,而不在中间证书上检查,但我不记得它们的确切时间修复了。