【ProVerif学习笔记】6:握手协议(handshake protocol)建模

这节先不继续往下看,巩固一下对ProVerif建模握手协议的理解。

握手协议的流程如下:
在这里插入图片描述

ex_handshake.pv(只验证保密性)

手册里在引入security性质之前,握手协议的模型如下:

(* ------------------------对称加密相关------------------------ *)
(* 对称密钥 *)
type key.
(* 对称加密:传入要加密的内容和密钥,返回加密结果 *)
fun senc(bitstring, key): bitstring.
(* 对称解密:用于析构对称加密,传入加密结果senc(m,k),用同一个密钥k可解密出原始信息m *)
reduc forall m: bitstring, k: key; sdec(senc(m,k),k) = m.


(* ------------------------非对称加密相关------------------------ *)
(* 非对称加密-私钥 *)
type skey.
(* 非对称加密-公钥 *)
type pkey.
(* 从私钥获取公钥:可以用于密钥配对 *)
fun pk(skey): pkey.
(* 非对称加密:传入要加密的内容和公钥,返回加密结果 *)
fun aenc(bitstring, pkey): bitstring.
(* 非对称解密:用于析构非对称加密,传入加密结果aenc(m,pk(sk)),用其私钥sk可解密出原始信息m *)
reduc forall m: bitstring, sk: skey; adec(aenc(m,pk(sk)),sk) = m.


(* ------------------------数字签名相关------------------------ *)
(* 数字签名-私钥 *)
type sskey.
(* 数字签名-公钥 *)
type spkey.
(* 从私钥获取公钥:可以用于密钥配对 *)
fun spk(sskey): spkey.
(* 数字签名:传入要签名的内容和私钥,返回签名结果 *)
fun sign(bitstring, sskey): bitstring.
(* 获取消息:无视签名用的私钥ssk,直接获取其中的消息m *)
reduc forall m: bitstring, ssk: sskey; getmess(sign(m,ssk)) = m.
(* 验证签名后获取消息:使用公钥spk(ssk)方能对使用ssk签名的消息验证,并成功取出m *)
reduc forall m: bitstring, ssk: sskey; checksign(sign(m,ssk),spk(ssk)) = m.


(* ------------------------全局声明相关------------------------ *)
(* 通信信道c,是一个公有的信道 *)
free c:channel.
(* 要传输的保密消息s,由于使用[private]修饰,所以攻击者无法直接获取 *)
free s:bitstring [private].


(* ------------------------查询相关------------------------ *)
(* 查询攻击者能否窃取s,即关于s的保密性 *)
query attacker(s).


(* ------------------------进程相关------------------------ *)
(* 客户端进程A:A公钥、A私钥、B公钥 *)
let clientA(pkA:pkey,skA:skey,pkB:spkey) =
	(* 将A公钥发送到通道c *)
	out(c,pkA);
	(* 从通道c取回B传来的结果x *)
	in(c,x:bitstring);
	(* 用自己的私钥skA对结果x解密得到y *)
	let y = adec(x,skA) in
	(* 用B的公钥pkB对y验证,验证结果需是(pkB, k),第二项k即是对称密钥 *)
	let (=pkB,k:key) = checksign(y,pkB) in
	(* 使用对称密钥k对消息明文s加密并发送 *)
	out(c,senc(s,k)).

(* 服务器进程B:B公钥、B私钥 *)
let serverB(pkB:spkey,skB:sskey) =
	(* 从通道c取出公钥,是A的公钥 *)
	in(c,pkX:pkey);
	(* 新建一个对称密钥k *)
	new k:key;
	(*(pkB,k)用B的私钥skB签名,然后用A的公钥pkX加密,发到通道c *)
	out(c,aenc(sign((pkB,k),skB),pkX));
	(* 从通道c拿到A发来的消息x *)
	in(c,x:bitstring);
	(* 使用对称密钥k解密即可得到消息明文z *)
	let z = sdec(x,k) in
	0.

(* 总的进程 *)
process
	(* A的私钥 *)
	new skA:skey;
	(* B的私钥 *)
	new skB:sskey;
	(* A的公钥,生成后发到通道c上,攻击者可获取 *)
	let pkA = pk(skA) in out(c,pkA);
	(* B的公钥,生成后发到通道c上,攻击者可获取 *)
	let pkB = spk(skB) in out(c,pkB); 
	(* 实际行为是无限个客户端A和无限个服务器B的并发执行 *)
	( (!clientA(pkA,skA,pkB)) | (!serverB(pkB,skB)) )

