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]}发送成功!

与原数据一致。

猜你喜欢

转载自blog.csdn.net/qq_35324057/article/details/105750773
今日推荐