TLS1.3實現篇---serverhello

前言

在前面一篇博客,對於clienthello的實現,我已經基本介紹完了,今天來介紹一下如何去模擬實現serverhello,結構的編寫是建立在之前的基礎上完成的,我只是模擬實現了當接收到clienthello時,根據clienthello中的數據隨機選擇一下serverhello需要的數據,然後將其打包編碼發送給原地址。

結果

老樣子,我們先來看一下測試的截圖,我還是使用wireshark截圖得到的截圖:

tls1.3
我們可以發現其中的ip地址是127.0.0.1,也就是回送地址,我是將ClientHello的數據發送到該地址,並且在發送數據之前會運行程序進行監聽,如果接收到數據,那麼會將數據解析出來,選取要使用的數據,生成serverhello發送回去。

  • 127.0.0.1
    127.0.0.1是回送地址,指本地機,一般用來測試使用。回送地址(127.x.x.x)是本機回送地址(Loopback Address),即主機IP堆棧內部的IP地址,主要用於網絡軟件測試以及本地機進程間通信,無論什麼程序,一旦使用回送地址發送數據,協議軟件立即返回,不進行任何網絡傳輸。
  • ClientKeyShare
    client_key_share
  • ServerKeyShare
    server_key_share
    對比這兩張圖片,我們發現其中的公鑰是相同的,我們都清楚,在生成公鑰的時候需要選取隨機數,所以公鑰基本不會相同,即證實了這是我們從發送過來的數據中選取的,而非自己生成的。下面開始介紹如何實現的。

ServerHello

我們先來看一下ServerHello的結構,與ClientHello基本一致,不過還是存在一些不同的地方。

type ServerHello struct {
	legacy_version 				tls.ProtocolVersion
	random						tls.ClientRandom
	legacy_session_id			tls.LegacySessionId
	cipher_suite				tls.CipherSuite
	legacy_compression_method	tls.LegacyCompressionMethod
	extensions					tls.Extensions
}

其中的幾個字段和ClientHello還是有區別的,cipher_suitelegacy_compression_method我們都需要取單個,也就是說它裏面的數據不再是成一組了,而是一個,所以不再需要組的長度,因此需要去掉長度字段。下面具體來看一下。

Serverkeyshare

type Serverkeyshare struct {
	share 		tls.KeyShareEntry
}

func NewServerkeyshare(share tls.KeyShareEntry) Serverkeyshare {
	return Serverkeyshare{
		share:		share,
	}
}

與前面所說的類似,keyshare擴展也只是包含一個KeyShareEntry,所以不再設置length字段。

type supportedversion struct {
	version tls.ProtocolVersion
}

func Newsupportedversion(version tls.ProtocolVersion) supportedversion {
	return supportedversion{
		version:version,
	}
}

同時supportedversion是類似的,也只是選取一個自己所支持的版本,從而不需要設置成數組,也不許要length字段。

ChooseCipherSuites

我的目的是模擬實現,所以我採用隨機選取的方式,而不是選取客戶端傾向最高的,或者說排在數組前列的,所以我寫了一個選取的函數,以選取ciphersuite爲例:

func ChooseCipherSuites(cs []tls.CipherSuite) tls.CipherSuite {
	var result tls.CipherSuite
	rand.Seed(time.Now().Unix())
	num := rand.Intn(10)
	for index,cp := range cs{
		if index == num{
			result = cp
		} else{
			continue
		}
	}
	return result
}

主要就是首先從解析出來的數據中提取客戶端使用的密碼套件組,然後傳進來選取就可以了。

解析ClientHello

我們還是拿例子來說,不會一一解釋的。

解析KeyShare

func DeserializeKeyShare(buf []byte) (KeyShare,int) {
	value := binary.BigEndian.Uint16(buf[0:2])

	var shares []KeyShareEntry

	bufferPosition := 0
	shares_length := value

	bufferPosition += 2


	for{
		if bufferPosition >= int(shares_length+2){
			break
		}else{
			group := binary.BigEndian.Uint16(buf[bufferPosition:bufferPosition+2])
			bufferPosition += 2
			length := binary.BigEndian.Uint16(buf[bufferPosition:bufferPosition+2])
			bufferPosition += 2
			var entry KeyShareEntry
			entry.group = NamedGroup(group)
			entry.length = length
			entry.keyExchange = buf[bufferPosition:bufferPosition+int(length)]
			shares = append(shares,entry)
			bufferPosition += int(length)
		}
	}
	var entrys KeyShareEntrys
	entrys.Entrys = shares
	return KeyShare{
		length:		KsSize(value),
		Shares:		entrys,
	},2+int(value)
}

主要還是說一下思想,我覺得思想最重要,因爲傳過來的數據是大端編碼的字節類型,所以我們要根據字段的數據類型進行類型轉換,我覺得在整個實現過程中數據類型的轉換最爲重要,如果不太清楚KeyShare的結構請看我前一篇博客。首先是length字段,類型是uint16,所以需要將byte轉換成uint16,16位佔兩個字節,所以取數組的前兩個字節。因爲裏面存在着多個KeyShareEntry所以需要設置標誌位,標識解析到的位置,直到當標誌位等於KeyShare的長度,此時將退出循環,最後返回KeyShare和它的長度。循環中我們把每一個KeyShareEntry中包含的字段解析出來,並且重新生成一個新的KeyShareEntry,並且放入集合中,標誌位也隨之改變。
這樣一個擴展就解析好了,之後再一層層的解析直到TLSPlaintext,將需要的字段數據返回,至此解析完成。

main函數

那麼怎樣進行監聽的呢?

func main(){
	//getclienthello
	listen,err := net.Listen("tcp","127.0.0.1:443")
	chkError(err)
	fmt.Println("正在監聽!")
	myconn,err := listen.Accept()
	chkError(err)

	defer myconn.Close()

	response := make([]byte,0,65536)
	tmp := make([]byte,65536)

	n,err := myconn.Read(tmp)
	chkError(err)
	response = append(response, tmp[:n]...)
	fmt.Println("正在發送!")
	defer myconn.Close()
	// decode clienthello&send serverhello
	_,_,clienthello_mess,k:= tls.DeserializeTLSPlaintext(response)
	//extension
	client_cipher := clienthello_mess.CipherSuites
	fmt.Printf("%x\n",k.Shares.Entrys)
	fmt.Printf("%x",client_cipher)
	supportedVersion := server.NewsupportedversionExtension(tls.TLS13)
	keyshare := server.NewServerKeyShareExtension(k.Shares.Entrys)//填入entrys
	//body
	ServerHello := server.NewServerHello(client_cipher.Ciphersuites,keyshare,supportedVersion)
	ServerHandshake := tls.NewHandshake(tls.HandshakeTypeServerHello,ServerHello.GetSerialization())
	ServerHandshakeMessage := tls.NewTLSPlaintext(tls.RecordTypeHandshake,ServerHandshake.GetSerialization())

	err = binary.Write(myconn,binary.BigEndian,ServerHandshakeMessage.GetSerialization().Serialize())
	chkError(err)

	myconn.Close()

	fmt.Println("發送成功!")
}

使用的是net包,之前發送ClientHello也是使用的該包,不過這次是使用的listen函數,它的功能就是監聽某地址有沒有請求發送過來,利用它我們可以搭建自己的服務器,具體深入的東西還需要時間學習,在這裏就不展開了,Accept()方法 作用在 *Listener上,返回一個通用的 Conn類型數據與客戶端進行通訊,依據解析出的數據生成ServerHello,然後編碼成大端字節發送回去,這就是整個的一個過程。我們再來看一下我打印出的解析數據:

正在監聽!
正在發送!
[{1d 20 5a8d31c2a6272de4261c3a027563a93139bcdacc6f120511457b803300000000} {17 41 0495495e89bebb75a1222f7fd339a9d93ecf91ea850efa12ec87218c8a83609181ec59e6711409530ac645a0176ed5fd686855634135b1de3f0ff2847a00000000} {18 61 04500632eec9ed80e735473c59402c9baaaf2b83cfd290289ffc845c4250d0322a7002d3eff7064c3f2bc26bf460f848d04dda76e1a66a0a6026744d1269296801fe26b22d857273051db9fdaa36ece9df823b3f52ff5883f9059d8ed500000000}]
{16 [1301 1304 1302 1305 c02b 1303 c02f c014 c028 c030 35]}發送成功!

與原數據一致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章