亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Netty系列之Netty安全性

系統(tǒng) 2206 0

1. 背景

1.1. 嚴峻的安全形勢

1.1.1. OpenSSL Heart bleed漏洞

2014年上半年對網(wǎng)絡(luò)安全影響最大的問題就是OpenSSL Heart bleed漏洞,來自Codenomicon和谷歌安全部門的研究人員發(fā)現(xiàn)OpenSSL的源代碼中存在一個漏洞,可以讓攻擊者獲得服務(wù)器上64K內(nèi)存中的數(shù)據(jù)內(nèi)容。該漏洞在國內(nèi)被譯為” OpenSSL心臟出血漏洞”,因其破壞性之大和影響的范圍之廣,堪稱網(wǎng)絡(luò)安全里程碑事件。

OpenSSL是為網(wǎng)絡(luò)通信提供安全及數(shù)據(jù)完整性的一種安全協(xié)議,囊括了主要的密碼算法、常用的密鑰和證書封裝管理功能以及SSL協(xié)議.多數(shù)SSL加密網(wǎng)站是用名為OpenSSL的開源軟件包,由于這也是互聯(lián)網(wǎng)應(yīng)用最廣泛的安全傳輸方法,被網(wǎng)銀、在線支付、電商網(wǎng)站、門戶網(wǎng)站、電子郵件等重要網(wǎng)站廣泛使用,所以漏洞影響范圍廣大。

全球第一個被攻擊通告的案例是加拿大稅務(wù)局確認OpenSSL Heart bleed漏洞導(dǎo)致了900個納稅人的社會保障號被盜,這900個納稅人的社保號被攻擊者在系統(tǒng)中完全刪除了。

?

1.1.2. 安全漏洞的高成本

任何網(wǎng)絡(luò)攻擊都能夠給企業(yè)造成破壞,但是如何將這些破壞具體量化成金融數(shù)據(jù)呢?2013年,B2B International聯(lián)合卡巴斯基實驗室基于對全球企業(yè)的調(diào)查結(jié)果,計算出網(wǎng)絡(luò)攻擊平均造成的損失。

根據(jù)調(diào)查報告得出的結(jié)論,當(dāng)企業(yè)遭遇網(wǎng)絡(luò)攻擊后平均損失為649,000美元。損失主要包括兩方面:

  1. 安全事件本身造成的損失,即由重要數(shù)據(jù)泄漏、業(yè)務(wù)連續(xù)性以及安全修復(fù)專家費用相關(guān)成本;
  2. 為列入計劃的”響應(yīng)”成本,用于阻止未來發(fā)生類似的攻擊事件,包括雇傭、培訓(xùn)員工成本以及硬件、軟件和其它基礎(chǔ)設(shè)施安全升級成本。

1.2. Netty面臨的安全風(fēng)險

作為一個高性能的NIO通信框架,基于Netty的行業(yè)應(yīng)用非常廣泛,不同的行業(yè)、不同的應(yīng)用場景,面臨的安全挑戰(zhàn)也不同,下面我們根據(jù)Netty的典型應(yīng)用場景,分析下Netty面臨的安全挑戰(zhàn)。

?

1.2.1. 僅限內(nèi)部使用的RPC通信框架

隨著業(yè)務(wù)的發(fā)展,網(wǎng)站規(guī)模的擴大,傳統(tǒng)基于MVC的垂直架構(gòu)已經(jīng)無法應(yīng)對業(yè)務(wù)的快速發(fā)展。需要對數(shù)據(jù)和業(yè)務(wù)進行水平拆分,基于RPC的分布式服務(wù)框架成為最佳選擇。

業(yè)務(wù)水平拆分之后,內(nèi)部的各個模塊需要進行高性能的通信,傳統(tǒng)基于RMI和Hession的同步阻塞式通信已經(jīng)無法滿足性能和可靠性要求。因此,高性能的NIO框架成為構(gòu)建分布式服務(wù)框架的基石。

網(wǎng)站的架構(gòu)演進過程如下:

圖1-1 網(wǎng)站的架構(gòu)演進

高性能的RPC框架,各模塊之間往往采用長連接通信,通過心跳檢測保證鏈路的可靠性。由于RPC框架通常是在內(nèi)部各模塊之間使用,運行在授信的內(nèi)部安全域中,不直接對外開放接口。因此,不需要做握手、黑白名單、SSL/TLS等,正所謂是“防君子不防小人”。

在這種應(yīng)用場景下,Netty的安全性是依托企業(yè)的防火墻、安全加固操作系統(tǒng)等系統(tǒng)級安全來保障的,它自身并不需要再做額外的安全性保護工作。

1.2.2. 對第三方開放的通信框架

如果使用Netty做RPC框架或者私有協(xié)議棧,RPC框架面向非授信的第三方開放,例如將內(nèi)部的一些能力通過服務(wù)對外開放出去,此時就需要進行安全認證,如果開放的是公網(wǎng)IP,對于安全性要求非常高的一些服務(wù),例如在線支付、訂購等,需要通過SSL/TLS進行通信。

它的原理圖如下:

圖1-2 對第三方開放的通信框架

