Моя проблема:
Я хочу подключиться к серверам (не ограничиваясь протоколом HTTPS — может быть LDAP-over-SSL, может быть SMTPS, может быть IMAPS и т. д.), которые могут быть использование сертификатов, которым Java не будет доверять по умолчанию (поскольку они самозаверяющие).
Желаемый рабочий процесс состоит в том, чтобы попытаться установить соединение, получить информацию о сертификате, представить ее пользователю и, если он примет ее, добавить ее в хранилище доверенных сертификатов, чтобы в дальнейшем ему можно было доверять.
Я не могу получить сертификат. У меня есть код (см. в конце поста), который я взял отсюда и с сайтов, на которые указывают ответы на вопросы о java SSL. Код просто создает SSLSocket
, запускает рукопожатие SSL и запрашивает у сеанса SSL сертификат Certificate[]
. Код отлично работает, когда я подключаюсь к серверу, используя уже заслуживающий доверия сертификат. Но когда я подключаюсь к серверу с помощью самозаверяющего сертификата, я получаю обычное:
Exception in thread "main" javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
[etc]
Если я запускаю с -Djavax.net.debug=all
, я вижу, что JVM извлекает самозаверяющий сертификат. cert, но взорвет соединение из-за использования ненадежного сертификата, прежде чем дойдет до точки, где он вернет сертификаты.
Похоже на проблему курицы и яйца. Это не позволит мне увидеть сертификаты, потому что им не доверяют. Но мне нужно увидеть сертификаты, чтобы иметь возможность добавить их в хранилище доверенных сертификатов, чтобы им можно было доверять. Как вырваться из этого?
Например, если я запускаю программу как:
java SSLTest www.google.com 443
Я получаю распечатку сертификатов, которые использует Google. Но если я запускаю его как
java SSLTest my.imap.server 993
, я получаю исключение, упомянутое выше.
Код:
import java.io.InputStream;
import java.io.OutputStream;
import java.security.cert.*;
import javax.net.SocketFactory;
import javax.net.ssl.*;
public class SSLTest
{
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: SSLTest host port");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
SocketFactory factory = SSLSocketFactory.getDefault();
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.startHandshake();
Certificate[] certs = socket.getSession().getPeerCertificates();
System.out.println("Certs retrieved: " + certs.length);
for (Certificate cert : certs) {
System.out.println("Certificate is: " + cert);
if(cert instanceof X509Certificate) {
try {
( (X509Certificate) cert).checkValidity();
System.out.println("Certificate is active for current date");
} catch(CertificateExpiredException cee) {
System.out.println("Certificate is expired");
}
}
}
}
}