1. 程式人生 > >OpenVPN協議解析-網路結構之外

OpenVPN協議解析-網路結構之外

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

引:

前面寫了不少關於OpenVPN的文章,那些文章大多數都是側重於網路的,要麼就是原始碼解析。其實OpenVPN還有更深的可挖掘的東西。只可惜我們很難搜到中文的,因此本文就增添幾筆。實際上,很多人都很看好OpenVPN,雖然它看起來是很簡陋,然而確實有很多值得挖掘的。

1.OpenVPN的基本框架答疑

如果用過OpenVPN並且瞭解過其實現,我們知道OpenVPN是一個巧妙構思的成果,網路通路部分使用了虛擬網絡卡構建一個虛擬的網路,這正合“虛擬專用網”之含義,而資料認證/加密則使用了SSL。然而如果我們真的去深究的話,事情又不是那麼簡單。

1.1.問題一:TCP隧道的問題

首先請閱讀《TCP封裝的隧道對於擁塞控制的意義》一文,內中敘述了TCP封裝隧道的問題,那就是在有大量背景流量的情況下,TCP隧道的效能將嚴重下降,最終導致網路不可用。
     OpenVPN需要一個隧道封裝,且是從使用者態開始的封裝,因此它需要要麼使用TCP,要麼使用UDP。CIPE的作者經過實測已經得到了TCP隧道將導致問題結論,由於站在巨人的肩上, OpenVPN首先選擇了UDP(當然它還是支援TCP的)。

1.2.問題二:SSL協議的問題

SSL分為握手協議和記錄協議,而記錄協議最開始在TCP上開發,因為SSL記錄協議要求下層鏈路的可靠性。然而又不是非用TCP不可,使用TCP只是用其兩個特性:其一是有連線-這可以很方便的實現SSL握手協議,其二是可靠傳輸-這可以方便實現SSL記錄協議。TCP的其它特性,諸如擁塞控制之類的,和SSL沒有任何關係,只會限制其被廣泛使用。
     最終,DTLS產生,在UDP上實現一個有連線且可靠傳輸的層,然後將SSL/TLS佈置於其上,這樣SSL/TLS就可以在TCP和UDP上跑了,實際上在某些場合是直接替代了TCP和UDP,成了名副其實的傳輸層協議。但是,這是2006年以後的事了,此時OpenVPN已經上路了。

1.3.問題三:綜合的事實因素

SSL首先選擇了TCP,而OpenVPN首先選擇了UDP,那時DTLS還沒有出生,這意味著OpenVPN不能直接使用SSL記錄協議。然而OpenVPN確實需要SSL握手協議這個良好的,簡單的,經過實戰的協議,因此從歷史和設計的角度,OpenVPN需要重新設計一套自己的協議,平滑適配這一系列的矛盾!

2.OpenVPN的協議設計思想

一個絕對的真理就是:增加一個層! 由於OpenVPN需要SSL握手協議,不一定需要SSL記錄協議,因此需要將SSL握手協議和自己的記錄協議適配在一起。

2.1.到底為何需要SSL握手協議

OpenVPN到底為何需要SSL握手協議?是需要其認證功能,還是需要其最終協商好的那個對稱金鑰。我們可以說,是前者。那麼很顯然就可以在SSL握手協議最終協商好的安全通道上協商新的OpenVPN的金鑰。
     事實上, 由於OpenVPN不想使用SSL記錄協議,因此它也就不便使用SSL握手協議最終協商的那個對稱金鑰。

2.2.IPSec的經驗

IPSec使用IKE來協商金鑰,我們知道,它分為兩個階段,第一階段實行認證功能,最終協商好一條安全加密通道,接下來第二階段在這個安全加密通道上協商真正的資料通道的加密金鑰。
     這個經驗十分有用,因為它實際上將認證和對稱金鑰的協商之間的耦合解除了,這樣就可以實現各種靈活的金鑰協商方式,並且可以將認證和金鑰協商使用各自的策略各自進行互不影響。最顯然的,需要重新協商金鑰的時候,不需要再次進行認證。

2.3.兩階段協商對OpenVPN的影響

IKE兩階段的協商幾乎很肯定的對OpenVPN產生了影響。OpenVPN需要自己的記錄協議,而能將握手認證和記錄協議分開的方式就是將通道分離。首先我們先看看這種OpenVPN實現的記錄協議需要什麼特徵。 由於OpenVPN記錄協議只是作用於一個隧道,且它封裝的是一個IP資料報,因此OpenVPN的協議只需要加密和HMAC即可,完全不需要可靠傳輸。

