java.security.cert.CertificateException: Illegal given domain xxx_xx.test.com.cn

今天解決了一個(gè)因https url中存在不合法字符導(dǎo)致證書校驗(yàn)失敗的問題钮热,錯(cuò)誤信息為java.security.cert.CertificateException: Illegal given domain xxx_xx.test.com.cn,網(wǎng)上對(duì)于這個(gè)問題的解決辦法一般都是通過向HttpsURLConnection設(shè)置一個(gè)自定義的HostnameVerifier禁用證書中的域名校驗(yàn)即可烛芬,因?yàn)楸緛磉@中域名就不合法隧期,如果對(duì)方不愿意配合修改域名的話,只能在我方這邊關(guān)閉域名校驗(yàn)赘娄。
本文簡(jiǎn)單記錄一下為什么這么設(shè)置可以禁用域名校驗(yàn)仆潮,以及這么做的優(yōu)缺點(diǎn)。

問題現(xiàn)象

今天發(fā)現(xiàn)日志中出現(xiàn)大量調(diào)對(duì)方https服務(wù)失敗的情況遣臼,錯(cuò)誤堆棧如下:

Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: Illegal given domain name: xxx_xx.test.com.cn
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1334)
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1309)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:259)
    at com.xxx.utils.http.SimpleHttpClient.doRequest(SimpleHttpClient.java:57)
    ... 91 more
Caused by: java.security.cert.CertificateException: Illegal given domain name: xxx_xx.test.com.cn
    at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:195)
    at sun.security.util.HostnameChecker.match(HostnameChecker.java:96)
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:455)
    at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:436)
    at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:200)
    at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
    ... 107 more
Caused by: java.lang.IllegalArgumentException: Contains non-LDH ASCII characters
    at java.net.IDN.toASCIIInternal(IDN.java:296)
    at java.net.IDN.toASCII(IDN.java:122)
    at javax.net.ssl.SNIHostName.<init>(SNIHostName.java:99)
    at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:193)
    ... 113 more

問題分析

1性置、錯(cuò)誤原因是什么

我們先從堆棧的最底層看起,先看最終出異常的地方java.net.IDN.toASCIIInternal()

for (int i = 0; i < dest.length(); i++) {
        int c = dest.charAt(i);
        if (isNonLDHAsciiCodePoint(c)) {
              throw new IllegalArgumentException(
                     "Contains non-LDH ASCII characters");
         }
}

//
// LDH stands for "letter/digit/hyphen", with characters restricted to the
// 26-letter Latin alphabet <A-Z a-z>, the digits <0-9>, and the hyphen
// <->.
// Non LDH refers to characters in the ASCII range, but which are not
// letters, digits or the hypen.
//
// non-LDH = 0..0x2C, 0x2E..0x2F, 0x3A..0x40, 0x5B..0x60, 0x7B..0x7F
//
private static boolean isNonLDHAsciiCodePoint(int ch){
    return (0x0000 <= ch && ch <= 0x002C) ||
           (0x002E <= ch && ch <= 0x002F) ||
           (0x003A <= ch && ch <= 0x0040) ||
           (0x005B <= ch && ch <= 0x0060) ||
           (0x007B <= ch && ch <= 0x007F);
}

isNonLDHAsciiCodePoint(int ch)方法的注釋和實(shí)現(xiàn)上可以看到揍堰,我們域名xxx_xx.test.com.cn里的_(ASCII碼:0x5F)是不符合這個(gè)校驗(yàn)規(guī)則的蚌讼。

2、為什么設(shè)置了HttpsURLConnectionHostnameVerifier就能解決這個(gè)問題

我們從剛才的錯(cuò)誤堆棧處往上追溯个榕,到這一個(gè)堆棧這里:

at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:200)

這一步的下一步就開始調(diào)用checkIdentity來檢查域名了篡石,這一步的代碼片段如下:

// check endpoint identity
String identityAlg = sslSocket.getSSLParameters().
                       getEndpointIdentificationAlgorithm();
if (identityAlg != null && identityAlg.length() != 0) {
     checkIdentity(session, chain, identityAlg, checkClientTrusted);
}