整体就是两类进程,客户端clientA和服务器serverB。认为一个服务器B的进程实体是可以服务任何一个具体的客户端的,所以在它的macro里只传入了自己签名用的公钥和私钥。而一个客户端A的进程实体只会找一个具体的服务器,所以是在自己的macro里传入了想要服务自己的服务器B的公钥,在这里定死了。

这里希望有无数的进程在并发执行,对客户端和服务器都是做了replication(符号!)然后再并发。

还有要注意的就是之前学习的解构器(destructor)模式匹配(pattern matching) 的使用。

解构器本身如果传入的东西不匹配就会解构失败, 解构失败会走let-in-else的else路线,如果else被省略掉(像上面一样)就会把进程直接halt掉,所以一般解构器本身就能检查传过来的东西是不是按期望的合规的。

模式匹配可以和解构器一起用,这样不仅要求解构成功,还需要模式匹配成功才能走let-in-else的in路线,否则一律走else路线。比如

let (=pkB,k:key) = checksign(y,pkB)

这一行,不仅要求y是用pkB对应的私钥skB签名的(这样才能解构成功),而且要求解构后的内容能匹配到一个二元组形式(分别是签名方公钥和签名方提供的对称密钥),还要求二元组的第二项能匹配到一个key类型(对称密钥),二元组的第一项能匹配到公钥pkB上(和自己期望的pkB是相同的,这块只是签名方自己声明自己的身份,其实在密码学上还不够强,比如第二节里学习到可能遭受中间人攻击)。

这个是最原始的握手协议,因为可能遭受中间人攻击,所以验证结果是不通过的:

--------------------------------------------------------------
Verification summary:

Query not attacker(s[]) is false.

--------------------------------------------------------------

ex_handshake_annotated.pv(加入对认证性的验证)

手册里3.2节讲security性质的时候引用了这个case,这里“annotated”意思就是如果要证明authentication(一种security性质,表达认证关系,包括用correspondence断言和injective correspondence断言两种声明),那么是需要用event来标记程序点的:

To reason with correspondence assertions, we annotate processes withevents, which markimportant stages reached by the protocol but do not otherwise affect behavior.

因为correspondence断言就是用来捕获事件先后关系的:

“if an event e has been executed, then event e′ has been previously executed.”

这种标记不会影响进程本身的行为,也不会让攻击者的知识有任何的增加或者减少。添加了事件标记的模型如下:

type key.
fun senc(bitstring, key): bitstring.
reduc forall m: bitstring, k: key; sdec(senc(m,k),k) = m.

type skey.
type pkey.
fun pk(skey): pkey.
fun aenc(bitstring, pkey): bitstring.
reduc forall m: bitstring, sk: skey; adec(aenc(m,pk(sk)),sk) = m.

type sskey.
type spkey.
fun spk(sskey): spkey.
fun sign(bitstring, sskey): bitstring.
reduc forall m: bitstring, ssk: sskey; getmess(sign(m,ssk)) = m.
reduc forall m: bitstring, ssk: sskey; checksign(sign(m,ssk),spk(ssk)) = m.

free c:channel.
free s:bitstring [private].
query attacker(s).

(* 客户端认为自己在使用key对称密钥和服务器进行协议交互 *)
event acceptsClient(key).
(* 服务器认为自己在使用key对称密钥和pkey公钥标识的客户端进行协议交互 *)
event acceptsServer(key,pkey).
(* pkey标识的客户端认为自己使用key和服务器进行协议交互结束 *)
event termClient(key,pkey).
(* 服务器认为自己使用key对称密钥和客户端进行协议交互结束 *)
event termServer(key).

(*
对于每个以对称密钥x和服务器交互结束的客户端y
前面总是存在认定以对称密钥x和客户端y交互的服务器
*)
query x:key,y:pkey; event(termClient(x,y))==>event(acceptsServer(x,y)).
(*
对于每个以对称密钥x交互结束的服务器
前面总是存在一个区别于其它的“以对称密钥x和服务器交互的客户端”
即单射关系
*)
query x:key; inj-event(termServer(x))==>inj-event(acceptsClient(x)).

let clientA(pkA:pkey,skA:skey,pkB:spkey) = 
	out(c,pkA);
	in(c,x:bitstring); 
	let y = adec(x,skA) in
	let (=pkB,k:key) = checksign(y,pkB) in
	(* 客户端解密成功+验证成功后即认为接受了使用密钥k通信 *)
	event acceptsClient(k);
	out(c,senc(s,k));
	(* 客户端运行结束,本次是使用k通信,自己的公钥是pkA *)
	event termClient(k,pkA).