對第三方開放的通信框架的接口調(diào)用存在三種場景:

  • 在企業(yè)內(nèi)網(wǎng),開放給內(nèi)部其它模塊調(diào)用的服務(wù),通常不需要進行安全認證和SSL/TLS傳輸;
  • 在企業(yè)內(nèi)網(wǎng),被外部其它模塊調(diào)用的服務(wù),往往需要利用IP黑白名單、握手登陸等方式進行安全認證,認證通過之后雙方使用普通的Socket進行通信,如果認證失敗,則拒絕客戶端連接;
  • 開放給企業(yè)外部第三方應(yīng)用訪問的服務(wù),往往需要監(jiān)聽公網(wǎng)IP(通常是防火墻的IP地址),由于對第三方服務(wù)調(diào)用者的監(jiān)管存在諸多困難,或者無法有效監(jiān)管,這些第三方應(yīng)用實際是非授信的。為了有效應(yīng)對安全風(fēng)險,對于敏感的服務(wù)往往需要通過SSL/TLS進行安全傳輸。

1.2.3. 應(yīng)用層協(xié)議的安全性

作為高性能、異步事件驅(qū)動的NIO框架,Netty非常適合構(gòu)建上層的應(yīng)用層協(xié)議,相關(guān)原理,如下圖所示:

圖1-3 基于Netty構(gòu)建應(yīng)用層協(xié)議

由于絕大多數(shù)應(yīng)用層協(xié)議都是公有的,這意味著底層的Netty需要向上層提供通信層的安全傳輸,也就是需要支持SSL/TLS。

JDK的安全類庫提供了javax.net.ssl.SSLSocket和javax.net.ssl.SSLServerSocket類庫用于支持SSL/TLS安全傳輸,對于NIO非阻塞Socket通信,JDK并沒有提供現(xiàn)成可用的類庫簡化用戶開發(fā)。

Netty通過JDK的SSLEngine,以SslHandler的方式提供對SSL/TLS安全傳輸?shù)闹С郑瑯O大的簡化了用戶的開發(fā)工作量,降低開發(fā)難度。

對于Netty默認提供的HTTP協(xié)議,Netty利用SslHandler,同樣支持HTTPS協(xié)議。

2. Netty SSL開發(fā)

2.1. SSL單向認證

單向認證,即客戶端只驗證服務(wù)端的合法性,服務(wù)端不驗證客戶端。下面我們通過Netty的SSL單向認證代碼開發(fā)來掌握基于Netty的SSL單向認證。

2.1.1. SSL單向認證開發(fā)

首先,利用JDK的keytool工具,Netty服務(wù)端依次生成服務(wù)端的密鑰對和證書倉庫、服務(wù)端自簽名證書。

生成Netty服務(wù)端私鑰和證書倉庫命令:

      keytool -genkey -alias securechat -keysize 2048 -validity 
365 -keyalg RSA -dname "CN=localhost" -keypass sNetty 
-storepass sNetty -keystore sChat.jks
    

生成Netty服務(wù)端自簽名證書:

      keytool -export -alias securechat -keystore sChat.jks -storepass sNetty -file sChat.cer
    

生成客戶端的密鑰對和證書倉庫,用于將服務(wù)端的證書保存到客戶端的授信證書倉庫中,命令如下:

      keytool -genkey -alias smcc -keysize 2048 -validity 365
 -keyalg RSA -dname "CN=localhost" -keypass cNetty 
-storepass cNetty -keystore cChat.jks
    

隨后,將Netty服務(wù)端的證書導(dǎo)入到客戶端的證書倉庫中,命令如下:

keytool -import -trustcacerts -alias securechat -file sChat.cer -storepass cNetty -keystore cChat.jks

上述工作完成之后,我們就開始編寫SSL服務(wù)端和客戶端的代碼,下面我們對核心代碼進行講解。

首先看服務(wù)端的代碼,在TCP鏈路初始化的時候,創(chuàng)建SSLContext并對其進行正確的初始化,下面我們對SSLContext的創(chuàng)建進行講解:

因為是客戶端認證服務(wù)端,因此服務(wù)端需要正確的設(shè)置和加載私鑰倉庫KeyStore,相關(guān)代碼如下:

初始化KeyManagerFactory之后,創(chuàng)建SSLContext并初始化,代碼如下:

由于是單向認證,服務(wù)端不需要驗證客戶端的合法性,因此,TrustManager為空,安全隨機數(shù)不需要設(shè)置,使用JDK默認創(chuàng)建的即可。

服務(wù)端的SSLContext創(chuàng)建完成之后,利用SSLContext創(chuàng)建SSL引擎SSLEngine,設(shè)置SSLEngine為服務(wù)端模式,由于不需要對客戶端進行認證,因此NeedClientAuth不需要額外設(shè)置,使用默認值False。相關(guān)代碼如下:

SSL服務(wù)端創(chuàng)建完成之后,下面繼續(xù)看客戶端的創(chuàng)建,它的原理同服務(wù)端類似,也是在初始化TCP鏈路的時候創(chuàng)建并設(shè)置SSLEngine,代碼如下:

由于是客戶端認證服務(wù)端,因此,客戶端只需要加載存放服務(wù)端CA的證書倉庫即可。

加載證書倉庫完成之后,初始化SSLContext,代碼如下:對于客戶端只需要設(shè)置信任證書TrustManager。

客戶端SSLContext初始化完成之后,創(chuàng)建SSLEngine并將其設(shè)置為客戶端工作模式,代碼如下:

