使用 ProVerif 證明 TLS 握手的安全性
背景
對於我的論文,我正在研究一個需要 TLS 會話的應用程序協議。我希望實現完美前向保密,這意味著我選擇了短暫的 Diffie-Hellman 密碼套件。當我完成模型時,我打算偏愛橢圓曲線密碼學而不是正常 DHE。我希望這在我的模型中很容易互換。我發現了一個用 Pi 演算編寫的模型,它使用了 RSA,我已經適應了 DH。密切關注RFC 5246,我來到了下面的模型。
問題
不知何故,大部分似乎是死程式碼。我在死程式碼檢查失敗之前的行中添加了註釋。請幫助我了解出了什麼問題並導致 ProVerif 報告“無法證明”。
模型
(************************************************************************** * DEFINITIONS * **************************************************************************) param verboseClauses = explained. param traceDisplay = long. (* A public channel *) free net. (* Message tags *) free ClientHello, ClientKeyExchange, ClientCertificate, ServerHello, ServerCertificate, CertificateVerify, ServerKeyExchange. (* Agent initialization is done over a private channel *) private free initialClientData, initialServerData. (* The cryptographic constructors *) fun hash/1. (* hashing *) fun encrypt/2. (* symmetric key encryption *) fun pencrypt/2. (* Public key encryption *) fun sign/2. (* Public key signing *) fun enc/1. (* Extracts encryption key from a keypair *) fun dec/1. (* Extracts decryption key from a keypair *) (* The cryptographic destructors *) reduc decrypt(encrypt(x, y), y) = x. reduc pdecrypt(pencrypt(x, enc(y)), dec(y)) = x. reduc unsign(sign(x, enc(y)), dec(y)) = x. (* A constructor that maps agents to their secret keypairs *) private fun signkeypair/1. (* Pseudo−random−number function *) fun PRF/1. (* Symmetric key construction *) fun clientK/3. fun serverK/3. (* Certificates *) private fun cert/2. (* If cert(x, y) establishes x as owner of key y, y is returned *) reduc verify(cert(x, y), x) = y. (* Diffie-Hellman functions *) fun f/2. fun g/1. equation f(x, g(y)) = f(y, g(x)). (************************************************************************** * QUERIES * **************************************************************************) (* secrecy Pre Master secret *) query attacker: PMS. (* secrecy Master secret *) query attacker: MSa. query attacker: MSb. (* secrecy Finished message from the client *) query attacker: FinishedAFlag. (* secrecy Finished message from the server *) query attacker: FinishedBFlag. (* authenticity of the server *) query evinj: endServerAuth(x, y, z) ==> evinj: beginServerAuth(x, y, z). (* authenticity of the client *) query evinj: endClientAuth(x, y, z) ==> evinj: beginClientAuth(x, y, z). (* Dead code check *) query attacker: clientFinished. query attacker: serverFinished. (************************************************************************** * CLIENT PROCESS, this client is the initiator of the protocol. * **************************************************************************) let client = (** Initial agent data from a trusted channel **) in(initialClientData, (A, keypair, clientCert)); let SKc = enc(keypair) in let PKc = dec(keypair) in (** Replication to model arbitrary sessions **) ! (** Get the server’s name, perhaps as user input **) in(net, B); (* A generates fresh nonce Na *) new Na; (* A determines Sid, session is randomly generated in basic model *) new Sid; (* A chooses options Pa, options are just random data for our model *) new Pa; (* A −> B : ClientHello *) let CH = (ClientHello, A, Na, Sid, Pa) in out(net, CH); (* B −> A : ServerHello *) in(net, SH); let (=ServerHello, Nb, =Sid, Pb) = SH in (* B −> A : ServerCertificate *) in(net, SC); let (=ServerCertificate, serverCert) = SC in (* Receiving Server DH Key Parameters and checking integrity *) in(net, SKE); let (=ServerKeyExchange, G, N, GY, DHSignature) = SKE in let unsignKey = verify(serverCert, B) in (** THE LINE BELOW CAUSES PROVERIF TO CLAIM "CANNOT BE PROVED" WHEN CHECKING DEAD CODE **) let (=Na, =Nb, =G, =N, =GY) = unsign(DHSignature, unsignKey) in (** dead code check **) new clientFinished; out(net, clientFinished); (* A −> B: ClientCertificate *) let CC = (ClientCertificate, clientCert) in out(net, CC); (* A −> B : ClientKeyExchange *) new X; let CKE = (ClientKeyExchange, g(X)) in out(net, CKE); let cvHash = hash((CH, SH, SC, SKE, CC, CKE)) in (** begin client authentication **) event beginClientAuth(A, B, cvHash); (* A −> B: CertificateVerify *) let CV = sign((CertificateVerify, cvHash), SKc) in out(net, CV); (* Compute the pre-master secret (g^xy) *) let PMS = f(GY, g(X)) in (* A calculates the Master secret M *) let M = PRF((PMS, Na, Nb)) in (* A calculates Finished *) let Finished = hash((CH, SH, SC, CC, CKE, CV, M)) in (* A −> B : Finished *) out(net, encrypt(Finished, clientK(Na, Nb, M))); (* B −> A : Finished *) in(net, FB); (* A verifies received finished *) let =Finished = decrypt(FB, serverK(Na, Nb, M)) in (** end server authentication **) event endServerAuth(A, B, PMS); (** secrecy check on the Master secret **) new MSa; out(M, MSa) | (** secrecy check on the Finished message **) new FinishedAFlag; out(Finished, FinishedAFlag). (************************************************************************** * THE SERVER PROCESS * **************************************************************************) let server = (** THE LINE BELOW CAUSES PROVERIF TO CLAIM "CANNOT BE PROVED" WHEN CHECKING DEAD CODE **) (** Initial agent data from a trusted channel **) in(initialServerData, (B, keypair, serverCert)); (** dead code check **) new serverFinished; out(net, serverFinished); let PKs = dec(keypair) in let SKs = enc(keypair) in (** Replication to model arbitrary sessions **) ! (* A −> B : ClientHello *) in(net, CH); let (=ClientHello, A, Na, Sid, Pa) = CH in (* B generates fresh nonce Nb *) new Nb; (* B chooses options Pb, just some random data in our model *) new Pb; (* B −> A : ServerHello *) let SH = (ServerHello, Nb, Sid, Pb) in out(net, SH); (* B −> A : ServerCertificate *) let SC = (ServerCertificate, serverCert) in out(net, SC); (* Generate Diffie-Hellman Key Exchange parameters *) new G; (* Generator, public *) new N; (* Modulus (prime), public *) new Y; (* Server's secret exponent *) (* B -> A: ServerKeyExchange *) let SKE = (ServerKeyExchange, G, N, g(Y), sign((Na, Nb, G, N, g(Y)), SKs)) in out(net, SKE); (* A −> B : ClientCertificate *) in(net, CC); let (=ClientCertificate, clientCert) = CC in (* A −> B : ClientKeyExchange *) in(net, CKE); let(=ClientKeyExchange, GX) = CKE in (* A −> B : CertificateVerify *) let unsignKey = verify(clientCert, A) in in(net, CV); let (=CertificateVerify, cvHash) = unsign(CV, unsignKey) in (* B verifies client signature *) let =cvHash = hash((CH, SH, SC, SKE, CC, CKE)) in (** end client authentication **) event endClientAuth(A, B, cvHash); (* Calculating the Pre-Master Secret *) let PMS = f(GX, g(Y)) in (* A −> B : Finished *) in(net, FA); (* B calculates M *) let M = PRF((PMS, Na, Nb)) in (* B calculates Finished *) let Finished = hash((CH, SH, SC, SKE, CC, CKE, CV, M)) in (** server authentication **) event beginServerAuth(A, B, PMS); (* B −> A : Finished *) out(net, encrypt(Finished, serverK(Na, Nb, M))); (* B verifies received Finished *) let =Finished = decrypt(FA, clientK(Na, Nb, M)) in (** secrecy check on the Master secret **) new MSb; out(M, MSb) | (** secrecy check on the finished **) new FinishedBFlag; out(Finished, FinishedBFlag). (************************************************************************** * THE INITIALIZER PROCESS * **************************************************************************) let initializer = new C; (* Generate agent name *) new S; (* Generate agent name *) let clientKeyPair = signkeypair(C) in (* Generate client key *) let serverKeyPair = signkeypair(S) in (* Generate server key *) let clientCert = cert(C, dec(clientKeyPair)) in let serverCert = cert(S, dec(serverKeyPair)) in out(initialClientData, (C, clientKeyPair, clientCert)); out(initialServerData, (S, serverKeyPair, serverCert)); out(net, S). (************************************************************************** * THE SYSTEM * **************************************************************************) process !initializer | !client | !server
按照評論中的建議,我已將此問題發送到 ProVerif 郵件列表。我從布魯諾·布蘭切特那裡得到的答復如下。
實際上,ProVerif 並沒有說程式碼死了,它只是說它不能證明它沒有死。(它說“
RESULT not attacker:serverFinished_96[...]
無法證明”。)如果它說“RESULT not attacker:serverFinished_96[...] is true.
”,那麼程式碼肯定會死。程式碼真的沒有死。ProVerif 無法證明此屬性的原因來自於 Horn 子句在協議的內部表示中所做的抽象。更準確地說,你有
let initializer = ... out(initialClientData, (C, clientKeyPair, clientCert)); out(initialServerData, (S, serverKeyPair, serverCert)); ... let server = (** Initial agent data from a trusted channel **) in(initialServerData, (B, keypair, serverCert)); (** dead code check **) new serverFinished; out(net, serverFinished); ... let client = (** Initial agent data from a trusted channel **) in(initialClientData, (A, keypair, clientCert)); ...
在 Horn 子句級別,ProVerif 設法輸出
serverFinished
,如下文所述:
- 消息
(S_12356,signkeypair(S_12356),cert(S_12356,dec(signkeypair(S_12356))))
可以initialServerData[]
在輸出 {9} 的通道上發送。mess:initialServerData[]
,(S_12356,signkeypair(S_12356),cert(S_12356,dec(signkeypair(S_12356))))
.- 可以在輸入 {53} 處接收可由 1
(S_12356,signkeypair(S_12356),cert(S_12356,dec(signkeypair(S_12356))))
在通道上發送的消息。initialServerData[]
因此消息serverFinished_12357
可能會在輸出 {55} 處發送給攻擊者。attacker:serverFinished_12357
.ProVerif 認為它只需要執行初始化程序來發送消息
initialServerData
(上面的步驟 1),然後伺服器接收它以便輸出serverFinished
(上面的步驟 2)。ProVerif 然後嘗試在 pi 演算級別重建軌跡。但是,要initialServerData
在初始化程序中執行輸出,它首先需要在 上輸出一條消息initialClientData
。由於initialClientData
是私人頻道,因此需要輸入才能接收此消息。此輸入位於客戶端中。然而,它根本沒有出現在上面的 Horn 子句推導中。這就是跟踪重建失敗的原因,ProVerif 說它無法證明程式碼沒有死。更一般地說,Horn 子句表示在存在私有通道的情況下引入了更多抽象,即使在相當簡單的情況下,有時也會導致跟踪重建失敗,例如上述情況。
在您的範例中,避免該問題的一種方法是重寫初始化程序過程,以便它並行而不是按順序執行最終輸出:
(out(initialClientData, (C, clientKeyPair, clientCert)) | out(initialServerData, (S, serverKeyPair, serverCert)) | out(net, S)).