这看起来是一个标准问题,但我在任何地方都找不到明确的方向。
我有java代码试图连接到一个可能自签名(或过期)证书的服务器。代码报告以下错误:
[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught
when processing request: sun.security.validator.ValidatorException: PKIX path
building failed: sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
根据我的理解,我必须使用keytool并告诉java允许此连接是OK的。
解决此问题的所有说明都假设我完全熟练使用keytool,例如
为服务器生成私有密钥并将其导入密钥存储库
有人能给我详细说明吗?
我正在运行unix,所以bash脚本将是最好的。
不确定这是否重要,但在jboss中执行的代码。
Apache HttpClient 4.5支持接受自签名证书:
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(new TrustSelfSignedStrategy())
.build();
SSLConnectionSocketFactory socketFactory =
new SSLConnectionSocketFactory(sslContext);
Registry<ConnectionSocketFactory> reg =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", socketFactory)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse sslResponse = httpClient.execute(httpGet);
这将构建一个SSL套接字工厂,它将使用TrustSelfSignedStrategy,将其注册到自定义连接管理器,然后使用该连接管理器执行HTTP GET。
我同意那些高呼“不要在生产环境中这样做”的人,但是在生产环境之外接受自签名证书是有用例的;我们在自动化集成测试中使用它们,因此即使不在生产硬件上运行,我们也在使用SSL(就像在生产中一样)。
接受的答案需要选项3
也
选项2很糟糕。绝对不应该使用它(特别是在生产中),因为它提供了一种虚假的安全感。只需使用HTTP而不是选项2。
选项3
使用自签名证书进行Https连接。
这里有一个例子:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.KeyStore;
/*
* Use a SSLSocket to send a HTTP GET request and read the response from an HTTPS server.
* It assumes that the client is not behind a proxy/firewall
*/
public class SSLSocketClientCert
{
private static final String[] useProtocols = new String[] {"TLSv1.2"};
public static void main(String[] args) throws Exception
{
URL inputUrl = null;
String certFile = null;
if(args.length < 1)
{
System.out.println("Usage: " + SSLSocketClient.class.getName() + " <url>");
System.exit(1);
}
if(args.length == 1)
{
inputUrl = new URL(args[0]);
}
else
{
inputUrl = new URL(args[0]);
certFile = args[1];
}
SSLSocket sslSocket = null;
PrintWriter outWriter = null;
BufferedReader inReader = null;
try
{
SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certFile);
sslSocket = (SSLSocket) sslSocketFactory.createSocket(inputUrl.getHost(), inputUrl.getPort() == -1 ? inputUrl.getDefaultPort() : inputUrl.getPort());
String[] enabledProtocols = sslSocket.getEnabledProtocols();
System.out.println("Enabled Protocols: ");
for(String enabledProtocol : enabledProtocols) System.out.println("\t" + enabledProtocol);
String[] supportedProtocols = sslSocket.getSupportedProtocols();
System.out.println("Supported Protocols: ");
for(String supportedProtocol : supportedProtocols) System.out.println("\t" + supportedProtocol + ", ");
sslSocket.setEnabledProtocols(useProtocols);
/*
* Before any data transmission, the SSL socket needs to do an SSL handshake.
* We manually initiate the handshake so that we can see/catch any SSLExceptions.
* The handshake would automatically be initiated by writing & flushing data but
* then the PrintWriter would catch all IOExceptions (including SSLExceptions),
* set an internal error flag, and then return without rethrowing the exception.
*
* This means any error messages are lost, which causes problems here because
* the only way to tell there was an error is to call PrintWriter.checkError().
*/
sslSocket.startHandshake();
outWriter = sendRequest(sslSocket, inputUrl);
readResponse(sslSocket);
closeAll(sslSocket, outWriter, inReader);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
closeAll(sslSocket, outWriter, inReader);
}
}
private static PrintWriter sendRequest(SSLSocket sslSocket, URL inputUrl) throws IOException
{
PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream())));
outWriter.println("GET " + inputUrl.getPath() + " HTTP/1.1");
outWriter.println("Host: " + inputUrl.getHost());
outWriter.println("Connection: Close");
outWriter.println();
outWriter.flush();
if(outWriter.checkError()) // Check for any PrintWriter errors
System.out.println("SSLSocketClient: PrintWriter error");
return outWriter;
}
private static void readResponse(SSLSocket sslSocket) throws IOException
{
BufferedReader inReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
String inputLine;
while((inputLine = inReader.readLine()) != null)
System.out.println(inputLine);
}
// Terminate all streams
private static void closeAll(SSLSocket sslSocket, PrintWriter outWriter, BufferedReader inReader) throws IOException
{
if(sslSocket != null) sslSocket.close();
if(outWriter != null) outWriter.close();
if(inReader != null) inReader.close();
}
// Create an SSLSocketFactory based on the certificate if it is available, otherwise use the JVM default certs
public static SSLSocketFactory getSSLSocketFactory(String certFile)
throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
{
if (certFile == null) return (SSLSocketFactory) SSLSocketFactory.getDefault();
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(new File(certFile)));
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("server", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
}
受下面annser的启发,我找到了一种信任自签名CA并保持信任默认CA的方法。
File file = new File(System.getProperty("java.home"), "lib/security/cacerts");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(file), "changeit".toCharArray());
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("testCer.cer");
Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(resourceAsStream);
keyStore.setCertificateEntry("my-server-alias", certificate);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContexts.createDefault();
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
// check domain
// SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
// not check domain
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext,
new String[]{"TLSv1","TLSv1.1","TLSv1.2","SSLv3"},null, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(factory);
我有一个问题,我正在传递一个URL到一个库调用URL . openconnection ();我改编了jon-daniel的答案,
public class TrustHostUrlStreamHandler extends URLStreamHandler {
private static final Logger LOG = LoggerFactory.getLogger(TrustHostUrlStreamHandler.class);
@Override
protected URLConnection openConnection(final URL url) throws IOException {
final URLConnection urlConnection = new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile()).openConnection();
// adapated from
// https://stackoverflow.com/questions/2893819/accept-servers-self-signed-ssl-certificate-in-java-client
if (urlConnection instanceof HttpsURLConnection) {
final HttpsURLConnection conHttps = (HttpsURLConnection) urlConnection;
try {
// Set up a Trust all manager
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
}
@Override
public void checkServerTrusted(final java.security.cert.X509Certificate[] certs, final String authType) {
}
} };
// Get a new SSL context
final SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
// Set our connection to use this SSL context, with the "Trust all" manager in place.
conHttps.setSSLSocketFactory(sc.getSocketFactory());
// Also force it to trust all hosts
final HostnameVerifier allHostsValid = new HostnameVerifier() {
@Override
public boolean verify(final String hostname, final SSLSession session) {
return true;
}
};
// and set the hostname verifier.
conHttps.setHostnameVerifier(allHostsValid);
} catch (final NoSuchAlgorithmException e) {
LOG.warn("Failed to override URLConnection.", e);
} catch (final KeyManagementException e) {
LOG.warn("Failed to override URLConnection.", e);
}
} else {
LOG.warn("Failed to override URLConnection. Incorrect type: {}", urlConnection.getClass().getName());
}
return urlConnection;
}
}
使用这个类可以创建一个新的URL:
trustedUrl = new URL(new URL(originalUrl), "", new TrustHostUrlStreamHandler());
trustedUrl.openConnection();
这样做的好处是它是本地化的,不会替换默认的URL.openConnection。