將SslHandler添加到pipeline中,利用SslHandler實現(xiàn)Socket安全傳輸,代碼如下:

客戶端和服務(wù)端創(chuàng)建完成之后,測試下SSL單向認證功能是否OK,為了查看SSL握手過程,我們打開SSL握手的調(diào)測日志,Eclipse設(shè)置如下:

圖2-1 打開SSL調(diào)測日志

分別運行服務(wù)端和客戶端,運行結(jié)果如下:

圖2-2 客戶端SSL握手日志

圖2-3 服務(wù)端SSL握手日志

在客戶端輸入信息,服務(wù)端原樣返回,測試結(jié)果如下:

到此,Netty SSL單向認證已經(jīng)開發(fā)完成,下個小節(jié)我們將結(jié)合SSL握手日志,詳細解讀下SSL單向認證的原理。

2.1.2. SSL單向認證原理分析

SSL單向認證的過程總結(jié)如下:

  1. SSL客戶端向服務(wù)端傳送客戶端SSL協(xié)議的版本號、支持的加密算法種類、產(chǎn)生的隨機數(shù),以及其它可選信息;
  2. 服務(wù)端返回握手應(yīng)答,向客戶端傳送確認SSL協(xié)議的版本號、加密算法的種類、隨機數(shù)以及其它相關(guān)信息;
  3. 服務(wù)端向客戶端發(fā)送自己的公鑰;
  4. 客戶端對服務(wù)端的證書進行認證,服務(wù)端的合法性校驗包括:證書是否過期、發(fā)行服務(wù)器證書的CA是否可靠、發(fā)行者證書的公鑰能否正確解開服務(wù)器證書的“發(fā)行者的數(shù)字簽名”、服務(wù)器證書上的域名是否和服務(wù)器的實際域名相匹配等;
  5. 客戶端隨機產(chǎn)生一個用于后面通訊的“對稱密碼”,然后用服務(wù)端的公鑰對其加密,將加密后的“預(yù)主密碼”傳給服務(wù)端;
  6. 服務(wù)端將用自己的私鑰解開加密的“預(yù)主密碼”,然后執(zhí)行一系列步驟來產(chǎn)生主密碼;
  7. 客戶端向服務(wù)端發(fā)出信息,指明后面的數(shù)據(jù)通訊將使用主密碼為對稱密鑰,同時通知服務(wù)器客戶端的握手過程結(jié)束;
  8. 服務(wù)端向客戶端發(fā)出信息,指明后面的數(shù)據(jù)通訊將使用主密碼為對稱密鑰,同時通知客戶端服務(wù)器端的握手過程結(jié)束;
  9. SSL的握手部分結(jié)束,SSL安全通道建立,客戶端和服務(wù)端開始使用相同的對稱密鑰對數(shù)據(jù)進行加密,然后通過Socket進行傳輸;

下面,我們結(jié)合JDK的SSL工作原理對Netty的SSL單向認證過程進行講解,首先,我們看下JDK SSL單向認證的流程圖:

圖2-4 SSL單向認證流程圖

下面結(jié)合JDK SSL引擎的調(diào)測日志信息我們對SSL單向認證的流程進行詳細講解,對于比較簡單的流程會進行步驟合并。

步驟1:客戶端使用TLS協(xié)議版本發(fā)送一個ClientHello消息,這個消息包含一個隨機數(shù)、建議的加密算法套件和壓縮方法列表,如下所示:

      *** ClientHello, TLSv1
