Proof dump, to be checked

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-02-10 15:45:12 +01:00
parent 186b1ad512
commit 008df914d4
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
8 changed files with 686 additions and 874 deletions

View File

@ -1,24 +1,68 @@
Copyright (c) 2015-2016, Luca Fulchir<luca@fulchir.it> Apache License
All rights reserved. Version 2.0, January 2004
http://www.apache.org/licenses/
Redistribution and use in source and binary forms, with or without TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 1. Definitions.
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,5 +1,13 @@
# Formal proofs
This repository houses all theory proofs to test the Fenrir project. This repository houses all theory proofs to test the Fenrir project.
NOTE: some of the proofs might be tests, of for older versions of Fenrir NOTE: some of the proofs might be tests, of for older versions of Fenrir
**This is a dump of proofs I worked on**
# TODO
* general clean up
* add cryptoverif proof

View File

@ -1,388 +0,0 @@
(*
* Formal definition to verify the Fenrir protocol.
* to be used with ProVerif
*
* This is to verify the correctness of the full 3-RTT handshake.
* DNSSEC is NOT verified, as it is assumed to be correct.
*
* The test *WILL* take a while... proverif says it has inserted more than 23k rules, and has a rule base of more than 12k rules.
* just leave it to work...
*)
(*
* ==============
* Type definition
* ==============
*)
type host.
type sKey. (* symmetric key *)
type pubKey. (* public key for asymmetric key encryption *)
type privKey. (* private key for asymmetric key encryption *)
type nonce. (* just a nonce *)
const leak: channel. (* to leak private material *)
const cf_full: channel. (* client-fenrir, full-security handshake *)
const cf_state: channel. (* client-fenrir, stateful handshake *)
const cf_dir: channel. (* client-fenrir, directory-synchronized handshake *)
const cs: channel. (* client-service *)
const sf: channel [private]. (* service-fenrir: since it is managed as the client-fenrir channel, this is not checked *)
const C, F, S: host. (* Client, Fenrir, Service *)
(* various stuff that should remain private *)
const leakCheck: bitstring [private].
const Auth: nonce [private].
const prF: privKey [private].
const prF_dir: privKey [private].
free fsKey: sKey [private].
free Rekey: bitstring. (* rekey message *)
type algorithm.
const alg_bad: algorithm. (* broken key exchange algorithm *)
const alg_good: algorithm. (* good key exchange algorithm *)
const alg_both: algorithm. (* both good and bad algorithms support*)
(*
* ==============
* Function definition
* ==============
*)
(* symmetric key encription *)
fun sEnc(bitstring, sKey): bitstring. (* encrypt with symmetric key *)
reduc forall c: bitstring, k: sKey; sDec(sEnc(c,k),k) = c. (* decrypt with symmetric key *)
(* public key definition *)
fun genPub(privKey): pubKey. (* public keys are generated from private ones *)
fun aEnc(bitstring, pubKey): bitstring. (* encrypt with asymmetric public key *)
reduc forall c: bitstring, k: privKey; aDec(aEnc(c,genPub(k)),k) = c. (* decrypt with private key *)
(* public key signing *)
fun sign(bitstring, privKey): bitstring.
reduc forall c: bitstring, k: privKey; ver(c, sign(c, k), genPub(k)) = true. (* verify signature *)
(* integrity for encrypt then mac *)
fun int(bitstring, sKey): bitstring.
reduc forall c: bitstring, k: sKey; verint(c, int(c, k), k) = true.
(* some key exchange based on public keys *)
fun exchange(privKey, pubKey) : sKey.
equation forall p1 : privKey, p2 : privKey; exchange(p1, genPub(p2)) = exchange(p2, genPub(p1)).
(*
* ==============
* Events -- used for correlation
* ==============
*)
event downgrade().
event acceptedClient(host, sKey).
event acceptedFenrir(host, sKey).
event acceptedClientState(host, sKey).
event acceptedFenrirState(host, sKey).
event clientConnect(host, host, sKey).
event serviceConnect(host, host, sKey).
(* Deactivated because the complexity explodes...
let client_rekey (key: sKey, chan: channel) =
new rekey_p1: privKey;
new rekey_n1: nonce;
out (chan, sEnc((rekey_n1, genPub(rekey_p1)), key));
in (chan, enc: bitstring);
let (=rekey_n1, srv_eph : pubKey) = sDec(enc, key) in
let (new_Key) = exchange(rekey_p1, srv_eph) in
out (chan, sEnc((Auth, Auth), new_Key))
.
let server_rekey (key: sKey, chan: channel) =
new rekey_s_p: privKey;
in (chan, enc: bitstring);
let (client_n1: nonce, client_p: pubKey) = sDec (enc, key) in
out (chan, sEnc((client_n1, genPub(rekey_s_p)), key));
let (new_Key) = exchange(rekey_s_p, client_p) in
in (chan, enc2: bitstring);
let (=Auth, =Auth) = sDec(enc2, new_Key)
.
*)
(*
* ==========================================================================
* Client definition, Full-security handshake with good and broken algorithms
* ==========================================================================
*)
let client_full(pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce, supported: algorithm) =
new cn1: nonce; (* Client Nonce 1 *)
new prC: privKey;
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_full, (client, cn1, supported));
in (cf_full, (sn1: nonce, selected: algorithm, s1: bitstring, s2: bitstring));
if ver((sn1, cn1, supported, selected), s1, pubF) = true then
if ver((sn1, selected), s2, pubF) = true then
(* RTT 1 completed *)
(* start algorithm agreement *)
new cn2: nonce;
(* out: nonces and signature, supported_algorithms *)
(* bad algorithm somehow exposes authentication data *)
if selected = alg_bad && (supported = alg_both || supported = alg_bad) then
(
event downgrade()
)
else
(
if selected = alg_good then
out (cf_full, (sn1, selected, s2, cn2, genPub(prC)));
(* se1: server encrypt 1: contains supported authentication algorithms
* unused here, it's the same mechanism as the "selected" one
*)
in (cf_full, (ephF: pubKey, se1: bitstring, s3: bitstring));
if ver((ephF, se1), s3, pubF) = true then
let (cfKey) = exchange(prC, ephF) in
(* RTT 2 Completed *)
out (cf_full, (sEnc((Auth, Auth), cfKey)));
in (cf_full, (se2: bitstring));
let (chan: channel, csKey: sKey) = sDec(se2, cfKey) in
event acceptedClient(client, cfKey);
(* RTT 3 completed, can now connect to service *)
(* also send private data to test secrecy *)
event clientConnect(service, client, csKey);
out (chan, sEnc((Auth, Auth), csKey))
).
(* now rekey *)
(*
* ===========================================================================
* Fenrir definition, full security handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_full(privF: privKey, fenrir: host, service: host, Auth: nonce, supported: algorithm) =
(*
* NOTE: we do not model the service <-> fenrir authentication, as that is just the same
* as the fenrir <-> client full security authentication. We assume fenrir and service already share a key
*)
(* Nonce exchange *)
in (cf_full, (client: host, fin1: nonce, client_alg: algorithm));
new s_nonce: nonce;
(* triggers "event downgrade()" in the client, no need to go further *)
(* bad only if both support bad *)
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then
(
out (cf_full, (s_nonce, alg_bad, sign((s_nonce, fin1, client_alg, alg_bad), privF),
sign((s_nonce, alg_bad), privF)))
)
else
(
if client_alg = alg_good || client_alg = alg_both then
out (cf_full, (s_nonce, alg_good, sign((s_nonce, fin1, client_alg, alg_good), privF),
sign((s_nonce, alg_good), privF)));
(* RTT 1 finished *)
in (cf_full, (=s_nonce, =alg_good, signed: bitstring, fin2: nonce, pubC: pubKey));
if ver((s_nonce, alg_good), signed, genPub(privF)) = true then
new prF2: privKey;
let (cfKey) = exchange(prF2, pubC) in
out (cf_full, (genPub(prF2), sEnc((supported, supported), cfKey), sign((genPub(prF2), sEnc((s_nonce, s_nonce), cfKey)), privF)));
(* RTT 2 finished, now we have a state *)
in (cf_full, (ce1: bitstring));
let (data: nonce, data2: nonce) = sDec(ce1, cfKey) in
if data = Auth && data2 = Auth then
new csKey: sKey;
out (sf, (client, csKey)); (* inform service of new client *)
event acceptedFenrir(client, cfKey);
let (se1) = sEnc((cs, csKey), cfKey) in
out (cf_full, (sEnc((cs, csKey), cfKey)))
(* RT3 finished *)
).
(*
* ==================================
* Service definition, common for all
* ==================================
*)
let service(service: host) =
in (sf, (cli: host, csKey:sKey));
in (cs, enc2 : bitstring);
let (Auth1 : nonce, Auth2 : nonce) = sDec(enc2, csKey) in
event serviceConnect(service, cli, csKey).
(*
* ======================================================================
* Client Definition, Stateful handshake, with good and broken algorithms
* ======================================================================
*)
let client_state (pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce, supported: algorithm) =
new cn1: nonce; (* Client Nonce 1 *)
new prC: privKey;
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_state, (cn1, client, supported));
in (cf_state, (alg: algorithm, ephF: pubKey, s1: bitstring));
if ver((cn1, client, supported, alg, ephF), s1, pubF) = true then
(* RTT 1 completed *)
if alg = alg_bad then
(
if supported = alg_bad || supported = alg_both then
(* bad algorithm exposes authentication data *)
event downgrade()
)
else
(
if alg = alg_good then
if supported = alg_good || supported = alg_both then
let (cfKey) = exchange(prC, ephF) in
out (cf_state, (genPub(prC), sEnc((Auth,Auth), cfKey)));
in (cf_state, enc: bitstring);
let (chan : channel, csKey : sKey) = sDec(enc, cfKey) in
event acceptedClientState(client, csKey);
(* RTT 2 completed, can now connect to service *)
(* also send private data to test secrecy *)
event clientConnect(service, client, csKey);
out (chan, sEnc((Auth, Auth), csKey))
).
(*
* ===========================================================================
* Fenrir definition, stateful handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_state (privF: privKey, fenrir: host, service: host, Auth: nonce, supported: algorithm) =
(*
* NOTE: we do not model the service <-> fenrir authentication, as that is just the same
* as the fenrir <-> client full-securoty authentication. We assume fenrir and service already share a key
*)
(* Nonce exchange *)
new prF2 : privKey;
new s_nonce: nonce;
in (cf_state, (fin1: nonce, client: host, client_alg: algorithm));
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then
(
out(cf_state, (alg_bad, genPub(prF2), sign((fin1, client, client_alg, alg_bad, genPub(prF2)), privF)))
)
else
(
if (client_alg = alg_good || client_alg = alg_both) && (supported = alg_good || supported = alg_both) then
out(cf_state, (alg_good, genPub(prF2), sign((fin1, client, client_alg, alg_good, genPub(prF2)), privF)));
(* RTT 1 finished *)
new csKey: sKey;
in (cf_state, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(prF2, pubC) in
let (data: nonce, data2: nonce) = sDec(enc, cfKey) in
if data = Auth && data2 = Auth then
out (sf, (client, csKey)); (* inform service of new client *)
event acceptedFenrirState(client, csKey);
out (cf_state, sEnc((cs, csKey), cfKey))
(* RTT 2 finished *)
).
(*
* ===========================================================================
* Client definition, directory synchronized handshake
* ===========================================================================
*)
let client_dir (pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce) =
new prC : privKey;
new cn : nonce;
let (cfKey) = exchange(prC, pubF) in
out (cf_dir, (genPub(prC), sEnc((client, Auth,cn), cfKey)));
in (cf_dir, enc: bitstring);
let (=cn, chan: channel, csKey: sKey) = sDec(enc, cfKey) in
(* RTT 1 finished, now connect to service *)
out (chan, sEnc((Auth, Auth), csKey)).
(*
* ===========================================================================
* Fenrir definition, directory synchronized handshake
* ===========================================================================
*)
let fenrir_dir (privF: privKey, fenrir: host, service: host, Auth: nonce) =
in (cf_dir, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(privF, pubC) in
let (cname: host, =Auth, cn: nonce) = sDec (enc, cfKey) in
new csKey : sKey;
out (sf, sEnc((cname, csKey), fsKey)); (* inform service of new client *)
out (cf_dir, sEnc((cn, cs, csKey), cfKey)).
(*
* Event correlation:
*)
(* query h: host, k: sKey; event(acceptedFenrir(h, k)) ==> event(acceptedClient(h, k)). *)
(* *)
query h: host, k: sKey; event(acceptedClientState(h, k)) ==> event(acceptedFenrirState(h, k)).
query h1: host, h2 : host, k: sKey, t: nonce; event(serviceConnect(h1, h2, k)) ==> event(clientConnect(h1, h2, k)).
(*
* ==============
* What should be secret?
* ==============
*)
(*query attacker(prF). do NOT check this as we volountarly leak it after the process *)
(*query attacker(prF_dir).*) (* the private key in the directory-synchronized case must be secret *)
query attacker(Auth). (* client auth data. *)
query event(downgrade()).
(*
* ==============
* Run the verification
* ==============
*)
(* make it easy to deactivate single parts, like the 2-RTT login, which has unproven events problems *)
(* TODO: fix PFS: must leak both server and client public key *)
process
let pbF = genPub(prF) in
let pbF_dir = genPub(prF_dir) in
( (!service(S))
| (!client_full (pbF, C, S, F, Auth, alg_both)) | (!client_full (pbF, C, S, F, Auth, alg_good))
| (!fenrir_full (prF, F, S, Auth, alg_both)) | (!fenrir_full (prF, F, S, Auth, alg_good))
| (!client_state(pbF, C, S, F, Auth, alg_both)) | (!client_state(pbF, C, S, F, Auth, alg_good))
| (!fenrir_state(prF, F, S, Auth, alg_both)) | (!fenrir_state(prF, F, S, Auth, alg_good))
| (!client_dir (pbF_dir, C, S, F, Auth)) | (!fenrir_dir (prF, F, S, Auth))
| phase 1; out(leak, prF) (* PFS: leaking of the long-term secret will NOT affect previous communications *)
(* Now do everything again, WITHOUT leaking. Everything must be done again since
* we leaked the previous private key. If we didn't do everything again we could not prove
* that the interaction of the handshakes is secure.
* This is because the 1-RTT handshake does NOT have long-term secret
*)
; (!client_full (pbF_dir, C, S, F, Auth, alg_both)) | (!client_full (pbF_dir, C, S, F, Auth, alg_good))
| (!fenrir_full (prF_dir, F, S, Auth, alg_both)) | (!fenrir_full (prF_dir, F, S, Auth, alg_good))
| (!client_state(pbF_dir, C, S, F, Auth, alg_both)) | (!client_state(pbF_dir, C, S, F, Auth, alg_good))
| (!fenrir_state(prF_dir, F, S, Auth, alg_both)) | (!fenrir_state(prF_dir, F, S, Auth, alg_good))
| (!client_dir (pbF_dir, C, S, F, Auth)) | (!fenrir_dir (prF_dir, F, S, Auth))
)

View File

@ -0,0 +1,493 @@
(*
* Formal definition to verify the Fenrir protocol.
* to be used with ProVerif
*
* This is to verify the correctness of the full 3-RTT handshake.
* DNSSEC is NOT verified, as it is assumed to be correct.
*
*)
(*
* ==============
* Type definition
* ==============
*)
type host.
type sKey. (* symmetric key *)
type pubKey. (* public key for asymmetric key encryption *)
type privKey. (* private key for asymmetric key encryption *)
type nonce. (* just a nonce *)
type encrypted. (* to avoid unifying endlessly
* used only for the encrypted, authenticated server state sent in one packet.
* without this proverif tries to unify all bitstrings
* while it should be impossible.
*)
const leak: channel. (* to leak private material *)
const cf_full: channel. (* client-fenrir, full-security handshake *)
const cf_state: channel. (* client-fenrir, stateful handshake *)
const cf_dir: channel. (* client-fenrir, directory-synchronized handshake *)
const cs: channel. (* client-service *)
const sf: channel [private]. (* service-fenrir: since it is managed as the client-fenrir channel, this is not checked *)
(* various stuff that should remain private *)
const Auth: nonce [private].
const prS: privKey [private]. (* service private key *)
const prF: privKey [private].
const prC: privKey [private].
const prF_dir: privKey [private].
const OTP: sKey [private].
const f_key: sKey [private].
free csKey: sKey [private].
free Rekey: bitstring. (* rekey message *)
type algorithm.
const alg_bad: algorithm. (* broken key exchange algorithm *)
const alg_good: algorithm. (* good key exchange algorithm *)
const alg_both: algorithm. (* both good and bad algorithms support*)
(*
* ==============
* Function definition
* ==============
*)
(* any to bitstring conversion *)
fun fromKey(sKey) : bitstring [data, typeConverter].
reduc forall a : sKey; toKey(fromKey(a)) = a.
fun fromNonce(nonce) : bitstring [data, typeConverter].
reduc forall a : nonce; toNonce(fromNonce(a)) = a.
fun toEncrypted(bitstring) : encrypted [data, typeConverter].
reduc forall a :bitstring; fromEncrypted(toEncrypted(a)) = a.
(* symmetric key encription *)
fun sEnc(bitstring, sKey): bitstring. (* encrypt with symmetric key *)
reduc forall c: bitstring, k: sKey; sDec(sEnc(c,k),k) = c. (* decrypt with symmetric key *)
(* public key definition *)
fun genPub(privKey): pubKey. (* public keys are generated from private ones *)
(* public key signing *)
fun sign(bitstring, privKey): bitstring.
reduc forall c: bitstring, k: privKey; ver(c, sign(c, k), genPub(k)) = true. (* verify signature *)
(* some key exchange based on public keys *)
fun exchange(privKey, pubKey) : sKey.
equation forall p1 : privKey, p2 : privKey; exchange(p1, genPub(p2)) = exchange(p2, genPub(p1)).
(* hash used by OTP, sKey based *)
fun hash(sKey) : sKey.
(* xor function *)
fun xor(sKey, sKey) : sKey.
reduc forall a : sKey, b : sKey; unxor(xor(a, b), b) = a.
reduc forall a : sKey, b : sKey; unxor2(xor(a, b), a) = b. (* created for proverif, not used for simplicity *)
(*
* ==============
* Events -- used for correlation
* ==============
*)
event downgrade().
event acceptedFenrir(sKey).
event clientConnect(sKey, sKey).
event serviceConnect(sKey).
event connection_full().
event connection_state().
event connection_dir().
(* NOTE: in multiple places we have an if-else branch over a nonce.
* and the else branch start with an "if" that is the opposite of the first "if".
* This "if" after the else seems unnecessary but actually hugely speeds up proverif.
* reason: proverif does not have to check for all the possible nonces. It seems
* it doesn't notice that the nonce can have only few vales, since it actually is an enum *)
(*
* ==========================================================================
* Client definition, Full-security handshake with good and broken algorithms
* ==========================================================================
*)
set ignoreTypes = false.
set expandIfTermsToTerms = true. (* slower ? *)
let client_full(pubF: pubKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm, OTP: sKey) =
new cn1: nonce; (* Client Nonce 1 *)
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_full, (cn1, supported_enc));
in (cf_full, (sn1: nonce, selected_enc : algorithm, srv_supported_auth : algorithm, s1: bitstring, s2: bitstring));
if ver((cn1, supported_enc, sn1, selected_enc, srv_supported_auth), s1, pubF) = true then
if ver((sn1, selected_enc), s2, pubF) = true then
(* RTT 1 completed *)
(* start algorithm agreement *)
new cn2: nonce;
(* out: nonces and signature, supported_algorithms *)
(* bad algorithm somehow exposes authentication data *)
if selected_enc = alg_bad && (supported_enc = alg_both || supported_enc = alg_bad) then
(
event downgrade()
)
else
(
if selected_enc = alg_good && (supported_enc = alg_both || supported_enc = alg_good) then
out (cf_full, (sn1, selected_enc, s2, cn2, genPub(prC)));
(* s_auth: server supported 1: contains supported authentication algorithms
* unused here, it's the same mechanism as the "selected" one
*)
in (cf_full, (ephF: pubKey, srv_key : encrypted, s3: bitstring));
if ver((ephF, srv_key, cn2), s3, pubF) = true then
let (cfKey) = exchange(prC, ephF) in
(* RTT 2 Completed *)
if srv_supported_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then
(
(* authentication somehow leaks info through encryption
* are we being a bit paranoid?
*)
event downgrade()
)
else
(
if (srv_supported_auth = alg_both || srv_supported_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then
out (cf_full, (srv_key, sEnc(fromNonce(Auth), cfKey)));
in (cf_full, (se2: bitstring));
let (chan: channel, xorKey: sKey) = sDec(se2, cfKey) in (* TYPE ERROR? *)
(* RTT 3 completed, can now connect to service *)
let cs_Key : sKey = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_full()
)
).
(*
* ===========================================================================
* Fenrir definition, full security handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_full(privF: privKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm, sec_key : sKey) =
(* Nonce exchange *)
in (cf_full, (c_n1: nonce, client_support_enc: algorithm));
new s_nonce: nonce;
(* triggers "event downgrade()" in the client, no need to go further *)
(* bad only if both support bad *)
if client_support_enc = alg_bad && (supported_enc = alg_bad || supported_enc = alg_both) then
(
out (cf_full, (s_nonce, alg_bad, supported_auth, sign((c_n1, client_support_enc, s_nonce, alg_bad, supported_auth), privF),
sign((s_nonce, alg_bad), privF)))
)
else
(
if client_support_enc = alg_good || client_support_enc = alg_both then
out (cf_full, (s_nonce, alg_good, supported_auth, sign((c_n1, client_support_enc, s_nonce, alg_good, supported_auth), privF),
sign((s_nonce, alg_good), privF)));
(* RTT 1 finished *)
in (cf_full, (=s_nonce, =alg_good, signed: bitstring, c_n2: nonce, pubC: pubKey));
if ver((s_nonce, alg_good), signed, genPub(privF)) = true then
new prF2: privKey;
let (cfKey) = exchange(prF2, pubC) in
let enc_srv_key : bitstring = sEnc(fromKey(cfKey), sec_key) in
out (cf_full, (genPub(prF2), toEncrypted(enc_srv_key), sign((genPub(prF2), enc_srv_key, c_n2), privF)));
(* RTT 2 finished, now we have a state *)
in (cf_full, (srv_en : encrypted, ce1: bitstring));
let keystring : bitstring = sDec(fromEncrypted(srv_en), sec_key) in
let datastring : bitstring = sDec(ce1, toKey(keystring)) in
if toNonce(datastring) = Auth then
new user_info : nonce;
out (sf, user_info);
in (sf, key : sKey);
event acceptedFenrir(toKey(keystring));
out (cf_full, (sEnc((cs, key), toKey(keystring))))
(* RT3 finished *)
).
(*
* ==================================
* Service definition, common for all
* ==================================
*)
let service(OTP : sKey) =
(* sf channel is secret, thus trusted. do not encrypt to reduce
* proverif running time *)
in (sf, rnd : nonce); (* user info. useless in our model *)
out (sf, xor(csKey, hash(OTP)));
in (cs, enc2 : bitstring);
let is_OTP : bitstring = sDec(enc2, csKey) in
if OTP = toKey(is_OTP) then
event serviceConnect(csKey).
(*
* ======================================================================
* Client Definition, Stateful handshake, with good and broken algorithms
* ======================================================================
*)
let client_state (pubF: pubKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm, OTP: sKey) =
new cn1: nonce; (* Client Nonce 1 *)
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_state, (cn1, supported_enc));
in (cf_state, (selected_alg: algorithm, srv_supported_auth: algorithm, ephF: pubKey, s1: bitstring));
if ver((cn1, supported_enc, selected_alg, srv_supported_auth, ephF), s1, pubF) = true then
(* RTT 1 completed *)
if selected_alg = alg_bad then
(
if supported_enc = alg_bad || supported_enc = alg_both then
(* bad algorithm exposes authentication data *)
event downgrade()
)
else
(
if selected_alg = alg_good && (supported_enc = alg_good || supported_enc = alg_both) then
if srv_supported_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then
(
event downgrade()
)
else
(
if (srv_supported_auth = alg_both || srv_supported_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then
let (cfKey) = exchange(prC, ephF) in
out (cf_state, (genPub(prC), sEnc(fromNonce(Auth), cfKey)));
in (cf_state, enc: bitstring);
(* RTT 2 completed, can now connect to service *)
let (chan: channel, xorKey: sKey) = sDec(enc, cfKey) in
let cs_Key = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_state()
)
).
(*
* ===========================================================================
* Fenrir definition, stateful handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_state (privF: privKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm) =
(* Nonce exchange *)
new prF2 : privKey;
new s_nonce: nonce;
in (cf_state, (c_n1: nonce, client_supported_enc: algorithm));
if client_supported_enc = alg_bad && (supported_enc = alg_bad || supported_enc = alg_both) then
(
out(cf_state, (alg_bad, supported_auth, genPub(prF2), sign((c_n1, client_supported_enc, alg_bad, supported_auth, genPub(prF2)), privF)))
)
else
(
if (client_supported_enc = alg_good || client_supported_enc = alg_both) && (supported_enc = alg_good || supported_enc = alg_both) then
out(cf_state, (alg_good, supported_auth, genPub(prF2), sign((c_n1, client_supported_enc, alg_good, supported_auth, genPub(prF2)), privF)));
(* RTT 1 finished *)
in (cf_state, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(prF2, pubC) in
let datastring : bitstring = sDec(enc, cfKey) in
if toNonce(datastring) = Auth then
new user_info : nonce;
out (sf, (user_info));
in (sf, key : sKey);
event acceptedFenrir(cfKey);
out (cf_state, sEnc((cs, key), cfKey))
(* RTT 2 finished *)
).
(*
* ===========================================================================
* Client definition, directory synchronized handshake
* ===========================================================================
*)
let client_dir (pubF: pubKey, Auth: nonce, OTP: sKey) =
new cn : nonce;
let (cfKey) = exchange(prC, pubF) in
out (cf_dir, (genPub(prC), sEnc((Auth,cn), cfKey)));
in (cf_dir, enc: bitstring);
(* RTT 1 finished, now connect to service *)
let (=cn, chan: channel, xorKey: sKey) = sDec(enc, cfKey) in
let cs_Key = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_dir().
(*
* ===========================================================================
* Fenrir definition, directory synchronized handshake
* ===========================================================================
*)
let fenrir_dir (privF: privKey, Auth: nonce) =
in (cf_dir, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(privF, pubC) in
let (=Auth, cn: nonce) = sDec (enc, cfKey) in
new user_info : nonce;
out (sf, user_info);
in (sf, key : sKey);
event acceptedFenrir(cfKey);
out (cf_dir, sEnc((cn, cs, key), cfKey)).
(*
* ==============
* Event correlation:
* ==============
*)
query k1 : sKey, k2 : sKey; event(serviceConnect(k2)) ==> event(clientConnect(k1, k2)).
query k1 : sKey, k2 : sKey; event(clientConnect(k1, k2)) ==> event(acceptedFenrir(k1)).
(*
* ==============
* What should be secret?
* ==============
*)
query event(downgrade()).
query attacker(OTP). (* client-service OTP data. *)
query attacker(Auth). (* client auth data. *)
(*
* Event connection() HAS to be triggered
* we use it as a proof that we didn't just make a protocol that will never compete a handshake.
*)
query event(connection_full()).
query event(connection_state()).
query event(connection_dir()).
(*
* ==============
* Run the verification
* ==============
*)
process
let pbF = genPub(prF) in
let pbF_dir = genPub(prF_dir) in
(
(* Perfect forward secrecy test. Note that the _dir methods work with non-leaked keys, as there
* is no master key
*)
(* these tests alone needs about 26k to 46k rules in proverif
* phase 0: both parts support good algorithms.
* phase 1: client supports only bad algorithms
* phase 2: auth-server supports only bad algorithms
* phase 3: leak long term secret to test perfect forward secrecy,
*)
(!service(OTP))
| (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_good, alg_both, OTP))
| (!client_full (pbF, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF, Auth, alg_both, alg_both, f_key))| (!fenrir_full (prF, Auth, alg_good, alg_both, f_key))
| (!fenrir_full (prF, Auth, alg_both, alg_good, f_key))| (!fenrir_full (prF, Auth, alg_good, alg_good, f_key))
(*
(!service(OTP))
| (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_good, alg_both, OTP))
| (!client_full (pbF, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF, Auth, alg_both, alg_both, f_key))| (!fenrir_full (prF, Auth, alg_good, alg_both, f_key))
| (!fenrir_full (prF, Auth, alg_both, alg_good, f_key))| (!fenrir_full (prF, Auth, alg_good, alg_good, f_key))
| (!client_state(pbF, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF, Auth, alg_good, alg_both, OTP))
| (!client_state(pbF, Auth, alg_both, alg_good, OTP)) | (!client_state(pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_good, alg_both))
| (!fenrir_state(prF, Auth, alg_both, alg_good)) | (!fenrir_state(prF, Auth, alg_good, alg_good))
*)
(*
(!service(OTP))
| (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_both, OTP))
| (!client_full (pbF, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_full (prF, Auth, alg_good, alg_good, f_key))
| (!client_state(pbF, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_both, OTP))
| (!client_state(pbF, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_state(prF, Auth, alg_good, alg_good))
*)
(*
(!service(OTP))
| (!client_full (pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF, Auth, alg_both, alg_both)) | (!fenrir_full (prF, Auth, alg_bad, alg_both))
| (!fenrir_full (prF, Auth, alg_both, alg_bad, f_key))| (!fenrir_full (prF, Auth, alg_bad, alg_bad, f_key))
| (!client_state(pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_bad, alg_both))
| (!fenrir_state(prF, Auth, alg_both, alg_bad)) | (!fenrir_state(prF, Auth, alg_bad, alg_bad))
*)
(*
| phase 1; out(leak, prF)
*)
(*
*)
(* PFS: leaking of the long-term secret will not affect previous communications *)
(* Now do everything again, WITHOUT leaking. Everything must be done again since
* we leaked the previous private key. If we didn't do everything again we could not prove
* that the interaction of the handshakes is secure.
* This is because the 1-RTT handshake does NOT have long-term secret, so wen can't leak its private key.
*)
(* these next tests alone needs about 37k to 55k rules in proverif
* the previous and the next tests together are way too much, as proverif seems to have a huge slow down for every new phase,
* even when empty. Therefore activate either the previous or the next tests, they are independent anyway.
* pahse 0-2: like previous phase 0-2, except we add the directory synchronized handshake
* and do NOT leak the key at the end. This way we can test the interaction
* of different handshakes with the same public key.
*)
(*
(!service(OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_both, OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF_dir, Auth, alg_both, alg_both, f_key))| (!fenrir_full (prF_dir, Auth, alg_good, alg_both, f_key))
| (!fenrir_full (prF_dir, Auth, alg_both, alg_good, f_key))| (!fenrir_full (prF_dir, Auth, alg_good, alg_good, f_key))
| (!client_state(pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF_dir, Auth, alg_good, alg_both, OTP))
| (!client_state(pbF_dir, Auth, alg_both, alg_good, OTP)) | (!client_state(pbF_dir, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF_dir, Auth, alg_both, alg_both)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_both))
| (!fenrir_state(prF_dir, Auth, alg_both, alg_good)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_good))
| (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth))
*)
(*
(!service(OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_both, OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_full (prF_dir, Auth, alg_good, alg_good, f_key))
| (!client_state(pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF_dir, Auth, alg_bad, alg_both, OTP))
| (!client_state(pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF_dir, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_state(prF_dir, Auth, alg_good, alg_good))
| (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth))
*)
(*
(!service(OTP))
| (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF_dir, Auth, alg_both, alg_both, f_key))| (!fenrir_full (prF_dir, Auth, alg_bad, alg_both, f_key))
| (!fenrir_full (prF_dir, Auth, alg_both, alg_bad, f_key))| (!fenrir_full (prF_dir, Auth, alg_bad, alg_bad, f_key))
| (!client_state(pbF_dir, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF_dir, Auth, alg_both, alg_both)) | (!fenrir_state(prF_dir, Auth, alg_bad, alg_both))
| (!fenrir_state(prF_dir, Auth, alg_both, alg_bad)) | (!fenrir_state(prF_dir, Auth, alg_bad, alg_bad))
| (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth))
*)
)

View File

@ -51,8 +51,10 @@ const alg_both: algorithm. (* both good and bad algorithms support*)
*) *)
(* any to bitstring conversion *) (* any to bitstring conversion *)
fun fromKey(sKey) : bitstring. fun fromKey(sKey) : bitstring [data, typeConverter].
reduc forall a : sKey; toKey(fromKey(a)) = a. reduc forall a : sKey; toKey(fromKey(a)) = a.
fun fromNonce(nonce) : bitstring [data, typeConverter].
reduc forall a : nonce; toNonce(fromNonce(a)) = a.
(* symmetric key encription *) (* symmetric key encription *)
@ -87,14 +89,16 @@ event downgrade().
event acceptedFenrir(sKey). event acceptedFenrir(sKey).
event clientConnect(sKey, sKey). event clientConnect(sKey, sKey).
event serviceConnect(sKey). event serviceConnect(sKey).
event connection_full().
event connection_state().
event connection_dir().
(* NOTE: in multiple places we have an if-else branch over a nonce. (* NOTE: in multiple places we have an if-else branch over a nonce.
* and the else branch start with an "if" that is the opposite of the first "if". * and the else branch start with an "if" that is the opposite of the first "if".
* This "if" after the else seems unnecessary but actually hugely speeds up proverif. * This "if" after the else seems unnecessary but actually hugely speeds up proverif.
* reason: proverif does not have to check for all the possible nonces, as it seems * reason: proverif does not have to check for all the possible nonces. It seems
* it doesn't notice that the nonce can have only few vales, as it actually is an enum *) * it doesn't notice that the nonce can have only few vales, since it actually is an enum *)
(* (*
* ========================================================================== * ==========================================================================
@ -102,7 +106,11 @@ event serviceConnect(sKey).
* ========================================================================== * ==========================================================================
*) *)
let client_full(pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth: algorithm, OTP: sKey) =
set ignoreTypes = false.
set expandIfTermsToTerms = true. (* slower ? *)
let client_full(pubF: pubKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm, OTP: sKey) =
new cn1: nonce; (* Client Nonce 1 *) new cn1: nonce; (* Client Nonce 1 *)
(* Note: this nonce is technically useless, but prevents amplification/DoS attack (* Note: this nonce is technically useless, but prevents amplification/DoS attack
@ -110,25 +118,24 @@ let client_full(pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth:
* and doesn't play nicely with timers, so we don't really care... * and doesn't play nicely with timers, so we don't really care...
*) *)
(* start cookie exchange *) (* start cookie exchange *)
out (cf_full, (cn1, supported)); out (cf_full, (cn1, supported_enc));
in (cf_full, (sn1: nonce, srv_auth : algorithm, selected: algorithm, s1: bitstring, s2: bitstring)); in (cf_full, (sn1: nonce, selected_enc : algorithm, srv_supported_auth : algorithm, s1: bitstring, s2: bitstring));
if ver((sn1, cn1, supported, selected, srv_auth), s1, pubF) = true then if ver((cn1, supported_enc, sn1, selected_enc, srv_supported_auth), s1, pubF) = true then
if ver((sn1, selected, srv_auth), s2, pubF) = true then if ver((sn1, selected_enc), s2, pubF) = true then
(* RTT 1 completed *) (* RTT 1 completed *)
(* start algorithm agreement *) (* start algorithm agreement *)
new cn2: nonce; new cn2: nonce;
(* out: nonces and signature, supported_algorithms *) (* out: nonces and signature, supported_algorithms *)
(* bad algorithm somehow exposes authentication data *) (* bad algorithm somehow exposes authentication data *)
if selected = alg_bad && (supported = alg_both || supported = alg_bad) then if selected_enc = alg_bad && (supported_enc = alg_both || supported_enc = alg_bad) then
( (
event downgrade() event downgrade()
) )
else else
( (
if selected = alg_good && (supported = alg_both || supported = alg_good) then if selected_enc = alg_good && (supported_enc = alg_both || supported_enc = alg_good) then
out (cf_full, (sn1, selected, s2, cn2, genPub(prC))); out (cf_full, (sn1, selected_enc, s2, cn2, genPub(prC)));
(* s_auth: server supported 1: contains supported authentication algorithms (* s_auth: server supported 1: contains supported authentication algorithms
* unused here, it's the same mechanism as the "selected" one * unused here, it's the same mechanism as the "selected" one
*) *)
@ -136,7 +143,7 @@ let client_full(pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth:
if ver((ephF, cn2), s3, pubF) = true then if ver((ephF, cn2), s3, pubF) = true then
let (cfKey) = exchange(prC, ephF) in let (cfKey) = exchange(prC, ephF) in
(* RTT 2 Completed *) (* RTT 2 Completed *)
if srv_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then if srv_supported_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then
( (
(* authentication somehow leaks info through encryption (* authentication somehow leaks info through encryption
* are we being a bit paranoid? * are we being a bit paranoid?
@ -145,16 +152,17 @@ let client_full(pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth:
) )
else else
( (
if (srv_auth = alg_both || srv_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then if (srv_supported_auth = alg_both || srv_supported_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then
out (cf_full, (sEnc((Auth, Auth), cfKey))); out (cf_full, (sEnc(fromNonce(Auth), cfKey)));
in (cf_full, (se2: bitstring)); in (cf_full, (se2: bitstring));
let (chan: channel, xorKey: bitstring) = sDec(se2, cfKey) in let (chan: channel, xorKey: sKey) = sDec(se2, cfKey) in (* TYPE ERROR? *)
(* RTT 3 completed, can now connect to service *) (* RTT 3 completed, can now connect to service *)
let cs_Key = sDec(xorKey, hash(OTP)) in let cs_Key : sKey = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, toKey(cs_Key)); event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), toKey(cs_Key))) out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_full()
) )
). ).
@ -165,32 +173,32 @@ let client_full(pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth:
* =========================================================================== * ===========================================================================
*) *)
let fenrir_full(privF: privKey, Auth: nonce, supported: algorithm, supported_auth: algorithm) = let fenrir_full(privF: privKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm) =
(* Nonce exchange *) (* Nonce exchange *)
in (cf_full, (fin1: nonce, client_alg: algorithm)); in (cf_full, (c_n1: nonce, client_support_enc: algorithm));
new s_nonce: nonce; new s_nonce: nonce;
(* triggers "event downgrade()" in the client, no need to go further *) (* triggers "event downgrade()" in the client, no need to go further *)
(* bad only if both support bad *) (* bad only if both support bad *)
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then if client_support_enc = alg_bad && (supported_enc = alg_bad || supported_enc = alg_both) then
( (
out (cf_full, (s_nonce, alg_bad, supported_auth, sign((s_nonce, fin1, client_alg, alg_bad, supported_auth), privF), out (cf_full, (s_nonce, alg_bad, supported_auth, sign((c_n1, client_support_enc, s_nonce, alg_bad, supported_auth), privF),
sign((s_nonce, alg_bad, supported_auth), privF))) sign((s_nonce, alg_bad), privF)))
) )
else else
( (
if client_alg = alg_good || client_alg = alg_both then if client_support_enc = alg_good || client_support_enc = alg_both then
out (cf_full, (s_nonce, alg_good, supported_auth, sign((s_nonce, fin1, client_alg, supported_auth, alg_good), privF), out (cf_full, (s_nonce, alg_good, supported_auth, sign((c_n1, client_support_enc, s_nonce, alg_good, supported_auth), privF),
sign((s_nonce, alg_good, supported_auth), privF))); sign((s_nonce, alg_good), privF)));
(* RTT 1 finished *) (* RTT 1 finished *)
in (cf_full, (=s_nonce, =alg_good, signed: bitstring, fin2: nonce, pubC: pubKey)); in (cf_full, (=s_nonce, =alg_good, signed: bitstring, c_n2: nonce, pubC: pubKey));
if ver((s_nonce, alg_good), signed, genPub(privF)) = true then if ver((s_nonce, alg_good), signed, genPub(privF)) = true then
new prF2: privKey; new prF2: privKey;
let (cfKey) = exchange(prF2, pubC) in let (cfKey) = exchange(prF2, pubC) in
out (cf_full, (genPub(prF2), sign((genPub(prF2), fin2), privF))); out (cf_full, (genPub(prF2), sign((genPub(prF2), c_n2), privF)));
(* RTT 2 finished, now we have a state *) (* RTT 2 finished, now we have a state *)
in (cf_full, (ce1: bitstring)); in (cf_full, (ce1: bitstring));
let (data: nonce, data2: nonce) = sDec(ce1, cfKey) in let datastring :bitstring = sDec(ce1, cfKey) in
if data = Auth && data2 = Auth then if toNonce(datastring) = Auth then
new user_info : nonce; new user_info : nonce;
out (sf, user_info); out (sf, user_info);
@ -210,9 +218,7 @@ let service(OTP : sKey) =
(* sf channel is secret, thus trusted. do not encrypt to reduce (* sf channel is secret, thus trusted. do not encrypt to reduce
* proverif running time *) * proverif running time *)
in (sf, rnd : nonce); (* user info. useless in our model *) in (sf, rnd : nonce); (* user info. useless in our model *)
(* this should be cskey XOR hash(OTP), but proverif can't handle XOR. out (sf, xor(csKey, hash(OTP)));
* so we treat it as encryption, as it has the same properties in our case *)
out (sf, sEnc(fromKey(csKey), hash(OTP)));
in (cs, enc2 : bitstring); in (cs, enc2 : bitstring);
let is_OTP : bitstring = sDec(enc2, csKey) in let is_OTP : bitstring = sDec(enc2, csKey) in
if OTP = toKey(is_OTP) then if OTP = toKey(is_OTP) then
@ -223,8 +229,7 @@ let service(OTP : sKey) =
* Client Definition, Stateful handshake, with good and broken algorithms * Client Definition, Stateful handshake, with good and broken algorithms
* ====================================================================== * ======================================================================
*) *)
let client_state (pubF: pubKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm, OTP: sKey) =
let client_state (pubF: pubKey, Auth: nonce, supported: algorithm, supported_auth: algorithm, OTP: sKey) =
new cn1: nonce; (* Client Nonce 1 *) new cn1: nonce; (* Client Nonce 1 *)
(* Note: this nonce is technically useless, but prevents amplification/DoS attack (* Note: this nonce is technically useless, but prevents amplification/DoS attack
@ -232,65 +237,67 @@ let client_state (pubF: pubKey, Auth: nonce, supported: algorithm, supported_aut
* and doesn't play nicely with timers, so we don't really care... * and doesn't play nicely with timers, so we don't really care...
*) *)
(* start cookie exchange *) (* start cookie exchange *)
out (cf_state, (cn1, supported)); out (cf_state, (cn1, supported_enc));
in (cf_state, (alg: algorithm, srv_auth: algorithm, ephF: pubKey, s1: bitstring)); in (cf_state, (selected_alg: algorithm, srv_supported_auth: algorithm, ephF: pubKey, s1: bitstring));
if ver((cn1, supported, alg, srv_auth, ephF), s1, pubF) = true then if ver((cn1, supported_enc, selected_alg, srv_supported_auth, ephF), s1, pubF) = true then
(* RTT 1 completed *) (* RTT 1 completed *)
if alg = alg_bad then if selected_alg = alg_bad then
( (
if supported = alg_bad || supported = alg_both then if supported_enc = alg_bad || supported_enc = alg_both then
(* bad algorithm exposes authentication data *) (* bad algorithm exposes authentication data *)
event downgrade() event downgrade()
) )
else else
( (
if alg = alg_good && (supported = alg_good || supported = alg_both) then if selected_alg = alg_good && (supported_enc = alg_good || supported_enc = alg_both) then
if srv_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then if srv_supported_auth = alg_bad && (supported_auth = alg_both || supported_auth = alg_bad) then
( (
event downgrade() event downgrade()
) )
else else
( (
if (srv_auth = alg_both || srv_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then if (srv_supported_auth = alg_both || srv_supported_auth = alg_good) && (supported_auth = alg_both || supported_auth = alg_good) then
let (cfKey) = exchange(prC, ephF) in let (cfKey) = exchange(prC, ephF) in
out (cf_state, (genPub(prC), sEnc((Auth,Auth), cfKey))); out (cf_state, (genPub(prC), sEnc(fromNonce(Auth), cfKey)));
in (cf_state, enc: bitstring); in (cf_state, enc: bitstring);
(* RTT 2 completed, can now connect to service *) (* RTT 2 completed, can now connect to service *)
let (chan: channel, xorKey: bitstring) = sDec(enc, cfKey) in let (chan: channel, xorKey: sKey) = sDec(enc, cfKey) in
let cs_Key = sDec(xorKey, hash(OTP)) in let cs_Key = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, toKey(cs_Key)); event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), toKey(cs_Key))) out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_state()
) )
). ).
(* (*
* =========================================================================== * ===========================================================================
* Fenrir definition, stateful handshake, with good and broken algorithms * Fenrir definition, stateful handshake, with good and broken algorithms
* =========================================================================== * ===========================================================================
*) *)
let fenrir_state (privF: privKey, Auth: nonce, supported_enc: algorithm, supported_auth: algorithm) =
let fenrir_state (privF: privKey, Auth: nonce, supported: algorithm, supported_auth: algorithm) =
(* Nonce exchange *) (* Nonce exchange *)
new prF2 : privKey; new prF2 : privKey;
new s_nonce: nonce; new s_nonce: nonce;
in (cf_state, (fin1: nonce, client_alg: algorithm)); in (cf_state, (c_n1: nonce, client_supported_enc: algorithm));
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then if client_supported_enc = alg_bad && (supported_enc = alg_bad || supported_enc = alg_both) then
( (
out(cf_state, (alg_bad, supported_auth, genPub(prF2), sign((fin1, client_alg, alg_bad, supported_auth, genPub(prF2)), privF))) out(cf_state, (alg_bad, supported_auth, genPub(prF2), sign((c_n1, client_supported_enc, alg_bad, supported_auth, genPub(prF2)), privF)))
) )
else else
( (
if (client_alg = alg_good || client_alg = alg_both) && (supported = alg_good || supported = alg_both) then if (client_supported_enc = alg_good || client_supported_enc = alg_both) && (supported_enc = alg_good || supported_enc = alg_both) then
out(cf_state, (alg_good, supported_auth, genPub(prF2), sign((fin1, client_alg, alg_good, supported_auth, genPub(prF2)), privF))); out(cf_state, (alg_good, supported_auth, genPub(prF2), sign((c_n1, client_supported_enc, alg_good, supported_auth, genPub(prF2)), privF)));
(* RTT 1 finished *) (* RTT 1 finished *)
in (cf_state, (pubC: pubKey, enc: bitstring)); in (cf_state, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(prF2, pubC) in let (cfKey) = exchange(prF2, pubC) in
let (data: nonce, data2: nonce) = sDec(enc, cfKey) in let datastring : bitstring = sDec(enc, cfKey) in
if data = Auth && data2 = Auth then if toNonce(datastring) = Auth then
new user_info : nonce; new user_info : nonce;
out (sf, (user_info)); out (sf, (user_info));
in (sf, key : sKey); in (sf, key : sKey);
@ -300,27 +307,32 @@ let fenrir_state (privF: privKey, Auth: nonce, supported: algorithm, supported_a
). ).
(* (*
* =========================================================================== * ===========================================================================
* Client definition, directory synchronized handshake * Client definition, directory synchronized handshake
* =========================================================================== * ===========================================================================
*) *)
let client_dir (pubF: pubKey, Auth: nonce, OTP: sKey) = let client_dir (pubF: pubKey, Auth: nonce, OTP: sKey) =
new cn : nonce; new cn : nonce;
let (cfKey) = exchange(prC, pubF) in let (cfKey) = exchange(prC, pubF) in
out (cf_dir, (genPub(prC), sEnc((Auth,cn), cfKey))); out (cf_dir, (genPub(prC), sEnc((Auth,cn), cfKey)));
in (cf_dir, enc: bitstring); in (cf_dir, enc: bitstring);
(* RTT 1 finished, now connect to service *) (* RTT 1 finished, now connect to service *)
let (=cn, chan: channel, xorKey: bitstring) = sDec(enc, cfKey) in let (=cn, chan: channel, xorKey: sKey) = sDec(enc, cfKey) in
let cs_Key = sDec(xorKey, hash(OTP)) in let cs_Key = unxor(xorKey, hash(OTP)) in
event clientConnect(cfKey, toKey(cs_Key)); event clientConnect(cfKey, cs_Key);
out (chan, sEnc(fromKey(OTP), toKey(cs_Key))). out (chan, sEnc(fromKey(OTP), cs_Key));
event connection_dir().
(* (*
* =========================================================================== * ===========================================================================
* Fenrir definition, directory synchronized handshake * Fenrir definition, directory synchronized handshake
* =========================================================================== * ===========================================================================
*) *)
let fenrir_dir (privF: privKey, Auth: nonce) = let fenrir_dir (privF: privKey, Auth: nonce) =
in (cf_dir, (pubC: pubKey, enc: bitstring)); in (cf_dir, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(privF, pubC) in let (cfKey) = exchange(privF, pubC) in
@ -334,9 +346,6 @@ let fenrir_dir (privF: privKey, Auth: nonce) =
(* (*
* ============== * ==============
* Event correlation: * Event correlation:
@ -355,6 +364,14 @@ query k1 : sKey, k2 : sKey; event(clientConnect(k1, k2)) ==> event(acceptedFenri
query event(downgrade()). query event(downgrade()).
query attacker(OTP). (* client-service OTP data. *) query attacker(OTP). (* client-service OTP data. *)
query attacker(Auth). (* client auth data. *) query attacker(Auth). (* client auth data. *)
(*
* Event connection() HAS to be triggered
* we use it as a proof that we didn't just make a protocol that will never compete a handshake.
*)
query event(connection_full()).
query event(connection_state()).
query event(connection_dir()).
(* (*
* ============== * ==============
@ -367,17 +384,23 @@ process
let pbF = genPub(prF) in let pbF = genPub(prF) in
let pbF_dir = genPub(prF_dir) in let pbF_dir = genPub(prF_dir) in
( (
(* DO NOT PUT ALL THE TESTS TOGETHER OR PROVERIF WILL HAVE WAY TOO MUCH TO TEST!!
* THE NEXT TESTS ARE COMPLEMENTARY AND INDEPENDENT. TRY ONE SECTION AT A TIME.
*)
(* Perfect forward secrecy test. Note that the _dir methods work with non-leaked keys, as there (* Perfect forward secrecy test. Note that the _dir methods work with non-leaked keys, as there
* is no master key * is no master key
*) *
(* these tests alone needs about 26k to 46k rules in proverif * these tests alone needs about 36k to 56k rules in proverif
* phase 0: both parts support good algorithms. * test 0: both parts support GOOD AND BAD algorithms.
* phase 1: client supports only bad algorithms * test 1: some clients are "BAD_ONLY"
* phase 2: auth-server supports only bad algorithms * test 2: some auth servers are "BAD_ONLY"
* phase 3: leak long term secret to test perfect forward secrecy, * test 3: leak long term secret to test perfect forward secrecy,
*) *)
(* (*
*) *)
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_good, alg_both, OTP)) | (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_good, alg_both, OTP))
| (!client_full (pbF, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF, Auth, alg_good, alg_good, OTP)) | (!client_full (pbF, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF, Auth, alg_good, alg_good, OTP))
@ -387,7 +410,8 @@ process
| (!client_state(pbF, Auth, alg_both, alg_good, OTP)) | (!client_state(pbF, Auth, alg_good, alg_good, OTP)) | (!client_state(pbF, Auth, alg_both, alg_good, OTP)) | (!client_state(pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_good, alg_both)) | (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_good, alg_both))
| (!fenrir_state(prF, Auth, alg_both, alg_good)) | (!fenrir_state(prF, Auth, alg_good, alg_good)) | (!fenrir_state(prF, Auth, alg_both, alg_good)) | (!fenrir_state(prF, Auth, alg_good, alg_good))
| phase 1; *)
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_both, OTP)) | (!client_full (pbF, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_both, OTP))
| (!client_full (pbF, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_bad, OTP)) | (!client_full (pbF, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF, Auth, alg_bad, alg_bad, OTP))
@ -395,7 +419,9 @@ process
| (!client_state(pbF, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_both, OTP)) | (!client_state(pbF, Auth, alg_both, alg_both, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_both, OTP))
| (!client_state(pbF, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_bad, OTP)) | (!client_state(pbF, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_state(prF, Auth, alg_good, alg_good)) | (!fenrir_state(prF, Auth, alg_good, alg_good))
| phase 2; *)
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF, Auth, alg_good, alg_good, OTP)) | (!client_full (pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF, Auth, alg_both, alg_both)) | (!fenrir_full (prF, Auth, alg_bad, alg_both)) | (!fenrir_full (prF, Auth, alg_both, alg_both)) | (!fenrir_full (prF, Auth, alg_bad, alg_both))
@ -403,7 +429,12 @@ process
| (!client_state(pbF, Auth, alg_good, alg_good, OTP)) | (!client_state(pbF, Auth, alg_good, alg_good, OTP))
| (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_bad, alg_both)) | (!fenrir_state(prF, Auth, alg_both, alg_both)) | (!fenrir_state(prF, Auth, alg_bad, alg_both))
| (!fenrir_state(prF, Auth, alg_both, alg_bad)) | (!fenrir_state(prF, Auth, alg_bad, alg_bad)) | (!fenrir_state(prF, Auth, alg_both, alg_bad)) | (!fenrir_state(prF, Auth, alg_bad, alg_bad))
| phase 3; out(leak, prF) *)
(* LEAK PFS *)
(*
| phase 1; out(leak, prF);
*)
(* (*
*) *)
(* PFS: leaking of the long-term secret will not affect previous communications *) (* PFS: leaking of the long-term secret will not affect previous communications *)
@ -414,12 +445,12 @@ process
*) *)
(* these next tests alone needs about 37k to 55k rules in proverif (* these next tests alone needs about 37k to 55k rules in proverif
* the previous and the next tests together are way too much, as proverif seems to have a huge slow down for every new phase, * the previous and the next tests together are way too much, as proverif seems to have a huge slow down for every new phase,
* even when empty. Therefore activate either the previous or the next tests, they are independent anyway. * even when empty. Therefore activate one section at a time, they are independent anyway.
* pahse 0-2: like previous phase 0-2, except we add the directory synchronized handshake * like previous tests 0-2, except we add the directory synchronized handshake
* and do NOT leak the key at the end. This way we can test the interaction * and do NOT leak the key at the end. This way we can test the interaction
* of different handshakes with the same public key. * of different handshakes with the same public key.
*) *)
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_both, OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP)) | (!client_full (pbF_dir, Auth, alg_both, alg_good, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP))
@ -430,7 +461,8 @@ process
| (!fenrir_state(prF_dir, Auth, alg_both, alg_both)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_both)) | (!fenrir_state(prF_dir, Auth, alg_both, alg_both)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_both))
| (!fenrir_state(prF_dir, Auth, alg_both, alg_good)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_good)) | (!fenrir_state(prF_dir, Auth, alg_both, alg_good)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_good))
| (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth)) | (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth))
| phase 1;
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_both, alg_both, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_both, OTP))
| (!client_full (pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_bad, OTP)) | (!client_full (pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_full (pbF_dir, Auth, alg_bad, alg_bad, OTP))
@ -439,7 +471,8 @@ process
| (!client_state(pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF_dir, Auth, alg_bad, alg_bad, OTP)) | (!client_state(pbF_dir, Auth, alg_both, alg_bad, OTP)) | (!client_state(pbF_dir, Auth, alg_bad, alg_bad, OTP))
| (!fenrir_state(prF_dir, Auth, alg_good, alg_good)) | (!fenrir_state(prF_dir, Auth, alg_good, alg_good))
| (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth)) | (!client_dir (pbF_dir, Auth, OTP)) | (!fenrir_dir (prF_dir, Auth))
| phase 2; *)
(*
(!service(OTP)) (!service(OTP))
| (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP)) | (!client_full (pbF_dir, Auth, alg_good, alg_good, OTP))
| (!fenrir_full (prF_dir, Auth, alg_both, alg_both)) | (!fenrir_full (prF_dir, Auth, alg_bad, alg_both)) | (!fenrir_full (prF_dir, Auth, alg_both, alg_both)) | (!fenrir_full (prF_dir, Auth, alg_bad, alg_both))

