Использование Java SunMSCAPI/Windows-MY для доступа к сертификатам смарт-карт для соединения TLS/SSL с аутентификацией клиента
У меня есть приложение Java, которое использует сертификаты от смарт-карты для аутентификации клиента TLS/SSL. Смарт-карта имеет 2 сертификата, один для подписи, а другой для аутентификации. Вот как я это делаю:
// loading windows-my store
KeyStore windowsMyKeyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
windowsMyKeyStore.load(null, null);
// loading keymanager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(windowsMyKeyStore, null);
// building truststore
TrustManager[] trustAllManager = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustAllManager, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1.2", "TLSv1.1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.build();
HttpGet get = new HttpGet(...);
Проблема возникает из-за того, что Java выбрала первый сертификат (неправильный), который соответствует CertificateRequest с сервера, как можно увидеть в этом фрагменте, когда -Djavax.net.debug=all
:
*** ServerHelloDone
[read] MD5 and SHA1 hashes: len = 4
0000: 0E 00 00 00 ....
matching alias: <<alias for SIGNING certificate>>
matching alias: <<alias for AUTHENTICATION certificate>>
*** Certificate chain
chain [0] = [
<< SIGNING certificate >>
]
Можно ли настроить Java так, чтобы он использовал правильный сертификат?
1 ответ
Та же проблема здесь, я решил это следующим образом:
KeyStore windowsMyKeyStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
windowsMyKeyStore.load(null, null);
SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(windowsMyKeyStore, null, new PrivateKeyStrategy() {
@Override
public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
for (String alias : aliases.keySet()) {
PrivateKeyDetails privateKeyDetails = aliases.get(alias);
for (X509Certificate certificate : privateKeyDetails.getCertChain()) {
try {
certificate.checkValidity();
List<String> extKeyUsage = certificate.getExtendedKeyUsage();
if (extKeyUsage != null && extKeyUsage.contains("1.3.6.1.5.5.7.3.2"))
return alias;
} catch (CertificateExpiredException | CertificateNotYetValidException | CertificateParsingException e) {
continue;
}
}
}
return null;
}
}).build();
Спасибо за помощь @dave_thompson_085, я мог решить эту проблему:
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadKeyMaterial(windowsMyKeyStore, null, new PrivateKeyStrategy() {
@Override
public String chooseAlias(Map<String, PrivateKeyDetails> aliases, Socket socket) {
for (String alias : aliases.keySet()) {
PrivateKeyDetails privateKeyDetails = aliases.get(alias);
for (X509Certificate certificate : privateKeyDetails.getCertChain()) {
if (requiredCertificate.getSerialNumber().equals(certificate.getSerialNumber())) {
return alias;
}
}
}
throw new IllegalStateException("required certificate not found");
}
});
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build());
Есть и другие решения:
Используйте certutil.exe для удаления всех сертификатов, кроме тех, которые находятся на карте. Ужасное решение, но оно работает и решает другие проблемы, такие как раздражающее всплывающее окно "Пожалуйста, вставьте свою карту" при загрузке магазина Windows-MY.
Используйте "NewSunX509" из Java Multiple и Dynamic Keystores (смарт-карты), который должен работать со смарт-картами. Не работал для меня
Создайте свой собственный провайдер и отфильтруйте его на уровне библиотеки, вам нужно будет изменить исходный код sunmscapi.dll и создать своего собственного провайдера. Это будет работать? Не проверял это.