主机名错误的例外

时间:2012-04-10 08:40:04

标签: java https

我的主机名错误异常。我在我的程序中使用过this code(从某个链接获取)。我的程序工作正常。我的问题是否足够安全? (因为它没有验证证书链)

public class Host {

    public String subscribe() throws Exception {   
        String resp = "";
        String urlString="https://xxx.xxx.xx.xx:8443/WebApplication3/NewServlet";
        URL url;
        URLConnection urlConn;
        DataOutputStream printout;
        DataInputStream input;
        String str = "";
        int flag=1;

        try {
            HostnameVerifier hv = new HostnameVerifier() {
                public boolean verify(String urlHostName, SSLSession session) {
                    System.out.println("Warning: URL Host: " + urlHostName + " vs. "
                      + session.getPeerHost());
                    return true;
                }
            };

            trustAllHttpsCertificates();
            HttpsURLConnection.setDefaultHostnameVerifier(hv);

            url = new URL(urlString);
            urlConn = url.openConnection();
            urlConn.setDoInput(true);
            Object object;
            urlConn.setUseCaches(false);

            urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            input = new DataInputStream(urlConn.getInputStream());

            while (null != ((str = input.readLine()))) {
                if (str.length() >0) {
                    str = str.trim();
                    if(!str.equals("")) {
                        //System.out.println(str);
                        resp += str;
                    }
                }
            }
            input.close();
        } catch ( MalformedURLException mue) { 
            mue.printStackTrace();
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }
        return resp;
    }

    public static class miTM implements javax.net.ssl.TrustManager,
        javax.net.ssl.X509TrustManager {

        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
            return true;
        }

        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
            return;
        }

        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException {
            return;
        }
    }

    private static void trustAllHttpsCertificates() throws Exception {

        //  Create a trust manager that does not validate certificate chains:
        javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];

        javax.net.ssl.TrustManager tm = new miTM();

        trustAllCerts[0] = tm;

        javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");

        sc.init(null, trustAllCerts, null);

        javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

    }

}

4 个答案:

答案 0 :(得分:4)

miTM中的代码实际上禁用了任何SSL安全检查,因此安全级别非常低(如果SSL证书被破坏,您只会遇到错误,但是当证书没有时,您不会收到错误匹配域名。)

基本上,您尝试在没有任何安全性的情况下建立连接。如果这就是你想要的,解决方案可能“足够安全”,但最有可能的答案是“不”。

此类问题的正确解决方案是为此域创建匹配的证书。

不幸的是,当您的HTTP服务器使用“虚拟主机”(=许多域名映射到同一IP地址)时,这是不可能的。解决此问题的正确方法是获取您自己的IP地址。

如果您仍想尝试仅使用Java的解决方案,请查看以下答案:https://stackoverflow.com/a/3293720/34088

答案 1 :(得分:1)

这是一种清理代码并保持安全的方法。我想代码连接到已知服务(可信)。要使Java SSL堆栈接受连接,即使主机名不匹配,最好的方法是将服务器证书添加到JVM信任库。

首先,您可以从浏览器导出服务器证书并将其保存在磁盘上。在Linux中,您可以使用openssl s_client -connect xxx.xxx.xx.xx:8443并将ascii-armored格式的服务器证书复制/粘贴到文本文件中。

然后使用keytool

将服务器证书导入jre/lib/security/cacerts JKS文件
keytool -import -alias myservice -file servercertificate.cer

我更喜欢的另一个选项,就是在Java更新时避免回归,就是在你自己的地方复制cacerts并通过javax.net.ssl.trustStore系统属性声明它。

由于服务器证书位于信任存储区中...它是可信的,直到它过期。这通常用于自签名服务器证书。

答案 2 :(得分:1)

在java中曾经多次出现过这种异常

问题可能是ipconflict / ip-domain mismatch / invalid certificate

我通过使用适当的IP地址和安装证书解决了这个问题。

答案 3 :(得分:1)

为确保连接安全,您必须(至少):

  • 确认您信任该证书,
  • 验证主机名(除非您确定这是您唯一信任的证书)。

您的代码在这两点上失败了:

  • 您正在使用的TrustManager根本不检查证书(它从不抛出异常,而API期望在证书不受信任时抛出CertificateException形式)
  • 您的主机名验证程序始终返回true

修复您的代码:

  • 保留默认信任管理器,或使用您自己的信任库和默认TrustManagerFactory初始化它们。
  • 保留默认主机名验证程序。

您的问题标题(“主机名错误”)和您的示例网址https://xxx.xxx.xx.xx:8443似乎建议您连接到IP地址。

与某些浏览器不同,Java严格遵循规范(RFC 2818):

  

如果存在类型为dNSName的subjectAltName扩展名,则必须将其用作标识。否则,(最具体)共同   必须使用证书的“主题”字段中的名称字段。   虽然使用通用名称是现有的做法,但确实如此   不推荐使用,并鼓励认证机构使用   而是dNSName。

     

[...]

     

在某些情况下,URI被指定为IP地址而不是主机名。在这种情况下,必须存在iPAddress subjectAltName   在证书中,必须与URI中的IP完全匹配。

这意味着您不能只是将IP地址放在服务器证书中的主题DN的公用名(CN)中。如果您使用的是IP地址,则必须在主题备用名称条目中。 (从Java 7开始,keytool具有生成此类证书的选项。)

您可以在this answer中找到有关使用哪些命令的更多详细信息。

话虽如此,使用IP地址最多只能在测试环境中工作。我认为任何商业CA都不会为您提供基于IP地址的证书。我建议设置DNS条目(即使它只是在测试环境中的hosts文件中)。

即使您没有使用IP地址,也必须确保该证书对您尝试与服务器联系的主机名有效:如果您有主题备用名称条目,其中一个必须匹配主机名;否则,主机名必须位于此证书的主题DN的CN RDN中。