如何使用NuSMV见证中间人攻击(Needham-Schroeder协议)?
How to use NuSMV to witness the man-in-the-middle attack (Needham-Schroeder protocol)?
我有以下简化的 public-key Needham-Schroeder 协议:
A → B: {Na, A} Kb
B → A: {Na, Nb} Ka
A → B: {Nb} Kb
其中 Na
、Nb
是 A
、B
的随机数,Ka
、Kb
是 public 键分别为 A
、B
。
由一方的 public 密钥加密的消息只能由一方解密。
At Step (1), A
initiates the protocol by sending a nonce and its identity (encrypted by B
’s public key) to B
. Using its private key, B
deciphers the message and gets A
’s identity.
At Step (2), B
sends A
’s and its nonces (encrypted by A
’s public key) back to A
. Using its private key, A
decodes the message and checks its nonce is returned.
At Step (3), A
returns B
’s nonce (encrypted by B
’s public key) back to B
.
这是对上述简化协议的中间人攻击:
- (1A)
A → E: {Na, A} Ke
(A
想与 E
交谈)
- (1B)
E → B: {Na, A} Kb
(E
想说服B
是A
)
- (2B)
B → E: {Na, Nb} Ka
(B
returns 由 Ka
加密的随机数)
- (2A)
E → A: {Na, Nb} Ka
(E
转发加密信息到 A
)
- (3A)
A → E: {Nb} Ke
(A
确认正在与 E
通话)
- (3B)
E → B: {Nb} Kb
(E
returns B
的随机数返回)
我希望在发现攻击时,提出修复以防止攻击(B
将其身份与随机数一起发送回 A
):
A → B: {Na, A} Kb
B → A: {Na, Nb, B} Ka
(B
将其身份与随机数一起发送回 A
)
A → B: {Nb} Kb
问题是:
- 如何编写 LTL 公式和 NuSMV 模块
eve
来模拟攻击者并见证中间人攻击?
- 如何防止攻击?
The process of alice(A):
MODULE alice (in0, in1, inkey, out0, out1, outkey)
VAR
st : { request, wait, attack, finish };
nonce : { NONE, Na, Nb, Ne };
ASSIGN
init (st) := request;
next (st) := case
st = request : wait;
st = wait & in0 = Na & inkey = Ka : attack;
st = attack : finish;
TRUE : st;
esac;
init (nonce) := NONE;
next (nonce) := case
st = wait & in0 = Na & inkey = Ka : in1;
TRUE : nonce;
esac;
init (out0) := NONE;
next (out0) := case
st = request : Na;
st = attack : nonce;
TRUE : out0;
esac;
init (out1) := NOONE;
next (out1) := case
st = request : Ia;
st = attack : NOONE;
TRUE : out1;
esac;
init (outkey) := NOKEY;
next (outkey) := case
st = request : { Kb };
TRUE : outkey;
esac;
FAIRNESS running;
The process of bob(B):
MODULE bob (in0, in1, inkey, out0, out1, outkey)
VAR
st : { wait, receive, confirm, done };
nonce : { NONE, Na, Nb, Ne };
other : { NOONE, Ia, Ib, Ie };
ASSIGN
init (st) := wait;
next (st) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : receive;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : receive;
st = receive : confirm;
st = confirm & in0 = Nb & in1 = NOONE & inkey = Kb : done;
TRUE : st;
esac;
init (nonce) := NONE;
next (nonce) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : in0;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : in0;
TRUE : nonce;
esac;
init (other) := NOONE;
next (other) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : in1;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : in1;
TRUE : other;
esac;
init (out0) := NONE;
next (out0) := case
st = confirm : nonce;
TRUE : out0;
esac;
init (out1) := NONE;
next (out1) := case
st = confirm : Nb;
TRUE : out1;
esac;
init (outkey) := NOKEY;
next (outkey) := case
st = confirm & other = Ia : Ka;
st = confirm & other = Ie : Ke;
TRUE : outkey;
esac;
FAIRNESS running;
The process of main:
MODULE main
VAR
a_in0 : { NONE, Na, Nb, Ne };
a_in1 : { NONE, Na, Nb, Ne };
a_out0 : { NONE, Na, Nb, Ne };
a_out1 : { NOONE, Ia, Ib, Ie };
a_inkey : { NOKEY, Ka, Kb, Ke };
a_outkey : { NOKEY, Ka, Kb, Ke };
a : process alice (a_in0, a_in1, a_inkey, a_out0, a_out1, a_outkey);
b : process bob (a_out0, a_out1, a_outkey, a_in0, a_in1, a_inkey);
FAIRNESS running;
LTLSPEC F (a.st = finish & b.st = done)
非常感谢!
(注意:使用其他工具(例如Spin
or the STIATE Toolkit
)对您想要的系统进行建模和验证会简单得多。)
爱丽丝和鲍勃。
这里我们对 user
的类型进行建模,它以诚实、透明的方式运行,并且在您的用例中可以实例化为 Alice
或 Bob
。
作为简化,我硬编码了一个事实,即如果 user
是 Alice
那么它将通过尝试联系其他实体来启动协议。
输入 my_nonce
、my_id
和 my_key
定义 user
的身份,而 other_key
和 other_id
代表公开关于我们想要联系的其他 user
的可用信息。输入 in_1
、in_2
和 in_k
就像您的代码示例中一样,而 in_3
保留用于交换 patched[= 中使用的第三个值152=] 协议版本。
A user
可以处于五种状态之一:
IDLE
:初始状态,Alice
将启动协议,而Bob
等待一些请求。
WAIT_RESPONSE
:当Alice
等待回复她时request
WAIT_CONFIRMATION
:当Bob
等待对他的response
的确认时
OK
:当Alice
和Bob
握手成功时
ERROR
:当握手出现问题时(例如意外输入)
用户可以执行以下操作之一:
SEND_REQUEST
: {Na, IA} Kb
SEND_RESPONSE
: {Na, Nb} Ka
SEND_CONFIRMATION
: {Nb} Kb
作为一种简化,与您自己的模型类似,我使输出值在多次转换中保持不变,而不是立即将它们放回 NONE
。这样,我就不必添加额外的变量来在重置之前跟踪输入值。
MODULE user(patched, my_nonce, my_id, my_key, other_key, other_id, in_1, in_2, in_3, in_k)
VAR
state : { IDLE, WAIT_RESPONSE, WAIT_CONFIRMATION, OK, ERROR };
action : { NONE, SEND_REQUEST, SEND_RESPONSE, SEND_CONFIRMATION };
out_1 : { NONE, NA, NB, NE, IA, IB, IE };
out_2 : { NONE, NA, NB, NE, IA, IB, IE };
out_3 : { NONE, NA, NB, NE, IA, IB, IE };
out_k : { NONE, KA, KB, KE };
INIT
state = IDLE & action = NONE & out_1 = NONE
& out_2 = NONE & out_3 = NONE & out_k = NONE;
-- protocol actions defining outputs
TRANS
next(action) = SEND_REQUEST -> (
next(out_1) = my_nonce & next(out_2) = my_id &
next(out_3) = NONE & next(out_k) = other_key
);
TRANS
((next(action) = SEND_RESPONSE) & patched) -> (
next(out_1) = in_1 & next(out_2) = my_nonce &
next(out_3) = my_id & next(out_k) = other_key
);
TRANS
((next(action) = SEND_RESPONSE) & !patched) -> (
next(out_1) = in_1 & next(out_2) = my_nonce &
next(out_3) = NONE & next(out_k) = other_key
);
TRANS
next(action) = SEND_CONFIRMATION -> (
next(out_1) = in_2 & next(out_2) = NONE &
next(out_3) = NONE & next(out_k) = other_key
);
-- outputs stabilization: easier modeling
TRANS
next(action) = NONE -> (
next(out_1) = out_1 & next(out_2) = out_2 &
next(out_3) = out_3 & next(out_k) = out_k
);
-- protocol life-cycle
TRANS
case
-- protocol: end-positions
(action = NONE &
state = ERROR)
: next(action) = NONE &
next(state) = ERROR;
(action = NONE &
state = OK)
: next(action) = NONE &
next(state) = OK;
-- protocol: send request
(action = NONE &
state = IDLE &
my_id = IA)
: next(action) = SEND_REQUEST &
next(state) = WAIT_RESPONSE;
-- protocol: handle request
(action = NONE &
state = IDLE &
in_k = my_key)
: next(action) = SEND_RESPONSE &
next(state) = WAIT_CONFIRMATION;
-- protocol: handle response
-- without patch
(action = NONE &
state = WAIT_RESPONSE &
in_k = my_key &
in_1 = my_nonce &
!patched)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- with patch
(action = NONE &
state = WAIT_RESPONSE &
in_k = my_key &
in_1 = my_nonce &
in_3 = other_id &
patched)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- protocol: handle confirmation
(action = NONE &
state = WAIT_CONFIRMATION &
in_k = my_key &
in_1 = my_nonce)
: next(action) = NONE &
next(state) = OK;
-- protocol: no change state while performing action
(action != NONE)
: next(action) = NONE &
next(state) = state;
-- protocol: no state change if no valid input
(action = NONE &
in_k != my_key)
: next(action) = NONE &
next(state) = state;
-- sink error condition for malformed inputs
TRUE
: next(action) = NONE &
next(state) = ERROR;
esac;
我们添加了一个非常简单的 main
模块和一个简单的 CTL
属性 来检查 Alice
和 Bob
是否以预期的方式运行并且是正常情况下能完成握手:
MODULE main
VAR
a1 : process user(FALSE, NA, IA, KA, KB, IB, b1.out_1, b1.out_2, b1.out_3, b1.out_k);
b1 : process user(FALSE, NB, IB, KB, KA, IA, a1.out_1, a1.out_2, a1.out_3, a1.out_k);
FAIRNESS running;
CTLSPEC ! EF (a1.state = OK & b1.state = OK);
输出如下:
NuSMV > reset; read_model -i ns01.smv ; go ; check_property
...
-- specification !(EF (a1.state = OK & b1.state = OK)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a1.state = IDLE
a1.action = NONE
a1.out_1 = NONE
a1.out_2 = NONE
a1.out_3 = NONE
a1.out_k = NONE
b1.state = IDLE
b1.action = NONE
b1.out_1 = NONE
b1.out_2 = NONE
b1.out_3 = NONE
b1.out_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
b1.running = FALSE
a1.running = FALSE
-> State: 1.2 <-
a1.state = WAIT_RESPONSE
a1.action = SEND_REQUEST
a1.out_1 = NA
a1.out_2 = IA
a1.out_k = KB
-> Input: 1.3 <-
-> State: 1.3 <-
a1.action = NONE
b1.state = WAIT_CONFIRMATION
b1.action = SEND_RESPONSE
b1.out_1 = NA
b1.out_2 = NB
b1.out_k = KA
-> Input: 1.4 <-
-> State: 1.4 <-
a1.state = OK
a1.action = SEND_CONFIRMATION
a1.out_1 = NB
a1.out_2 = NONE
b1.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
a1.action = NONE
b1.state = OK
爱丽丝、鲍勃和夏娃。
为了对我们的攻击场景进行建模,我们首先需要对attacker
进行建模。这和Alice
和[=22=很相似,只是它有双inputs
和outputs
,所以它可以同时与Alice
和[=22=通信]同时
它的设计与Alice
和Bob
的设计非常相似,所以我就不多说了。作为简化,我删除了 attacker
上的任何错误检查,因为它实际上没有任何有意义的理由在所考虑的用例场景中失败。不这样做会使代码毫无理由地复杂化。
MODULE attacker(my_nonce, my_id, my_key, a_key, b_key,
ain_1, ain_2, ain_3, ain_k,
bin_1, bin_2, bin_3, bin_k)
VAR
state : { IDLE, WAIT_RESPONSE, WAIT_CONFIRMATION, OK, ERROR };
action : { NONE, SEND_REQUEST, SEND_RESPONSE, SEND_CONFIRMATION };
aout_1 : { NONE, NA, NB, NE, IA, IB, IE };
aout_2 : { NONE, NA, NB, NE, IA, IB, IE };
aout_3 : { NONE, NA, NB, NE, IA, IB, IE };
aout_k : { NONE, KA, KB, KE };
bout_1 : { NONE, NA, NB, NE, IA, IB, IE };
bout_2 : { NONE, NA, NB, NE, IA, IB, IE };
bout_3 : { NONE, NA, NB, NE, IA, IB, IE };
bout_k : { NONE, KA, KB, KE };
INIT
state = IDLE & action = NONE &
aout_1 = NONE & aout_2 = NONE & aout_3 = NONE & aout_k = NONE &
bout_1 = NONE & bout_2 = NONE & bout_3 = NONE & bout_k = NONE;
-- protocol actions defining outputs
TRANS
-- attacker: forward A secrets to B
next(action) = SEND_REQUEST -> (
next(aout_1) = NONE & next(aout_2) = NONE &
next(aout_3) = NONE & next(aout_k) = NONE &
next(bout_1) = ain_1 & next(bout_2) = ain_2 &
next(bout_3) = ain_3 & next(bout_k) = b_key
);
TRANS
-- attacker: forward B response to A (cannot be unencripted)
next(action) = SEND_RESPONSE -> (
next(aout_1) = bin_1 & next(aout_2) = bin_2 &
next(aout_3) = bin_3 & next(aout_k) = bin_k &
next(bout_1) = NONE & next(bout_2) = NONE &
next(bout_3) = NONE & next(bout_k) = NONE
);
TRANS
-- attacker: send confirmation to B
next(action) = SEND_CONFIRMATION -> (
next(aout_1) = NONE & next(aout_2) = NONE &
next(aout_3) = NONE & next(aout_k) = NONE &
next(bout_1) = ain_1 & next(bout_2) = NONE &
next(bout_3) = NONE & next(bout_k) = b_key
);
-- outputs stabilization: easier modeling
TRANS
next(action) = NONE -> (
next(aout_1) = aout_1 & next(aout_2) = aout_2 &
next(aout_3) = aout_3 & next(aout_k) = aout_k &
next(bout_1) = bout_1 & next(bout_2) = bout_2 &
next(bout_3) = bout_3 & next(bout_k) = bout_k
);
-- attack life-cycle
TRANS
case
-- attack: end-positions
(action = NONE &
state = ERROR)
: next(action) = NONE &
next(state) = ERROR;
(action = NONE &
state = OK)
: next(action) = NONE &
next(state) = OK;
-- attack: handle request, send forged request
(action = NONE &
state = IDLE &
ain_k = my_key)
: next(action) = SEND_REQUEST &
next(state) = WAIT_RESPONSE;
-- attack: handle response, forward undecryptable response
(action = NONE &
state = WAIT_RESPONSE &
bin_k = a_key)
: next(action) = SEND_RESPONSE &
next(state) = WAIT_CONFIRMATION;
-- attack: handle confirmation, send confirmation
(action = NONE &
state = WAIT_CONFIRMATION &
ain_k = my_key)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- attack: simple catch-all control no error checking
TRUE
: next(action) = NONE &
next(state) = state;
esac;
再次,我们添加一个非常简单的 main
模块和一个简单的 CTL
属性 来检查 Eve
是否能够成功攻击 Alice
和Bob
.. 最后,Alice
认为是在与 Eve
交谈(按原样),而 Bob
认为是在与 Alice
交谈,当它真的在与 Eve
.
交谈
MODULE main
VAR
a2 : process user(FALSE, NA, IA, KA, KE, IE, e2.aout_1, e2.aout_2, e2.aout_3, e2.aout_k);
b2 : process user(FALSE, NB, IB, KB, KA, IA, e2.bout_1, e2.bout_2, e2.bout_3, e2.bout_k);
e2 : process attacker(NE, IE, KE, KA, KB,
a2.out_1, a2.out_2, a2.out_3, a2.out_k,
b2.out_1, b2.out_2, b2.out_3, b2.out_k);
FAIRNESS running;
CTLSPEC ! EF (a2.state = OK & b2.state = OK & e2.state = OK);
输出如下:
NuSMV > reset; read_model -i ns02.smv ; go ; check_property
...
-- specification !(EF ((a2.state = OK & b2.state = OK) & e2.state = OK)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a2.state = IDLE
a2.action = NONE
a2.out_1 = NONE
a2.out_2 = NONE
a2.out_3 = NONE
a2.out_k = NONE
b2.state = IDLE
b2.action = NONE
b2.out_1 = NONE
b2.out_2 = NONE
b2.out_3 = NONE
b2.out_k = NONE
e2.state = IDLE
e2.action = NONE
e2.aout_1 = NONE
e2.aout_2 = NONE
e2.aout_3 = NONE
e2.aout_k = NONE
e2.bout_1 = NONE
e2.bout_2 = NONE
e2.bout_3 = NONE
e2.bout_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
e2.running = FALSE
b2.running = FALSE
a2.running = FALSE
-> State: 1.2 <-
a2.state = WAIT_RESPONSE
a2.action = SEND_REQUEST
a2.out_1 = NA
a2.out_2 = IA
a2.out_k = KE
-> Input: 1.3 <-
-> State: 1.3 <-
a2.action = NONE
e2.state = WAIT_RESPONSE
e2.action = SEND_REQUEST
e2.bout_1 = NA
e2.bout_2 = IA
e2.bout_k = KB
-> Input: 1.4 <-
-> State: 1.4 <-
b2.state = WAIT_CONFIRMATION
b2.action = SEND_RESPONSE
b2.out_1 = NA
b2.out_2 = NB
b2.out_k = KA
e2.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
b2.action = NONE
e2.state = WAIT_CONFIRMATION
e2.action = SEND_RESPONSE
e2.aout_1 = NA
e2.aout_2 = NB
e2.aout_k = KA
e2.bout_1 = NONE
e2.bout_2 = NONE
e2.bout_k = NONE
-> Input: 1.6 <-
-> State: 1.6 <-
a2.state = OK
a2.action = SEND_CONFIRMATION
a2.out_1 = NB
a2.out_2 = NONE
e2.action = NONE
-> Input: 1.7 <-
-> State: 1.7 <-
a2.action = NONE
e2.state = OK
e2.action = SEND_CONFIRMATION
e2.aout_1 = NONE
e2.aout_2 = NONE
e2.aout_k = NONE
e2.bout_1 = NB
e2.bout_k = KB
-> Input: 1.8 <-
-> State: 1.8 <-
b2.state = OK
e2.action = NONE
已修补 Alice、Bob 和 Eve。
幸运的是,我已经在我已经展示的代码中偷偷添加了 Alice
和 Bob
的 patched 版本。因此,剩下要做的就是通过编写一个将 Alice
、Bob
和 Eve
组合在一起的新 main
来检查补丁是否满足所需的行为:
MODULE main
VAR
a3 : process user(TRUE, NA, IA, KA, KE, IE, e3.aout_1, e3.aout_2, e3.aout_3, e3.aout_k);
b3 : process user(TRUE, NB, IB, KB, KA, IA, e3.bout_1, e3.bout_2, e3.bout_3, e3.bout_k);
e3 : process attacker(NE, IE, KE, KA, KB,
a3.out_1, a3.out_2, a3.out_3, a3.out_k,
b3.out_1, b3.out_2, b3.out_3, b3.out_k);
FAIRNESS running;
CTLSPEC ! EF (a3.state = OK & b3.state = OK & e3.state = OK);
CTLSPEC ! EF (a3.state = ERROR & b3.state = ERROR);
输出如下:
NuSMV > reset; read_model -i ns03.smv ; go ; check_property
...
-- specification !(EF ((a3.state = OK & b3.state = OK) & e3.state = OK)) is true
-- specification !(EF (a3.state = ERROR & b3.state = ERROR)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a3.state = IDLE
a3.action = NONE
a3.out_1 = NONE
a3.out_2 = NONE
a3.out_3 = NONE
a3.out_k = NONE
b3.state = IDLE
b3.action = NONE
b3.out_1 = NONE
b3.out_2 = NONE
b3.out_3 = NONE
b3.out_k = NONE
e3.state = IDLE
e3.action = NONE
e3.aout_1 = NONE
e3.aout_2 = NONE
e3.aout_3 = NONE
e3.aout_k = NONE
e3.bout_1 = NONE
e3.bout_2 = NONE
e3.bout_3 = NONE
e3.bout_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
e3.running = FALSE
b3.running = FALSE
a3.running = FALSE
-> State: 1.2 <-
a3.state = WAIT_RESPONSE
a3.action = SEND_REQUEST
a3.out_1 = NA
a3.out_2 = IA
a3.out_k = KE
-> Input: 1.3 <-
-> State: 1.3 <-
a3.action = NONE
e3.state = WAIT_RESPONSE
e3.action = SEND_REQUEST
e3.bout_1 = NA
e3.bout_2 = IA
e3.bout_k = KB
-> Input: 1.4 <-
-> State: 1.4 <-
b3.state = WAIT_CONFIRMATION
b3.action = SEND_RESPONSE
b3.out_1 = NA
b3.out_2 = NB
b3.out_3 = IB
b3.out_k = KA
e3.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
b3.action = NONE
e3.state = WAIT_CONFIRMATION
e3.action = SEND_RESPONSE
e3.aout_1 = NA
e3.aout_2 = NB
e3.aout_3 = IB
e3.aout_k = KA
e3.bout_1 = NONE
e3.bout_2 = NONE
e3.bout_k = NONE
-> Input: 1.6 <-
-> State: 1.6 <-
a3.state = ERROR
e3.action = NONE
-> Input: 1.7 <-
-> State: 1.7 <-
e3.state = OK
e3.action = SEND_CONFIRMATION
e3.aout_1 = NONE
e3.aout_2 = NONE
e3.aout_3 = NONE
e3.aout_k = NONE
e3.bout_1 = NA
e3.bout_k = KB
-> Input: 1.8 <-
-> State: 1.8 <-a
b3.state = ERROR
e3.action = NONE
第一个属性确认攻击失败并且握手没有完成Alice
和Bob
因为Eve
不满足它。第二个 属性 显示 如何 尝试攻击以及它在实践中如何失败。
我有以下简化的 public-key Needham-Schroeder 协议:
A → B: {Na, A} Kb
B → A: {Na, Nb} Ka
A → B: {Nb} Kb
其中 Na
、Nb
是 A
、B
的随机数,Ka
、Kb
是 public 键分别为 A
、B
。
由一方的 public 密钥加密的消息只能由一方解密。
At Step (1),
A
initiates the protocol by sending a nonce and its identity (encrypted byB
’s public key) toB
. Using its private key,B
deciphers the message and getsA
’s identity.At Step (2),
B
sendsA
’s and its nonces (encrypted byA
’s public key) back toA
. Using its private key,A
decodes the message and checks its nonce is returned.At Step (3),
A
returnsB
’s nonce (encrypted byB
’s public key) back toB
.
这是对上述简化协议的中间人攻击:
- (1A)
A → E: {Na, A} Ke
(A
想与E
交谈) - (1B)
E → B: {Na, A} Kb
(E
想说服B
是A
) - (2B)
B → E: {Na, Nb} Ka
(B
returns 由Ka
加密的随机数) - (2A)
E → A: {Na, Nb} Ka
(E
转发加密信息到A
) - (3A)
A → E: {Nb} Ke
(A
确认正在与E
通话) - (3B)
E → B: {Nb} Kb
(E
returnsB
的随机数返回)
我希望在发现攻击时,提出修复以防止攻击(B
将其身份与随机数一起发送回 A
):
A → B: {Na, A} Kb
B → A: {Na, Nb, B} Ka
(B
将其身份与随机数一起发送回A
)A → B: {Nb} Kb
问题是:
- 如何编写 LTL 公式和 NuSMV 模块
eve
来模拟攻击者并见证中间人攻击? - 如何防止攻击?
The process of alice(A):
MODULE alice (in0, in1, inkey, out0, out1, outkey)
VAR
st : { request, wait, attack, finish };
nonce : { NONE, Na, Nb, Ne };
ASSIGN
init (st) := request;
next (st) := case
st = request : wait;
st = wait & in0 = Na & inkey = Ka : attack;
st = attack : finish;
TRUE : st;
esac;
init (nonce) := NONE;
next (nonce) := case
st = wait & in0 = Na & inkey = Ka : in1;
TRUE : nonce;
esac;
init (out0) := NONE;
next (out0) := case
st = request : Na;
st = attack : nonce;
TRUE : out0;
esac;
init (out1) := NOONE;
next (out1) := case
st = request : Ia;
st = attack : NOONE;
TRUE : out1;
esac;
init (outkey) := NOKEY;
next (outkey) := case
st = request : { Kb };
TRUE : outkey;
esac;
FAIRNESS running;
The process of bob(B):
MODULE bob (in0, in1, inkey, out0, out1, outkey)
VAR
st : { wait, receive, confirm, done };
nonce : { NONE, Na, Nb, Ne };
other : { NOONE, Ia, Ib, Ie };
ASSIGN
init (st) := wait;
next (st) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : receive;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : receive;
st = receive : confirm;
st = confirm & in0 = Nb & in1 = NOONE & inkey = Kb : done;
TRUE : st;
esac;
init (nonce) := NONE;
next (nonce) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : in0;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : in0;
TRUE : nonce;
esac;
init (other) := NOONE;
next (other) := case
st = wait & in0 = Na & in1 = Ia & inkey = Kb : in1;
st = wait & in0 = Ne & in1 = Ie & inkey = Kb : in1;
TRUE : other;
esac;
init (out0) := NONE;
next (out0) := case
st = confirm : nonce;
TRUE : out0;
esac;
init (out1) := NONE;
next (out1) := case
st = confirm : Nb;
TRUE : out1;
esac;
init (outkey) := NOKEY;
next (outkey) := case
st = confirm & other = Ia : Ka;
st = confirm & other = Ie : Ke;
TRUE : outkey;
esac;
FAIRNESS running;
The process of main:
MODULE main
VAR
a_in0 : { NONE, Na, Nb, Ne };
a_in1 : { NONE, Na, Nb, Ne };
a_out0 : { NONE, Na, Nb, Ne };
a_out1 : { NOONE, Ia, Ib, Ie };
a_inkey : { NOKEY, Ka, Kb, Ke };
a_outkey : { NOKEY, Ka, Kb, Ke };
a : process alice (a_in0, a_in1, a_inkey, a_out0, a_out1, a_outkey);
b : process bob (a_out0, a_out1, a_outkey, a_in0, a_in1, a_inkey);
FAIRNESS running;
LTLSPEC F (a.st = finish & b.st = done)
非常感谢!
(注意:使用其他工具(例如Spin
or the STIATE Toolkit
)对您想要的系统进行建模和验证会简单得多。)
爱丽丝和鲍勃。
这里我们对 user
的类型进行建模,它以诚实、透明的方式运行,并且在您的用例中可以实例化为 Alice
或 Bob
。
作为简化,我硬编码了一个事实,即如果 user
是 Alice
那么它将通过尝试联系其他实体来启动协议。
输入 my_nonce
、my_id
和 my_key
定义 user
的身份,而 other_key
和 other_id
代表公开关于我们想要联系的其他 user
的可用信息。输入 in_1
、in_2
和 in_k
就像您的代码示例中一样,而 in_3
保留用于交换 patched[= 中使用的第三个值152=] 协议版本。
A user
可以处于五种状态之一:
IDLE
:初始状态,Alice
将启动协议,而Bob
等待一些请求。WAIT_RESPONSE
:当Alice
等待回复她时request
WAIT_CONFIRMATION
:当Bob
等待对他的response
的确认时
OK
:当Alice
和Bob
握手成功时ERROR
:当握手出现问题时(例如意外输入)
用户可以执行以下操作之一:
SEND_REQUEST
:{Na, IA} Kb
SEND_RESPONSE
:{Na, Nb} Ka
SEND_CONFIRMATION
:{Nb} Kb
作为一种简化,与您自己的模型类似,我使输出值在多次转换中保持不变,而不是立即将它们放回 NONE
。这样,我就不必添加额外的变量来在重置之前跟踪输入值。
MODULE user(patched, my_nonce, my_id, my_key, other_key, other_id, in_1, in_2, in_3, in_k)
VAR
state : { IDLE, WAIT_RESPONSE, WAIT_CONFIRMATION, OK, ERROR };
action : { NONE, SEND_REQUEST, SEND_RESPONSE, SEND_CONFIRMATION };
out_1 : { NONE, NA, NB, NE, IA, IB, IE };
out_2 : { NONE, NA, NB, NE, IA, IB, IE };
out_3 : { NONE, NA, NB, NE, IA, IB, IE };
out_k : { NONE, KA, KB, KE };
INIT
state = IDLE & action = NONE & out_1 = NONE
& out_2 = NONE & out_3 = NONE & out_k = NONE;
-- protocol actions defining outputs
TRANS
next(action) = SEND_REQUEST -> (
next(out_1) = my_nonce & next(out_2) = my_id &
next(out_3) = NONE & next(out_k) = other_key
);
TRANS
((next(action) = SEND_RESPONSE) & patched) -> (
next(out_1) = in_1 & next(out_2) = my_nonce &
next(out_3) = my_id & next(out_k) = other_key
);
TRANS
((next(action) = SEND_RESPONSE) & !patched) -> (
next(out_1) = in_1 & next(out_2) = my_nonce &
next(out_3) = NONE & next(out_k) = other_key
);
TRANS
next(action) = SEND_CONFIRMATION -> (
next(out_1) = in_2 & next(out_2) = NONE &
next(out_3) = NONE & next(out_k) = other_key
);
-- outputs stabilization: easier modeling
TRANS
next(action) = NONE -> (
next(out_1) = out_1 & next(out_2) = out_2 &
next(out_3) = out_3 & next(out_k) = out_k
);
-- protocol life-cycle
TRANS
case
-- protocol: end-positions
(action = NONE &
state = ERROR)
: next(action) = NONE &
next(state) = ERROR;
(action = NONE &
state = OK)
: next(action) = NONE &
next(state) = OK;
-- protocol: send request
(action = NONE &
state = IDLE &
my_id = IA)
: next(action) = SEND_REQUEST &
next(state) = WAIT_RESPONSE;
-- protocol: handle request
(action = NONE &
state = IDLE &
in_k = my_key)
: next(action) = SEND_RESPONSE &
next(state) = WAIT_CONFIRMATION;
-- protocol: handle response
-- without patch
(action = NONE &
state = WAIT_RESPONSE &
in_k = my_key &
in_1 = my_nonce &
!patched)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- with patch
(action = NONE &
state = WAIT_RESPONSE &
in_k = my_key &
in_1 = my_nonce &
in_3 = other_id &
patched)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- protocol: handle confirmation
(action = NONE &
state = WAIT_CONFIRMATION &
in_k = my_key &
in_1 = my_nonce)
: next(action) = NONE &
next(state) = OK;
-- protocol: no change state while performing action
(action != NONE)
: next(action) = NONE &
next(state) = state;
-- protocol: no state change if no valid input
(action = NONE &
in_k != my_key)
: next(action) = NONE &
next(state) = state;
-- sink error condition for malformed inputs
TRUE
: next(action) = NONE &
next(state) = ERROR;
esac;
我们添加了一个非常简单的 main
模块和一个简单的 CTL
属性 来检查 Alice
和 Bob
是否以预期的方式运行并且是正常情况下能完成握手:
MODULE main
VAR
a1 : process user(FALSE, NA, IA, KA, KB, IB, b1.out_1, b1.out_2, b1.out_3, b1.out_k);
b1 : process user(FALSE, NB, IB, KB, KA, IA, a1.out_1, a1.out_2, a1.out_3, a1.out_k);
FAIRNESS running;
CTLSPEC ! EF (a1.state = OK & b1.state = OK);
输出如下:
NuSMV > reset; read_model -i ns01.smv ; go ; check_property
...
-- specification !(EF (a1.state = OK & b1.state = OK)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a1.state = IDLE
a1.action = NONE
a1.out_1 = NONE
a1.out_2 = NONE
a1.out_3 = NONE
a1.out_k = NONE
b1.state = IDLE
b1.action = NONE
b1.out_1 = NONE
b1.out_2 = NONE
b1.out_3 = NONE
b1.out_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
b1.running = FALSE
a1.running = FALSE
-> State: 1.2 <-
a1.state = WAIT_RESPONSE
a1.action = SEND_REQUEST
a1.out_1 = NA
a1.out_2 = IA
a1.out_k = KB
-> Input: 1.3 <-
-> State: 1.3 <-
a1.action = NONE
b1.state = WAIT_CONFIRMATION
b1.action = SEND_RESPONSE
b1.out_1 = NA
b1.out_2 = NB
b1.out_k = KA
-> Input: 1.4 <-
-> State: 1.4 <-
a1.state = OK
a1.action = SEND_CONFIRMATION
a1.out_1 = NB
a1.out_2 = NONE
b1.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
a1.action = NONE
b1.state = OK
爱丽丝、鲍勃和夏娃。
为了对我们的攻击场景进行建模,我们首先需要对attacker
进行建模。这和Alice
和[=22=很相似,只是它有双inputs
和outputs
,所以它可以同时与Alice
和[=22=通信]同时
它的设计与Alice
和Bob
的设计非常相似,所以我就不多说了。作为简化,我删除了 attacker
上的任何错误检查,因为它实际上没有任何有意义的理由在所考虑的用例场景中失败。不这样做会使代码毫无理由地复杂化。
MODULE attacker(my_nonce, my_id, my_key, a_key, b_key,
ain_1, ain_2, ain_3, ain_k,
bin_1, bin_2, bin_3, bin_k)
VAR
state : { IDLE, WAIT_RESPONSE, WAIT_CONFIRMATION, OK, ERROR };
action : { NONE, SEND_REQUEST, SEND_RESPONSE, SEND_CONFIRMATION };
aout_1 : { NONE, NA, NB, NE, IA, IB, IE };
aout_2 : { NONE, NA, NB, NE, IA, IB, IE };
aout_3 : { NONE, NA, NB, NE, IA, IB, IE };
aout_k : { NONE, KA, KB, KE };
bout_1 : { NONE, NA, NB, NE, IA, IB, IE };
bout_2 : { NONE, NA, NB, NE, IA, IB, IE };
bout_3 : { NONE, NA, NB, NE, IA, IB, IE };
bout_k : { NONE, KA, KB, KE };
INIT
state = IDLE & action = NONE &
aout_1 = NONE & aout_2 = NONE & aout_3 = NONE & aout_k = NONE &
bout_1 = NONE & bout_2 = NONE & bout_3 = NONE & bout_k = NONE;
-- protocol actions defining outputs
TRANS
-- attacker: forward A secrets to B
next(action) = SEND_REQUEST -> (
next(aout_1) = NONE & next(aout_2) = NONE &
next(aout_3) = NONE & next(aout_k) = NONE &
next(bout_1) = ain_1 & next(bout_2) = ain_2 &
next(bout_3) = ain_3 & next(bout_k) = b_key
);
TRANS
-- attacker: forward B response to A (cannot be unencripted)
next(action) = SEND_RESPONSE -> (
next(aout_1) = bin_1 & next(aout_2) = bin_2 &
next(aout_3) = bin_3 & next(aout_k) = bin_k &
next(bout_1) = NONE & next(bout_2) = NONE &
next(bout_3) = NONE & next(bout_k) = NONE
);
TRANS
-- attacker: send confirmation to B
next(action) = SEND_CONFIRMATION -> (
next(aout_1) = NONE & next(aout_2) = NONE &
next(aout_3) = NONE & next(aout_k) = NONE &
next(bout_1) = ain_1 & next(bout_2) = NONE &
next(bout_3) = NONE & next(bout_k) = b_key
);
-- outputs stabilization: easier modeling
TRANS
next(action) = NONE -> (
next(aout_1) = aout_1 & next(aout_2) = aout_2 &
next(aout_3) = aout_3 & next(aout_k) = aout_k &
next(bout_1) = bout_1 & next(bout_2) = bout_2 &
next(bout_3) = bout_3 & next(bout_k) = bout_k
);
-- attack life-cycle
TRANS
case
-- attack: end-positions
(action = NONE &
state = ERROR)
: next(action) = NONE &
next(state) = ERROR;
(action = NONE &
state = OK)
: next(action) = NONE &
next(state) = OK;
-- attack: handle request, send forged request
(action = NONE &
state = IDLE &
ain_k = my_key)
: next(action) = SEND_REQUEST &
next(state) = WAIT_RESPONSE;
-- attack: handle response, forward undecryptable response
(action = NONE &
state = WAIT_RESPONSE &
bin_k = a_key)
: next(action) = SEND_RESPONSE &
next(state) = WAIT_CONFIRMATION;
-- attack: handle confirmation, send confirmation
(action = NONE &
state = WAIT_CONFIRMATION &
ain_k = my_key)
: next(action) = SEND_CONFIRMATION &
next(state) = OK;
-- attack: simple catch-all control no error checking
TRUE
: next(action) = NONE &
next(state) = state;
esac;
再次,我们添加一个非常简单的 main
模块和一个简单的 CTL
属性 来检查 Eve
是否能够成功攻击 Alice
和Bob
.. 最后,Alice
认为是在与 Eve
交谈(按原样),而 Bob
认为是在与 Alice
交谈,当它真的在与 Eve
.
MODULE main
VAR
a2 : process user(FALSE, NA, IA, KA, KE, IE, e2.aout_1, e2.aout_2, e2.aout_3, e2.aout_k);
b2 : process user(FALSE, NB, IB, KB, KA, IA, e2.bout_1, e2.bout_2, e2.bout_3, e2.bout_k);
e2 : process attacker(NE, IE, KE, KA, KB,
a2.out_1, a2.out_2, a2.out_3, a2.out_k,
b2.out_1, b2.out_2, b2.out_3, b2.out_k);
FAIRNESS running;
CTLSPEC ! EF (a2.state = OK & b2.state = OK & e2.state = OK);
输出如下:
NuSMV > reset; read_model -i ns02.smv ; go ; check_property
...
-- specification !(EF ((a2.state = OK & b2.state = OK) & e2.state = OK)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a2.state = IDLE
a2.action = NONE
a2.out_1 = NONE
a2.out_2 = NONE
a2.out_3 = NONE
a2.out_k = NONE
b2.state = IDLE
b2.action = NONE
b2.out_1 = NONE
b2.out_2 = NONE
b2.out_3 = NONE
b2.out_k = NONE
e2.state = IDLE
e2.action = NONE
e2.aout_1 = NONE
e2.aout_2 = NONE
e2.aout_3 = NONE
e2.aout_k = NONE
e2.bout_1 = NONE
e2.bout_2 = NONE
e2.bout_3 = NONE
e2.bout_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
e2.running = FALSE
b2.running = FALSE
a2.running = FALSE
-> State: 1.2 <-
a2.state = WAIT_RESPONSE
a2.action = SEND_REQUEST
a2.out_1 = NA
a2.out_2 = IA
a2.out_k = KE
-> Input: 1.3 <-
-> State: 1.3 <-
a2.action = NONE
e2.state = WAIT_RESPONSE
e2.action = SEND_REQUEST
e2.bout_1 = NA
e2.bout_2 = IA
e2.bout_k = KB
-> Input: 1.4 <-
-> State: 1.4 <-
b2.state = WAIT_CONFIRMATION
b2.action = SEND_RESPONSE
b2.out_1 = NA
b2.out_2 = NB
b2.out_k = KA
e2.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
b2.action = NONE
e2.state = WAIT_CONFIRMATION
e2.action = SEND_RESPONSE
e2.aout_1 = NA
e2.aout_2 = NB
e2.aout_k = KA
e2.bout_1 = NONE
e2.bout_2 = NONE
e2.bout_k = NONE
-> Input: 1.6 <-
-> State: 1.6 <-
a2.state = OK
a2.action = SEND_CONFIRMATION
a2.out_1 = NB
a2.out_2 = NONE
e2.action = NONE
-> Input: 1.7 <-
-> State: 1.7 <-
a2.action = NONE
e2.state = OK
e2.action = SEND_CONFIRMATION
e2.aout_1 = NONE
e2.aout_2 = NONE
e2.aout_k = NONE
e2.bout_1 = NB
e2.bout_k = KB
-> Input: 1.8 <-
-> State: 1.8 <-
b2.state = OK
e2.action = NONE
已修补 Alice、Bob 和 Eve。
幸运的是,我已经在我已经展示的代码中偷偷添加了 Alice
和 Bob
的 patched 版本。因此,剩下要做的就是通过编写一个将 Alice
、Bob
和 Eve
组合在一起的新 main
来检查补丁是否满足所需的行为:
MODULE main
VAR
a3 : process user(TRUE, NA, IA, KA, KE, IE, e3.aout_1, e3.aout_2, e3.aout_3, e3.aout_k);
b3 : process user(TRUE, NB, IB, KB, KA, IA, e3.bout_1, e3.bout_2, e3.bout_3, e3.bout_k);
e3 : process attacker(NE, IE, KE, KA, KB,
a3.out_1, a3.out_2, a3.out_3, a3.out_k,
b3.out_1, b3.out_2, b3.out_3, b3.out_k);
FAIRNESS running;
CTLSPEC ! EF (a3.state = OK & b3.state = OK & e3.state = OK);
CTLSPEC ! EF (a3.state = ERROR & b3.state = ERROR);
输出如下:
NuSMV > reset; read_model -i ns03.smv ; go ; check_property
...
-- specification !(EF ((a3.state = OK & b3.state = OK) & e3.state = OK)) is true
-- specification !(EF (a3.state = ERROR & b3.state = ERROR)) is false
-- as demonstrated by the following execution sequence
Trace Description: CTL Counterexample
Trace Type: Counterexample
-> State: 1.1 <-
a3.state = IDLE
a3.action = NONE
a3.out_1 = NONE
a3.out_2 = NONE
a3.out_3 = NONE
a3.out_k = NONE
b3.state = IDLE
b3.action = NONE
b3.out_1 = NONE
b3.out_2 = NONE
b3.out_3 = NONE
b3.out_k = NONE
e3.state = IDLE
e3.action = NONE
e3.aout_1 = NONE
e3.aout_2 = NONE
e3.aout_3 = NONE
e3.aout_k = NONE
e3.bout_1 = NONE
e3.bout_2 = NONE
e3.bout_3 = NONE
e3.bout_k = NONE
-> Input: 1.2 <-
_process_selector_ = main
running = TRUE
e3.running = FALSE
b3.running = FALSE
a3.running = FALSE
-> State: 1.2 <-
a3.state = WAIT_RESPONSE
a3.action = SEND_REQUEST
a3.out_1 = NA
a3.out_2 = IA
a3.out_k = KE
-> Input: 1.3 <-
-> State: 1.3 <-
a3.action = NONE
e3.state = WAIT_RESPONSE
e3.action = SEND_REQUEST
e3.bout_1 = NA
e3.bout_2 = IA
e3.bout_k = KB
-> Input: 1.4 <-
-> State: 1.4 <-
b3.state = WAIT_CONFIRMATION
b3.action = SEND_RESPONSE
b3.out_1 = NA
b3.out_2 = NB
b3.out_3 = IB
b3.out_k = KA
e3.action = NONE
-> Input: 1.5 <-
-> State: 1.5 <-
b3.action = NONE
e3.state = WAIT_CONFIRMATION
e3.action = SEND_RESPONSE
e3.aout_1 = NA
e3.aout_2 = NB
e3.aout_3 = IB
e3.aout_k = KA
e3.bout_1 = NONE
e3.bout_2 = NONE
e3.bout_k = NONE
-> Input: 1.6 <-
-> State: 1.6 <-
a3.state = ERROR
e3.action = NONE
-> Input: 1.7 <-
-> State: 1.7 <-
e3.state = OK
e3.action = SEND_CONFIRMATION
e3.aout_1 = NONE
e3.aout_2 = NONE
e3.aout_3 = NONE
e3.aout_k = NONE
e3.bout_1 = NA
e3.bout_k = KB
-> Input: 1.8 <-
-> State: 1.8 <-a
b3.state = ERROR
e3.action = NONE
第一个属性确认攻击失败并且握手没有完成Alice
和Bob
因为Eve
不满足它。第二个 属性 显示 如何 尝试攻击以及它在实践中如何失败。