2.4.OpenVPN的記錄協議和SSL記錄協議的區別

SSL直接接於傳輸層協議之上,它在保證安全的同時一定不能更改應用的語義,比如http是tcp的,那麼https也需要是tcp的,SSL原本就是設計用於加密應用資料而不是構建三層隧道的。而OpenVPN的記錄協議保護的只是一個IP資料報的再封裝,最終的端系統應用才負責傳輸層語義,因此OpenVPN不需要可靠傳輸。

2.5.如何將SSL握手協議和OpenVPN的記錄協議封裝在一個協議中

還是那句話:增加一個層!實際上就是增加一個“多路解複用碼”,通過對該欄位的解析,識別協議包是握手包還是記錄包。這只是第一點,還有一點必須要實現的就是將SSL握手訊息封裝在OpenVPN協議中,由於OpenVPN使用了OpenSSL,這一點是通過BIO實現的。

3.OpenVPN協議

3.1.OpenVPN協議總攬

OpenVPN協議的封裝格式如下圖所示:

注意,正是OVPN頭封裝了OpenVPN的握手協議和記錄協議,它起到了複用和解複用的功能,按通道劃分,OpenVPN的通道有兩類,一類是控制通道,另一類是資料通道,其中的操作碼在該層協議上有“地址”的含義,正是操作碼區分了控制通道和資料通道-換句話說就是控制協議和記錄協議,其中控制協議分為握手協議和金鑰協議,類似IP協議的IP地址,TCP/UDP的埠。操作碼的含義如下:
P_CONTROL_HARD_RESET_CLIENT_V1:Key method 1, initial key from client, forget previous state.
P_CONTROL_HARD_RESET_SERVER_V1:Key method 2, initial key from server, forget previous state.
P_CONTROL_SOFT_RESET_V1:New key
P_CONTROL_V1:Control channel packet (usually TLS ciphertext).
P_ACK_V1:Acknowledgement for P_CONTROL packets received.
P_DATA_V1:Data channel packet containing actual tunnel data ciphertext.
P_CONTROL_HARD_RESET_CLIENT_V2:Key method 2, initial key from client, forget previous state.
P_CONTROL_HARD_RESET_SERVER_V2:Key method 2, initial key from server, forget previous state.
     我們可以將上述的每一個操作碼看做是一個子協議。KEY ID的作用很有意思,它表示一個key集合,OpenVPN中一共有3個key集合,想了解其中要旨需要再稍等片刻。

3.2.OpenVPN記錄協議

OpenVPN記錄格式如下圖所示:

其中HMAC用於認證包的完整性,IV用於CBC解密,Packet ID用於防止重放,OpenVPN記錄協議包含了一個非常簡易的滑動視窗機制用於防止重放。通過和SSL記錄協議做對比,我們發現它們真的很像,只不過SSL記錄協議的下面是複雜的TCP協議,而OpenVPN記錄協議下面首選的卻是UDP協議。
     可以看到,OpenVPN記錄協議並不是ACK的相關資訊,因此它封裝的是一個“盡力而為”的網路。

3.3.OpenVPN控制協議

這個協議包含了兩個部分,第一部分是握手協議,第二部分是金鑰協議。由於OpenVPN使用SSL握手協議,為何不使用SSL握手協議最終協商出來的那個對稱金鑰,這是一個值得思考的問題。這裡給出一些思路:
a.IPSec所使用的IKE兩階段模式無疑對所有VPN幾乎都有很大的影響,第一階段構建的安全通道用於第二階段的金鑰協商而不是最終的資料傳輸;
b.SSL握手最終協商的那個金鑰和SSL握手過程是分不開的,而OpenVPN最終的設計是希望將握手過程和資料傳輸過程完全分離,從而獨立的實現單一的金鑰重協商;
c.這樣實現的OpenVPN協議更加簡潔。
我們看一下OpenVPN的控制協議:

這裡面最重要的恐怕要算ACK buffer了,由於SSL握手協議需要一個可靠的下層,因此需要確認機制,值得注意的是,不要一說確認就是TCP,UDP也可以實現的,只是這裡的UDP實現的重傳機制很簡陋,確認機制也很簡陋。另外還有一點值得注意,那就是OpenVPN只在控制協議中使用可靠機制的協議,在記錄協議中是不使用的。
     沒有必要浪費太多的時間在OpenVPN的握手協議上,它僅僅是對SSL握手的一個封裝而已,最終協商出一個加密的安全通道,金鑰協議將運行於其上,實際上金鑰協議的載荷也是封裝在OpenVPN的控制協議頭裡面的。
     接下來,我們來看一下OpenVPN的金鑰交換/協商/管理協議

3.4.OpenVPN控制協議之-金鑰協議

OpenVPN自己實現了金鑰管理,金鑰交換,金鑰重協商等,需要注意的是,它並不使用SSL握手最終確定的那些金鑰,OpenVPN使用SSL握手僅僅是完成了連線者身份認證以及為後續的金鑰管理,金鑰交換,金鑰交換以及控制通道構建一個安全的加密通道,完全類似於IKE的第一階段協商。
     值得說明的是,雖然不斷開SSL連線就可以重新協商金鑰,也就是僅進行第二階段,然而當前的OpenVPN實現中,reneg選項實現的是兩個階段同時重新協商,也就是說SSL握手也要重新進行。由於SSL的BIO直接接於一個記憶體BIO上,因此斷開SSL連線並不意味著底層的傳輸層連線斷開,OpenVPN實現了資料通道和控制通道的複用,因此任何通道都不是真正的物理通道,都是虛擬的,重新協商任何引數都不需要斷開連線。在重新協商安全引數的時候,絲毫不影響資料通道的正常傳輸。
     目前OpenVPN實現了兩類金鑰交換的方法,第一類比較簡單,相當於直接告訴對方解密金鑰了,雖然這樣,依然很安全,因為這裡的“告訴”並不是明文告知,而是在SSL握手後的加密通道中告知的,它的訊息格式如下:

通過訊息格式可知,一對連線中,一共需要4個金鑰,一個方向2個,這也TM太像IPSec的SA了啊!
     第二類方式稍微複雜一些,然而它又很像SSL握手協議中的金鑰交換方法,使用了一個pre master的金鑰,然後兩方分別進行計算,最終算出相同的金鑰。第二類金鑰交換的資料格式如下:

最終,一個金鑰匯出函式計算出最終的金鑰,關於金鑰匯出函式的細節,請google,或者Email給我,要是我不知道,我們一起學習。從第二類方式的訊息格式中可以看出,pre-master的使用方式和標準的SSL握手中最終的金鑰協商的方式一樣,只不過SSL握手中這個pre-master是用server端的公鑰加密的,而這裡的pre-master直接使用SSL握手確定的對稱金鑰加密。第一類方式很像IPSec的SA,第二類方式很像SSL握手中的金鑰協商,可見OpenVPN真的站在了巨人肩上啊。
     最後,有個KEY ID十分奇妙,它的作用就是金鑰更新時的平滑過渡。OpenVPN一共定義了3組金鑰集合,其中只有一組是當前活動的,然而在新的活動金鑰沒有生效之前,還是可以用老的。值得再次強調的是,重新協商並不斷開任何連線,雖然重新初始化了SSL,但是記住,SSL只是一個過濾,和Socket沒有關係的,它可以用於任何管道性質的檔案描述符之間的安全通道協商,在OpenVPN的場景下,重新協商SSL時,底層的UDP連線仍然沒有斷開(這裡所說的UDP連線,是因為UDP之上實現了一個Reliability Layer),只要OpenVPN客戶端連上伺服器,它就不會再因為重新協商而主動斷開,除非協商失敗,比如證書已經被吊銷等。

3.5.OpenVPN協議總結

現在總結一下OpenVPN的協議。
     它真的很複雜,我們先看一幅可以表示OpenVPN協議總體設計的圖,我並沒有按照傳統的網路協議按照控制面和使用者面來畫圖,那樣太抽象,我選擇的方式是直接的方式:

通過這幅圖我們再來溫習一下OpenVPN協議的設計。如圖所示,雖然協議分了好幾部分,每一個部分又分若干層次,然而它們在橫向上是有聯絡的。從設計上說,首先是安全認證和第一階段的握手,這和IKE一致,第一階段握手為第二階段金鑰協商提供了安全通道,然後第二階段最終協商出了加密/MAC金鑰。從此記錄協議和控制協議分離,直到再次協商。 這是OpenVPN協議的第一個要點:分層次,分階段。
     OpenVPN協議的第二個要點就是多路複用到UDP。這樣就不必為SSL協商之類的控制面專門設定一個通道,OpenVPN巧妙使用OpenSSL的BIO機制劫持了SSL握手包,實現了對其的封裝,這樣所有的協議都可以複用了一個UDP“連線”上,因此重新協商金鑰的時候,資料傳輸不會停止。
     OpenVPN的第三個要素有點歷史原因,OpenVPN完全可以使用SSL記錄協議來完成最終的資料加密,然而卻面對著TCP重傳疊加的問題,當時DTLS還沒有出現,然而SSL握手實在太好了,因此OpenVPN做出了一個漂亮的折中,實現了協議的重新封裝,保留了SSL握手,重新實現了“盡力而為”的記錄協議。這樣做的結果是,OpenVPN可以在本來已經很安全的SSL握手協商上又做了一層封裝,可以實現比如tls-auth的功能,對TLS/SSL握手訊息本身進行完整性驗證,這樣就預防了Dos攻擊,消耗掉伺服器的資源(SSL連線的處理很消耗資源,即使不消耗什麼記憶體,消耗CPU是巨大的)...同時,大量顯式的SSL連線也不復存在,再者,由於金鑰交換和SSL握手徹底分離,這樣可以實現更多的選擇,比如可以配置cipher為none,然而SSL握手還是安全的,或者說僅僅提供資料的HMAC驗證,此時SSL握手以及後面的HMAC金鑰協商依然是安全的...要知道,SSL握手的結果永遠都是協商出一條安全加密的通道,你在這通道上作甚,你自己配置,這就是分離的好處。

3.6.使用BIO封裝SSL握手訊息

這一點實際上並不是特別的重要,因為它是實現相關的,要不是OpenVPN使用了OpenSSL這個開源的SSL庫或者OpenSSL中沒有BIO,那OpenVPN還真得費點周折去思索如何得到SSL握手訊息然後再封裝成OpenVPN自己的控制訊息。
     BIO在這裡我就不再深入介紹了,它很簡單,就是一個堆疊式的IO部件,每一層都有一定的功能,比方說過濾,十分類似於Windows的IRP的概念,如果實在不懂,要麼google,要麼直接看OpenSSL的實現,但是千萬別百度。為了說明如何使用BIO封裝SSL握手訊息,給出一幅圖:

4.OpenVPN連線建立

OpenVPN連線的建立顯得很詭異,不過本文看到此處,你就不覺得什麼詭異了。如果不瞭解OpenVPN的協議設計,僅僅知道它使用了SSL握手的話,你會感到很費解,因為你用ssldump或者tcpdump抓取不到任何SSL握手的訊息。實際上這很好解釋,那就是SSL握手訊息被OpenVPN的協議給封裝了,上一小節講述瞭如何封裝SSL握手訊息,使用OpenSSL的BIO可以很方便的做到這一點,實際上,OpenSSL本身將SSL型別的BIO設計成一個過濾型BIO,這才是精妙所在,這樣SSL訊息可以直接發往任何型別的IO實體了,而不一定必須是網路套接字了。
     OpenVPN連線的建立大致分為4個階段,每一個階段完成不同的事情,如下圖所示:

5.OpenVPN實現

略!最終,還是貼上OpenVPN原始碼中關於協議實現的註釋:
OpenVPN Protocol, taken from ssl.h in OpenVPN source code.
TCP/UDP Packet:  This represents the top-level encapsulation.
TCP/UDP packet format:
Packet length (16 bits, unsigned) -- TCP only, always sent as plaintext.  Since TCP is a stream protocol, the packet length words define the packetization of  the stream.

Packet opcode/key_id (8 bits) -- TLS only, not used in pre-shared secret mode.
packet message type, a P_* constant (high 5 bits)
key_id (low 3 bits, see key_id in struct tls_session below for comment).  The key_id refers to an already negotiated TLS session.  OpenVPN seamlessly
renegotiates the TLS session by using a new key_id for the new session.  Overlap (controlled by user definable parameters) between old and new TLS sessions is allowed, providing a seamless transition during tunnel operation.