let serverB(pkB:spkey,skB:sskey,pkA:pkey) = 
	in(c,pkX:pkey);
	new k:key;
	(* 服务器收到客户端公钥pkX,并创建了密钥k,即认为接受了使用密钥k和客户端pkX通信 *)
	event acceptsServer(k,pkX);
	out(c,aenc(sign((pkB,k),skB),pkX));
	in(c,x:bitstring);
	let z = sdec(x,k) in
	(* 如果接收到的pkX确实是pkA,即认为服务器使用对称密钥k运行结束 *)
	if pkX = pkA then event termServer(k).

process
	new skA:skey;
	new skB:sskey;
	let pkA = pk(skA) in out(c,pkA);
	let pkB = spk(skB) in out(c,pkB);
	( (!clientA(pkA,skA,pkB)) | (!serverB(pkB,skB,pkA)) )

注意这一版不仅加入了event,而且在serverB的macro中传入了pkA,这是因为termServer本身需要判断自己交互的pkX是pkA。

这一版只是加了认证性质的验证,但是还是没有解决中间人攻击问题。

--------------------------------------------------------------
Verification summary:

Query not attacker(s[]) is false.

Query event(termClient(x_2,y_1)) ==> event(acceptsServer(x_2,y_1)) is false.

Query inj-event(termServer(x_2)) ==> inj-event(acceptsClient(x_2)) is true.

--------------------------------------------------------------

结果第一条表明,对消息s还是不满足的。

结果第二条表明,从B(服务器)到A(客户端)的认证是不满足的,也就是说如果客户端觉得自己和服务器完成了协议流程,不一定真的有这个服务器在和客户端走协议,这个也是因为中间人可以冒充服务器来和客户端通信。

结果第三条表明,从A(客户端)到B(服务器)的认证是满足的,也就是说如果服务器觉得自己走完了协议流程,一定至少有一个客户端是在和自己走协议的。

ex_handshake_annotated_fixed.pv(解决中间人攻击)

这里主要就是像第二节里说的那样,让服务器回传的信息中包含目标客户端的标识(这里是用公钥)来防止中间人攻击发生。

type key.
fun senc(bitstring, key): bitstring.
reduc forall m: bitstring, k: key; sdec(senc(m,k),k) = m.

type skey.
type pkey.
fun pk(skey): pkey.
fun aenc(bitstring, pkey): bitstring.
reduc forall m: bitstring, sk: skey; adec(aenc(m,pk(sk)),sk) = m.

type sskey.
type spkey.
fun spk(sskey): spkey.
fun sign(bitstring, sskey): bitstring.
reduc forall m: bitstring, ssk: sskey; getmess(sign(m,ssk)) = m.
reduc forall m: bitstring, ssk: sskey; checksign(sign(m,ssk),spk(ssk)) = m.

free c:channel.
free s:bitstring [private].
query attacker(s).

event acceptsClient(key).
event acceptsServer(key,pkey).
event termClient(key,pkey).
event termServer(key).

query x:key,y:pkey; event(termClient(x,y))==>event(acceptsServer(x,y)).
query x:key; inj-event(termServer(x))==>inj-event(acceptsClient(x)).

let clientA(pkA:pkey,skA:skey,pkB:spkey) = 
	out(c,pkA);
	in(c,x:bitstring); 
	let y = adec(x,skA) in
	(* 这里检查一下这个包确实是要发给自己pkA的 *)
	let (=pkA,=pkB,k:key) = checksign(y,pkB) in
	event acceptsClient(k);
	out(c,senc(s,k));
	event termClient(k,pkA).

let serverB(pkB:spkey,skB:sskey,pkA:pkey) = 
	in(c,pkX:pkey);
	new k:key; 
	event acceptsServer(k,pkX);
	(* 这里把要通信的客户端标识pkX放进去 *)
	out(c,aenc(sign((pkX,pkB,k),skB),pkX));
	in(c,x:bitstring); 
	let z = sdec(x,k) in
	if pkX = pkA then event termServer(k).

process 
	new skA:skey; 
	new skB:sskey;
	let pkA = pk(skA) in out(c,pkA);
	let pkB = spk(skB) in out(c,pkB);
	( (!clientA(pkA,skA,pkB)) | (!serverB(pkB,skB,pkA)) )

现在三条性质就都能验证通过了:

--------------------------------------------------------------
Verification summary:

Query not attacker(s[]) is true.

Query event(termClient(x_2,y_1)) ==> event(acceptsServer(x_2,y_1)) is true.

Query inj-event(termServer(x_2)) ==> inj-event(acceptsClient(x_2)) is true.

--------------------------------------------------------------
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值