Commit f80c40ff authored by Stephane Glondu's avatar Stephane Glondu

Add a "verify-diff" command to belenios-tool

parent 80eabfc3
......@@ -434,7 +434,38 @@ module Mkelection : CMDLINER_MODULE = struct
end
let cmds = Tkeygen.cmds @ Election.cmds @ Credgen.cmds @ Mkelection.cmds
module Verifydiff : CMDLINER_MODULE = struct
open Tool_verifydiff
let main dir1 dir2 =
wrap_main (fun () ->
match dir1, dir2 with
| Some dir1, Some dir2 -> verifydiff dir1 dir2
| _, _ -> failcmd "--dir1 or --dir2 is missing"
)
let dir1_t =
let doc = "First directory to compare." in
Arg.(value & opt (some dir) None & info ["dir1"] ~docv:"DIR1" ~doc)
let dir2_t =
let doc = "Second directory to compare." in
Arg.(value & opt (some dir) None & info ["dir2"] ~docv:"DIR2" ~doc)
let verifydiff_cmd =
let doc = "verify an election directory update" in
let man = [
`S "DESCRIPTION";
`P "This command is run by an auditor on two directories $(i,DIR1) and $(i,DIR2). It checks that $(i,DIR2) is a valid update of $(i,DIR1).";
] @ common_man in
Term.(ret (pure main $ dir1_t $ dir2_t)),
Term.info "verify-diff" ~doc ~man
let cmds = [verifydiff_cmd]
end
let cmds = Tkeygen.cmds @ Election.cmds @ Credgen.cmds @ Mkelection.cmds @ Verifydiff.cmds
let default_cmd =
let version = Belenios_version.(Printf.sprintf "%s (%s)" version build) in
......
(**************************************************************************)
(* BELENIOS *)
(* *)
(* Copyright © 2012-2016 Inria *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU Affero General Public License as *)
(* published by the Free Software Foundation, either version 3 of the *)
(* License, or (at your option) any later version, with the additional *)
(* exemption that compiling, linking, and/or using OpenSSL is allowed. *)
(* *)
(* This program is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
(* Affero General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU Affero General Public *)
(* License along with this program. If not, see *)
(* <http://www.gnu.org/licenses/>. *)
(**************************************************************************)
open Signatures
open Serializable_j
let stream_to_list s =
let res = ref [] in
Stream.iter (fun x -> res := x :: !res) s;
List.rev !res
let lines_of_file fname =
let ic = open_in fname in
Stream.from (fun _ ->
try Some (input_line ic)
with End_of_file -> close_in ic; None
)
let string_of_file f =
lines_of_file f |> stream_to_list |> String.concat "\n"
let load_from_file of_string filename =
if Sys.file_exists filename then (
Some (lines_of_file filename |> stream_to_list |> List.rev_map of_string)
) else None
let ( / ) = Filename.concat
type verifydiff_error =
| ElectionMismatch
| PublicKeysMismatch
| MissingPublicKeys
| InvalidPublicKeys
| PublicKeyMismatch
| MissingCredentials
| InvalidCredential
| CredentialsMismatch
| MissingBallots
| InvalidBallot
| DuplicateBallot
| BallotSignedByInvalidKey
| DecreasingBallots
| BallotSignedByReplacedKey
exception VerifydiffError of verifydiff_error
let explain_error = function
| ElectionMismatch -> "election mismatch"
| PublicKeysMismatch -> "public keys mismatch"
| MissingPublicKeys -> "missing public keys"
| InvalidPublicKeys -> "invalid public keys"
| PublicKeyMismatch -> "public key mismatch"
| MissingCredentials -> "missing credentials"
| InvalidCredential -> "invalid credential"
| CredentialsMismatch -> "credentials mismatch"
| MissingBallots -> "missing ballots"
| InvalidBallot -> "invalid ballot"
| DuplicateBallot -> "duplicate ballot"
| BallotSignedByInvalidKey -> "ballot signed by invalid key"
| DecreasingBallots -> "decreasing ballots"
| BallotSignedByReplacedKey -> "ballot signed by replaced key"
let () =
Printexc.register_printer (function
| VerifydiffError e -> Some ("verify-diff error: " ^ explain_error e)
| _ -> None)
let verifydiff dir1 dir2 =
(* the elections must be the same *)
let election = string_of_file (dir1 / "election.json") in
let () =
let election2 = string_of_file (dir2 / "election.json") in
if election2 <> election then raise (VerifydiffError ElectionMismatch)
in
(* the public keys must be the same *)
let pks = load_from_file (fun x -> x) (dir1 / "public_keys.jsons") in
let () =
let pks2 = load_from_file (fun x -> x) (dir2 / "public_keys.jsons") in
if pks2 <> pks then raise (VerifydiffError PublicKeysMismatch)
in
(* the public keys must be valid *)
let module ED = (val Group.election_params_of_string election) in
let open ED in
let module M = Election.MakeSimpleMonad (G) in
let module E = Election.MakeElection (G) (M) in
let module KG = Election.MakeSimpleDistKeyGen (G) (M) in
let pks = match pks with
| None -> raise (VerifydiffError MissingPublicKeys)
| Some pks -> List.map (trustee_public_key_of_string G.read) pks
in
let () =
if not (List.for_all KG.check pks) then
raise (VerifydiffError InvalidPublicKeys)
in
(* the public keys must correspond to the public key of election *)
let y = KG.combine (Array.of_list pks) in
let () =
if not G.(election.e_params.e_public_key =~ y) then
raise (VerifydiffError PublicKeyMismatch)
in
(* load both public_creds.txt and check that their contents is valid *)
let module GSet = Set.Make (G) in
let creds dir =
match load_from_file G.of_string (dir / "public_creds.txt") with
| None -> raise (VerifydiffError MissingCredentials)
| Some creds ->
if not (List.for_all G.check creds) then
raise (VerifydiffError InvalidCredential);
List.fold_left (fun accu x -> GSet.add x accu) GSet.empty creds
in
let creds1 = creds dir1 and creds2 = creds dir2 in
(* both public_creds.txt have the same cardinal *)
let () =
if GSet.cardinal creds1 <> GSet.cardinal creds2 then
raise (VerifydiffError CredentialsMismatch)
in
(* compute credentials that have been replaced *)
let creds_replaced =
GSet.fold (fun x accu ->
if not (GSet.mem x creds2) then GSet.add x accu else accu
) creds1 GSet.empty
in
(* issue a warning when credentials have changed *)
let () =
if not (GSet.is_empty creds_replaced) then
Printf.eprintf "W: credentials have changed\n%!"
in
(* load both ballots.jsons and check that their contents is valid *)
let module GMap = Map.Make (G) in
let ballots dir =
match load_from_file (ballot_of_string G.read) (dir / "ballots.jsons") with
| None -> raise (VerifydiffError MissingBallots)
| Some ballots ->
if not (List.for_all (E.check_ballot election) ballots) then
raise (VerifydiffError InvalidBallot);
(* return the set of ballots indexed by the public keys used to sign *)
List.fold_left (fun accu x ->
match x.signature with
| None -> raise (VerifydiffError InvalidBallot)
| Some s -> if GMap.mem s.s_public_key accu then
raise (VerifydiffError DuplicateBallot)
else GMap.add s.s_public_key x accu
) GMap.empty ballots
in
let ballots1 = ballots dir1 and ballots2 = ballots dir2 in
(* each ballot is signed with a valid key *)
let check_keys ballots creds =
GMap.for_all (fun pk _ -> GSet.mem pk creds) ballots
in
let () =
if not (check_keys ballots1 creds1 && check_keys ballots2 creds2) then
raise (VerifydiffError BallotSignedByInvalidKey)
in
(* the set of ballots increases *)
let () =
if not (GMap.for_all (fun pk _ -> GMap.mem pk ballots2) ballots1) then
raise (VerifydiffError DecreasingBallots)
in
(* the keys of modified ballots have not been replaced *)
let () =
if not (GMap.for_all (fun pk ballot1 ->
let ballot2 = GMap.find pk ballots2 in
ballot1 = ballot2 || not (GSet.mem pk creds_replaced)
) ballots1)
then raise (VerifydiffError BallotSignedByReplacedKey)
in
Printf.eprintf "I: all tests passed!\n%!"
(**************************************************************************)
(* BELENIOS *)
(* *)
(* Copyright © 2012-2016 Inria *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU Affero General Public License as *)
(* published by the Free Software Foundation, either version 3 of the *)
(* License, or (at your option) any later version, with the additional *)
(* exemption that compiling, linking, and/or using OpenSSL is allowed. *)
(* *)
(* This program is distributed in the hope that it will be useful, but *)
(* WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *)
(* Affero General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU Affero General Public *)
(* License along with this program. If not, see *)
(* <http://www.gnu.org/licenses/>. *)
(**************************************************************************)
type verifydiff_error =
| ElectionMismatch
| PublicKeysMismatch
| MissingPublicKeys
| InvalidPublicKeys
| PublicKeyMismatch
| MissingCredentials
| InvalidCredential
| CredentialsMismatch
| MissingBallots
| InvalidBallot
| DuplicateBallot
| BallotSignedByInvalidKey
| DecreasingBallots
| BallotSignedByReplacedKey
exception VerifydiffError of verifydiff_error
val explain_error : verifydiff_error -> string
val verifydiff : string -> string -> unit
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment