前言
在前面一篇博客,對於clienthello的實現,我已經基本介紹完了,今天來介紹一下如何去模擬實現serverhello,結構的編寫是建立在之前的基礎上完成的,我只是模擬實現了當接收到clienthello時,根據clienthello中的數據隨機選擇一下serverhello需要的數據,然後將其打包編碼發送給原地址。
結果
老樣子,我們先來看一下測試的截圖,我還是使用wireshark截圖得到的截圖:
我們可以發現其中的ip地址是127.0.0.1,也就是回送地址,我是將ClientHello的數據發送到該地址,並且在發送數據之前會運行程序進行監聽,如果接收到數據,那麼會將數據解析出來,選取要使用的數據,生成serverhello發送回去。
- 127.0.0.1
127.0.0.1是回送地址,指本地機,一般用來測試使用。回送地址(127.x.x.x)是本機回送地址(Loopback Address),即主機IP堆棧內部的IP地址,主要用於網絡軟件測試以及本地機進程間通信,無論什麼程序,一旦使用回送地址發送數據,協議軟件立即返回,不進行任何網絡傳輸。 - ClientKeyShare
- ServerKeyShare
對比這兩張圖片,我們發現其中的公鑰是相同的,我們都清楚,在生成公鑰的時候需要選取隨機數,所以公鑰基本不會相同,即證實了這是我們從發送過來的數據中選取的,而非自己生成的。下面開始介紹如何實現的。
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_suite
、legacy_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]}發送成功!
與原數據一致。