RandomCookie:  GMT: 1389796107 bytes = { 125, 107, 138, 150, 226, 182, 238, 75, 38, 
150, 222, 147, 127, 35, 36, 149, 172, 128, 152, 34, 110, 104, 176, 34, 180, 118, 185, 55 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 
TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
 TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
 TLS_ECDHE_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, 
TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
 SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, 
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_RC4_128_MD5,
 TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, 
sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1,
 sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, 
sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
    

步驟2:服務(wù)端使用ServerHello消息來響應(yīng),這個消息包含由客戶提供的信息基礎(chǔ)上的另一個隨機數(shù)和一個可選的會話ID,以及服務(wù)端選擇的加密套件算法,響應(yīng)消息如下:

      *** ServerHello, TLSv1
RandomCookie:  GMT: 1389796108 bytes = { 27, 170, 76, 238, 56, 58, 172, 146, 
41, 159, 249, 213, 16, 214, 53, 167, 50, 74, 39, 107, 121, 63, 80, 26, 210, 149, 249, 194 }
Session ID:  {83, 215, 155, 12, 122, 5, 231, 3, 13, 11, 17, 204, 56, 73, 119,
 49, 85, 229, 220, 92, 55, 40, 25, 194, 198, 244, 200, 6, 55, 209, 23, 245}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***
    

步驟3:服務(wù)端發(fā)送自簽名的證書消息,包含完整的證書鏈:

      *** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=localhost
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 180074092335949740506599932729136061127910704822256890304785299212
89120399567693292155689698972062026646025485683710348109589875614228688670418
26997320367322716218554750309434289655244757299259864742384047112657948157239
74656070231306588457907121768485493115189644689102055777319298694358710534010
07782509767857568645054682957874162480829502504137753701941108204165639642395
91445925708790136700350526512926021140926345621403182628994210668730957728483
67874786322927437079881769937503767679525485790533062220506746478912515940552
94347989837561879359652740344329755331698082706888032724267649830488014296906
294110074041
  public exponent: 65537
  Validity: [From: Sun Jul 27 08:49:30 CST 2014,
               To: Mon Jul 27 08:49:30 CST 2015]
  Issuer: CN=localhost
  SerialNumber: [    53d44c9a]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 10 05 5E D4 EE A8 1C 8E   82 F1 3F 6B 0A 34 9B 96  ..^.......?k.4..
0010: 97 BE 62 13 F7 2E 94 74   A5 46 CC AB C5 0B FC 67  ..b....t.F.....g
0020: 3C E1 1B 43 B8 A4 3B C9   F9 44 9F F2 D2 90 35 3C  <..C..;..D....5<
0030: F6 47 78 3A AC 6B 87 E5   43 EA C8 C5 8C 4C 6E AB  .Gx:.k..C....Ln.
0040: 46 F8 C8 C4 BA 86 97 1E   C5 75 2F 85 15 CB A1 93  F........u/.....
0050: 0E 23 06 57 93 47 DF 8D   04 0F 21 AC FC E0 7D 14  .#.W.G....!.....
0060: 07 BE 0F 62 F4 75 A9 CE   F9 B3 11 0B 75 B4 87 22  ...b.u......u.."
0070: D5 8E E2 0A A9 1F C2 15   3A 64 B2 23 8F 1A 84 6C  ........:d.#...l
0080: EE 2C 3A C3 24 65 F5 BC   5C AF BD F8 B9 C4 45 83  .,:.$e..\.....E.
0090: 5B FF BD 36 E8 5D BE 98   03 2E AB 3F FE EC 9A 7B  [..6.].....?....
00A0: 31 35 7D EF 53 81 8B 7A   8B 37 7D BD EB 17 F0 36  15..S..z.7.....6
00B0: 93 CF 74 28 A3 C1 8B E1   B1 12 9F 44 20 CA 48 64  ..t(.......D .Hd
00C0: D6 F5 B0 B1 D9 18 AA F6   88 02 26 93 C8 B8 91 1A  ..........&.....
00D0: F8 B0 8B E6 7D C6 56 39   B2 6A AF 73 D2 78 76 1A  ......V9.j.s.xv.
00E0: 10 F0 C5 98 4F 90 39 2F   84 BC A0 78 81 8B ED 04  ....O.9/...x....
00F0: B8 60 49 84 C3 BD CC D2   CA 52 0A 03 E0 6C 21 B3  .`I......R...l!.

]
***
    

步驟4:服務(wù)端向客戶端發(fā)送自己的公鑰信息,最后發(fā)送ServerHelloDone:

      *** ECDH ServerKeyExchange
Server key: Sun EC public key, 256 bits
  public x coord: 11246390291863077910794590233832192297756589204670697
905888685651118114908704
  public y coord: 14161558430218398366136024174925258002831938156653157
074058492642854053163673
  parameters: secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)
*** ServerHelloDone
    

步驟5:客戶端對服務(wù)端自簽名的證書進行認證,如果客戶端的信任證書列表中包含了服務(wù)端發(fā)送的證書,對證書進行合法性認證,相關(guān)信息如下:

      ***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=localhost
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 18007409233594974050659993272913606112791070482225689030478529921
2891203995676932921556896989720620266460254856837103481095898756142286886704
1826997320367322716218554750309434289655244757299259864742384047112657948157
2397465607023130658845790712176848549311518964468910205577731929869435871053
4010077825097678575686450546829578741624808295025041377537019411082041656396
4239591445925708790136700350526512926021140926345621403182628994210668730957
7284836787478632292743707988176993750376767952548579053306222050674647891251
5940552943479898375618793596527403443297553316980827068880327242676498304880
14296906294110074041
  public exponent: 65537
  Validity: [From: Sun Jul 27 08:49:30 CST 2014,
               To: Mon Jul 27 08:49:30 CST 2015]
  Issuer: CN=localhost
  SerialNumber: [    53d44c9a]

]
  Algorithm: [SHA1withRSA]
  Signature:
0000: 10 05 5E D4 EE A8 1C 8E   82 F1 3F 6B 0A 34 9B 96  ..^.......?k.4..
0010: 97 BE 62 13 F7 2E 94 74   A5 46 CC AB C5 0B FC 67  ..b....t.F.....g
0020: 3C E1 1B 43 B8 A4 3B C9   F9 44 9F F2 D2 90 35 3C  <..C..;..D....5<
0030: F6 47 78 3A AC 6B 87 E5   43 EA C8 C5 8C 4C 6E AB  .Gx:.k..C....Ln.
0040: 46 F8 C8 C4 BA 86 97 1E   C5 75 2F 85 15 CB A1 93  F........u/.....
0050: 0E 23 06 57 93 47 DF 8D   04 0F 21 AC FC E0 7D 14  .#.W.G....!.....
0060: 07 BE 0F 62 F4 75 A9 CE   F9 B3 11 0B 75 B4 87 22  ...b.u......u.."
0070: D5 8E E2 0A A9 1F C2 15   3A 64 B2 23 8F 1A 84 6C  ........:d.#...l
0080: EE 2C 3A C3 24 65 F5 BC   5C AF BD F8 B9 C4 45 83  .,:.$e..\.....E.
0090: 5B FF BD 36 E8 5D BE 98   03 2E AB 3F FE EC 9A 7B  [..6.].....?....
00A0: 31 35 7D EF 53 81 8B 7A   8B 37 7D BD EB 17 F0 36  15..S..z.7.....6
00B0: 93 CF 74 28 A3 C1 8B E1   B1 12 9F 44 20 CA 48 64  ..t(.......D .Hd
00C0: D6 F5 B0 B1 D9 18 AA F6   88 02 26 93 C8 B8 91 1A  ..........&.....
00D0: F8 B0 8B E6 7D C6 56 39   B2 6A AF 73 D2 78 76 1A  ......V9.j.s.xv.
00E0: 10 F0 C5 98 4F 90 39 2F   84 BC A0 78 81 8B ED 04  ....O.9/...x....
00F0: B8 60 49 84 C3 BD CC D2   CA 52 0A 03 E0 6C 21 B3  .`I......R...l!.

]
    

步驟6:客戶端通知服務(wù)器改變加密算法,通過Change Cipher Spec消息發(fā)給服務(wù)端,隨后發(fā)送Finished消息,告知服務(wù)器請檢查加密算法的變更請求:

      nioEventLoopGroup-2-1, WRITE: TLSv1 Change Cipher Spec, length = 1
*** Finished
    

步驟7:服務(wù)端讀取到Change Cipher Spec變更請求消息,向客戶端返回確認密鑰變更消息,最后通過發(fā)送Finished消息表示SSL/TLS握手結(jié)束。

      nioEventLoopGroup-3-1, READ: TLSv1 Change Cipher Spec, length = 1
nioEventLoopGroup-3-1, READ: TLSv1 Handshake, length = 48
*** Finished
verify_data:  { 157, 255, 187, 52, 139, 16, 20, 190, 11, 35, 79, 0 }
***
nioEventLoopGroup-3-1, WRITE: TLSv1 Change Cipher Spec, length = 1
*** Finished
    

2.2. SSL雙向認證

2.2.1. SSL雙向認證開發(fā)

我們在2.1章節(jié)的基礎(chǔ)上進行開發(fā),與單向認證不同的是服務(wù)端也需要對客戶端進行安全認證。這就意味著客戶端的自簽名證書也需要導(dǎo)入到服務(wù)端的數(shù)字證書倉庫中。

首先,生成客戶端的自簽名證書:

      keytool -export -alias smcc -keystore cChat.jks -storepass cNetty 
-file cChat.cer


    

最后,將客戶端的自簽名證書導(dǎo)入到服務(wù)端的信任證書倉庫中:

      keytool -import -trustcacerts -alias smcc -file cChat.cer -storepass 
sNetty -keystore sChat.jks


    

證書導(dǎo)入之后,需要對SSL客戶端和服務(wù)端的代碼同時進行修改,首先我們看下服務(wù)端如何修改。

由于服務(wù)端需要對客戶端進行驗證,因此在初始化服務(wù)端SSLContext的時候需要加載證書倉庫。首先需要對TrustManagerFactory進行初始化,代碼如下:

初始化SSLContext的時候根據(jù)TrustManagerFactory獲取TrustManager數(shù)組,代碼如下:

最后,創(chuàng)建SSLEngine之后,設(shè)置需要進行客戶端認證,代碼如下:

完成服務(wù)端修改之后,再回頭看下客戶端的修改,由于服務(wù)端需要認證客戶端的證書,因此,需要初始化和加載私鑰倉庫,向服務(wù)端發(fā)送公鑰,初始化KeyStore的代碼如下:

初始化SSLContext的時候需要傳入KeyManager數(shù)組,代碼如下:

客戶端開發(fā)完成之后,測試下程序是否能夠正常工作,運行結(jié)果如下所示。

客戶端運行結(jié)果:

圖2-5 Netty SSL雙向認證客戶端運行結(jié)果

服務(wù)端運行結(jié)果:

圖2-6 Netty SSL雙向認證服務(wù)端運行結(jié)果

在客戶端控制臺進行輸入,看SSL傳輸是否正常:

圖2-7 Netty SSL 安全傳輸測試

2.2.2. SSL雙向認證原理分析

SSL雙向認證相比單向認證,多了一步服務(wù)端發(fā)送認證請求消息給客戶端,客戶端發(fā)送自簽名證書給服務(wù)端進行安全認證的過程。下面,我們結(jié)合Netty SSL調(diào)測日志,對雙向認證的差異點進行分析。

相比于客戶端,服務(wù)端在發(fā)送ServerHello時攜帶了要求客戶端認證的請求信息,如下所示:

      *** CertificateRequest
Cert Types: RSA, DSS, ECDSA
Cert Authorities:
<CN=localhost>
<CN=localhost>
*** ServerHelloDone
    

客戶端接收到服務(wù)端要求客戶端認證的請求消息之后,發(fā)送自己的證書信息給服務(wù)端,信息如下:

      matching alias: smcc
*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=localhost
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 212639695562264078962258083015763969567082142460170954053624074
53705267323050920051941696590911289892005894127848317880153980200067657563
15944918691324084822137929027919841383304228071408660098765703368443353862
47349919704780645114810932016343908989985053434023995248208445566727867691
73042913746571760169661698040844437316556983406538131853892449014877947773
16977794500345715634646402492099542466990685058179767825995777860790787074
72339147926907851214779520246763960901175126351376922481444497141021631392
59603124160944922844840171133151822882039207352509182052426500279100525773
147139994269292585983679425433429361
  public exponent: 65537
  Validity: [From: Sun Jul 27 08:50:35 CST 2014,
               To: Mon Jul 27 08:50:35 CST 2015]
  Issuer: CN=localhost
  SerialNumber: [    53d44cdb]
    

服務(wù)端對客戶端的自簽名證書進行認證,信息如下:

      ***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=localhost
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits
  modulus: 21263969556226407896225808301576396956708214246017095405362407
4537052673230509200519416965909112898920058941278483178801539802000676575
6315944918691324084822137929027919841383304228071408660098765703368443353
8624734991970478064511481093201634390898998505343402399524820844556672786
7691730429137465717601696616980408444373165569834065381318538924490148779
4777316977794500345715634646402492099542466990685058179767825995777860790
7870747233914792690785121477952024676396090117512635137692248144449714102
1631392596031241609449228448401711331518228820392073525091820524265002791
00525773147139994269292585983679425433429361
  public exponent: 65537
  Validity: [From: Sun Jul 27 08:50:35 CST 2014,
               To: Mon Jul 27 08:50:35 CST 2015]
  Issuer: CN=localhost
  SerialNumber: [    53d44cdb]
    

2.3. 第三方CA認證

使用jdk keytool生成的數(shù)字證書是自簽名的。自簽名就是指證書只能保證自己是完整且沒有經(jīng)過非法修改,但是無法保證這個證書是屬于誰的。為了對自簽名證書進行認證,需要每個客戶端和服務(wù)端都交換自己自簽名的私有證書,對于一個大型網(wǎng)站或者應(yīng)用服務(wù)器,這種工作量是非常大的。

基于自簽名的SSL雙向認證,只要客戶端或者服務(wù)端修改了密鑰和證書,就需要重新進行簽名和證書交換,這種調(diào)試和維護工作量是非常大的。因此,在實際的商用系統(tǒng)中往往會使用第三方CA證書頒發(fā)機構(gòu)進行簽名和驗證。我們的瀏覽器就保存了幾個常用的CA_ROOT。每次連接到網(wǎng)站時只要這個網(wǎng)站的證書是經(jīng)過這些CA_ROOT簽名過的。就可以通過驗證了。

CA數(shù)字證書認證服務(wù)往往是收費的,國內(nèi)有很多數(shù)字認證中心都提供相關(guān)的服務(wù),如下所示:

圖2-8 商業(yè)的數(shù)字認證中心

作為示例,我們自己生成一個CA_ROOT的密鑰對,部署應(yīng)用時,把這個CA_ROOT的私鑰部署在所有需要SSL傳輸?shù)墓?jié)點就可以完成安全認證。作為示例,如果要生成CA_ROOT,我們使用開源的OpenSSL。