Payload (n bytes), which may be a P_CONTROL, P_ACK, or P_DATA message.
Message types:
P_CONTROL_HARD_RESET_CLIENT_V1 -- Key method 1, initial key from client, forget previous state.
P_CONTROL_HARD_RESET_SERVER_V1 -- Key method 2, initial key from server, forget previous state.
P_CONTROL_SOFT_RESET_V1 -- New key, with a graceful transition from old to new key in the sense that a transition window exists where both the old or new key_id can be used.  OpenVPN uses two different forms of key_id.  The first form is 64 bits and is used for all P_CONTROL messages.  P_DATA messages on the other hand use a shortened key_id of 3 bits for efficiency reasons since the vast majority of OpenVPN packets in an active tunnel will be P_DATA messages.  The 64 bit form is referred to as a session_id, while the 3 bit form is referred to as a key_id.
P_CONTROL_V1 -- Control channel packet (usually TLS ciphertext).
P_ACK_V1 -- Acknowledgement for P_CONTROL packets received.
P_DATA_V1 -- Data channel packet containing actual tunnel data ciphertext.
P_CONTROL_HARD_RESET_CLIENT_V2 -- Key method 2, initial key from client, forget previous state.
P_CONTROL_HARD_RESET_SERVER_V2 -- Key method 2, initial key from server, forget previous state.
P_CONTROL* and P_ACK Payload:  The P_CONTROL message type indicates a TLS ciphertext packet which has been encapsulated inside of a reliability layer.  The reliability layer is implemented as a straightforward ACK and retransmit model.
P_CONTROL message format:
local session_id (random 64 bit value to identify TLS session).
HMAC signature of entire encapsulation header for integrity check if --tls-auth is specified (usually 16 or 20 bytes).
packet-id for replay protection (4 or 8 bytes, includes
sequence number and optional time_t timestamp).
P_ACK packet_id array length (1 byte).
P_ACK packet-id array (if length > 0).
P_ACK remote session_id (if length > 0).
message packet-id (4 bytes).
TLS payload ciphertext (n bytes) (only for P_CONTROL).

Once the TLS session has been initialized and authenticated,the TLS channel is used to exchange random key material for bidirectional cipher and HMAC keys which will be used to secure actual tunnel packets.  OpenVPN currently implements two key methods.  Key method 1 directly derives keys using random bits obtained from the RAND_bytes OpenSSL function.  Key method 2 mixes random key material from both sides of the connection using the TLS PRF mixing function.  Key method 2 is the preferred method and is the default for OpenVPN 2.0.

TLS plaintext content:
TLS plaintext packet (if key_method == 1):
Cipher key length in bytes (1 byte).
Cipher key (n bytes).
HMAC key length in bytes (1 byte).
HMAC key (n bytes).
Options string (n bytes, null terminated, client/server options string should match).

TLS plaintext packet (if key_method == 2):
Literal 0 (4 bytes).
key_method type (1 byte).
key_source structure (pre_master only defined for client -> server).
options_string_length, including null (2 bytes).
Options string (n bytes, null terminated, client/server options string must match).
[The username/password data below is optional, record can end at this point.]
username_string_length, including null (2 bytes).
Username string (n bytes, null terminated).
password_string_length, including null (2 bytes).
Password string (n bytes, null terminated).

The P_DATA payload represents encrypted, encapsulated tunnel packets which tend to be either IP packets or Ethernet frames. This is essentially the "payload" of the VPN.


P_DATA message content:
HMAC of ciphertext IV + ciphertext (if not disabled by --auth none).
Ciphertext IV (size is cipher-dependent, if not disabled by --no-iv).
Tunnel packet ciphertext.

P_DATA plaintext
packet_id (4 or 8 bytes, if not disabled by --no-replay).
In SSL/TLS mode, 4 bytes are used because the implementation can force a TLS renegotation before 2^32 packets are sent. In pre-shared key mode, 8 bytes are used (sequence number and time_t value) to allow long-term key usage without packet_id collisions.
User plaintext (n bytes).

Notes:
(1) ACK messages can be encoded in either the dedicated P_ACK record or they can be prepended to a P_CONTROL message.
(2) P_DATA and P_CONTROL/P_ACK use independent packet-id sequences because P_DATA is an unreliable channel while P_CONTROL/P_ACK is a reliable channel.  Each use their own independent HMAC keys.
(3) Note that when --tls-auth is used, all message types are protected with an HMAC signature, even the initial packets of the TLS handshake.  This makes it easy for OpenVPN to throw away bogus packets quickly, without wasting resources on attempting a TLS handshake which will ultimately fail.
           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述