前言
在前面一篇博客,对于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]}发送成功!
与原数据一致。