openssl在实现ssl握手的时候是用状态机实现的,实际上linux内核的tcp协议也是状态机实现的,原因就在于这些协议本身的握手规则就是状态机(简直是废话)。SSLv3握手规则要比tcp复杂的多,因此它的实现如果要既美观又高效的话就一定需要很多技巧,造成这种结果的原因就在于ssl握手过程中存在很多的可选消息,而这些可选消息是否存在并不简单依赖与前一个或者前若干个消息的消息头,而只有解析了前面的消息体内容之后才能知道是否存在可选消息,在这个意义上可以说客户端的握手状态机实现要么保存曾经的消息遗留的状态,要么不保留状态,默认按状态机转化顺序处理任何消息(还好,状态机有且只有一种顺序),如果采用前一个方案的话,那么势必要在握手规则和消息处理handler之间进行耦合,如果那一天改变了一方的框架,就会导致另一方的改变,openssl的实现采用了后一种解决方案巧妙的解决了这个问题,不管怎样状态机顺序地在所有可能的状态中转变,每一个状态中调用固定的处理函数,然后在处理函数中采用了一个技巧:处理函数首先不去下面的BIO(一般是tcp套接字)中读消息,而是先检查一个标志reuse,如果这个标志置位了,那么就清除这个标志,取上次读到的消息后返回,在返回状态机处理handler之后,首先根据得到消息的头判断其类型是否是状态机处于该状态所处理消息的对应类型,如果不是,上述的reuse标志重新置位,handler返回,状态机推进到下一个状态,从而在接下来的状态处理当中不再从套接字读取消息。大致框架就是:
state_machine()
{
while (true) {
switch (state)
case s1:
handler1(param);
state = s2;
break;
case s2:
handler2(param);
state = s3;
break;
case s3:
handler3(param);
state = s4;
break;
...
}
}
如果s2状态所对应的消息是可选的,但是s3是必须的状态,并且此次此时并不需要处理s2这个状态,但是handler2中确实要读取消息,怎么办呢?如果真的从套接字读取消息的话,那么处理就会乱掉,handler2中读取的是s3状态所对应的消息,那么怎么处理呢?openssl的方式是在case s2中调用handler2之中判断一下当前读到消息的类型,如果不是对应于s2的消息,那就说明s2状态所对应的消息不存在,于是在case s2的handler中进行完判断之后,将一个reuse标志设置上,于是在s3状态中的s3处理中首先判断是否有reuse标志,如果有的话,那么就不再从套接字读取消息,而是将在handler2中读取的消息返回,并且清除reuse标志,之后以此类推。总结一下就是只要在可选消息的handler处理之后,都要进行类似s2处理的判断,如果失败就将reuse设置。
但是这又引出了另外一个问题,那就是在每个handler中都要判断reuse标志,这个工作实在繁重,状态机处理是解放了,每个状态的handler的任务却更加繁重了,任何好的设计需要做到的就是尽量将繁杂的判断放到稳定的逻辑当中,状态机是不稳定的,因为有很多可选状态,每个状态的handler也不是稳定的,因为每个状态的处理太复杂了,随着版本的更新,任何细节的改变都会导致大量的更改,于是openssl就把这个逻辑抽取出来,专门放到SSL_METHOD的一个回调函数ssl3_get_message中,在调用完这个回调函数之后马上检查读到的消息类型是否是我们本状态要处理的类型,如果不是,那么马上将reuse设置为1,我们看一下ssl_get_message中在真正在套接字上读取消息之前的一个处理:
if (s->s3->tmp.reuse_message)
{
s->s3->tmp.reuse_message=0;
if ((mt >= 0) && (s->s3->tmp.message_type != mt)) {
...
}
*ok=1;
s->init_msg = s->init_buf->data + 4;
s->init_num = (int)s->s3->tmp.message_size;
return s->init_num;
}
以上代码是s->method->ssl_get_message中首先要调用的,对应上面代码的另一部分在ssl3_connect中可选消息handler的调用之后,以ssl3_get_key_exchange为例:
int ssl3_get_key_exchange(SSL *s)
{
...
n=s->method->ssl_get_message(s,
SSL3_ST_CR_KEY_EXCH_A,
SSL3_ST_CR_KEY_EXCH_B,
-1,
s->max_cert_list,
&ok);
...
if (s->s3->tmp.message_type != SSL3_MT_SERVER_KEY_EXCHANGE) {
s->s3->tmp.reuse_message=1;
return(1);
}
...
}
openssl的这种实现使得握手状态机处理看起来十分清晰,所有的处理消息是否可选的逻辑都隐藏到了逻辑比较单一的回调函数中,然后在逻辑复杂的处理函数中简单判断即可,这种分离比SSLv2的设计要更好,在SSLv2的状态机实现中,每一个状态处理完了之后就会判断结果,然后根据结果去设置下一个状态,其实这种方式更像是一个状态机,SSLv3的实现反而不像状态机了,一个一个顺序的处理,没有变数,这种情况其实完全不用状态机实现,一个顺序执行的函数就可以了,连循环都不用,谁知道以后的版本会怎样呢。
OpenSSL状态机中可选消息的处理
最新推荐文章于 2023-10-23 09:01:18 发布