View File

@ -1,378 +0,0 @@
(*
* Formal definition to verify the Fenrir protocol.
* to be used with ProVerif
*
* This is to verify the correctness of the full 3-RTT handshake.
* DNSSEC is NOT verified, as it is assumed to be correct.
*
* The test *WILL* take a while... proverif says it has inserted more than 23k rules, and has a rule base of more than 12k rules.
* just leave it to work...
*)
(*
* ==============
* Type definition
* ==============
*)
type host.
type sKey. (* symmetric key *)
type pubKey. (* public key for asymmetric key encryption *)
type privKey. (* private key for asymmetric key encryption *)
type nonce. (* just a nonce *)
const leak: channel. (* to leak private material *)
const cf_full: channel. (* client-fenrir, full-security handshake *)
const cf_state: channel. (* client-fenrir, stateful handshake *)
const cf_dir: channel. (* client-fenrir, directory-synchronized handshake *)
const cs: channel. (* client-service *)
const sf: channel [private]. (* service-fenrir: since it is managed as the client-fenrir channel, this is not checked *)
const C, F, S: host. (* Client, Fenrir, Service *)
(* various stuff that should remain private *)
const leakCheck: bitstring [private].
const Auth: nonce [private].
const prF: privKey [private].
const prF_dir: privKey [private].
free fsKey: sKey [private].
free Rekey: bitstring. (* rekey message *)
type algorithm.
const alg_bad: algorithm. (* broken key exchange algorithm *)
const alg_good: algorithm. (* good key exchange algorithm *)
const alg_both: algorithm. (* both good and bad algorithms support*)
(*
* ==============
* Function definition
* ==============
*)
(* symmetric key encription *)
fun sEnc(bitstring, sKey): bitstring. (* encrypt with symmetric key *)
reduc forall c: bitstring, k: sKey; sDec(sEnc(c,k),k) = c. (* decrypt with symmetric key *)
(* public key definition *)
fun genPub(privKey): pubKey. (* public keys are generated from private ones *)
fun aEnc(bitstring, pubKey): bitstring. (* encrypt with asymmetric public key *)
reduc forall c: bitstring, k: privKey; aDec(aEnc(c,genPub(k)),k) = c. (* decrypt with private key *)
(* public key signing *)
fun sign(bitstring, privKey): bitstring.
reduc forall c: bitstring, k: privKey; ver(c, sign(c, k), genPub(k)) = true. (* verify signature *)
(* some key exchange based on public keys *)
fun exchange(privKey, pubKey) : sKey.
equation forall p1 : privKey, p2 : privKey; exchange(p1, genPub(p2)) = exchange(p2, genPub(p1)).
(*
* ==============
* Events -- used for correlation
* ==============
*)
event downgrade().
event acceptedClient(host, sKey).
event acceptedFenrir(host, sKey).
event acceptedClientState(host, sKey).
event acceptedFenrirState(host, sKey).
event clientConnect(host, host, sKey).
event serviceConnect(host, host, sKey).
(* Deactivated because the complexity explodes...
let client_rekey (key: sKey, chan: channel) =
new rekey_p1: privKey;
new rekey_n1: nonce;
out (chan, sEnc((rekey_n1, genPub(rekey_p1)), key));
in (chan, enc: bitstring);
let (=rekey_n1, srv_eph : pubKey) = sDec(enc, key) in
let (new_Key) = exchange(rekey_p1, srv_eph) in
out (chan, sEnc((Auth, Auth), new_Key))
.
let server_rekey (key: sKey, chan: channel) =
new rekey_s_p: privKey;
in (chan, enc: bitstring);
let (client_n1: nonce, client_p: pubKey) = sDec (enc, key) in
out (chan, sEnc((client_n1, genPub(rekey_s_p)), key));
let (new_Key) = exchange(rekey_s_p, client_p) in
in (chan, enc2: bitstring);
let (=Auth, =Auth) = sDec(enc2, new_Key)
.
*)
(*
* ==========================================================================
* Client definition, Full-security handshake with good and broken algorithms
* ==========================================================================
*)
let client_full(pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce, supported: algorithm) =
new cn1: nonce; (* Client Nonce 1 *)
new prC: privKey;
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_full, (client, cn1));
in (cf_full, (sn1: nonce , s1: bitstring));
if ver((sn1, cn1), s1, pubF) = true then
(* RTT 1 completed *)
(* start algorithm agreement *)
new cn2: nonce;
(* out: nonces and signature, supported_algorithms *)
out (cf_full, (cn2, cn1, sn1, s1, supported));
in (cf_full, (selected: algorithm, ephF : pubKey, s2 : bitstring));
if ver((supported, selected, ephF, cn2), s2, pubF) then
if selected = alg_bad && (supported = alg_both || supported = alg_bad) then
(
(* bad algorithm somehow exposes authentication data *)
event downgrade()
)
else
(
if selected = alg_good then
(* RTT 2 completed *)
let (cfKey) = exchange(prC, ephF) in
out (cf_full, (genPub(prC), sEnc((Auth,Auth), cfKey)));
in (cf_full, enc: bitstring);
let (chan : channel, csKey : sKey) = sDec(enc, cfKey) in
event acceptedClient(client, cfKey);
(* RTT 3 completed, can now connect to service *)
(* also send private data to test secrecy *)
event clientConnect(service, client, csKey);
out (chan, sEnc((Auth, Auth), csKey))
).
(* now rekey *)
(*
* ===========================================================================
* Fenrir definition, full security handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_full(privF: privKey, fenrir: host, service: host, Auth: nonce, supported: algorithm) =
(*
* NOTE: we do not model the service <-> fenrir authentication, as that is just the same
* as the fenrir <-> client full security authentication. We assume fenrir and service already share a key
*)
(* Nonce exchange *)
in (cf_full, (client: host, fin1: nonce));
new s_nonce: nonce;
out(cf_full, (s_nonce, sign((s_nonce, fin1), privF)));
(* RTT 1 finished *)
in (cf_full, (fin2: nonce, fin1_1: nonce, srv_n: nonce, signature: bitstring, client_alg: algorithm));
if ver((srv_n, fin1_1), signature, genPub(privF)) = true then
new prF2 : privKey;
new csKey: sKey;
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then
( (* bad only if both support bad *)
out (cf_full, (alg_bad, genPub(prF2), sign((client_alg, alg_bad, genPub(prF2), fin2), privF)))
)
else
(
if (client_alg = alg_good || client_alg = alg_both) && (supported = alg_good || supported = alg_both) then
out (cf_full, (alg_good, genPub(prF2), sign((client_alg, alg_good, genPub(prF2), fin2), privF)));
(* RTT 2 finished *)
in (cf_full, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(prF2, pubC) in
let (data: nonce, data2: nonce) = sDec(enc, cfKey) in
if data = Auth && data2 = Auth then
out (sf, (client, csKey)); (* inform service of new client *)
event acceptedFenrir(client, cfKey);
out (cf_full, sEnc((cs, csKey), cfKey))
(* RTT 3 finished *)
).
(*
* ==================================
* Service definition, common for all
* ==================================
*)
let service(service: host) =
in (sf, (cli: host, csKey:sKey));
in (cs, enc2 : bitstring);
let (Auth1 : nonce, Auth2 : nonce) = sDec(enc2, csKey) in
event serviceConnect(service, cli, csKey).
(*
* ======================================================================
* Client Definition, Stateful handshake, with good and broken algorithms
* ======================================================================
*)
let client_state (pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce, supported: algorithm) =
new cn1: nonce; (* Client Nonce 1 *)
new prC: privKey;
(* Note: this nonce is technically useless, but prevents amplification/DoS attack
* also: the nonce should include a timer, but proverif doesn't check for DoS,
* and doesn't play nicely with timers, so we don't really care...
*)
(* start cookie exchange *)
out (cf_state, (cn1, client, supported));
in (cf_state, (alg: algorithm, ephF: pubKey, s1: bitstring));
if ver((cn1, client, supported, alg, ephF), s1, pubF) = true then
(* RTT 1 completed *)
if alg = alg_bad then
(
if supported = alg_bad || supported = alg_both then
(* bad algorithm exposes authentication data *)
event downgrade()
)
else
(
if alg = alg_good then
if supported = alg_good || supported = alg_both then
let (cfKey) = exchange(prC, ephF) in
out (cf_state, (genPub(prC), sEnc((Auth,Auth), cfKey)));
in (cf_state, enc: bitstring);
let (chan : channel, csKey : sKey) = sDec(enc, cfKey) in
event acceptedClientState(client, csKey);
(* RTT 2 completed, can now connect to service *)
(* also send private data to test secrecy *)
event clientConnect(service, client, csKey);
out (chan, sEnc((Auth, Auth), csKey))
).
(*
* ===========================================================================
* Fenrir definition, stateful handshake, with good and broken algorithms
* ===========================================================================
*)
let fenrir_state (privF: privKey, fenrir: host, service: host, Auth: nonce, supported: algorithm) =
(*
* NOTE: we do not model the service <-> fenrir authentication, as that is just the same
* as the fenrir <-> client full-securoty authentication. We assume fenrir and service already share a key
*)
(* Nonce exchange *)
new prF2 : privKey;
new s_nonce: nonce;
in (cf_state, (fin1: nonce, client: host, client_alg: algorithm));
if client_alg = alg_bad && (supported = alg_bad || supported = alg_both) then
(
out(cf_state, (alg_bad, genPub(prF2), sign((fin1, client, client_alg, alg_bad, genPub(prF2)), privF)))
)
else
(
if (client_alg = alg_good || client_alg = alg_both) && (supported = alg_good || supported = alg_both) then
out(cf_state, (alg_good, genPub(prF2), sign((fin1, client, client_alg, alg_good, genPub(prF2)), privF)));
(* RTT 1 finished *)
new csKey: sKey;
in (cf_state, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(prF2, pubC) in
let (data: nonce, data2: nonce) = sDec(enc, cfKey) in
if data = Auth && data2 = Auth then
out (sf, (client, csKey)); (* inform service of new client *)
event acceptedFenrirState(client, csKey);
out (cf_state, sEnc((cs, csKey), cfKey))
(* RTT 2 finished *)
).
(*
* ===========================================================================
* Client definition, directory synchronized handshake
* ===========================================================================
*)
let client_dir (pubF: pubKey, client: host, service: host, fenrir: host, Auth: nonce) =
new prC : privKey;
new cn : nonce;
let (cfKey) = exchange(prC, pubF) in
out (cf_dir, (genPub(prC), sEnc((client, Auth,cn), cfKey)));
in (cf_dir, enc: bitstring);
let (=cn, chan: channel, csKey: sKey) = sDec(enc, cfKey) in
(* RTT 1 finished, now connect to service *)
out (chan, sEnc((Auth, Auth), csKey)).
(*
* ===========================================================================
* Fenrir definition, directory synchronized handshake
* ===========================================================================
*)
let fenrir_dir (privF: privKey, fenrir: host, service: host, Auth: nonce) =
in (cf_dir, (pubC: pubKey, enc: bitstring));
let (cfKey) = exchange(privF, pubC) in
let (cname: host, =Auth, cn: nonce) = sDec (enc, cfKey) in
new csKey : sKey;
out (sf, sEnc((cname, csKey), fsKey)); (* inform service of new client *)
out (cf_dir, sEnc((cn, cs, csKey), cfKey)).
(*
* Event correlation:
*)
(* query h: host, k: sKey; event(acceptedFenrir(h, k)) ==> event(acceptedClient(h, k)). *)
(* *)
query h: host, k: sKey; event(acceptedClientState(h, k)) ==> event(acceptedFenrirState(h, k)).
query h1: host, h2 : host, k: sKey, t: nonce; event(serviceConnect(h1, h2, k)) ==> event(clientConnect(h1, h2, k)).
(*
* ==============
* What should be secret?
* ==============
*)
(*query attacker(prF). do NOT check this as we volountarly leak it after the process *)
(*query attacker(prF_dir).*) (* the private key in the directory-synchronized case must be secret *)
query attacker(Auth). (* client auth data. *)
query event(downgrade()).
(*
* ==============
* Run the verification
* ==============
*)
(* make it easy to deactivate single parts, like the 2-RTT login, which has unproven events problems *)
(* TODO: fix PFS: must leak both server and client public key *)
process
let pbF = genPub(prF) in
let pbF_dir = genPub(prF_dir) in
( (!service(S))
| (!client_full (pbF, C, S, F, Auth, alg_both)) | (!client_full (pbF, C, S, F, Auth, alg_good))
| (!fenrir_full (prF, F, S, Auth, alg_both)) | (!fenrir_full (prF, F, S, Auth, alg_good))
| (!client_state(pbF, C, S, F, Auth, alg_both)) | (!client_state(pbF, C, S, F, Auth, alg_good))
| (!fenrir_state(prF, F, S, Auth, alg_both)) | (!fenrir_state(prF, F, S, Auth, alg_good))
| (!client_dir (pbF_dir, C, S, F, Auth)) | (!fenrir_dir (prF, F, S, Auth))
| phase 1; out(leak, prF) (* PFS: leaking of the long-term secret will NOT affect previous communications *)
(* Now do everything again, WITHOUT leaking. Everything must be done again since
* we leaked the previous private key. If we didn't do everything again we could not prove
* that the interaction of the handshakes is secure.
* This is because the 1-RTT handshake does NOT have long-term secret
*)
(* ; (!client_full (pbF, C, S, F, Auth, alg_both)) | (!client_full (pbF, C, S, F, Auth, alg_good))
| (!fenrir_full (prF, F, S, Auth, alg_both)) | (!fenrir_full (prF, F, S, Auth, alg_good))
| (!client_state(pbF, C, S, F, Auth, alg_both)) | (!client_state(pbF, C, S, F, Auth, alg_good))
| (!fenrir_state(prF, F, S, Auth, alg_both)) | (!fenrir_state(prF, F, S, Auth, alg_good))
| (!client_dir (pbF_dir, C, S, F, Auth)) | (!fenrir_dir (prF_dir, F, S, Auth)) *)
)

View File

@ -154,8 +154,8 @@ let client_otp(OTP: sKey, pbS: pubKey) =
event serviceConnect(toKey(xorKey)); event serviceConnect(toKey(xorKey));
out (cs_xor, sEnc(fromKey(OTP), toKey(xorKey))); out (cs_xor, sEnc(fromKey(OTP), toKey(xorKey)));
client_rekey(toKey(xorKey), cs_xor, pbS). client_rekey(toKey(xorKey), cs_xor, pbS).
(* (*
* Event correlation: * Event correlation:
*) *)

View File

@ -1 +1 @@
proverif -color -in pitype $1|GREP_COLOR='01;32' egrep --line-buffered --color=always -E "^|attacker\(.*\)"|GREP_COLOR='01;36' egrep -i --line-buffered --color=always -E "^|message \(.*\)"|GREP_COLOR='01;31' egrep --line-buffered --color=always -E "|^RESULT.*" time proverif -color -in pitype $1|GREP_COLOR='01;32' egrep --line-buffered --color=always -E "^|attacker\(.*\)"|GREP_COLOR='01;36' egrep -i --line-buffered --color=always -E "^|message \(.*\)"|GREP_COLOR='01;31' egrep --line-buffered --color=always -E "|^RESULT.*"