this repo has no description
1open Swim.Crypto
2
3let valid_key = String.make 16 '\x00'
4
5let test_roundtrip_property random =
6 QCheck.Test.make ~count:500 ~name:"crypto roundtrip" Generators.arb_cstruct
7 (fun plaintext ->
8 match init_key valid_key with
9 | Error _ -> false
10 | Ok key -> (
11 let ciphertext = encrypt ~key ~random plaintext in
12 match decrypt ~key ciphertext with
13 | Ok decrypted -> Cstruct.equal plaintext decrypted
14 | Error _ -> false))
15
16let test_encrypt_increases_size random =
17 QCheck.Test.make ~count:100 ~name:"encrypt increases size by overhead"
18 Generators.arb_cstruct (fun plaintext ->
19 match init_key valid_key with
20 | Error _ -> false
21 | Ok key ->
22 let ciphertext = encrypt ~key ~random plaintext in
23 Cstruct.length ciphertext = Cstruct.length plaintext + overhead)
24
25let test_different_plaintexts_different_ciphertexts random =
26 QCheck.Test.make ~count:100
27 ~name:"different plaintexts produce different ciphertexts"
28 (QCheck.pair Generators.arb_cstruct Generators.arb_cstruct) (fun (p1, p2) ->
29 if Cstruct.equal p1 p2 then true
30 else
31 match init_key valid_key with
32 | Error _ -> false
33 | Ok key ->
34 let c1 = encrypt ~key ~random p1 in
35 let c2 = encrypt ~key ~random p2 in
36 not (Cstruct.equal c1 c2))
37
38let test_init_key_valid_length () =
39 match init_key (String.make 16 'a') with
40 | Ok _ -> ()
41 | Error _ -> Alcotest.fail "expected valid key"
42
43let test_init_key_15_bytes_rejected () =
44 match init_key (String.make 15 'a') with
45 | Error `Invalid_key_length -> ()
46 | _ -> Alcotest.fail "expected Invalid_key_length"
47
48let test_init_key_17_bytes_rejected () =
49 match init_key (String.make 17 'a') with
50 | Error `Invalid_key_length -> ()
51 | _ -> Alcotest.fail "expected Invalid_key_length"
52
53let test_init_key_empty_rejected () =
54 match init_key "" with
55 | Error `Invalid_key_length -> ()
56 | _ -> Alcotest.fail "expected Invalid_key_length"
57
58let test_tampered_ciphertext_fails random () =
59 match init_key valid_key with
60 | Error _ -> Alcotest.fail "key init failed"
61 | Ok key -> (
62 let plaintext = Cstruct.of_string "hello world" in
63 let ciphertext = encrypt ~key ~random plaintext in
64 let tampered = Cstruct.of_string (Cstruct.to_string ciphertext) in
65 let pos = Cstruct.length tampered - 1 in
66 Cstruct.set_uint8 tampered pos
67 ((Cstruct.get_uint8 tampered pos + 1) land 0xFF);
68 match decrypt ~key tampered with
69 | Error `Decryption_failed -> ()
70 | _ -> Alcotest.fail "expected Decryption_failed")
71
72let test_truncated_ciphertext_fails random () =
73 match init_key valid_key with
74 | Error _ -> Alcotest.fail "key init failed"
75 | Ok key -> (
76 let plaintext = Cstruct.of_string "hello world" in
77 let ciphertext = encrypt ~key ~random plaintext in
78 let truncated = Cstruct.sub ciphertext 0 (overhead - 1) in
79 match decrypt ~key truncated with
80 | Error `Too_short -> ()
81 | _ -> Alcotest.fail "expected Too_short")
82
83let test_wrong_key_fails random () =
84 match (init_key valid_key, init_key (String.make 16 '\xFF')) with
85 | Ok key1, Ok key2 -> (
86 let plaintext = Cstruct.of_string "secret message" in
87 let ciphertext = encrypt ~key:key1 ~random plaintext in
88 match decrypt ~key:key2 ciphertext with
89 | Error `Decryption_failed -> ()
90 | _ -> Alcotest.fail "expected Decryption_failed")
91 | _ -> Alcotest.fail "key init failed"
92
93let test_empty_plaintext random () =
94 match init_key valid_key with
95 | Error _ -> Alcotest.fail "key init failed"
96 | Ok key -> (
97 let plaintext = Cstruct.empty in
98 let ciphertext = encrypt ~key ~random plaintext in
99 Alcotest.(check int)
100 "ciphertext size" overhead
101 (Cstruct.length ciphertext);
102 match decrypt ~key ciphertext with
103 | Ok decrypted ->
104 Alcotest.(check int) "decrypted size" 0 (Cstruct.length decrypted)
105 | Error _ -> Alcotest.fail "decrypt failed")
106
107let test_nonce_uniqueness random () =
108 match init_key valid_key with
109 | Error _ -> Alcotest.fail "key init failed"
110 | Ok key ->
111 let plaintext = Cstruct.of_string "test" in
112 let c1 = encrypt ~key ~random plaintext in
113 let c2 = encrypt ~key ~random plaintext in
114 let nonce1 = Cstruct.sub c1 1 nonce_size in
115 let nonce2 = Cstruct.sub c2 1 nonce_size in
116 if Cstruct.equal nonce1 nonce2 then
117 Alcotest.fail "nonces should be different"
118 else ()
119
120let test_ciphertext_differs_from_plaintext random () =
121 match init_key valid_key with
122 | Error _ -> Alcotest.fail "key init failed"
123 | Ok key ->
124 let plaintext = Cstruct.of_string "hello world secret" in
125 let ciphertext = encrypt ~key ~random plaintext in
126 let ciphertext_body =
127 Cstruct.sub ciphertext (1 + nonce_size)
128 (Cstruct.length ciphertext - 1 - nonce_size)
129 in
130 if
131 Cstruct.equal plaintext
132 (Cstruct.sub ciphertext_body 0
133 (min (Cstruct.length plaintext) (Cstruct.length ciphertext_body)))
134 then Alcotest.fail "ciphertext should differ from plaintext"
135 else ()
136
137let () =
138 Eio_main.run @@ fun env ->
139 let random = Eio.Stdenv.secure_random env in
140 let qcheck_tests =
141 List.map QCheck_alcotest.to_alcotest
142 [
143 test_roundtrip_property random;
144 test_encrypt_increases_size random;
145 test_different_plaintexts_different_ciphertexts random;
146 ]
147 in
148 let unit_tests =
149 [
150 ("init_key_valid_length", `Quick, test_init_key_valid_length);
151 ("init_key_15_bytes_rejected", `Quick, test_init_key_15_bytes_rejected);
152 ("init_key_17_bytes_rejected", `Quick, test_init_key_17_bytes_rejected);
153 ("init_key_empty_rejected", `Quick, test_init_key_empty_rejected);
154 ( "tampered_ciphertext_fails",
155 `Quick,
156 test_tampered_ciphertext_fails random );
157 ( "truncated_ciphertext_fails",
158 `Quick,
159 test_truncated_ciphertext_fails random );
160 ("wrong_key_fails", `Quick, test_wrong_key_fails random);
161 ("empty_plaintext", `Quick, test_empty_plaintext random);
162 ("nonce_uniqueness", `Quick, test_nonce_uniqueness random);
163 ( "ciphertext_differs_from_plaintext",
164 `Quick,
165 test_ciphertext_differs_from_plaintext random );
166 ]
167 in
168 Alcotest.run "crypto" [ ("property", qcheck_tests); ("unit", unit_tests) ]