允许 Java 使用不受信任的证书进行 SSL/HTTPS 连接

Posted

技术标签:

【中文标题】允许 Java 使用不受信任的证书进行 SSL/HTTPS 连接【英文标题】:Allowing Java to use an untrusted certificate for SSL/HTTPS connection 【发布时间】:2010-11-15 02:55:53 【问题描述】:

我一直在开发一个从动态 Web 应用程序中提取信息的程序,该程序运行良好,直到我将我的 tomcat 服务器设置为使用自签名(因此,不受信任)证书使用 SSL。错误的堆栈跟踪是:

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
Error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:150)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1584)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:174)
        at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:168)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:848)
        at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:106)
        at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:495)
        at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:433)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:877)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1089)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1116)
        at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1100)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:402)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:170)
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:857)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:230)
        at com.certicom.gls.glscs.nongui.URLReader$PostURL.setupURL(URLReader.java:34)
        at com.certicom.gls.glscs.nongui.URLReader.getLogin(URLReader.java:227)
        at com.certicom.gls.glscs.nongui.URLReader.testLogin(URLReader.java:436)
        at com.certicom.gls.glscs.nongui.Controller.loginMenu(Controller.java:384)
        at com.certicom.gls.glscs.nongui.Controller.menu(Controller.java:324)
        at com.certicom.gls.glscs.nongui.Controller.<init>(Controller.java:49)
        at com.certicom.gls.glscs.nongui.Controller.main(Controller.java:61)

在网络浏览器中,当用户使用不受信任的证书访问 HTTPS 站点时,系统会提示用户一个警告,如果他想继续,则要求他例外;我想为我的命令行应用程序实现类似的功能......我承认我对套接字编程和一般网络是新手;任何解决这个问题的建议都会很棒!

【问题讨论】:

您可以使用this implemenation... 它有两个部分 1. 在您调用Https 之前调用一个带有静态方法的类文件 【参考方案1】:

Here 是一些相关代码:

// 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(
            java.security.cert.X509Certificate[] certs, String authType) 
        
        public void checkServerTrusted(
            java.security.cert.X509Certificate[] certs, String authType) 
        
    
;

// Install the all-trusting trust manager
try 
    SSLContext sc = SSLContext.getInstance("SSL");
    sc.init(null, trustAllCerts, new java.security.SecureRandom());
    HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
 catch (Exception e) 


// Now you can access an https URL without having the certificate in the truststore
try 
    URL url = new URL("https://hostname/index.html");
 catch (MalformedURLException e) 

这将完全禁用 SSL 检查——只是不要从此类代码中学习异常处理!

要做你想做的事,你必须在你的 TrustManager 中实现一个提示用户的检查。

【讨论】:

这是这样做的方法;但是,您需要非常小心,不要让此代码投入生产 - 如果您在生产应用程序中使用不受信任的证书,这是一件坏事。如果您的应用中有像 spring 这样的依赖注入框架,请考虑隔离此代码并为生产环境准备一个安全版本。 new java.security.SecureRandom() 参数也可以替换为null 从 Java 7 开始,X509ExtendedTrustManagerrather 可以规避算法约束等更多问题。当然,我必须重申@TimHowland 的评论.. 不知何故,当我使用 java 1.8_77 在 linux 服务器上运行它时,它没有进入方法 checkServerTrusted 并且握手问题仍然存在。知道为什么吗?【参考方案2】:

遵循here 的代码是一个有用的解决方案。没有密钥库等。只需在初始化服务和端口(在 SOAP 中)之前调用方法 SSLUtilities.trustAllHttpsCertificates()。

import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
 * This class provide various static methods that relax X509 certificate and
 * hostname verification while using the SSL over the HTTP protocol.
 *  
 * @author Jiramot.info
 */