在Windows上安裝和使用OpenSSL網(wǎng)上有很多教程,也不是本文的重點,因此,OpenSSL的安裝和使用本文不詳細介紹。

下面我們對基于第三方CA認證的步驟進行詳細介紹。

2.3.1. 服務(wù)端證書制作

步驟1:利用OpenSSL生成CA證書:

      openssl req -new -x509 -keyout ca.key -out ca.crt -days 365
    

步驟2:生成服務(wù)端密鑰對:

      keytool -genkey -alias securechat -keysize 2048 -validity 365 
-keyalg RSA -dname "CN=localhost" -keypass sNetty -storepass sNetty
 -keystore sChat.jks
    

步驟3:生成證書簽名請求:

      keytool -certreq -alias securechat -sigalg MD5withRSA -file  sChat.csr 
-keypass sNetty -storepass sNetty -keystore sChat.jks
    

步驟4:用CA私鑰進行簽名:

      openssl ca -in sChat.csr -out sChat.crt -cert ca.crt -keyfile ca.key -notext
    

步驟5:導(dǎo)入信任的CA根證書到keystore:

      keytool -import -v -trustcacerts -alias ca_root -file ca.crt -storepass 
sNetty -keystore sChat.jks
    

步驟6:將CA簽名后的server端證書導(dǎo)入keystore:

      keytool -import -v -alias securechat -file server.crt -keypass sNetty 