可以看出這里是根據(jù)SSLParameters里的getEndpointIdentificationAlgorithm()返回的值來決定要不要做域名校驗(yàn)的。通過查找該方法內(nèi)的屬性值的set方法的調(diào)用方西采,發(fā)現(xiàn)在HttpsClientafterConnect方法中根據(jù)條件設(shè)置了該屬性的值(從錯(cuò)誤堆棧上也可以看到有這個(gè)方法的堆棧記錄)凰萨,代碼內(nèi)關(guān)于這部分設(shè)置還寫了很詳細(xì)的注釋,如下:

// We have two hostname verification approaches. One is in
// SSL/TLS socket layer, where the algorithm is configured with
// SSLParameters.setEndpointIdentificationAlgorithm(), and the
// hostname verification is done by X509ExtendedTrustManager when
// the algorithm is "HTTPS". The other one is in HTTPS layer,
// where the algorithm is customized by
// HttpsURLConnection.setHostnameVerifier(), and the hostname
// verification is done by HostnameVerifier when the default
// rules for hostname verification fail.
//
// The relationship between two hostname verification approaches
// likes the following:
//
//               |             EIA algorithm
//               +----------------------------------------------
//               |     null      |   HTTPS    |   LDAP/other   |
// -------------------------------------------------------------
//     |         |1              |2           |3               |
// HNV | default | Set HTTPS EIA | use EIA    | HTTPS          |
//     |--------------------------------------------------------
//     | non -   |4              |5           |6               |
//     | default | HTTPS/HNV     | use EIA    | HTTPS/HNV      |
// -------------------------------------------------------------
//
// Abbreviation:
//     EIA: the endpoint identification algorithm in SSL/TLS
//           socket layer
//     HNV: the hostname verification object in HTTPS layer
// Notes:
//     case 1. default HNV and EIA is null
//           Set EIA as HTTPS, hostname check done in SSL/TLS
//           layer.
//     case 2. default HNV and EIA is HTTPS
//           Use existing EIA, hostname check done in SSL/TLS
//           layer.
//     case 3. default HNV and EIA is other than HTTPS
//           Use existing EIA, EIA check done in SSL/TLS
//           layer, then do HTTPS check in HTTPS layer.
//     case 4. non-default HNV and EIA is null
//           No EIA, no EIA check done in SSL/TLS layer, then do
//           HTTPS check in HTTPS layer using HNV as override.
//     case 5. non-default HNV and EIA is HTTPS
//           Use existing EIA, hostname check done in SSL/TLS
//           layer. No HNV override possible. We will review this
//           decision and may update the architecture for JDK 7.
//     case 6. non-default HNV and EIA is other than HTTPS
//           Use existing EIA, EIA check done in SSL/TLS layer,
//           then do HTTPS check in HTTPS layer as override.
boolean needToCheckSpoofing = true;
String identification =
    s.getSSLParameters().getEndpointIdentificationAlgorithm();
if (identification != null && identification.length() != 0) {
    if (identification.equalsIgnoreCase("HTTPS")) {
        // Do not check server identity again out of SSLSocket,
        // the endpoint will be identified during TLS handshaking
        // in SSLSocket.
        needToCheckSpoofing = false;
    }   // else, we don't understand the identification algorithm,
        // need to check URL spoofing here.
} else {
    boolean isDefaultHostnameVerifier = false;

    // We prefer to let the SSLSocket do the spoof checks, but if
    // the application has specified a HostnameVerifier (HNV),
    // we will always use that.
    if (hv != null) {
        String canonicalName = hv.getClass().getCanonicalName();
        if (canonicalName != null &&
        canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) {
            isDefaultHostnameVerifier = true;
        }
    } else {
        // Unlikely to happen! As the behavior is the same as the
        // default hostname verifier, so we prefer to let the
        // SSLSocket do the spoof checks.
        isDefaultHostnameVerifier = true;
    }

    if (isDefaultHostnameVerifier) {
        // If the HNV is the default from HttpsURLConnection, we
        // will do the spoof checks in SSLSocket.
        SSLParameters paramaters = s.getSSLParameters();
        paramaters.setEndpointIdentificationAlgorithm("HTTPS");
        s.setSSLParameters(paramaters);

        needToCheckSpoofing = false;
    }
}

我把這部分代碼的邏輯以流程圖的形式表示,看起來可能會(huì)更清晰一點(diǎn)胖眷,重點(diǎn)過程以紅色字體表示:

