我有一個帶有嵌入式 SSL 服務器和客戶端的 Java 應用程式。
我的應用程式使用客戶端身份驗證來確定客戶端的身份,因此服務器配置了 wantClientAuth=true 和 needClientAuth=true。服務器還配置了服務器身份(證書/密鑰對)。服務器證書 SubjectDN 在專有名稱的 CN 部分中不包含服務器的主機名。服務器證書也不在 x.509 備用名稱擴展中包含服務器的 IP 地址。
我的客戶端配置了客戶端身份。它被配置為不執行主機名驗證。它還配置了一個以通常方式定義的全信任信任管理器(臨時)。在客戶端,收到的錯誤是:
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
到目前為止,我所做的所有嘗試修復都只是成功地使它更頻繁地失敗。
我在另一個 stackoverflow 問題中找到了這個命令列并嘗試連接:
openssl s_client -connect 10.200.84.48:9298 -cert cert.pem -key key.pem -state -debug
這行得通!我可以使用 openssl 客戶端以及客戶端的私鑰和證書建立連接,但是當我嘗試使用我的 Java 客戶端進行連接時,它會因上述錯誤而失敗。
我確定我在兩端都使用了正確的密鑰和證書。
出于除錯目的,我在“trust-all”客戶端信任存盤中添加了列印陳述句,我注意到這三種方法都沒有被呼叫來驗證服務器的證書(無論證書的內容如何,??它都應該這樣做)。
我在動態管理的服務器端信任存盤中做了同樣的事情,因為客戶端身份來來去去。我知道每當修改信任庫內容時都必須構建一個新的信任管理器,因為信任管理器復制信任庫內容而不是持有對提供的 KeyStore 物件的參考,所以我的代碼就是這樣做的。當客戶端嘗試連接時,服務器會呼叫 checkClientTrusted 和 getAcceptedIssuers 并且顯示的證書內容是正確的。
這是真正奇怪的部分 - 它間歇性地作業。有時我會獲得成功的連接和資料交換,有時它會因標題錯誤(在服務器的 JSSE 除錯輸出中看到)以及上面提到的有關 PKIX 路徑構建的相關客戶端錯誤而失敗。
另一個事實:服務器使用的是從 SSLEngineConfigurator 創建的嵌入式服務器,客戶端是純 Jersey 客戶端,配置了 SSLContext。
我完全不知所措。有沒有人見過這樣的事情?我可以提供更多資訊來幫助您更好地理解背景關系嗎?
更新:
這是來自服務器端 JSSE 除錯日志的片段:
javax.net.ssl|FINE|25|grizzly-nio-kernel(7) SelectorRunner|2022-05-24 03:06:01.221 UTC|Alert.java:238|Received alert message (
"Alert": {
"level" : "fatal",
"description": "certificate_unknown"
}
)
javax.net.ssl|SEVERE|25|grizzly-nio-kernel(7) SelectorRunner|2022-05-24 03:06:01.221 UTC|TransportContext.java:316|Fatal (CERTIFICATE_UNKNOWN): Received fatal alert: certificate_unknown (
"throwable" : {
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alert.createSSLException(Alert.java:131)
at sun.security.ssl.Alert.createSSLException(Alert.java:117)
at sun.security.ssl.TransportContext.fatal(TransportContext.java:311)
服務器“收到致命警報:certificate_unknown”這一事實告訴我,客戶端是生成警報并導致問題的人。似乎客戶端不喜歡服務器的證書,事件雖然我使用的是定義如下的全信任信任管理器:
RestClientImpl(@Nonnull Endpoint endpoint, @Nonnull Credentials clientCreds,
@Nullable KeyStore trustStore, @Nonnull Configuration cfg, @Nonnull ExecutorService es) {
this.endpoint = endpoint;
ClientBuilder builder = ClientBuilder.newBuilder();
setupClientSecurity(builder, clientCreds, trustStore);
this.client = builder
.executorService(es)
.register(JsonProcessingFeature.class)
.register(LoggingFeature.class)
.property(LoggingFeature.LOGGING_FEATURE_LOGGER_NAME_CLIENT, log.getName())
.connectTimeout(cfg.getLong(CFG_REST_CLIENT_TMOUT_CONNECT_MILLIS), TimeUnit.MILLISECONDS)
.readTimeout(cfg.getLong(CFG_REST_CLIENT_TMOUT_READ_MILLIS), TimeUnit.MILLISECONDS)
.build();
this.baseUri = "https://" endpoint.getAddress() ':' endpoint.getPort() '/' BASE_PATH;
log.debug("client created for endpoint={}, identity={}: client-side truststore {}active; "
"hostname verification {}active", endpoint, osvIdentity,
clientSideTrustStoreActive ? "" : "NOT ", hostnameVerifierActive ? "" : "NOT ");
}
private void setupClientSecurity(ClientBuilder builder, @Nonnull Credentials clientCreds,
@Nullable KeyStore trustStore) {
try {
SSLContext sslContext = makeSslContext(clientCreds, trustStore);
builder.sslContext(sslContext);
if (trustStore != null) {
hostnameVerifierActive = true;
} else {
builder.hostnameVerifier((hostname, session) -> true);
}
} catch (IOException | GeneralSecurityException e) {
log.error("Failed to create SSL context with specified client credentials and "
"server certificate for endpoint={}, osv identity={}", endpoint, osvIdentity);
throw new IllegalArgumentException("Failed to create SSL context for connection to endpoint="
endpoint ", osv identity=" osvIdentity, e);
}
}
private SSLContext makeSslContext(@Nonnull Credentials clientCreds, @Nullable KeyStore trustStore)
throws IOException, GeneralSecurityException {
SSLContext context = SSLContext.getInstance(SSL_PROTOCOL); // TLSv1.2
X509Certificate clientCert = clientCreds.getCertificate();
PrivateKey privateKey = clientCreds.getPrivateKey();
// initialize key store with client private key and certificate
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
keyStore.setCertificateEntry(CLIENT_CERT_ALIAS, clientCert);
keyStore.setKeyEntry(CLIENT_KEY_ALIAS, privateKey, KEYSTORE_PASSWORD, new Certificate[] {clientCert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD);
KeyManager[] km = kmf.getKeyManagers();
// initialize trust store with server cert or with no-verify trust manager if no server cert provided
TrustManager[] tm;
if (trustStore != null) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
tm = tmf.getTrustManagers();
clientSideTrustStoreActive = true;
} else {
tm = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
log.debug("client-side trust manager: getAcceptedIssuers (returning empty cert list)");
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
log.debug("client-side trust manager: checkClientTrusted authType={}, certs={}",
authType, certs);
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
log.debug("client-side trust manager: checkServerTrusted authType={}, certs={}",
authType, certs);
}
}
};
}
context.init(km, tm, null);
return context;
}
uj5u.com熱心網友回復:
碰巧,這個問題的答案與客戶端的使用方式有關,而不是它的配置方式。客戶端非常主流,主要使用默認設定構建。唯一獨特(且相關)的配置方面是它使用自定義 SSLContext。
這個自 2016 年以來一直存在的 JDK 1.8.0 錯誤指出了問題的根本原因。https://bugs.openjdk.java.net/browse/JDK-8160347
該錯誤針對 1.8.0_92-b14 提交。我正在 1.8.0_312-b07 上測驗我的代碼。看起來這個錯誤在 6 年后仍然存在于 JSSE 中!
值得慶幸的是,提交錯誤的用戶也提交了解決方法:只需呼叫一次 HttpsURLConnection.getDefaultSSLSocketFactory(),然后允許多個執行緒同時訪問您的客戶端。我試過了,現在我的客戶完美無缺。希望這可以幫助某人。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/482785.html
下一篇:外部服務的Istio直通不起作用