-storepass sNetty -keystore sChat.jks
    

2.3.2. 客戶端證書制作

步驟1:生成客戶端密鑰對:

      keytool -genkey -alias smcc -keysize 2048 -validity 365 -keyalg 
RSA -dname "CN=localhost" -keypass cNetty -storepass cNetty -keystore cChat.jks
    

步驟2:生成證書簽名請求:

      keytool -certreq -alias smcc -sigalg MD5withRSA -file  cChat.csr 
-keypass cNetty -storepass cNetty -keystore cChat.jks
    

步驟3:用CA私鑰進行簽名:

      openssl ca -in cChat.csr -out cNetty.crt -cert ca.crt -keyfile ca.key -notext
    

步驟4:導(dǎo)入信任的CA根證書到keystore:

      keytool -import -v -trustcacerts -alias ca_root -file ca.crt 
-storepass cNetty -keystore cChat.jks
    

步驟5:將CA簽名后的client端證書導(dǎo)入keystore:

      keytool -import -v -alias smcc -file cNetty.crt -keypass cNetty -storepass 
cNetty -keystore cChat.jks
    

2.3.3. 開發(fā)和測試

基于CA認證的開發(fā)和測試與SSL雙向和單向認證代碼相同,此處不再贅述。

3. Netty SSL源碼分析