SSL域名校驗(yàn)流程圖

從圖上可以看出武通,當(dāng)用戶為HttpsURLConnection設(shè)置了非默認(rèn)的自定義hostnameVerifier,那么當(dāng)SSL域名校驗(yàn)失敗時(shí)珊搀,才會(huì)調(diào)用用戶自定義的hostnameVerifier執(zhí)行二次校驗(yàn)冶忱,當(dāng)且僅當(dāng)自定義的hostnameVerifier返回true時(shí),才會(huì)認(rèn)為域名校驗(yàn)成功境析。這也是為什么自定義HttpsURLConnection的hostnameVerifier為什么可以解決域名校驗(yàn)失敗的原因囚枪。

解決方案

最簡(jiǎn)單的解決方案:自定義javax.net.ssl.HostnameVerifier實(shí)現(xiàn),check方法直接返回true劳淆。

public class AcceptAllDomainHostnameVerifier implements HostnameVerifier {
  public boolean verify(String hostname, SSLSession session){
    return true;
  }
}

但是這種接受所有SSL校驗(yàn)失敗的域名會(huì)有安全風(fēng)險(xiǎn)链沼,相對(duì)安全點(diǎn)的做法建立一個(gè)SSL校驗(yàn)失敗的域名白名單列表,只有配置在該列表種的域名沛鸵,才算通過二次校驗(yàn)括勺。

public class AcceptAllDomainHostnameVerifier implements HostnameVerifier {
  public boolean verify(String hostname, SSLSession session){
    if (isInWhiteList(hostname)) {
      return true;
    }
    return false;
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市曲掰,隨后出現(xiàn)的幾起案子疾捍,更是在濱河造成了極大的恐慌,老刑警劉巖栏妖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拾氓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡底哥,警方通過查閱死者的電腦和手機(jī)咙鞍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾徽,“玉大人续滋,你說我怎么就攤上這事》跄蹋” “怎么了疲酌?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)了袁。 經(jīng)常有香客問我朗恳,道長(zhǎng),這世上最難降的妖魔是什么载绿? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任粥诫,我火速辦了婚禮,結(jié)果婚禮上崭庸,老公的妹妹穿的比我還像新娘怀浆。我一直安慰自己谊囚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布执赡。 她就那樣靜靜地躺著镰踏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沙合。 梳的紋絲不亂的頭發(fā)上奠伪,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音首懈,去河邊找鬼绊率。 笑死,一個(gè)胖子當(dāng)著我的面吹牛猜拾,可吹牛的內(nèi)容都是我干的即舌。 我是一名探鬼主播佣盒,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼挎袜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了肥惭?” 一聲冷哼從身側(cè)響起盯仪,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜜葱,沒想到半個(gè)月后全景,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牵囤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年爸黄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揭鳞。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡炕贵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出野崇,到底是詐尸還是另有隱情称开,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布乓梨,位于F島的核電站鳖轰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扶镀。R本人自食惡果不足惜蕴侣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臭觉。 院中可真熱鬧睛蛛,春花似錦鹦马、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至客冈,卻和暖如春旭从,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背场仲。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工和悦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渠缕。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓鸽素,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親亦鳞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子馍忽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 目錄介紹 01.整體概述介紹1.1 項(xiàng)目背景1.2 思考問題1.3 設(shè)計(jì)目標(biāo)1.4 收益分析 02.市面抓包的分析...
    楊充211閱讀 1,897評(píng)論 0 24
  • 自動(dòng)化檢測(cè)360顯微鏡(完全免費(fèi)) http://appscan.#/阿里聚安全(部分收費(fèi))https:...
    極客圈閱讀 8,626評(píng)論 0 18
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料燕差? 從這篇文章中你...
    hw1212閱讀 12,713評(píng)論 2 59
  • 隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展遭笋,各大傳統(tǒng)保險(xiǎn)公司和銀行金融公司都開發(fā)了自己的App,那么App的信息安全就變得非常重要了徒探。如...
    碼農(nóng)一顆顆閱讀 2,939評(píng)論 1 6
  • 一瓦呼、Java語言規(guī)范 詳見:Android開發(fā)java編寫規(guī)范 二、Android資源文件命名與使用 1. 【推薦...
    王朋6閱讀 963評(píng)論 0 0