一次IOS通知推送問題排查全過程

打碼日記發表於2022-05-04

原創:打碼日記(微信公眾號ID:codelogs),歡迎分享,轉載請保留出處。

發現問題

在上週一個將要下班的夜晚,測試突然和我打招呼,說IOS推送的修復更新上線後存在問題,後臺報錯。

連忙跑到測試那裡看報錯詳情,報錯如下:
image_2022-05-04_20220504143349

重現問題

看到這個報錯後,在網上搜尋了一下,這種錯誤一般都是因為客戶端不信任服務端SSL證照導致的,回想工作以來,好像遇到這種問題好多次了,只要將證照匯入一下就好了。

由於不能冒然線上上修改解決問題,於是獲取推送相關資訊(如:裝置token)後,到自己電腦上去測試,看是否能重現問題。

啪啦啪啦,程式碼修改完畢,點執行坐等錯誤出現。

5秒鐘過後,發現推送訊息傳送成功了,沒有出現報錯,有點懵逼!心裡想,程式碼都是完全一樣的啊,怎麼線上報錯,我這卻是好的呢???

糾結了一會,於是開始靜下心來分析:

  1. 程式碼肯定是一樣的,應該不是表面上的程式碼原因。
  2. 其次推送裝置也是一樣的,應該也不是手機問題。
  3. 那麼。。。

就在沒有頭緒之際,我又掃了一眼工程目錄,發現了jdk8,但我們線上系統使用的是jdk7啊。

image_2022-05-04_20220504143511

於是我將自己工程的jdk8換成jdk7試一下,同樣的報錯終於出現了!

那為什麼jdk8沒問題,jdk7卻報錯呢???

尋找原因

圍繞著網上的說法和之前的經驗,這hand_failure錯誤應該是SSL/TLS握手過程中不信任服務端證照導致的,那辦法很簡單,去蘋果官網找到蘋果服務端提供的證照,匯入到java的證照信任庫cacert檔案中即可。

image_2022-05-04_20220504143534

於是,下載了證照檔案GeoTrust_Global__CA.cer,並用jdk自帶的keytool工具將證照匯入到cacert檔案中,如下:
image_2022-05-04_20220504143548
如上,匯入過程中,發現提示已經有這個證照了,當時沒想那麼多,再導一次試試吧!

匯入後,再次執行程式碼,結果還是報錯!

心裡又慌亂起來,沒招了,於是又百度/google去了,但搜尋出來的結論幾乎都是證照信任問題,和自己的招式一樣!

image_2022-05-04_20220504143607

同時,也瞭解了一下SSL/TLS協議相關知識,大致握手過程如下:

image_2022-05-04_20220504143625

詳細如下:

  1. 客戶端傳送clientHello訊息,告訴服務端我使用的TLS版本與加密套件等。

  2. 伺服器返回serverHello訊息,告訴自己選擇哪個TLS協議版本與加密套件等。

  3. 伺服器傳送Certification訊息,將自己的數字證照(包括伺服器名稱、CA和公鑰)作為訊息內容發給客戶端。

  4. 客戶端Certificate verify校驗伺服器的數字證照的有效性。

  5. 客戶端Change cipher spec選擇加密套件並生成會話金鑰(客戶端與伺服器之間後續的資料傳輸將使用此會話金鑰)。

  6. 客戶端、伺服器之間傳送加密資料。

另外,jvm有一個-Djavax.net.debug=SSL的引數,可以把SSL/TLS的握手過程都在控制檯通過日誌顯示出來,如下:

image_2022-05-04_20220504143642

Ok,既然知道了握手過程,那就把SSL/TLS的日誌顯示出來看一下吧,看看是什麼階段出現的問題,如下:

image_2022-05-04_20220504143658
這說明GeoTrust_Global.cer證照確實已經新增到信任庫中了,而詳細的SSL日誌如下:

image_2022-05-04_20220504143712

如上圖,報錯發生在clientHello傳送之後,在讀服務端返回的serverHello訊息時,卻讀到的是Alert:handshake_failure警告資訊,然後SSL握手就中斷了,就好像你在和伺服器打招呼,然後伺服器回覆了一個滾蛋一樣!

百思不得其解?

準備放棄

一會,我看到測試走了過來。

測試:“問題解決的怎麼樣了,啥情況啊”

:“還不知道,本來以為很簡單,實際上並沒有,jdk8可以,7不行”

測試:“好吧”

:”要不升級jdk8吧“

測試:”能行嗎“

:”......能行,jdk相容性都很好,而且其它系統都已經升級過了,這個系統本來也計劃要升,問題不大“

測試:”......好的“

於是,測試去升級jdk8了,本來我應該站在測試身後等待問題被解決,但我還是想在這期間查一查根本原因,於是又琢磨了起來。

發現真相

回想起,這個系統以前本來是jdk6,就是因為蘋果強制開發者必須使用Https,且需要TLSv1.2版本,而jdk7才開始支援TLSv1.2,所以這個系統才升級到jdk7,那現在這個報錯會不會...

於是,我趕緊使用TLSv1.2試試,新增jvm引數-Dhttps.protocols=TLSv1.2即可強制https使用TLSv1.2版本協議,如下:

image_2022-05-04_20220504143729

加完後再次執行,報錯依舊,那還有那裡可能會有問題呢?

我突然靈光一閃,使用jdk7執行一次,再使用jdk8執行一次,將兩次的SSL日誌對比一下,看看哪裡不一樣,說不定能找到線索!

於是我馬上試執行並對比兩次SSL日誌,發現兩次SSL日誌中TLS協議使用的加密套件不同,如下圖:

jdk7的除錯資訊如下 :
image_2022-05-04_20220504143747
jdk8的除錯資訊如下:
image_2022-05-04_20220504143802
jdk8的TLS握手在服務端ServerHello之後,選擇的加密套件是TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,而jdk7中可供選擇的加密套件中沒有TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

問題差不多清晰了,應該是jdk7中的加密套件,蘋果伺服器覺得太老不安全,都不支援了,那麼如果我使用jdk8,並將加密套件強制為jdk7中的加密套件呢,應該也是會報錯的!

於是,我還是使用jdk8,並新增了jvm引數-Dhttps.cipherSuites=SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5,SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA,以強制jdk8使用jdk7的加密套件,如下:

image_2022-05-04_20220504143817

再次執行後,果然報錯了!所以應該是jdk8支援了一些新的加密套件,而蘋果服務端只認這些新的加密套件導致的。

於是,我去jdk官網,查了一下jdk8的新增特性,發現TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384中AES加密演算法的GCM模式在jdk8中才支援,如下:

image_2022-05-04_20220504143832

至此,問題原因全部搞清楚了,測試升級jdk8後也報告推送正常,此時已是10點多,兩人寫寫日報趕緊回家去了......

往期內容

密碼學入門
時區的坑,不想再踩了!
常見的Socket網路異常場景分析
字元編碼解惑

相關文章