3.1. SSL客戶端

當(dāng)客戶端和服務(wù)端的TCP鏈路建立成功之后,SslHandler的channelActive被觸發(fā),SSL客戶端通過SSL引擎發(fā)起握手請求消息,代碼如下:

發(fā)起握手請求之后,需要將SSLEngine創(chuàng)建的握手請求消息進行SSL編碼,發(fā)送給服務(wù)端,因此,握手之后立即調(diào)用wrapNonAppData方法,下面具體對該方法進行分析:

因為只需要發(fā)送握手請求消息,因此Source ByteBuf為空,下面看下wrap方法的具體實現(xiàn):

將SSL引擎中創(chuàng)建的握手請求消息編碼到目標(biāo)ByteBuffer中,然后對寫索引進行更新。判斷寫入操作是否越界,如果越界說明out容量不足,需要調(diào)用ensureWritable對ByteBuf進行動態(tài)擴展,擴展之后繼續(xù)嘗試編碼操作。如果編碼成功,返回SSL引擎操作結(jié)果。

對編碼結(jié)果進行判斷,如果編碼字節(jié)數(shù)大于0,則將編碼后的結(jié)果發(fā)送給服務(wù)端,然后釋放臨時變量out。

判斷SSL引擎的操作結(jié)果,SSL引擎的操作結(jié)果定義如下:

  1. FINISHED:SSLEngine 已經(jīng)完成握手;
  2. NEED_TASK:SSLEngine 在繼續(xù)進行握手前需要一個(或多個)代理任務(wù)的結(jié)果;
  3. NEED_UNWRAP:在繼續(xù)進行握手前,SSLEngine 需要從遠端接收數(shù)據(jù),所以應(yīng)帶調(diào)用SSLEngine.unwrap();
  4. NEED_WRAP:在繼續(xù)進行握手前,SSLEngine 必須向遠端發(fā)送數(shù)據(jù),所以應(yīng)該調(diào)用 SSLEngine.wrap();
  5. NOT_HANDSHAKING:SSLEngine 當(dāng)前沒有進行握手。

下面我們分別對5種操作的代碼進行分析:

如果握手成功,則設(shè)置handshakePromise的操作結(jié)果為成功,同時發(fā)送SslHandshakeCompletionEvent.SUCCES給SSL監(jiān)聽器,代碼如下:

如果是NEED_TASK,說明異步執(zhí)行SSL Task,完成后續(xù)可能耗時的操作或者任務(wù),Netty封裝了一個任務(wù)立即執(zhí)行線程池專門處理SSL的代理任務(wù),代碼如下:

如果是NEED_UNWRAP,則判斷是否由UNWRAP發(fā)起,如果不是則執(zhí)行UNWRAP操作。

如果是NOT_HANDSHAKING,則調(diào)用unwrap,繼續(xù)接收服務(wù)端的消息。

服務(wù)端應(yīng)答消息的接收跟服務(wù)端接收客戶端的代碼類似,唯一不同之處在于SSL引擎的客戶端模式設(shè)置不同,一個是服務(wù)端,一個是客戶端。上層的代碼處理是相同的,下面我們在SSL服務(wù)端章節(jié)分析握手消息的接收。

3.2. SSL服務(wù)端

SSL服務(wù)端接收客戶端握手請求消息的入口方法是decode方法,下面對它進行詳細分析。

首先獲取接收緩沖區(qū)的讀寫索引,并對讀取的偏移量指針進行備份:

對半包標(biāo)識進行判斷,如果上一個消息是半包消息,則判斷當(dāng)前可讀的字節(jié)數(shù)是否小于整包消息的長度,如果小于整包長度,則說明本次讀取操作仍然沒有把SSL整包消息讀取完整,需要返回IO線程繼續(xù)讀取,代碼如下:

如果消息讀取完整,則修改偏移量:同時置位半包長度標(biāo)識。

下面在for循環(huán)中讀取SSL消息,因為TCP存在拆包和粘包,因此一個ByteBuf可能包含多條完整的SSL消息。

首先判斷可讀的字節(jié)數(shù)是否小于協(xié)議消息頭長度,如果是則退出循環(huán)繼續(xù)由IO線程接收后續(xù)的報文:

獲取SSL消息包的報文長度,具體算法不再介紹,可以參考SSL的規(guī)范文檔進行解讀,代碼如下:

對長度進行判斷,如果SSL報文長度大于可讀的字節(jié)數(shù),說明是個半包消息,將半包標(biāo)識長度置位,返回IO線程繼續(xù)讀取后續(xù)的數(shù)據(jù)報,代碼如下:

對消息進行解碼,將SSL加密的消息解碼為加密前的原始數(shù)據(jù),unwrap方法如下:

