![](/img/trans.png)
[英]java.security.cert.CertificateException: No subject alternative names present;
[英]How to fix the “java.security.cert.CertificateException: No subject alternative names present” error?
我有一個 Java Web 服務客戶端,它通過 HTTPS 使用 Web 服務。
import javax.xml.ws.Service;
@WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...")
public class ISomeService
extends Service
{
public ISomeService() {
super(__getWsdlLocation(), ISOMESERVICE_QNAME);
}
當我連接到服務 URL ( https://AAA.BBB.CCC.DDD:9443/ISomeService
) 時,我收到異常java.security.cert.CertificateException: No subject alternative names present
。
為了修復它,我首先運行openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt
並在文件certs.txt
獲得以下內容:
CONNECTED(00000003)
---
Certificate chain
0 s:/CN=someSubdomain.someorganisation.com
i:/CN=someSubdomain.someorganisation.com
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=someSubdomain.someorganisation.com
issuer=/CN=someSubdomain.someorganisation.com
---
No client certificate CA names sent
---
SSL handshake has read 489 bytes and written 236 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 512 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : RC4-MD5
Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Session-ID-ctx:
Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Key-Arg : None
Start Time: 1382521838
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
AFAIK,現在我需要
-----BEGIN CERTIFICATE-----
和-----END CERTIFICATE-----
之間的certs.txt
部分,AAA.BBB.CCC.DDD
和keytool -importcert -file fileWithModifiedCertificate
導入結果(其中fileWithModifiedCertificate
是操作 1 和 2 的結果)。這是正確的嗎?
如果是這樣,我如何才能使第 1 步中的證書與基於 IP 的地址( AAA.BBB.CCC.DDD
)一起使用?
更新 1 (23.10.2013 15:37 MSK):在對類似問題的回答中,我閱讀了以下內容:
如果您無法控制該服務器,請使用其主機名(前提是現有證書中至少有一個 CN 與該主機名匹配)。
“使用”究竟是什么意思?
我通過使用此處介紹的方法禁用 HTTPS 檢查解決了該問題:
我將以下代碼放入ISomeService
類中:
static {
disableSslVerification();
}
private static void disableSslVerification() {
try
{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
由於我使用https://AAA.BBB.CCC.DDD:9443/ISomeService
僅用於測試目的,這是一個足夠好的解決方案,但不要在生產中這樣做。
請注意,您還可以為“一次一個連接”禁用 SSL,例如:
// don't call disableSslVerification but use its internal code:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn instanceof HttpsURLConnection) {
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
httpsConn.setHostnameVerifier(allHostsValid);
httpsConn.setSSLSocketFactory(sc.getSocketFactory());
}
我有同樣的問題並用這段代碼解決了。 我把這段代碼放在第一次調用我的網絡服務之前。
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){
public boolean verify(String hostname,
javax.net.ssl.SSLSession sslSession) {
return hostname.equals("localhost"); // or return true
}
});
它很簡單而且工作正常。
這里是原始來源。
這是一個老問題,但是從 JDK 1.8.0_144 移動到 jdk 1.8.0_191 時我遇到了同樣的問題
我們在變更日志中發現了一個提示:
我們添加了以下附加系統屬性,這在我們的案例中有助於解決此問題:
-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true
證書身份的驗證是根據客戶端請求執行的。
當您的客戶端使用https://xxx.xxx.xxx.xxx/something
(其中xxx.xxx.xxx.xxx
為 IP 地址)時,會根據此 IP 地址檢查證書身份(理論上,僅使用 IP SAN擴展名)。
如果您的證書沒有 IP SAN,但有 DNS SAN(或者如果沒有 DNS SAN,主題 DN 中有一個通用名稱),您可以通過讓您的客戶端使用具有該主機名的 URL(或主機名如果有多個可能的值,則證書對其有效)。 例如,如果您的證書名稱為www.example.com
,請使用https://www.example.com/something
。
當然,您需要該主機名才能解析為該 IP 地址。
此外,如果有任何 DNS SAN,主題 DN 中的 CN 將被忽略,因此在這種情況下使用與 DNS SAN 之一匹配的名稱。
導入證書:
openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt
這將以 PEM 格式提取證書。openssl x509 -in certs.txt -out certs.der -outform DER
sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>
默認 cacerts 密碼是“changeit”。如果證書是為 FQDN 頒發的,並且您嘗試通過 Java 代碼中的 IP 地址進行連接,那么這可能應該在您的代碼中修復,而不是弄亂證書本身。 更改您的代碼以通過 FQDN 連接。 如果 FQDN 在您的開發機器上無法解析,只需將其添加到您的主機文件,或使用可以解析此 FQDN 的 DNS 服務器配置您的機器。
我通過在證書中添加主題替代名稱以正確的方式解決了這個問題,而不是在代碼中進行任何更改或禁用 SSL,這與此處其他答案建議的不同。 如果您清楚地看到異常顯示“缺少主題替代名稱”,那么正確的方法應該是添加它們
請查看此鏈接以逐步了解。
上述錯誤意味着您的 JKS 文件缺少您嘗試訪問應用程序所需的域。您將需要使用 Open SSL 和關鍵工具添加多個域
echo '[ subject_alt_name ]' >> openssl.cnf
echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/emailAddress=postmaster@example.com' -days 365
將公鑰 (.pem) 文件導出為 PKS12 格式。 這將提示您輸入密碼
openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in self-signed.pem -inkey private.key -name myalias -out keystore.p12
從自簽名 PEM(密鑰庫)創建 a.JKS
keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
從上面的密鑰庫或 JKS 文件生成證書
keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
由於上面的證書是自簽名的並且沒有經過 CA 驗證,所以需要在 Truststore 中添加它(對於 MAC,在下面位置的 Cacerts 文件,對於 Windows,找出您的 JDK 安裝位置。)
sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
原始答案張貼在此鏈接上。
我通過使用完整 URL“qtest.ourCompany.com/webService” 而不是“qtest/webService”解決了我收到此錯誤的問題。 原因是我們的安全證書有一個通配符,即“*.ourCompany.com”。 一旦我輸入完整地址,異常就消失了。 希望這會有所幫助。
您可能不想禁用所有 ssl 驗證,因此您可以通過此禁用主機名驗證,這比替代方案更不可怕:
HttpsURLConnection.setDefaultHostnameVerifier(
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
[編輯]
正如 conapart3 所提到的SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
現在已被棄用,因此它可能會在更高版本中被刪除,因此您將來可能會被迫自行推出,盡管我仍然會說我會避開所有驗證都在的任何解決方案關閉。
已經在https://stackoverflow.com/a/53491151/1909708 中回答了。
這將失敗,因為證書公用名(證書Subject
CN
)和任何備用名稱(證書中的Subject Alternative Name
)都不與目標主機名或 IP 地址匹配。
例如,從 JVM 中,當嘗試連接到 IP 地址( WW.XX.YY.ZZ
)而不是 DNS 名稱( https://stackoverflow.com )時,HTTPS 連接將失敗,因為存儲在java truststore cacerts
期望通用名稱(或證書備用名稱,如 stackexchange.com 或 *.stackoverflow.com 等)與目標地址匹配。
請檢查: https : //docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier
HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
urlConnection.setSSLSocketFactory(socketFactory());
urlConnection.setDoOutput(true);
urlConnection.setRequestMethod("GET");
urlConnection.setUseCaches(false);
urlConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
});
urlConnection.getOutputStream();
上面,傳遞了一個實現的HostnameVerifier
對象,它總是返回true
:
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession sslSession) {
return true;
}
}
對於 Spring Boot RestTemplate
:
org.apache.httpcomponents.httpcore
依賴將NoopHostnameVerifier
用於 SSL 工廠:
SSLContext sslContext = new SSLContextBuilder() .loadTrustMaterial(new URL("file:pathToServerKeyStore"), storePassword) // .loadKeyMaterial(new URL("file:pathToClientKeyStore"), storePassword, storePassword) .build(); SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client); RestTemplate restTemplate = new RestTemplate(factory);
此代碼將像魅力一樣工作,並在其余代碼中使用 restTemple 對象。
RestTemplate restTemplate = new RestTemplate();
TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
@Override
public boolean isTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {
return true;
}
};
SSLContext sslContext = null;
try {
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
restTemplate.setRequestFactory(requestFactory);
}
我也面臨與自簽名證書相同的問題。 通過參考上述幾個解決方案,我嘗試使用正確的 CN(即服務器的 IP 地址)重新生成證書。但它仍然對我不起作用。 最后,我嘗試通過下面提到的命令添加 SAN 地址來重新生成證書
**keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -alias <IP_ADDRESS> -ext san=ip:<IP_ADDRESS>**
之后我啟動了我的服務器並通過下面提到的 openssl 命令下載了客戶端證書
**openssl s_client -showcerts -connect <IP_ADDRESS>:443 < /dev/null | openssl x509 -outform PEM > myCert.pem**
然后我通過下面提到的命令將此客戶端證書導入到我的客戶端機器的 java 默認密鑰庫文件(cacerts)
**keytool -import -trustcacerts -keystore /home/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/cacerts -alias <IP_ADDRESS> -file ./mycert.pem**
我們最近遇到了一個類似的問題“找不到主題替代 DNS 名稱匹配”,這是一場噩夢,因為我們只能在生產服務器中重現它,而對調試的訪問接近於零。 其余環境運行良好。 我們的堆棧是 JDK 1.8.x+、JBoss EAP 7+、Java Spring Boot 應用程序和作為身份提供者的 Okta(從 Okta 恢復眾所周知的配置時 SSL 握手失敗,其中 okta 在 AWS 雲中可用 - 虛擬服務器)。
最后,我們發現(沒人知道為什么)我們使用的 JBoss EAP 應用服務器有一個額外的 JVM 系統屬性:
jsse.enableSNIExtension = false
這會阻止建立 TLS 連接,我們能夠通過在其他環境中添加相同的系統屬性/值來重現該問題。 所以解決方案很簡單,刪除不需要的屬性和值。
根據 Java 安全文檔,對於 Java 7+,此屬性默認設置為 true(請參閱https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization )
- jsse.enableSNIExtension 系統屬性。 服務器名稱指示 (SNI) 是 RFC 4366 中定義的 TLS 擴展。它支持與虛擬服務器的 TLS 連接,其中具有不同網絡名稱的多個服務器托管在單個底層網絡地址上。 一些非常老的 SSL/TLS 供應商可能無法處理 SSL/TLS 擴展。 在這種情況下,將此屬性設置為 false 以禁用 SNI 擴展。
如果收到相同的錯誤消息,我就遇到了這個問題。 但是,在我的情況下,我們有兩個 URL 具有不同的子域( http://example1.xxx.com/someservice和http://example2.yyy.com/someservice ),它們被定向到同一台服務器。 該服務器只有一個用於 *.xxx.com 域的通配符證書。 通過第二個域使用服務時,找到的證書 (*.xxx.com) 與請求的域 (*.yyy.com) 不匹配,並發生錯誤。
在這種情況下,我們不應嘗試通過降低 SSL 安全性來修復此類錯誤消息,而應檢查服務器及其上的證書。
我在 springboot 中經歷了 2 路 SSL。 我已經做了所有正確的配置服務 tomcat 服務器和服務調用者 RestTemplate。 但我收到錯誤消息“java.security.cert.CertificateException:沒有主題替代名稱存在”
經過解決方案后,我發現 JVM 需要此證書,否則會出現握手錯誤。
現在,如何將它添加到 JVM。
轉到 jre/lib/security/cacerts 文件。 我們需要將我們的服務器證書文件添加到這個 jvm 的 cacerts 文件中。
在 Windows 中通過命令行將服務器證書添加到 cacerts 文件的命令。
C:\\Program Files\\Java\\jdk1.8.0_191\\jre\\lib\\security> keytool -import -noprompt -trustcacerts -alias sslserver -file E:\\spring_cloud_sachin\\ssl_keys\\sslserver.cer -keystore cacerts -storepass changeit
檢查服務器證書是否安裝:
C:\\Program Files\\Java\\jdk1.8.0_191\\jre\\lib\\security> keytool -list -keystore cacerts
您可以看到已安裝的證書列表:
更多詳情: https : //sachin4java.blogspot.com/2019/08/javasecuritycertificateexception-no.html
添加證書中CN對應ip的host表項
CN=someSubdomain.someorganisation.com
現在使用您嘗試訪問 url 的 CN 名稱更新 ip。
它對我有用。
當您擁有同時包含 CN 和主題備用名稱 (SAN) 的證書時,如果您根據 CN 內容提出請求,那么該特定內容也必須存在於 SAN 下,否則會因相關錯誤而失敗。
就我而言,CN 有一些東西,SAN 有其他東西。 我不得不使用 SAN URL,然后它工作得很好。
我已經解決了所說的
MqttException (0) - javax.net.ssl.SSLHandshakeException: 證書上沒有 subjectAltNames 匹配
通過在服務器證書(具有 CN=example.com)中添加一個(可以添加多個)替代主題名稱而出錯,該證書在打印證書部分后如下所示:
Subject Alternative Name:
DNS: example.com
我在 Windows 上使用 KeyExplorer 來生成我的服務器證書。 您可以按照此鏈接添加替代主題名稱(按照添加它的唯一部分)。
正如之前有人指出的那樣,我在創建 RestTemplate 對象之前添加了以下代碼(使用 lambda),並且它工作正常。 IT 僅適用於我的內部測試課程,因此我將為生產代碼提供更好的解決方案。
javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, sslSession) -> true);
public class RESTfulClientSSL {
static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}};
public class NullHostNameVerifier implements HostnameVerifier {
/*
* (non-Javadoc)
*
* @see javax.net.ssl.HostnameVerifier#verify(java.lang.String,
* javax.net.ssl.SSLSession)
*/
@Override
public boolean verify(String arg0, SSLSession arg1) {
// TODO Auto-generated method stub
return true;
}
}
public static void main(String[] args) {
HttpURLConnection connection = null;
try {
HttpsURLConnection.setDefaultHostnameVerifier(new RESTfulwalkthroughCer().new NullHostNameVerifier());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
String uriString = "https://172.20.20.12:9443/rest/hr/exposed/service";
URL url = new URL(uriString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
//connection.setRequestMethod("POST");
BASE64Encoder encoder = new BASE64Encoder();
String username = "admin";
String password = "admin";
String encodedCredential = encoder.encode((username + ":" + password).getBytes());
connection.setRequestProperty("Authorization", "Basic " + encodedCredential);
connection.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
StringBuffer stringBuffer = new StringBuffer();
String line = "";
while ((line = reader.readLine()) != null) {
stringBuffer.append(line);
}
String content = stringBuffer.toString();
System.out.println(content);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}
在 hosts 文件中添加您的 IP 地址。該文件位於 C:\\Windows\\System32\\drivers\\etc 文件夾中。 還要添加 IP 地址的 IP 和域名。 示例:aaa.bbb.ccc.ddd abc@def.com
我已經通過以下方式解決了這個問題。
class MyTrustManager implements X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString)
throws CertificateException {
// TODO Auto-generated method stub
}
private static void disableSSL() {
try {
TrustManager[] trustAllCerts = new TrustManager[] { new MyTrustManager() };
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.