public final class SSLUtilities 

  /**
   * Hostname verifier for the Sun's deprecated API.
   *
   * @deprecated see @link #_hostnameVerifier.
   */
  private static com.sun.net.ssl.HostnameVerifier __hostnameVerifier;
  /**
   * Thrust managers for the Sun's deprecated API.
   *
   * @deprecated see @link #_trustManagers.
   */
  private static com.sun.net.ssl.TrustManager[] __trustManagers;
  /**
   * Hostname verifier.
   */
  private static HostnameVerifier _hostnameVerifier;
  /**
   * Thrust managers.
   */
  private static TrustManager[] _trustManagers;

  /**
   * Set the default Hostname Verifier to an instance of a fake class that
   * trust all hostnames. This method uses the old deprecated API from the
   * com.sun.ssl package.
   *  
   * @deprecated see @link #_trustAllHostnames().
   */
  private static void __trustAllHostnames() 
    // Create a trust manager that does not validate certificate chains
    if (__hostnameVerifier == null) 
        __hostnameVerifier = new SSLUtilities._FakeHostnameVerifier();
     // if
    // Install the all-trusting host name verifier
    com.sun.net.ssl.HttpsURLConnection
            .setDefaultHostnameVerifier(__hostnameVerifier);
   // __trustAllHttpsCertificates

  /**
   * Set the default X509 Trust Manager to an instance of a fake class that
   * trust all certificates, even the self-signed ones. This method uses the
   * old deprecated API from the com.sun.ssl package.
   *
   * @deprecated see @link #_trustAllHttpsCertificates().
   */
  private static void __trustAllHttpsCertificates() 
    com.sun.net.ssl.SSLContext context;

    // Create a trust manager that does not validate certificate chains
    if (__trustManagers == null) 
        __trustManagers = new com.sun.net.ssl.TrustManager[]new SSLUtilities._FakeX509TrustManager();
     // if
    // Install the all-trusting trust manager
    try 
        context = com.sun.net.ssl.SSLContext.getInstance("SSL");
        context.init(null, __trustManagers, new SecureRandom());
     catch (GeneralSecurityException gse) 
        throw new IllegalStateException(gse.getMessage());
     // catch
    com.sun.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(context
            .getSocketFactory());
   // __trustAllHttpsCertificates

  /**
   * Return true if the protocol handler property java. protocol.handler.pkgs
   * is set to the Sun's com.sun.net.ssl. internal.www.protocol deprecated
   * one, false otherwise.
   *
   * @return true if the protocol handler property is set to the Sun's
   * deprecated one, false otherwise.
   */
  private static boolean isDeprecatedSSLProtocol() 
    return ("com.sun.net.ssl.internal.www.protocol".equals(System
            .getProperty("java.protocol.handler.pkgs")));
   // isDeprecatedSSLProtocol

  /**
   * Set the default Hostname Verifier to an instance of a fake class that
   * trust all hostnames.
   */
  private static void _trustAllHostnames() 
      // Create a trust manager that does not validate certificate chains
      if (_hostnameVerifier == null) 
          _hostnameVerifier = new SSLUtilities.FakeHostnameVerifier();
       // if
      // Install the all-trusting host name verifier:
      HttpsURLConnection.setDefaultHostnameVerifier(_hostnameVerifier);
   // _trustAllHttpsCertificates

  /**
   * Set the default X509 Trust Manager to an instance of a fake class that
   * trust all certificates, even the self-signed ones.
   */
  private static void _trustAllHttpsCertificates() 
    SSLContext context;

      // Create a trust manager that does not validate certificate chains
      if (_trustManagers == null) 
          _trustManagers = new TrustManager[]new SSLUtilities.FakeX509TrustManager();
       // if
      // Install the all-trusting trust manager:
      try 
          context = SSLContext.getInstance("SSL");
          context.init(null, _trustManagers, new SecureRandom());
       catch (GeneralSecurityException gse) 
          throw new IllegalStateException(gse.getMessage());
       // catch
      HttpsURLConnection.setDefaultSSLSocketFactory(context
            .getSocketFactory());
   // _trustAllHttpsCertificates

  /**
   * Set the default Hostname Verifier to an instance of a fake class that
   * trust all hostnames.
   */
  public static void trustAllHostnames() 
      // Is the deprecated protocol setted?
      if (isDeprecatedSSLProtocol()) 
          __trustAllHostnames();
       else 
          _trustAllHostnames();
       // else
   // trustAllHostnames

  /**
   * Set the default X509 Trust Manager to an instance of a fake class that
   * trust all certificates, even the self-signed ones.
   */
  public static void trustAllHttpsCertificates() 
    // Is the deprecated protocol setted?
    if (isDeprecatedSSLProtocol()) 
        __trustAllHttpsCertificates();
     else 
        _trustAllHttpsCertificates();
     // else
   // trustAllHttpsCertificates

  /**
   * This class implements a fake hostname verificator, trusting any host
   * name. This class uses the old deprecated API from the com.sun. ssl
   * package.
   *
   * @author Jiramot.info
   *
   * @deprecated see @link SSLUtilities.FakeHostnameVerifier.
   */
  public static class _FakeHostnameVerifier implements
        com.sun.net.ssl.HostnameVerifier 

    /**
     * Always return true, indicating that the host name is an acceptable
     * match with the server's authentication scheme.
     *
     * @param hostname the host name.
     * @param session the SSL session used on the connection to host.
     * @return the true boolean value indicating the host name is trusted.
     */
    public boolean verify(String hostname, String session) 
        return (true);
     // verify
   // _FakeHostnameVerifier

  /**
   * This class allow any X509 certificates to be used to authenticate the
   * remote side of a secure socket, including self-signed certificates. This
   * class uses the old deprecated API from the com.sun.ssl package.
   *
   * @author Jiramot.info
   *
   * @deprecated see @link SSLUtilities.FakeX509TrustManager.
   */
  public static class _FakeX509TrustManager implements
        com.sun.net.ssl.X509TrustManager 

    /**
     * Empty array of certificate authority certificates.
     */
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[];

    /**
     * Always return true, trusting for client SSL chain peer certificate
     * chain.
     *
     * @param chain the peer certificate chain.
     * @return the true boolean value indicating the chain is trusted.
     */
    public boolean isClientTrusted(X509Certificate[] chain) 
        return (true);
     // checkClientTrusted

    /**
     * Always return true, trusting for server SSL chain peer certificate
     * chain.
     *
     * @param chain the peer certificate chain.
     * @return the true boolean value indicating the chain is trusted.
     */
    public boolean isServerTrusted(X509Certificate[] chain) 
        return (true);
     // checkServerTrusted

    /**
     * Return an empty array of certificate authority certificates which are
     * trusted for authenticating peers.
     *
     * @return a empty array of issuer certificates.
     */
    public X509Certificate[] getAcceptedIssuers() 
        return (_AcceptedIssuers);
     // getAcceptedIssuers
   // _FakeX509TrustManager

  /**
   * This class implements a fake hostname verificator, trusting any host
   * name.
   *
   * @author Jiramot.info
   */
  public static class FakeHostnameVerifier implements HostnameVerifier 

    /**
     * Always return true, indicating that the host name is an acceptable
     * match with the server's authentication scheme.
     *
     * @param hostname the host name.
     * @param session the SSL session used on the connection to host.
     * @return the true boolean value indicating the host name is trusted.
     */
    public boolean verify(String hostname, javax.net.ssl.SSLSession session) 
        return (true);
     // verify
   // FakeHostnameVerifier

  /**
   * This class allow any X509 certificates to be used to authenticate the
   * remote side of a secure socket, including self-signed certificates.
   *
   * @author Jiramot.info
   */
  public static class FakeX509TrustManager implements X509TrustManager 

    /**
     * Empty array of certificate authority certificates.
     */
    private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[];

    /**
     * Always trust for client SSL chain peer certificate chain with any
     * authType authentication types.
     *
     * @param chain the peer certificate chain.
     * @param authType the authentication type based on the client
     * certificate.
     */
    public void checkClientTrusted(X509Certificate[] chain, String authType) 
     // checkClientTrusted

    /**
     * Always trust for server SSL chain peer certificate chain with any
     * authType exchange algorithm types.
     *
     * @param chain the peer certificate chain.
     * @param authType the key exchange algorithm used.
     */
    public void checkServerTrusted(X509Certificate[] chain, String authType) 
     // checkServerTrusted

    /**
     * Return an empty array of certificate authority certificates which are
     * trusted for authenticating peers.
     *
     * @return a empty array of issuer certificates.
     */
    public X509Certificate[] getAcceptedIssuers() 
        return (_AcceptedIssuers);
     // getAcceptedIssuers
   // FakeX509TrustManager
 // SSLUtilities

【讨论】:

这比@Yishai 解决方案更安全、更安全还是同样安全?【参考方案3】:

另一种选择是获取该特定服务器的“.pem”(公钥)文件,并将其本地安装到您的 JRE 的“cacerts”文件的中心(使用 keytool 帮助应用程序),然后它将能够从该服务器下载而无需投诉,不会损害您正在运行的 JVM 的整个 SSL 结构并允许从其他未知证书服务器下载...

【讨论】:

以上是关于允许 Java 使用不受信任的证书进行 SSL/HTTPS 连接的主要内容,如果未能解决你的问题,请参考以下文章

允许使用HttpClient的不受信任的SSL证书

如何阻止不受信任的证书?

charles进行手机抓包:安全证书不受信任,错误码3 怎么解决下?

受信任的证书条目不受密码保护 java

受信任的证书条目不受密码保护 java

登录失败。该登录名来自不受信任的域,不能与 Windows 身份验证一起使用。