調(diào)用SSLEngine的unwrap方法對SSL原始消息進行解碼,對解碼結(jié)果進行判斷,如果越界,說明out緩沖區(qū)不夠,需要進行動態(tài)擴展。如果是首次越界,為了盡量節(jié)約內(nèi)存,使用SSL最大緩沖區(qū)長度和SSL原始緩沖區(qū)可讀的字節(jié)數(shù)中較小的。如果再次發(fā)生緩沖區(qū)越界,說明擴張后的緩沖區(qū)仍然不夠用,直接使用SSL緩沖區(qū)的最大長度,保證下次解碼成功。

解碼成功之后,對SSL引擎的操作結(jié)果進行判斷:如果需要繼續(xù)接收數(shù)據(jù),則繼續(xù)執(zhí)行解碼操作;如果需要發(fā)送握手消息,則調(diào)用wrapNonAppData發(fā)送握手消息;如果需要異步執(zhí)行SSL代理任務(wù),則調(diào)用立即執(zhí)行線程池執(zhí)行代理任務(wù);如果是握手成功,則設(shè)置SSL操作結(jié)果,發(fā)送SSL握手成功事件;如果是

應(yīng)用層的業(yè)務(wù)數(shù)據(jù),則繼續(xù)執(zhí)行解碼操作,其它操作結(jié)果,拋出操作類型異常。

需要指出的是,SSL客戶端和服務(wù)端接收對方SSL握手消息的代碼是相同的,那為什么SSL服務(wù)端和客戶端發(fā)送的握手消息不同呢?這些是SSL引擎負責(zé)區(qū)分和處理的,我們在創(chuàng)建SSL引擎的時候設(shè)置了客戶端模式,SSL引擎就是根據(jù)這個來進行區(qū)分的,代碼如下:

無論客戶端還是服務(wù)端,只需要圍繞SSL引擎的操作結(jié)果進行編程即可。

3.3. SSL消息讀取

SSL的消息讀取實際就是ByteToMessageDecoder將接收到的SSL加密后的報文解碼為原始報文,然后將整包消息投遞給后續(xù)的消息解碼器,對消息做二次解碼。基于SSL的消息解碼模型如下:

SSL消息讀取的入口都是decode,因為是非握手消息,它的處理非常簡單,就是循環(huán)調(diào)用引擎的unwrap方法,將SSL報文解碼為原始的報文,代碼如下:

握手成功之后的所有消息都是應(yīng)用數(shù)據(jù),因此它的操作結(jié)果為NOT_HANDSHAKING,遇到此標(biāo)識之后繼續(xù)讀取消息,直到?jīng)]有可讀的字節(jié),退出循環(huán),代碼如下:

如果讀取到了可用的字節(jié),則將讀取到的緩沖區(qū)加到輸出結(jié)果列表中,代碼如下:

ByteToMessageDecoder判斷解碼結(jié)果List,如果非空,則循環(huán)調(diào)用后續(xù)的Handler,由后續(xù)的解碼器對解密后的報文進行二次解碼。

3.4. SSL消息發(fā)送

SSL消息發(fā)送時,由SslHandler對消息進行編碼,編碼后的消息實際就是SSL加密后的消息,它的入口是flush方法,代碼如下:

從待加密的消息隊列中彈出消息,調(diào)用SSL引擎的wrap方法進行編碼,代碼如下:

wrap方法很簡單,就是調(diào)用SSL引擎的編碼方法,然后對寫索引進行修改,如果緩沖區(qū)越界,則動態(tài)擴展緩沖區(qū):

對SSL操作結(jié)果進行判斷,因為已經(jīng)握手成功,因此返回的結(jié)果是NOT_HANDSHAKING,執(zhí)行finishWrap方法,調(diào)用ChannelHandlerContext的write方法,將消息寫入發(fā)送緩沖區(qū)中,如果待發(fā)送的消息為空,則構(gòu)造空的ByteBuf寫入:

編碼后,調(diào)用ChannelHandlerContext的flush方法消息發(fā)送給對方,代碼如下:

      ctx.flush();
    

?

Netty系列之Netty安全性


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 6一10周岁毛片在线 717影院理论午夜伦八戒 | 精品国产精品a | 深夜在线影院 | 国产精品欧美久久久久天天影视 | 小h片在线播放 | 99国产精品2018视频全部 | 黄在线观看网站 | 射综合网 | 国产成人精品s8sp视频 | 国产精品福利影院 | 国产91久久精品一区二区 | 亚洲精品高清在线一区二区三区 | 免费高h影片在线观看 | 中国一级毛片免费观看 | 久久精品国产色蜜蜜麻豆 | 一区二区三区免费精品视频 | 国内外成人在线视频 | 天天干天天干天天干天天 | 天天操夜夜骑 | a一级毛片免费播放 | 免费 高清 日本1在线观看 | 日韩视频免费在线播放 | 色综合天天综合网亚洲影院 | 精品视频 九九九 | 久久精品国产欧美成人 | 亚洲精品在线网 | 九九影院韩国理伦片 | 成年女人视频免费免费看 | 亚洲国产精品网 | 午夜影网| 够爱久久 | 在线视频久久 | 久久精品亚洲欧美va | 色综合天天综合网国产人 | 国内久久 | 美利坚永久精品视频在线观看 | 亚洲综合五月天婷 | a久久| 久久精品国产精品亚洲精品 | 色色视频网 | 九七97影院理论片手机在线观看 |